Index: include/asterisk/aes.h =================================================================== --- include/asterisk/aes.h (revision 7211) +++ include/asterisk/aes.h (arbetskopia) @@ -115,6 +115,8 @@ #ifdef AES_ENCRYPT +#define aes_encrypt ast_aes_encrypt + typedef struct { aes_32t ks[KS_LENGTH]; } aes_encrypt_ctx; @@ -140,6 +142,8 @@ #ifdef AES_DECRYPT +#define aes_decrypt ast_aes_decrypt + typedef struct { aes_32t ks[KS_LENGTH]; } aes_decrypt_ctx; Index: include/asterisk/rtp.h =================================================================== --- include/asterisk/rtp.h (revision 7211) +++ include/asterisk/rtp.h (arbetskopia) @@ -67,6 +67,66 @@ */ struct ast_rtp; +struct ast_srtp; + +struct ast_srtp_policy; + +struct ast_srtp_cb { + int (*no_ctx)(struct ast_rtp *rtp, unsigned long ssrc, void *data); +}; + +struct ast_srtp_res { + int (*create)(struct ast_srtp **srtp, struct ast_rtp *rtp, + struct ast_srtp_policy *policy); + void (*destroy)(struct ast_srtp *srtp); + int (*add_stream)(struct ast_srtp *srtp, struct ast_srtp_policy *policy); + void (*set_cb)(struct ast_srtp *srtp, + const struct ast_srtp_cb *cb, void *data); + int (*unprotect)(struct ast_srtp *srtp, void *buf, int *size); + int (*protect)(struct ast_srtp *srtp, void **buf, int *size); + int (*get_random)(unsigned char *key, size_t len); +}; + +/* Crypto suites */ +enum ast_srtp_suite { + AST_AES_CM_128_HMAC_SHA1_80 = 1, + AST_AES_CM_128_HMAC_SHA1_32 = 2, + AST_F8_128_HMAC_SHA1_80 = 3 +}; + +enum ast_srtp_ealg { + AST_MIKEY_SRTP_EALG_NULL = 0, + AST_MIKEY_SRTP_EALG_AESCM = 1 +}; + +enum ast_srtp_aalg { + AST_MIKEY_SRTP_AALG_NULL = 0, + AST_MIKEY_SRTP_AALG_SHA1HMAC = 1 +}; + +struct ast_srtp_policy_res { + struct ast_srtp_policy *(*alloc)(void); + void (*destroy)(struct ast_srtp_policy *policy); + int (*set_suite)(struct ast_srtp_policy *policy, + enum ast_srtp_suite suite); + int (*set_master_key)(struct ast_srtp_policy *policy, + const unsigned char *key, size_t key_len, + const unsigned char *salt, size_t salt_len); + int (*set_encr_alg)(struct ast_srtp_policy *policy, + enum ast_srtp_ealg ealg); + int (*set_auth_alg)(struct ast_srtp_policy *policy, + enum ast_srtp_aalg aalg); + void (*set_encr_keylen)(struct ast_srtp_policy *policy, int ekeyl); + void (*set_auth_keylen)(struct ast_srtp_policy *policy, int akeyl); + void (*set_srtp_auth_taglen)(struct ast_srtp_policy *policy, int autht); + void (*set_srtp_encr_enable)(struct ast_srtp_policy *policy, int enable); + void (*set_srtcp_encr_enable)(struct ast_srtp_policy *policy, int enable); + void (*set_srtp_auth_enable)(struct ast_srtp_policy *policy, int enable); + void (*set_ssrc)(struct ast_srtp_policy *policy, unsigned long ssrc, + int inbound); +}; + + typedef int (*ast_rtp_callback)(struct ast_rtp *rtp, struct ast_frame *f, void *data); /*! @@ -160,6 +220,32 @@ void ast_rtp_reload(void); +int ast_rtp_register_srtp(struct ast_srtp_res *srtp_res, struct ast_srtp_policy_res *policy_res); + +int ast_rtp_unregister_srtp(struct ast_srtp_res *srtp_res, struct ast_srtp_policy_res *policy_res); + +int ast_srtp_is_registered(void); + +unsigned int ast_rtp_get_ssrc(struct ast_rtp *rtp); +void ast_rtp_set_srtp_cb(struct ast_rtp *rtp, const struct ast_srtp_cb *cb, + void *data); +int ast_rtp_add_srtp_policy(struct ast_rtp *rtp, struct ast_srtp_policy *policy); +struct ast_srtp_policy *ast_srtp_policy_alloc(void); +int ast_srtp_policy_set_suite(struct ast_srtp_policy *policy, enum ast_srtp_suite suite); +int ast_srtp_policy_set_master_key(struct ast_srtp_policy *policy, const unsigned char *key, size_t key_len, const unsigned char *salt, size_t salt_len); +int ast_srtp_policy_set_encr_alg(struct ast_srtp_policy *policy, enum ast_srtp_ealg ealg); +int ast_srtp_policy_set_auth_alg(struct ast_srtp_policy *policy, enum ast_srtp_aalg aalg); +void ast_srtp_policy_set_encr_keylen(struct ast_srtp_policy *policy, int ekeyl); +void ast_srtp_policy_set_auth_keylen(struct ast_srtp_policy *policy, int akeyl); +void ast_srtp_policy_set_srtp_auth_taglen(struct ast_srtp_policy *policy, int autht); +void ast_srtp_policy_set_srtp_encr_enable(struct ast_srtp_policy *policy, int enable); +void ast_srtp_policy_set_srtcp_encr_enable(struct ast_srtp_policy *policy, int enable); +void ast_srtp_policy_set_srtp_auth_enable(struct ast_srtp_policy *policy, int enable); +void ast_srtp_policy_set_ssrc(struct ast_srtp_policy *policy, unsigned long ssrc, int inbound); + +void ast_srtp_policy_destroy(struct ast_srtp_policy *policy); +int ast_srtp_get_random(unsigned char *key, size_t len); + #if defined(__cplusplus) || defined(c_plusplus) } #endif Index: rtp.c =================================================================== --- rtp.c (revision 7211) +++ rtp.c (arbetskopia) @@ -70,6 +70,9 @@ static int nochecksums = 0; #endif +struct ast_srtp_res *g_srtp_res; +struct ast_srtp_policy_res *g_policy_res; + /* The value of each payload format mapping: */ struct rtpPayloadType { int isAstFormat; /* whether the following code is an AST_FORMAT */ @@ -126,6 +129,7 @@ int rtp_lookup_code_cache_result; int rtp_offered_from_local; struct ast_rtcp *rtcp; + struct ast_srtp *srtp; }; /*! @@ -354,6 +358,235 @@ return f; } +int ast_rtp_register_srtp(struct ast_srtp_res *srtp_res, + struct ast_srtp_policy_res *policy_res) +{ + if (g_srtp_res || g_policy_res) + return -1; + + if (!srtp_res || !policy_res) + return -1; + + g_srtp_res = srtp_res; + g_policy_res = policy_res; + return 0; +} + +int ast_rtp_unregister_srtp(struct ast_srtp_res *srtp_res, + struct ast_srtp_policy_res *policy_res) +{ + g_srtp_res = NULL; + g_policy_res = NULL; + return 0; +} + +int ast_srtp_is_registered(void) +{ + return g_srtp_res && g_policy_res; +} + +unsigned int ast_rtp_get_ssrc(struct ast_rtp *rtp) +{ + return rtp->ssrc; +} + +void ast_rtp_set_srtp_cb(struct ast_rtp *rtp, const struct ast_srtp_cb *cb, + void *data) +{ + if (!g_srtp_res || !rtp->srtp) + return; + + g_srtp_res->set_cb(rtp->srtp, cb, data); +} + +void +ast_srtp_policy_set_ssrc(struct ast_srtp_policy *policy, + unsigned long ssrc, int inbound) +{ + if (!g_policy_res) + return; + + g_policy_res->set_ssrc(policy, ssrc, inbound); +} + +int ast_rtp_add_srtp_policy(struct ast_rtp *rtp, struct ast_srtp_policy *policy) +{ + int res; + + if (!g_srtp_res) + return -1; + + if (!rtp->srtp) { + res = g_srtp_res->create(&rtp->srtp, rtp, policy); + } else { + res = g_srtp_res->add_stream(rtp->srtp, policy); + } + + return res; +} + +struct ast_srtp_policy *ast_srtp_policy_alloc() +{ + if (!g_policy_res) + return NULL; + + return g_policy_res->alloc(); +} + +void +ast_srtp_policy_destroy(struct ast_srtp_policy *policy) +{ + if (!g_policy_res) + return; + + g_policy_res->destroy(policy); +} + +int +ast_srtp_policy_set_suite(struct ast_srtp_policy *policy, + enum ast_srtp_suite suite) +{ + if (!g_policy_res) + return -1; + + return g_policy_res->set_suite(policy, suite); +} + +int +ast_srtp_policy_set_master_key(struct ast_srtp_policy *policy, + const unsigned char *key, size_t key_len, + const unsigned char *salt, size_t salt_len) +{ + if (!g_policy_res) + return -1; + + return g_policy_res->set_master_key(policy, key, key_len, + salt, salt_len); +} + +int +ast_srtp_policy_set_encr_alg(struct ast_srtp_policy *policy, + enum ast_srtp_ealg ealg) +{ + if (!g_policy_res) + return -1; + + return g_policy_res->set_encr_alg(policy, ealg); +} + +int +ast_srtp_policy_set_auth_alg(struct ast_srtp_policy *policy, + enum ast_srtp_aalg aalg) +{ + if (!g_policy_res) + return -1; + + return g_policy_res->set_auth_alg(policy, aalg); +} + +void ast_srtp_policy_set_encr_keylen(struct ast_srtp_policy *policy, int ekeyl) +{ + if (!g_policy_res) + return; + + return g_policy_res->set_encr_keylen(policy, ekeyl); +} + +void +ast_srtp_policy_set_auth_keylen(struct ast_srtp_policy *policy, + int akeyl) +{ + if (!g_policy_res) + return; + + return g_policy_res->set_auth_keylen(policy, akeyl); +} + +void +ast_srtp_policy_set_srtp_auth_taglen(struct ast_srtp_policy *policy, + int autht) +{ + if (!g_policy_res) + return; + + return g_policy_res->set_srtp_auth_taglen(policy, autht); +} + +void +ast_srtp_policy_set_srtp_encr_enable(struct ast_srtp_policy *policy, + int enable) +{ + if (!g_policy_res) + return; + + return g_policy_res->set_srtp_encr_enable(policy, enable); +} + +void +ast_srtp_policy_set_srtcp_encr_enable(struct ast_srtp_policy *policy, + int enable) +{ + if (!g_policy_res) + return; + + return g_policy_res->set_srtcp_encr_enable(policy, enable); +} + +void +ast_srtp_policy_set_srtp_auth_enable(struct ast_srtp_policy *policy, + int enable) +{ + if (!g_policy_res) + return; + + return g_policy_res->set_srtp_auth_enable(policy, enable); +} + +int ast_srtp_get_random(unsigned char *key, size_t len) +{ + if (!g_srtp_res) + return -1; + + return g_srtp_res->get_random(key, len); +} + +static int rtp_recvfrom(struct ast_rtp *rtp, void *buf, size_t size, + int flags, struct sockaddr *sa, socklen_t *salen) +{ + int len; + + len = recvfrom(rtp->s, buf, size, flags, sa, salen); + + if (len < 0) + return len; + + if (g_srtp_res && rtp->srtp) { + int res; + + res = g_srtp_res->unprotect(rtp->srtp, buf, &len); + if (res < 0) + return -1; + } + + return len; +} + +static int rtp_sendto(struct ast_rtp *rtp, void *buf, size_t size, + int flags, struct sockaddr *sa, socklen_t salen) +{ + int len = size; + void *temp = buf; + + if (g_srtp_res && rtp->srtp) { + int res = g_srtp_res->protect(rtp->srtp, &temp, &len); + + if (res < 0) + return -1; + } + + return sendto(rtp->s, temp, len, flags, sa, salen); +} + static int rtpread(int *id, int fd, short events, void *cbdata) { struct ast_rtp *rtp = cbdata; @@ -444,7 +677,7 @@ len = sizeof(sin); /* Cache where the header will go */ - res = recvfrom(rtp->s, rtp->rawdata + AST_FRIENDLY_OFFSET, sizeof(rtp->rawdata) - AST_FRIENDLY_OFFSET, + res = rtp_recvfrom(rtp, rtp->rawdata + AST_FRIENDLY_OFFSET, sizeof(rtp->rawdata) - AST_FRIENDLY_OFFSET, 0, (struct sockaddr *)&sin, &len); @@ -1083,6 +1316,10 @@ close(rtp->rtcp->s); free(rtp->rtcp); } + if (g_srtp_res && rtp->srtp) { + g_srtp_res->destroy(rtp->srtp); + rtp->srtp = NULL; + } free(rtp); } @@ -1145,7 +1382,7 @@ rtpheader[3] = htonl((digit << 24) | (0xa << 16) | (0)); for (x = 0; x < 6; x++) { if (rtp->them.sin_port && rtp->them.sin_addr.s_addr) { - res = sendto(rtp->s, (void *) rtpheader, hdrlen + 4, 0, (struct sockaddr *) &rtp->them, sizeof(rtp->them)); + res = rtp_sendto(rtp, (void *) rtpheader, hdrlen + 4, 0, (struct sockaddr *) &rtp->them, sizeof(rtp->them)); if (res < 0) ast_log(LOG_ERROR, "RTP Transmission error to %s:%d: %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), rtp->them.sin_addr), @@ -1211,7 +1448,7 @@ rtpheader[2] = htonl(rtp->ssrc); data[12] = level; if (rtp->them.sin_port && rtp->them.sin_addr.s_addr) { - res = sendto(rtp->s, (void *)rtpheader, hdrlen + 1, 0, (struct sockaddr *)&rtp->them, sizeof(rtp->them)); + res = rtp_sendto(rtp, (void *)rtpheader, hdrlen + 1, 0, (struct sockaddr *)&rtp->them, sizeof(rtp->them)); if (res <0) ast_log(LOG_ERROR, "RTP Comfort Noise Transmission error to %s:%d: %s\n", ast_inet_ntoa(iabuf, sizeof(iabuf), rtp->them.sin_addr), ntohs(rtp->them.sin_port), strerror(errno)); if(rtp_debug_test_addr(&rtp->them)) @@ -1281,7 +1518,7 @@ put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc)); if (rtp->them.sin_port && rtp->them.sin_addr.s_addr) { - res = sendto(rtp->s, (void *)rtpheader, f->datalen + hdrlen, 0, (struct sockaddr *)&rtp->them, sizeof(rtp->them)); + res = rtp_sendto(rtp, (void *)rtpheader, f->datalen + hdrlen, 0, (struct sockaddr *)&rtp->them, sizeof(rtp->them)); if (res <0) { if (!rtp->nat || (rtp->nat && (ast_test_flag(rtp, FLAG_NAT_ACTIVE) == FLAG_NAT_ACTIVE))) { ast_log(LOG_DEBUG, "RTP Transmission error of packet %d to %s:%d: %s\n", rtp->seqno, ast_inet_ntoa(iabuf, sizeof(iabuf), rtp->them.sin_addr), ntohs(rtp->them.sin_port), strerror(errno)); @@ -1575,6 +1812,14 @@ ast_mutex_unlock(&c1->lock); return AST_BRIDGE_FAILED_NOWARN; } + + if (p0->srtp || p1->srtp) { + ast_log(LOG_NOTICE, "Cannot native bridge in SRTP.\n"); + ast_mutex_unlock(&c0->lock); + ast_mutex_unlock(&c1->lock); + return AST_BRIDGE_FAILED_NOWARN; + } + /* Get codecs from both sides */ if (pr0->get_codec) codec0 = pr0->get_codec(c0); Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 7211) +++ channels/chan_sip.c (arbetskopia) @@ -103,6 +103,17 @@ #define DEFAULT_REGISTRATION_TIMEOUT 20 #define DEFAULT_MAX_FORWARDS "70" +#define SRTP_MASTER_LEN 30 +#define SRTP_MASTERKEY_LEN 16 +#define SRTP_MASTERSALT_LEN (SRTP_MASTER_LEN - SRTP_MASTERKEY_LEN) +#define SRTP_MASTER_LEN64 ((SRTP_MASTER_LEN * 8 + 5) / 6 + 1) + +struct sip_srtp { + char *a_crypto; + unsigned char local_key[SRTP_MASTER_LEN]; + char local_key64[SRTP_MASTER_LEN64]; +}; + /* guard limit must be larger than guard secs */ /* guard min must be < 1000, and should be >= 250 */ #define EXPIRY_GUARD_SECS 15 /* How long before expiry do we reregister */ @@ -687,6 +698,7 @@ struct ast_variable *chanvars; /*!< Channel variables to set for call */ struct sip_pvt *next; /*!< Next call in chain */ struct sip_invite_param *options; /*!< Options for INVITE */ + struct sip_srtp *srtp; } *iflist = NULL; #define FLAG_RESPONSE (1 << 0) @@ -1990,8 +2002,231 @@ } +/* + * SRTP sdescriptions + * Specified in: draft-ietf-mmusic-sdescriptions-12.txt + */ +static struct sip_srtp *sip_srtp_alloc(void) +{ + struct sip_srtp *srtp = malloc(sizeof(*srtp)); + memset(srtp, 0, sizeof(*srtp)); + return srtp; +} + +static void sip_srtp_destroy(struct sip_srtp *srtp) +{ + free(srtp->a_crypto); + srtp->a_crypto = NULL; +} + + +static int setup_crypto(struct sip_pvt *p) +{ + if (!ast_srtp_is_registered()) + return -1; + + p->srtp = sip_srtp_alloc(); + if (!p->srtp) + return -1; + + if (ast_srtp_get_random(p->srtp->local_key, + sizeof(p->srtp->local_key)) < 0) { + sip_srtp_destroy(p->srtp); + p->srtp = NULL; + return -1; + } + + ast_base64encode(p->srtp->local_key64, p->srtp->local_key, + SRTP_MASTER_LEN, sizeof(p->srtp->local_key64)); + return 0; +} + +static int set_crypto_policy(struct ast_srtp_policy *policy, + int suite_val, const unsigned char *master_key, + unsigned long ssrc, int inbound) +{ + const unsigned char *master_salt = NULL; + + master_salt = master_key + SRTP_MASTERKEY_LEN; + if (ast_srtp_policy_set_master_key(policy, + master_key, SRTP_MASTERKEY_LEN, + master_salt, SRTP_MASTERSALT_LEN) < 0) + return -1; + + + if (ast_srtp_policy_set_suite(policy, suite_val)) { + ast_log(LOG_WARNING, "Could not set remote SRTP suite\n"); + return -1; + } + + ast_srtp_policy_set_ssrc(policy, ssrc, inbound); + + return 0; +} + +static int activate_crypto(struct sip_pvt *p, int suite_val, + unsigned char *remote_key) +{ + struct ast_srtp_policy *local_policy = NULL; + struct ast_srtp_policy *remote_policy = NULL; + int res = -1; + struct sip_srtp *srtp = p->srtp; + + if (!srtp) + return -1; + + local_policy = ast_srtp_policy_alloc(); + if (!local_policy) + goto err; + + remote_policy = ast_srtp_policy_alloc(); + if (!remote_policy) { + goto err; + } + + if (set_crypto_policy(local_policy, suite_val, srtp->local_key, + ast_rtp_get_ssrc(p->rtp), 0) < 0) + goto err; + + if (set_crypto_policy(remote_policy, suite_val, remote_key, 0, 1) < 0) + goto err; + + if (ast_rtp_add_srtp_policy(p->rtp, local_policy)) { + ast_log(LOG_WARNING, "Could not set local SRTP policy\n"); + goto err; + } + + if (ast_rtp_add_srtp_policy(p->rtp, remote_policy)) { + ast_log(LOG_WARNING, "Could not set remote SRTP policy\n"); + goto err; + } + + + ast_log(LOG_DEBUG, "SRTP policy activated\n"); + res = 0; + +err: + if (local_policy) + ast_srtp_policy_destroy(local_policy); + + if (remote_policy) + ast_srtp_policy_destroy(remote_policy); + return res; +} + +static int process_crypto(struct sip_pvt *p, const char *attr) +{ + char *str = NULL; + char *name = NULL; + char *tag = NULL; + char *suite = NULL; + char *key_params = NULL; + char *key_param = NULL; + char *session_params = NULL; + char *key_salt = NULL; + char *lifetime = NULL; + int found = 0; + int attr_len = strlen(attr); + int key_len = 0; + unsigned char remote_key[SRTP_MASTER_LEN]; + int suite_val = 0; + struct sip_srtp *srtp = p->srtp; + + if (!ast_srtp_is_registered()) + return -1; + + /* Crypto already accepted */ + if (srtp && srtp->a_crypto) + return -1; + + str = strdupa(attr); + + name = strsep(&str, ":"); + tag = strsep(&str, " "); + suite = strsep(&str, " "); + key_params = strsep(&str, " "); + session_params = strsep(&str, " "); + + if (!tag || !suite) { + ast_log(LOG_WARNING, "Unrecognized a=%s", attr); + return -1; + } + + if (session_params) { + ast_log(LOG_WARNING, "Unsupported crypto parameters: %s", + session_params); + return -1; + } + + if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_80")) { + suite_val = AST_AES_CM_128_HMAC_SHA1_80; + } else if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_32")) { + suite_val = AST_AES_CM_128_HMAC_SHA1_32; + } else { + ast_log(LOG_WARNING, "Unsupported crypto suite: %s", + suite); + return -1; + } + + while ((key_param = strsep(&key_params, ";"))) { + char *method = NULL; + char *info = NULL; + + method = strsep(&key_param, ":"); + info = strsep(&key_param, ";"); + + if (!strcmp(method, "inline")) { + key_salt = strsep(&info, "|"); + lifetime = strsep(&info, "|"); + + if (lifetime) { + ast_log(LOG_NOTICE, "Crypto life time unsupported: %s\n", + attr); + continue; + } + +/* if (info || strncmp(lifetime, "2^", 2)) { */ +/* ast_log(LOG_NOTICE, "MKI unsupported: %s\n", */ +/* attr); */ +/* continue; */ +/* } */ + + found = 1; + break; + } + } + + if (!found) { + ast_log(LOG_NOTICE, "SRTP crypto offer not acceptable\n"); + return -1; + } + + if (!srtp) { + setup_crypto(p); + srtp = p->srtp; + } + + key_len = ast_base64decode(remote_key, key_salt, sizeof(remote_key)); + if (key_len != SRTP_MASTER_LEN) { + ast_log(LOG_WARNING, "SRTP sdescriptions key %d != %d\n", + key_len, SRTP_MASTER_LEN); + return -1; + } + + if (activate_crypto(p, suite_val, remote_key) < 0) + return -1; + + srtp->a_crypto = malloc(attr_len+11); + snprintf(srtp->a_crypto, attr_len+10, + "a=crypto:%s %s inline:%s\r\n", + tag, suite, srtp->local_key64); + + return 0; +} + + /*! \brief sip_call: Initiate SIP call from PBX * used from the dial() application */ static int sip_call(struct ast_channel *ast, char *dest, int timeout) @@ -2028,6 +2263,18 @@ } else if (!p->options->addsipheaders && !strncasecmp(ast_var_name(current), "SIPADDHEADER", strlen("SIPADDHEADER"))) { /* Check whether there is a variable with a name starting with SIPADDHEADER */ p->options->addsipheaders = 1; + } else if (!strncasecmp(ast_var_name(current), "SIP_SRTP_SDES", strlen("SIP_SRTP_SDES"))) { + if (!ast_srtp_is_registered()) { + ast_log(LOG_WARNING, "SIP_SRTP_SDES set but SRTP is not available\n"); + return -1; + } + + if (!p->srtp) { + if (setup_crypto(p) < 0) { + ast_log(LOG_WARNING, "SIP SRTP sdes setup failed\n"); + return -1; + } + } } @@ -2176,6 +2423,10 @@ ast_variables_destroy(p->chanvars); p->chanvars = NULL; } + if (p->srtp) { + sip_srtp_destroy(p->srtp); + p->srtp = NULL; + } ast_mutex_destroy(&p->lock); free(p); } @@ -3010,6 +3261,7 @@ snprintf(tagbuf, len, "as%08x", thread_safe_rand()); } + /*! \brief sip_alloc: Allocate SIP_PVT structure and set defaults ---*/ static struct sip_pvt *sip_alloc(char *callid, struct sockaddr_in *sin, int useglobal_nat, const int intended_method) { @@ -3392,6 +3644,7 @@ int x,y; int debug=sip_debug_test_pvt(p); struct ast_channel *bridgepeer = NULL; + int secure_audio = 0; if (!p->rtp) { ast_log(LOG_ERROR, "Got SDP but have no RTP session allocated.\n"); @@ -3427,9 +3680,24 @@ sdpLineNum_iterator_init(&iterator); ast_set_flag(p, SIP_NOVIDEO); while ((m = get_sdp_iterate(&iterator, req, "m"))[0] != '\0') { + char protocol[5] = ""; int found = 0; - if ((sscanf(m, "audio %d/%d RTP/AVP %n", &x, &y, &len) == 2) || - (sscanf(m, "audio %d RTP/AVP %n", &x, &len) == 1)) { + + len = -1; + if ((sscanf(m, "audio %d/%d RTP/%4s %n", &x, &y, protocol, &len) == 3) || + (sscanf(m, "audio %d RTP/%4s %n", &x, protocol, &len) == 2)) { + if (!strcmp(protocol, "SAVP")) + secure_audio = 1; + else if (strcmp(protocol, "AVP")) { + ast_log(LOG_WARNING, "Unknown SDP media protocol in offer: %s\n", protocol); + continue; + } + + if (len < 0) { + ast_log(LOG_WARNING, "Unknown SDP media type in offer: %s\n", m); + continue; + } + found = 1; portno = x; /* Scan through the RTP payload types specified in a "m=" line: */ @@ -3449,7 +3717,13 @@ if (p->vrtp) ast_rtp_pt_clear(p->vrtp); /* Must be cleared in case no m=video line exists */ + len= -1; if (p->vrtp && (sscanf(m, "video %d RTP/AVP %n", &x, &len) == 1)) { + if (len < 0) { + ast_log(LOG_WARNING, "Unknown SDP media type in offer: %s\n", m); + continue; + } + found = 1; ast_clear_flag(p, SIP_NOVIDEO); vportno = x; @@ -3536,9 +3810,14 @@ sendonly=1; continue; } + if (!strncasecmp(a, "crypto:", 7)) { + process_crypto(p, a); + continue; + } if (!strcasecmp(a, "sendrecv")) { sendonly=0; } + if (sscanf(a, "rtpmap: %u %[^/]/", &codec, mimeSubtype) != 2) continue; if (debug) ast_verbose("Found description format %s\n", mimeSubtype); @@ -3548,6 +3827,11 @@ ast_rtp_set_rtpmap_type(p->vrtp, codec, "video", mimeSubtype); } + if (secure_audio && !(p->srtp && p->srtp->a_crypto)) { + ast_log(LOG_WARNING, "Can't provide secure audio requested in SDP offer\n"); + return -2; + } + /* Now gather all of the codecs that were asked for: */ ast_rtp_get_current_formats(p->rtp, &peercapability, &peernoncodeccapability); @@ -4307,6 +4591,8 @@ char m_video[256]; char a_audio[1024]; char a_video[1024]; + char crypto_buf[128]; + const char *a_crypto = NULL; char *m_audio_next = m_audio; char *m_video_next = m_video; size_t m_audio_left = sizeof(m_audio); @@ -4321,6 +4607,8 @@ struct sockaddr_in dest; struct sockaddr_in vdest = { 0, }; int debug; + const char *protocol = NULL; + struct sip_srtp *srtp = p->srtp; debug = sip_debug_test_pvt(p); @@ -4366,6 +4654,24 @@ ast_verbose("Video is at %s port %d\n", ast_inet_ntoa(iabuf, sizeof(iabuf), p->ourip), ntohs(vsin.sin_port)); } + if (srtp) { + if (srtp->a_crypto) { + a_crypto = srtp->a_crypto; + } else { + const char *crypto_suite = "AES_CM_128_HMAC_SHA1_80"; + snprintf(crypto_buf, sizeof(crypto_buf), + "a=crypto:1 %s inline:%s\r\n", + crypto_suite, srtp->local_key64); + a_crypto = crypto_buf; + } + } + + if (a_crypto) { + protocol = "SAVP"; + } else { + protocol = "AVP"; + } + /* We break with the "recommendation" and send our IP, in order that our peer doesn't have to ast_gethostbyname() us */ @@ -4375,7 +4681,7 @@ snprintf(c, sizeof(c), "c=IN IP4 %s\r\n", ast_inet_ntoa(iabuf, sizeof(iabuf), dest.sin_addr)); snprintf(t, sizeof(t), "t=0 0\r\n"); - ast_build_string(&m_audio_next, &m_audio_left, "m=audio %d RTP/AVP", ntohs(dest.sin_port)); + ast_build_string(&m_audio_next, &m_audio_left, "m=audio %d RTP/%s", ntohs(dest.sin_port), protocol); ast_build_string(&m_video_next, &m_video_left, "m=video %d RTP/AVP", ntohs(vdest.sin_port)); /* Prefer the codec we were requested to use, first, no matter what */ @@ -4459,6 +4765,9 @@ if ((p->vrtp) && (!ast_test_flag(p, SIP_NOVIDEO)) && (capability & VIDEO_CODEC_MASK)) /* only if video response is appropriate */ len += strlen(m_video) + strlen(a_video); + if (a_crypto) + len += strlen(a_crypto); + add_header(resp, "Content-Type", "application/sdp"); add_header_contentLength(resp, len); add_line(resp, v); @@ -4468,6 +4777,8 @@ add_line(resp, t); add_line(resp, m_audio); add_line(resp, a_audio); + if (a_crypto) + add_line(resp, a_crypto); if ((p->vrtp) && (!ast_test_flag(p, SIP_NOVIDEO)) && (capability & VIDEO_CODEC_MASK)) { /* only if video response is appropriate */ add_line(resp, m_video); add_line(resp, a_video); Index: res/Makefile =================================================================== --- res/Makefile (revision 7211) +++ res/Makefile (arbetskopia) @@ -60,6 +60,14 @@ endif endif # WITHOUT_ZAPTEL +# SRTP +ifneq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/local/include/srtp)$(wildcard $(CROSS_COMPILE_TARGET)/usr/include/srtp),) + ifneq ($(wildcard $(CROSS_COMPILE_TARGET)/usr/local/lib/libsrtp.so)$(wildcard $(CROSS_COMPILE_TARGET)/usr/local/lib/libsrtp.a)$(wildcard $(CROSS_COMPILE_TARGET)/usr/lib/libsrtp.so)$(wildcard $(CROSS_COMPILE_TARGET)/usr/lib/libsrtp.a),) + MODS+=res_srtp.so + SRTP_LIBS=-lsrtp + endif +endif + # # Work around buggy RedHat 9.0 # @@ -109,6 +117,9 @@ res_config_odbc.so: res_config_odbc.o $(CC) $(SOLINK) -o $@ ${CYGSOLINK} $< ${CYGSOLIB} ${CYG_RES_CONFIG_ODBC_LIB} +res_srtp.so: res_srtp.o + $(CC) $(SOLINK) -o $@ ${CYGSOLINK} $< ${CYGSOLIB} $(SRTP_LIBS) + ifneq ($(wildcard .depend),) include .depend endif --- /dev/null 1970-01-01 01:00:00.000000000 +0100 +++ res/res_srtp.c 2005-11-29 18:44:00.000000000 +0100 @@ -0,0 +1,548 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2005, Mikael Magnusson + * + * Mikael Magnusson + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! \file res_srtp.c + * + * \brief Secure RTP (SRTP) + * + * Secure RTP (SRTP) + * Specified in RFC 3711. + */ + +#include +#include "asterisk.h" + +#include "asterisk/lock.h" +#include "asterisk/module.h" +#include "asterisk/options.h" +#include "asterisk/rtp.h" + +struct ast_srtp { + struct ast_rtp *rtp; + srtp_t session; + const struct ast_srtp_cb *cb; + void *data; + unsigned char buf[8192 + AST_FRIENDLY_OFFSET]; +}; + +struct ast_srtp_policy { + srtp_policy_t sp; +}; + +static const char desc[] = "Secure RTP (SRTP)"; +static int srtpdebug = 1; +static int g_initialized = 0; + +/* Exported functions */ +int load_module(void); +int unload_module(void); +int usecount(void); +char *description(void); + +/* SRTP functions */ +static int res_srtp_create(struct ast_srtp **srtp, + struct ast_rtp *rtp, + struct ast_srtp_policy *policy); +static void res_srtp_destroy(struct ast_srtp *srtp); +static int res_srtp_add_stream(struct ast_srtp *srtp, + struct ast_srtp_policy *policy); + +static int res_srtp_unprotect(struct ast_srtp *srtp, void *buf, int *len); +static int res_srtp_protect(struct ast_srtp *srtp, void **buf, int *len); +static int res_srtp_get_random(unsigned char *key, size_t len); +static void res_srtp_set_cb(struct ast_srtp *srtp, + const struct ast_srtp_cb *cb, void *data); + +/* Policy functions */ +static struct ast_srtp_policy *res_srtp_policy_alloc(void); +static void res_srtp_policy_destroy(struct ast_srtp_policy *policy); +static int res_srtp_policy_set_suite(struct ast_srtp_policy *policy, + enum ast_srtp_suite suite); +static int res_srtp_policy_set_master_key(struct ast_srtp_policy *policy, + const unsigned char *key, size_t key_len, + const unsigned char *salt, size_t salt_len); +static int res_srtp_policy_set_encr_alg(struct ast_srtp_policy *policy, + enum ast_srtp_ealg ealg); +static int res_srtp_policy_set_auth_alg(struct ast_srtp_policy *policy, + enum ast_srtp_aalg aalg); +static void res_srtp_policy_set_encr_keylen(struct ast_srtp_policy *policy, + int ekeyl); +static void res_srtp_policy_set_auth_keylen(struct ast_srtp_policy *policy, + int akeyl); +static void res_srtp_policy_set_srtp_auth_taglen(struct ast_srtp_policy *policy, + int autht); +static void res_srtp_policy_set_srtp_encr_enable(struct ast_srtp_policy *policy, + int enable); +static void res_srtp_policy_set_srtcp_encr_enable(struct ast_srtp_policy *policy, + int enable); +static void res_srtp_policy_set_srtp_auth_enable(struct ast_srtp_policy *policy, + int enable); +static void res_srtp_policy_set_ssrc(struct ast_srtp_policy *policy, + unsigned long ssrc, int inbound); + +static struct ast_srtp_res srtp_res = { + .create = res_srtp_create, + .destroy = res_srtp_destroy, + .add_stream = res_srtp_add_stream, + .set_cb = res_srtp_set_cb, + .unprotect = res_srtp_unprotect, + .protect = res_srtp_protect, + .get_random = res_srtp_get_random +}; + +static struct ast_srtp_policy_res policy_res = { + .alloc = res_srtp_policy_alloc, + .destroy = res_srtp_policy_destroy, + .set_suite = res_srtp_policy_set_suite, + .set_master_key = res_srtp_policy_set_master_key, + .set_encr_alg = res_srtp_policy_set_encr_alg, + .set_auth_alg = res_srtp_policy_set_auth_alg, + .set_encr_keylen = res_srtp_policy_set_encr_keylen, + .set_auth_keylen = res_srtp_policy_set_auth_keylen, + .set_srtp_auth_taglen = res_srtp_policy_set_srtp_auth_taglen, + .set_srtp_encr_enable = res_srtp_policy_set_srtp_encr_enable, + .set_srtcp_encr_enable = res_srtp_policy_set_srtcp_encr_enable, + .set_srtp_auth_enable = res_srtp_policy_set_srtp_auth_enable, + .set_ssrc = res_srtp_policy_set_ssrc +}; + +static const char *srtp_errstr(int err) +{ + switch(err) { + case err_status_ok: + return "nothing to report"; + case err_status_fail: + return "unspecified failure"; + case err_status_bad_param: + return "unsupported parameter"; + case err_status_alloc_fail: + return "couldn't allocate memory"; + case err_status_dealloc_fail: + return "couldn't deallocate properly"; + case err_status_init_fail: + return "couldn't initialize"; + case err_status_terminus: + return "can't process as much data as requested"; + case err_status_auth_fail: + return "authentication failure"; + case err_status_cipher_fail: + return "cipher failure"; + case err_status_replay_fail: + return "replay check failed (bad index)"; + case err_status_replay_old: + return "replay check failed (index too old)"; + case err_status_algo_fail: + return "algorithm failed test routine"; + case err_status_no_such_op: + return "unsupported operation"; + case err_status_no_ctx: + return "no appropriate context found"; + case err_status_cant_check: + return "unable to perform desired validation"; + case err_status_key_expired: + return "can't use key any more"; + default: + return "unknown"; + } +} + +static struct ast_srtp *res_srtp_new(void) +{ + struct ast_srtp *srtp = malloc(sizeof(*srtp)); + memset(srtp, 0, sizeof(*srtp)); + return srtp; +} + +/* + struct ast_srtp_policy +*/ +static void srtp_event_cb(srtp_event_data_t *data) +{ + switch (data->event) { + case event_ssrc_collision: { + if (option_debug || srtpdebug) { + ast_log(LOG_DEBUG, "SSRC collision ssrc:%u dir:%d\n", + ntohl(data->stream->ssrc), + data->stream->direction); + } + break; + } + case event_key_soft_limit: { + if (option_debug || srtpdebug) { + ast_log(LOG_DEBUG, "event_key_soft_limit\n"); + } + break; + } + case event_key_hard_limit: { + if (option_debug || srtpdebug) { + ast_log(LOG_DEBUG, "event_key_hard_limit\n"); + } + break; + } + case event_packet_index_limit: { + if (option_debug || srtpdebug) { + ast_log(LOG_DEBUG, "event_packet_index_limit\n"); + } + break; + } + } +} + +static void res_srtp_policy_set_ssrc(struct ast_srtp_policy *policy, + unsigned long ssrc, int inbound) +{ + if (ssrc) { + policy->sp.ssrc.type = ssrc_specific; + policy->sp.ssrc.value = ssrc; + } else { + policy->sp.ssrc.type = + inbound ? ssrc_any_inbound : ssrc_any_outbound; + } +} + +static struct ast_srtp_policy *res_srtp_policy_alloc() +{ + struct ast_srtp_policy *tmp = malloc(sizeof(*tmp)); + + memset(tmp, 0, sizeof(*tmp)); + return tmp; +} + +static void +res_srtp_policy_destroy(struct ast_srtp_policy *policy) +{ + if (policy->sp.key) { + free(policy->sp.key); + policy->sp.key = NULL; + } + free(policy); +} + +static int policy_set_suite(crypto_policy_t *p, enum ast_srtp_suite suite) +{ + switch (suite) { + case AST_AES_CM_128_HMAC_SHA1_80: + p->cipher_type = AES_128_ICM; + p->cipher_key_len = 30; + p->auth_type = HMAC_SHA1; + p->auth_key_len = 20; + p->auth_tag_len = 10; + p->sec_serv = sec_serv_conf_and_auth; + return 0; + + case AST_AES_CM_128_HMAC_SHA1_32: + p->cipher_type = AES_128_ICM; + p->cipher_key_len = 30; + p->auth_type = HMAC_SHA1; + p->auth_key_len = 20; + p->auth_tag_len = 4; + p->sec_serv = sec_serv_conf_and_auth; + return 0; + + default: + ast_log(LOG_ERROR, "Invalid crypto suite: %d\n", suite); + return -1; + } +} + +static int +res_srtp_policy_set_suite(struct ast_srtp_policy *policy, + enum ast_srtp_suite suite) +{ + int res = policy_set_suite(&policy->sp.rtp, suite) | + policy_set_suite(&policy->sp.rtcp, suite); + + return res; +} + +static int +res_srtp_policy_set_master_key(struct ast_srtp_policy *policy, + const unsigned char *key, size_t key_len, + const unsigned char *salt, size_t salt_len) +{ + size_t size = key_len + salt_len; + unsigned char *master_key = NULL; + + if (policy->sp.key) { + free(policy->sp.key); + policy->sp.key = NULL; + } + + master_key = malloc(size); + + memcpy(master_key, key, key_len); + memcpy(master_key + key_len, salt, salt_len); + + policy->sp.key = master_key; + return 0; +} + +static int +res_srtp_policy_set_encr_alg(struct ast_srtp_policy *policy, + enum ast_srtp_ealg ealg) +{ + int type = -1; + + switch (ealg) { + case AST_MIKEY_SRTP_EALG_NULL: + type = NULL_CIPHER; + break; + case AST_MIKEY_SRTP_EALG_AESCM: + type = AES_128_ICM; + break; + default: + return -1; + } + + policy->sp.rtp.cipher_type = type; + policy->sp.rtcp.cipher_type = type; + return 0; +} + +static int +res_srtp_policy_set_auth_alg(struct ast_srtp_policy *policy, + enum ast_srtp_aalg aalg) +{ + int type = -1; + + switch (aalg) { + case AST_MIKEY_SRTP_AALG_NULL: + type = NULL_AUTH; + break; + case AST_MIKEY_SRTP_AALG_SHA1HMAC: + type = HMAC_SHA1; + break; + default: + return -1; + } + + policy->sp.rtp.auth_type = type; + policy->sp.rtcp.auth_type = type; + return 0; +} + +static void +res_srtp_policy_set_encr_keylen(struct ast_srtp_policy *policy, int ekeyl) +{ + policy->sp.rtp.cipher_key_len = ekeyl; + policy->sp.rtcp.cipher_key_len = ekeyl; +} + +static void +res_srtp_policy_set_auth_keylen(struct ast_srtp_policy *policy, int akeyl) +{ + policy->sp.rtp.auth_key_len = akeyl; + policy->sp.rtcp.auth_key_len = akeyl; +} + +static void +res_srtp_policy_set_srtp_auth_taglen(struct ast_srtp_policy *policy, int autht) +{ + policy->sp.rtp.auth_tag_len = autht; + policy->sp.rtcp.auth_tag_len = autht; + +} + +static void +res_srtp_policy_set_srtp_encr_enable(struct ast_srtp_policy *policy, int enable) +{ + int serv = enable ? sec_serv_conf : sec_serv_none; + policy->sp.rtp.sec_serv = + (policy->sp.rtp.sec_serv & ~sec_serv_conf) | serv; +} + +static void +res_srtp_policy_set_srtcp_encr_enable(struct ast_srtp_policy *policy, int enable) +{ + int serv = enable ? sec_serv_conf : sec_serv_none; + policy->sp.rtcp.sec_serv = + (policy->sp.rtcp.sec_serv & ~sec_serv_conf) | serv; +} + +static void +res_srtp_policy_set_srtp_auth_enable(struct ast_srtp_policy *policy, int enable) +{ + int serv = enable ? sec_serv_auth : sec_serv_none; + policy->sp.rtp.sec_serv = + (policy->sp.rtp.sec_serv & ~sec_serv_auth) | serv; +} + + +static int res_srtp_get_random(unsigned char *key, size_t len) +{ + int res = crypto_get_random(key, len); + + return res != err_status_ok ? -1: 0; +} + +static void res_srtp_set_cb(struct ast_srtp *srtp, + const struct ast_srtp_cb *cb, void *data) +{ + if (!srtp) + return; + + srtp->cb = cb; + srtp->data = data; +} + + +/* Vtable functions */ + +static int +res_srtp_unprotect(struct ast_srtp *srtp, void *buf, int *len) +{ + int res = 0; + int i; + + for (i = 0; i < 2; i++) { + srtp_hdr_t *header = buf; + + res = srtp_unprotect(srtp->session, buf, len); + if (res != err_status_no_ctx) + break; + + if (srtp->cb && srtp->cb->no_ctx) { + if (srtp->cb->no_ctx(srtp->rtp, ntohl(header->ssrc), srtp->data) < 0) { + break; + } + + } else { + break; + } + } + + if (res != err_status_ok) { + if (option_debug || srtpdebug) { + ast_log(LOG_DEBUG, "SRTP unprotect: %s\n", + srtp_errstr(res)); + } + return -1; + } + + return *len; +} + +static int +res_srtp_protect(struct ast_srtp *srtp, void **buf, int *len) +{ + int res = 0; + + if ((*len + SRTP_MAX_TRAILER_LEN) > sizeof(srtp->buf)) + return -1; + + memcpy(srtp->buf, *buf, *len); + + res = srtp_protect(srtp->session, srtp->buf, len); + + if (res != err_status_ok) { + if (option_debug || srtpdebug) { + ast_log(LOG_DEBUG, "SRTP protect: %s\n", + srtp_errstr(res)); + } + return -1; + } + + *buf = srtp->buf; + return *len; +} + +static int +res_srtp_create(struct ast_srtp **srtp, struct ast_rtp *rtp, + struct ast_srtp_policy *policy) +{ + int res; + struct ast_srtp *temp = res_srtp_new(); + + res = srtp_create(&temp->session, &policy->sp); + if (res != err_status_ok) { + return -1; + } + + temp->rtp = rtp; + *srtp = temp; + + return 0; +} + +static void +res_srtp_destroy(struct ast_srtp *srtp) +{ + if (srtp->session) { + srtp_dealloc(srtp->session); + } + + free(srtp); +} + +static int +res_srtp_add_stream(struct ast_srtp *srtp, struct ast_srtp_policy *policy) +{ + int res; + + res = srtp_add_stream(srtp->session, &policy->sp); + if (res != err_status_ok) + return -1; + + return 0; +} + +static int res_srtp_init(void) +{ + int res; + + if (g_initialized) + return 0; + + res = srtp_init(); + if (res != err_status_ok) + return -1; + + srtp_install_event_handler(srtp_event_cb); + + return ast_rtp_register_srtp(&srtp_res, &policy_res); +} + + +/* + * Exported functions + */ + +int load_module(void) +{ + return res_srtp_init(); +} + +int unload_module(void) +{ + return ast_rtp_unregister_srtp(&srtp_res, &policy_res); +} + +int usecount(void) +{ + return 1; +} + +char *description(void) +{ + return (char *)desc; +} + +char *key() +{ + return ASTERISK_GPL_KEY; +}