Skip to main content

Crate sealed_channel

Crate sealed_channel 

Source
Expand description

§sealed-channel

sealed-channel is a small, transport-agnostic authenticated record channel. It derives directional ChaCha20-Poly1305 record keys from:

  • a high-entropy pre-shared secret (PSK),
  • an externally supplied ephemeral Diffie-Hellman shared secret,
  • the exact wire bytes of the client and server handshake messages.

The crate is pure compute. It performs no networking, no I/O, and no random number generation. Callers own the transport, the ephemeral key exchange, and all randomness.

§What It Provides

  • HKDF-SHA256 key schedule with transcript binding.
  • Directional client-to-server and server-to-client AEAD keys.
  • Strict in-order record opening with replay and reordering rejection.
  • Deterministic nonce construction that fails closed on counter exhaustion.
  • #![no_std] with alloc.
  • #![forbid(unsafe_code)].

§Usage

use sealed_channel::schedule;

fn main() -> Result<(), sealed_channel::Error> {
    let psk = b"a-high-entropy-pre-shared-secret";
    let dh_shared_secret = [7_u8; 32];
    let client_hello = b"client hello wire bytes";
    let server_challenge = b"server challenge wire bytes";

    let client_keys = schedule::derive(
        psk,
        &dh_shared_secret,
        client_hello,
        server_challenge,
    )?;
    let server_keys = schedule::derive(
        psk,
        &dh_shared_secret,
        client_hello,
        server_challenge,
    )?;

    let (mut client_seal, _client_open) = client_keys.into_client();
    let (_server_seal, mut server_open) = server_keys.into_server();

    let frame = client_seal.seal(b"hello")?;
    let plaintext = server_open.open(&frame)?;

    assert_eq!(plaintext.as_slice(), b"hello");
    Ok(())
}

In a real protocol, each side should use fresh ephemeral keys per connection, derive the same 32-byte DH shared secret, and pass the exact handshake bytes seen on the wire.

§Construction

The key schedule computes:

transcript = SHA256(domain || len(client_hello) || client_hello
                          || len(server_challenge) || server_challenge)
ikm        = dh_shared_secret || psk
hk         = HKDF-SHA256(salt = transcript, ikm)

Four domain-separated labels expand to:

  • client-to-server AEAD key,
  • server-to-client AEAD key,
  • client-to-server nonce prefix,
  • server-to-client nonce prefix.

Records are encoded as:

[0xE0] || seq:u64be || ChaCha20-Poly1305(ciphertext || tag)

The clear 0xE0 || seq header is authenticated as AEAD additional data. The nonce is:

nonce_prefix:4 || seq:u64be

The sequence number is strict and monotonic per direction. Replays, reordering, malformed frames, and authentication failures return errors without advancing opener state.

§Security Boundary

Authentication strength is the entropy of the PSK.

An active relay can run its own Diffie-Hellman exchange with each peer. The PSK is therefore the secret that authenticates the channel against that active attacker. Use a high-entropy PSK, at least 128 bits and preferably 256 bits.

Do not use a PIN, passphrase, or other low-entropy human secret as the PSK. Authenticating weak secrets against active attackers requires a PAKE such as SPAKE2+ or OPAQUE. This crate is not a PAKE.

Forward secrecy depends on the caller using fresh ephemeral DH keys and discarding the ephemeral private keys after the handshake.

§Non-Goals

  • No transport security or metadata confidentiality.
  • No TLS replacement.
  • No PAKE.
  • No randomness.
  • No handshake format.
  • No network protocol.

§Status

This is a pre-1.0 crate and has not had an external security audit. Treat the API and wire format as subject to change until the crate reaches a stable release.

§License

Licensed under either of Apache-2.0 or MIT at your option.

§Construction

sealed-channel is a transport-agnostic, forward-secret authenticated record channel. It performs pure compute only: it knows nothing about sockets, WebSockets, JSON, browsers, tokens, or any transport, and it performs no RNG and no I/O. The caller supplies all randomness, the ephemeral keys, and the Diffie-Hellman shared secret.

Keys are derived with HKDF-SHA256 from the pre-shared secret (PSK), the externally-supplied ephemeral DH shared secret, and a transcript hash that binds the exact bytes of both handshake messages. Records are sealed with ChaCha20-Poly1305, with the cleartext magic || seq header used as the AEAD additional authenticated data and a per-direction nonce prefix concatenated with the big-endian sequence number used as the nonce.

Forward secrecy comes from the ephemeral DH shared secret supplied by the caller: once the ephemeral private keys are discarded, past traffic cannot be decrypted even if the PSK is later compromised.

§CRITICAL SECURITY INVARIANT

Authentication strength equals the entropy of the PSK. An active man-in-the-middle (e.g. a relay) can perform its own DH with each side and therefore knows both DH shared secrets; the ONLY thing keeping it out is the PSK, which it does not know. The PSK MUST be high-entropy (at least 128 bits, 256 recommended). NEVER pass a low-entropy secret such as a PIN or password as the PSK — authenticating a weak secret against an active MITM requires a PAKE (e.g. SPAKE2+ / OPAQUE), which this crate is NOT.

Re-exports§

pub use error::Error;
pub use record::RecordOpener;
pub use record::RecordSealer;
pub use schedule::SessionKeys;
pub use transcript::transcript_hash;

Modules§

error
Error type for the sealed channel.
record
Authenticated record framing.
schedule
Key schedule: derives directional AEAD keys and nonce prefixes from the pre-shared secret, the ephemeral DH shared secret, and the handshake transcript using HKDF-SHA256.
transcript
Handshake transcript hashing.