X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libs%2Fsrtp%2Fsrtp.c;h=ec303da83ab5470d1f1981fce45b66e36fb8dcc0;hb=3f3025aa794fbdad01150af9401bcb708263488f;hp=bb6fd5ebf48d4564eb817d3d1346b54d6816e894;hpb=22c5fe4ae5640b1f2e7358dccbc720faa84616a2;p=vlc diff --git a/libs/srtp/srtp.c b/libs/srtp/srtp.c index bb6fd5ebf4..ec303da83a 100644 --- a/libs/srtp/srtp.c +++ b/libs/srtp/srtp.c @@ -17,6 +17,12 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +/* TODO: + * Useless stuff (because nothing depends on it): + * - non-nul key derivation rate + * - MKI payload + */ + #ifdef HAVE_CONFIG_H # include #endif @@ -33,26 +39,22 @@ #include -/* TODO: - * Useful stuff: - * - ROC profil thingy (multicast really needs this) - * - replay protection - * - * Requirements for conformance: - * - suites with NULL cipher - * - SRTCP - * - * Useless stuff (because nothing depends on it): - * - non-nul key derivation rate - * - MKI payload - */ +#ifdef WIN32 +# include +#else +# include +# include +GCRY_THREAD_OPTION_PTHREAD_IMPL; +#endif + +#define debug( ... ) (void)0 typedef struct srtp_proto_t { gcry_cipher_hd_t cipher; gcry_md_hd_t mac; + uint64_t window; uint32_t salt[4]; - uint8_t mac_len; } srtp_proto_t; struct srtp_session_t @@ -64,6 +66,8 @@ struct srtp_session_t uint32_t rtcp_index; uint32_t rtp_roc; uint16_t rtp_seq; + uint16_t rtp_rcc; + uint8_t tag_len; }; enum @@ -76,25 +80,23 @@ enum SRTCP_SALT }; -#ifdef WIN32 -# include -#else -# include -# include -GCRY_THREAD_OPTION_PTHREAD_IMPL; -#endif + +static inline unsigned rcc_mode (const srtp_session_t *s) +{ + return (s->flags >> 4) & 3; +} static bool libgcrypt_usable = false; static void initonce_libgcrypt (void) { - if ((gcry_check_version ("1.1.94") == NULL) - || gcry_control (GCRYCTL_DISABLE_SECMEM, 0) - || gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0) #ifndef WIN32 - || gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread) + gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread); #endif - ) + + if ((gcry_check_version ("1.1.94") == NULL) + || gcry_control (GCRYCTL_DISABLE_SECMEM, 0) + || gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0)) return; libgcrypt_usable = true; @@ -109,13 +111,16 @@ static int init_libgcrypt (void) pthread_mutex_lock (&mutex); pthread_once (&once, initonce_libgcrypt); - retval = -libgcrypt_usable; - pthread_mutex_unlock (&mutex); #else # warning FIXME: This is not thread-safe. if (!libgcrypt_usable) initonce_libgcrypt (); - retval = -libgcrypt_usable; +#endif + + retval = libgcrypt_usable ? 0 : -1; + +#ifndef WIN32 + pthread_mutex_unlock (&mutex); #endif return retval; @@ -156,39 +161,57 @@ static int proto_create (srtp_proto_t *p, int gcipher, int gmd) /** - * Allocates a Secure RTP session. + * Allocates a Secure RTP one-way session. + * The same session cannot be used both ways because this would confuse + * internal cryptographic counters; it is however of course feasible to open + * multiple simultaneous sessions with the same master key. * - * @param name cipher-suite name - * @param kdr key derivation rate - * @param winsize anti-replay windows size (between 64 and 32767 inclusive) - * 0 disable replay attack protection (OK for send only) + * @param encr encryption algorithm number + * @param auth authentication algortihm number + * @param tag_len authentication tag byte length (NOT including RCC) * @param flags OR'ed optional flags. * * @return NULL in case of error */ srtp_session_t * -srtp_create (const char *name, unsigned flags, unsigned kdr, uint16_t winsize) +srtp_create (int encr, int auth, unsigned tag_len, int prf, unsigned flags) { - assert (name != NULL); + if ((flags & ~SRTP_FLAGS_MASK) || init_libgcrypt ()) + return NULL; - if (kdr != 0) - return NULL; // FIXME: KDR not implemented yet - if (winsize != 0) - return NULL; // FIXME: replay protection not implemented yet + int cipher, md; + switch (encr) + { + case SRTP_ENCR_NULL: + cipher = GCRY_CIPHER_NONE; + break; - uint8_t mac_len; - int cipher = GCRY_CIPHER_AES, md = GCRY_MD_SHA1; + case SRTP_ENCR_AES_CM: + cipher = GCRY_CIPHER_AES; + break; - if (strcmp (name, "AES_CM_128_HMAC_SHA1_80") == 0) - mac_len = 80; - else - if (strcmp (name, "AES_CM_128_HMAC_SHA1_32") == 0) - mac_len = 32; - else - // F8_128_HMAC_SHA1_80 is not implemented + default: + return NULL; + } + + switch (auth) + { + case SRTP_AUTH_NULL: + md = GCRY_MD_NONE; + break; + + case SRTP_AUTH_HMAC_SHA1: + md = GCRY_MD_SHA1; + break; + + default: + return NULL; + } + + if (tag_len > gcry_md_get_algo_dlen (md)) return NULL; - if ((flags & ~SRTP_FLAGS_MASK) || (winsize > 32767) || init_libgcrypt ()) + if (prf != SRTP_PRF_AES_CM) return NULL; srtp_session_t *s = malloc (sizeof (*s)); @@ -197,7 +220,13 @@ srtp_create (const char *name, unsigned flags, unsigned kdr, uint16_t winsize) memset (s, 0, sizeof (*s)); s->flags = flags; - s->kdr = kdr; + s->tag_len = tag_len; + s->rtp_rcc = 1; /* Default RCC rate */ + if (rcc_mode (s)) + { + if (tag_len < 4) + goto error; + } if (proto_create (&s->rtp, cipher, md) == 0) { @@ -206,11 +235,43 @@ srtp_create (const char *name, unsigned flags, unsigned kdr, uint16_t winsize) proto_destroy (&s->rtp); } +error: free (s); return NULL; } +/** + * Counter Mode encryption/decryption (ctr length = 16 bytes) + * with non-padded (truncated) text + */ +static int +ctr_crypt (gcry_cipher_hd_t hd, const void *ctr, uint8_t *data, size_t len) +{ + const size_t ctrlen = 16; + div_t d = div (len, ctrlen); + + if (gcry_cipher_setctr (hd, ctr, ctrlen) + || gcry_cipher_encrypt (hd, data, d.quot * ctrlen, NULL, 0)) + return -1; + + if (d.rem) + { + /* Truncated last block */ + uint8_t dummy[ctrlen]; + data += d.quot * ctrlen; + memcpy (dummy, data, d.rem); + memset (dummy + d.rem, 0, ctrlen - d.rem); + + if (gcry_cipher_encrypt (hd, dummy, ctrlen, data, ctrlen)) + return -1; + memcpy (data, dummy, d.rem); + } + + return 0; +} + + /** * AES-CM key derivation (saltlen = 14 bytes) */ @@ -229,29 +290,8 @@ derive (gcry_cipher_hd_t prf, const void *salt, for (size_t i = 0; i < rlen; i++) iv[sizeof (iv) - rlen + i] ^= r[i]; - /* TODO: retry with CTR mode */ - while (outlen >= sizeof (iv)) - { - /* AES */ - if (gcry_cipher_encrypt (prf, out, sizeof (iv), iv, sizeof (iv))) - return EINVAL; - outlen -= sizeof (iv); - out = ((uint8_t *)out) + sizeof (iv); - - /* Increment IV in network byte order */ - if (++iv[sizeof (iv) - 1] == 0) - ++iv[sizeof (iv) -2]; - } - - if (outlen > 0) - { - /* Truncated last AES output block */ - if (gcry_cipher_encrypt (prf, iv, sizeof (iv), NULL, 0)) - return -1; - memcpy (out, iv, outlen); - } - - return 0; + memset (out, 0, outlen); + return ctr_crypt (prf, iv, out, outlen); } @@ -263,13 +303,14 @@ proto_derive (srtp_proto_t *p, gcry_cipher_hd_t prf, if (saltlen != 14) return -1; - uint8_t cipherkey[16]; + uint8_t keybuf[20]; uint8_t label = rtcp ? SRTCP_CRYPT : SRTP_CRYPT; - if (derive (prf, salt, r, rlen, label++, cipherkey, 16) - || gcry_cipher_setkey (p->cipher, cipherkey, 16) - || derive (prf, salt, r, rlen, label++, NULL, 0) /* FIXME HMAC */ - || derive (prf, salt, r, rlen, label++, p->salt, 14)) + if (derive (prf, salt, r, rlen, label++, keybuf, 16) + || gcry_cipher_setkey (p->cipher, keybuf, 16) + || derive (prf, salt, r, rlen, label++, keybuf, 20) + || gcry_md_setkey (p->mac, keybuf, 20) + || derive (prf, salt, r, rlen, label, p->salt, 14)) return -1; return 0; @@ -286,11 +327,11 @@ srtp_derive (srtp_session_t *s, const void *key, size_t keylen, gcry_cipher_hd_t prf; uint8_t r[6]; - /* TODO: retry with CTR mode */ - if (gcry_cipher_open (&prf, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_ECB, 0) + if (gcry_cipher_open (&prf, GCRY_CIPHER_AES, GCRY_CIPHER_MODE_CTR, 0) || gcry_cipher_setkey (prf, key, keylen)) return -1; +#if 0 /* RTP key derivation */ if (s->kdr != 0) { @@ -304,6 +345,7 @@ srtp_derive (srtp_session_t *s, const void *key, size_t keylen, } } else +#endif memset (r, 0, sizeof (r)); if (proto_derive (&s->rtp, prf, salt, saltlen, r, 6, false)) @@ -319,7 +361,6 @@ srtp_derive (srtp_session_t *s, const void *key, size_t keylen, } - /** * Sets (or resets) the master key and master salt for a SRTP session. * This must be done at least once before using rtp_send(), rtp_recv(), @@ -338,43 +379,37 @@ srtp_setkey (srtp_session_t *s, const void *key, size_t keylen, } -/** AES-CM encryption/decryption (ctr length = 16 bytes) */ -static int -encrypt (gcry_cipher_hd_t hd, uint32_t *ctr, uint8_t *data, size_t len) +/** + * Sets Roll-over-Counter Carry (RCC) rate for the SRTP session. If not + * specified (through this function), the default rate of ONE is assumed + * (i.e. every RTP packets will carry the RoC). RCC rate is ignored if none + * of the RCC mode has been selected. + * + * The RCC mode is selected through one of these flags for srtp_create(): + * SRTP_RCC_MODE1: integrity protection only for RoC carrying packets + * SRTP_RCC_MODE2: integrity protection for all packets + * SRTP_RCC_MODE3: no integrity protection + * + * RCC mode 3 is insecure. Compared to plain RTP, it provides confidentiality + * (through encryption) but is much more prone to DoS. It can only be used if + * anti-spoofing protection is provided by lower network layers (e.g. IPsec, + * or trusted routers and proper source address filtering). + * + * If RCC rate is 1, RCC mode 1 and 2 are functionally identical. + * + * @param rate RoC Carry rate (MUST NOT be zero) + */ +void srtp_setrcc_rate (srtp_session_t *s, uint16_t rate) { - const size_t ctrlen = 16; - while (len >= ctrlen) - { - if (gcry_cipher_setctr (hd, ctr, ctrlen) - || gcry_cipher_encrypt (hd, data, ctrlen, NULL, 0)) - return -1; - - data += ctrlen; - len -= ctrlen; - ctr[3] = htonl (ntohl (ctr[3]) + 1); - } - - if (len > 0) - { - /* Truncated last block */ - uint8_t dummy[ctrlen]; - memcpy (dummy, data, len); - memset (dummy + len, 0, ctrlen - len); - - if (gcry_cipher_setctr (hd, ctr, ctrlen) - || gcry_cipher_encrypt (hd, dummy, ctrlen, data, ctrlen)) - return -1; - memcpy (data, dummy, len); - } - - return 0; + assert (rate != 0); + s->rtp_rcc = rate; } /** AES-CM for RTP (salt = 14 bytes + 2 nul bytes) */ -static inline int -rtp_encrypt (gcry_cipher_hd_t hd, uint32_t ssrc, uint32_t roc, uint16_t seq, - const uint32_t *salt, uint8_t *data, size_t len) +static int +rtp_crypt (gcry_cipher_hd_t hd, uint32_t ssrc, uint32_t roc, uint16_t seq, + const uint32_t *salt, uint8_t *data, size_t len) { /* Determines cryptographic counter (IV) */ uint32_t counter[4]; @@ -384,7 +419,50 @@ rtp_encrypt (gcry_cipher_hd_t hd, uint32_t ssrc, uint32_t roc, uint16_t seq, counter[3] = salt[3] ^ htonl (seq << 16); /* Encryption */ - return encrypt (hd, counter, data, len); + return ctr_crypt (hd, counter, data, len); +} + + +/** Determines SRTP Roll-Over-Counter (in host-byte order) */ +static uint32_t +srtp_compute_roc (const srtp_session_t *s, uint16_t seq) +{ + uint32_t roc = s->rtp_roc; + + if (((seq - s->rtp_seq) & 0xffff) < 0x8000) + { + /* Sequence is ahead, good */ + if (seq < s->rtp_seq) + roc++; /* Sequence number wrap */ + } + else + { + /* Sequence is late, bad */ + if (seq > s->rtp_seq) + roc--; /* Wrap back */ + } + return roc; +} + + +/** Returns RTP sequence (in host-byte order) */ +static inline uint16_t rtp_seq (const uint8_t *buf) +{ + return (buf[2] << 8) | buf[3]; +} + + +/** Message Authentication and Integrity for RTP */ +static const uint8_t * +rtp_digest (srtp_session_t *s, const uint8_t *data, size_t len, + uint32_t roc) +{ + const gcry_md_hd_t md = s->rtp.mac; + + gcry_md_reset (md); + gcry_md_write (md, data, len); + gcry_md_write (md, &(uint32_t){ htonl (roc) }, 4); + return gcry_md_read (md, 0); } @@ -393,13 +471,14 @@ rtp_encrypt (gcry_cipher_hd_t hd, uint32_t ssrc, uint32_t roc, uint16_t seq, * (CTR block cypher mode of operation has identical encryption and * decryption function). * - * @param buf RTP packet to be encrypted/digested + * @param buf RTP packet to be en-/decrypted * @param len RTP packet length * * @return 0 on success, in case of error: * EINVAL malformatted RTP packet + * EACCES replayed packet or out-of-window or sync lost */ -static int srtp_encrypt (srtp_session_t *s, uint8_t *buf, size_t len) +static int srtp_crypt (srtp_session_t *s, uint8_t *buf, size_t len) { assert (s != NULL); @@ -419,35 +498,41 @@ static int srtp_encrypt (srtp_session_t *s, uint8_t *buf, size_t len) return EINVAL; memcpy (&extlen, buf + offset - 2, 2); - offset += htons (extlen); + offset += htons (extlen); // skips RTP extension header } if (len < offset) return EINVAL; /* Determines RTP 48-bits counter and SSRC */ - uint32_t ssrc; + uint16_t seq = rtp_seq (buf); + uint32_t roc = srtp_compute_roc (s, seq), ssrc; memcpy (&ssrc, buf + 8, 4); - uint16_t seq = (buf[2] << 8) | buf[3]; - if (((seq - s->rtp_seq) & 0xffff) < 32768) + /* Updates ROC and sequence (it's safe now) */ + int16_t diff = seq - s->rtp_seq; + if (diff > 0) { - if (seq < s->rtp_seq) - s->rtp_roc++; /* Sequence number wrap */ + /* Sequence in the future, good */ + s->rtp.window = s->rtp.window << diff; + s->rtp.window |= 1; + s->rtp_seq = seq, s->rtp_roc = roc; } else { - if (seq > s->rtp_seq) - s->rtp_roc--; + /* Sequence in the past/present, bad */ + diff = -diff; + if ((diff >= 64) || ((s->rtp.window >> diff) & 1)) + return EACCES; /* Replay attack */ + s->rtp.window |= 1 << diff; } - s->rtp_seq = seq; - + /* Encrypt/Decrypt */ if (s->flags & SRTP_UNENCRYPTED) return 0; - if (rtp_encrypt (s->rtp.cipher, ssrc, s->rtp_roc, seq, s->rtp.salt, - buf + offset, len - offset)) + if (rtp_crypt (s->rtp.cipher, ssrc, roc, seq, s->rtp.salt, + buf + offset, len - offset)) return EINVAL; return 0; @@ -461,51 +546,273 @@ static int srtp_encrypt (srtp_session_t *s, uint8_t *buf, size_t len) * * @param buf RTP packet to be encrypted/digested * @param lenp pointer to the RTP packet length on entry, - * set to the SRTP length on exit (undefined in case of error) + * set to the SRTP length on exit (undefined on non-ENOSPC error) * @param bufsize size (bytes) of the packet buffer * * @return 0 on success, in case of error: - * EINVAL malformatted RTP packet - * ENOSPC bufsize is too small (to add authentication tag) + * EINVAL malformatted RTP packet or internal error + * ENOSPC bufsize is too small to add authentication tag + * ( will hold the required byte size) + * EACCES packet would trigger a replay error on receiver */ int srtp_send (srtp_session_t *s, uint8_t *buf, size_t *lenp, size_t bufsize) { size_t len = *lenp; - int val = srtp_encrypt (s, buf, len); + int val = srtp_crypt (s, buf, len); if (val) return val; - if (bufsize < (len + s->rtp.mac_len)) - return ENOSPC; + if (!(s->flags & SRTP_UNAUTHENTICATED)) + { + size_t tag_len = s->tag_len; + *lenp = len + tag_len; + if (bufsize < (len + tag_len)) + return ENOSPC; + + uint32_t roc = srtp_compute_roc (s, rtp_seq (buf)); + const uint8_t *tag = rtp_digest (s, buf, len, roc); + if (rcc_mode (s)) + { + assert (s->rtp_rcc); + if ((rtp_seq (buf) % s->rtp_rcc) == 0) + { + memcpy (buf + len, &(uint32_t){ htonl (s->rtp_roc) }, 4); + len += 4; + if (rcc_mode (s) == 3) + tag_len = 0; + else + tag_len -= 4; + } + else + { + if (rcc_mode (s) & 1) + tag_len = 0; + } + } + memcpy (buf + len, tag, tag_len); + } - /* FIXME: HMAC and anti-replay */ return 0; } /** - * Turns a RTP packet into a SRTP packet: encrypt it, then computes - * the authentication tag and appends it. - * Note that you can encrypt packet in disorder. + * Turns a SRTP packet into a RTP packet: authenticates the packet, + * then decrypts it. * - * @param buf RTP packet to be decrypted/digested - * @param lenp pointer to the RTP packet length on entry, - * set to the SRTP length on exit (undefined in case of error) + * @param buf RTP packet to be digested/decrypted + * @param lenp pointer to the SRTP packet length on entry, + * set to the RTP length on exit (undefined in case of error) * * @return 0 on success, in case of error: - * EINVAL malformatted RTP packet + * EINVAL malformatted SRTP packet * EACCES authentication failed (spoofed packet or out-of-sync) */ int srtp_recv (srtp_session_t *s, uint8_t *buf, size_t *lenp) { size_t len = *lenp; - int val = srtp_encrypt (s, buf, len); + if (len < 12u) + return EINVAL; + + if (!(s->flags & SRTP_UNAUTHENTICATED)) + { + size_t tag_len = s->tag_len, roc_len = 0; + if (rcc_mode (s)) + { + if ((rtp_seq (buf) % s->rtp_rcc) == 0) + { + roc_len = 4; + if (rcc_mode (s) == 3) + tag_len = 0; + else + tag_len -= 4; + } + else + { + if (rcc_mode (s) & 1) + tag_len = 0; // RCC mode 1 or 3: no auth + } + } + + if (len < (12u + roc_len + tag_len)) + return EINVAL; + len -= roc_len + tag_len; + + uint32_t roc = srtp_compute_roc (s, rtp_seq (buf)), rcc; + if (roc_len) + { + assert (roc_len == 4); + memcpy (&rcc, buf + len, 4); + rcc = ntohl (rcc); + } + else + rcc = roc; + + const uint8_t *tag = rtp_digest (s, buf, len, rcc); + if (memcmp (buf + len + roc_len, tag, s->tag_len)) + return EACCES; + + if (roc_len) + { + /* Authenticated packet carried a Roll-Over-Counter */ + s->rtp_roc += rcc - roc; + assert (srtp_compute_roc (s, rtp_seq (buf)) == rcc); + } + *lenp = len; + } + + return srtp_crypt (s, buf, len); +} + + +/** AES-CM for RTCP (salt = 14 bytes + 2 nul bytes) */ +static int +rtcp_crypt (gcry_cipher_hd_t hd, uint32_t ssrc, uint32_t index, + const uint32_t *salt, uint8_t *data, size_t len) +{ + return rtp_crypt (hd, ssrc, index >> 16, index & 0xffff, salt, data, len); +} + + +/** Message Authentication and Integrity for RTCP */ +static const uint8_t * +rtcp_digest (gcry_md_hd_t md, const void *data, size_t len) +{ + gcry_md_reset (md); + gcry_md_write (md, data, len); + return gcry_md_read (md, 0); +} + + +/** + * Encrypts/decrypts a RTCP packet and updates SRTCP context + * (CTR block cypher mode of operation has identical encryption and + * decryption function). + * + * @param buf RTCP packet to be en-/decrypted + * @param len RTCP packet length + * + * @return 0 on success, in case of error: + * EINVAL malformatted RTCP packet + */ +static int srtcp_crypt (srtp_session_t *s, uint8_t *buf, size_t len) +{ + assert (s != NULL); + + /* 8-bytes unencrypted header, and 4-bytes unencrypted footer */ + if ((len < 12) || ((buf[0] >> 6) != 2)) + return EINVAL; + + uint32_t index; + memcpy (&index, buf + len, 4); + index = ntohl (index); + if (((index >> 31) != 0) != ((s->flags & SRTCP_UNENCRYPTED) == 0)) + return EINVAL; // E-bit mismatch + + index &= ~(1 << 31); // clear E-bit for counter + + /* Updates SRTCP index (safe here) */ + int32_t diff = index - s->rtcp_index; + if (diff > 0) + { + /* Packet in the future, good */ + s->rtcp.window = s->rtcp.window << diff; + s->rtcp.window |= 1; + s->rtcp_index = index; + } + else + { + /* Packet in the past/present, bad */ + diff = -diff; + if ((diff >= 64) || ((s->rtcp.window >> diff) & 1)) + return EACCES; // replay attack! + s->rtp.window |= 1 << diff; + } + + /* Crypts SRTCP */ + if (s->flags & SRTCP_UNENCRYPTED) + return 0; + + uint32_t ssrc; + memcpy (&ssrc, buf + 4, 4); + + if (rtcp_crypt (s->rtcp.cipher, ssrc, index, s->rtp.salt, + buf + 8, len - 8)) + return EINVAL; + return 0; +} + + +/** + * Turns a RTCP packet into a SRTCP packet: encrypt it, then computes + * the authentication tag and appends it. + * + * @param buf RTCP packet to be encrypted/digested + * @param lenp pointer to the RTCP packet length on entry, + * set to the SRTCP length on exit (undefined in case of error) + * @param bufsize size (bytes) of the packet buffer + * + * @return 0 on success, in case of error: + * EINVAL malformatted RTCP packet or internal error + * ENOSPC bufsize is too small (to add index and authentication tag) + */ +int +srtcp_send (srtp_session_t *s, uint8_t *buf, size_t *lenp, size_t bufsize) +{ + size_t len = *lenp; + if (bufsize < (len + 4 + s->tag_len)) + return ENOSPC; + + uint32_t index = ++s->rtcp_index; + if (index >> 31) + s->rtcp_index = index = 0; /* 31-bit wrap */ + + if ((s->flags & SRTCP_UNENCRYPTED) == 0) + index |= 0x80000000; /* Set Encrypted bit */ + memcpy (buf + len, &(uint32_t){ htonl (index) }, 4); + + int val = srtcp_crypt (s, buf, len); if (val) return val; - /* FIXME: HMAC and anti-replay */ + len += 4; /* Digests SRTCP index too */ + + const uint8_t *tag = rtcp_digest (s->rtp.mac, buf, len); + memcpy (buf + len, tag, s->tag_len); + *lenp = len + s->tag_len; return 0; } + +/** + * Turns a SRTCP packet into a RTCP packet: authenticates the packet, + * then decrypts it. + * + * @param buf RTCP packet to be digested/decrypted + * @param lenp pointer to the SRTCP packet length on entry, + * set to the RTCP length on exit (undefined in case of error) + * + * @return 0 on success, in case of error: + * EINVAL malformatted SRTCP packet + * EACCES authentication failed (spoofed packet or out-of-sync) + */ +int +srtcp_recv (srtp_session_t *s, uint8_t *buf, size_t *lenp) +{ + size_t len = *lenp; + + if (len < (4u + s->tag_len)) + return EINVAL; + len -= s->tag_len; + + const uint8_t *tag = rtcp_digest (s->rtp.mac, buf, len); + if (memcmp (buf + len, tag, s->tag_len)) + return EACCES; + + len -= 4; /* Remove SRTCP index before decryption */ + *lenp = len; + return srtp_crypt (s, buf, len); +} +