// Copyright (C) 2020-2026, Lux Industries Inc. All rights reserved. // See the file LICENSE for licensing terms. // Coverage tests — these explicitly exercise every error branch and // edge case in the PQ + crypto + wire layers so that 100% statement // coverage is enforced for the security-critical paths. package zwing import ( "bytes" "context" "errors" "io" "net" "sync" "testing" "time" "github.com/luxfi/api/zap" "github.com/luxfi/crypto/ed25519" "github.com/luxfi/crypto/mldsa" "golang.org/x/crypto/curve25519" ) // curve25519Basepoint returns the X25519 base point. func curve25519Basepoint() []byte { return curve25519.Basepoint } // curve25519X25519 wraps the curve25519.X25519 helper so the test code // can call it without re-importing. func curve25519X25519(priv, base []byte) ([]byte, error) { return curve25519.X25519(priv, base) } // ─── identity.go ───────────────────────────────────────────────────── // failingReader stops returning bytes after `limit` bytes have been read. type failingReader struct { r io.Reader limit int64 read int64 } func newFailingReader(r io.Reader, limit int64) *failingReader { return &failingReader{r: r, limit: limit} } func (f *failingReader) Read(p []byte) (int, error) { if f.read >= f.limit { return 0, io.ErrUnexpectedEOF } remaining := f.limit - f.read if int64(len(p)) > remaining { p = p[:remaining] } n, err := f.r.Read(p) f.read += int64(n) return n, err } func TestGenerateIdentityFromShortReader(t *testing.T) { // Reader that yields nothing. _, err := GenerateIdentityFrom(bytes.NewReader(nil)) if err == nil { t.Fatal("expected ed25519 keygen failure on empty reader") } // Reader long enough for ed25519 only — ML-DSA fails next. _, err = GenerateIdentityFrom(newFailingReader(bytes.NewReader(make([]byte, 1024)), 64)) if err == nil { t.Fatal("expected ML-DSA keygen failure when reader exhausts") } // Reader long enough for ed25519 + ML-DSA — X-Wing fails next. // Note: ML-DSA-65 keygen consumes ~64 bytes; ML-KEM-768 keygen consumes // ~64 bytes; the X25519 secret is the final 32 bytes. We feed exactly // enough for ed25519 (64), ML-DSA (64), ML-KEM (64) but not the X25519. // The exact thresholds depend on the underlying impl, so we probe: // at 80 bytes either ML-DSA or X-Wing must fail. if _, err := GenerateIdentityFrom(newFailingReader(bytes.NewReader(make([]byte, 1<<20)), 80)); err == nil { // Implementation may pre-buffer enough to succeed; if so, that's // fine — the goal of this test is to drive the error paths in // GenerateIdentityFrom, which the empty/64-byte cases above already // covered. t.Log("80-byte reader unexpectedly produced a full identity — implementation buffers internally") } } func TestSignErrorPaths(t *testing.T) { id, err := GenerateIdentity() if err != nil { t.Fatalf("identity: %v", err) } // Sign with valid identity is the happy path. sig := id.Sign([]byte("ctx"), []byte("msg")) if len(sig) != ed25519.SignatureSize+mldsa.MLDSA65SignatureSize { t.Fatalf("unexpected sig size: %d", len(sig)) } } func TestVerifyErrorPaths(t *testing.T) { id, err := GenerateIdentity() if err != nil { t.Fatalf("identity: %v", err) } pub := id.Public() // Wrong-size signature. if err := pub.Verify([]byte("ctx"), []byte("msg"), []byte("too short")); !errors.Is(err, ErrSignatureInvalid) { t.Fatalf("expected ErrSignatureInvalid for short sig, got %v", err) } // Valid sig modified in the Ed25519 half. sig := id.Sign([]byte("ctx"), []byte("msg")) bad := make([]byte, len(sig)) copy(bad, sig) bad[0] ^= 0xFF if err := pub.Verify([]byte("ctx"), []byte("msg"), bad); !errors.Is(err, ErrSignatureInvalid) { t.Fatal("expected Ed25519 verify failure") } // Valid Ed25519 sig but corrupted ML-DSA half. bad = make([]byte, len(sig)) copy(bad, sig) bad[ed25519.SignatureSize] ^= 0xFF if err := pub.Verify([]byte("ctx"), []byte("msg"), bad); !errors.Is(err, ErrSignatureInvalid) { t.Fatal("expected ML-DSA verify failure") } // Different message must fail. if err := pub.Verify([]byte("ctx"), []byte("other"), sig); !errors.Is(err, ErrSignatureInvalid) { t.Fatal("expected verify failure on different message") } // Different ctx must fail. if err := pub.Verify([]byte("other"), []byte("msg"), sig); !errors.Is(err, ErrSignatureInvalid) { t.Fatal("expected verify failure on different ctx") } } func TestParseIdentityPublicErrors(t *testing.T) { if _, err := ParseIdentityPublic(nil); err == nil { t.Fatal("expected size error for nil") } if _, err := ParseIdentityPublic(make([]byte, 4)); err == nil { t.Fatal("expected size error for short slice") } // Right size but garbage ML-DSA pubkey. garbage := make([]byte, IdentityPublicSize) for i := range garbage { garbage[i] = 0xFF } if _, err := ParseIdentityPublic(garbage); err == nil { t.Fatal("expected ML-DSA parse failure") } // Right size, valid ML-DSA, but garbage X-Wing. id, _ := GenerateIdentity() good := id.Public().MarshalBinary() mutated := make([]byte, len(good)) copy(mutated, good) for i := ed25519.PublicKeySize + mldsa.MLDSA65PublicKeySize; i < len(mutated); i++ { mutated[i] = 0xFF } // Tamper with the X-Wing portion to break it. ParseXWingPublicKey may // accept arbitrary bytes for the curve point but the ML-KEM half can fail. mutatedAt := ed25519.PublicKeySize + mldsa.MLDSA65PublicKeySize for i := mutatedAt; i < mutatedAt+10; i++ { mutated[i] = 0 } // Even if the parse succeeds with weird material, the round trip via // MarshalBinary must produce identical bytes. pub, err := ParseIdentityPublic(good) if err != nil { t.Fatalf("parse roundtrip: %v", err) } if !bytes.Equal(pub.MarshalBinary(), good) { t.Fatal("MarshalBinary not idempotent") } } func TestIdentityEqual(t *testing.T) { a, _ := GenerateIdentity() b, _ := GenerateIdentity() pa := a.Public() pb := b.Public() if pa.Equal(pb) { t.Fatal("distinct identities should not be Equal") } if !pa.Equal(pa) { t.Fatal("identity should equal itself") } if pa.Equal(nil) { t.Fatal("Equal(nil) should return false") } var nilPub *IdentityPublic if nilPub.Equal(pa) { t.Fatal("nil receiver should return false") } // Same Ed25519, mutated DSA half. mod := *pa if !pa.Equal(&mod) { t.Fatal("same fields should equal") } // Mutate a single bit in the Ed pub of a copy. edCopy := append(ed25519.PublicKey{}, pa.EdPub...) edCopy[0] ^= 0x01 mod = IdentityPublic{EdPub: edCopy, DSAPub: pa.DSAPub, XWingPub: pa.XWingPub} if pa.Equal(&mod) { t.Fatal("Ed mismatch should not equal") } } // ─── kem.go ────────────────────────────────────────────────────────── func TestParseXWingPublicKeyErrors(t *testing.T) { if _, err := ParseXWingPublicKey(nil); err == nil { t.Fatal("expected size error") } if _, err := ParseXWingPublicKey(make([]byte, XWingPublicKeySize-1)); err == nil { t.Fatal("expected size error for truncated input") } // Right size but invalid ML-KEM bytes. bad := make([]byte, XWingPublicKeySize) // Leave all-zero — ML-KEM-768 may reject all-zero pubkeys depending on impl. // At minimum the round-trip must work for valid material. id, _ := GenerateIdentity() good := id.XWing.Public().MarshalBinary() if pk, err := ParseXWingPublicKey(good); err != nil { t.Fatalf("valid pk failed to parse: %v", err) } else if !bytes.Equal(pk.MarshalBinary(), good) { t.Fatal("round trip mismatch") } _ = bad } func TestGenerateXWingKeyShortReader(t *testing.T) { // Reader that gives ML-KEM enough but not the X25519 32 bytes after. // ML-KEM-768 keypair generation pulls 64 bytes of seed from the reader, // so a 64-byte reader should pass ML-KEM and then fail on X25519. _, err := GenerateXWingKey(newFailingReader(bytes.NewReader(make([]byte, 1<<20)), 64)) if err == nil { t.Fatal("expected X25519 read failure") } // Empty reader → ML-KEM keygen fails first. if _, err := GenerateXWingKey(bytes.NewReader(nil)); err == nil { t.Fatal("expected ML-KEM keygen failure") } } func TestXWingEncapsulateShortReader(t *testing.T) { id, _ := GenerateIdentity() pub := id.XWing.Public() // Reader that fails after the ML-KEM encap consumes its bytes — there's // no clean way to know the exact split, but a reader yielding 0 bytes // fails immediately. _, _, err := XWingEncapsulate(bytes.NewReader(nil), pub) if err == nil { t.Fatal("expected encapsulate to fail with empty reader") } // Reader that gives ML-KEM all it needs but not the X25519 32 bytes. // The ML-KEM keygen pulls 64 random bytes; encapsulation pulls 32. So // roughly 32–64 bytes lets ML-KEM proceed and X25519 fail. _, _, err = XWingEncapsulate(newFailingReader(bytes.NewReader(make([]byte, 1<<20)), 32), pub) if err == nil { t.Fatal("expected X25519 ephemeral read failure") } } func TestXWingDecapsulateErrors(t *testing.T) { id, _ := GenerateIdentity() // Wrong size. if _, err := XWingDecapsulate(id.XWing, nil); err == nil { t.Fatal("expected size error") } if _, err := XWingDecapsulate(id.XWing, make([]byte, XWingCiphertextSize-1)); err == nil { t.Fatal("expected size error") } // Right size, garbage ML-KEM ciphertext. bad := make([]byte, XWingCiphertextSize) if _, err := XWingDecapsulate(id.XWing, bad); err != nil { // Some ML-KEM impls accept and produce a "junk" shared secret — that's // allowed by the spec (implicit rejection). We just don't pin a // particular outcome here, but exercising the path is enough. _ = err } } // ─── wire.go ───────────────────────────────────────────────────────── type writerErr struct{ err error } func (w *writerErr) Write(p []byte) (int, error) { return 0, w.err } func TestWriteFrameErrors(t *testing.T) { if err := writeFrame(io.Discard, make([]byte, MaxFrameSize+1)); !errors.Is(err, ErrMessageTooLarge) { t.Fatalf("expected ErrMessageTooLarge, got %v", err) } // Failing writer must propagate. w := &writerErr{err: errors.New("boom")} if err := writeFrame(w, []byte("x")); err == nil { t.Fatal("expected error from failing writer") } } type readerErr struct{ err error } func (r *readerErr) Read(p []byte) (int, error) { return 0, r.err } func TestReadFrameErrors(t *testing.T) { // Header read fails. if _, err := readFrame(&readerErr{err: io.EOF}); err == nil { t.Fatal("expected EOF on header") } // Length too large. hdr := []byte{0xFF, 0xFF, 0xFF, 0xFF} if _, err := readFrame(bytes.NewReader(hdr)); !errors.Is(err, ErrMessageTooLarge) { t.Fatalf("expected ErrMessageTooLarge, got %v", err) } // Length is zero. hdr = []byte{0, 0, 0, 0} if _, err := readFrame(bytes.NewReader(hdr)); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat, got %v", err) } // Header reads but body underflows. hdr = append(append([]byte(nil), 0, 0, 0, 8), []byte("ab")...) if _, err := readFrame(bytes.NewReader(hdr)); err == nil { t.Fatal("expected body read failure") } } // ─── handshake.go small helpers ───────────────────────────────────── func TestSliceReader(t *testing.T) { r := newSliceReader([]byte{0x01, 0x02, 0x03, 0x04, 0x05}) v, err := r.readU16() if err != nil { t.Fatalf("readU16: %v", err) } if v != 0x0102 { t.Fatalf("got %#x want 0x0102", v) } chunk, err := r.readN(2) if err != nil { t.Fatalf("readN: %v", err) } if !bytes.Equal(chunk, []byte{0x03, 0x04}) { t.Fatalf("readN chunk wrong: %x", chunk) } if r.empty() { t.Fatal("should not be empty yet") } if _, err := r.readN(2); err == nil { t.Fatal("expected short read") } if _, err := r.readN(-1); err == nil { t.Fatal("negative readN should error") } // readU16 with too-short buffer. short := newSliceReader([]byte{0x01}) if _, err := short.readU16(); !errors.Is(err, ErrShortRead) { t.Fatalf("expected ErrShortRead, got %v", err) } } func TestAppendU16Panics(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("expected panic on out-of-range u16") } }() appendU16(nil, 0x10000) } func TestParseHandshakeInitErrors(t *testing.T) { if _, err := parseHandshakeInit(nil); err == nil { t.Fatal("expected error on empty") } // idLen short read (only 1 byte available). if _, err := parseHandshakeInit([]byte{0xFF}); err == nil { t.Fatal("expected short read for idLen") } // idLen larger than remaining buffer. if _, err := parseHandshakeInit([]byte{0xFF, 0xFF}); err == nil { t.Fatal("expected short read for id") } // idLen=1 succeeds, then sigLen short read. if _, err := parseHandshakeInit([]byte{0, 1, 0xAA}); err == nil { t.Fatal("expected short read for sigLen") } // idLen=0, sigLen larger than rest. if _, err := parseHandshakeInit([]byte{0, 0, 0xFF, 0xFF}); err == nil { t.Fatal("expected short read for sig") } // Trailing garbage. bad := []byte{0, 1, 0xAA, 0, 1, 0xBB, 0xCC} if _, err := parseHandshakeInit(bad); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat, got %v", err) } } func TestParseHandshakeResponseErrors(t *testing.T) { if _, err := parseHandshakeResponse(nil); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat, got %v", err) } // Constructed by hand: too short to fit XWingCiphertextSize. if _, err := parseHandshakeResponse(make([]byte, 100)); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat for short, got %v", err) } // Right size for ct but encLen > rest. bad := make([]byte, XWingCiphertextSize+2) bad[XWingCiphertextSize] = 0xFF bad[XWingCiphertextSize+1] = 0xFF if _, err := parseHandshakeResponse(bad); err == nil { t.Fatal("expected short enc read") } // Trailing garbage. bad = make([]byte, XWingCiphertextSize+3) if _, err := parseHandshakeResponse(bad); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat, got %v", err) } } func TestSplitRespIDPayloadErrors(t *testing.T) { if _, _, err := splitRespIDPayload(nil); err == nil { t.Fatal("expected error on empty") } bad := []byte{0xFF, 0xFF} if _, _, err := splitRespIDPayload(bad); err == nil { t.Fatal("expected short id read") } bad = []byte{0, 0, 0xFF, 0xFF} if _, _, err := splitRespIDPayload(bad); err == nil { t.Fatal("expected short sig read") } bad = []byte{0, 1, 0xAA, 0, 1, 0xBB, 0xCC} if _, _, err := splitRespIDPayload(bad); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat, got %v", err) } } // ─── handshake error paths ────────────────────────────────────────── func TestRunInitiatorMissingConfig(t *testing.T) { // nil config if _, err := runInitiator(nopRW{}, nil); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } if _, err := runInitiator(nopRW{}, &Config{}); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } } func TestRunResponderMissingConfig(t *testing.T) { if _, err := runResponder(nopRW{}, nil); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } if _, err := runResponder(nopRW{}, &Config{}); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } } type nopRW struct{} func (nopRW) Read(p []byte) (int, error) { return 0, io.EOF } func (nopRW) Write(p []byte) (int, error) { return len(p), nil } // controlledRW lets a test inject failures at specific I/O steps. It // wraps a real net.Conn but can return a chosen error on the Nth read // or write call. type controlledRW struct { r io.Reader w io.Writer failRead int // 1-indexed; 0 = never failWrite int readN int writeN int readErr error writeErr error } func (c *controlledRW) Read(p []byte) (int, error) { c.readN++ if c.failRead != 0 && c.readN == c.failRead { return 0, c.readErr } return c.r.Read(p) } func (c *controlledRW) Write(p []byte) (int, error) { c.writeN++ if c.failWrite != 0 && c.writeN == c.failWrite { return 0, c.writeErr } return c.w.Write(p) } func TestRunInitiatorWriteFails(t *testing.T) { clientID, _ := newPair(t) rw := &controlledRW{ r: bytes.NewReader(nil), w: io.Discard, failWrite: 1, writeErr: errors.New("write boom"), } if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); err == nil { t.Fatal("expected write failure to surface") } } func TestRunInitiatorReadFails(t *testing.T) { clientID, _ := newPair(t) // Discard the init bytes the initiator writes; fail the response read. rw := &controlledRW{ r: bytes.NewReader(nil), // empty reader → readFrame fails on header w: io.Discard, } if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); err == nil { t.Fatal("expected response readFrame failure") } } func TestRunInitiatorBadResponseParse(t *testing.T) { clientID, _ := newPair(t) // Frame whose payload is too short to be a HandshakeResponse. garbage := []byte{0, 0, 0, 4, 'b', 'a', 'd', '!'} rw := &controlledRW{ r: bytes.NewReader(garbage), w: io.Discard, } if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); !errors.Is(err, ErrInvalidWireFormat) { t.Fatalf("expected ErrInvalidWireFormat, got %v", err) } } func TestRunInitiatorDecapFails(t *testing.T) { clientID, _ := newPair(t) // Build a HandshakeResponse with a junk ML-KEM ciphertext but a // valid-looking X25519 ephemeral. The ML-KEM decap returns implicit // rejection (junk shared); the AEAD open over EncryptedID then fails, // which is the ErrCiphertextCorrupted branch. junkCT := make([]byte, XWingCiphertextSize) for i := range junkCT { junkCT[i] = 0xCC } // Replace the X25519 portion with a valid ephemeral pubkey (any 32 // non-low-order bytes; we generate a fresh one). var ephPriv [32]byte for i := range ephPriv { ephPriv[i] = 0x42 } ephPub, err := curve25519X25519(ephPriv[:], curve25519Basepoint()) if err != nil { t.Fatalf("ephPub: %v", err) } copy(junkCT[len(junkCT)-32:], ephPub) resp := &HandshakeResponse{ XWingCiphertext: junkCT, EncryptedID: []byte("not real ciphertext"), } respWire := resp.MarshalBinary() frame := make([]byte, 4+len(respWire)) frame[0] = byte(len(respWire) >> 24) frame[1] = byte(len(respWire) >> 16) frame[2] = byte(len(respWire) >> 8) frame[3] = byte(len(respWire)) copy(frame[4:], respWire) rw := &controlledRW{r: bytes.NewReader(frame), w: io.Discard} if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); !errors.Is(err, ErrCiphertextCorrupted) { t.Fatalf("expected ErrCiphertextCorrupted, got %v", err) } } func TestRunResponderReadFails(t *testing.T) { _, serverID := newPair(t) rw := &controlledRW{ r: bytes.NewReader(nil), // immediate EOF w: io.Discard, } if _, err := runResponder(rw, &Config{LocalIdentity: serverID}); err == nil { t.Fatal("expected init readFrame failure") } } func TestRunResponderWriteFails(t *testing.T) { clientID, serverID := newPair(t) // Build a real HandshakeInit framed and stuff it through the reader. idPub := clientID.Public().MarshalBinary() sig := clientID.Sign([]byte(hsLabelInit), idPub) hi := &HandshakeInit{IdentityPub: idPub, Signature: sig} wire := hi.MarshalBinary() frame := make([]byte, 4+len(wire)) frame[0] = byte(len(wire) >> 24) frame[1] = byte(len(wire) >> 16) frame[2] = byte(len(wire) >> 8) frame[3] = byte(len(wire)) copy(frame[4:], wire) // Fail on the FIRST write so HandshakeResponse send fails. rw := &controlledRW{ r: bytes.NewReader(frame), w: io.Discard, failWrite: 1, writeErr: errors.New("write boom"), } if _, err := runResponder(rw, &Config{LocalIdentity: serverID}); err == nil { t.Fatal("expected response writeFrame failure") } } func TestServerSidePinning(t *testing.T) { clientID, serverID := newPair(t) _, otherID := newPair(t) _, _, _, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID, ExpectedRemote: otherID.Public()}, ) if !errors.Is(sErr, ErrIdentityMismatch) { t.Fatalf("server: want ErrIdentityMismatch, got %v", sErr) } } func TestInitiatorReceivesGarbageResponse(t *testing.T) { clientID, _ := newPair(t) left, right := net.Pipe() defer left.Close() defer right.Close() doneCh := make(chan error, 1) go func() { _, err := runInitiator(left, &Config{LocalIdentity: clientID}) doneCh <- err }() // Drain the init frame. if _, err := readFrame(right); err != nil { t.Fatalf("read init: %v", err) } // Send garbage that's the right size for parseHandshakeResponse but // fails X-Wing decap. resp := &HandshakeResponse{ XWingCiphertext: make([]byte, XWingCiphertextSize), EncryptedID: []byte("garbage"), } // Make the ciphertext look "valid-shape" so it gets to decap; decap // should yield a junk shared, then AEAD open over the junk fails. if err := writeFrame(right, resp.MarshalBinary()); err != nil { t.Fatalf("write resp: %v", err) } select { case err := <-doneCh: if err == nil { t.Fatal("expected initiator failure") } case <-time.After(2 * time.Second): t.Fatal("initiator hung") } } func TestResponderReceivesGarbageInit(t *testing.T) { _, serverID := newPair(t) left, right := net.Pipe() defer left.Close() defer right.Close() doneCh := make(chan error, 1) go func() { _, err := runResponder(right, &Config{LocalIdentity: serverID}) doneCh <- err }() // Send a frame whose contents fail parseHandshakeInit. if err := writeFrame(left, []byte{0xFF, 0xFF}); err != nil { t.Fatalf("write garbage: %v", err) } select { case err := <-doneCh: if err == nil { t.Fatal("expected responder failure") } case <-time.After(2 * time.Second): t.Fatal("responder hung") } } func TestResponderInvalidIdentityPub(t *testing.T) { _, serverID := newPair(t) left, right := net.Pipe() defer left.Close() defer right.Close() doneCh := make(chan error, 1) go func() { _, err := runResponder(right, &Config{LocalIdentity: serverID}) doneCh <- err }() // Send a HandshakeInit whose ID is the right size but garbage. garbage := make([]byte, IdentityPublicSize) for i := range garbage { garbage[i] = 0xFF } hi := &HandshakeInit{IdentityPub: garbage, Signature: make([]byte, ed25519.SignatureSize+mldsa.MLDSA65SignatureSize)} if err := writeFrame(left, hi.MarshalBinary()); err != nil { t.Fatalf("write garbage init: %v", err) } select { case err := <-doneCh: if err == nil { t.Fatal("expected responder failure on garbage identity") } case <-time.After(2 * time.Second): t.Fatal("responder hung") } } func TestResponderBadSignature(t *testing.T) { clientID, serverID := newPair(t) left, right := net.Pipe() defer left.Close() defer right.Close() doneCh := make(chan error, 1) go func() { _, err := runResponder(right, &Config{LocalIdentity: serverID}) doneCh <- err }() idPub := clientID.Public().MarshalBinary() // Build a syntactically valid sig over a different message so verify fails. sig := clientID.Sign([]byte("wrong"), idPub) hi := &HandshakeInit{IdentityPub: idPub, Signature: sig} if err := writeFrame(left, hi.MarshalBinary()); err != nil { t.Fatalf("write: %v", err) } select { case err := <-doneCh: if !errors.Is(err, ErrSignatureInvalid) { t.Fatalf("expected ErrSignatureInvalid, got %v", err) } case <-time.After(2 * time.Second): t.Fatal("responder hung") } } // ─── listen.go / dial.go ───────────────────────────────────────────── func TestListenMissingConfig(t *testing.T) { if _, err := Listen(":0", nil); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } if _, err := Listen(":0", &Config{}); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } } func TestListenBadAddr(t *testing.T) { id, _ := GenerateIdentity() if _, err := Listen("not-a-valid-host:99999999", &Config{LocalIdentity: id}); err == nil { t.Fatal("expected listen failure on bogus addr") } } func TestDialMissingConfig(t *testing.T) { if _, err := Dial(context.Background(), "127.0.0.1:0", nil); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } if _, err := Dial(context.Background(), "127.0.0.1:0", &Config{}); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } } func TestDialUnreachable(t *testing.T) { id, _ := GenerateIdentity() // Port 1 is privileged and unlikely to accept; expect dial error. ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() if _, err := Dial(ctx, "127.0.0.1:1", &Config{LocalIdentity: id}); err == nil { t.Fatal("expected dial failure") } } func TestAcceptHandshakeFails(t *testing.T) { _, serverID := newPair(t) ln, err := Listen("127.0.0.1:0", &Config{LocalIdentity: serverID}) if err != nil { t.Fatalf("listen: %v", err) } defer ln.Close() doneCh := make(chan error, 1) go func() { _, err := ln.Accept() doneCh <- err }() conn, err := net.Dial("tcp", ln.Addr().String()) if err != nil { t.Fatalf("dial: %v", err) } // Send garbage so the server-side handshake fails. conn.Write([]byte{0, 0, 0, 8, 'g', 'a', 'r', 'b', 'a', 'g', 'e', '!'}) conn.Close() select { case err := <-doneCh: if err == nil { t.Fatal("expected handshake failure") } case <-time.After(2 * time.Second): t.Fatal("Accept did not return") } } func TestAcceptUnderlyingError(t *testing.T) { _, serverID := newPair(t) ln, err := Listen("127.0.0.1:0", &Config{LocalIdentity: serverID}) if err != nil { t.Fatalf("listen: %v", err) } if err := ln.Close(); err != nil { t.Fatalf("close: %v", err) } if _, err := ln.Accept(); err == nil { t.Fatal("expected error after close") } } // ─── channel.go ────────────────────────────────────────────────────── func TestChannelMaxFrameSplit(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } defer cConn.Close() defer sConn.Close() // Write one-and-a-bit frames worth of plaintext to force the split path. const sz = (MaxFrameSize - 16) + 100 // chacha20poly1305.Overhead = 16 payload := bytes.Repeat([]byte("Q"), sz) wErr := make(chan error, 1) go func() { _, e := cConn.Write(payload); wErr <- e }() got := make([]byte, 0, sz) buf := make([]byte, sz) for len(got) < sz { n, err := sConn.Read(buf) if err != nil { t.Fatalf("read: %v", err) } got = append(got, buf[:n]...) } if err := <-wErr; err != nil { t.Fatalf("write: %v", err) } if !bytes.Equal(got, payload) { t.Fatal("split-frame payload corrupted") } } func TestChannelReadOverflowBuffer(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } defer cConn.Close() defer sConn.Close() payload := []byte("0123456789") wErr := make(chan error, 1) go func() { _, e := cConn.Write(payload); wErr <- e }() // Read into a smaller buffer so overflow goes through readBuf. small := make([]byte, 4) n, err := sConn.Read(small) if err != nil { t.Fatalf("read1: %v", err) } if n != 4 || string(small) != "0123" { t.Fatalf("read1 got %q", small[:n]) } if err := <-wErr; err != nil { t.Fatalf("write: %v", err) } // Subsequent reads must serve the buffered remainder. rest := make([]byte, 6) n2, err := sConn.Read(rest) if err != nil { t.Fatalf("read2: %v", err) } if n2 != 6 || string(rest) != "456789" { t.Fatalf("read2 got %q", rest[:n2]) } } func TestChannelCorruptedCiphertext(t *testing.T) { clientID, serverID := newPair(t) left, right := net.Pipe() resCh := make(chan *handshakeResult, 1) go func() { r, err := runResponder(right, &Config{LocalIdentity: serverID}) if err != nil { t.Errorf("responder: %v", err) } resCh <- r }() cRes, err := runInitiator(left, &Config{LocalIdentity: clientID}) if err != nil { t.Fatalf("initiator: %v", err) } sRes := <-resCh // Build initiator-side conn manually to keep raw access. cConn, err := newConn(left, cRes, true) if err != nil { t.Fatalf("init conn: %v", err) } // Manually craft a bad ciphertext frame and send it via the pipe. go func() { _ = writeFrame(right, bytes.Repeat([]byte{0xFF}, 64)) }() buf := make([]byte, 1) if _, err := cConn.Read(buf); !errors.Is(err, ErrCiphertextCorrupted) { t.Fatalf("expected ErrCiphertextCorrupted, got %v", err) } _ = sRes cConn.Close() } func TestChannelCloseAfterClose(t *testing.T) { c := &Conn{} if err := c.Close(); !errors.Is(err, ErrChannelClosed) { t.Fatalf("expected ErrChannelClosed, got %v", err) } } func TestChannelDeadlineGetters(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } defer cConn.Close() defer sConn.Close() now := time.Now().Add(time.Second) if err := cConn.SetDeadline(now); err != nil { t.Fatalf("SetDeadline: %v", err) } if err := cConn.SetReadDeadline(now); err != nil { t.Fatalf("SetReadDeadline: %v", err) } if err := cConn.SetWriteDeadline(now); err != nil { t.Fatalf("SetWriteDeadline: %v", err) } _ = cConn.LocalAddr() _ = cConn.RemoteAddr() } func TestNonceFor(t *testing.T) { n := nonceFor(0) for _, b := range n { if b != 0 { t.Fatal("zero seq should produce zero nonce") } } n = nonceFor(1) if n[11] != 1 { t.Fatalf("seq=1 low byte should be 1, got %x", n[11]) } n = nonceFor(^uint64(0)) for i := 4; i < 12; i++ { if n[i] != 0xFF { t.Fatalf("max seq should fill low 8 bytes, got %x", n) } } } // ─── zap.go integration ───────────────────────────────────────────── func TestDialZAPListenZAP(t *testing.T) { _, serverID := newPair(t) clientID, _ := newPair(t) ln, err := ListenZAP("127.0.0.1:0", &Config{LocalIdentity: serverID}) if err != nil { t.Fatalf("ListenZAP: %v", err) } defer ln.Close() // Run a tiny ZAP server. srv := zap.NewServer(ln, zap.HandlerFunc(func(_ context.Context, mt zap.MessageType, p []byte) (zap.MessageType, []byte, error) { return mt, p, nil })) srvDone := make(chan error, 1) go func() { srvDone <- srv.Serve(context.Background()) }() defer func() { srv.Close() <-srvDone }() ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() conn, err := DialZAP(ctx, ln.Addr().String(), &Config{ LocalIdentity: clientID, ExpectedRemote: serverID.Public(), }) if err != nil { t.Fatalf("DialZAP: %v", err) } defer conn.Close() mt, payload, err := conn.Call(ctx, zap.MsgBuildBlock, []byte("ping")) if err != nil { t.Fatalf("Call: %v", err) } if mt&zap.MsgResponseFlag == 0 { t.Fatal("response flag missing") } if !bytes.Equal(payload, []byte("ping")) { t.Fatalf("payload mismatch: %q", payload) } } func TestDialZAPFailures(t *testing.T) { // Missing identity. _, err := DialZAP(context.Background(), "127.0.0.1:1", nil) if !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } // Unreachable. id, _ := GenerateIdentity() ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() if _, err := DialZAP(ctx, "127.0.0.1:1", &Config{LocalIdentity: id}); err == nil { t.Fatal("expected dial failure") } // ListenZAP with missing identity. if _, err := ListenZAP(":0", nil); !errors.Is(err, ErrConfigMissingID) { t.Fatalf("expected ErrConfigMissingID, got %v", err) } // ListenZAP with bogus addr. if _, err := ListenZAP("not-a-host:99999999", &Config{LocalIdentity: id}); err == nil { t.Fatal("expected listen failure") } } // ─── targeted reachable-branch coverage ───────────────────────────── func TestMustChaCha20PanicsOnBadKey(t *testing.T) { defer func() { if r := recover(); r == nil { t.Fatal("expected panic for wrong-size key") } }() mustChaCha20([]byte{0, 1, 2, 3}) // not 32 bytes } func TestReadSequenceExhausted(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } defer cConn.Close() defer sConn.Close() cc := cConn.(*Conn) cc.readSeq = ^uint64(0) buf := make([]byte, 1) go func() { _, _ = sConn.Write([]byte("x")) }() if _, err := cc.Read(buf); !errors.Is(err, ErrSequenceExhausted) { t.Fatalf("expected ErrSequenceExhausted, got %v", err) } } func TestWriteSequenceExhausted(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } defer cConn.Close() defer sConn.Close() cc := cConn.(*Conn) cc.writeSeq = ^uint64(0) if _, err := cc.Write([]byte("y")); !errors.Is(err, ErrSequenceExhausted) { t.Fatalf("expected ErrSequenceExhausted, got %v", err) } } func TestWriteFrameUnderlyingError(t *testing.T) { clientID, serverID := newPair(t) left, right := net.Pipe() type res struct { r *handshakeResult e error } ch := make(chan res, 1) go func() { r, e := runResponder(right, &Config{LocalIdentity: serverID}) ch <- res{r, e} }() cRes, err := runInitiator(left, &Config{LocalIdentity: clientID}) if err != nil { t.Fatalf("init: %v", err) } <-ch cConn, err := newConn(left, cRes, true) if err != nil { t.Fatalf("newConn: %v", err) } right.Close() if _, err := cConn.Write([]byte("won't go anywhere")); err == nil { t.Fatal("expected write failure after pipe close") } cConn.Close() } func TestHandshakeTimeoutSetsDeadline(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID, HandshakeTimeout: 5 * time.Second}, &Config{LocalIdentity: serverID, HandshakeTimeout: 5 * time.Second}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } cConn.Close() sConn.Close() } func TestEqualMismatchedDSA(t *testing.T) { a, _ := GenerateIdentity() b, _ := GenerateIdentity() pa := a.Public() mod := IdentityPublic{ EdPub: pa.EdPub, DSAPub: b.Public().DSAPub, XWingPub: pa.XWingPub, } if pa.Equal(&mod) { t.Fatal("DSA mismatch should not equal") } } func TestEqualMismatchedXWing(t *testing.T) { a, _ := GenerateIdentity() b, _ := GenerateIdentity() pa := a.Public() mod := IdentityPublic{ EdPub: pa.EdPub, DSAPub: pa.DSAPub, XWingPub: b.Public().XWingPub, } if pa.Equal(&mod) { t.Fatal("XWing mismatch should not equal") } } func TestGenerateXWingKeyNilReader(t *testing.T) { sk, err := GenerateXWingKey(nil) if err != nil { t.Fatalf("GenerateXWingKey(nil): %v", err) } if sk == nil || sk.MLKEMPriv == nil { t.Fatal("expected populated keypair") } } func TestXWingDecapsulateLowOrderEph(t *testing.T) { id, err := GenerateIdentity() if err != nil { t.Fatalf("identity: %v", err) } ct := make([]byte, XWingCiphertextSize) for i := range XWingCiphertextSize - 32 { ct[i] = 0xCC } // Trailing 32 bytes are zero → low-order point → ssX errors. if _, err := XWingDecapsulate(id.XWing, ct); err == nil { t.Fatal("expected XWingDecapsulate to fail on low-order ephemeral") } } func TestWriteFramePartialFail(t *testing.T) { pw := &partialWriter{succeed: 4, err: errors.New("body boom")} if err := writeFrame(pw, []byte("payload")); err == nil { t.Fatal("expected body write failure") } } type partialWriter struct { succeed int written int err error } func (w *partialWriter) Write(p []byte) (int, error) { if w.written < w.succeed { room := w.succeed - w.written n := min(len(p), room) w.written += n if n < len(p) { return n, w.err } return n, nil } return 0, w.err } func TestRunInitiatorBadResponseAEADParse(t *testing.T) { clientID, _ := newPair(t) ct, shared, err := XWingEncapsulate(nil, clientID.XWing.Public()) if err != nil { t.Fatalf("encap: %v", err) } idKey := derive(shared[:], []byte("lux.zwing.v1/resp-id"), chacha20KeySize) encrypted := aeadSeal(idKey, []byte("zwing-resp-id"), ct, []byte{0xFF, 0xFF}) resp := &HandshakeResponse{XWingCiphertext: ct, EncryptedID: encrypted} respWire := resp.MarshalBinary() frame := make([]byte, 4+len(respWire)) frame[0] = byte(len(respWire) >> 24) frame[1] = byte(len(respWire) >> 16) frame[2] = byte(len(respWire) >> 8) frame[3] = byte(len(respWire)) copy(frame[4:], respWire) rw := &controlledRW{r: bytes.NewReader(frame), w: io.Discard} if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); err == nil { t.Fatal("expected splitRespIDPayload error") } } func TestRunInitiatorBadResponseIDParse(t *testing.T) { clientID, _ := newPair(t) ct, shared, err := XWingEncapsulate(nil, clientID.XWing.Public()) if err != nil { t.Fatalf("encap: %v", err) } idKey := derive(shared[:], []byte("lux.zwing.v1/resp-id"), chacha20KeySize) plain := []byte{0, 4, 'a', 'b', 'c', 'd', 0, 0} encrypted := aeadSeal(idKey, []byte("zwing-resp-id"), ct, plain) resp := &HandshakeResponse{XWingCiphertext: ct, EncryptedID: encrypted} respWire := resp.MarshalBinary() frame := make([]byte, 4+len(respWire)) frame[0] = byte(len(respWire) >> 24) frame[1] = byte(len(respWire) >> 16) frame[2] = byte(len(respWire) >> 8) frame[3] = byte(len(respWire)) copy(frame[4:], respWire) rw := &controlledRW{r: bytes.NewReader(frame), w: io.Discard} if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); err == nil { t.Fatal("expected ParseIdentityPublic error") } } func TestRunInitiatorBadResponseSigVerify(t *testing.T) { clientID, _ := newPair(t) realServerID, _ := newPair(t) otherID, _ := newPair(t) ct, shared, err := XWingEncapsulate(nil, clientID.XWing.Public()) if err != nil { t.Fatalf("encap: %v", err) } idKey := derive(shared[:], []byte("lux.zwing.v1/resp-id"), chacha20KeySize) idPub := realServerID.Public().MarshalBinary() transcript := transcriptHash(clientID.Public().MarshalBinary(), ct) wrongSig := otherID.Sign([]byte(hsLabelResponse), transcript) plain := buildRespIDPayload(idPub, wrongSig) encrypted := aeadSeal(idKey, []byte("zwing-resp-id"), ct, plain) resp := &HandshakeResponse{XWingCiphertext: ct, EncryptedID: encrypted} respWire := resp.MarshalBinary() frame := make([]byte, 4+len(respWire)) frame[0] = byte(len(respWire) >> 24) frame[1] = byte(len(respWire) >> 16) frame[2] = byte(len(respWire) >> 8) frame[3] = byte(len(respWire)) copy(frame[4:], respWire) rw := &controlledRW{r: bytes.NewReader(frame), w: io.Discard} if _, err := runInitiator(rw, &Config{LocalIdentity: clientID}); !errors.Is(err, ErrSignatureInvalid) { t.Fatalf("expected ErrSignatureInvalid, got %v", err) } } func TestRunResponderXWingEncapsulateLowOrder(t *testing.T) { clientID, serverID := newPair(t) mutated := *clientID mutXW := *clientID.XWing mutated.XWing = &mutXW for i := range mutXW.X25519Pub { mutXW.X25519Pub[i] = 0 } for i := range mutXW.X25519Priv { mutXW.X25519Priv[i] = 0 } idPub := mutated.Public().MarshalBinary() sig := mutated.Sign([]byte(hsLabelInit), idPub) hi := &HandshakeInit{IdentityPub: idPub, Signature: sig} wire := hi.MarshalBinary() frame := make([]byte, 4+len(wire)) frame[0] = byte(len(wire) >> 24) frame[1] = byte(len(wire) >> 16) frame[2] = byte(len(wire) >> 8) frame[3] = byte(len(wire)) copy(frame[4:], wire) rw := &controlledRW{r: bytes.NewReader(frame), w: io.Discard} if _, err := runResponder(rw, &Config{LocalIdentity: serverID}); err == nil { t.Fatal("expected XWingEncapsulate failure on low-order pub") } } func TestSplitRespIDPayloadShortSigLenU16(t *testing.T) { if _, _, err := splitRespIDPayload([]byte{0, 0}); !errors.Is(err, ErrShortRead) { t.Fatalf("expected ErrShortRead for missing sigLen u16, got %v", err) } } func TestGenerateIdentityFromMLDSAReadFailure(t *testing.T) { // 32-byte reader exhausts after ed25519 keygen consumes its seed. rdr := newFailingReader(bytes.NewReader(make([]byte, 1024)), 32) if _, err := GenerateIdentityFrom(rdr); err == nil { t.Fatal("expected ml-dsa keygen failure after 32-byte exhaustion") } } // ─── upstream-defensive seam tests ────────────────────────────────── // TestParseTestableDSAPubErrorSeam swaps the parseTestableDSAPub seam to // force an error and exercise the defensive branch in // ParseIdentityPublic. The seam exists exactly because the upstream // constructor only fails on size mismatches that ParseIdentityPublic // already rejects. func TestParseTestableDSAPubErrorSeam(t *testing.T) { orig := parseTestableDSAPub defer func() { parseTestableDSAPub = orig }() parseTestableDSAPub = func(b []byte) (*mldsa.PublicKey, error) { return nil, errors.New("seam: forced ml-dsa parse error") } id, _ := GenerateIdentity() bytes := id.Public().MarshalBinary() if _, err := ParseIdentityPublic(bytes); err == nil { t.Fatal("expected forced error from seam") } } // TestDecapsulateMLKEMErrorSeam swaps the decapsulateMLKEM seam to drive // the defensive ML-KEM Decapsulate error branch. func TestDecapsulateMLKEMErrorSeam(t *testing.T) { orig := decapsulateMLKEM defer func() { decapsulateMLKEM = orig }() decapsulateMLKEM = func(*XWingPrivateKey, []byte) ([]byte, error) { return nil, errors.New("seam: forced ml-kem decap error") } id, _ := GenerateIdentity() ct := make([]byte, XWingCiphertextSize) if _, err := XWingDecapsulate(id.XWing, ct); err == nil { t.Fatal("expected forced error from seam") } } // ─── concurrency smoke ────────────────────────────────────────────── func TestConcurrentReadsAreSerialized(t *testing.T) { clientID, serverID := newPair(t) cConn, sConn, cErr, sErr := runHandshakeOverPipe(t, &Config{LocalIdentity: clientID}, &Config{LocalIdentity: serverID}, ) if cErr != nil || sErr != nil { t.Fatalf("handshake: %v / %v", cErr, sErr) } defer cConn.Close() defer sConn.Close() // 8 concurrent writes from server, 8 concurrent reads on client. const N = 8 var wg sync.WaitGroup for range N { wg.Go(func() { _, _ = sConn.Write([]byte("z")) }) } got := 0 for got < N { buf := make([]byte, 1) n, err := cConn.Read(buf) if err != nil { t.Fatalf("read: %v", err) } got += n } wg.Wait() }