Files
RyanandClaude Sonnet 5.5 bf40b92c52
Test and deploy enrollment demo / test (push) Successful in 7s
Test and deploy enrollment demo / deploy (push) Successful in 1m12s
Add MQTT enrollment demo with Gitea deploy pipeline
Go backend plus C (libsodium) and C++ (OpenSSL) simulated boards enroll
through a Mosquitto 2.1 broker with Dynamic Security using X25519/HKDF/
Ed25519; a web view shows the run live. The Gitea workflow validates and
builds the images, redeploys the stack on the server with the viewer on
port 8099 and fails unless both boards pass all checks.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 21:25:14 +03:30

143 lines
4.5 KiB
Go

// Interop spike: X25519 + HKDF-SHA256 + Ed25519 with fixed inputs (Go stdlib only).
// Prints the same key=value lines as the C and C++ programs so the outputs can be diffed.
package main
import (
"bytes"
"crypto/ecdh"
"crypto/ed25519"
"crypto/hkdf"
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
)
const (
boardPriv = "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"
backendEph = "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f"
signingSeed = "404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f"
nonce = "a0a1a2a3a4a5a6a7a8a9aaabacadaeaf"
clientID = "e1c9f0a2b3d4c5b6a79881726354453f"
fn = "FN-000042"
sn = "SN000123"
)
func die(msg string, err error) {
fmt.Fprintf(os.Stderr, "FAIL: %s: %v\n", msg, err)
os.Exit(1)
}
func unhex(s string) []byte {
b, err := hex.DecodeString(s)
if err != nil {
die("hex", err)
}
return b
}
func x25519Pub(priv []byte) []byte {
k, err := ecdh.X25519().NewPrivateKey(priv)
if err != nil {
die("x25519 priv", err)
}
return k.PublicKey().Bytes()
}
func x25519Shared(priv, peer []byte) []byte {
k, err := ecdh.X25519().NewPrivateKey(priv)
if err != nil {
die("x25519 priv", err)
}
p, err := ecdh.X25519().NewPublicKey(peer)
if err != nil {
die("x25519 pub", err)
}
s, err := k.ECDH(p)
if err != nil {
die("x25519 derive", err)
}
return s
}
func derive(ikm, salt []byte, info string, n int) []byte {
out, err := hkdf.Key(sha256.New, ikm, salt, info, n)
if err != nil {
die("hkdf", err)
}
return out
}
func expect(name string, got []byte, want string) {
if hex.EncodeToString(got) != want {
fmt.Fprintf(os.Stderr, "FAIL: %s\n got %x\n want %s\n", name, got, want)
os.Exit(1)
}
}
func kat() {
// RFC 7748 section 6.1
alicePriv := unhex("77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a")
bobPriv := unhex("5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb")
alicePub := x25519Pub(alicePriv)
bobPub := x25519Pub(bobPriv)
expect("rfc7748 alice pub", alicePub, "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
expect("rfc7748 bob pub", bobPub, "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f")
expect("rfc7748 shared a", x25519Shared(alicePriv, bobPub), "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")
expect("rfc7748 shared b", x25519Shared(bobPriv, alicePub), "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742")
fmt.Println("kat.x25519=ok")
// RFC 5869 test case 1
ikm := bytes.Repeat([]byte{0x0b}, 22)
salt := unhex("000102030405060708090a0b0c")
info := string(unhex("f0f1f2f3f4f5f6f7f8f9"))
expect("rfc5869 tc1", derive(ikm, salt, info, 42),
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865")
fmt.Println("kat.hkdf=ok")
// RFC 8032 section 7.1, test 1 (empty message)
key := ed25519.NewKeyFromSeed(unhex("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"))
expect("rfc8032 pub", key.Public().(ed25519.PublicKey), "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a")
expect("rfc8032 sig", ed25519.Sign(key, []byte{}),
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b")
fmt.Println("kat.ed25519=ok")
}
func password(shared []byte, id string) string {
return hex.EncodeToString(derive(shared, unhex(nonce), "robot-market/mqtt-pw/v1|"+id, 32))
}
func main() {
kat()
b := unhex(boardPriv)
s := unhex(backendEph)
boardPub := x25519Pub(b)
backendPub := x25519Pub(s)
fmt.Printf("board_pub=%x\n", boardPub)
fmt.Printf("backend_eph_pub=%x\n", backendPub)
shared := x25519Shared(s, boardPub) // backend side
fmt.Printf("shared_secret=%x\n", shared)
if !bytes.Equal(shared, x25519Shared(b, backendPub)) { // board side
die("shared secret mismatch", fmt.Errorf("board and backend differ"))
}
fmt.Println("shared_secret_peer_match=ok")
fmt.Printf("password_fn=%s\n", password(shared, fn))
fmt.Printf("password_sn=%s\n", password(shared, sn))
// Backend signs S || B || nonce || clientID.
key := ed25519.NewKeyFromSeed(unhex(signingSeed))
pub := key.Public().(ed25519.PublicKey)
msg := append(append(append(append([]byte{}, backendPub...), boardPub...), unhex(nonce)...), []byte(clientID)...)
sig := ed25519.Sign(key, msg)
fmt.Printf("signing_pub=%x\n", []byte(pub))
fmt.Printf("signed_message=%x\n", msg)
fmt.Printf("signature=%x\n", sig)
if !ed25519.Verify(pub, msg, sig) {
die("signature verify", fmt.Errorf("invalid"))
}
fmt.Println("signature_verify=ok")
}