From 8ee288d2586f216aa698ef8e6d107f02c813ea06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Fri, 12 Oct 2012 15:01:06 +0300 Subject: [PATCH 1/2] lavu: Add an API for calculating HMAC (RFC 2104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This supports HMAC-MD5 and HMAC-SHA1 for now, other hashes are simple to add. Signed-off-by: Martin Storsjö --- doc/APIchanges | 3 + libavutil/Makefile | 2 + libavutil/hmac.c | 138 ++++++++++++++++++++++++++++++++++++++++++++ libavutil/hmac.h | 95 ++++++++++++++++++++++++++++++ libavutil/version.h | 2 +- 5 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 libavutil/hmac.c create mode 100644 libavutil/hmac.h diff --git a/doc/APIchanges b/doc/APIchanges index 4b6025345c..3120f7ee6a 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -13,6 +13,9 @@ libavutil: 2012-10-22 API changes, most recent first: +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/libavutil/Makefile b/libavutil/Makefile index 48a0e16af3..79e67b50dd 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -19,6 +19,7 @@ HEADERS = adler32.h \ eval.h \ fifo.h \ file.h \ + hmac.h \ imgutils.h \ intfloat.h \ intfloat_readwrite.h \ @@ -66,6 +67,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..3308f1bb58 --- /dev/null +++ b/libavutil/hmac.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2012 Martin Storsjo + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include + +#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..28c2062b1b --- /dev/null +++ b/libavutil/hmac.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2012 Martin Storsjo + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; 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 + +/** + * @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 83a1f819d3..68f5752f05 100644 --- a/libavutil/version.h +++ b/libavutil/version.h @@ -37,7 +37,7 @@ */ #define LIBAVUTIL_VERSION_MAJOR 52 -#define LIBAVUTIL_VERSION_MINOR 4 +#define LIBAVUTIL_VERSION_MINOR 5 #define LIBAVUTIL_VERSION_MICRO 0 #define LIBAVUTIL_VERSION_INT AV_VERSION_INT(LIBAVUTIL_VERSION_MAJOR, \ From ab2ad8bd56882c0ea160b154e8b836eb71abc49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Storsj=C3=B6?= Date: Sun, 21 Oct 2012 01:20:35 +0300 Subject: [PATCH 2/2] lavf: Add functions for SRTP decryption/encryption MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This supports the AES_CM_128_HMAC_SHA1_80 and AES_CM_128_HMAC_SHA1_32 cipher suites (from RFC 4568) at the moment. The main missing features are replay protection (which can be added later without changing the internal API), and the F8 and null ciphers. Signed-off-by: Martin Storsjö --- libavformat/srtp.c | 293 +++++++++++++++++++++++++++++++++++++++++++++ libavformat/srtp.h | 52 ++++++++ 2 files changed, 345 insertions(+) create mode 100644 libavformat/srtp.c create mode 100644 libavformat/srtp.h diff --git a/libavformat/srtp.c b/libavformat/srtp.c new file mode 100644 index 0000000000..6c589b0354 --- /dev/null +++ b/libavformat/srtp.c @@ -0,0 +1,293 @@ +/* + * SRTP encryption/decryption + * Copyright (c) 2012 Martin Storsjo + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; 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..a1cb92d2ba --- /dev/null +++ b/libavformat/srtp.h @@ -0,0 +1,52 @@ +/* + * SRTP encryption/decryption + * Copyright (c) 2012 Martin Storsjo + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; 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 + +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 */