diff options
Diffstat (limited to 'modules/srtp/srtp.c')
-rw-r--r-- | modules/srtp/srtp.c | 472 |
1 files changed, 472 insertions, 0 deletions
diff --git a/modules/srtp/srtp.c b/modules/srtp/srtp.c new file mode 100644 index 0000000..bb86e96 --- /dev/null +++ b/modules/srtp/srtp.c @@ -0,0 +1,472 @@ +/** + * @file modules/srtp/srtp.c Secure Real-time Transport Protocol (RFC 3711) + * + * Copyright (C) 2010 Creytiv.com + */ +#if defined (__GNUC__) && !defined (asm) +#define asm __asm__ /* workaround */ +#endif +#include <srtp/srtp.h> +#include <re.h> +#include <baresip.h> +#include "sdes.h" + + +#define DEBUG_MODULE "srtp" +#define DEBUG_LEVEL 5 +#include <re_dbg.h> + + +struct menc_st { + /* one SRTP session per media line */ + uint8_t key_tx[32]; /* 32 for alignment, only 30 used */ + uint8_t key_rx[32]; + srtp_t srtp_tx, srtp_rx; + srtp_policy_t policy_tx, policy_rx; + bool use_srtp; + char *crypto_suite; + + void *rtpsock; + void *rtcpsock; + struct udp_helper *uh_rtp; /**< UDP helper for RTP encryption */ + struct udp_helper *uh_rtcp; /**< UDP helper for RTCP encryption */ + struct sdp_media *sdpm; +}; + + +static const char aes_cm_128_hmac_sha1_32[] = "AES_CM_128_HMAC_SHA1_32"; +static const char aes_cm_128_hmac_sha1_80[] = "AES_CM_128_HMAC_SHA1_80"; + + +static void destructor(void *arg) +{ + struct menc_st *st = arg; + + mem_deref(st->sdpm); + mem_deref(st->crypto_suite); + + /* note: must be done before freeing socket */ + mem_deref(st->uh_rtp); + mem_deref(st->uh_rtcp); + mem_deref(st->rtpsock); + mem_deref(st->rtcpsock); + + if (st->srtp_tx) + srtp_dealloc(st->srtp_tx); + if (st->srtp_rx) + srtp_dealloc(st->srtp_rx); +} + + +static bool cryptosuite_issupported(const struct pl *suite) +{ + if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_32)) return true; + if (0 == pl_strcasecmp(suite, aes_cm_128_hmac_sha1_80)) return true; + + return false; +} + + +static int errstatus_print(struct re_printf *pf, err_status_t e) +{ + const char *s; + + switch (e) { + + case err_status_ok: s = "ok"; break; + case err_status_fail: s = "fail"; break; + case err_status_auth_fail: s = "auth_fail"; break; + case err_status_cipher_fail: s = "cipher_fail"; break; + case err_status_replay_fail: s = "replay_fail"; break; + + default: + return re_hprintf(pf, "err=%d", e); + } + + return re_hprintf(pf, "%s", s); +} + + +/* + * See RFC 5764 figure 3: + * + * +----------------+ + * | 127 < B < 192 -+--> forward to RTP + * | | + * packet --> | 19 < B < 64 -+--> forward to DTLS + * | | + * | B < 2 -+--> forward to STUN + * +----------------+ + * + */ +static bool is_rtp_or_rtcp(const struct mbuf *mb) +{ + uint8_t b; + + if (mbuf_get_left(mb) < 1) + return false; + + b = mbuf_buf(mb)[0]; + + return 127 < b && b < 192; +} + + +static bool is_rtcp_packet(const struct mbuf *mb) +{ + uint8_t pt; + + if (mbuf_get_left(mb) < 2) + return false; + + pt = mbuf_buf(mb)[1] & 0x7f; + + return 64 <= pt && pt <= 95; +} + + +static int start_srtp(struct menc_st *st, const char *suite) +{ + crypto_policy_t policy; + err_status_t e; + + if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_32)) { + crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy); + } + else if (0 == str_casecmp(suite, aes_cm_128_hmac_sha1_80)) { + crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy); + } + else { + DEBUG_WARNING("unknown SRTP crypto suite (%s)\n", suite); + return ENOENT; + } + + /* transmit policy */ + st->policy_tx.rtp = policy; + st->policy_tx.rtcp = policy; + st->policy_tx.ssrc.type = ssrc_any_outbound; + st->policy_tx.key = st->key_tx; + st->policy_tx.next = NULL; + + /* receive policy */ + st->policy_rx.rtp = policy; + st->policy_rx.rtcp = policy; + st->policy_rx.ssrc.type = ssrc_any_inbound; + st->policy_rx.key = st->key_rx; + st->policy_rx.next = NULL; + + /* allocate and initialize the SRTP session */ + e = srtp_create(&st->srtp_tx, &st->policy_tx); + if (e != err_status_ok) { + DEBUG_WARNING("srtp_create TX failed (%H)\n", + errstatus_print, e); + return EPROTO; + } + + e = srtp_create(&st->srtp_rx, &st->policy_rx); + if (err_status_ok != e) { + DEBUG_WARNING("srtp_create RX failed (%H)\n", + errstatus_print, e); + return EPROTO; + } + + /* use SRTP for this stream/session */ + st->use_srtp = true; + + return 0; +} + + +static int setup_srtp(struct menc_st *st) +{ + err_status_t e; + + /* init SRTP */ + e = crypto_get_random(st->key_tx, SRTP_MASTER_KEY_LEN); + if (err_status_ok != e) { + DEBUG_WARNING("crypto_get_random() failed (%H)\n", + errstatus_print, e); + return ENOSYS; + } + + return 0; +} + + +static bool send_handler(int *err, struct sa *dst, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + err_status_t e; + int len; + (void)dst; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (mbuf_get_space(mb) < ((size_t)len + SRTP_MAX_TRAILER_LEN)) { + mbuf_resize(mb, mb->pos + len + SRTP_MAX_TRAILER_LEN); + } + + if (is_rtcp_packet(mb)) { + e = srtp_protect_rtcp(st->srtp_tx, mbuf_buf(mb), &len); + } + else { + e = srtp_protect(st->srtp_tx, mbuf_buf(mb), &len); + } + + if (err_status_ok != e) { + DEBUG_WARNING("send: failed to protect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + *err = EPROTO; + return false; + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +static bool recv_handler(struct sa *src, struct mbuf *mb, void *arg) +{ + struct menc_st *st = arg; + err_status_t e; + int len; + (void)src; + + if (!st->use_srtp || !is_rtp_or_rtcp(mb)) + return false; + + len = (int)mbuf_get_left(mb); + + if (is_rtcp_packet(mb)) { + e = srtp_unprotect_rtcp(st->srtp_rx, mbuf_buf(mb), &len); + } + else { + e = srtp_unprotect(st->srtp_rx, mbuf_buf(mb), &len); + } + + if (e != err_status_ok) { + DEBUG_WARNING("recv: failed to unprotect %s-packet" + " with %d bytes (%H)\n", + is_rtcp_packet(mb) ? "RTCP" : "RTP", + len, errstatus_print, e); + return true; /* error - drop packet */ + } + + mbuf_set_end(mb, mb->pos + len); + + return false; /* continue processing */ +} + + +/* a=crypto:<tag> <crypto-suite> <key-params> [<session-params>] */ +static int sdp_enc(struct menc_st *st, struct sdp_media *m, + uint32_t tag, const char *suite) +{ + char key[128] = ""; + size_t olen; + int err; + + olen = sizeof(key); + err = base64_encode(st->key_tx, SRTP_MASTER_KEY_LEN, key, &olen); + if (err) + return err; + + return sdes_encode_crypto(m, tag, suite, key, olen); +} + + +static int start_crypto(struct menc_st *st, const struct pl *key_info) +{ + size_t olen; + int err; + + /* key-info is BASE64 encoded */ + + olen = sizeof(st->key_rx); + err = base64_decode(key_info->p, key_info->l, st->key_rx, &olen); + if (err) + return err; + + if (SRTP_MASTER_KEY_LEN != olen) { + DEBUG_WARNING("srtp keylen is %u (should be 30)\n", olen); + } + + err = start_srtp(st, st->crypto_suite); + if (err) + return err; + + info("srtp: %s: SRTP is Enabled (cryptosuite=%s)\n", + sdp_media_name(st->sdpm), st->crypto_suite); + + return 0; +} + + +static bool sdp_attr_handler(const char *name, const char *value, void *arg) +{ + struct menc_st *st = arg; + struct crypto c; + (void)name; + + if (sdes_decode_crypto(&c, value)) + return false; + + if (0 != pl_strcmp(&c.key_method, "inline")) + return false; + + if (!cryptosuite_issupported(&c.suite)) + return false; + + st->crypto_suite = mem_deref(st->crypto_suite); + pl_strdup(&st->crypto_suite, &c.suite); + + if (start_crypto(st, &c.key_info)) + return false; + + sdp_enc(st, st->sdpm, c.tag, st->crypto_suite); + + return true; +} + + +static int alloc(struct menc_media **stp, struct menc_sess *sess, + struct rtp_sock *rtp, + int proto, void *rtpsock, void *rtcpsock, + struct sdp_media *sdpm) +{ + struct menc_st *st; + const char *rattr = NULL; + int layer = 10; /* above zero */ + int err = 0; + bool mux = (rtpsock == rtcpsock); + (void)sess; + (void)rtp; + + if (!stp || !sdpm) + return EINVAL; + if (proto != IPPROTO_UDP) + return EPROTONOSUPPORT; + + st = (struct menc_st *)*stp; + if (!st) { + + st = mem_zalloc(sizeof(*st), destructor); + if (!st) + return ENOMEM; + + st->sdpm = mem_ref(sdpm); + + err = sdp_media_set_alt_protos(st->sdpm, 4, + "RTP/AVP", + "RTP/AVPF", + "RTP/SAVP", + "RTP/SAVPF"); + if (err) + goto out; + + if (rtpsock) { + st->rtpsock = mem_ref(rtpsock); + err |= udp_register_helper(&st->uh_rtp, rtpsock, + layer, send_handler, + recv_handler, st); + } + if (rtcpsock && !mux) { + st->rtcpsock = mem_ref(rtcpsock); + err |= udp_register_helper(&st->uh_rtcp, rtcpsock, + layer, send_handler, + recv_handler, st); + } + if (err) + goto out; + + /* set our preferred crypto-suite */ + err |= str_dup(&st->crypto_suite, aes_cm_128_hmac_sha1_80); + if (err) + goto out; + + err = setup_srtp(st); + if (err) + goto out; + } + + /* SDP handling */ + + if (sdp_media_rattr(st->sdpm, "crypto")) { + + rattr = sdp_media_rattr_apply(st->sdpm, "crypto", + sdp_attr_handler, st); + if (!rattr) { + DEBUG_WARNING("no valid a=crypto attribute from" + " remote peer\n"); + } + } + + if (!rattr) + err = sdp_enc(st, sdpm, 0, st->crypto_suite); + + out: + if (err) + mem_deref(st); + else + *stp = (struct menc_media *)st; + + return err; +} + + +static struct menc menc_srtp_opt = { + LE_INIT, "srtp", "RTP/AVP", NULL, alloc +}; + +static struct menc menc_srtp_mand = { + LE_INIT, "srtp-mand", "RTP/SAVP", NULL, alloc +}; + +static struct menc menc_srtp_mandf = { + LE_INIT, "srtp-mandf", "RTP/SAVPF", NULL, alloc +}; + + +static int mod_srtp_init(void) +{ + err_status_t err; + + err = srtp_init(); + if (err_status_ok != err) { + DEBUG_WARNING("srtp_init() failed (%H)\n", + errstatus_print, err); + return ENOSYS; + } + + menc_register(&menc_srtp_opt); + menc_register(&menc_srtp_mand); + menc_register(&menc_srtp_mandf); + + return 0; +} + + +static int mod_srtp_close(void) +{ + menc_unregister(&menc_srtp_mandf); + menc_unregister(&menc_srtp_mand); + menc_unregister(&menc_srtp_opt); + + crypto_kernel_shutdown(); + + return 0; +} + + +EXPORT_SYM const struct mod_export DECL_EXPORTS(srtp) = { + "srtp", + "menc", + mod_srtp_init, + mod_srtp_close +}; |