From 2c7c14a492815424e649b902961d1557942bd9ed Mon Sep 17 00:00:00 2001 From: Lukas Volf Date: Sat, 6 Jun 2026 23:29:57 +0200 Subject: [PATCH 1/2] B Fixed RTCP unprotect replay window for GCM --- .../net/DtlsSrtp/Lib/SRTP/SrtpContext.cs | 454 ++++++++---------- 1 file changed, 210 insertions(+), 244 deletions(-) diff --git a/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs b/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs index 72cde1d4e..ecd5607a4 100644 --- a/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs +++ b/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs @@ -60,55 +60,6 @@ public class SsrcSrtpContext public uint S_l { get; private set; } public bool S_l_set { get; private set; } = false; - /// - /// Checks and updates the replay window for the given sequence number. - /// - /// RTP/RTCP sequence number. - /// true if the replay check passed, false when the packed was replayed. - /// https://datatracker.ietf.org/doc/html/rfc2401 Appendix C - public bool CheckAndUpdateReplayWindow(uint sequenceNumber) - { - int diff; - - if (sequenceNumber == 0) - { - if (!S_l_set) - { - S_l_set = true; - return true; /* first is good */ - } - return false; /* first == 0 or wrapped */ - } - if (sequenceNumber > S_l) - { - /* new larger sequence number */ - diff = (int)(sequenceNumber - S_l); - if (diff < REPLAY_WINDOW_SIZE) - { - /* In window */ - Bitmap = Bitmap << diff; - Bitmap |= 1; /* set bit for this packet */ - } - else - { - Bitmap = 1; /* This packet has a "way larger" */ - } - S_l = sequenceNumber; - return true; /* larger is good */ - } - diff = (int)(S_l - sequenceNumber); - if (diff >= REPLAY_WINDOW_SIZE) - { - return false; /* too old or wrapped */ - } - if ((Bitmap & ((ulong)1 << diff)) == ((ulong)1 << diff)) - { - return false; /* already seen */ - } - Bitmap |= ((ulong)1 << diff); /* mark as seen */ - return true; /* out of order but good */ - } - /// /// Read-only replay check. Returns whether (a 32-bit packet /// index) is acceptable, WITHOUT mutating any state. Per RFC 3711 section 3.3 the replay list, @@ -909,146 +860,146 @@ public virtual int UnprotectRtp(ReadOnlyBytes input, Bytes output, out int outpu try { - switch (context.Cipher) - { - case SrtpCiphers.NULL: - { - var dataLen = length - mki.Length - context.N_tag; - input.Slice(0, dataLen).CopyTo(output.Slice(0, dataLen)); - outputBufferLength = dataLen; - } - break; - - case SrtpCiphers.AES_128_F8: - { - SRTP.Encryption.F8.GenerateRtpMessageKeyIV(context.PayloadF8, context.K_e, context.K_s, input, roc, context.Iv16); - var decLen = length - mki.Length - context.N_tag; - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.F8.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); - outputBufferLength = decLen; - } - break; - - case SrtpCiphers.AES_128_CM: - case SrtpCiphers.AES_192_CM: - case SrtpCiphers.AES_256_CM: - case SrtpCiphers.ARIA_128_CTR: - case SrtpCiphers.ARIA_256_CTR: - case SrtpCiphers.SEED_128_CTR: - { - SRTP.Encryption.CTR.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv16); - var decLen = length - mki.Length - context.N_tag; - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.CTR.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); - outputBufferLength = decLen; - } - break; - - case SrtpCiphers.AEAD_AES_128_GCM: - case SrtpCiphers.AEAD_AES_256_GCM: - case SrtpCiphers.AEAD_ARIA_128_GCM: - case SrtpCiphers.AEAD_ARIA_256_GCM: - case SrtpCiphers.SEED_128_CCM: - case SrtpCiphers.SEED_128_GCM: - { - SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv12); - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - mki.Length - offset), output.Slice(offset), context.Iv12, context.K_e, context.N_tag, input.Slice(0, offset)); - outputBufferLength = length - mki.Length - context.N_tag; - } - break; - - case SrtpCiphers.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM: - case SrtpCiphers.DOUBLE_AEAD_AES_256_GCM_AEAD_AES_256_GCM: - { - // apply outer cryptographic algorithm - var outerK_e = KeyParameter.Create(context.K_e.Slice(context.K_e.Length / 2)); - var outerK_s = context.K_s.AsSpan(context.K_s.Length / 2); - SRTP.Encryption.AEAD.GenerateMessageKeyIV(outerK_s, ssrc, index, context.Iv12); - SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - mki.Length - offset), output.Slice(offset), context.Iv12, outerK_e, context.N_tag / 2, input.Slice(0, offset)); - - // copy header from input to output - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + switch (context.Cipher) + { + case SrtpCiphers.NULL: + { + var dataLen = length - mki.Length - context.N_tag; + input.Slice(0, dataLen).CopyTo(output.Slice(0, dataLen)); + outputBufferLength = dataLen; + } + break; - // calculate OHB size - it can now be larger than 1 byte if it was modified - var lastOhbByteIndex = length - mki.Length - context.N_tag / 2 - 1; - var ohbConfig = output[lastOhbByteIndex]; - var ohbLength = 1; - if ((ohbConfig & 0x01) == 0x01) + case SrtpCiphers.AES_128_F8: { - ohbLength += 2; + SRTP.Encryption.F8.GenerateRtpMessageKeyIV(context.PayloadF8, context.K_e, context.K_s, input, roc, context.Iv16); + var decLen = length - mki.Length - context.N_tag; + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + SRTP.Encryption.F8.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); + outputBufferLength = decLen; } - if ((ohbConfig & 0x02) == 0x02) + break; + + case SrtpCiphers.AES_128_CM: + case SrtpCiphers.AES_192_CM: + case SrtpCiphers.AES_256_CM: + case SrtpCiphers.ARIA_128_CTR: + case SrtpCiphers.ARIA_256_CTR: + case SrtpCiphers.SEED_128_CTR: { - ohbLength += 1; + SRTP.Encryption.CTR.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv16); + var decLen = length - mki.Length - context.N_tag; + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + SRTP.Encryption.CTR.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); + outputBufferLength = decLen; } + break; - // form a synthetic RTP packet - var rtpHeaderLength = RtpReader.ReadHeaderLenWithoutExtensions(output); - var rtpExtensionsLength = RtpReader.ReadExtensionsLength(output); - var syntheticRtpPacketLen = length - rtpExtensionsLength - (context.N_tag / 2) - ohbLength; - var syntheticRtpPacket = ArrayPool.Shared.Rent(syntheticRtpPacketLen); + case SrtpCiphers.AEAD_AES_128_GCM: + case SrtpCiphers.AEAD_AES_256_GCM: + case SrtpCiphers.AEAD_ARIA_128_GCM: + case SrtpCiphers.AEAD_ARIA_256_GCM: + case SrtpCiphers.SEED_128_CCM: + case SrtpCiphers.SEED_128_GCM: + { + SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv12); + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - mki.Length - offset), output.Slice(offset), context.Iv12, context.K_e, context.N_tag, input.Slice(0, offset)); + outputBufferLength = length - mki.Length - context.N_tag; + } + break; - try + case SrtpCiphers.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM: + case SrtpCiphers.DOUBLE_AEAD_AES_256_GCM_AEAD_AES_256_GCM: { - // copy header without extensions - output.Slice(0, rtpHeaderLength).CopyTo(syntheticRtpPacket.AsSpan(0, rtpHeaderLength)); + // apply outer cryptographic algorithm + var outerK_e = KeyParameter.Create(context.K_e.Slice(context.K_e.Length / 2)); + var outerK_s = context.K_s.AsSpan(context.K_s.Length / 2); + SRTP.Encryption.AEAD.GenerateMessageKeyIV(outerK_s, ssrc, index, context.Iv12); + SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - mki.Length - offset), output.Slice(offset), context.Iv12, outerK_e, context.N_tag / 2, input.Slice(0, offset)); - // set X bit to 0 - syntheticRtpPacket[0] &= 0xEF; + // copy header from input to output + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - // restore original header values from the OHB + // calculate OHB size - it can now be larger than 1 byte if it was modified + var lastOhbByteIndex = length - mki.Length - context.N_tag / 2 - 1; + var ohbConfig = output[lastOhbByteIndex]; + var ohbLength = 1; if ((ohbConfig & 0x01) == 0x01) { - syntheticRtpPacket[2] = output[lastOhbByteIndex - ohbLength - 1]; - syntheticRtpPacket[3] = output[lastOhbByteIndex - ohbLength]; + ohbLength += 2; } if ((ohbConfig & 0x02) == 0x02) { - var pt = output[lastOhbByteIndex - ohbLength]; - syntheticRtpPacket[1] = (byte)((syntheticRtpPacket[1] & 0x80) | (pt & 0x7F)); + ohbLength += 1; } - if ((ohbConfig & 0x04) == 0x04) - { - var markerBit = (ohbConfig & 0x08) == 0x08; - syntheticRtpPacket[1] = (byte)((markerBit ? 0x80 : 0x00) | (syntheticRtpPacket[1] & 0x7F)); - } - - // copy the payload including the inner authentication tag - output.Slice(offset, length - offset - mki.Length - context.N_tag / 2 - ohbLength).CopyTo(syntheticRtpPacket.AsSpan(rtpHeaderLength, length - offset - mki.Length - context.N_tag / 2 - ohbLength)); - - var innerSsrc = RtpReader.ReadSsrc(syntheticRtpPacket); - var innerSequenceNumber = RtpReader.ReadSequenceNumber(syntheticRtpPacket); - var innerIndex = SrtpContext.DetermineRtpIndex(lastSeq, innerSequenceNumber, lastRoc); - // apply inner cryptographic algorithm - var innerK_e = KeyParameter.Create(context.K_e.Slice(0, context.K_e.Length / 2)); - var innerK_s = context.K_s.AsSpan(0, context.K_s.Length / 2); - SRTP.Encryption.AEAD.GenerateMessageKeyIV(innerK_s, innerSsrc, innerIndex, context.Iv12); - SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, syntheticRtpPacket.Slice(rtpHeaderLength, syntheticRtpPacketLen - rtpHeaderLength), syntheticRtpPacket.Slice(rtpHeaderLength, syntheticRtpPacketLen - rtpHeaderLength), context.Iv12, innerK_e, context.N_tag / 2, syntheticRtpPacket.Slice(0, rtpHeaderLength)); - - // copy the unprotected payload back to the output buffer - syntheticRtpPacket.AsSpan(rtpHeaderLength, syntheticRtpPacketLen - rtpHeaderLength - context.N_tag / 2).CopyTo(output.Slice(offset, syntheticRtpPacketLen - rtpHeaderLength - context.N_tag / 2)); + // form a synthetic RTP packet + var rtpHeaderLength = RtpReader.ReadHeaderLenWithoutExtensions(output); + var rtpExtensionsLength = RtpReader.ReadExtensionsLength(output); + var syntheticRtpPacketLen = length - rtpExtensionsLength - (context.N_tag / 2) - ohbLength; + var syntheticRtpPacket = ArrayPool.Shared.Rent(syntheticRtpPacketLen); - // copy the synthetic header back to the output buffer - syntheticRtpPacket.AsSpan(0, rtpHeaderLength).CopyTo(output.Slice(0, rtpHeaderLength)); - - // update the output buffer length - outputBufferLength = offset + syntheticRtpPacketLen - rtpHeaderLength - context.N_tag / 2; + try + { + // copy header without extensions + output.Slice(0, rtpHeaderLength).CopyTo(syntheticRtpPacket.AsSpan(0, rtpHeaderLength)); + + // set X bit to 0 + syntheticRtpPacket[0] &= 0xEF; + + // restore original header values from the OHB + if ((ohbConfig & 0x01) == 0x01) + { + syntheticRtpPacket[2] = output[lastOhbByteIndex - ohbLength - 1]; + syntheticRtpPacket[3] = output[lastOhbByteIndex - ohbLength]; + } + if ((ohbConfig & 0x02) == 0x02) + { + var pt = output[lastOhbByteIndex - ohbLength]; + syntheticRtpPacket[1] = (byte)((syntheticRtpPacket[1] & 0x80) | (pt & 0x7F)); + } + if ((ohbConfig & 0x04) == 0x04) + { + var markerBit = (ohbConfig & 0x08) == 0x08; + syntheticRtpPacket[1] = (byte)((markerBit ? 0x80 : 0x00) | (syntheticRtpPacket[1] & 0x7F)); + } + + // copy the payload including the inner authentication tag + output.Slice(offset, length - offset - mki.Length - context.N_tag / 2 - ohbLength).CopyTo(syntheticRtpPacket.AsSpan(rtpHeaderLength, length - offset - mki.Length - context.N_tag / 2 - ohbLength)); + + var innerSsrc = RtpReader.ReadSsrc(syntheticRtpPacket); + var innerSequenceNumber = RtpReader.ReadSequenceNumber(syntheticRtpPacket); + var innerIndex = SrtpContext.DetermineRtpIndex(lastSeq, innerSequenceNumber, lastRoc); + + // apply inner cryptographic algorithm + var innerK_e = KeyParameter.Create(context.K_e.Slice(0, context.K_e.Length / 2)); + var innerK_s = context.K_s.AsSpan(0, context.K_s.Length / 2); + SRTP.Encryption.AEAD.GenerateMessageKeyIV(innerK_s, innerSsrc, innerIndex, context.Iv12); + SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, syntheticRtpPacket.Slice(rtpHeaderLength, syntheticRtpPacketLen - rtpHeaderLength), syntheticRtpPacket.Slice(rtpHeaderLength, syntheticRtpPacketLen - rtpHeaderLength), context.Iv12, innerK_e, context.N_tag / 2, syntheticRtpPacket.Slice(0, rtpHeaderLength)); + + // copy the unprotected payload back to the output buffer + syntheticRtpPacket.AsSpan(rtpHeaderLength, syntheticRtpPacketLen - rtpHeaderLength - context.N_tag / 2).CopyTo(output.Slice(offset, syntheticRtpPacketLen - rtpHeaderLength - context.N_tag / 2)); + + // copy the synthetic header back to the output buffer + syntheticRtpPacket.AsSpan(0, rtpHeaderLength).CopyTo(output.Slice(0, rtpHeaderLength)); + + // update the output buffer length + outputBufferLength = offset + syntheticRtpPacketLen - rtpHeaderLength - context.N_tag / 2; + } + finally + { + ArrayPool.Shared.Return(syntheticRtpPacket); + } } - finally + break; + + default: { - ArrayPool.Shared.Return(syntheticRtpPacket); + outputBufferLength = 0; + return ERROR_UNSUPPORTED_CIPHER; } - } - break; - - default: - { - outputBufferLength = 0; - return ERROR_UNSUPPORTED_CIPHER; - } - } + } } catch (Org.BouncyCastle.Crypto.InvalidCipherTextException) { @@ -1296,7 +1247,7 @@ public virtual int UnprotectRtcp(ReadOnlyBytes input, Bytes output, out int outp } } - if (!ssrcContext.CheckAndUpdateReplayWindow(index)) + if (!ssrcContext.CheckReplayWindow(index)) { outputBufferLength = 0; return ERROR_REPLAY_CHECK_FAILED; @@ -1304,93 +1255,104 @@ public virtual int UnprotectRtcp(ReadOnlyBytes input, Bytes output, out int outp if (isEncrypted) { - switch (context.Cipher) + try { - case SrtpCiphers.NULL: - { - var dataLen = length - 4 - context.N_tag - mki.Length; - input.Slice(0, dataLen).CopyTo(output.Slice(0, dataLen)); - outputBufferLength = dataLen; - } - break; - - case SrtpCiphers.AES_128_F8: - { - var decLen = length - 4 - context.N_tag - mki.Length; - var iv = SRTP.Encryption.F8.GenerateRtcpMessageKeyIV(context.PayloadF8, context.K_e, context.K_s, input, originalIndex); - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.F8.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), iv); - outputBufferLength = decLen; - } - break; - - case SrtpCiphers.AES_128_CM: - case SrtpCiphers.AES_192_CM: - case SrtpCiphers.AES_256_CM: - case SrtpCiphers.ARIA_128_CTR: - case SrtpCiphers.ARIA_256_CTR: - case SrtpCiphers.SEED_128_CTR: - { - var decLen = length - 4 - context.N_tag - mki.Length; - SRTP.Encryption.CTR.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv16); - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.CTR.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); - outputBufferLength = decLen; - } - break; + switch (context.Cipher) + { + case SrtpCiphers.NULL: + { + var dataLen = length - 4 - context.N_tag - mki.Length; + input.Slice(0, dataLen).CopyTo(output.Slice(0, dataLen)); + outputBufferLength = dataLen; + } + break; - case SrtpCiphers.AEAD_AES_128_GCM: - case SrtpCiphers.AEAD_AES_256_GCM: - case SrtpCiphers.AEAD_ARIA_128_GCM: - case SrtpCiphers.AEAD_ARIA_256_GCM: - case SrtpCiphers.SEED_128_CCM: - case SrtpCiphers.SEED_128_GCM: - { - SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv12); - var associatedDataRented = ArrayPool.Shared.Rent(offset + 4); - try + case SrtpCiphers.AES_128_F8: { - input.Slice(0, offset).CopyTo(associatedDataRented.AsSpan(0, offset)); - BinaryPrimitives.WriteUInt32BigEndian(associatedDataRented.AsSpan(offset, 4), originalIndex); + var decLen = length - 4 - context.N_tag - mki.Length; + var iv = SRTP.Encryption.F8.GenerateRtcpMessageKeyIV(context.PayloadF8, context.K_e, context.K_s, input, originalIndex); input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - 4 - mki.Length - offset), output.Slice(offset), context.Iv12, context.K_e, context.N_tag, associatedDataRented.Slice(0, offset + 4)); - outputBufferLength = length - 4 - context.N_tag - mki.Length; + SRTP.Encryption.F8.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), iv); + outputBufferLength = decLen; } - finally + break; + + case SrtpCiphers.AES_128_CM: + case SrtpCiphers.AES_192_CM: + case SrtpCiphers.AES_256_CM: + case SrtpCiphers.ARIA_128_CTR: + case SrtpCiphers.ARIA_256_CTR: + case SrtpCiphers.SEED_128_CTR: + { + var decLen = length - 4 - context.N_tag - mki.Length; + SRTP.Encryption.CTR.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv16); + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + SRTP.Encryption.CTR.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); + outputBufferLength = decLen; + } + break; + + case SrtpCiphers.AEAD_AES_128_GCM: + case SrtpCiphers.AEAD_AES_256_GCM: + case SrtpCiphers.AEAD_ARIA_128_GCM: + case SrtpCiphers.AEAD_ARIA_256_GCM: + case SrtpCiphers.SEED_128_CCM: + case SrtpCiphers.SEED_128_GCM: { - ArrayPool.Shared.Return(associatedDataRented); + SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv12); + var associatedDataRented = ArrayPool.Shared.Rent(offset + 4); + try + { + input.Slice(0, offset).CopyTo(associatedDataRented.AsSpan(0, offset)); + BinaryPrimitives.WriteUInt32BigEndian(associatedDataRented.AsSpan(offset, 4), originalIndex); + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - 4 - mki.Length - offset), output.Slice(offset), context.Iv12, context.K_e, context.N_tag, associatedDataRented.Slice(0, offset + 4)); + outputBufferLength = length - 4 - context.N_tag - mki.Length; + } + finally + { + ArrayPool.Shared.Return(associatedDataRented); + } } - } - break; + break; - case SrtpCiphers.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM: - case SrtpCiphers.DOUBLE_AEAD_AES_256_GCM_AEAD_AES_256_GCM: - { - // RTCP under Double AEAD is protected only with the outer layer - var outerK_e = KeyParameter.Create(context.K_e.Slice(context.K_e.Length / 2)); - var outerK_s = context.K_s.AsSpan(context.K_s.Length / 2); - SRTP.Encryption.AEAD.GenerateMessageKeyIV(outerK_s, ssrc, ssrcContext.S_l, context.Iv12); - var associatedDataRented = ArrayPool.Shared.Rent(offset + 4); - try + case SrtpCiphers.DOUBLE_AEAD_AES_128_GCM_AEAD_AES_128_GCM: + case SrtpCiphers.DOUBLE_AEAD_AES_256_GCM_AEAD_AES_256_GCM: { - input.Slice(0, offset).CopyTo(associatedDataRented.AsSpan(0, offset)); - BinaryPrimitives.WriteUInt32BigEndian(associatedDataRented.AsSpan(offset, 4), originalIndex); - input.Slice(0, offset).CopyTo(output.Slice(0, offset)); - SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - 4 - mki.Length - offset), output.Slice(offset), context.Iv12, outerK_e, context.N_tag / 2, associatedDataRented.Slice(0, offset + 4)); - outputBufferLength = length - 4 - context.N_tag / 2 - mki.Length; + // RTCP under Double AEAD is protected only with the outer layer + var outerK_e = KeyParameter.Create(context.K_e.Slice(context.K_e.Length / 2)); + var outerK_s = context.K_s.AsSpan(context.K_s.Length / 2); + SRTP.Encryption.AEAD.GenerateMessageKeyIV(outerK_s, ssrc, ssrcContext.S_l, context.Iv12); + var associatedDataRented = ArrayPool.Shared.Rent(offset + 4); + try + { + input.Slice(0, offset).CopyTo(associatedDataRented.AsSpan(0, offset)); + BinaryPrimitives.WriteUInt32BigEndian(associatedDataRented.AsSpan(offset, 4), originalIndex); + input.Slice(0, offset).CopyTo(output.Slice(0, offset)); + SRTP.Encryption.AEAD.Encrypt(context.PayloadAEAD, false, input.Slice(offset, length - 4 - mki.Length - offset), output.Slice(offset), context.Iv12, outerK_e, context.N_tag / 2, associatedDataRented.Slice(0, offset + 4)); + outputBufferLength = length - 4 - context.N_tag / 2 - mki.Length; + } + finally + { + ArrayPool.Shared.Return(associatedDataRented); + } } - finally + break; + + default: { - ArrayPool.Shared.Return(associatedDataRented); + outputBufferLength = 0; + return ERROR_UNSUPPORTED_CIPHER; } - } - break; - - default: - { - outputBufferLength = 0; - return ERROR_UNSUPPORTED_CIPHER; - } + } + } + catch (Org.BouncyCastle.Crypto.InvalidCipherTextException) + { + // AEAD (GCM/CCM) authentication failed. Drop the packet WITHOUT advancing the replay + // window / ROC, so a single unauthenticated, corrupted or reordered packet cannot desync + // the ROC and cause every subsequent packet to fail to decrypt. RFC 3711 section 3.3. + outputBufferLength = 0; + return ERROR_HMAC_CHECK_FAILED; } } else @@ -1400,6 +1362,10 @@ public virtual int UnprotectRtcp(ReadOnlyBytes input, Bytes output, out int outp outputBufferLength = dataLen; } + // The packet has now been authenticated (HMAC above for HMAC profiles, or the AEAD decrypt + // for GCM/CCM profiles). Only now is it safe to advance the replay window. + ssrcContext.UpdateReplayWindow(index); + return 0; } From 19a09fb3c22d6cc5381a4928f145dcaf91a43685 Mon Sep 17 00:00:00 2001 From: Lukas Volf Date: Mon, 8 Jun 2026 00:30:52 +0200 Subject: [PATCH 2/2] B Fixed RTCP Unprotect regression issue --- src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs b/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs index ecd5607a4..92f278f0b 100644 --- a/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs +++ b/src/SIPSorcery/net/DtlsSrtp/Lib/SRTP/SrtpContext.cs @@ -1285,7 +1285,7 @@ public virtual int UnprotectRtcp(ReadOnlyBytes input, Bytes output, out int outp case SrtpCiphers.SEED_128_CTR: { var decLen = length - 4 - context.N_tag - mki.Length; - SRTP.Encryption.CTR.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv16); + SRTP.Encryption.CTR.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv16); input.Slice(0, offset).CopyTo(output.Slice(0, offset)); SRTP.Encryption.CTR.Encrypt(context.PayloadCTR, input.Slice(offset, decLen - offset), output.Slice(offset, decLen - offset), context.Iv16); outputBufferLength = decLen; @@ -1299,7 +1299,7 @@ public virtual int UnprotectRtcp(ReadOnlyBytes input, Bytes output, out int outp case SrtpCiphers.SEED_128_CCM: case SrtpCiphers.SEED_128_GCM: { - SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, ssrcContext.S_l, context.Iv12); + SRTP.Encryption.AEAD.GenerateMessageKeyIV(context.K_s, ssrc, index, context.Iv12); var associatedDataRented = ArrayPool.Shared.Rent(offset + 4); try { @@ -1322,7 +1322,7 @@ public virtual int UnprotectRtcp(ReadOnlyBytes input, Bytes output, out int outp // RTCP under Double AEAD is protected only with the outer layer var outerK_e = KeyParameter.Create(context.K_e.Slice(context.K_e.Length / 2)); var outerK_s = context.K_s.AsSpan(context.K_s.Length / 2); - SRTP.Encryption.AEAD.GenerateMessageKeyIV(outerK_s, ssrc, ssrcContext.S_l, context.Iv12); + SRTP.Encryption.AEAD.GenerateMessageKeyIV(outerK_s, ssrc, index, context.Iv12); var associatedDataRented = ArrayPool.Shared.Rent(offset + 4); try {