Skip to content

Implement and harden DTLS 1.2 Connection ID (RFC 9146)#104

Draft
jaredwolff wants to merge 1 commit intoalgesten:mainfrom
circuitdojo:dtls-conn-id
Draft

Implement and harden DTLS 1.2 Connection ID (RFC 9146)#104
jaredwolff wants to merge 1 commit intoalgesten:mainfrom
circuitdojo:dtls-conn-id

Conversation

@jaredwolff
Copy link
Copy Markdown
Contributor

Adds DTLS 1.2 Connection ID (RFC 9146) with record-layer hardening and adjacent DTLS 1.3 (RFC 9147) / RFC 5246 / RFC 8446 extension-handling gaps closed.

Record-layer hardening (RFC 9146 / RFC 6347 §4.1.2):

  • Wire CID constant-time compared and threaded into AAD (not the cached copy); wire version bytes likewise threaded through the AAD.
  • Malformed record boundaries, too-short AEAD records, and the 2^14 inner-plaintext ceiling all silent-drop per §4.1.2.7, symmetric with the send-side guard.
  • Epoch-0 tls12_cid rejected at DTLSRecord::parse; legacy-framed epoch-1 records rejected when inbound CID is expected.
  • Pre-CCS CID records cleartext-filtered against the negotiated inbound CID before entering queue_rx, closing a spray DoS vector.
  • Replay window updates only after AEAD success AND CID inner-type unwrap succeeds — peer-bug inner types no longer consume sequence slots.

CID state model:

  • Single private CidState enum replaces separate our_cid/peer_cid fields so valid combinations are unrepresentable outside the module.
  • Per-direction activation: outbound armed at negotiation, inbound flipped live only after the peer's ChangeCipherSpec.

Extension parser and HVR cookie:

  • Duplicate extensions (supported and unknown) rejected fail-closed via a 64-codepoint tracker with try_push(...).map_err(...)?, including a defense-in-depth ExtensionVec dedup pass.
  • HVR cookie HMAC binds the raw offered-CID extension bytes with a 0x00/0x01 marker, catching CH1/CH2 CID swap attempts.
  • Stateful offered_cid flag on dtls13::Client replaces the earlier config-based proxy so the direct Dtls::new_13 path still rejects unsolicited 0x0036 echoes per RFC 8446 §4.2.

Outbound sizing and lifetimes:

  • Shared Engine::outbound_record_overhead keeps create_handshake fragmentation in sync with CID + AEAD overhead.
  • New errors: Error::Oversized, Error::MtuTooSmall, Error::SequenceNumberExhausted; 2^14 plaintext ceiling enforced both sides; 48-bit sequence wrap surfaced distinctly from payload size failures.

Auto-mode:

  • Hybrid ClientHello now emits connection_id(0x0036) when configured, so CH1/CH2 carry identical CID bytes and the stateless cookie binds once.
  • DTLS 1.3 client silent-accepts a server echo only when its own ClientHello on the wire solicited 0x0036.
  • DTLS 1.3 server and client explicitly log-ignore 0x0036 per RFC.

@jaredwolff
Copy link
Copy Markdown
Contributor Author

Diff against origin/main

Area Code Comments Blank Total+ Net
src/ 1,399 1,021 148 2,568 +2,419
tests/ 2,234 485 440 3,159 +3,059
README.md 21 +21
Total 3,633 1,506 588 5,748 +5,499
  • Tests-to-code ratio: 1.60 : 1 (by code lines)
  • Comment share of non-blank src additions: 42% — RFC rationale, not noise

Files touched (top by modification weight)

File +Lines -Lines
src/dtls12/incoming.rs 898 30
src/dtls12/engine.rs 365 37
src/config.rs 253 3
src/dtls12/message/client_hello.rs 185 5
src/crypto/dtls_aead.rs 153 6
src/dtls12/server.rs 131 6
src/dtls12/client.rs 105 3
src/dtls13/client.rs 90 10
src/dtls12/message/server_hello.rs 73 4
src/lib.rs 70 0
src/error.rs 51 0
src/dtls12/message/extensions/connection_id.rs 97 0 (new file)
tests/dtls12/cid.rs 2,404 0 (new file)

@jaredwolff jaredwolff force-pushed the dtls-conn-id branch 2 times, most recently from 4ed8cfb to 326e4b1 Compare April 24, 2026 07:09
Comment thread tests/dtls12/cid.rs Fixed
Comment thread tests/dtls12/cid.rs Fixed
Comment thread tests/dtls12/cid.rs Fixed
Comment thread tests/dtls12/cid.rs Fixed
Adds DTLS 1.2 Connection ID (RFC 9146) with record-layer hardening and
adjacent DTLS 1.3 (RFC 9147) / RFC 5246 / RFC 8446 extension-handling
gaps closed.

Record-layer hardening (RFC 9146 / RFC 6347 §4.1.2):
- Wire CID constant-time compared and threaded into AAD (not the cached
  copy); wire version bytes likewise threaded through the AAD.
- Malformed record boundaries, too-short AEAD records, and the 2^14
  inner-plaintext ceiling all silent-drop per §4.1.2.7, symmetric with
  the send-side guard.
- Epoch-0 `tls12_cid` rejected at DTLSRecord::parse; legacy-framed
  epoch-1 records rejected when inbound CID is expected.
- Pre-CCS CID records cleartext-filtered against the negotiated inbound
  CID before entering queue_rx, closing a spray DoS vector.
- Replay window updates only after AEAD success AND CID inner-type
  unwrap succeeds — peer-bug inner types no longer consume sequence
  slots.

CID state model:
- Single private `CidState` enum replaces separate `our_cid`/`peer_cid`
  fields so valid combinations are unrepresentable outside the module.
- Per-direction activation: outbound armed at negotiation, inbound
  flipped live only after the peer's ChangeCipherSpec.

Extension parser and HVR cookie:
- Duplicate extensions (supported and unknown) rejected fail-closed via
  a 64-codepoint tracker with `try_push(...).map_err(...)?`, including
  a defense-in-depth ExtensionVec dedup pass.
- HVR cookie HMAC binds the raw offered-CID extension bytes with a
  0x00/0x01 marker, catching CH1/CH2 CID swap attempts.
- Stateful `offered_cid` flag on dtls13::Client replaces the earlier
  config-based proxy so the direct `Dtls::new_13` path still rejects
  unsolicited `0x0036` echoes per RFC 8446 §4.2.

Outbound sizing and lifetimes:
- Shared `Engine::outbound_record_overhead` keeps create_handshake
  fragmentation in sync with CID + AEAD overhead.
- New errors: `Error::Oversized`, `Error::MtuTooSmall`,
  `Error::SequenceNumberExhausted`; 2^14 plaintext ceiling enforced
  both sides; 48-bit sequence wrap surfaced distinctly from payload
  size failures.

Auto-mode:
- Hybrid ClientHello now emits `connection_id(0x0036)` when configured,
  so CH1/CH2 carry identical CID bytes and the stateless cookie binds
  once.
- DTLS 1.3 client silent-accepts a server echo only when its own
  ClientHello on the wire solicited `0x0036`.
- DTLS 1.3 server and client explicitly log-ignore `0x0036` per RFC.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants