diff options
-rw-r--r-- | doc/APIchanges | 3 | ||||
-rw-r--r-- | libavformat/srtp.c | 293 | ||||
-rw-r--r-- | libavformat/srtp.h | 52 | ||||
-rw-r--r-- | libavutil/Makefile | 2 | ||||
-rw-r--r-- | libavutil/hmac.c | 138 | ||||
-rw-r--r-- | libavutil/hmac.h | 95 | ||||
-rw-r--r-- | libavutil/version.h | 2 |
7 files changed, 584 insertions, 1 deletions
diff --git a/doc/APIchanges b/doc/APIchanges index 99b1c92983..2a7206a75a 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -132,6 +132,9 @@ API changes, most recent first: 2012-03-26 - a67d9cf - lavfi 2.66.100 Add avfilter_fill_frame_from_{audio_,}buffer_ref() functions. +2013-01-xx - xxxxxxx - lavu 52.5.0 - hmac.h + Add AVHMAC. + 2013-01-13 - xxxxxxx - lavc 54.36.0 - vdpau.h Add AVVDPAUContext struct for VDPAU hardware-accelerated decoding. diff --git a/libavformat/srtp.c b/libavformat/srtp.c new file mode 100644 index 0000000000..55b3b5b40c --- /dev/null +++ b/libavformat/srtp.c @@ -0,0 +1,293 @@ +/* + * SRTP encryption/decryption + * Copyright (c) 2012 Martin Storsjo + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "libavutil/base64.h" +#include "libavutil/aes.h" +#include "libavutil/hmac.h" +#include "libavutil/intreadwrite.h" +#include "libavutil/log.h" +#include "rtp.h" +#include "srtp.h" + +void ff_srtp_free(struct SRTPContext *s) +{ + if (!s) + return; + av_freep(&s->aes); + if (s->hmac) + av_hmac_free(s->hmac); + s->hmac = NULL; +} + +static void encrypt_counter(struct AVAES *aes, uint8_t *iv, uint8_t *outbuf, + int outlen) +{ + int i, j, outpos; + for (i = 0, outpos = 0; outpos < outlen; i++) { + uint8_t keystream[16]; + AV_WB16(&iv[14], i); + av_aes_crypt(aes, keystream, iv, 1, NULL, 0); + for (j = 0; j < 16 && outpos < outlen; j++, outpos++) + outbuf[outpos] ^= keystream[j]; + } +} + +static void derive_key(struct AVAES *aes, const uint8_t *salt, int label, + uint8_t *out, int outlen) +{ + uint8_t input[16] = { 0 }; + memcpy(input, salt, 14); + // Key derivation rate assumed to be zero + input[14 - 7] ^= label; + memset(out, 0, outlen); + encrypt_counter(aes, input, out, outlen); +} + +int ff_srtp_set_crypto(struct SRTPContext *s, const char *suite, + const char *params) +{ + uint8_t buf[30]; + + ff_srtp_free(s); + + // RFC 4568 + if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_80")) { + s->hmac_size = 10; + } else if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_32")) { + s->hmac_size = 4; + } else { + av_log(NULL, AV_LOG_WARNING, "SRTP Crypto suite %s not supported\n", + suite); + return AVERROR(EINVAL); + } + if (av_base64_decode(buf, params, sizeof(buf)) != sizeof(buf)) { + av_log(NULL, AV_LOG_WARNING, "Incorrect amount of SRTP params\n"); + return AVERROR(EINVAL); + } + // MKI and lifetime not handled yet + s->aes = av_aes_alloc(); + s->hmac = av_hmac_alloc(AV_HMAC_SHA1); + if (!s->aes || !s->hmac) + return AVERROR(ENOMEM); + memcpy(s->master_key, buf, 16); + memcpy(s->master_salt, buf + 16, 14); + + // RFC 3711 + av_aes_init(s->aes, s->master_key, 128, 0); + + derive_key(s->aes, s->master_salt, 0x00, s->rtp_key, sizeof(s->rtp_key)); + derive_key(s->aes, s->master_salt, 0x02, s->rtp_salt, sizeof(s->rtp_salt)); + derive_key(s->aes, s->master_salt, 0x01, s->rtp_auth, sizeof(s->rtp_auth)); + + derive_key(s->aes, s->master_salt, 0x03, s->rtcp_key, sizeof(s->rtcp_key)); + derive_key(s->aes, s->master_salt, 0x05, s->rtcp_salt, sizeof(s->rtcp_salt)); + derive_key(s->aes, s->master_salt, 0x04, s->rtcp_auth, sizeof(s->rtcp_auth)); + return 0; +} + +static void create_iv(uint8_t *iv, const uint8_t *salt, uint64_t index, + uint32_t ssrc) +{ + uint8_t indexbuf[8]; + int i; + memset(iv, 0, 16); + AV_WB32(&iv[4], ssrc); + AV_WB64(indexbuf, index); + for (i = 0; i < 8; i++) // index << 16 + iv[6 + i] ^= indexbuf[i]; + for (i = 0; i < 14; i++) + iv[i] ^= salt[i]; +} + +int ff_srtp_decrypt(struct SRTPContext *s, uint8_t *buf, int *lenptr) +{ + uint8_t iv[16] = { 0 }, hmac[20]; + int len = *lenptr; + int ext, seq_largest; + uint32_t ssrc, roc; + uint64_t index; + int rtcp; + + // TODO: Missing replay protection + + if (len < s->hmac_size) + return AVERROR_INVALIDDATA; + + rtcp = RTP_PT_IS_RTCP(buf[1]); + + // Authentication HMAC + av_hmac_init(s->hmac, rtcp ? s->rtcp_auth : s->rtp_auth, sizeof(s->rtp_auth)); + // If MKI is used, this should exclude the MKI as well + av_hmac_update(s->hmac, buf, len - s->hmac_size); + + if (!rtcp) { + int seq = AV_RB16(buf + 2); + uint32_t v; + uint8_t rocbuf[4]; + + // RFC 3711 section 3.3.1, appendix A + seq_largest = s->seq_initialized ? s->seq_largest : seq; + v = roc = s->roc; + if (seq_largest < 32768) { + if (seq - seq_largest > 32768) + v = roc - 1; + } else { + if (seq_largest - 32768 > seq) + v = roc + 1; + } + if (v == roc) { + seq_largest = FFMAX(seq_largest, seq); + } else if (v == roc + 1) { + seq_largest = seq; + roc = v; + } + index = seq + (((uint64_t)v) << 16); + + AV_WB32(rocbuf, roc); + av_hmac_update(s->hmac, rocbuf, 4); + } + + av_hmac_final(s->hmac, hmac, sizeof(hmac)); + if (memcmp(hmac, buf + len - s->hmac_size, s->hmac_size)) { + av_log(NULL, AV_LOG_WARNING, "HMAC mismatch\n"); + return AVERROR_INVALIDDATA; + } + + len -= s->hmac_size; + *lenptr = len; + + if (len < 12) + return AVERROR_INVALIDDATA; + + if (rtcp) { + uint32_t srtcp_index = AV_RB32(buf + len - 4); + len -= 4; + *lenptr = len; + + ssrc = AV_RB32(buf + 4); + index = srtcp_index & 0x7fffffff; + + buf += 8; + len -= 8; + if (!(srtcp_index & 0x80000000)) + return 0; + } else { + s->seq_initialized = 1; + s->seq_largest = seq_largest; + s->roc = roc; + + ext = buf[0] & 0x10; + ssrc = AV_RB32(buf + 8); + + buf += 12; + len -= 12; + + if (ext) { + if (len < 4) + return AVERROR_INVALIDDATA; + ext = (AV_RB16(buf + 2) + 1) * 4; + if (len < ext) + return AVERROR_INVALIDDATA; + len -= ext; + buf += ext; + } + } + + create_iv(iv, rtcp ? s->rtcp_salt : s->rtp_salt, index, ssrc); + av_aes_init(s->aes, rtcp ? s->rtcp_key : s->rtp_key, 128, 0); + encrypt_counter(s->aes, iv, buf, len); + + return 0; +} + +int ff_srtp_encrypt(struct SRTPContext *s, const uint8_t *in, int len, + uint8_t *out, int outlen) +{ + uint8_t iv[16] = { 0 }, hmac[20]; + uint64_t index; + uint32_t ssrc; + int rtcp; + uint8_t *buf; + + if (len + 14 > outlen) + return 0; + if (len < 12) + return 0; + + memcpy(out, in, len); + buf = out; + + rtcp = RTP_PT_IS_RTCP(buf[1]); + + if (rtcp) { + ssrc = AV_RB32(buf + 4); + index = s->rtcp_index++; + + buf += 8; + len -= 8; + } else { + int ext; + int seq = AV_RB16(buf + 2); + ssrc = AV_RB32(buf + 8); + + if (seq < s->seq_largest) + s->roc++; + s->seq_largest = seq; + index = seq + (((uint64_t)s->roc) << 16); + + ext = buf[0] & 0x10; + + buf += 12; + len -= 12; + + if (ext) { + if (len < 4) + return AVERROR_INVALIDDATA; + ext = (AV_RB16(buf + 2) + 1) * 4; + if (len < ext) + return AVERROR_INVALIDDATA; + len -= ext; + buf += ext; + } + } + + create_iv(iv, rtcp ? s->rtcp_salt : s->rtp_salt, index, ssrc); + av_aes_init(s->aes, rtcp ? s->rtcp_key : s->rtp_key, 128, 0); + encrypt_counter(s->aes, iv, buf, len); + + if (rtcp) { + AV_WB32(buf + len, 0x80000000 | index); + len += 4; + } + + av_hmac_init(s->hmac, rtcp ? s->rtcp_auth : s->rtp_auth, sizeof(s->rtp_auth)); + av_hmac_update(s->hmac, out, buf + len - out); + if (!rtcp) { + uint8_t rocbuf[4]; + AV_WB32(rocbuf, s->roc); + av_hmac_update(s->hmac, rocbuf, 4); + } + av_hmac_final(s->hmac, hmac, sizeof(hmac)); + + memcpy(buf + len, hmac, s->hmac_size); + len += s->hmac_size; + return buf + len - out; +} diff --git a/libavformat/srtp.h b/libavformat/srtp.h new file mode 100644 index 0000000000..97bbf2b787 --- /dev/null +++ b/libavformat/srtp.h @@ -0,0 +1,52 @@ +/* + * SRTP encryption/decryption + * Copyright (c) 2012 Martin Storsjo + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFORMAT_SRTP_H +#define AVFORMAT_SRTP_H + +#include <stdint.h> + +struct AVAES; +struct AVHMAC; + +struct SRTPContext { + struct AVAES *aes; + struct AVHMAC *hmac; + int hmac_size; + uint8_t master_key[16]; + uint8_t master_salt[14]; + uint8_t rtp_key[16], rtcp_key[16]; + uint8_t rtp_salt[14], rtcp_salt[14]; + uint8_t rtp_auth[20], rtcp_auth[20]; + int seq_largest, seq_initialized; + uint32_t roc; + + uint32_t rtcp_index; +}; + +int ff_srtp_set_crypto(struct SRTPContext *s, const char *suite, + const char *params); +void ff_srtp_free(struct SRTPContext *s); +int ff_srtp_decrypt(struct SRTPContext *s, uint8_t *buf, int *lenptr); +int ff_srtp_encrypt(struct SRTPContext *s, const uint8_t *in, int len, + uint8_t *out, int outlen); + +#endif /* AVFORMAT_SRTP_H */ diff --git a/libavutil/Makefile b/libavutil/Makefile index 61ddc9a3fc..a0044dade4 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -22,6 +22,7 @@ HEADERS = adler32.h \ eval.h \ fifo.h \ file.h \ + hmac.h \ imgutils.h \ intfloat.h \ intfloat_readwrite.h \ @@ -72,6 +73,7 @@ OBJS = adler32.o \ fifo.o \ file.o \ float_dsp.o \ + hmac.o \ imgutils.o \ intfloat_readwrite.o \ intmath.o \ diff --git a/libavutil/hmac.c b/libavutil/hmac.c new file mode 100644 index 0000000000..1520148246 --- /dev/null +++ b/libavutil/hmac.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012 Martin Storsjo + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <string.h> + +#include "hmac.h" +#include "md5.h" +#include "sha.h" +#include "mem.h" + +#define MAX_HASHLEN 20 +#define MAX_BLOCKLEN 64 + +struct AVHMAC { + void *hash; + int blocklen, hashlen; + void (*final)(void*, uint8_t*); + void (*update)(void*, const uint8_t*, int len); + void (*init)(void*); + uint8_t key[MAX_BLOCKLEN]; + int keylen; +}; + +static void sha1_init(void *ctx) +{ + av_sha_init(ctx, 160); +} + +AVHMAC *av_hmac_alloc(enum AVHMACType type) +{ + AVHMAC *c = av_mallocz(sizeof(*c)); + if (!c) + return NULL; + switch (type) { + case AV_HMAC_MD5: + c->blocklen = 64; + c->hashlen = 16; + c->init = av_md5_init; + c->update = av_md5_update; + c->final = av_md5_final; + c->hash = av_md5_alloc(); + break; + case AV_HMAC_SHA1: + c->blocklen = 64; + c->hashlen = 20; + c->init = sha1_init; + c->update = av_sha_update; + c->final = av_sha_final; + c->hash = av_sha_alloc(); + break; + default: + av_free(c); + return NULL; + } + if (!c->hash) { + av_free(c); + return NULL; + } + return c; +} + +void av_hmac_free(AVHMAC *c) +{ + if (!c) + return; + av_free(c->hash); + av_free(c); +} + +void av_hmac_init(AVHMAC *c, const uint8_t *key, unsigned int keylen) +{ + int i; + uint8_t block[MAX_BLOCKLEN]; + if (keylen > c->blocklen) { + c->init(c->hash); + c->update(c->hash, key, keylen); + c->final(c->hash, c->key); + c->keylen = c->hashlen; + } else { + memcpy(c->key, key, keylen); + c->keylen = keylen; + } + c->init(c->hash); + for (i = 0; i < c->keylen; i++) + block[i] = c->key[i] ^ 0x36; + for (i = c->keylen; i < c->blocklen; i++) + block[i] = 0x36; + c->update(c->hash, block, c->blocklen); +} + +void av_hmac_update(AVHMAC *c, const uint8_t *data, unsigned int len) +{ + c->update(c->hash, data, len); +} + +int av_hmac_final(AVHMAC *c, uint8_t *out, unsigned int outlen) +{ + uint8_t block[MAX_BLOCKLEN]; + int i; + if (outlen < c->hashlen) + return AVERROR(EINVAL); + c->final(c->hash, out); + c->init(c->hash); + for (i = 0; i < c->keylen; i++) + block[i] = c->key[i] ^ 0x5C; + for (i = c->keylen; i < c->blocklen; i++) + block[i] = 0x5C; + c->update(c->hash, block, c->blocklen); + c->update(c->hash, out, c->hashlen); + c->final(c->hash, out); + return c->hashlen; +} + +int av_hmac_calc(AVHMAC *c, const uint8_t *data, unsigned int len, + const uint8_t *key, unsigned int keylen, + uint8_t *out, unsigned int outlen) +{ + av_hmac_init(c, key, keylen); + av_hmac_update(c, data, len); + return av_hmac_final(c, out, outlen); +} diff --git a/libavutil/hmac.h b/libavutil/hmac.h new file mode 100644 index 0000000000..aef84c6439 --- /dev/null +++ b/libavutil/hmac.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Martin Storsjo + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_HMAC_H +#define AVUTIL_HMAC_H + +#include <stdint.h> + +/** + * @defgroup lavu_hmac HMAC + * @ingroup lavu_crypto + * @{ + */ + +enum AVHMACType { + AV_HMAC_MD5, + AV_HMAC_SHA1, +}; + +typedef struct AVHMAC AVHMAC; + +/** + * Allocate an AVHMAC context. + * @param type The hash function used for the HMAC. + */ +AVHMAC *av_hmac_alloc(enum AVHMACType type); + +/** + * Free an AVHMAC context. + * @param ctx The context to free, may be NULL + */ +void av_hmac_free(AVHMAC *ctx); + +/** + * Initialize an AVHMAC context with an authentication key. + * @param ctx The HMAC context + * @param key The authentication key + * @param keylen The length of the key, in bytes + */ +void av_hmac_init(AVHMAC *ctx, const uint8_t *key, unsigned int keylen); + +/** + * Hash data with the HMAC. + * @param ctx The HMAC context + * @param data The data to hash + * @param len The length of the data, in bytes + */ +void av_hmac_update(AVHMAC *ctx, const uint8_t *data, unsigned int len); + +/** + * Finish hashing and output the HMAC digest. + * @param ctx The HMAC context + * @param out The output buffer to write the digest into + * @param outlen The length of the out buffer, in bytes + * @return The number of bytes written to out, or a negative error code. + */ +int av_hmac_final(AVHMAC *ctx, uint8_t *out, unsigned int outlen); + +/** + * Hash an array of data with a key. + * @param ctx The HMAC context + * @param data The data to hash + * @param len The length of the data, in bytes + * @param key The authentication key + * @param keylen The length of the key, in bytes + * @param out The output buffer to write the digest into + * @param outlen The length of the out buffer, in bytes + * @return The number of bytes written to out, or a negative error code. + */ +int av_hmac_calc(AVHMAC *ctx, const uint8_t *data, unsigned int len, + const uint8_t *key, unsigned int keylen, + uint8_t *out, unsigned int outlen); + +/** + * @} + */ + +#endif /* AVUTIL_HMAC_H */ diff --git a/libavutil/version.h b/libavutil/version.h index f59fd9baea..c0c8fcad2e 100644 --- a/libavutil/version.h +++ b/libavutil/version.h @@ -75,7 +75,7 @@ */ #define LIBAVUTIL_VERSION_MAJOR 52 -#define LIBAVUTIL_VERSION_MINOR 14 +#define LIBAVUTIL_VERSION_MINOR 15 #define LIBAVUTIL_VERSION_MICRO 100 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ |