Skip to content

Hybrid X25519 + ML-KEM-1024 handshake and full PQ integration#6

Merged
disentangle-network merged 2 commits intofeature/pq-certificatesfrom
feature/pq-handshake
Feb 19, 2026
Merged

Hybrid X25519 + ML-KEM-1024 handshake and full PQ integration#6
disentangle-network merged 2 commits intofeature/pq-certificatesfrom
feature/pq-handshake

Conversation

@disentangle-network
Copy link
Copy Markdown
Owner

Summary

Complete post-quantum integration across the full Nebula stack. Builds on #5 (PQ certificates).

Hybrid handshake (NIST SP 800-56C)

  • X25519 DH for classical security (unchanged Noise IX state machine)
  • ML-KEM-1024 KEM layered on top via handshake payload fields
  • Both shared secrets mixed via HKDF-SHA256 into final symmetric keys
  • Breaking either primitive alone is insufficient to compromise the session

Handshake flow for Curve_PQ

  1. Initiator: generate ephemeral ML-KEM-1024 keypair, include KemPublicKey in NebulaHandshakeDetails
  2. Responder: encapsulate to initiator's KEM pubkey, include KemCiphertext in response, mix into dKey/eKey
  3. Initiator: decapsulate, mix into dKey/eKey
  4. Tunnel established with hybrid symmetric keys

nebula-cert CLI

  • nebula-cert ca -curve PQ generates ML-DSA-87 CA (V2 only)
  • nebula-cert sign with PQ CA generates ML-KEM-1024 host certs
  • nebula-cert keygen -curve PQ generates ML-KEM-1024 keypairs

PKI integration

  • PQ certs marshal with full public key (cert pubkey is ML-KEM, not Noise PeerStatic)
  • UnmarshalCertificateFromBytes for PQ cert reconstruction in handshake
  • VerifyPrivateKey fixed for ML-KEM-1024 packed format

E2E test

  • TestGoodHandshakePQ: full tunnel between two PQ nodes, bidirectional data transfer verified
  • 0.41s handshake time
  • Zero regressions in classical (X25519, P256) handshake tests

Security stack

Layer Algorithm Standard
Transport AES-256-GCM Already PQ-safe
Key exchange (classical) X25519 DH Defense-in-depth
Key exchange (PQ) ML-KEM-1024 FIPS 203
Key combiner HKDF-SHA256 NIST SP 800-56C
Certificate signatures ML-DSA-87 FIPS 204

Test plan

  • 7 KEM unit tests (keygen, encapsulate/decapsulate roundtrip, hybrid key mixing)
  • TestGoodHandshakePQ e2e: PQ CA -> host certs -> handshake -> tunnel -> bidirectional data
  • TestGoodHandshake (classical): zero regressions
  • TestGoodHandshakeNoOverlap (V2 classical): zero regressions
  • All 46 cert + noiseutil tests pass

lclose added 2 commits February 19, 2026 06:06
Implement NIST-recommended hybrid key exchange for the Noise IX handshake.
When Curve_PQ is selected, the handshake uses X25519 DH for classical
security and layers ML-KEM-1024 KEM on top for post-quantum security.
Both shared secrets are mixed via HKDF-SHA256 into the final symmetric
keys. Breaking either primitive alone is insufficient.

Handshake flow for Curve_PQ:
  Stage 0 (initiator): generate ephemeral ML-KEM-1024 keypair, include
    KemPublicKey in NebulaHandshakeDetails
  Stage 1 (responder): encapsulate to initiator's KEM pubkey, include
    KemCiphertext in response, mix KEM shared secret into Noise-derived
    dKey/eKey via HKDF
  Stage 2 (initiator): decapsulate KEM ciphertext, mix shared secret
    into dKey/eKey via same HKDF

Changes:
- connection_state.go: PQ KEM state fields, X25519 ephemeral keypair for
  Noise DH (cert's ML-KEM key is for KEM exchange, not DH)
- handshake_ix.go: KEM exchange in all 3 stages, pqMixCipherStates
  helper, PQ-aware cert pubkey validation
- nebula.proto/pb.go: KemPublicKey and KemCiphertext fields in
  NebulaHandshakeDetails
- noiseutil/pq.go: PQKEMKeypair, PQKEMEncapsulate, PQKEMDecapsulate,
  HybridMixKeys
- noiseutil/pq_test.go: 7 tests for KEM roundtrip, key mixing, full
  handshake simulation

Security properties:
- AES-256-GCM transport (unchanged, already PQ-safe)
- X25519 DH provides defense-in-depth against KEM failures
- ML-KEM-1024 KEM provides FIPS 203 post-quantum key exchange
- ML-DSA-87 certificate signatures (Phase 1) authenticate identities
- HKDF-SHA256 combiner per NIST SP 800-56C rev2
Complete the PQ integration across the full Nebula stack:

CLI (nebula-cert):
- ca.go: -curve PQ generates ML-DSA-87 CA keypair (V2 only)
- sign.go: PQ host certs use ML-KEM-1024 key agreement keys
- keygen.go: -curve PQ generates ML-KEM-1024 keypair

PKI integration:
- pki.go: PQ certs marshal with full public key (not stripped)
  because MarshalForHandshakes assumes pubkey == Noise PeerStatic
- cert.go: UnmarshalCertificateFromBytes for PQ cert reconstruction
- cert_v2.go: fix VerifyPrivateKey to use Unpack for ML-KEM-1024

Handshake fixes:
- handshake_ix.go: PQ path uses direct unmarshal instead of Recombine
  (cert pubkey is ML-KEM, not the X25519 PeerStatic)
- Skip pubkey==PeerStatic check for PQ (different key types)

Test infrastructure:
- cert_test/cert.go: PQ cases for NewTestCaCert and NewTestCert
- e2e/helpers_test.go: derive curve from CA cert (not hardcoded)
- e2e/handshakes_test.go: TestGoodHandshakePQ -- full tunnel test

TestGoodHandshakePQ proves: PQ CA signs host cert, two nodes establish
tunnel via hybrid X25519+ML-KEM-1024 handshake, bidirectional encrypted
data transfer works. 0.41s handshake time. Zero regressions in
classical handshake tests.
@disentangle-network disentangle-network merged commit e0845a7 into feature/pq-certificates Feb 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant