From 7877b7ce1491ac6ddb7a8ce087d8207b341606e0 Mon Sep 17 00:00:00 2001 From: Richard Fuchs Date: Fri, 7 Jun 2013 13:49:55 -0400 Subject: [PATCH] implement srtp session key generation algorithm --- daemon/Makefile | 2 +- daemon/call.c | 59 ++++++++++++++++++++------- daemon/call.h | 7 +++- daemon/crypto.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++ daemon/crypto.h | 30 +++++++++++++- daemon/rtp.c | 62 +++++++++++++++++++++++++++++ daemon/rtp.h | 18 +++++++++ daemon/sdp.c | 19 ++++++++- daemon/str.h | 6 +++ 9 files changed, 285 insertions(+), 22 deletions(-) create mode 100644 daemon/rtp.c create mode 100644 daemon/rtp.h diff --git a/daemon/Makefile b/daemon/Makefile index b831a6282..422e144e5 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -33,7 +33,7 @@ endif SRCS= main.c kernel.c poller.c aux.c control_tcp.c streambuf.c call.c control_udp.c redis.c \ bencode.c cookie_cache.c udp_listener.c control_ng.c sdp.c str.c stun.c rtcp.c \ - crypto.c + crypto.c rtp.c OBJS= $(SRCS:.c=.o) diff --git a/daemon/call.c b/daemon/call.c index 16e2fa159..d852c0055 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -28,6 +28,7 @@ #include "str.h" #include "stun.h" #include "rtcp.h" +#include "rtp.h" @@ -215,28 +216,58 @@ void kernelize(struct callstream *c) { -int __dummy_stream_handler(str *s) { +static int __dummy_stream_handler(str *s, struct streamrelay *r) { abort(); return 0; } +static int call_avpf2avp(str *s, struct streamrelay *r) { + return rtcp_avpf2avp(s); +} +static int call_avp2savp_rtp(str *s, struct streamrelay *r) { + return rtp_savp2avp(s, &r->peer.crypto); +} +static int call_avp2savp_rtcp(str *s, struct streamrelay *r) { + return 0; +} + static stream_handler determine_handler(struct streamrelay *in) { if (in->peer.protocol == in->peer_advertised.protocol) goto dummy; - if (in->peer.protocol == PROTO_UNKNOWN) - goto dummy; if (in->peer_advertised.protocol == PROTO_UNKNOWN) goto dummy; - if (in->peer.protocol == PROTO_RTP_AVPF && in->peer_advertised.protocol == PROTO_RTP_AVP) { - if (!in->rtcp) + switch (in->peer.protocol) { + case PROTO_UNKNOWN: goto dummy; - return rtcp_avpf2avp; - } - if (in->peer.protocol == PROTO_RTP_AVP && in->peer_advertised.protocol == PROTO_RTP_AVPF) - goto dummy; - /* XXX warn? */ + case PROTO_RTP_AVP: + switch (in->peer_advertised.protocol) { + case PROTO_RTP_AVPF: + goto dummy; + + case PROTO_RTP_SAVP: + return in->rtcp ? call_avp2savp_rtcp + : call_avp2savp_rtp; + + default: + abort(); + } + + case PROTO_RTP_AVPF: + switch (in->peer_advertised.protocol) { + case PROTO_RTP_AVP: + if (!in->rtcp) + goto dummy; + return call_avpf2avp; + + default: + abort(); + } + + default: + abort(); + } dummy: return __dummy_stream_handler; @@ -292,7 +323,7 @@ static int stream_packet(struct streamrelay *sr_incoming, str *s, struct sockadd if (!sr_incoming->handler) sr_incoming->handler = determine_handler(sr_incoming); if (sr_incoming->handler != __dummy_stream_handler) - handler_ret = sr_incoming->handler(s); + handler_ret = sr_incoming->handler(s, sr_incoming); use_cand: if (p_incoming->confirmed || !p_incoming->filled || sr_incoming->idx != 0) @@ -1054,12 +1085,10 @@ static int setup_peer(struct peer *p, struct stream_input *s, const str *tag) { unkernelize(&cs->peers[1]); } - a->peer.ip46 = s->stream.ip46; - b->peer.ip46 = s->stream.ip46; - a->peer.port = b->peer.port = s->stream.port; + a->peer = s->stream; + b->peer = s->stream; if (b->peer.port) b->peer.port++; - a->peer.protocol = b->peer.protocol = s->stream.protocol; a->peer_advertised = a->peer; b->peer_advertised = b->peer; a->rtcp = s->is_rtcp; diff --git a/daemon/call.h b/daemon/call.h index f41a373c6..e155a057e 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -15,6 +15,7 @@ #include "aux.h" #include "bencode.h" #include "str.h" +#include "crypto.h" struct poller; struct control_stream; @@ -26,6 +27,7 @@ struct callstream; struct call; struct callmaster; struct redis; +struct crypto_suite; @@ -69,6 +71,7 @@ struct stream { u_int16_t port; int num; enum transport_protocol protocol; + struct crypto_context crypto; }; struct stream_input { struct stream stream; @@ -83,8 +86,8 @@ struct udp_fd { u_int16_t localport; }; -typedef int (*stream_handler)(str *); -int __dummy_stream_handler(str *); +struct streamrelay; +typedef int (*stream_handler)(str *, struct streamrelay *); struct streamrelay { struct udp_fd fd; diff --git a/daemon/crypto.c b/daemon/crypto.c index 2e65d332f..063de724e 100644 --- a/daemon/crypto.c +++ b/daemon/crypto.c @@ -1,6 +1,7 @@ #include "crypto.h" #include +#include #include "str.h" #include "aux.h" @@ -13,6 +14,8 @@ const struct crypto_suite crypto_suites[] = { .name = "AES_CM_128_HMAC_SHA1_80", .master_key_len = 128, .master_salt_len = 112, + .session_key_len = 128, + .session_salt_len = 112, .srtp_lifetime = 1ULL << 48, .srtcp_lifetime = 1ULL << 31, .cipher = CIPHER_AES_CM, @@ -27,6 +30,8 @@ const struct crypto_suite crypto_suites[] = { .name = "AES_CM_128_HMAC_SHA1_32", .master_key_len = 128, .master_salt_len = 112, + .session_key_len = 128, + .session_salt_len = 112, .srtp_lifetime = 1ULL << 48, .srtcp_lifetime = 1ULL << 31, .cipher = CIPHER_AES_CM, @@ -41,6 +46,8 @@ const struct crypto_suite crypto_suites[] = { .name = "F8_128_HMAC_SHA1_80", .master_key_len = 128, .master_salt_len = 112, + .session_key_len = 128, + .session_salt_len = 112, .srtp_lifetime = 1ULL << 48, .srtcp_lifetime = 1ULL << 31, .cipher = CIPHER_AES_F8, @@ -79,3 +86,100 @@ const struct crypto_suite *crypto_find_suite(const str *s) { return NULL; } + + + +/* rfc 3711 section 4.1 and 4.1.1 */ +static void aes_ctr_128(char *out, str *in, char *key, char *iv) { + EVP_CIPHER_CTX ecc; + unsigned char ivx[16]; + unsigned char key_block[16]; + unsigned char *p, *q; + unsigned int left; + int outlen, i; + + memcpy(ivx, iv, 16); + p = (unsigned char *) in->s; + q = (unsigned char *) out; + left = in->len; + + /* XXX do this only once per thread? */ + EVP_CIPHER_CTX_init(&ecc); + + EVP_EncryptInit_ex(&ecc, EVP_aes_128_ecb(), NULL, (unsigned char *) key, NULL); + + while (left) { + EVP_EncryptUpdate(&ecc, key_block, &outlen, ivx, 16); + assert(outlen == 16); + + for (i = 0; i < 16; i++) { + *q = *p ^ key_block[i]; + q++; + p++; + left--; + if (!left) + goto done; + } + + for (i = 15; i >= 0; i--) { + ivx[i]++; + if (G_LIKELY(ivx[i])) + break; + } + } + +done: + + EVP_EncryptFinal_ex(&ecc, key_block, &outlen); + /* assert(outlen == 0); */ + + EVP_CIPHER_CTX_cleanup(&ecc); +} + +/* rfc 3711 section 4.3.1 and 4.3.3 + * key: 128 bits + * x: 112 bits + * n <= 256 + * out->len := n / 8 */ +static void prf_n(str *out, char *key, char *x) { + char iv[16]; + char o[32]; + char in[32]; + str in_s; + + assert(sizeof(o) >= out->len); + + ZERO(iv); + memcpy(iv, x, 14); + /* iv[14] = iv[15] = 0; := x << 16 */ + ZERO(in); /* outputs the key stream */ + str_init_len(&in_s, in, 16); + aes_ctr_128(o, &in_s, key, iv); + + memcpy(out->s, o, out->len); +} + + + +/* rfc 3711 section 4.3.1 */ +int crypto_gen_session_key(struct crypto_context *c, str *out, unsigned char label) { + unsigned char key_id[7]; /* [ label, 48-bit ROC || SEQ ] */ + unsigned char x[14]; + int i; + + if (!c->crypto_suite) + return -1; + + ZERO(key_id); + /* key_id[1..6] := r + * key_derivation_rate == 0 --> r == 0 */ + + key_id[0] = label; + memcpy(x, c->master_salt, 14); + for (i = 7; i < 14; i++) + x[i] = key_id[i - 7] ^ x[i]; + + prf_n(out, c->master_key, (char *) x); + + return 0; +} diff --git a/daemon/crypto.h b/daemon/crypto.h index f2585fdaa..d2d1f7cbf 100644 --- a/daemon/crypto.h +++ b/daemon/crypto.h @@ -3,6 +3,7 @@ +#include #include "str.h" @@ -28,10 +29,12 @@ struct crypto_suite { unsigned int master_key_len, master_salt_len, + session_key_len, /* n_e */ + session_salt_len, /* n_s */ encryption_key, - srtp_auth_tag, + srtp_auth_tag, /* n_a */ srtcp_auth_tag, - srtp_auth_key_len, + srtp_auth_key_len, /* n_a */ srtcp_auth_key_len; unsigned long long int srtp_lifetime, @@ -40,6 +43,28 @@ struct crypto_suite { enum mac mac; }; +struct crypto_context { + const struct crypto_suite *crypto_suite; + /* we only support one master key for now */ + char master_key[16]; + char master_salt[14]; + u_int64_t mki; + unsigned int mki_len; + + /* from rfc 3711 */ + u_int32_t roc; + u_int16_t s_l; + /* XXX replay list */ + u_int64_t num_packets; + /* ? */ + + char session_key[16]; + char session_salt[14]; + char session_auth_key[20]; + + int have_session_key:1; +}; + @@ -49,6 +74,7 @@ extern const int num_crypto_suites; const struct crypto_suite *crypto_find_suite(const str *); +int crypto_gen_session_key(struct crypto_context *, str *, unsigned char); diff --git a/daemon/rtp.c b/daemon/rtp.c new file mode 100644 index 000000000..d8a4a8d2c --- /dev/null +++ b/daemon/rtp.c @@ -0,0 +1,62 @@ +#include "rtp.h" + +#include +#include "str.h" +#include "crypto.h" + + + + +struct rtp_header { + unsigned char v_p_x_cc; + unsigned char m_pt; + u_int16_t seq_num; + u_int32_t timestamp; + u_int32_t ssrc; + u_int32_t csrc[]; +} __attribute__ ((packed)); + + + + + +static inline int check_session_key(struct crypto_context *c) { + str s; + + if (c->have_session_key) + return 0; + + str_init_len(&s, c->session_key, c->crypto_suite->session_key_len); + if (crypto_gen_session_key(c, &s, 0x00)) + return -1; + str_init_len(&s, c->session_auth_key, c->crypto_suite->srtp_auth_key_len); + if (crypto_gen_session_key(c, &s, 0x01)) + return -1; + str_init_len(&s, c->session_salt, c->crypto_suite->session_salt_len); + if (crypto_gen_session_key(c, &s, 0x02)) + return -1; + + c->have_session_key = 1; + return 0; +} + +/* XXX some error handling/logging here */ +int rtp_avp2savp(str *s, struct crypto_context *c) { + struct rtp_header *rtp; + + if (s->len < sizeof(*rtp)) + return -1; + + rtp = (void *) s->s; + + if (check_session_key(c)) + return -1; + + return 0; +} + +int rtp_savp2avp(str *s, struct crypto_context *c) { + if (check_session_key(c)) + return -1; + return 0; +} diff --git a/daemon/rtp.h b/daemon/rtp.h new file mode 100644 index 000000000..d219938f0 --- /dev/null +++ b/daemon/rtp.h @@ -0,0 +1,18 @@ +#ifndef _RTP_H_ +#define _RTP_H_ + + + +#include "str.h" + + + +struct crypto_context; + +int rtp_avp2savp(str *, struct crypto_context *); +int rtp_savp2avp(str *, struct crypto_context *); + + + + +#endif diff --git a/daemon/sdp.c b/daemon/sdp.c index dadd22253..cd4cf8fdd 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -97,7 +97,7 @@ struct attribute_crypto { str master_key; str salt; char key_salt_buf[30]; - unsigned long long lifetime; + u_int64_t lifetime; unsigned int mki, mki_len; }; @@ -306,6 +306,7 @@ static int parse_attribute_ssrc(struct sdp_attribute *output) { return 0; } +/* XXX error handling/logging */ static int parse_attribute_crypto(struct sdp_attribute *output) { char *start, *end; struct attribute_crypto *c; @@ -393,7 +394,7 @@ static int parse_attribute_crypto(struct sdp_attribute *output) { return -1; c->mki = strtoul(c->mki_str.s, NULL, 10); c->mki_len = strtoul(s.s + 1, NULL, 10); - if (!c->mki || !c->mki_len) + if (!c->mki || !c->mki_len || c->mki_len > 128) return -1; } @@ -791,6 +792,20 @@ int sdp_streams(const GQueue *sessions, GQueue *streams, GHashTable *streamhash, si->is_rtcp = 1; si->stream.protocol = tp; + id = ATTR_CRYPTO; + attr = g_hash_table_lookup(media->attributes.id_hash, &id); + if (attr) { + si->stream.crypto.crypto_suite = attr->u.crypto.crypto_suite; + si->stream.crypto.mki = attr->u.crypto.mki; + si->stream.crypto.mki_len = attr->u.crypto.mki_len; + assert(sizeof(si->stream.crypto.master_key) >= attr->u.crypto.master_key.len); + assert(sizeof(si->stream.crypto.master_salt) >= attr->u.crypto.salt.len); + memcpy(si->stream.crypto.master_key, attr->u.crypto.master_key.s, attr->u.crypto.master_key.len); + memcpy(si->stream.crypto.master_salt, attr->u.crypto.salt.s, attr->u.crypto.salt.len); + assert(sizeof(si->stream.crypto.session_key) >= attr->u.crypto.crypto_suite->session_key_len); + assert(sizeof(si->stream.crypto.session_salt) >= attr->u.crypto.crypto_suite->session_salt_len); + } + g_hash_table_insert(streamhash, si, si); g_queue_push_tail(streams, si); } diff --git a/daemon/str.h b/daemon/str.h index 751596b2f..dd8546d95 100644 --- a/daemon/str.h +++ b/daemon/str.h @@ -43,6 +43,8 @@ static inline int str_cmp_str(const str *a, const str *b); static inline int str_cmp_str0(const str *a, const str *b); /* inits a str object from a regular string */ static inline void str_init(str *out, char *s); +/* inits a str object from any binary string */ +static inline void str_init_len(str *out, char *s, int len); /* returns new str object allocated with malloc, including buffer */ static inline str *str_dup(const str *s); /* returns new str object allocated from chunk, including buffer */ @@ -131,6 +133,10 @@ static inline void str_init(str *out, char *s) { out->s = s; out->len = s ? strlen(s) : 0; } +static inline void str_init_len(str *out, char *s, int len) { + out->s = s; + out->len = len; +} static inline str *str_dup(const str *s) { str *r; r = malloc(sizeof(*r) + s->len + 1);