Skip to content

Add PSK cipher suites for DTLS 1.2#92

Open
jaredwolff wants to merge 8 commits intoalgesten:mainfrom
circuitdojo:psk-support
Open

Add PSK cipher suites for DTLS 1.2#92
jaredwolff wants to merge 8 commits intoalgesten:mainfrom
circuitdojo:psk-support

Conversation

@jaredwolff
Copy link

@jaredwolff jaredwolff commented Mar 10, 2026

Summary

  • Implement RFC 4279 PSK key exchange for DTLS 1.2 with TLS_PSK_WITH_AES_128_CCM_8 (0xC0A8)
    • Only mandatory PSK cipher suite per RFC 7925 (IoT TLS/DTLS profile)
  • Add PskResolver trait for callback-based key lookup and Dtls::new_12_psk() constructor
  • Model PSK config as Psk enum with Client and Server variants
  • Unify certificate/PSK auth via AuthMode enum in CryptoContext and Engine
  • Add PskError variant for PSK-specific error conditions
  • AES-128-CCM-8 cipher via ccm crate, shared across both aws-lc-rs and RustCrypto backends
  • Handle servers that omit ServerKeyExchange when no PSK identity hint is provided (RFC 4279 §2)

Motivation

PSK support is needed for IoT devices (e.g., nRF9151 modem) that use pre-shared keys
instead of certificates for DTLS 1.2 authentication.

Test plan

  • Self-handshake and bidirectional data transfer tests for PSK_AES128_CCM_8
  • PSK identity validation tests (invalid identity rejected at Finished)
  • OpenSSL interop tests Ignored (OpenSSL does not support CCM-8 over DTLS)
  • All existing tests continue to pass (48 tests + doctests)
  • cargo clippy clean

@motters
Copy link

motters commented Mar 10, 2026

I’ve been watching this repo for like 7 months, waiting for PSK support. PSK + CIDs + session resumption would finally unlock Rust for a lot of IoT use-cases where DTLS is everywhere.

@jaredwolff
Copy link
Author

Its definitely been a long time coming for the rust eco system. Excited to get this out there.

I'll get working on the rebase. Looks like some conflicts.

jaredwolff and others added 4 commits March 10, 2026 21:03
Implement pure PSK key exchange (RFC 4279) with AES-128-CCM-8 AEAD
(RFC 6655) for DTLS 1.2, targeting nRF9151 modem compatibility.

- AES-128-CCM-8 cipher via RustCrypto `ccm` crate (both backends)
- PSK handshake flow: skip Certificate/CertificateVerify states
- PskResolver trait for callback-based key lookup
- Dtls::new_12_psk() constructor (no certificate required)
- Self-handshake + application data round-trip tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reuses existing AES-128-GCM cipher and PSK handshake path from CCM-8,
with standard 16-byte GCM authentication tag instead of 8-byte CCM tag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…nal SKE

- Add PSK_AES256_GCM_SHA384 (0x00A9) and PSK_CHACHA20_POLY1305_SHA256
  (0xCCAB) to both crypto backends
- Add bidirectional OpenSSL PSK interop tests (dimpl client/server)
- Fix client to handle servers that omit ServerKeyExchange when no PSK
  identity hint is provided (RFC 4279 §2)
- Fix ArrayVec capacity in detect.rs for expanded cipher suite list
- Update lib.rs docs to list all supported PSK suites

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove "Not supported: PSK cipher suites" from README and lib.rs
- Add all 4 PSK suites to cryptography surface in README and lib.rs
- Add Dtls::new_12_psk to version selection section
- Add PSK client example with PskResolver to lib.rs and README
- Add "psk" keyword to Cargo.toml
- Add PSK entries to CHANGELOG.md under Unreleased

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@HMBSbige
Copy link
Contributor

HMBSbige commented Mar 11, 2026

I took a quick look at the changes and noticed a potential issue: when a Config has both a certificate and a psk_resolver, new_12 (certificate mode) can still negotiate PSK suites, since is_cipher_suite_compatible() returns true for PSK (no signature_algorithm). This could let a peer downgrade the connection to PSK, skipping Certificate/CertificateRequest and bypassing require_client_certificate. Might be worth filtering out PSK suites in the certificate path, or catching this in build().

@jaredwolff
Copy link
Author

Good catch. Let me get a fix in there for that.

Certificate-mode contexts (with a private key) could negotiate PSK
suites, skipping Certificate/CertificateVerify and bypassing certificate
authentication. Fix is_cipher_suite_compatible() to reject PSK suites
when a private key is present, and filter PSK suites from
dtls12_cipher_suites() when no PskResolver is configured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Contributor

@HMBSbige HMBSbige left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also noticed a couple of minor issues:

Reordered cipher list putting CCM_8 after AEAD.
Added dummy-PSK fallback to avoid potential attacks to determinne valid identities.
Test coverage for both scenarios.

Signed-off-by: Jared Wolff <hello@jaredwolff.com>
@jaredwolff
Copy link
Author

Addressing the CI issues shortly.

- Run cargo fmt to fix long lines in PSK tests and other files
- Shorten 114-char comment lines in validation/mod.rs to fit 110-char limit
- Add #[cfg(feature = "rcgen")] to context tests using generate_self_signed_certificate
- Remove duplicate DrainedOutputs/drain_outputs/deliver_packets from edge.rs (use common module)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Owner

@algesten algesten left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there! I have read through the code. Overall looks good, I think there are a couple of improvements to do.

The ambition of dimpl was never to be a generic DTLS implementation (beyond my own needs in WebRTC), however I'm not opposed to it going that direction. Although I'm not a crypto person, I'll remain the code steward for it, for now.

The PSK stuff goes beyond what I expect we'll use dimpl for ourselves, so accepting this into the tree also means we will be relying on yourself (and other volunteers) to stay on top of that part of the code. HMBSbige has already stepped up, so maybe that won't be much of a problem.

static PSK_AES_128_CCM_8: PskAes128Ccm8 = PskAes128Ccm8;
static PSK_AES_128_GCM_SHA256: PskAes128GcmSha256 = PskAes128GcmSha256;
static PSK_AES_256_GCM_SHA384: PskAes256GcmSha384 = PskAes256GcmSha384;
static PSK_CHACHA20_POLY1305_SHA256: PskChaCha20Poly1305Sha256 = PskChaCha20Poly1305Sha256;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jaredwolff @HMBSbige When I set out to make the dimpl crate, I deliberately selected a very narrow set of ciphers. Only the ones I know are often used in WebRTC (three of them).

With this PSK PR, we are adding 4 new, which means we have more PSK cipher suites than we have non-PSK, which is quite at odds with the narrow focus of where I started.

I'm not saying we can't do this, but is it strictly necessary? Are some of these suites rarely or almost never used?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can definitely pair this back. It wasn't a big deal to add them. For embedded (my use case) the most secure that our device supports in an offloaded manor (handled by the hardware) is TLS-PSK-WITH-AES-128-CCM-8. Ideally ECDHE-PSK + AEAD as it's more secure. Maybe it's worth just having those two. Offloading to mbedTLS is expensive when it comes to code space. (which is what we would use to get the more secure cipher)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More research:

RFC 7925 (DTLS + IoT) - PSK_AES_128_CCM_8 is considered a MUST
LwM2M (which is just a beefier CoAP client) - again PSK_AES_128_CCM_8 is considered a MUST

So I went overboard a bit. 😇 Stay tuned for a refactor.

Remove PSK_AES128_GCM_SHA256, PSK_AES256_GCM_SHA384, and
PSK_CHACHA20_POLY1305_SHA256 — only TLS_PSK_WITH_AES_128_CCM_8 is
mandated by IoT standards (RFC 7925, LwM2M). This keeps the PSK
surface minimal and aligned with dimpl's narrow-focus philosophy.

Architectural changes per review feedback:
- Model PSK config as Psk enum (Client/Server) replacing three loose
  fields; builder API becomes with_psk_client/with_psk_server
- Unify CryptoContext constructors via AuthMode enum (Certificate/Psk)
- Merge Engine::new and Engine::new_psk into single constructor
- Add Error::PskError variant for PSK-specific errors
- Move Debug impls to bottom of config.rs before tests
- Fix code ordering: PSK branches before ECDHE in client.rs
- Import ServerKeyExchangeParams properly instead of inline path
- Remove dead code: get_key_exchange_group_info, _group_info, CurveType
- Export Psk, ConfigBuilder from public API
- Mark OpenSSL PSK interop tests #[ignore] (OpenSSL excludes CCM-8
  from DTLS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jaredwolff
Copy link
Author

Probably a good candidate for a squashed commit everything is hashed out. Thanks again all.

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.

4 participants