diff --git a/cmd/busvectors/main.go b/cmd/busvectors/main.go index ef34226e..0ba9636a 100644 --- a/cmd/busvectors/main.go +++ b/cmd/busvectors/main.go @@ -35,7 +35,9 @@ import ( cs "fn-registry/functions/cybersecurity" + "github.com/enmanuel/unibus/pkg/busauth" "github.com/enmanuel/unibus/pkg/frame" + "github.com/enmanuel/unibus/pkg/membership" "golang.org/x/crypto/chacha20poly1305" "golang.org/x/crypto/curve25519" ) @@ -58,12 +60,14 @@ var ( // is hex except the frame wire bytes, which are base64 (the frame is JSON, so the // TS side compares the exact UTF-8 bytes). type vectors struct { - Note string `json:"note"` - Endpoint endpointVector `json:"endpoint_id"` - Sign signVector `json:"sign"` - AEAD aeadVector `json:"aead"` - KeyBox keyboxVector `json:"keybox"` - Frame frameVector `json:"frame"` + Note string `json:"note"` + Endpoint endpointVector `json:"endpoint_id"` + Nkey nkeyVector `json:"nkey"` + Sign signVector `json:"sign"` + AEAD aeadVector `json:"aead"` + KeyBox keyboxVector `json:"keybox"` + Frame frameVector `json:"frame"` + CtrlReq controlReqVector `json:"control_request"` } type endpointVector struct { @@ -71,6 +75,22 @@ type endpointVector struct { EndpointID string `json:"endpoint_id"` // base64url(sha256(sign_pub)), unpadded } +type nkeyVector struct { + SignPubHex string `json:"sign_pub_hex"` + NkeyPublic string `json:"nkey_public"` // NATS user nkey ("U...") from the Ed25519 pubkey +} + +type controlReqVector struct { + Method string `json:"method"` + Path string `json:"path"` + Ts string `json:"ts"` + Nonce string `json:"nonce"` + BodyHex string `json:"body_hex"` // raw request body (empty for GET) + CanonicalHex string `json:"canonical_hex"` // bytes that get signed + SigHex string `json:"sig_hex"` // Ed25519 over canonical, by the signer below + SignPrivHex string `json:"sign_priv_hex"` +} + type signVector struct { SignPrivHex string `json:"sign_priv_hex"` SignPubHex string `json:"sign_pub_hex"` @@ -171,6 +191,24 @@ func run(out *os.File) error { return fmt.Errorf("marshal frame: %w", err) } + // NATS user nkey derived from the Ed25519 public key (the browser must produce + // the same "U..." string to authenticate on the data plane). + nkeyPub, err := busauth.NkeyPublicFromSignPub(signPub) + if err != nil { + return fmt.Errorf("nkey public: %w", err) + } + + // A signed control-plane request vector: the browser signs CanonicalRequest the + // same way to authenticate every HTTP call to membershipd. A POST with a body + // exercises the sha256(body) term. + const ctrlMethod = "POST" + const ctrlPath = "/rooms" + const ctrlTs = "1700000000" + const ctrlNonce = "Zm9vYmFyMTIzNDU2Nzg5MA==" + ctrlBody := []byte(`{"subject":"room.parity"}`) + canonical := membership.CanonicalRequest(ctrlMethod, ctrlPath, ctrlTs, ctrlNonce, ctrlBody) + ctrlSig := ed25519.Sign(signPriv, canonical) + v := vectors{ Note: "Deterministic cross-language vectors for the unibus protocol. Generated by " + "cmd/busvectors in the unibus repo; regenerate with `go run ./cmd/busvectors`. " + @@ -179,6 +217,10 @@ func run(out *os.File) error { SignPubHex: hex.EncodeToString(signPub), EndpointID: frame.EndpointID(signPub), }, + Nkey: nkeyVector{ + SignPubHex: hex.EncodeToString(signPub), + NkeyPublic: nkeyPub, + }, Sign: signVector{ SignPrivHex: hex.EncodeToString(signPriv), SignPubHex: hex.EncodeToString(signPub), @@ -210,6 +252,16 @@ func run(out *os.File) error { SigningB64: base64.StdEncoding.EncodeToString(f.SigningBytes()), SigHex: hex.EncodeToString(f.Sig), }, + CtrlReq: controlReqVector{ + Method: ctrlMethod, + Path: ctrlPath, + Ts: ctrlTs, + Nonce: ctrlNonce, + BodyHex: hex.EncodeToString(ctrlBody), + CanonicalHex: hex.EncodeToString(canonical), + SigHex: hex.EncodeToString(ctrlSig), + SignPrivHex: hex.EncodeToString(signPriv), + }, // kexPub is unused in a vector field today but derived above to validate the // scalar; reference it so the intent is documented. }