diff --git a/daemon/Makefile b/daemon/Makefile index 07207da7e..b831a6282 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -16,7 +16,7 @@ else CFLAGS+= -O3 endif -LDFLAGS= -ldl -rdynamic +LDFLAGS= -ldl -rdynamic -lm LDFLAGS+= `pkg-config --libs glib-2.0` LDFLAGS+= `pkg-config --libs gthread-2.0` LDFLAGS+= `pkg-config --libs zlib` @@ -32,7 +32,8 @@ LDFLAGS+= `dpkg-buildflags --get LDFLAGS` 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 + bencode.c cookie_cache.c udp_listener.c control_ng.c sdp.c str.c stun.c rtcp.c \ + crypto.c OBJS= $(SRCS:.c=.o) diff --git a/daemon/call.c b/daemon/call.c index f0db9e71f..16e2fa159 100644 --- a/daemon/call.c +++ b/daemon/call.c @@ -112,7 +112,7 @@ static char *rtp_codecs[] = { [33] = "MP2T", [34] = "H263", }; -const char *transport_protocol_strings[__PROTO_RTP_LAST] = { +const char *transport_protocol_strings[__PROTO_LAST] = { [PROTO_RTP_AVP] = "RTP/AVP", [PROTO_RTP_SAVP] = "RTP/SAVP", [PROTO_RTP_AVPF] = "RTP/AVPF", @@ -2283,6 +2283,24 @@ struct callstream *callstream_new(struct call *ca, int num) { } +enum transport_protocol transport_protocol(const str *s) { + int i; + + if (!s || !s->s) + goto out; + + for (i = PROTO_UNKNOWN + 1; i < __PROTO_LAST; i++) { + if (strlen(transport_protocol_strings[i]) != s->len) + continue; + if (strncasecmp(transport_protocol_strings[i], s->s, s->len)) + continue; + return i; + } + +out: + return PROTO_UNKNOWN; +} + static void call_ng_process_flags(struct sdp_ng_flags *out, GQueue *streams, bencode_item_t *input) { bencode_item_t *list, *it; struct stream_input *si; @@ -2344,7 +2362,8 @@ static void call_ng_process_flags(struct sdp_ng_flags *out, GQueue *streams, ben out->ice_force = 1; } - bencode_dictionary_get_str(input, "transport-protocol", &out->transport_protocol); + bencode_dictionary_get_str(input, "transport-protocol", &out->transport_protocol_str); + out->transport_protocol = transport_protocol(&out->transport_protocol_str); } static unsigned int stream_hash(struct stream_input *s) { diff --git a/daemon/call.h b/daemon/call.h index ad1d2a8a5..f41a373c6 100644 --- a/daemon/call.h +++ b/daemon/call.h @@ -54,9 +54,9 @@ enum transport_protocol { PROTO_RTP_AVPF, PROTO_RTP_SAVPF, - __PROTO_RTP_LAST + __PROTO_LAST }; -extern const char *transport_protocol_strings[__PROTO_RTP_LAST]; +extern const char *transport_protocol_strings[__PROTO_LAST]; struct stats { u_int64_t packets; @@ -242,5 +242,7 @@ static inline str *call_str_init_dup(struct call *c, char *s) { +enum transport_protocol transport_protocol(const str *s); + #endif diff --git a/daemon/crypto.c b/daemon/crypto.c new file mode 100644 index 000000000..2d502cff5 --- /dev/null +++ b/daemon/crypto.c @@ -0,0 +1,78 @@ +#include "crypto.h" + +#include + +#include "str.h" + + + +/* all lengths are in bits, some code assumes everything to be multiples of 8 */ +const struct crypto_suite_params crypto_suite_params[__CS_LAST] = { + [CS_AES_CM_128_HMAC_SHA1_80] = { + .name = "AES_CM_128_HMAC_SHA1_80", + .master_key_len = 128, + .master_salt_len = 112, + .srtp_lifetime = 1ULL << 48, + .srtcp_lifetime = 1ULL << 31, + .cipher = CIPHER_AES_CM, + .encryption_key = 128, + .mac = MAC_HMAC_SHA1, + .srtp_auth_tag = 80, + .srtcp_auth_tag = 80, + .srtp_auth_key_len = 160, + .srtcp_auth_key_len = 160, + }, + [CS_AES_CM_128_HMAC_SHA1_32] = { + .name = "AES_CM_128_HMAC_SHA1_32", + .master_key_len = 128, + .master_salt_len = 112, + .srtp_lifetime = 1ULL << 48, + .srtcp_lifetime = 1ULL << 31, + .cipher = CIPHER_AES_CM, + .encryption_key = 128, + .mac = MAC_HMAC_SHA1, + .srtp_auth_tag = 32, + .srtcp_auth_tag = 80, + .srtp_auth_key_len = 160, + .srtcp_auth_key_len = 160, + }, + [CS_F8_128_HMAC_SHA1_80] = { + .name = "F8_128_HMAC_SHA1_80", + .master_key_len = 128, + .master_salt_len = 112, + .srtp_lifetime = 1ULL << 48, + .srtcp_lifetime = 1ULL << 31, + .cipher = CIPHER_AES_F8, + .encryption_key = 128, + .mac = MAC_HMAC_SHA1, + .srtp_auth_tag = 80, + .srtcp_auth_tag = 80, + .srtp_auth_key_len = 160, + .srtcp_auth_key_len = 160, + }, +}; + + + + +enum crypto_suite crypto_find_suite(const str *s) { + int i, l; + const struct crypto_suite_params *cs; + + for (i = CS_UNKNOWN + 1; i < __CS_LAST; i++) { + cs = &crypto_suite_params[i]; + if (!cs->name) + continue; + + l = strlen(cs->name); + if (l != s->len) + continue; + + if (strncasecmp(cs->name, s->s, s->len)) + continue; + + return i; + } + + return CS_UNKNOWN; +} diff --git a/daemon/crypto.h b/daemon/crypto.h new file mode 100644 index 000000000..2492f2914 --- /dev/null +++ b/daemon/crypto.h @@ -0,0 +1,63 @@ +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + + + +#include "str.h" + + + +/* XXX get rid of the enums and replace with struct pointers? */ +enum crypto_suite { + CS_UNKNOWN = 0, + CS_AES_CM_128_HMAC_SHA1_80, + CS_AES_CM_128_HMAC_SHA1_32, + CS_F8_128_HMAC_SHA1_80, + + __CS_LAST +}; + +enum cipher { + CIPHER_UNKNOWN = 0, + CIPHER_AES_CM, + CIPHER_AES_F8, + + __CIPHER_LAST +}; + +enum mac { + MAC_UNKNOWN = 0, + MAC_HMAC_SHA1, + + __MAC_LAST +}; + +struct crypto_suite_params { + const char *name; + unsigned int + master_key_len, + master_salt_len, + encryption_key, + srtp_auth_tag, + srtcp_auth_tag, + srtp_auth_key_len, + srtcp_auth_key_len; + unsigned long long int + srtp_lifetime, + srtcp_lifetime; + enum cipher cipher; + enum mac mac; +}; + + + + +extern const struct crypto_suite_params crypto_suite_params[__CS_LAST]; + + + +enum crypto_suite crypto_find_suite(const str *); + + + +#endif diff --git a/daemon/sdp.c b/daemon/sdp.c index 09421c281..880217735 100644 --- a/daemon/sdp.c +++ b/daemon/sdp.c @@ -4,11 +4,13 @@ #include #include #include +#include #include "call.h" #include "log.h" #include "str.h" #include "call.h" +#include "crypto.h" struct network_address { str network_type; @@ -79,6 +81,26 @@ struct attribute_candidate { int parsed:1; }; +struct attribute_crypto { + str tag_str; + str crypto_suite_str; + str key_params_str; + /* str session_params; */ + + str key_base64_str; + str lifetime_str; + str mki_str; + + unsigned int tag; + enum crypto_suite crypto_suite; + str master_key; + str salt; + char key_salt_buf[30]; + unsigned long long lifetime; + unsigned int mki, + mki_len; +}; + struct sdp_attribute { str full_line, /* including a= and \r\n */ line_value, /* without a= and without \r\n */ @@ -92,11 +114,13 @@ struct sdp_attribute { ATTR_RTCP, ATTR_CANDIDATE, ATTR_ICE, + ATTR_CRYPTO, } attr; union { struct attribute_rtcp rtcp; struct attribute_candidate candidate; + struct attribute_crypto crypto; } u; }; @@ -240,6 +264,100 @@ static void attrs_init(struct sdp_attributes *a) { NULL, (GDestroyNotify) g_queue_free); } +static int parse_attribute_crypto(struct sdp_attribute *output) { + char *start, *end; + struct attribute_crypto *c; + const struct crypto_suite_params *cs; + int salt_key_len, enc_salt_key_len; + int b64_state = 0; + unsigned int b64_save = 0; + gsize ret; + str s; + + output->attr = ATTR_CRYPTO; + + start = output->value.s; + end = start + output->value.len; + + EXTRACT_TOKEN(u.crypto.tag_str); + EXTRACT_TOKEN(u.crypto.crypto_suite_str); + EXTRACT_TOKEN(u.crypto.key_params_str); + + c = &output->u.crypto; + + c->crypto_suite = crypto_find_suite(&c->crypto_suite_str); + if (c->crypto_suite == CS_UNKNOWN) + return -1; + cs = &crypto_suite_params[c->crypto_suite]; + /* assume everything is a multiple of 8 */ + salt_key_len = (cs->master_key_len + cs->master_salt_len) / 8; + assert(sizeof(c->key_salt_buf) >= salt_key_len); + enc_salt_key_len = ceil((double) salt_key_len * 4.0/3.0); + + if (c->key_params_str.len < 7 + enc_salt_key_len) + return -1; + if (strncasecmp(c->key_params_str.s, "inline:", 7)) + return -1; + c->key_base64_str = c->key_params_str; + str_shift(&c->key_base64_str, 7); + ret = g_base64_decode_step(c->key_base64_str.s, enc_salt_key_len, + (guchar *) c->key_salt_buf, &b64_state, &b64_save); + if (ret != salt_key_len) + return -1; + + c->master_key.s = c->key_salt_buf; + c->master_key.len = cs->master_key_len / 8; + c->salt.s = c->master_key.s + c->master_key.len; + c->salt.len = cs->master_salt_len / 8; + + c->lifetime_str = c->key_params_str; + str_shift(&c->lifetime_str, 7 + enc_salt_key_len); + if (c->lifetime_str.len >= 2) { + if (c->lifetime_str.s[0] != '|') + return -1; + str_shift(&c->lifetime_str, 1); + str_chr_str(&c->mki_str, &c->lifetime_str, '|'); + if (!c->mki_str.s) { + if (str_chr(&c->lifetime_str, ':')) { + c->mki_str = c->lifetime_str; + c->lifetime_str = STR_NULL; + } + } + else { + c->lifetime_str.len = c->mki_str.s - c->lifetime_str.s; + str_shift(&c->mki_str, 1); + } + } + else + c->lifetime_str = STR_NULL; + + if (c->lifetime_str.s) { + if (c->lifetime_str.len >= 3 && !memcmp(c->lifetime_str.s, "2^", 2)) { + c->lifetime = strtoull(c->lifetime_str.s + 2, NULL, 10); + if (!c->lifetime || c->lifetime > 64) + return -1; + c->lifetime = 1 << c->lifetime; + } + else + c->lifetime = strtoull(c->lifetime_str.s, NULL, 10); + + if (!c->lifetime || c->lifetime > cs->srtp_lifetime || c->lifetime > cs->srtcp_lifetime) + return -1; + } + + if (c->mki_str.s) { + str_chr_str(&s, &c->mki_str, ':'); + if (!s.s) + 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) + return -1; + } + + return 0; +} + static int parse_attribute_rtcp(struct sdp_attribute *output) { char *ep, *start, *end; @@ -311,11 +429,16 @@ static void parse_attribute(struct sdp_attribute *a) { a->key.len += 1 + a->value.len; } + /* XXX add error handling */ switch (a->name.len) { case 4: if (!str_cmp(&a->name, "rtcp")) parse_attribute_rtcp(a); break; + case 6: + if (!str_cmp(&a->name, "crypto")) + parse_attribute_crypto(a); + break; case 7: if (!str_cmp(&a->name, "ice-pwd")) a->attr = ATTR_ICE; @@ -557,34 +680,6 @@ static int fill_stream_rtcp(struct stream_input *si, struct sdp_media *media, in return 0; } -static enum transport_protocol transport_protocol(str *s) { - switch (s->len) { - case 7: - if (!str_cmp(s, "RTP/AVP")) - return PROTO_RTP_AVP; - if (!str_cmp(s, "rtp/avp")) - return PROTO_RTP_AVP; - break; - case 8: - if (!str_cmp(s, "RTP/SAVP")) - return PROTO_RTP_SAVP; - if (!str_cmp(s, "rtp/savp")) - return PROTO_RTP_SAVP; - if (!str_cmp(s, "RTP/AVPF")) - return PROTO_RTP_AVPF; - if (!str_cmp(s, "rtp/avpf")) - return PROTO_RTP_AVPF; - break; - case 9: - if (!str_cmp(s, "rtp/savpf")) - return PROTO_RTP_SAVPF; - if (!str_cmp(s, "rtp/savpf")) - return PROTO_RTP_SAVPF; - break; - } - return PROTO_UNKNOWN; -} - int sdp_streams(const GQueue *sessions, GQueue *streams, GHashTable *streamhash, struct sdp_ng_flags *flags) { struct sdp_session *session; struct sdp_media *media; @@ -939,6 +1034,16 @@ static int process_media_attributes(struct sdp_chopper *chop, struct sdp_attribu case ATTR_RTCP: goto strip; + case ATTR_CRYPTO: + switch (flags->transport_protocol) { + case PROTO_RTP_AVP: + case PROTO_RTP_AVPF: + goto strip; + default: + break; + } + break; + default: break; } @@ -1122,7 +1227,7 @@ int sdp_replace(struct sdp_chopper *chop, GQueue *sessions, struct call *call, goto error; fill_relays(&rtp, &rtcp, m, off, sip); - rtp->peer.protocol = transport_protocol(&flags->transport_protocol); + rtp->peer.protocol = flags->transport_protocol; rtcp->peer.protocol = rtp->peer.protocol; if (replace_media_port(chop, media, rtp)) diff --git a/daemon/sdp.h b/daemon/sdp.h index a92304b57..2ace50be3 100644 --- a/daemon/sdp.h +++ b/daemon/sdp.h @@ -10,7 +10,8 @@ struct sdp_ng_flags { int desired_family[2]; str received_from_family; str received_from_address; - str transport_protocol; + str transport_protocol_str; + enum transport_protocol transport_protocol; struct in6_addr parsed_address; int asymmetric:1, symmetric:1, diff --git a/daemon/str.h b/daemon/str.h index 008f50ab1..751596b2f 100644 --- a/daemon/str.h +++ b/daemon/str.h @@ -22,8 +22,8 @@ typedef struct _str str; #define STR_FORMAT "%.*s" #define STR_FMT(str) (str)->len, (str)->s #define STR_FMT0(str) ((str) ? (str)->len : 6), ((str) ? (str)->s : "(NULL)") -#define STR_NULL (str) { NULL, 0 } -#define STR_EMPTY (str) { "", 0 } +#define STR_NULL ((str) { NULL, 0 }) +#define STR_EMPTY ((str) { "", 0 })