feat(busvectors): add nkey + signed control-request vectors
Extend the cross-language vectors with the NATS user nkey derived from the Ed25519 public key, and a signed control-plane request (CanonicalRequest + Ed25519 signature). These let the TypeScript busauth port verify it authenticates on both planes exactly like the Go client (issue uniweb/0001, Phase 1).
This commit is contained in:
+58
-6
@@ -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.
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user