Files
unibus/pkg/busauth/nkey_test.go
T
egutierrez 413dd61041 feat(busauth): Ed25519<->NATS nkey conversion with round-trip test
A NATS nkey is an Ed25519 keypair, so the bus reuses each peer's signing
identity for the data plane instead of minting new key material. ClientNkey
derives the user nkey public string and a nonce-signing callback from the
peer's Ed25519 private key (its first 32 bytes are the nkey seed);
SignPubHexFromNkey maps a presented nkey back to the allowlist's hex key;
NkeyPublicFromSignPub is the public-only derivation.

This is NATS-specific transport glue kept in the app, not promoted to the
registry, to avoid pulling nats-io/nkeys into the multi-domain registry
module. The dedicated round-trip test runs first (spec requirement): it
proves the nkey signature equals the identity's raw Ed25519 signature and
that the nkey maps back to the identity's hex.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 12:37:46 +02:00

86 lines
2.7 KiB
Go

package busauth
import (
"bytes"
"crypto/ed25519"
"encoding/hex"
"testing"
cs "fn-registry/functions/cybersecurity"
"github.com/nats-io/nkeys"
)
// TestNkeyRoundTrip is the dedicated sign/verify round-trip the spec requires
// BEFORE the NATS server depends on this conversion. It proves three things end
// to end: (1) ClientNkey produces a signature callback whose output verifies
// under the derived nkey public key; (2) that signature is exactly the Ed25519
// signature of the same identity (the nkey is the same key, not a new one);
// (3) the nkey public string maps back to the identity's Ed25519 hex, which is
// the key the allowlist is indexed by.
func TestNkeyRoundTrip(t *testing.T) {
id, err := cs.GenerateIdentity()
if err != nil {
t.Fatalf("identity: %v", err)
}
pub, sign, err := ClientNkey(id.SignPriv)
if err != nil {
t.Fatalf("ClientNkey: %v", err)
}
// (1) The callback's signature over a server-style nonce verifies under the
// public nkey, exactly as the NATS server will verify it.
nonce := []byte("server-presented-nonce-1234567890")
sig, err := sign(nonce)
if err != nil {
t.Fatalf("sign: %v", err)
}
kpPub, err := nkeys.FromPublicKey(pub)
if err != nil {
t.Fatalf("FromPublicKey: %v", err)
}
if err := kpPub.Verify(nonce, sig); err != nil {
t.Fatalf("nkey verify failed: %v", err)
}
// (2) The signature is the very same bytes as a raw Ed25519 sign with the
// identity's private key — confirming no separate key material was minted.
want := ed25519.Sign(ed25519.PrivateKey(id.SignPriv), nonce)
if !bytes.Equal(sig, want) {
t.Fatalf("nkey signature differs from Ed25519 signature of the same identity")
}
// (3) The nkey public maps back to the identity's Ed25519 hex (allowlist key).
gotHex, err := SignPubHexFromNkey(pub)
if err != nil {
t.Fatalf("SignPubHexFromNkey: %v", err)
}
if gotHex != hex.EncodeToString(id.SignPub) {
t.Fatalf("nkey->hex mismatch: got %s want %s", gotHex, hex.EncodeToString(id.SignPub))
}
// And NkeyPublicFromSignPub is consistent with ClientNkey's public.
pub2, err := NkeyPublicFromSignPub(id.SignPub)
if err != nil {
t.Fatalf("NkeyPublicFromSignPub: %v", err)
}
if pub2 != pub {
t.Fatalf("public nkey mismatch between derivations: %s vs %s", pub2, pub)
}
}
// Error path: a wrong-length private key is rejected, not silently misused.
func TestClientNkeyBadKey(t *testing.T) {
if _, _, err := ClientNkey([]byte("too-short")); err == nil {
t.Fatalf("expected error for short private key")
}
}
// Error path: a non-nkey string does not decode to an allowlist key.
func TestSignPubHexFromNkeyBad(t *testing.T) {
if _, err := SignPubHexFromNkey("not-a-real-nkey"); err == nil {
t.Fatalf("expected error decoding a bogus nkey")
}
}