Skip to content

Instantly share code, notes, and snippets.

@maxlapshin
Created March 18, 2019 11:42
Show Gist options
  • Save maxlapshin/e0796d86bd118ea8a3e670f4aa971fe1 to your computer and use it in GitHub Desktop.
Save maxlapshin/e0796d86bd118ea8a3e670f4aa971fe1 to your computer and use it in GitHub Desktop.
%%%----------------------------------------------------------------------
%%% Copyright (c) 2012 Peter Lemenkov <[email protected]>
%%%
%%% All rights reserved.
%%%
%%% Redistribution and use in source and binary forms, with or without modification,
%%% are permitted provided that the following conditions are met:
%%%
%%% * Redistributions of source code must retain the above copyright notice, this
%%% list of conditions and the following disclaimer.
%%% * Redistributions in binary form must reproduce the above copyright notice,
%%% this list of conditions and the following disclaimer in the documentation
%%% and/or other materials provided with the distribution.
%%% * Neither the name of the authors nor the names of its contributors
%%% may be used to endorse or promote products derived from this software
%%% without specific prior written permission.
%%%
%%% THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
%%% EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
%%% WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
%%% DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
%%% DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
%%% (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
%%% LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
%%% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
%%% (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
%%% SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
%%%
%%%----------------------------------------------------------------------
-module(srtp).
-author('[email protected]').
-export([new_ctx/6]).
-export([new_ctx/7]).
-export([encrypt/3]).
-export([decrypt/3]).
-export([ssrc/1, index/1, packets_lost/1, packets_lost/2]).
-export([status/1]).
-export([set_ssrc/2, check/3, drop_padding/2]).
-include("rtcp.hrl").
-include("rtp.hrl").
-include("srtp.hrl").
new_ctx(SSRC, Ealg, Aalg, MasterKey, MasterSalt, TagLength) ->
new_ctx(SSRC, Ealg, Aalg, MasterKey, MasterSalt, TagLength, 0).
new_ctx(SSRC, Ealg, Aalg, MasterKey, MasterSalt, TagLength, KeyDerivationRate) ->
#srtp_crypto_ctx{
ssrc = SSRC,
aalg = Aalg,
ealg = Ealg,
keyDerivRate = KeyDerivationRate,
rtp_keys = derive_keyset(MasterKey, MasterSalt, {?SRTP_LABEL_RTP_AUTH, ?SRTP_LABEL_RTP_ENCR, ?SRTP_LABEL_RTP_SALT}, 0, 0),
rtcp_keys = derive_keyset(MasterKey, MasterSalt, {?SRTP_LABEL_RTCP_AUTH, ?SRTP_LABEL_RTCP_ENCR, ?SRTP_LABEL_RTCP_SALT}, 0, 0),
tagLength = TagLength,
packets_lost = 0
}.
ssrc(#srtp_crypto_ctx{ssrc = SSRC}) ->
SSRC.
set_ssrc(SSRC, #srtp_crypto_ctx{} = Ctx) ->
Ctx#srtp_crypto_ctx{ssrc = SSRC}.
index(#srtp_crypto_ctx{roc = Roc, s_l = SequenceNumber}) ->
Roc bsl 16 + SequenceNumber.
packets_lost(#srtp_crypto_ctx{packets_lost = LP}) ->
LP.
packets_lost(LP, #srtp_crypto_ctx{packets_lost = LP0} = Ctx) ->
{LP0, Ctx#srtp_crypto_ctx{packets_lost = LP}}.
status(#srtp_crypto_ctx{s_l = SL, roc = Roc}) ->
[{roc, Roc}, {s_l, SL}].
encrypt(rtp, <<?RTP_VERSION:2, _Pad:1, Ext:1, _CCM:5, _PayloadType:7, SequenceNumber:16, _Timestamp:32, SSRC:32, _/binary>> = Data,
#srtp_crypto_ctx{ssrc = SSRC, s_l = OldSequenceNumber, roc = Roc, aalg = Aalg, ealg = Ealg, keyDerivRate = KeyDerivationRate, rtp_keys = Keys, tagLength = TagLength} = Ctx
) ->
#srtp_keyset{k_a = KeyA, k_e = KeyE, k_s = Salt} = Keys,
<<Header:3/binary-unit:32, Ext_Payload/binary>> = Data,
{Extension, PayloadToEncrypt} = take_extension(Ext, Ext_Payload),
EncryptedPayload = encrypt_payload(PayloadToEncrypt, SSRC, guess_index(SequenceNumber, OldSequenceNumber, Roc), Ealg, KeyE, Salt, KeyDerivationRate, ?SRTP_LABEL_RTP_ENCR),
AuthenticatedPacket = append_auth(<<Header/binary, Extension/binary, EncryptedPayload/binary>>, <<Roc:32>>, Aalg, KeyA, TagLength),
{ok, AuthenticatedPacket, update_ctx(Ctx, SequenceNumber, OldSequenceNumber, Roc)};
encrypt(rtcp, Rtcp,
#srtp_crypto_ctx{ssrc = SSRC, rtcp_idx = Idx, aalg = Aalg, ealg = Ealg, keyDerivRate = KeyDerivationRate, rtcp_keys = Keys, tagLength = TagLength} = Ctx
) ->
#srtp_keyset{k_a = KeyA, k_e = KeyE, k_s = Salt} = Keys,
NextIdx = (Idx + 1) rem 16#80000000,
<<Header:2/binary-unit:32, Payload/binary>> = Rtcp,
EncryptedPayload = encrypt_payload(Payload, SSRC, Idx, Ealg, KeyE, Salt, KeyDerivationRate, ?SRTP_LABEL_RTCP_ENCR),
{ok, append_auth(<<Header/binary, EncryptedPayload/binary, 1:1, Idx:31>>, <<>>, Aalg, KeyA, TagLength), Ctx#srtp_crypto_ctx{rtcp_idx = NextIdx}};
% {ok, append_auth(<<Header/binary, Payload/binary, 0:1, Idx:31>>, <<>>, Aalg, KeyA, TagLength), Ctx#srtp_crypto_ctx{rtcp_idx = NextIdx}}.
encrypt(rtp, <<?RTP_VERSION:2, _:1, _:1, _:5, _:7, _:16, _:32, WrongSSRC:32, _/binary>>, #srtp_crypto_ctx{ssrc = SSRC}) ->
error({wrong_ssrc, WrongSSRC, SSRC}).
%% Check if given packet can be decrypted in given context
check(rtcp, <<?RTP_VERSION:2, _:6, _PT:8, _Len:16, SSRC:32, _/binary>> = Data, #srtp_crypto_ctx{} = Ctx0) ->
case check_auth(rtcp, Data, Ctx0) of
{ok, _, _} ->
try decrypt(rtcp, Data, Ctx0#srtp_crypto_ctx{ssrc = SSRC}) of
{ok, _, _} -> true;
_ -> false
catch
_:_ -> false
end;
{error, _} -> false
end.
decrypt(rtp, <<?RTP_VERSION:2, _Pad:1, Ext:1, _CCM:5, _PayloadType:7, SequenceNumber:16, _Timestamp:32, SSRC:32, _/binary>> = Data,
#srtp_crypto_ctx{ssrc = SSRC} = Ctx0) ->
{ok, <<Header:3/binary-unit:32, Ext_EncryptedPayload/binary>>, Ctx} = check_auth(rtp, Data, update_ctx(SequenceNumber, Ctx0)),
{Extension, EncryptedPayload} = take_extension(Ext, Ext_EncryptedPayload),
#srtp_crypto_ctx{s_l = OldSequenceNumber, roc = Roc, ealg = Ealg, keyDerivRate = KeyDerivationRate, rtp_keys = Keys} = Ctx,
#srtp_keyset{k_e = KeyE, k_s = Salt} = Keys,
% LostPackets = (SequenceNumber - (Ctx0#srtp_crypto_ctx.s_l + 1) + 16#10000) rem 16#10000,
% LostPackets == 0 orelse io:format("[SRTP] lost ~w packets ~w -> ~w~n", [LostPackets, Ctx0#srtp_crypto_ctx.s_l, SequenceNumber]),
DecryptedPayload = decrypt_payload(EncryptedPayload, SSRC, guess_index(SequenceNumber, OldSequenceNumber, Roc), Ealg, KeyE, Salt, KeyDerivationRate, ?SRTP_LABEL_RTP_ENCR),
% UnpaddedPayload = drop_padding(Pad, DecryptedPayload),
{ok, <<Header/binary, Extension/binary, DecryptedPayload/binary>>, Ctx};
decrypt(rtcp, <<?RTP_VERSION:2, _:6, _PT:8, _Len:16, SSRC:32, _/binary>> = Data,
#srtp_crypto_ctx{ssrc = SSRC, tagLength = TagLength} = Ctx0) ->
Size = size(Data) - (TagLength + 8 + 4),
{ok, <<Header:8/binary, EncryptedPayload:Size/binary, E:1, Index:31>>, Ctx} = check_auth(rtcp, Data, Ctx0),
#srtp_crypto_ctx{ssrc = SSRC, ealg = Ealg, keyDerivRate = KeyDerivationRate, rtcp_keys = Keys} = Ctx,
#srtp_keyset{k_e = KeyE, k_s = Salt} = Keys,
DecryptedPayload = case E of
0 -> EncryptedPayload; % no encryption
1 -> decrypt_payload(EncryptedPayload, SSRC, Index, Ealg, KeyE, Salt, KeyDerivationRate, ?SRTP_LABEL_RTCP_ENCR)
end,
{ok, <<Header:8/binary, DecryptedPayload/binary>>, Ctx};
%% @TODO investigate firefox
decrypt(rtcp, <<?RTP_VERSION:2, _:6, _PT:8, _Len:16, _SSRC:32, _/binary>> = _Data,
#srtp_crypto_ctx{ssrc = undefined, tagLength = _TagLength} = Ctx0) ->
{ok, <<>>, Ctx0}.
drop_padding(1, RawData) ->
<<PaddingSize>> = binary_part(RawData, byte_size(RawData), -1),
binary_part(RawData, 0, byte_size(RawData) - PaddingSize);
drop_padding(0, Data) ->
Data.
take_extension(0, Data) ->
{<<>>, Data};
take_extension(1, <<ExtType:16, Words:16, ExtData:Words/binary-unit:32, Data/binary>>) ->
Ext = <<ExtType:16, Words:16, ExtData:Words/binary-unit:32>>,
{Ext, Data}.
%%
%% Auth
%%
check_auth(_, Data, #srtp_crypto_ctx{aalg = srtpAuthenticationNull} = Ctx) ->
{ok, Data, Ctx};
check_auth(Type, Data, #srtp_crypto_ctx{s_l = SL, roc = Roc, aalg = srtpAuthenticationSha1Hmac, tagLength = TagLength} = Ctx) ->
{#srtp_keyset{k_a = Key}, Suffix} = case Type of
rtp -> {Ctx#srtp_crypto_ctx.rtp_keys, <<Roc:32>>};
rtcp -> {Ctx#srtp_crypto_ctx.rtcp_keys, <<>>}
end,
Size = size(Data) - TagLength,
<<NewData:Size/binary, Tag:TagLength/binary>> = Data,
<<Sign:TagLength/binary, _/binary>> = crypto:hmac(sha, Key, <<NewData/binary, Suffix/binary>>),
case Tag of
Sign -> {ok, NewData, Ctx};
_ -> {error, {invalid_auth, {Roc, SL}, Tag, Sign}}
end.
append_auth(Data, _, srtpAuthenticationNull, _, _) ->
Data;
append_auth(Data, Roc, srtpAuthenticationSha1Hmac, Key, TagLength) ->
<<Tag:TagLength/binary, _/binary>> = crypto:hmac(sha, Key, <<Data/binary, Roc/binary>>),
<<Data/binary, Tag/binary>>;
append_auth(Data, Roc, srtpAuthenticationSkeinHmac, Key, TagLength) ->
{ok, S} = skerl:init(512),
{ok, _} = skerl:update(S, Key),
{ok, _} = skerl:update(S, <<Data/binary, Roc/binary>>),
{ok, <<Tag:TagLength/binary, _/binary>>} = skerl:final(S),
<<Data/binary, Tag/binary>>.
encrypt_payload(Data, _, _, srtpEncryptionNull, _, _, _, _) ->
Data;
encrypt_payload(Data, SSRC, Index, srtpEncryptionAESCM, SessionKey, SessionSalt, KeyDerivationRate, Label) ->
encrypt_payload(Data, SSRC, Index, srtpEncryptionAESCM, SessionKey, SessionSalt, KeyDerivationRate, Label, 0, <<>>);
encrypt_payload(_Data, _SSRC, _Index, srtpEncryptionAESF8, _SessionKey, _SessionSalt, _KeyDerivationRate, _Label) ->
throw({error, aesf8_encryption_unsupported});
encrypt_payload(_Data, _SSRC, _Index, srtpEncryptionTWOCM, _SessionKey, _SessionSalt, _KeyDerivationRate, _Label) ->
throw({error, twocm_encryption_unsupported});
encrypt_payload(_Data, _SSRC, _Index, srtpEncryptionTWOF8, _SessionKey, _SessionSalt, _KeyDerivationRate, _Label) ->
throw({error, twof8_encryption_unsupported}).
encrypt_payload(<<>>, _, _, _, _, _, _, _, _, Encrypted) ->
Encrypted;
encrypt_payload(<<Uncrypted/binary>>, SSRC, Index, srtpEncryptionAESCM, SessionKey, <<S_S:112>> = SessionSalt, KeyDerivationRate, Label, Step, Encrypted) ->
% Take input block (128 bit at most)
{Part, Rest} = case Uncrypted of
<<FullPart:16/binary, More/binary>> -> {FullPart, More};
<<LastPart/binary>> -> {LastPart, <<>>}
end,
Key = SessionKey,
Counter = <<((S_S bxor (SSRC bsl 48)) bxor Index):112, Step:16>>,
EncPart = aes_ctr_encrypt(Key, Counter, Part),
encrypt_payload(Rest, SSRC, Index, srtpEncryptionAESCM, SessionKey, SessionSalt, KeyDerivationRate, Label, Step + 1, <<Encrypted/binary, EncPart/binary>>).
decrypt_payload(Data, _, _, srtpEncryptionNull, _, _, _, _) ->
Data;
decrypt_payload(Data, SSRC, Index, srtpEncryptionAESCM, SessionKey, SessionSalt, KeyDerivationRate, Label) ->
decrypt_payload(Data, SSRC, Index, srtpEncryptionAESCM, SessionKey, SessionSalt, KeyDerivationRate, Label, 0, <<>>);
decrypt_payload(_Data, _SSRC, _Index, srtpEncryptionAESF8, _SessionKey, _SessionSalt, _KeyDerivationRate, _Label) ->
throw({error, aesf8_decryption_unsupported});
decrypt_payload(_Data, _SSRC, _Index, srtpEncryptionTWOCM, _SessionKey, _SessionSalt, _KeyDerivationRate, _Label) ->
throw({error, twocm_decryption_unsupported});
decrypt_payload(_Data, _SSRC, _Index, srtpEncryptionTWOF8, _SessionKey, _SessionSalt, _KeyDerivationRate, _Label) ->
throw({error, twof8_decryption_unsupported}).
decrypt_payload(<<>>, _, _, _, _, _, _, _, _, Decrypted) ->
Decrypted;
decrypt_payload(Encrypted, SSRC, Index, srtpEncryptionAESCM, SessionKey, <<S_S:112>> = SessionSalt, KeyDerivationRate, Label, Step, Decrypted) ->
% Take input block (128 bit at most)
{Part, Rest} = case Encrypted of
<<FullPart:16/binary, More/binary>> -> {FullPart, More};
<<LastPart/binary>> -> {LastPart, <<>>}
end,
Key = SessionKey,
% IV = <<((SSRC bsl 48) bxor Index):112, 0:16>>,
Counter = <<((S_S bxor (SSRC bsl 48)) bxor Index):112, Step:16>>,
DecPart = aes_ctr_decrypt(Key, Counter, Part),
% io:format("aes icm: counter: ~s (IV ~s, EKey ~s)~n", [
% [io_lib:format("~.16b", [D]) || <<D:4>> <= Counter],
% [io_lib:format("~.16b", [D]) || <<D:4>> <= IV],
% [io_lib:format("~.16b", [D]) || <<D:4>> <= Key] ]),
% io:format("aes icm: ciphertext: ~s -> ~s~n", [[io_lib:format("~.16b", [D]) || <<D:4>> <= Part], [io_lib:format("~.16b", [D]) || <<D:4>> <= DecPart]]),
decrypt_payload(Rest, SSRC, Index, srtpEncryptionAESCM, SessionKey, SessionSalt, KeyDerivationRate, Label, Step + 1, <<Decrypted/binary, DecPart/binary>>).
%%
%% Crypto-specific functions
%%
computeIV(<<Salt:112>>, Label, KeyNumber) ->
<<(Salt bxor ((Label bsl 48) bor KeyNumber)):112>>.
derive_keyset(MasterKey, MasterSalt, {LabelA, LabelE, LabelS}, Index, KeyNumber) ->
#srtp_keyset{
index = Index,
num = KeyNumber,
k_a = derive_key(20, MasterKey, MasterSalt, LabelA, KeyNumber),
k_e = derive_key(byte_size(MasterKey), MasterKey, MasterSalt, LabelE, KeyNumber),
k_s = derive_key(byte_size(MasterSalt), MasterKey, MasterSalt, LabelS, KeyNumber)
}.
derive_key(Len, MasterKey, MasterSalt, Label, KeyNumber) ->
IV = computeIV(MasterSalt, Label, KeyNumber),
aes_ctr_encrypt(MasterKey, <<IV/binary, 0:16>>, <<0:Len/unit:8>>).
guess_index(SequenceNumber, null, Roc) ->
guess_index(SequenceNumber, SequenceNumber, Roc);
guess_index(SequenceNumber, OldSequenceNumber, Roc) when OldSequenceNumber < 32768 ->
GuessedRoc = case (SequenceNumber - OldSequenceNumber) > 32768 of
true -> Roc - 1;
false -> Roc
end,
GuessedRoc bsl 16 + SequenceNumber;
guess_index(SequenceNumber, OldSequenceNumber, Roc) ->
GuessedRoc = case (OldSequenceNumber - 32768) > SequenceNumber of
true -> Roc + 1;
false -> Roc
end,
GuessedRoc bsl 16 + SequenceNumber.
update_ctx(SequenceNumber, #srtp_crypto_ctx{s_l = OldSequenceNumber, roc = Roc, packets_lost = LP0} = Ctx) ->
LostPackets = (SequenceNumber - (OldSequenceNumber + 1) + 16#10000) rem 16#10000,
LP = if
LostPackets =< 0 -> LP0;
true -> LP0 + LostPackets
end,
update_ctx(Ctx#srtp_crypto_ctx{packets_lost = LP}, SequenceNumber, OldSequenceNumber, Roc).
update_ctx(Ctx, SequenceNumber, OldSequenceNumber, Roc) when OldSequenceNumber < 32768 ->
NewSequenceNumber = erlang:max(SequenceNumber, OldSequenceNumber),
GuessedRoc = case (SequenceNumber - OldSequenceNumber) > 32768 of
true -> Roc - 1;
false -> Roc
end,
case GuessedRoc > Roc of
true -> Ctx#srtp_crypto_ctx{s_l = SequenceNumber, roc = GuessedRoc};
false -> Ctx#srtp_crypto_ctx{s_l = NewSequenceNumber, roc = Roc}
end;
update_ctx(Ctx, SequenceNumber, OldSequenceNumber, Roc) ->
NewSequenceNumber = erlang:max(SequenceNumber, OldSequenceNumber),
GuessedRoc = case (OldSequenceNumber - 32768) > SequenceNumber of
true -> Roc + 1;
false -> Roc
end,
case GuessedRoc > Roc of
true -> Ctx#srtp_crypto_ctx{s_l = SequenceNumber, roc = GuessedRoc};
false -> Ctx#srtp_crypto_ctx{s_l = NewSequenceNumber, roc = Roc}
end.
aes_ctr_encrypt(Key, IVec, Text) ->
{_, Out} = crypto:stream_encrypt(crypto:stream_init(aes_ctr, Key, IVec), Text),
Out.
aes_ctr_decrypt(Key, IVec, Text) ->
{_, Out} = crypto:stream_decrypt(crypto:stream_init(aes_ctr, Key, IVec), Text),
Out.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment