Remove Noise protocol references from wiki docs and tests
Delete 8 Noise-specific documentation pages (noise-xx.md,
transport-keys.md, adr-001/003/006, framing-codec.md) and update
~30 remaining wiki pages to reflect QUIC+TLS as the sole transport.
Remove obsolete Noise-based integration tests (auth_service.rs,
mls_group.rs). Code-side Noise removal was done in f334ed3.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -30,30 +30,6 @@ Because the ephemeral keys exist only for the duration of the handshake,
|
||||
compromising the server's long-term TLS certificate key (currently self-signed
|
||||
in quicnprotochat) does not reveal past session keys.
|
||||
|
||||
### Noise\_XX
|
||||
|
||||
Inside the QUIC stream, the Noise\_XX handshake
|
||||
(`Noise_XX_25519_ChaChaPoly_BLAKE2s`) provides an additional layer of forward
|
||||
secrecy. The Noise\_XX pattern uses both ephemeral and static X25519 keys:
|
||||
|
||||
```text
|
||||
→ e Initiator sends ephemeral public key
|
||||
← e, ee, s, es Responder: ephemeral, DH(e,e), static, DH(e,s)
|
||||
→ s, se Initiator: static, DH(s,e)
|
||||
```
|
||||
|
||||
The `ee` DH (ephemeral-ephemeral) provides forward secrecy: even if both
|
||||
parties' static keys (`s`) are later compromised, the ephemeral keys that
|
||||
contributed to `ee` have already been discarded.
|
||||
|
||||
The `es` and `se` DH operations mix in the static keys for authentication, but
|
||||
the session key depends on the ephemeral contribution. An attacker who
|
||||
compromises only the static key learns the identity of the parties but cannot
|
||||
recover the session key without the ephemeral key.
|
||||
|
||||
See [X25519 Transport Keys](transport-keys.md) for details on the static
|
||||
keypair.
|
||||
|
||||
## Application Layer Forward Secrecy
|
||||
|
||||
### MLS Epoch Ratchet
|
||||
@@ -125,9 +101,9 @@ operates at two independent layers:
|
||||
|
|
||||
v
|
||||
+------------------------------------------------------+
|
||||
| TLS 1.3 / Noise_XX |
|
||||
| Forward secrecy via ephemeral ECDHE / X25519 DH |
|
||||
| Even if TLS cert or Noise static key is compromised,|
|
||||
| TLS 1.3 (QUIC) |
|
||||
| Forward secrecy via ephemeral ECDHE |
|
||||
| Even if TLS cert is compromised, |
|
||||
| past transport sessions are protected. |
|
||||
+------------------------------------------------------+
|
||||
|
|
||||
@@ -201,5 +177,4 @@ uniformly to all group members.
|
||||
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- when keys are created and destroyed
|
||||
- [Post-Compromise Security](post-compromise-security.md) -- the complementary property (protecting the future)
|
||||
- [Threat Model](threat-model.md) -- attacker models and what FS protects against
|
||||
- [X25519 Transport Keys](transport-keys.md) -- Noise ephemeral DH details
|
||||
- [Ed25519 Identity Keys](identity-keys.md) -- long-term key that FS protects against compromising
|
||||
|
||||
@@ -138,26 +138,6 @@ The Ed25519 public key bytes (`public_key_bytes()`) are used as the
|
||||
KeyPackages indexed by this key, and the Delivery Service routes messages to
|
||||
queues indexed by the same key.
|
||||
|
||||
## Distinction from the X25519 Noise Keypair
|
||||
|
||||
It is critical to understand that the Ed25519 identity key and the X25519
|
||||
transport key are **separate keys on different curves serving different
|
||||
purposes**:
|
||||
|
||||
| Property | Ed25519 Identity Key | X25519 Noise Key |
|
||||
|----------|---------------------|-----------------|
|
||||
| Curve | Twisted Edwards (Ed25519) | Montgomery (Curve25519) |
|
||||
| Operation | Digital signatures | Diffie-Hellman key exchange |
|
||||
| Purpose | MLS credentials, AS registration | Noise\_XX mutual authentication |
|
||||
| Lifetime | Permanent (per client) | Per server process or per connection |
|
||||
| Persistence | Serialized to state file | Not serialized (M6 deferred) |
|
||||
| Source | `identity.rs` | `keypair.rs` |
|
||||
|
||||
Although both curves are related (Curve25519 is birationally equivalent to
|
||||
Ed25519's curve), the keys are **not interchangeable**. Converting between them
|
||||
requires explicit birational mapping, which quicnprotochat intentionally avoids
|
||||
to maintain clean separation of concerns.
|
||||
|
||||
## Serialization
|
||||
|
||||
`IdentityKeypair` implements `Serialize` and `Deserialize` (serde) by
|
||||
@@ -193,7 +173,6 @@ is deterministically re-derived on load.
|
||||
## Related Pages
|
||||
|
||||
- [Cryptography Overview](overview.md) -- algorithm inventory
|
||||
- [X25519 Transport Keys](transport-keys.md) -- the other keypair
|
||||
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- full lifecycle diagram
|
||||
- [Post-Compromise Security](post-compromise-security.md) -- how MLS credentials interact with PCS
|
||||
- [Threat Model](threat-model.md) -- what identity keys protect and do not protect
|
||||
|
||||
@@ -13,9 +13,6 @@ Key Type Creation Distribution Use
|
||||
Ed25519 Identity Once per client AS registration MLS signing, Zeroizing<[u8;32]>
|
||||
(OsRng) + MLS credential credential binding on struct drop
|
||||
|
||||
X25519 Noise Per server process Noise_XX handshake DH key exchange ZeroizeOnDrop
|
||||
or per client conn (in-band) (transport session) on struct drop
|
||||
|
||||
HPKE Init Key Per KeyPackage Uploaded to AS Decrypt Welcome Consumed by openmls;
|
||||
(openmls backend) in KeyPackage (join_group) deleted from keystore
|
||||
|
||||
@@ -93,73 +90,6 @@ The fingerprint (`SHA-256(public_key_bytes)`) is derived from the public key and
|
||||
is used as a compact identifier in logs. It is not secret and does not require
|
||||
zeroization.
|
||||
|
||||
## X25519 Noise Key
|
||||
|
||||
**Source:** `crates/quicnprotochat-core/src/keypair.rs`
|
||||
|
||||
The X25519 Noise key provides mutual authentication during the Noise\_XX
|
||||
handshake. It is shorter-lived than the identity key and is not currently
|
||||
persisted.
|
||||
|
||||
### Lifecycle
|
||||
|
||||
```text
|
||||
+-----------------+
|
||||
| OsRng |
|
||||
| (getrandom) |
|
||||
+--------+--------+
|
||||
|
|
||||
generate()
|
||||
|
|
||||
+--------v--------+
|
||||
| NoiseKeypair |
|
||||
| private: Secret | <-- StaticSecret (ZeroizeOnDrop)
|
||||
| public: PubKey | <-- 32-byte public key
|
||||
+--------+--------+
|
||||
|
|
||||
+--------------+--------------+
|
||||
| |
|
||||
private_bytes() public_bytes()
|
||||
-> Zeroizing<[u8;32]> -> [u8; 32]
|
||||
| |
|
||||
Passed to snow::Builder Exchanged during
|
||||
local_private_key() Noise_XX handshake
|
||||
| |
|
||||
Zeroizing copy drops Stored by peer
|
||||
immediately after use (not secret)
|
||||
| |
|
||||
+-------------+---------------+
|
||||
|
|
||||
Noise handshake completes
|
||||
|
|
||||
+-------------v--------------+
|
||||
| Transport session holds |
|
||||
| derived symmetric keys |
|
||||
| (managed by snow) |
|
||||
+-------------+--------------+
|
||||
|
|
||||
Connection closes
|
||||
|
|
||||
+-------------v--------------+
|
||||
| NoiseKeypair dropped |
|
||||
| StaticSecret::drop() |
|
||||
| overwrites scalar with 0 |
|
||||
+----------------------------+
|
||||
```
|
||||
|
||||
### Key Properties
|
||||
|
||||
- **Generation:** `StaticSecret::random_from_rng(OsRng)` generates a 32-byte
|
||||
Curve25519 scalar.
|
||||
- **Dual zeroization:** The `StaticSecret` itself implements `ZeroizeOnDrop`,
|
||||
and `private_bytes()` returns a `Zeroizing<[u8; 32]>` wrapper.
|
||||
- **Debug redaction:** The `Debug` impl shows only the first 4 bytes of the
|
||||
public key and prints `[redacted]` for the private key.
|
||||
- **No serialization:** `NoiseKeypair` does not implement `Serialize`. Persistence
|
||||
is deferred to M6.
|
||||
- **Current lifetime:** Per server process start (server) or per connection
|
||||
attempt (client). After M6, keys may be persisted with passphrase encryption.
|
||||
|
||||
## HPKE Init Keys
|
||||
|
||||
**Source:** `crates/quicnprotochat-core/src/keystore.rs` and
|
||||
@@ -364,8 +294,6 @@ attacks.
|
||||
|----------|----------------------|------|
|
||||
| Ed25519 seed | `Zeroizing<[u8; 32]>` | `IdentityKeypair` drop |
|
||||
| Ed25519 seed (accessor) | Plain `[u8; 32]` copy | Caller responsibility |
|
||||
| X25519 private | `ZeroizeOnDrop` (x25519-dalek) | `NoiseKeypair` drop |
|
||||
| X25519 private (accessor) | `Zeroizing<[u8; 32]>` | Accessor drop |
|
||||
| HPKE init private | Managed by openmls/`DiskKeyStore` | After Welcome processing |
|
||||
| MLS epoch keys | Managed by openmls internally | After Commit processing |
|
||||
| Hybrid IKM | `Zeroizing<Vec<u8>>` | After HKDF derivation |
|
||||
@@ -396,7 +324,6 @@ attacks.
|
||||
|
||||
- [Cryptography Overview](overview.md) -- algorithm inventory
|
||||
- [Ed25519 Identity Keys](identity-keys.md) -- identity key details
|
||||
- [X25519 Transport Keys](transport-keys.md) -- transport key details
|
||||
- [Forward Secrecy](forward-secrecy.md) -- how key deletion enables FS
|
||||
- [Post-Compromise Security](post-compromise-security.md) -- epoch advancement
|
||||
- [Post-Quantum Readiness](post-quantum-readiness.md) -- hybrid KEM integration
|
||||
|
||||
@@ -10,10 +10,8 @@ the security margin it provides.
|
||||
| Algorithm | Purpose | Crate | Security Level |
|
||||
|-----------|---------|-------|----------------|
|
||||
| Ed25519 | Identity signing, MLS credentials | `ed25519-dalek 2` | 128-bit classical |
|
||||
| X25519 | Noise DH, MLS HPKE key exchange | `x25519-dalek 2` | 128-bit classical |
|
||||
| ChaCha20-Poly1305 | Noise AEAD | `chacha20poly1305 0.10` | 256-bit key |
|
||||
| X25519 | MLS HPKE key exchange, Hybrid KEM | `x25519-dalek 2` | 128-bit classical |
|
||||
| AES-128-GCM | MLS AEAD | `openmls` (via RustCrypto) | 128-bit |
|
||||
| BLAKE2s | Noise hash function | `snow 0.9` (built-in) | 128-bit |
|
||||
| SHA-256 | Key fingerprints, HKDF | `sha2 0.10` | 128-bit collision resistance |
|
||||
| ML-KEM-768 | Post-quantum KEM | `ml-kem 0.2` | NIST Level 3 (~192-bit PQ) |
|
||||
| HKDF-SHA256 | Key derivation | `hkdf 0.12` | Depends on input entropy |
|
||||
@@ -26,19 +24,11 @@ the security margin it provides.
|
||||
|
||||
### Transport Layer
|
||||
|
||||
The transport layer uses two independent encryption substrates:
|
||||
|
||||
1. **QUIC/TLS 1.3** (via `quinn 0.11` + `rustls 0.23`): Provides the
|
||||
outermost encrypted tunnel. The TLS 1.3 handshake negotiates an ephemeral
|
||||
ECDHE key exchange (X25519 or P-256, depending on the peer) and an AEAD
|
||||
cipher (AES-128-GCM or ChaCha20-Poly1305). This layer protects connection
|
||||
metadata from passive network observers.
|
||||
|
||||
2. **Noise\_XX** (via `snow 0.9`): Runs inside the QUIC stream. The Noise
|
||||
pattern `Noise_XX_25519_ChaChaPoly_BLAKE2s` provides mutual authentication
|
||||
using static X25519 keys, with ChaCha20-Poly1305 as the AEAD and BLAKE2s
|
||||
as the hash function. See [X25519 Transport Keys](transport-keys.md) for
|
||||
details on the keypair.
|
||||
**QUIC/TLS 1.3** (via `quinn 0.11` + `rustls 0.23`): Provides the encrypted
|
||||
transport tunnel. The TLS 1.3 handshake negotiates an ephemeral ECDHE key
|
||||
exchange (X25519 or P-256, depending on the peer) and an AEAD cipher
|
||||
(AES-128-GCM or ChaCha20-Poly1305). This layer protects connection metadata
|
||||
from passive network observers.
|
||||
|
||||
### Application Layer
|
||||
|
||||
@@ -81,7 +71,6 @@ and is considered adequate for the foreseeable future.
|
||||
Layer Classical Security Post-Quantum Security
|
||||
--------------------------------------------------------------------
|
||||
QUIC/TLS 1.3 128-bit (ECDHE) None (classical only)
|
||||
Noise_XX 128-bit (X25519) None (classical only)
|
||||
MLS (content) 128-bit (AES-128-GCM) None (classical only)
|
||||
Hybrid KEM (M5+) 128-bit (X25519) ~192-bit (ML-KEM-768)
|
||||
```
|
||||
@@ -94,7 +83,6 @@ security properties these algorithms enable.
|
||||
## Related Pages
|
||||
|
||||
- [Ed25519 Identity Keys](identity-keys.md) -- long-term signing keypair
|
||||
- [X25519 Transport Keys](transport-keys.md) -- Noise handshake keypair
|
||||
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- creation through destruction
|
||||
- [Forward Secrecy](forward-secrecy.md) -- past message protection
|
||||
- [Post-Compromise Security](post-compromise-security.md) -- future message recovery
|
||||
|
||||
@@ -163,7 +163,7 @@ hybrid KEM for HPKE init key exchange:
|
||||
via the MLS group context extensions. Classical-only clients can still
|
||||
participate in groups that do not require PQ protection.
|
||||
|
||||
## The PQ Gap (ADR-006)
|
||||
## The PQ Gap
|
||||
|
||||
There is an important asymmetry in quicnprotochat's post-quantum protection:
|
||||
|
||||
@@ -171,7 +171,6 @@ There is an important asymmetry in quicnprotochat's post-quantum protection:
|
||||
Layer Classical Protection Post-Quantum Protection
|
||||
---------------------------------------------------------------------
|
||||
QUIC/TLS 1.3 Yes (ECDHE) No
|
||||
Noise_XX Yes (X25519) No
|
||||
MLS content (M5+) Yes (X25519 DHKEM) Yes (hybrid KEM)
|
||||
```
|
||||
|
||||
@@ -182,8 +181,8 @@ MLS content (M5+) Yes (X25519 DHKEM) Yes (hybrid KEM)
|
||||
the message payload.
|
||||
|
||||
- **Transport metadata** (who connects to the server, when, message sizes) is
|
||||
protected only by classical cryptography. A quantum attacker who recorded the
|
||||
TLS/Noise handshake transcripts could, in theory, recover the transport session
|
||||
protected only by classical ECDHE. A quantum attacker who recorded the
|
||||
TLS handshake transcripts could, in theory, recover the transport session
|
||||
keys and observe the metadata.
|
||||
|
||||
This is the **PQ gap**: content is safe, but metadata is not.
|
||||
@@ -195,10 +194,6 @@ the IETF and is supported by some TLS libraries, but `rustls` does not yet
|
||||
support it in a stable release. When `rustls` adds ML-KEM support, quicnprotochat
|
||||
will adopt it to close the PQ gap at the transport layer.
|
||||
|
||||
Similarly, post-quantum Noise patterns are an active research area but are not
|
||||
yet standardized. The `snow` crate does not currently support post-quantum DH
|
||||
primitives.
|
||||
|
||||
## Harvest-Now, Decrypt-Later Risk
|
||||
|
||||
The "harvest-now, decrypt-later" (HNDL) threat model assumes an adversary who:
|
||||
@@ -213,7 +208,7 @@ In quicnprotochat's case:
|
||||
ML-KEM-768, which resists quantum attacks. Even if the recorded traffic is
|
||||
decrypted at the transport layer, the MLS ciphertext inside is still protected.
|
||||
|
||||
- **Transport metadata is at risk.** An HNDL attacker who records TLS/Noise
|
||||
- **Transport metadata is at risk.** An HNDL attacker who records TLS
|
||||
handshakes today could, with a future quantum computer, recover the transport
|
||||
session keys and observe:
|
||||
- Which clients connected to the server and when.
|
||||
|
||||
@@ -17,12 +17,12 @@ payloads. Cannot modify traffic.
|
||||
- Connection metadata: which IP addresses connect to the server and when.
|
||||
- Message timing and sizes: observable patterns (e.g., message frequency,
|
||||
payload lengths) that could reveal communication patterns.
|
||||
- Encrypted payloads: TLS 1.3 ciphertext containing Noise ciphertext containing
|
||||
MLS ciphertext. Three layers of encryption must be broken to access content.
|
||||
- Encrypted payloads: TLS 1.3 ciphertext containing MLS ciphertext. Both layers
|
||||
of encryption must be broken to access content.
|
||||
|
||||
**What they cannot learn:**
|
||||
|
||||
- Message content: protected by MLS encryption inside Noise inside TLS.
|
||||
- Message content: protected by MLS encryption inside TLS.
|
||||
- Group membership details: MLS Commits are encrypted.
|
||||
- Which specific recipient a message is destined for (from the network
|
||||
perspective, all messages go to the server).
|
||||
@@ -51,7 +51,7 @@ state-level adversary).
|
||||
|
||||
**What they cannot do (assuming no cert MITM):**
|
||||
|
||||
- Decrypt TLS/Noise traffic: both use authenticated ephemeral key exchange.
|
||||
- Decrypt TLS traffic: TLS 1.3 uses authenticated ephemeral key exchange.
|
||||
- Forge MLS messages: MLS Commits and application messages are signed by the
|
||||
sender's Ed25519 identity key. The attacker does not possess any member's
|
||||
signing key.
|
||||
@@ -145,8 +145,8 @@ The healing window is the time between the compromise and the next Commit. See
|
||||
| Message integrity | MLS signing (Ed25519) | Forgery by server or network |
|
||||
| Group membership changes | MLS Commits (signed, authenticated) | Unauthorized modification |
|
||||
| Key exchange material | Single-use HPKE init keys | Replay, forward compromise |
|
||||
| Transport confidentiality | TLS 1.3 + Noise\_XX (double encryption) | Passive eavesdropper |
|
||||
| Transport integrity | TLS 1.3 AEAD + Noise AEAD | Active network attacker |
|
||||
| Transport confidentiality | TLS 1.3 (QUIC) | Passive eavesdropper |
|
||||
| Transport integrity | TLS 1.3 AEAD | Active network attacker |
|
||||
| Past messages | Forward secrecy (epoch key deletion) | Future client compromise |
|
||||
| Future messages | Post-compromise security (ratchet tree update) | Past client compromise |
|
||||
|
||||
@@ -223,16 +223,16 @@ log of public key bindings.
|
||||
### Classical-Only Transport
|
||||
|
||||
As discussed in [Post-Quantum Readiness](post-quantum-readiness.md), the
|
||||
transport layer (TLS 1.3, Noise\_XX) uses classical-only cryptography. An
|
||||
adversary performing harvest-now-decrypt-later (HNDL) could record transport
|
||||
traffic today and decrypt it with a future quantum computer, revealing transport
|
||||
transport layer (QUIC/TLS 1.3) uses classical-only ECDHE. An adversary
|
||||
performing harvest-now-decrypt-later (HNDL) could record transport traffic
|
||||
today and decrypt it with a future quantum computer, revealing transport
|
||||
metadata.
|
||||
|
||||
**Impact:** Future exposure of transport metadata (not content, assuming
|
||||
hybrid KEM is active for MLS).
|
||||
|
||||
**Mitigation path:** Adopt post-quantum TLS (ML-KEM in TLS 1.3 handshake) when
|
||||
`rustls` supports it. Investigate post-quantum Noise patterns.
|
||||
`rustls` supports it.
|
||||
|
||||
## Future Mitigations
|
||||
|
||||
@@ -313,14 +313,14 @@ communication patterns from traffic analysis.
|
||||
|
||||
| Threat | Current Protection | Gap | Planned Fix |
|
||||
|--------|-------------------|-----|-------------|
|
||||
| Passive eavesdropper | TLS + Noise + MLS (3 layers) | Traffic analysis | Padding, Tor |
|
||||
| Active MITM | TLS 1.3 + Noise\_XX | Self-signed certs | Cert pinning, CA |
|
||||
| Passive eavesdropper | TLS 1.3 + MLS (2 layers) | Traffic analysis | Padding, Tor |
|
||||
| Active MITM | TLS 1.3 (QUIC) | Self-signed certs | Cert pinning, CA |
|
||||
| Compromised server | MLS E2E encryption | Metadata visible | Sealed Sender, PIR |
|
||||
| Compromised client | FS + PCS | Current epoch exposed | Periodic Updates |
|
||||
| Spam/flooding | None | No auth on DS | AUTHZ\_PLAN |
|
||||
| Key substitution | None | BasicCredential only | Key Transparency |
|
||||
| Quantum adversary (content) | Hybrid KEM (M5+) | Pre-M5 messages | Deploy hybrid ASAP |
|
||||
| Quantum adversary (transport) | None | Classical TLS/Noise | PQ TLS, PQ Noise |
|
||||
| Quantum adversary (transport) | None | Classical TLS (ECDHE) | PQ TLS |
|
||||
|
||||
## Related Pages
|
||||
|
||||
|
||||
@@ -1,191 +0,0 @@
|
||||
# X25519 Transport Keys
|
||||
|
||||
The X25519 transport keypair is used for mutual authentication in the Noise\_XX
|
||||
handshake. Unlike the [Ed25519 identity key](identity-keys.md), which is a
|
||||
signing key, the X25519 key performs Diffie-Hellman key exchange to establish
|
||||
encrypted transport sessions.
|
||||
|
||||
**Source:** `crates/quicnprotochat-core/src/keypair.rs`
|
||||
|
||||
## Structure
|
||||
|
||||
The `NoiseKeypair` struct holds two fields:
|
||||
|
||||
```rust
|
||||
pub struct NoiseKeypair {
|
||||
/// Private scalar -- zeroized on drop via x25519_dalek's ZeroizeOnDrop impl.
|
||||
private: StaticSecret,
|
||||
/// Corresponding public key -- derived from private at construction time.
|
||||
public: PublicKey,
|
||||
}
|
||||
```
|
||||
|
||||
| Field | Type | Size | Secret? |
|
||||
|-------|------|------|---------|
|
||||
| `private` | `x25519_dalek::StaticSecret` | 32 bytes | Yes -- `ZeroizeOnDrop` |
|
||||
| `public` | `x25519_dalek::PublicKey` | 32 bytes | No -- safe to log/transmit |
|
||||
|
||||
## Key Generation
|
||||
|
||||
A fresh keypair is generated from the OS CSPRNG:
|
||||
|
||||
```rust
|
||||
use quicnprotochat_core::keypair::NoiseKeypair;
|
||||
|
||||
let keypair = NoiseKeypair::generate();
|
||||
// private: random 32-byte scalar from OsRng
|
||||
// public: derived via Curve25519 scalar multiplication
|
||||
```
|
||||
|
||||
Internally:
|
||||
|
||||
```rust
|
||||
pub fn generate() -> Self {
|
||||
let private = StaticSecret::random_from_rng(OsRng);
|
||||
let public = PublicKey::from(&private);
|
||||
Self { private, public }
|
||||
}
|
||||
```
|
||||
|
||||
The `StaticSecret::random_from_rng` call uses the operating system's CSPRNG
|
||||
(`getrandom` on Linux, `SecRandomCopyBytes` on macOS) and is suitable for
|
||||
generating long-lived static identity keys.
|
||||
|
||||
## Accessing Key Material
|
||||
|
||||
### Private Key Bytes
|
||||
|
||||
The `private_bytes()` method returns the raw 32-byte private scalar wrapped in
|
||||
`Zeroizing<[u8; 32]>`:
|
||||
|
||||
```rust
|
||||
pub fn private_bytes(&self) -> Zeroizing<[u8; 32]> {
|
||||
Zeroizing::new(self.private.to_bytes())
|
||||
}
|
||||
```
|
||||
|
||||
The `Zeroizing` wrapper ensures the caller's copy of the key material is
|
||||
overwritten with zeros when it goes out of scope. The intended usage pattern is
|
||||
to pass the bytes directly to `snow::Builder` and let the wrapper drop
|
||||
immediately:
|
||||
|
||||
```rust
|
||||
let private = keypair.private_bytes();
|
||||
let session = snow::Builder::new(params)
|
||||
.local_private_key(&private[..])
|
||||
.build_initiator()?;
|
||||
// private is zeroized here when it falls out of scope.
|
||||
```
|
||||
|
||||
### Public Key Bytes
|
||||
|
||||
The `public_bytes()` method returns a plain `[u8; 32]`:
|
||||
|
||||
```rust
|
||||
pub fn public_bytes(&self) -> [u8; 32] {
|
||||
self.public.to_bytes()
|
||||
}
|
||||
```
|
||||
|
||||
The public key is not secret and may be freely cloned, logged, or transmitted
|
||||
over the wire.
|
||||
|
||||
## Zeroization Strategy
|
||||
|
||||
The `NoiseKeypair` has two layers of zeroization protection:
|
||||
|
||||
1. **`StaticSecret` (inner):** The `x25519_dalek` crate implements
|
||||
`ZeroizeOnDrop` on `StaticSecret`. When the `NoiseKeypair` struct is dropped,
|
||||
the private scalar is automatically overwritten with zeros.
|
||||
|
||||
2. **`Zeroizing<[u8; 32]>` (accessor):** When callers use `private_bytes()`, the
|
||||
returned copy is also wrapped in `Zeroizing`, so the caller's copy is zeroed
|
||||
on drop too.
|
||||
|
||||
This dual-layer approach ensures that key material does not linger in memory
|
||||
whether the key is accessed by value or held in the struct.
|
||||
|
||||
## Debug Redaction
|
||||
|
||||
The `Debug` implementation intentionally redacts the private key and shows only
|
||||
the first 4 bytes of the public key as a sanity identifier:
|
||||
|
||||
```rust
|
||||
impl std::fmt::Debug for NoiseKeypair {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let pub_bytes = self.public_bytes();
|
||||
write!(
|
||||
f,
|
||||
"NoiseKeypair {{ public: {:02x}{:02x}{:02x}{:02x}..., private: [redacted] }}",
|
||||
pub_bytes[0], pub_bytes[1], pub_bytes[2], pub_bytes[3],
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
This prevents accidental leakage of secret material through logging or
|
||||
`println!("{:?}", keypair)`.
|
||||
|
||||
## Role in Noise\_XX
|
||||
|
||||
The Noise\_XX handshake pattern performs mutual authentication: both initiator
|
||||
and responder prove possession of their static X25519 keys. The handshake
|
||||
proceeds in three messages:
|
||||
|
||||
```text
|
||||
→ e (initiator sends ephemeral public key)
|
||||
← e, ee, s, es (responder sends ephemeral + static, DH results)
|
||||
→ s, se (initiator sends static, final DH result)
|
||||
```
|
||||
|
||||
After the handshake completes, both parties have:
|
||||
|
||||
- Authenticated each other's static X25519 public keys.
|
||||
- Derived symmetric transport keys from the DH shared secrets.
|
||||
- Established forward secrecy via the ephemeral keys (which are discarded).
|
||||
|
||||
The `NoiseKeypair` provides the `s` (static) key in this pattern. Ephemeral keys
|
||||
(`e`) are generated internally by `snow` during each handshake.
|
||||
|
||||
## Ephemeral vs Static
|
||||
|
||||
In the context of Noise\_XX:
|
||||
|
||||
- **Ephemeral keys** are generated per-handshake by `snow` and discarded after
|
||||
key derivation. They provide forward secrecy.
|
||||
- **Static keys** (the `NoiseKeypair`) are longer-lived and provide identity.
|
||||
In the current implementation, the server generates a new `NoiseKeypair` per
|
||||
process start, and the client generates one per connection.
|
||||
|
||||
## Persistence
|
||||
|
||||
`NoiseKeypair` **intentionally does not implement `Serialize`**. Key persistence
|
||||
to disk is deferred to milestone M6, which will add:
|
||||
|
||||
- Appropriate file permission checks (e.g., `0600` on Unix).
|
||||
- Optional passphrase-based encryption of the key file.
|
||||
- A key rotation mechanism.
|
||||
|
||||
Until M6, the transport key is ephemeral to the process lifetime. This is
|
||||
acceptable because the Noise key is not used for MLS group membership -- that
|
||||
role belongs to the [Ed25519 identity key](identity-keys.md).
|
||||
|
||||
## Comparison with Ed25519 Identity Key
|
||||
|
||||
| Property | X25519 Noise Key | Ed25519 Identity Key |
|
||||
|----------|-----------------|---------------------|
|
||||
| Curve | Montgomery (Curve25519) | Twisted Edwards (Ed25519) |
|
||||
| Operation | Diffie-Hellman key exchange | Digital signatures |
|
||||
| Purpose | Noise\_XX mutual authentication | MLS credentials, AS registration |
|
||||
| Lifetime | Per process / per connection | Permanent (per client) |
|
||||
| Serialization | Not implemented | Serde (seed bytes) |
|
||||
| Zeroize | `ZeroizeOnDrop` (x25519-dalek) | `Zeroizing<[u8; 32]>` (manual) |
|
||||
| Source file | `keypair.rs` | `identity.rs` |
|
||||
|
||||
## Related Pages
|
||||
|
||||
- [Cryptography Overview](overview.md) -- algorithm inventory
|
||||
- [Ed25519 Identity Keys](identity-keys.md) -- the other keypair
|
||||
- [Key Lifecycle and Zeroization](key-lifecycle.md) -- full lifecycle diagram
|
||||
- [Forward Secrecy](forward-secrecy.md) -- how ephemeral DH provides FS at the transport layer
|
||||
- [Noise\_XX Handshake](../protocol-layers/noise-xx.md) -- protocol details
|
||||
Reference in New Issue
Block a user