Skip to content

Commit 4481f77

Browse files
authored
Compare auth tags in constant time (#14)
1 parent 68a915f commit 4481f77

6 files changed

Lines changed: 59 additions & 28 deletions

File tree

lib/ex_srtp/cipher/aes_cm_hmac_sha1.ex

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,9 @@ defmodule ExSRTP.Cipher.AesCmHmacSha1 do
7878
header_size = byte_size(data) - byte_size(packet.payload)
7979

8080
<<encrypted_data::binary-size(byte_size(data) - tag_size), tag::binary>> = data
81-
new_tag = generate_srtp_auth_tag(cipher, encrypted_data, roc)
81+
expected_tag = generate_srtp_auth_tag(cipher, encrypted_data, roc)
8282

83-
if tag == new_tag do
83+
if :crypto.hash_equals(tag, expected_tag) do
8484
idx = packet.ssrc <<< 48 ||| roc <<< 16 ||| packet.sequence_number
8585
iv = bxor(cipher.rtp_salt, idx <<< 16)
8686

@@ -95,7 +95,7 @@ defmodule ExSRTP.Cipher.AesCmHmacSha1 do
9595

9696
{:ok, %{packet | payload: payload}}
9797
else
98-
{:error, :auth_failed}
98+
{:error, :authentication_failed}
9999
end
100100
end
101101

@@ -119,10 +119,11 @@ defmodule ExSRTP.Cipher.AesCmHmacSha1 do
119119

120120
<<rtcp_data::binary-size(authenticated_data_size - 4), e::1, index::31, tag::binary>> = data
121121

122-
new_tag = generate_srtcp_auth_tag(cipher, binary_part(data, 0, authenticated_data_size))
122+
expected_tag =
123+
generate_srtcp_auth_tag(cipher, binary_part(data, 0, authenticated_data_size))
123124

124125
cond do
125-
new_tag != tag ->
126+
not :crypto.hash_equals(tag, expected_tag) ->
126127
{:error, :authentication_failed}
127128

128129
e == 0 ->

native/ex_srtp/Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

native/ex_srtp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ aes = "0.8.4"
1414
ctr = "0.9.2"
1515
hmac = "0.12.1"
1616
rustler = "0.37.2"
17+
subtle = "2.6.1"
1718

1819
[target.'cfg(windows)'.dependencies]
1920
sha1 = "0.10.6"

native/ex_srtp/src/cipher/aes_cm_hmac_sha1.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use aes::cipher::{KeyIvInit, StreamCipher};
22
use hmac::Mac;
33
use rustler::OwnedBinary;
4+
use subtle::ConstantTimeEq;
45

56
use crate::{
67
cipher::Cipher, key_derivation::aes_cm_key_derivation, protection_profile::ProtectionProfile,
@@ -101,16 +102,18 @@ impl Cipher for AesCmHmacSha1Cipher {
101102
roc: u32,
102103
) -> Result<OwnedBinary, String> {
103104
let (encrypted_data, auth_tag) = payload.split_at(payload.len() - self.profile.tag_size());
104-
let expected_tag =
105-
self.generate_rtp_auth_tag(&[&header[..], &encrypted_data[..], &roc.to_be_bytes()[..]]);
105+
let expected_tag = &self.generate_rtp_auth_tag(&[
106+
&header[..],
107+
&encrypted_data[..],
108+
&roc.to_be_bytes()[..],
109+
]);
106110

107-
if auth_tag != expected_tag.as_slice() {
111+
if auth_tag.ct_eq(expected_tag).unwrap_u8() != 1 {
108112
return Err("authentication_failed".to_string());
109113
}
110114

111115
let size = payload.len() - self.profile.tag_size();
112116
let mut owned_binary = OwnedBinary::new(size).unwrap();
113-
// owned_binary.as_mut_slice()[..header.len()].copy_from_slice(header);
114117
owned_binary
115118
.as_mut_slice()
116119
.copy_from_slice(&payload[..payload.len() - self.profile.tag_size()]);
@@ -148,8 +151,8 @@ impl Cipher for AesCmHmacSha1Cipher {
148151
let tag_size = self.profile.tag_size();
149152
let (data, auth_tag) = compound_packet.split_at(compound_packet.len() - tag_size);
150153

151-
let expected_tag = self.generate_rtcp_auth_tag(data);
152-
if auth_tag != expected_tag.as_slice() {
154+
let expected_tag = &self.generate_rtcp_auth_tag(data);
155+
if auth_tag.ct_eq(expected_tag).unwrap_u8() != 1 {
153156
return Err("authentication_failed".to_string());
154157
}
155158

native/ex_srtp/src/lib.rs

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ fn unprotect<'a>(
9898
state: ResourceArc<State>,
9999
header: Binary<'a>,
100100
payload: Binary<'a>,
101-
) -> Result<Binary<'a>, String> {
101+
) -> Result<Binary<'a>, Atom> {
102102
let mut session = state.session.lock().unwrap();
103103
let ssrc = u32::from_be_bytes(header.as_slice()[8..12].try_into().unwrap());
104104
let seq = u16::from_be_bytes(header.as_slice()[2..4].try_into().unwrap());
@@ -109,24 +109,29 @@ fn unprotect<'a>(
109109
.or_insert_with(|| RTPContext::default());
110110

111111
let roc = ctx.estimate_roc(seq);
112-
113-
let owned = session
112+
match session
114113
.cipher
115-
.decrypt_rtp(&header.as_slice(), &payload.as_slice(), roc)?;
116-
117-
session.in_rtp_ctx.get_mut(&ssrc).unwrap().update_roc(seq);
118-
return Ok(Binary::from_owned(owned, env));
114+
.decrypt_rtp(&header.as_slice(), &payload.as_slice(), roc)
115+
{
116+
Err(err) => Err(Atom::from_str(env, err.as_str()).unwrap()),
117+
Ok(owned) => {
118+
session.in_rtp_ctx.get_mut(&ssrc).unwrap().update_roc(seq);
119+
Ok(Binary::from_owned(owned, env))
120+
}
121+
}
119122
}
120123

121124
#[rustler::nif]
122125
fn unprotect_rtcp<'a>(
123126
env: Env<'a>,
124127
state: ResourceArc<State>,
125128
data: Binary<'a>,
126-
) -> Result<Binary<'a>, String> {
129+
) -> Result<Binary<'a>, Atom> {
127130
let mut session = state.session.lock().unwrap();
128-
let owned = session.cipher.decrypt_rtcp(&data.as_slice())?;
129-
return Ok(Binary::from_owned(owned, env));
131+
match session.cipher.decrypt_rtcp(&data.as_slice()) {
132+
Err(err) => return Err(Atom::from_str(env, err.as_str()).unwrap()),
133+
Ok(owned) => Ok(Binary::from_owned(owned, env)),
134+
}
130135
}
131136

132137
#[rustler::nif]

test/ex_srtp_test.exs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,22 +86,21 @@ defmodule ExSRTPTest do
8686
end
8787

8888
describe "protect rtcp" do
89-
test "Erlnag backend", %{srtp: srtp, compound_packet: compound_packet} do
89+
setup do
9090
expected =
9191
<<128, 200, 0, 6, 137, 161, 255, 135, 235, 3, 169, 113, 236, 134, 217, 36, 127, 210, 78,
9292
156, 66, 244, 203, 218, 58, 80, 24, 60, 28, 171, 30, 89, 192, 155, 19, 59, 128, 0, 0, 1,
9393
139, 226, 152, 17, 40, 71, 251, 110, 11, 235>>
9494

95+
{:ok, expected: expected}
96+
end
97+
98+
test "Erlnag backend", %{srtp: srtp, compound_packet: compound_packet, expected: expected} do
9599
assert {:ok, ^expected, _srtp} = ExSRTP.protect_rtcp(compound_packet, srtp)
96100
assert {^expected, _srtp} = ExSRTP.protect_rtcp!(compound_packet, srtp)
97101
end
98102

99-
test "Rust backend", %{rust_srtp: srtp, compound_packet: compound_packet} do
100-
expected =
101-
<<128, 200, 0, 6, 137, 161, 255, 135, 235, 3, 169, 113, 236, 134, 217, 36, 127, 210, 78,
102-
156, 66, 244, 203, 218, 58, 80, 24, 60, 28, 171, 30, 89, 192, 155, 19, 59, 128, 0, 0, 1,
103-
139, 226, 152, 17, 40, 71, 251, 110, 11, 235>>
104-
103+
test "Rust backend", %{rust_srtp: srtp, compound_packet: compound_packet, expected: expected} do
105104
assert {:ok, ^expected, _srtp} = RustCrypto.protect_rtcp(compound_packet, srtp)
106105
end
107106
end
@@ -137,6 +136,15 @@ defmodule ExSRTPTest do
137136
assert {:ok, ^packet, srtp} = RustCrypto.unprotect(protected_packet, srtp)
138137
assert {:error, :replay} = RustCrypto.unprotect(protected_packet, srtp)
139138
end
139+
140+
test "authentication failed", %{srtp: srtp, rust_srtp: rust_srtp} do
141+
packet =
142+
<<128, 96, 0, 1, 0, 1, 226, 64, 137, 161, 255, 135, 146, 221, 94, 142, 7, 197, 169, 172,
143+
155, 23, 74, 128, 181, 142, 46>>
144+
145+
assert {:error, :authentication_failed} = ExSRTP.unprotect(packet, srtp)
146+
assert {:error, :authentication_failed} = RustCrypto.unprotect(packet, rust_srtp)
147+
end
140148
end
141149

142150
describe "unprotect rtcp" do
@@ -185,6 +193,18 @@ defmodule ExSRTPTest do
185193
ExSRTP.unprotect_rtcp!(protected_rtcp, srtp)
186194
end
187195
end
196+
197+
test "authentication failed", %{srtp: srtp, rust_srtp: rust_srtp} do
198+
protected_rtcp =
199+
<<128, 200, 0, 6, 137, 161, 255, 135, 235, 3, 169, 113, 236, 134, 217, 36, 127, 210, 78,
200+
156, 66, 244, 203, 218, 58, 80, 24, 60, 28, 171, 30, 89, 192, 155, 19, 59, 128, 0, 0, 1,
201+
139, 226, 152, 17, 40, 70, 251, 110, 11, 235>>
202+
203+
assert {:error, :authentication_failed} = ExSRTP.unprotect_rtcp(protected_rtcp, srtp)
204+
205+
assert {:error, :authentication_failed} =
206+
RustCrypto.unprotect_rtcp(protected_rtcp, rust_srtp)
207+
end
188208
end
189209

190210
for profile <- [:aes_cm_128_hmac_sha1_80, :aes_cm_128_hmac_sha1_32, :aes_gcm_128_16_auth] do

0 commit comments

Comments
 (0)