- internal/{address,identity,protocol,tce,transport,verify} -> pkg/ so
external Go projects can import the verified core; invariant tests
updated for the new paths
- Config.TrustProxy: key rate limiting by X-Forwarded-For when the relay
sits behind a reverse proxy (off by default, header never trusted
otherwise)
- examples/service + examples/approve: complete passwordless login round
trip (mint request -> wallet approves -> local verify), run live in CI
- docs/SERVICE-GUIDE.md: the integration recipe
124 lines
3.5 KiB
Go
124 lines
3.5 KiB
Go
package identity_test
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"testing"
|
|
|
|
"git.n1ko.dev/Niko/niko_trust/pkg/identity"
|
|
"git.n1ko.dev/Niko/niko_trust/pkg/identity/signer"
|
|
)
|
|
|
|
// FuzzVerifyNeverPanics asserts that verification is total over arbitrary
|
|
// message and signature bytes, and that it never accepts anything the signer
|
|
// did not actually sign.
|
|
func FuzzVerifyNeverPanics(f *testing.F) {
|
|
s, err := signer.Generate()
|
|
if err != nil {
|
|
f.Fatal(err)
|
|
}
|
|
msg := []byte("seed message")
|
|
f.Add(msg, s.Sign(msg))
|
|
f.Add([]byte{}, []byte{})
|
|
f.Add([]byte("x"), make([]byte, 64))
|
|
f.Add(make([]byte, 1024), make([]byte, 63))
|
|
|
|
id := s.Identity()
|
|
f.Fuzz(func(t *testing.T, msg, sig []byte) {
|
|
ok := id.Verify(msg, sig)
|
|
if ok != (id.VerifyErr(msg, sig) == nil) {
|
|
t.Fatal("Verify and VerifyErr disagree")
|
|
}
|
|
if !ok {
|
|
return
|
|
}
|
|
// An accepted signature must be exactly 64 bytes and must be the one
|
|
// the signer produces for this message. Finding any other accepted
|
|
// pair would mean a forgery.
|
|
if len(sig) != ed25519.SignatureSize {
|
|
t.Fatalf("accepted a %d-byte signature", len(sig))
|
|
}
|
|
expected := s.Sign(msg)
|
|
if string(expected) != string(sig) {
|
|
t.Fatalf("accepted a signature the signer would not produce\nmsg=%x\nsig=%x", msg, sig)
|
|
}
|
|
})
|
|
}
|
|
|
|
// FuzzSignVerifyRoundTrip asserts that every message a signer signs verifies
|
|
// under its own identity and under no other.
|
|
func FuzzSignVerifyRoundTrip(f *testing.F) {
|
|
f.Add([]byte("hello"), make([]byte, ed25519.SeedSize))
|
|
f.Add([]byte(""), []byte("0123456789abcdef0123456789abcdef"))
|
|
|
|
other, err := signer.Generate()
|
|
if err != nil {
|
|
f.Fatal(err)
|
|
}
|
|
|
|
f.Fuzz(func(t *testing.T, msg, seed []byte) {
|
|
if len(seed) != ed25519.SeedSize {
|
|
return
|
|
}
|
|
s, err := signer.FromSeed(seed)
|
|
if err != nil {
|
|
t.Fatalf("FromSeed rejected a %d-byte seed: %v", len(seed), err)
|
|
}
|
|
sig := s.Sign(msg)
|
|
|
|
if !s.Identity().Verify(msg, sig) {
|
|
t.Fatal("a signature did not verify under its own identity")
|
|
}
|
|
if other.Identity().Verify(msg, sig) {
|
|
t.Fatal("a signature verified under a foreign identity")
|
|
}
|
|
|
|
// Signing is deterministic for Ed25519, which the protocol relies on
|
|
// when comparing objects for equality.
|
|
if string(s.Sign(msg)) != string(sig) {
|
|
t.Fatal("signing is not deterministic")
|
|
}
|
|
|
|
// Any single-bit change to the message must break verification.
|
|
if len(msg) > 0 {
|
|
bad := make([]byte, len(msg))
|
|
copy(bad, msg)
|
|
bad[0] ^= 0x80
|
|
if s.Identity().Verify(bad, sig) {
|
|
t.Fatal("verified a modified message")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// FuzzIdentityParse asserts that identity parsing inherits every guarantee of
|
|
// address parsing and never yields a usable identity from invalid text.
|
|
func FuzzIdentityParse(f *testing.F) {
|
|
s, _ := signer.Generate()
|
|
f.Add(s.Identity().String())
|
|
f.Add("")
|
|
f.Add("trust1")
|
|
f.Add("trust1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq")
|
|
|
|
f.Fuzz(func(t *testing.T, str string) {
|
|
id, err := identity.Parse(str)
|
|
if err != nil {
|
|
if !id.IsZero() {
|
|
t.Fatal("an error result carried a non-zero identity")
|
|
}
|
|
return
|
|
}
|
|
if id.IsZero() {
|
|
t.Fatal("a successful parse produced the zero identity")
|
|
}
|
|
if id.String() != str {
|
|
t.Fatalf("parse normalised input: %q -> %q", str, id.String())
|
|
}
|
|
|
|
// A parsed identity must never verify a signature that was not made
|
|
// for it. It has no private key anywhere in the process, so nothing
|
|
// should verify except by astronomically unlikely chance.
|
|
if id.Verify([]byte("arbitrary"), make([]byte, ed25519.SignatureSize)) {
|
|
t.Fatal("a parsed identity verified an all-zero signature")
|
|
}
|
|
})
|
|
}
|