// Package busauth bridges a unibus peer's Ed25519 identity to NATS nkey // authentication. A NATS nkey IS an Ed25519 keypair, so the bus reuses the // peer's existing signing identity for the data plane instead of minting new // key material — one identity authenticates both planes (HTTP request signatures // and NATS connections), keyed in the user allowlist by the same Ed25519 public // key. // // This is transport glue specific to NATS + unibus, not a general-purpose // registry primitive: it deliberately lives in the app to avoid pulling // github.com/nats-io/nkeys into the multi-domain registry module. The Ed25519 // signing/verification it relies on comes from the registry cybersecurity // package; this package never reimplements a primitive. package busauth import ( "crypto/ed25519" "encoding/hex" "fmt" "github.com/nats-io/nkeys" ) // ClientNkey derives, from a peer's Ed25519 private key, the NATS user nkey // public string ("U...") and a signature callback suitable for // nats.Nkey(pub, sign). The callback signs the server-presented nonce with the // same Ed25519 key, so the server can verify it and map it back to the bus user. // // signPriv must be a 64-byte Ed25519 private key (as produced by the registry's // GenerateIdentity). Its first 32 bytes are the seed nkeys needs. func ClientNkey(signPriv []byte) (pub string, sign func([]byte) ([]byte, error), err error) { if len(signPriv) != ed25519.PrivateKeySize { return "", nil, fmt.Errorf("busauth: signPriv must be %d bytes, got %d", ed25519.PrivateKeySize, len(signPriv)) } seed := ed25519.PrivateKey(signPriv).Seed() // 32-byte Ed25519 seed kp, err := nkeys.FromRawSeed(nkeys.PrefixByteUser, seed) if err != nil { return "", nil, fmt.Errorf("busauth: derive nkey from seed: %w", err) } pub, err = kp.PublicKey() if err != nil { return "", nil, fmt.Errorf("busauth: nkey public key: %w", err) } sign = func(nonce []byte) ([]byte, error) { return kp.Sign(nonce) } return pub, sign, nil } // NkeyPublicFromSignPub derives the NATS user nkey public string from a 32-byte // Ed25519 public key. It is the inverse view of the identity used by callers // that have only the public key (e.g. to display or pre-register an nkey). func NkeyPublicFromSignPub(signPub []byte) (string, error) { if len(signPub) != ed25519.PublicKeySize { return "", fmt.Errorf("busauth: signPub must be %d bytes, got %d", ed25519.PublicKeySize, len(signPub)) } pub, err := nkeys.Encode(nkeys.PrefixByteUser, signPub) if err != nil { return "", fmt.Errorf("busauth: encode nkey public: %w", err) } return string(pub), nil } // SignPubHexFromNkey decodes a NATS user nkey public string ("U...") back to the // lowercase hex of its 32-byte Ed25519 public key — the identity key used to // look a peer up in the bus user allowlist. The server calls this to map the // nkey a client presented to the users table. func SignPubHexFromNkey(nkeyPub string) (string, error) { raw, err := nkeys.Decode(nkeys.PrefixByteUser, []byte(nkeyPub)) if err != nil { return "", fmt.Errorf("busauth: decode nkey %q: %w", nkeyPub, err) } if len(raw) != ed25519.PublicKeySize { return "", fmt.Errorf("busauth: decoded nkey is %d bytes, want %d", len(raw), ed25519.PublicKeySize) } return hex.EncodeToString(raw), nil }