/* $Id$ */ /* * Copyright (C) 2017 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) /* Include OpenSSL libraries for MSVC */ # ifdef _MSC_VER # if (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL) # if OPENSSL_VERSION_NUMBER >= 0x10100000L # pragma comment(lib, "libcrypto") # else # pragma comment(lib, "libeay32") # pragma comment(lib, "ssleay32") # endif # endif # endif #endif #include static pj_status_t sdes_media_create(pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned options, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t sdes_encode_sdp (pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t sdes_media_start (pjmedia_transport *tp, pj_pool_t *pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index); static pj_status_t sdes_media_stop (pjmedia_transport *tp); static pjmedia_transport_op sdes_op = { NULL, NULL, NULL, NULL, NULL, NULL, &sdes_media_create, &sdes_encode_sdp, &sdes_media_start, &sdes_media_stop, NULL, NULL }; static pj_status_t sdes_create(transport_srtp *srtp, pjmedia_transport **p_keying) { pjmedia_transport *sdes; sdes = PJ_POOL_ZALLOC_T(srtp->pool, pjmedia_transport); pj_ansi_strncpy(sdes->name, srtp->pool->obj_name, PJ_MAX_OBJ_NAME); pj_memcpy(sdes->name, "sdes", 4); sdes->type = (pjmedia_transport_type)PJMEDIA_SRTP_KEYING_SDES; sdes->op = &sdes_op; sdes->user_data = srtp; *p_keying = sdes; PJ_LOG(5,(srtp->pool->obj_name, "SRTP keying SDES created")); return PJ_SUCCESS; } /* Generate crypto attribute, including crypto key. * If crypto-suite chosen is crypto NULL, just return PJ_SUCCESS, * and set buffer_len = 0. */ static pj_status_t generate_crypto_attr_value(pj_pool_t *pool, char *buffer, int *buffer_len, pjmedia_srtp_crypto *crypto, int tag) { pj_status_t status; int cs_idx = get_crypto_idx(&crypto->name); char b64_key[PJ_BASE256_TO_BASE64_LEN(MAX_KEY_LEN)+1]; int b64_key_len = sizeof(b64_key); int print_len; if (cs_idx == -1) return PJMEDIA_SRTP_ENOTSUPCRYPTO; /* Crypto-suite NULL. */ if (cs_idx == 0) { *buffer_len = 0; return PJ_SUCCESS; } /* Generate key if not specified. */ if (crypto->key.slen == 0) { pj_bool_t key_ok; char key[MAX_KEY_LEN]; unsigned i; PJ_ASSERT_RETURN(MAX_KEY_LEN >= crypto_suites[cs_idx].cipher_key_len, PJ_ETOOSMALL); do { #if defined(PJ_HAS_SSL_SOCK) && (PJ_HAS_SSL_SOCK != 0) && \ (PJ_SSL_SOCK_IMP == PJ_SSL_SOCK_IMP_OPENSSL) int err = RAND_bytes((unsigned char*)key, crypto_suites[cs_idx].cipher_key_len); if (err != 1) { PJ_LOG(4,(THIS_FILE, "Failed generating random key " "(native err=%d)", err)); return PJMEDIA_ERRNO_FROM_LIBSRTP(1); } #else PJ_LOG(3,(THIS_FILE, "Warning: simple random generator is used " "for generating SRTP key")); for (i=0; ikey.ptr = (char*) pj_pool_zalloc(pool, crypto_suites[cs_idx].cipher_key_len); pj_memcpy(crypto->key.ptr, key, crypto_suites[cs_idx].cipher_key_len); crypto->key.slen = crypto_suites[cs_idx].cipher_key_len; } if (crypto->key.slen != (pj_ssize_t)crypto_suites[cs_idx].cipher_key_len) return PJMEDIA_SRTP_EINKEYLEN; /* Key transmitted via SDP should be base64 encoded. */ status = pj_base64_encode((pj_uint8_t*)crypto->key.ptr, (int)crypto->key.slen, b64_key, &b64_key_len); if (status != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, status, "Failed encoding plain key to base64")); return status; } b64_key[b64_key_len] = '\0'; PJ_ASSERT_RETURN(*buffer_len >= (crypto->name.slen + \ b64_key_len + 16), PJ_ETOOSMALL); /* Print the crypto attribute value. */ print_len = pj_ansi_snprintf(buffer, *buffer_len, "%d %s inline:%s", tag, crypto_suites[cs_idx].name, b64_key); if (print_len < 1 || print_len >= *buffer_len) return PJ_ETOOSMALL; *buffer_len = print_len; return PJ_SUCCESS; } /* Parse crypto attribute line */ static pj_status_t parse_attr_crypto(pj_pool_t *pool, const pjmedia_sdp_attr *attr, pjmedia_srtp_crypto *crypto, int *tag) { pj_str_t token, delim; pj_status_t status; int itmp; pj_ssize_t found_idx; pj_bzero(crypto, sizeof(*crypto)); /* Tag */ delim = pj_str(" "); found_idx = pj_strtok(&attr->value, &delim, &token, 0); if (found_idx == attr->value.slen) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting tag")); return PJMEDIA_SDP_EINATTR; } /* Tag must not use leading zeroes. */ if (token.slen > 1 && *token.ptr == '0') return PJMEDIA_SDP_EINATTR; /* Tag must be decimal, i.e: contains only digit '0'-'9'. */ for (itmp = 0; itmp < token.slen; ++itmp) if (!pj_isdigit(token.ptr[itmp])) return PJMEDIA_SDP_EINATTR; /* Get tag value. */ *tag = pj_strtoul(&token); /* Crypto-suite */ found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen); if (found_idx == attr->value.slen) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting crypto suite")); return PJMEDIA_SDP_EINATTR; } pj_strdup(pool, &crypto->name, &token); /* Key method */ delim = pj_str(": "); found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen); if (found_idx == attr->value.slen) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key method")); return PJMEDIA_SDP_EINATTR; } if (pj_stricmp2(&token, "inline")) { PJ_LOG(4,(THIS_FILE, "Attribute crypto key method '%.*s' " "not supported!", token.slen, token.ptr)); return PJMEDIA_SDP_EINATTR; } /* Key */ delim = pj_str("| "); found_idx = pj_strtok(&attr->value, &delim, &token, found_idx+token.slen); if (found_idx == attr->value.slen) { PJ_LOG(4,(THIS_FILE, "Attribute crypto expecting key")); return PJMEDIA_SDP_EINATTR; } if (PJ_BASE64_TO_BASE256_LEN(token.slen) > MAX_KEY_LEN) { PJ_LOG(4,(THIS_FILE, "Key too long")); return PJMEDIA_SRTP_EINKEYLEN; } /* Decode key */ crypto->key.ptr = (char*) pj_pool_zalloc(pool, MAX_KEY_LEN); itmp = MAX_KEY_LEN; status = pj_base64_decode(&token, (pj_uint8_t*)crypto->key.ptr, &itmp); if (status != PJ_SUCCESS) { PJ_PERROR(4,(THIS_FILE, status, "Failed decoding crypto key from base64")); return status; } crypto->key.slen = itmp; return PJ_SUCCESS; } static pj_status_t sdes_media_create( pjmedia_transport *tp, pj_pool_t *sdp_pool, unsigned options, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data; pj_uint32_t rem_proto = 0; PJ_UNUSED_ARG(options); PJ_UNUSED_ARG(sdp_pool); /* Verify remote media transport, it has to be RTP/AVP or RTP/SAVP */ if (!srtp->offerer_side) { pjmedia_sdp_media *m = sdp_remote->media[media_index]; /* Get transport protocol and drop any RTCP-FB flag */ rem_proto = pjmedia_sdp_transport_get_proto(&m->desc.transport); PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB); if (rem_proto != PJMEDIA_TP_PROTO_RTP_AVP && rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP) { return PJMEDIA_SRTP_ESDPINTRANSPORT; } } /* Validations */ if (srtp->offerer_side) { /* As offerer: do nothing. */ } else { /* Validate remote media transport based on SRTP usage option. */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP) return PJMEDIA_SRTP_ESDPINTRANSPORT; break; case PJMEDIA_SRTP_OPTIONAL: break; case PJMEDIA_SRTP_MANDATORY: if (rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP) return PJMEDIA_SRTP_ESDPINTRANSPORT; break; } } return PJ_SUCCESS; } static pj_status_t sdes_encode_sdp( pjmedia_transport *tp, pj_pool_t *sdp_pool, pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data; pjmedia_sdp_media *m_rem, *m_loc; enum { MAXLEN = 512 }; char buffer[MAXLEN]; int buffer_len; pj_status_t status; pjmedia_sdp_attr *attr; pj_str_t attr_value; unsigned i, j; m_rem = sdp_remote ? sdp_remote->media[media_index] : NULL; m_loc = sdp_local->media[media_index]; /* Verify media transport, it has to be RTP/AVP or RTP/SAVP */ { pjmedia_sdp_media *m = sdp_remote? m_rem : m_loc; pj_uint32_t proto = 0; /* Get transport protocol and drop any RTCP-FB flag */ proto = pjmedia_sdp_transport_get_proto(&m->desc.transport); PJMEDIA_TP_PROTO_TRIM_FLAG(proto, PJMEDIA_TP_PROFILE_RTCP_FB); if (proto != PJMEDIA_TP_PROTO_RTP_AVP && proto != PJMEDIA_TP_PROTO_RTP_SAVP) { return PJMEDIA_SRTP_ESDPINTRANSPORT; } } /* If the media is inactive, do nothing. */ /* No, we still need to process SRTP offer/answer even if the media is * marked as inactive, because the transport is still alive in this * case (e.g. for keep-alive). See: * http://trac.pjsip.org/repos/ticket/1079 */ /* if (pjmedia_sdp_media_find_attr(m_loc, &ID_INACTIVE, NULL) || (m_rem && pjmedia_sdp_media_find_attr(m_rem, &ID_INACTIVE, NULL))) goto BYPASS_SRTP; */ /* Check remote media transport & set local media transport * based on SRTP usage option. */ if (srtp->offerer_side) { /* Generate transport */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: /* Should never reach here */ return PJ_SUCCESS; case PJMEDIA_SRTP_OPTIONAL: m_loc->desc.transport = (srtp->peer_use == PJMEDIA_SRTP_MANDATORY)? ID_RTP_SAVP : ID_RTP_AVP; break; case PJMEDIA_SRTP_MANDATORY: m_loc->desc.transport = ID_RTP_SAVP; break; } /* Generate crypto attribute if not yet */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { int tag = 1; /* Offer only current active crypto if any, otherwise offer all * crypto-suites in the setting. */ for (i=0; isetting.crypto_count; ++i) { if (srtp->tx_policy.name.slen && pj_stricmp(&srtp->tx_policy.name, &srtp->setting.crypto[i].name) != 0) { continue; } buffer_len = MAXLEN; status = generate_crypto_attr_value(srtp->pool, buffer, &buffer_len, &srtp->setting.crypto[i], tag); if (status != PJ_SUCCESS) return status; /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); attr = pjmedia_sdp_attr_create(srtp->pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; ++tag; } } } } else { /* Answerer side */ pj_uint32_t rem_proto = 0; pj_assert(sdp_remote && m_rem); /* Get transport protocol and drop any RTCP-FB flag */ rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport); PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB); /* Generate transport */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: /* Should never reach here */ if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP) return PJMEDIA_SRTP_ESDPINTRANSPORT; return PJ_SUCCESS; case PJMEDIA_SRTP_OPTIONAL: break; case PJMEDIA_SRTP_MANDATORY: if (rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP) return PJMEDIA_SRTP_ESDPINTRANSPORT; break; } /* Generate crypto attribute if not yet */ if (pjmedia_sdp_media_find_attr(m_loc, &ID_CRYPTO, NULL) == NULL) { pjmedia_srtp_crypto tmp_rx_crypto; pj_bool_t has_crypto_attr = PJ_FALSE; int matched_idx = -1; int chosen_tag = 0; int tags[64]; /* assume no more than 64 crypto attrs in a media */ unsigned cr_attr_count = 0; /* Find supported crypto-suite, get the tag, and assign * policy_local. */ for (i=0; iattr_count; ++i) { if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) continue; has_crypto_attr = PJ_TRUE; status = parse_attr_crypto(srtp->pool, m_rem->attr[i], &tmp_rx_crypto, &tags[cr_attr_count]); if (status != PJ_SUCCESS) return status; /* Check duplicated tag */ for (j=0; jsetting.crypto_count; ++j) if (pj_stricmp(&tmp_rx_crypto.name, &srtp->setting.crypto[j].name) == 0) { int cs_idx = get_crypto_idx(&tmp_rx_crypto.name); if (cs_idx == -1) return PJMEDIA_SRTP_ENOTSUPCRYPTO; if (tmp_rx_crypto.key.slen != (int)crypto_suites[cs_idx].cipher_key_len) return PJMEDIA_SRTP_EINKEYLEN; srtp->rx_policy_neg = tmp_rx_crypto; chosen_tag = tags[cr_attr_count]; matched_idx = j; break; } } cr_attr_count++; } /* Check crypto negotiation result */ switch (srtp->setting.use) { case PJMEDIA_SRTP_DISABLED: /* Should never reach here */ break; case PJMEDIA_SRTP_OPTIONAL: /* Bypass SDES if remote uses RTP/AVP and: * - has no crypto-attr, or * - has no matching crypto */ if ((!has_crypto_attr || matched_idx == -1) && !PJMEDIA_TP_PROTO_HAS_FLAG(rem_proto, PJMEDIA_TP_PROFILE_SRTP)) { return PJ_SUCCESS; } break; case PJMEDIA_SRTP_MANDATORY: /* Do nothing, intentional */ break; } /* No crypto attr */ if (!has_crypto_attr) { //DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ESDPREQCRYPTO; } /* No crypto match */ if (matched_idx == -1) { //DEACTIVATE_MEDIA(sdp_pool, m_loc); return PJMEDIA_SRTP_ENOTSUPCRYPTO; } /* we have to generate crypto answer, * with srtp->tx_policy_neg matched the offer * and rem_tag contains matched offer tag. */ buffer_len = MAXLEN; status = generate_crypto_attr_value( srtp->pool, buffer, &buffer_len, &srtp->setting.crypto[matched_idx], chosen_tag); if (status != PJ_SUCCESS) return status; srtp->tx_policy_neg = srtp->setting.crypto[matched_idx]; /* If buffer_len==0, just skip the crypto attribute. */ if (buffer_len) { pj_strset(&attr_value, buffer, buffer_len); attr = pjmedia_sdp_attr_create(sdp_pool, ID_CRYPTO.ptr, &attr_value); m_loc->attr[m_loc->attr_count++] = attr; } /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ } /* Update transport description in local media SDP */ m_loc->desc.transport = m_rem->desc.transport; } return PJ_SUCCESS; } static pj_status_t fill_local_crypto(pj_pool_t *pool, const pjmedia_sdp_media *m_loc, pjmedia_srtp_crypto loc_crypto[], int *count) { int i; int crypto_count = 0; pj_status_t status = PJ_SUCCESS; for (i = 0; i < *count; ++i) { pj_bzero(&loc_crypto[i], sizeof(loc_crypto[i])); } for (i = 0; i < (int)m_loc->attr_count; ++i) { pjmedia_srtp_crypto tmp_crypto; int loc_tag; if (pj_stricmp(&m_loc->attr[i]->name, &ID_CRYPTO) != 0) continue; status = parse_attr_crypto(pool, m_loc->attr[i], &tmp_crypto, &loc_tag); if (status != PJ_SUCCESS) return status; if (loc_tag > *count) return PJMEDIA_SRTP_ESDPINCRYPTOTAG; loc_crypto[loc_tag-1] = tmp_crypto; ++crypto_count; } *count = crypto_count; return status; } static pj_status_t sdes_media_start( pjmedia_transport *tp, pj_pool_t *pool, const pjmedia_sdp_session *sdp_local, const pjmedia_sdp_session *sdp_remote, unsigned media_index) { struct transport_srtp *srtp = (struct transport_srtp*)tp->user_data; pjmedia_sdp_media *m_rem, *m_loc; pj_status_t status; unsigned i; pjmedia_srtp_crypto loc_crypto[PJMEDIA_SRTP_MAX_CRYPTOS]; int loc_cryto_cnt = PJMEDIA_SRTP_MAX_CRYPTOS; pjmedia_srtp_crypto tmp_tx_crypto; pj_bool_t has_crypto_attr = PJ_FALSE; int rem_tag; int j; m_rem = sdp_remote->media[media_index]; m_loc = sdp_local->media[media_index]; /* Verify media transport, it has to be RTP/AVP or RTP/SAVP */ { pj_uint32_t rem_proto; /* Get transport protocol and drop any RTCP-FB flag */ rem_proto = pjmedia_sdp_transport_get_proto(&m_rem->desc.transport); PJMEDIA_TP_PROTO_TRIM_FLAG(rem_proto, PJMEDIA_TP_PROFILE_RTCP_FB); if (rem_proto != PJMEDIA_TP_PROTO_RTP_AVP && rem_proto != PJMEDIA_TP_PROTO_RTP_SAVP) { return PJMEDIA_SRTP_ESDPINTRANSPORT; } /* Also check if peer signal SRTP as mandatory */ if (rem_proto == PJMEDIA_TP_PROTO_RTP_SAVP) srtp->peer_use = PJMEDIA_SRTP_MANDATORY; else srtp->peer_use = PJMEDIA_SRTP_OPTIONAL; } /* For answerer side, this function will just have to start SRTP as * SRTP crypto policies have been populated in media_encode_sdp(). */ if (!srtp->offerer_side) return PJ_SUCCESS; /* Check remote media transport & set local media transport * based on SRTP usage option. */ if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { if (pjmedia_sdp_media_find_attr(m_rem, &ID_CRYPTO, NULL)) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPINCRYPTO; } return PJ_SUCCESS; } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { // Regardless the answer's transport type (RTP/AVP or RTP/SAVP), // the answer must be processed through in optional mode. // Please note that at this point transport type is ensured to be // RTP/AVP or RTP/SAVP, see sdes_media_create() //if (pj_stricmp(&m_rem->desc.transport, &m_loc->desc.transport)) { //DEACTIVATE_MEDIA(pool, m_loc); //return PJMEDIA_SDP_EINPROTO; //} fill_local_crypto(srtp->pool, m_loc, loc_crypto, &loc_cryto_cnt); } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { if (srtp->peer_use != PJMEDIA_SRTP_MANDATORY) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SDP_EINPROTO; } fill_local_crypto(srtp->pool, m_loc, loc_crypto, &loc_cryto_cnt); } /* find supported crypto-suite, get the tag, and assign policy_local */ for (i=0; iattr_count; ++i) { if (pj_stricmp(&m_rem->attr[i]->name, &ID_CRYPTO) != 0) continue; /* more than one crypto attribute in media answer */ if (has_crypto_attr) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPAMBIGUEANS; } has_crypto_attr = PJ_TRUE; status = parse_attr_crypto(srtp->pool, m_rem->attr[i], &tmp_tx_crypto, &rem_tag); if (status != PJ_SUCCESS) return status; /* Tag range check, our tags in the offer must be in the SRTP * setting range, so does the remote answer's. The remote answer's * tag must not exceed the tag range of the local offer. */ if (rem_tag < 1 || rem_tag > (int)srtp->setting.crypto_count || rem_tag > loc_cryto_cnt) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPINCRYPTOTAG; } /* match the crypto name */ if (pj_stricmp(&tmp_tx_crypto.name, &loc_crypto[rem_tag-1].name)) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ECRYPTONOTMATCH; } /* Find the crypto from the setting. */ for (j = 0; j < (int)srtp->setting.crypto_count; ++j) { if (pj_stricmp(&tmp_tx_crypto.name, &srtp->setting.crypto[j].name) == 0) { srtp->tx_policy_neg = srtp->setting.crypto[j]; break; } } srtp->rx_policy_neg = tmp_tx_crypto; } if (srtp->setting.use == PJMEDIA_SRTP_DISABLED) { /* should never reach here */ return PJ_SUCCESS; } else if (srtp->setting.use == PJMEDIA_SRTP_OPTIONAL) { if (!has_crypto_attr) return PJ_SUCCESS; } else if (srtp->setting.use == PJMEDIA_SRTP_MANDATORY) { if (!has_crypto_attr) { DEACTIVATE_MEDIA(pool, m_loc); return PJMEDIA_SRTP_ESDPREQCRYPTO; } } /* At this point, we get valid rx_policy_neg & tx_policy_neg. */ return PJ_SUCCESS; } static pj_status_t sdes_media_stop(pjmedia_transport *tp) { PJ_UNUSED_ARG(tp); return PJ_SUCCESS; }