413dd61041
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>
86 lines
2.7 KiB
Go
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")
|
|
}
|
|
}
|