Biscuits are used to store and load session state. To enable replay protection, the protocol uses biscuit_ctr, biscuit_used, and biscuit_no variables.
Storing a Biscuit
When calling store_biscuit(), the biscuit_ctr is incremented. The resulting ciphertext includes the peer ID (pidi), the biscuit number (biscuit_no), and the current chaining key (ck). The biscuit_ct is then mixed into the state.
Loading a Biscuit
When calling load_biscuit(biscuit_ct), the protocol:
- Decrypts the biscuit using the
biscuit_key. - Looks up the peer using
pt.pidi. - Verifies replay protection: For protocol versions
< 0.3.0, it asserts that pt.biscuit_no >= peer.biscuit_used. - Restores the chaining key (
ck ← pt.ck). - Re-applies
mix(biscuit_ct) to ensure the chaining key is synchronized.
Important: Because mix(biscuit_ct) updates the chaining key but that update is not stored inside the biscuit, it must be reapplied during load_biscuit. Handshake code on both the initiator and responder sides must also handle any subsequent mix operations to keep ck in sync.
fn store_biscuit() {
biscuit_ctr ← biscuit_ctr + 1;
let k = biscuit_key;
let n = random_nonce();
let pt = Biscuit {
pidi: lhash("peer id", spki),
biscuit_no: biscuit_ctr,
ck: ck,
};
let ad = lhash(
"biscuit additional data",
spkr, sidi, sidr);
let ct = XAEAD::enc(k, n, pt, ad);
let biscuit_ct = concat(n, ct);
mix(biscuit_ct)
biscuit_ct
}
fn load_biscuit(biscuit_ct) {
// Decrypt the biscuit
let k = biscuit_key;
let concat(n, ct) = biscuit_ct;
let ad = lhash(
"biscuit additional data",
spkr, sidi, sidr);
let pt : Biscuit = XAEAD::dec(k, n, ct, ad);
// Find the peer and apply retransmission protection
lookup_peer(pt.pidi);
// In December 2024, the InitConf retransmission mechanism was redesigned
// in a backwards-compatible way. See the changelog.
//
// -- 2024-11-30, Karolin Varner
if (protocol_version!(< "0.3.0")) {
// Ensure that the biscuit is used only once
assert(pt.biscuit_no >= peer.biscuit_used);
}
// Restore the chaining key
ck ← pt.ck;
mix(biscuit_ct);
// Expose the biscuit no,
// so the handshake code can differentiate
// retransmission requests and first time handshake completion
pt.biscuit_no
}