You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
rtpengine/perl/NGCP/Rtpclient/SRTP.pm

452 lines
11 KiB

package NGCP::Rtpclient::SRTP;
use strict;
use warnings;
use Crypt::Rijndael;
use Digest::SHA qw(hmac_sha1);
use MIME::Base64;
our $SRTP_DEBUG = 0;
our @crypto_suites = (
{
str => 'AES_CM_128_HMAC_SHA1_80',
dtls_name => 'SRTP_AES128_CM_SHA1_80',
auth_tag => 10,
enc_func => \&aes_cm,
iv_rtp => \&aes_cm_iv_rtp,
iv_rtcp => \&aes_cm_iv_rtcp,
key_length => 16,
salt_length => 14,
},
{
str => 'AES_CM_128_HMAC_SHA1_32',
dtls_name => 'SRTP_AES128_CM_SHA1_32',
auth_tag => 4,
enc_func => \&aes_cm,
iv_rtp => \&aes_cm_iv_rtp,
iv_rtcp => \&aes_cm_iv_rtcp,
key_length => 16,
salt_length => 14,
},
{
str => 'F8_128_HMAC_SHA1_80',
auth_tag => 10,
enc_func => \&aes_f8,
iv_rtp => \&aes_f8_iv_rtp,
iv_rtcp => \&aes_f8_iv_rtcp,
key_length => 16,
salt_length => 14,
},
{
str => 'AES_192_CM_HMAC_SHA1_80',
#dtls_name => 'SRTP_AES128_CM_SHA1_80',
auth_tag => 10,
enc_func => \&aes_cm,
iv_rtp => \&aes_cm_iv_rtp,
iv_rtcp => \&aes_cm_iv_rtcp,
key_length => 24,
salt_length => 14,
},
{
str => 'AES_256_CM_HMAC_SHA1_80',
#dtls_name => 'SRTP_AES128_CM_SHA1_80',
auth_tag => 10,
enc_func => \&aes_cm,
iv_rtp => \&aes_cm_iv_rtp,
iv_rtcp => \&aes_cm_iv_rtcp,
key_length => 32,
salt_length => 14,
},
{
str => 'AES_192_CM_HMAC_SHA1_32',
#dtls_name => 'SRTP_AES128_CM_SHA1_80',
auth_tag => 4,
enc_func => \&aes_cm,
iv_rtp => \&aes_cm_iv_rtp,
iv_rtcp => \&aes_cm_iv_rtcp,
key_length => 24,
salt_length => 14,
},
{
str => 'AES_256_CM_HMAC_SHA1_32',
#dtls_name => 'SRTP_AES128_CM_SHA1_80',
auth_tag => 4,
enc_func => \&aes_cm,
iv_rtp => \&aes_cm_iv_rtp,
iv_rtcp => \&aes_cm_iv_rtcp,
key_length => 32,
salt_length => 14,
},
);
our %crypto_suites = map {$$_{str} => $_} @crypto_suites;
sub aes_cm {
my ($data, $key, $iv) = @_;
my $c = Crypt::Rijndael->new($key) or die;
length($iv) == 16 or die;
my @iv = unpack("C16", $iv);
my $out = '';
while ($data ne '') {
$iv = pack("C16", @iv);
my $key_segment = $c->encrypt($iv);
length($key_segment) == 16 or die;
my @ks = unpack("C16", $key_segment);
my @ds = unpack("C16", $data);
for my $i (0 .. $#ds) {
my $ss = $ds[$i];
my $kk = $ks[$i];
$out .= chr($ss ^ $kk);
}
substr($data, 0, 16, '');
$data eq '' and last;
for my $i (reverse(0 .. 15)) {
$iv[$i]++;
if ($iv[$i] == 256) {
$iv[$i] = 0;
}
else {
last;
}
}
}
return $out;
}
sub aes_f8 {
my ($data, $key, $iv, $salt) = @_;
my $m = $salt . "\x55\x55";
my $c = Crypt::Rijndael->new(xor_128($key, $m)) or die;
my $ivx = $c->encrypt($iv);
undef($c);
$c = Crypt::Rijndael->new($key) or die;
my $p_s = "\0" x 16;
my $j = 0;
my $out = '';
while ($data ne '') {
my $jx = ("\0" x 12) . pack("N", $j);
my $key_segment = $c->encrypt(xor_128($ivx, $jx, $p_s));
length($key_segment) == 16 or die;
my @ks = unpack("C16", $key_segment);
my @ds = unpack("C16", $data);
for my $i (0 .. $#ds) {
my $ss = $ds[$i];
my $kk = $ks[$i];
$out .= chr($ss ^ $kk);
}
substr($data, 0, 16, '');
$data eq '' and last;
$p_s = $key_segment;
$j++;
}
return $out;
}
sub prf_n {
my ($n, $key, $x) = @_;
my $d = "\0" x ($n / 8);
my $ks = aes_cm($d, $key, $x . "\0\0");
return substr($ks, 0, $n / 8);
}
sub xor_n {
my ($n, @l) = @_;
$n /= 8;
my @o = (0) x $n;
for my $e (@l) {
my @e = unpack("C$n", $e);
if (@e < $n) {
unshift(@e, ((0) x ($n - @e)));
}
for my $i (0 .. $#o) {
$o[$i] ^= $e[$i];
}
}
return pack("C$n", @o);
}
sub xor_112 {
return xor_n(112, @_);
}
sub xor_128 {
return xor_n(128, @_);
}
sub gen_rtp_session_keys {
my ($master_key, $master_salt) = @_;
# this assumes session key length identical to master key length
my $session_key = prf_n(length($master_key) * 8, $master_key, xor_112($master_salt, "\0\0\0\0\0\0\0"));
my $auth_key = prf_n(160, $master_key, xor_112($master_salt, "\1\0\0\0\0\0\0"));
my $session_salt = prf_n(112, $master_key, xor_112($master_salt, "\2\0\0\0\0\0\0"));
if ($SRTP_DEBUG) {
print("RTP keys generated for master key " . unpack("H8", $master_key) . "... and salt " .
unpack("H8", $master_salt) . "... are: " .
unpack("H8", $session_key) . "..., " .
unpack("H*", $auth_key) . ", " .
unpack("H8", $session_salt) . "...\n");
}
return ($session_key, $auth_key, $session_salt);
}
sub gen_rtcp_session_keys {
my ($master_key, $master_salt) = @_;
# this assumes session key length identical to master key length
my $session_key = prf_n(length($master_key) * 8, $master_key, xor_112($master_salt, "\3\0\0\0\0\0\0"));
my $auth_key = prf_n(160, $master_key, xor_112($master_salt, "\4\0\0\0\0\0\0"));
my $session_salt = prf_n(112, $master_key, xor_112($master_salt, "\5\0\0\0\0\0\0"));
if ($SRTP_DEBUG) {
print("RTCP keys generated for master key " . unpack("H8", $master_key) . "... and salt " .
unpack("H8", $master_salt) . "... are: " .
unpack("H8", $session_key) . "..., " .
unpack("H*", $auth_key) . ", " .
unpack("H8", $session_salt) . "...\n");
}
return ($session_key, $auth_key, $session_salt);
}
sub aes_cm_iv_rtp {
my ($r, $ssalt, $roc) = @_;
my ($hdr, $seq, $ts, $ssrc) = unpack('a2na4a4', $r);
my $iv = xor_128($ssalt . "\0\0",
$ssrc . "\0\0\0\0\0\0\0\0", pack("Nnn", $roc, $seq, 0));
return $iv;
}
sub aes_cm_iv_rtcp {
my ($r, $ssalt, $idx) = @_;
$idx ||= 0;
my ($hdr, $ssrc) = unpack('a4a4', $r);
my $iv = xor_128($ssalt . "\0\0",
$ssrc . "\0\0\0\0\0\0\0\0", pack("Nn", $idx, 0));
return $iv;
}
sub aes_f8_iv_rtp {
my ($r, $ssalt, $roc) = @_;
my ($hdr, $fields) = unpack('a1a11', $r);
my $iv = pack('Ca*N', 0, $fields, $roc);
return $iv;
}
sub aes_f8_iv_rtcp {
my ($r, $ssalt, $idx) = @_;
my ($fields) = unpack('a8', $r);
my $iv = pack('a*Na*', "\0\0\0\0", (($idx || 0) | 0x80000000), $fields);
return $iv;
}
sub decode_inline_base64 {
my ($b64, $cs) = @_;
# append possibly missing trailing ==
$b64 .= '=' x (4 - (length($b64) % 4)) if ((length($b64) % 4) != 0);
my $ks = decode_base64($b64);
length($ks) == ($cs->{key_length} + $cs->{salt_length}) or die;
my @ret = unpack("a$cs->{key_length}a$cs->{salt_length}", $ks);
return @ret;
}
sub encrypt_rtp {
my ($suite, $skey, $ssalt, $sauth, $roc, $mki, $mki_len, $unenc_srtp, $unauth_srtp, $packet) = @_;
my ($hdr, $seq, $h2, $to_enc) = unpack('a2na8a*', $packet);
$roc = $roc || 0;
$seq == 0 and $roc++;
my $iv = $$suite{iv_rtp}->($packet, $ssalt, $roc);
my $enc = $unenc_srtp ? $to_enc : $$suite{enc_func}->($to_enc, $skey,
$iv, $ssalt);
my $pkt = pack('a*na*a*', $hdr, $seq, $h2, $enc);
my $hmac = hmac_sha1($pkt . pack("N", $roc), $sauth);
# print("HMAC for packet " . unpack("H*", $pkt) . " ROC $roc is " . unpack("H*", $hmac) . "\n");
append_mki(\$pkt, $mki_len, $mki);
#$pkt .= pack("N", 1); # mki
$pkt .= substr($hmac, 0, $unauth_srtp ? 0 : $$suite{auth_tag});
return ($pkt, $roc);
}
sub decrypt_rtp {
my ($suite, $skey, $ssalt, $sauth, $roc, $packet) = @_;
# XXX MKI, session parameters
my $plen = length($packet);
my $auth_tag = substr($packet, $plen - $$suite{auth_tag}, $$suite{auth_tag});
$packet = substr($packet, 0, $plen - $$suite{auth_tag});
my ($hdr, $seq, $h2, $to_enc) = unpack('a2na8a*', $packet);
$roc = $roc || 0;
$seq == 0 and $roc++;
my $iv = $$suite{iv_rtp}->($packet, $ssalt, $roc);
my $enc = $$suite{enc_func}->($to_enc, $skey,
$iv, $ssalt);
my $pkt = pack('a*na*a*', $hdr, $seq, $h2, $enc);
my $hmac = hmac_sha1($packet . pack("N", $roc), $sauth);
# print("HMAC for packet " . unpack("H*", $pkt) . " ROC $roc is " . unpack("H*", $hmac) . "\n");
#$pkt .= pack("N", 1); # mki
return ($pkt, $roc, $auth_tag, $hmac);
}
sub encrypt_rtcp {
my ($suite, $skey, $ssalt, $sauth, $idx, $mki, $mki_len, $unenc_srtcp, $packet) = @_;
my $iv = $suite->{iv_rtcp}->($packet, $ssalt, $idx);
my ($hdr, $to_enc) = unpack('a8a*', $packet);
my $enc = $unenc_srtcp ? $to_enc :
$suite->{enc_func}->($to_enc, $skey,
$iv, $ssalt);
my $pkt = $hdr . $enc;
$pkt .= pack("N", (($idx || 0) | ($unenc_srtcp ? 0 : 0x80000000)));
my $hmac = hmac_sha1($pkt, $sauth);
append_mki(\$pkt, $mki_len, $mki);
#$pkt .= pack("N", 1); # mki
$pkt .= substr($hmac, 0, 10);
$idx++;
return ($pkt, $idx);
}
sub decrypt_rtcp {
my ($suite, $skey, $ssalt, $sauth, $packet) = @_;
# XXX MKI, session parameters
my $plen = length($packet);
my $auth_tag = substr($packet, $plen - 10, 10);
my $idx_raw = substr($packet, $plen - 4 - 10, 4);
my ($idx) = unpack('N', $idx_raw);
$idx &= 0x7fffffff;
my $auth_packet = substr($packet, 0, $plen - 10);
$packet = substr($packet, 0, $plen - 10 - 4);
my $iv = $suite->{iv_rtcp}->($packet, $ssalt, $idx);
my ($hdr, $to_enc) = unpack('a8a*', $packet);
my $enc = $suite->{enc_func}->($to_enc, $skey,
$iv, $ssalt);
my $pkt = $hdr . $enc;
my $hmac = hmac_sha1($auth_packet, $sauth);
return ($pkt, $idx, $auth_tag, $hmac);
}
sub append_mki {
my ($pack_r, $mki_len, $mki) = @_;
$mki_len or return;
$mki = pack('N', $mki);
while (length($mki) < $mki_len) {
$mki = "\x00" . $mki;
}
if (length($mki) > $mki_len) {
$mki = substr($mki, -$mki_len);
}
$$pack_r .= $mki;
}
package NGCP::Rtpclient::SRTP::Context;
sub new {
my ($class, $suite) = @_;
my $self = {};
bless $self, $class;
$self->{suite} = $suite; # includes all parameters
my $remote = $self->{remote} = $suite->{remote}; # shortcut
$self->{roc} = 0;
$self->{remote_roc} = 0;
@$self{qw(session_key auth_key session_salt)}
= NGCP::Rtpclient::SRTP::gen_rtp_session_keys($suite->{master_key}, $suite->{master_salt});
@$self{qw(rtcp_session_key rtcp_auth_key rtcp_session_salt)}
= NGCP::Rtpclient::SRTP::gen_rtcp_session_keys($suite->{master_key}, $suite->{master_salt});
@$self{qw(remote_session_key remote_auth_key remote_session_salt)}
= NGCP::Rtpclient::SRTP::gen_rtp_session_keys($remote->{master_key}, $remote->{master_salt});
@$self{qw(remote_rtcp_session_key remote_rtcp_auth_key remote_rtcp_session_salt)}
= NGCP::Rtpclient::SRTP::gen_rtcp_session_keys($remote->{master_key}, $remote->{master_salt});
return $self;
};
sub encrypt {
my ($self, $component, $pack) = @_;
if ($component == 0) {
# XXX MKI, SRTP/SDES session options
my ($p, $roc) = NGCP::Rtpclient::SRTP::encrypt_rtp(@$self{qw(suite session_key session_salt
auth_key roc)}, '', 0,
0, 0, $pack);
$self->{roc} = $roc;
return $p;
}
else {
# RTCP
my ($p, $idx) = NGCP::Rtpclient::SRTP::encrypt_rtcp(@$self{qw(suite rtcp_session_key
rtcp_session_salt
rtcp_auth_key rtcp_index)}, '', 0,
0, $pack);
$self->{rtcp_index} = $idx;
return $p;
}
}
sub decrypt {
my ($self, $component, $pack) = @_;
if ($component == 0) {
# XXX MKI, SRTP/SDES session options
my ($p, $roc) = NGCP::Rtpclient::SRTP::decrypt_rtp(@$self{qw(remote remote_session_key
remote_session_salt
remote_auth_key remote_roc)}, $pack);
$self->{remote_roc} = $roc;
# XXX verify hmac/auth
return $p;
}
else {
# RTCP
my ($p, $idx) = NGCP::Rtpclient::SRTP::decrypt_rtcp(@$self{qw(remote remote_rtcp_session_key
remote_rtcp_session_salt
remote_rtcp_auth_key)}, $pack);
$self->{remote_rtcp_index} = $idx;
# XXX verify hmac/auth
return $p;
}
}
1;