Repository navigation
Implement RFC 3830 key derivation - #23
Merged
Merged
Conversation
The PRF was a counter-mode KDF, not the construction in RFC 3830 §4.1.2,
so no key derived by mykey could match another MIKEY implementation.
Replace it with the specified feedback construction:
P(s, label, m) = HMAC(s, A_1||label) || ... || HMAC(s, A_m||label)
A_0 = label, A_i = HMAC(s, A_(i-1))
PRF(inkey, label) = P(s_1,label,m) XOR ... XOR P(s_n,label,m)
with the input key split into 256-bit blocks. This also removes the
`i as u8` block counter, which wrapped and repeated its keystream after
5120 bytes of output, and the `output_len as u16` truncation.
Reject an empty input key: with no blocks to iterate the XOR accumulator
would have stayed zero and silently returned an all-zero key.
Replace the ad-hoc ASCII labels ("TGK", "AUTH", "SRTP_KEY", ...) with the
RFC label layouts and constants:
§4.1.3, from a TGK: constant || cs_id || csb_id || RAND
§4.1.4, from a PSK: constant || 0xFF || csb_id || RAND
The SRTP master key and salt are now the TEK (0x2AD01C64) and salting key
(0x39A2C14B). csb_id was not mixed in at all before; it is now threaded
from the common header through derive_srtp_keys and derive_auth_key.
The PSK message authentication key is now derived from the pre-shared key
per §4.1.4 rather than from the TGK. Deriving it from the TGK meant the
MAC key was recoverable by anyone who could read the TGK, which is sent
in the clear in the KEMAC payload (tracked separately).
BREAKING: every derived key changes value, and derive_srtp_keys,
derive_auth_key and derive_enc_key take new arguments. Wire-incompatible
with mykey 1.0.0 on both sides of an exchange.
Add a "Deviations from RFC 3830" chapter cataloguing every known departure from the spec, so the reasoning is written down rather than rediscovered: - the TGK is derived through the PRF, which the RFC does not do in either the DH method (TGK = g^(xi*xr)) or the PSK method (TGK is transported) - DH mode uses X25519 on private-use DH-Group 255 rather than OAKLEY 5, and emits no SIGN payload, so it cannot interoperate with a compliant implementation regardless of the group - PSK mode sends the TGK in the clear and never verifies the MAC - the public-key method is not implemented Correct the PSK chapter, which claimed mutual authentication and MITM protection. Neither holds today: anyone who can read a PSK-Init recovers the SRTP master key without the PSK, and no receive path checks the MAC. Add a CHANGELOG recording the breaking key-derivation changes, and point README and the crate docs at the deviations chapter.
Record the gaps found while scoping interoperability work, and update the chapter's conclusions now that OAKLEY support is planned rather than ruled out. Wire-format findings, none of them fixed here: - "Last payload" is 255 in `PayloadType`, where RFC 3830 Table 6.1.b assigns 0 (and assigns nothing to HDR). Every chain mykey emits therefore terminates with an undefined value, and inbound parsing only works because the loop also stops on buffer exhaustion — a conformant 0 terminator resolves to `Hdr` and errors as `InvalidPayloadType(0)`. Affects every message in every mode and is the highest-value interop fix. - `TimestampType::value_len()` returns 4 for type 1, where Table 6.6 says 64 bits, so the payload cursor advances four bytes short. Measured: types 0 and 2 parse correctly, only type 1 is affected, and it fails closed. The variant name `NtpShort` is also a misnomer. - Key data sub-payloads (§6.13) are not implemented; the TGK is written bare. - The verification message (§6.9) is never built or checked, so PSK mode offers no mutual authentication. - COUNTER is emitted rather than a mandatory NTP timestamp type, and no replay handling exists. Split the TGK section, which previously justified one deviation but described two. In PSK mode, deriving the TGK instead of transporting it cannot interoperate, is the reason the TGK travels in the clear, and concentrates all session freshness in RAND; §3.1 compliance removes it. RFC 4650 requires the raw DH result, so an authenticated-DH mode must not carry it either. It survives only for X25519, where RFC 7748 §6.1 advises a KDF and no registered EC group exists to be conformant with — so there is no compliant alternative. Note the two RFC 7748 refinements still outstanding for that mode: the public keys are not bound into the derivation (§6.1), and there is no contributory or all-zero check (§7). `PinnedPeer::verify` compares all 32 bytes, so the equivalent-encoding concern in §7 is a robustness gap rather than a known exploit. Also rewrite the OAKLEY conclusion from "we do not intend to switch" to the actual plan — opt-in OAKLEY with X25519 as default, DHHMAC for authenticated DH, §3.3 an explicit non-goal — and record that MIKEY has no method negotiation, so multi-method support means explicit configuration rather than automatic downgrade.
This was referenced Oct 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #22.
@jcowgill was right on all three points in #22, and I confirmed each against the RFC text before changing anything.
What was wrong
The PRF was not RFC 3830 §4.1.2. It was a counter-mode KDF computing
HMAC(key, label || 0x00 || i || len)per block. The RFC specifies a TLS-style feedback construction with input-key splitting:Side by side on the same input, before the fix:
The labels were ad-hoc ASCII (
"TGK","AUTH","SRTP_KEY", …) instead of the RFC layouts, andcsb_idwas never mixed into any derived key.The
i as u8counter wrapped, repeating the keystream every 5120 bytes — reachable only well beyond any caller's request (the largest is 32 bytes), and gone entirely now that the construction has no counter.What changed
mikey_prfimplements §4.1.2, including the 256-bit input-key split and XOR combination.0x2AD01C64) and salting key (0x39A2C14B).csb_idis threaded from the MIKEY common header throughderive_srtp_keys,derive_auth_key, andderive_enc_key.New tests lock the feedback construction against a reference written independently from the RFC text, plus the block splitting and XOR, both label layouts, the constants, and
cs_id/csb_idreaching the output. The reference value above was computed before the implementation was written, and the new code reproduces it.Documentation
New Deviations from RFC 3830 chapter recording every known departure, so this reasoning is written down rather than rediscovered: the non-RFC TGK derivation, X25519 on private-use DH-Group 255 rather than OAKLEY 5, the absent SIGN payload, and the PSK gaps below.
On the DH group specifically — switching to OAKLEY 5 would not buy interoperability. The RFC's DH method is signature-authenticated (
SIGNi/SIGNr), and mykey emits no SIGN or CERT payload, so a conformant peer rejects the message regardless of group. OAKLEY 5 is also ~96-bit security against X25519's ~128. IANA reserves DH-Group 241–255 for private use, so255is a legitimate code point; no EC group has ever been registered for MIKEY.The PSK chapter claimed "Mutual authentication: Yes" and "MITM protection: Yes". Neither holds, so I corrected the table and added a warning.
Not addressed here
PSK mode still builds its KEMAC with NULL encryption and puts the TGK in
enc_datain the clear, so anyone who can read a PSK-Init recovers the SRTP master key without the PSK. No receive path verifies the KEMAC MAC either. Both need their own issue — the disclosure is exploitable in released 1.0.0, not merely non-interoperable, so it likely warrants a SECURITY.md advisory rather than being folded in here.Breaking
Every derived key changes value, and
derive_srtp_keys/derive_auth_key/derive_enc_keytake new arguments. Wire-incompatible with 1.0.0 — both ends of an exchange must upgrade together. This warrants a 2.0.0 release.Cargo.tomlis deliberately left at 1.0.0 for cargo-release to bump; the break is recorded inCHANGELOG.mdunder[Unreleased].Downstream: rlac pins
mykey = "1.0.0"and callsnew_psk_init/complete_psk. Those call sites still compile unchanged, but it needs a coordinated bump once this releases.