On Fri, Jun 29, 2012 at 2:06 PM, Martin Storsjö <[email protected]> wrote: > On Thu, 28 Jun 2012, Samuel Pitoiset wrote: > >> --- >> DONE: >> - fix Diego's remarks >> - fix build when rtmp is enabled without rtmpe >> >> TODO: >> - add support for GnuTLS >> Changelog | 2 +- >> configure | 4 + >> doc/general.texi | 2 +- >> doc/protocols.texi | 9 + >> libavformat/Makefile | 2 + >> libavformat/allformats.c | 2 + >> libavformat/rtmpdh.c | 212 ++++++++++++++++++ >> libavformat/rtmpdh.h | 107 +++++++++ >> libavformat/rtmpenc.c | 539 >> ++++++++++++++++++++++++++++++++++++++++++++++ >> libavformat/rtmpenc.h | 59 +++++ >> libavformat/rtmpproto.c | 88 +++++++- >> libavformat/version.h | 2 +- >> 12 files changed, 1020 insertions(+), 8 deletions(-) >> create mode 100644 libavformat/rtmpdh.c >> create mode 100644 libavformat/rtmpdh.h >> create mode 100644 libavformat/rtmpenc.c >> create mode 100644 libavformat/rtmpenc.h >> >> diff --git a/Changelog b/Changelog >> index 95f79bc..7d55f1b 100644 >> --- a/Changelog >> +++ b/Changelog >> @@ -30,7 +30,7 @@ version <next>: >> - Microsoft Screen 1 decoder >> - join audio filter >> - audio channel mapping filter >> - >> +- RTMPE protocol support > > > Here you accidentally remove a newline
Indeed, I fixed it. > > >> >> version 0.8: >> >> diff --git a/configure b/configure >> index 3619eff..6e9e245 100755 >> --- a/configure >> +++ b/configure >> @@ -1530,6 +1530,10 @@ mmsh_protocol_select="http_protocol" >> mmst_protocol_deps="network" >> rtmp_protocol_deps="!librtmp_protocol" >> rtmp_protocol_select="tcp_protocol" >> +rtmpe_protocol_deps="!librtmp_protocol" >> +rtmpe_protocol_select="rtmpenc_protocol" >> +rtmpenc_protocol_deps="!librtmp_protocol" >> +rtmpenc_protocol_select="tcp_protocol" > > > I think you need to make it rtmpenc_protocol_deps="!librtmp_protocol > openssl" or something similar. And once you add support for gnutls, you > should make it: > > rtmpenc_protocol_deps="!librtmp_protocol" > rtmpenc_protocol_deps_any="openssl gnutls" Done. > > >> rtmphttp_protocol_deps="!librtmp_protocol" >> rtmphttp_protocol_select="http_protocol" >> rtmpt_protocol_deps="!librtmp_protocol" >> diff --git a/doc/general.texi b/doc/general.texi >> index 28e89f0..d45a05b 100644 >> --- a/doc/general.texi >> +++ b/doc/general.texi >> @@ -829,7 +829,7 @@ performance on systems without hardware floating point >> support). >> @item MMST @tab X >> @item pipe @tab X >> @item RTMP @tab X >> -@item RTMPE @tab E >> +@item RTMPE @tab X >> @item RTMPS @tab E >> @item RTMPT @tab E > > > This one (RTMPT) should be switched to an X too (in a separate patch). Already committed =) > > >> @item RTMPTE @tab E >> diff --git a/doc/protocols.texi b/doc/protocols.texi >> index e75f108..5f20d8e 100644 >> --- a/doc/protocols.texi >> +++ b/doc/protocols.texi >> @@ -255,6 +255,15 @@ The Real-Time Messaging Protocol tunneled through >> HTTP (RTMPT) is used >> for streaming multimedia content within HTTP requests to traverse >> firewalls. >> >> +@section rtmpe >> + >> +Encrypted Real-Time Messaging Protocol. >> + >> +The Encrypted Real-Time Messaging Protocol (RTMPE) is used for >> +streaming multimedia content within standard cryptographic primitives, >> +consisting of Diffie-Hellman key exchange and HMACSHA256, generating >> +a pair of RC4 keys. >> + >> @section rtmp, rtmpe, rtmps, rtmpt, rtmpte >> >> Real-Time Messaging Protocol and its variants supported through >> diff --git a/libavformat/Makefile b/libavformat/Makefile >> index bf219c2..664f9f4 100644 >> --- a/libavformat/Makefile >> +++ b/libavformat/Makefile >> @@ -348,6 +348,8 @@ OBJS-$(CONFIG_MMST_PROTOCOL) += mmst.o >> mms.o asf.o >> OBJS-$(CONFIG_MD5_PROTOCOL) += md5proto.o >> OBJS-$(CONFIG_PIPE_PROTOCOL) += file.o >> OBJS-$(CONFIG_RTMP_PROTOCOL) += rtmpproto.o rtmppkt.o >> +OBJS-$(CONFIG_RTMPE_PROTOCOL) += rtmpproto.o rtmppkt.o >> +OBJS-$(CONFIG_RTMPENC_PROTOCOL) += rtmpenc.o rtmpdh.o >> OBJS-$(CONFIG_RTMPHTTP_PROTOCOL) += rtmphttp.o >> OBJS-$(CONFIG_RTMPT_PROTOCOL) += rtmpproto.o rtmppkt.o >> OBJS-$(CONFIG_RTP_PROTOCOL) += rtpproto.o >> diff --git a/libavformat/allformats.c b/libavformat/allformats.c >> index 8456398..cd8d752 100644 >> --- a/libavformat/allformats.c >> +++ b/libavformat/allformats.c >> @@ -257,6 +257,8 @@ void av_register_all(void) >> REGISTER_PROTOCOL (MD5, md5); >> REGISTER_PROTOCOL (PIPE, pipe); >> REGISTER_PROTOCOL (RTMP, rtmp); >> + REGISTER_PROTOCOL (RTMPE, rtmpe); >> + REGISTER_PROTOCOL (RTMPENC, rtmpenc); >> REGISTER_PROTOCOL (RTMPHTTP, rtmphttp); >> REGISTER_PROTOCOL (RTMPT, rtmpt); >> REGISTER_PROTOCOL (RTP, rtp); >> diff --git a/libavformat/rtmpdh.c b/libavformat/rtmpdh.c >> new file mode 100644 >> index 0000000..9b9c05d >> --- /dev/null >> +++ b/libavformat/rtmpdh.c >> @@ -0,0 +1,212 @@ >> +/* >> + * RTMP Diffie-Hellmann utilities >> + * Copyright (c) 2012 Samuel Pitoiset >> + * >> + * 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 >> + */ >> + >> +/** >> + * @file >> + * RTMP Diffie-Hellmann utilities >> + */ >> + >> +#include "rtmpdh.h" >> + >> +/* RFC 2631, Section 2.1.5, http://www.ietf.org/rfc/rfc2631.txt */ >> +static int dh_is_valid_public_key(ff_bignum y, ff_bignum p, ff_bignum q) >> +{ >> + ff_bignum bn = NULL; >> + int ret = 0; >> + >> + ff_bn_new(bn); >> + if (!bn) >> + return AVERROR(ENOMEM); >> + >> + /* y must lie in [2,p-1] */ >> + if (!ff_bn_set_word(bn, 1)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } > > > This could perhaps be made shorter by setting ret = AVERROR(EINVAL) before, > then each of these blocks drop from 4 lines to 2, when you can skip the > braces. (Then you obviously need to set ret back to 0 at the end of the > function.) I followed your suggestion and I factorized the code a bit more. > > >> + >> + if (!ff_bn_cmp(y, bn)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> + /* bn = p-2 */ >> + if (!ff_bn_copy(bn, p)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> + if (!ff_bn_sub_word(bn, 1)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> + if (!ff_bn_cmp(y, bn)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> + /* Verify with Sophie-Germain prime >> + * >> + * This is a nice test to make sure the public key position is >> calculated >> + * correctly. This test will fail in about 50% of the cases if >> applied to >> + * random data. >> + */ >> + /* y must fulfill y^q mod p = 1 */ >> +#if CONFIG_OPENSSL >> + BN_CTX *ctx = BN_CTX_new(); >> + if (!ctx) { >> + ret = AVERROR(ENOMEM); >> + goto fail; >> + } >> + >> + if (!BN_mod_exp(bn, y, q, p, ctx)) { >> + BN_CTX_free(ctx); >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> + BN_CTX_free(ctx); >> +#endif >> + >> + if (ff_bn_cmp_1(bn)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> +fail: >> + ff_bn_free(bn); >> + >> + return ret; >> +} >> + >> +ff_dh *ff_dh_init(int key_len) > > > Data structures should normally have capitalized names, normally without > underscores (like AVCodecContext and so on), and since internal data > structure names don't end up in the actual binary, they don't strictly need > to follow the same ff/av prefix rules. 'FFDH' doesn't look all that nice as > a name though... For this case, FF_DH might be the sanest choice though. I didn't know, fixed. > > >> +{ >> + ff_dh *dh; >> + int ret; >> + >> + if (!(dh = ff_dh_new())) >> + return AVERROR(ENOMEM); >> + >> + ff_bn_new(dh->g); >> + if (!dh->g) { >> + ret = AVERROR(ENOMEM); >> + goto fail; >> + } >> + >> + ff_bn_hex2bn(dh->p, P1024, ret); >> + if (!ret) { >> + ret = AVERROR(ENOMEM); >> + goto fail; >> + } >> + >> + if (!ff_bn_set_word(dh->g, 2)) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> + dh->length = key_len; >> + >> + return dh; >> + >> +fail: >> + ff_dh_free(dh); >> + >> + return 0; > > > We normally use NULL instead of 0 for pointers. Indeed, I fixed this function too. > > >> +} >> + >> +int ff_dh_generate_public_key(ff_dh *dh) >> +{ >> + int ret = 0; >> + >> + while (!ret) { >> + ff_bignum q1 = NULL; >> + >> + if (!ff_dh_generate_key(dh)) >> + return AVERROR(EINVAL); >> + >> + ff_bn_hex2bn(q1, Q1024, ret); >> + if (!ret) >> + return AVERROR(ENOMEM); >> + >> + ret = dh_is_valid_public_key(dh->pub_key, dh->p, q1); >> + ff_bn_free(q1); >> + >> + if (!ret) { >> + /* the public key is valid */ >> + break; >> + } >> + >> + ff_bn_free(dh->pub_key); >> + ff_bn_free(dh->priv_key); >> + } >> + >> + return ret; >> +} >> + >> +int ff_dh_write_public_key(ff_dh *dh, uint8_t *pub_key, int pub_key_len) >> +{ >> + int len; >> + >> + /* compute the length of the public key */ >> + len = ff_bn_num_bytes(dh->pub_key); >> + if (len <= 0 || len > pub_key_len) >> + return AVERROR(EINVAL); >> + >> + /* convert the public key value into big-endian form */ >> + memset(pub_key, 0, pub_key_len); >> + ff_bn_bn2bin(dh->pub_key, pub_key + pub_key_len - len, len); >> + >> + return 0; >> +} >> + >> +int ff_dh_compute_shared_secret_key(ff_dh *dh, const uint8_t *pub_key, >> + int pub_key_len, uint8_t *secret) >> +{ >> + ff_bignum q1 = NULL, pub_key_bn = NULL; >> + int ret; >> + >> + /* convert the big-endian form of the public key into a bignum */ >> + ff_bn_bin2bn(pub_key_bn, pub_key, pub_key_len); >> + if (!pub_key_bn) >> + return AVERROR(ENOMEM); >> + >> + /* convert the string containing a hexadecimal number into a bignum >> */ >> + ff_bn_hex2bn(q1, Q1024, ret); >> + if (!ret) { >> + ret = AVERROR(ENOMEM); >> + goto fail; >> + } >> + >> + /* when the public key is valid we have to compute the shared secret >> key */ >> + if ((ret = dh_is_valid_public_key(pub_key_bn, dh->p, q1)) < 0) { >> + goto fail; >> + } else if ((ret = ff_dh_compute_key(secret, pub_key_len, pub_key_bn, >> dh)) < 0) { >> + ret = AVERROR(EINVAL); >> + goto fail; >> + } >> + >> +fail: >> + ff_bn_free(pub_key_bn); >> + ff_bn_free(q1); >> + >> + return ret; >> +} >> + >> diff --git a/libavformat/rtmpdh.h b/libavformat/rtmpdh.h >> new file mode 100644 >> index 0000000..064a56b >> --- /dev/null >> +++ b/libavformat/rtmpdh.h >> @@ -0,0 +1,107 @@ >> +/* >> + * RTMP Diffie-Hellmann utilities >> + * Copyright (c) 2012 Samuel Pitoiset >> + * >> + * 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_RTMPDH_H >> +#define AVFORMAT_RTMPDH_H >> + >> +#include "../config.h" >> +#include "avformat.h" >> + >> +#if CONFIG_OPENSSL >> +#include <openssl/bn.h> >> +#include <openssl/dh.h> >> + >> +typedef BIGNUM *ff_bignum; > > > This one should perhaps be named FFBigNum or something similar I think. (The > function names are ok as such though.) Done. > > >> +#define ff_bn_new(m) m = BN_new() >> +#define ff_bn_set_word(mpi, w) BN_set_word(mpi, w) >> +#define ff_bn_cmp(u, v) BN_cmp(u, v) >> +#define ff_bn_copy(u, v) BN_copy(u, v) >> +#define ff_bn_sub_word(mpi, w) BN_sub_word(mpi, w) >> +#define ff_bn_cmp_1(mpi) BN_cmp(mpi, BN_value_one()) >> +#define ff_bn_free(mpi) BN_free(mpi) >> +#define ff_bn_hex2bn(u, hex, res) res = BN_hex2bn(&u, hex) >> +#define ff_bn_num_bytes(u) BN_num_bytes(u) >> +#define ff_bn_bn2bin(u, buf, len) BN_bn2bin(u, buf) >> +#define ff_bn_bin2bn(u, buf, len) u = BN_bin2bn(buf, len, 0) >> + >> +#define ff_dh DH >> +#define ff_dh_new() DH_new() >> +#define ff_dh_free(dh) DH_free(dh) >> +#define ff_dh_generate_key(dh) DH_generate_key(dh) >> +#define ff_dh_compute_key(secret, seclen, pub, dh) >> DH_compute_key(secret, pub, dh) >> +#endif >> + >> +/* http://www.ietf.org/rfc/rfc3526.txt */ >> +/* 2^1024 - 2^960 - 1 + 2^64 * { [2^894 pi] + 129093 } */ >> +#define P1024 \ >> + "FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \ >> + "29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \ >> + "EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \ >> + "E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \ >> + "EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \ >> + "FFFFFFFFFFFFFFFF" >> + >> +/* Group morder largest prime factor: */ >> +#define Q1024 \ >> + "7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68" \ >> + "948127044533E63A0105DF531D89CD9128A5043CC71A026E" \ >> + "F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122" \ >> + "F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6" \ >> + "F71C35FDAD44CFD2D74F9208BE258FF324943328F67329C0" \ >> + "FFFFFFFFFFFFFFFF" >> + >> +/** >> + * Initialize a Diffie-Hellmann context. >> + */ >> +ff_dh *ff_dh_init(int key_len); >> + >> +/** >> + * Generate a public key. >> + * >> + * @param dh Diffie-Hellmann context >> + * @return zero on success, negative value otherwise >> + */ >> +int ff_dh_generate_public_key(ff_dh *dh); >> + >> +/** >> + * Write the public key into big-endian form. >> + * >> + * @param dh Diffie-Hellmann context >> + * @param pub_key public key >> + * @param pub_key_len length of the public key >> + * @return zero on success, negative value otherwise >> + */ >> +int ff_dh_write_public_key(ff_dh *dh, uint8_t *pub_key, int pub_key_len); >> + >> +/** >> + * Compute the shared secret key from the private ff_dh value and the >> + * other party's public value. >> + * >> + * @param dh Diffie-Hellmann context >> + * @param pub_key public key >> + * @param pub_key_len length of the public key >> + * @param secret shared secret key >> + * @return length of the shared secret key on success, negative value >> otherwise >> + */ >> +int ff_dh_compute_shared_secret_key(ff_dh *dh, const uint8_t *pub_key, >> + int pub_key_len, uint8_t *secret); >> + >> +#endif /* AVFORMAT_RTMPDH_H */ >> diff --git a/libavformat/rtmpenc.c b/libavformat/rtmpenc.c >> new file mode 100644 >> index 0000000..4809f86 >> --- /dev/null >> +++ b/libavformat/rtmpenc.c >> @@ -0,0 +1,539 @@ >> +/* >> + * RTMPE network protocol >> + * Copyright (c) 2012 Samuel Pitoiset >> + * >> + * 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 >> + */ >> + >> +/** >> + * @file >> + * RTMPE protocol >> + */ >> + >> +#include "libavutil/rc4.h" >> + >> +#include "internal.h" >> +#include "rtmp.h" >> +#include "rtmpdh.h" >> +#include "rtmpenc.h" >> +#include "url.h" >> + >> +/* protocol handler context */ >> +typedef struct RTMPEContext { >> + URLContext *stream; ///< TCP stream >> + ff_dh *dh; ///< Diffie-Hellman context >> + struct AVRC4 key_in; ///< RC4 key used for decrypt data >> + struct AVRC4 key_out; ///< RC4 key used for encrypt data >> + int handshaked; ///< flag indicating when the >> handshake is performed >> +} RTMPEContext; >> + >> +static unsigned int get_dh_offset(const uint8_t *buf) >> +{ >> + int i, dh_offset = 0; >> + >> + for (i = 768; i < 772; i++) >> + dh_offset += buf[i]; >> + dh_offset = dh_offset % 632 + 8; >> + >> + return dh_offset; >> +} >> + >> +int ff_rtmpe_gen_pub_key(URLContext *h, uint8_t *buf) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + int offset, ret; >> + >> + if (!(rt->dh = ff_dh_init(1024))) >> + return AVERROR(ENOMEM); >> + >> + offset = get_dh_offset(buf); >> + if (offset < 0) >> + return offset; >> + >> + if ((ret = ff_dh_generate_public_key(rt->dh)) < 0) >> + return ret; >> + >> + if ((ret = ff_dh_write_public_key(rt->dh, buf + offset, 128)) < 0) >> + return ret; >> + >> + return 0; >> +} >> + >> +int ff_rtmpe_compute_secret_key(URLContext *h, const uint8_t *serverdata, >> + const uint8_t *clientdata) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + uint8_t secret_key[128], digest[32]; >> + int server_pos, client_pos; >> + int ret; >> + >> + if ((server_pos = get_dh_offset(serverdata)) < 0) >> + return server_pos; >> + >> + if ((client_pos = get_dh_offset(clientdata)) < 0) >> + return client_pos; >> + >> + /* compute the shared secret secret in order to compute RC4 keys */ >> + if ((ret = ff_dh_compute_shared_secret_key(rt->dh, serverdata + >> server_pos, 128, >> + secret_key)) < 0) >> + return ret; >> + >> + /* set output key */ >> + if ((ret = ff_rtmp_calc_digest(serverdata + server_pos, 128, 0, >> secret_key, 128, >> + digest)) < 0) >> + return ret; >> + av_rc4_init(&rt->key_out, digest, 16 * 8, 1); >> + >> + /* set input key */ >> + if ((ret = ff_rtmp_calc_digest(clientdata + client_pos, 128, 0, >> secret_key, 128, >> + digest)) < 0) >> + return ret; >> + av_rc4_init(&rt->key_in, digest, 16 * 8, 1); >> + >> + return 0; >> +} >> + >> +/* >> + * Please, do not read this part of code (below) if you don't want to >> waste your time :) >> + */ >> + >> +/* RTMPE type 9 uses Blowfish on the regular signature >> + * http://en.wikipedia.org/wiki/Blowfish_(cipher) >> + */ >> +#define BF_ROUNDS 16 >> +typedef struct bf_key { >> + uint32_t s[4][256]; >> + uint32_t p[BF_ROUNDS+2]; >> +} bf_key; >> + > > >> [huge tables removed for readability] > > >> + >> +#define BF_ENC(X,S) \ >> + (((S[0][X>>24] + S[1][X>>16 & 0xff]) ^ S[2][(X>>8) & 0xff]) + >> S[3][X & 0xff]) >> + >> +static void bf_enc(uint32_t *x, bf_key *key) >> +{ >> + uint32_t Xl; >> + uint32_t Xr; >> + uint32_t temp; >> + int i; >> + >> + Xl = x[0]; >> + Xr = x[1]; >> + >> + for (i = 0; i < BF_ROUNDS; ++i) { >> + Xl ^= key->p[i]; >> + Xr ^= BF_ENC(Xl,key->s); >> + >> + temp = Xl; >> + Xl = Xr; >> + Xr = temp; >> + } >> + >> + Xl ^= key->p[BF_ROUNDS]; >> + Xr ^= key->p[BF_ROUNDS + 1]; >> + >> + x[0] = Xr; >> + x[1] = Xl; >> +} >> + >> +static void bf_setkey(const unsigned char *kp, int keybytes, bf_key *key) >> +{ >> + uint32_t data, d[2]; >> + int i, j, k; >> + >> + memcpy(key->p, bf_pinit, sizeof(key->p)); >> + memcpy(key->s, bf_sinit, sizeof(key->s)); >> + >> + j = 0; >> + for (i = 0; i < BF_ROUNDS + 2; ++i) { >> + data = 0x00000000; >> + for (k = 0; k < 4; ++k) { >> + data = (data << 8) | kp[j]; >> + j = j + 1; >> + if (j >= keybytes) { >> + j = 0; >> + } >> + } >> + key->p[i] ^= data; >> + } >> + >> + d[0] = 0x00000000; >> + d[1] = 0x00000000; >> + >> + for (i = 0; i < BF_ROUNDS + 2; i += 2) { >> + bf_enc(d, key); >> + >> + key->p[i] = d[0]; >> + key->p[i + 1] = d[1]; >> + } >> + >> + for (i = 0; i < 4; ++i) { >> + for (j = 0; j < 256; j += 2) { >> + >> + bf_enc(d, key); >> + >> + key->s[i][j] = d[0]; >> + key->s[i][j + 1] = d[1]; >> + } >> + } >> +} > > > Is this based on some existing code, what license does that use then? This > should rather be added to libavutil (just as for rc4), or if the source > license isn't suitable, we'd have to use something from openssl instead. As discussed on IRC, I copied that code from rtmpdump, so it's lgplv2+. I still have to move the implementation of blowfish in libavutil. > > >> +static void rtmpe9_sig(uint8_t *in, uint8_t *out, int keyid) >> +{ >> + uint32_t d[2]; >> + bf_key key; >> + >> + bf_setkey(rtmpe9_keys[keyid], KEYBYTES, &key); >> + >> + /* input is little-endian */ >> + d[0] = in[0] | (in[1] << 8) | (in[2] << 16) | (in[3] << 24); > > > Use AV_RL32(in) instead of this > > >> + d[1] = in[4] | (in[5] << 8) | (in[6] << 16) | (in[7] << 24); > > > ... and AV_RL32(&in[4]) for this > > >> + bf_enc(d, &key); >> + out[0] = d[0] & 0xff; >> + out[1] = (d[0] >> 8) & 0xff; >> + out[2] = (d[0] >> 16) & 0xff; >> + out[3] = (d[0] >> 24) & 0xff; >> + out[4] = d[1] & 0xff; >> + out[5] = (d[1] >> 8) & 0xff; >> + out[6] = (d[1] >> 16) & 0xff; >> + out[7] = (d[1] >> 24) & 0xff; > > > ... and AV_WL32(&out[0], d[0]) and so on. Fixed. > > >> +} >> + >> +/* >> + * You can continue here :) >> + */ >> +void ff_rtmpe_encrypt_sig(URLContext *h, uint8_t *sig, const uint8_t >> *digest) >> +{ >> + int i; >> + >> + for (i = 0; i < 32; i += 8) >> + rtmpe9_sig(sig + i, sig + i, digest[i] % 15); >> +} >> + >> +void ff_rtmpe_update_keystream(URLContext *h) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + char buf[RTMP_HANDSHAKE_PACKET_SIZE]; >> + >> + av_rc4_crypt(&rt->key_in, buf, buf, sizeof(buf), NULL, 1); >> + av_rc4_crypt(&rt->key_out, buf, buf, sizeof(buf), NULL, 1); >> + rt->handshaked = 1; >> +} > > > Umm, what does this really do? buf is uninitialized here. If you want to get > random data, get a proper random seed and use some random number generator > (like the lfg stuff) to fill it with actual random data. It's needed for updating the RC4 keys. > > >> + >> +static int rtmpe_close(URLContext *h) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + >> + ff_dh_free(rt->dh); >> + >> + ffurl_close(rt->stream); >> + >> + return 0; >> +} >> + >> +static int rtmpe_open(URLContext *h, const char *uri, int flags) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + char host[256], url[1024]; >> + int ret, port; >> + >> + av_url_split(NULL, 0, NULL, 0, host, sizeof(host), &port, NULL, 0, >> uri); >> + >> + if (port < 0) >> + port = 1935; >> + >> + /* open the tcp connection */ >> + ff_url_join(url, sizeof(url), "tcp", NULL, host, port, NULL); >> + if ((ret = ffurl_open(&rt->stream, url, AVIO_FLAG_READ_WRITE, >> + &h->interrupt_callback, NULL)) < 0) { >> + rtmpe_close(h); >> + return ret; >> + } >> + >> + return 0; >> +} >> + >> +static int rtmpe_read(URLContext *h, uint8_t *buf, int size) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + int ret, off = 0; >> + >> + do { >> + ret = ffurl_read(rt->stream, buf + off, size); >> + if (ret < 0 && ret != AVERROR_EOF) >> + return ret; >> + if (ret != AVERROR_EOF) { >> + off += ret; >> + size -= ret; >> + } >> + } while (off <= 0); > > > Can't you get rid of this loop altogether? (I think you could do the same > thing for rtmpt too.) You call ffurl_read once, if it returns > 0, you > return the data immediately (after calling av_rc4_crypt), if it returns 0, > you'd also return 0, and if it returns an error, you return that error. Indeed, fixed. > > >> + >> + if (rt->handshaked) >> + av_rc4_crypt(&rt->key_in, buf, buf, off, NULL, 1); >> + >> + return off; >> +} >> + >> +static int rtmpe_write(URLContext *h, const uint8_t *buf, int size) >> +{ >> + RTMPEContext *rt = h->priv_data; >> + int ret; >> + >> + if (rt->handshaked) >> + av_rc4_crypt(&rt->key_out, buf, buf, size, NULL, 1); // TODO: Fix >> warning >> + >> + if ((ret = ffurl_write(rt->stream, buf, size)) < 0) >> + return ret; >> + >> + return size; >> +} >> + >> +URLProtocol ff_rtmpenc_protocol = { >> + .name = "rtmpenc", >> + .url_open = rtmpe_open, >> + .url_read = rtmpe_read, >> + .url_write = rtmpe_write, >> + .url_close = rtmpe_close, >> + .priv_data_size = sizeof(RTMPEContext), >> + .flags = URL_PROTOCOL_FLAG_NETWORK, >> +}; >> diff --git a/libavformat/rtmpenc.h b/libavformat/rtmpenc.h >> new file mode 100644 >> index 0000000..1073b41 >> --- /dev/null >> +++ b/libavformat/rtmpenc.h >> @@ -0,0 +1,59 @@ >> +/* >> + * RTMPE encryption utilities >> + * Copyright (c) 2012 Samuel Pitoiset >> + * >> + * 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_RTMPE_H >> +#define AVFORMAT_RTMPE_H >> + >> +#include <stdint.h> >> +#include "url.h" >> + >> +/** >> + * Initialize the Diffie-Hellmann context and generate the public key. >> + * >> + * @param buf handshake data (1536 bytes) >> + * @return zero on success, negative value otherwise >> + */ >> +int ff_rtmpe_gen_pub_key(URLContext *h, uint8_t *buf); >> + >> +/** >> + * Compute the shared secret key and initialize the RC4 encryption. >> + * >> + * @param buf server data (1536 bytes) >> + * @param buf client data (1536 bytes) >> + * @return zero on success, negative value otherwise >> + */ >> +int ff_rtmpe_compute_secret_key(URLContext *h, const uint8_t *serverdata, >> + const uint8_t *clientdata); >> + >> +/** >> + * Encrypt the signature. >> + * >> + * @param signature signature >> + * @param digest digest >> + */ >> +void ff_rtmpe_encrypt_sig(URLContext *h, uint8_t *signature, const >> uint8_t *digest); >> + >> +/** >> + * Update the keystream and set RC4 keys for encryption. >> + */ >> +void ff_rtmpe_update_keystream(URLContext *h); >> + >> +#endif /* AVFORMAT_RTMPE_H */ >> diff --git a/libavformat/rtmpproto.c b/libavformat/rtmpproto.c >> index 3dea650..33dc8c7 100644 >> --- a/libavformat/rtmpproto.c >> +++ b/libavformat/rtmpproto.c >> @@ -37,6 +37,7 @@ >> >> #include "flv.h" >> #include "rtmp.h" >> +#include "rtmpenc.h" >> #include "rtmppkt.h" >> #include "url.h" >> >> @@ -92,6 +93,7 @@ typedef struct RTMPContext { >> int server_bw; ///< server bandwidth >> int client_buffer_time; ///< client buffer time in ms >> int flush_interval; ///< number of packets >> flushed in the same request (RTMPT only) >> + int encrypted; ///< use an encrypted >> connection (RTMPE only) >> } RTMPContext; >> >> #define PLAYER_KEY_OPEN_PART_LEN 30 ///< length of partial key used for >> first client digest signing >> @@ -644,9 +646,9 @@ static int rtmp_handshake_imprint_with_digest(uint8_t >> *buf) >> int i, digest_pos = 0; >> int ret; >> >> - for (i = 8; i < 12; i++) >> + for (i = 772; i < 776; i++) >> digest_pos += buf[i]; >> - digest_pos = (digest_pos % 728) + 12; >> + digest_pos = (digest_pos % 728) + 776; >> > > Why these changes? Aren't these codepaths already used by the normal rtmp > code? Or is it just plain wrong and not validated in practice there? Anyway, > this feels like it should go into a separate commit if that's the case. These changes are needed for RTMPE, I saw that in librtmp... Anyway, it works fine with the normal rtmp. I'll separate it in a new patch. > > >> ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos, >> rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN, >> @@ -706,7 +708,7 @@ static int rtmp_handshake(URLContext *s, RTMPContext >> *rt) >> uint8_t serverdata[RTMP_HANDSHAKE_PACKET_SIZE+1]; >> int i; >> int server_pos, client_pos; >> - uint8_t digest[32]; >> + uint8_t digest[32], signature[32]; >> int ret; >> >> av_log(s, AV_LOG_DEBUG, "Handshaking...\n"); >> @@ -715,6 +717,25 @@ static int rtmp_handshake(URLContext *s, RTMPContext >> *rt) >> // generate handshake packet - 1536 bytes of pseudorandom data >> for (i = 9; i <= RTMP_HANDSHAKE_PACKET_SIZE; i++) >> tosend[i] = av_lfg_get(&rnd) >> 24; >> + >> + if (rt->encrypted) { >> +#if CONFIG_RTMPE_PROTOCOL > > > You should be able to write this like > > if (rt->encrypted && CONFIG_RTMPE_PROTOCOL) { > > Then the compiler's dead code optimization will be able to remove the calls > if RTMPE isn't enabled. We rely on this feature in a number of other places, > and it makes the code prettier when you get rid of the ifdefs. Okay. > > >> + /* When the client wants to use RTMPE, we have to change the >> command >> + * byte to 0x06 which means to use encrypted data and we have to >> set >> + * the flash version to at least 9.0.115.0. */ >> + tosend[0] = 6; >> + tosend[5] = 128; >> + tosend[6] = 0; >> + tosend[7] = 3; >> + tosend[8] = 2; >> + >> + /* Initialize the Diffie-Hellmann context and generate the public >> key >> + * to send to the server. */ >> + if ((ret = ff_rtmpe_gen_pub_key(rt->stream, tosend + 1)) < 0) >> + return ret; >> +#endif >> + } >> + >> client_pos = rtmp_handshake_imprint_with_digest(tosend + 1); >> if (client_pos < 0) >> return client_pos; >> @@ -737,9 +758,15 @@ static int rtmp_handshake(URLContext *s, RTMPContext >> *rt) >> return ret; >> } >> >> + av_log(s, AV_LOG_DEBUG, "Type answer %d\n", serverdata[0]); >> av_log(s, AV_LOG_DEBUG, "Server version %d.%d.%d.%d\n", >> serverdata[5], serverdata[6], serverdata[7], serverdata[8]); >> >> + if (rt->encrypted && serverdata[0] == 8) { >> + // TODO: Implement RTMPE type 8 (ie. XTEA). >> + return AVERROR_PATCHWELCOME; >> + } >> + >> if (rt->is_input && serverdata[5] >= 3) { >> server_pos = rtmp_validate_digest(serverdata + 1, 772); >> if (server_pos < 0) >> @@ -762,11 +789,25 @@ static int rtmp_handshake(URLContext *s, RTMPContext >> *rt) >> return ret; >> >> ret = ff_rtmp_calc_digest(clientdata, RTMP_HANDSHAKE_PACKET_SIZE - >> 32, 0, >> - digest, 32, digest); >> + digest, 32, signature); >> + >> if (ret < 0) >> return ret; >> >> - if (memcmp(digest, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, >> 32)) { >> + if (rt->encrypted) { >> +#if CONFIG_RTMPE_PROTOCOL >> + /* Compute the shared secret key sent by the server and >> initialize >> + * the RC4 encryption. */ >> + if ((ret = ff_rtmpe_compute_secret_key(rt->stream, serverdata >> + 1, >> + tosend + 1)) < 0) >> + return ret; >> + >> + /* Encrypt the signature received by the server. */ >> + ff_rtmpe_encrypt_sig(rt->stream, signature, digest); >> +#endif >> + } >> + >> + if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - >> 32, 32)) { >> av_log(s, AV_LOG_ERROR, "Signature mismatch\n"); >> return AVERROR(EIO); >> } >> @@ -785,10 +826,25 @@ static int rtmp_handshake(URLContext *s, RTMPContext >> *rt) >> if (ret < 0) >> return ret; >> >> + if (rt->encrypted) { >> +#if CONFIG_RTMPE_PROTOCOL >> + /* Encrypt the signature to be send to the server. */ >> + ff_rtmpe_encrypt_sig(rt->stream, tosend + >> + RTMP_HANDSHAKE_PACKET_SIZE - 32, >> digest); >> +#endif >> + } >> + >> // write reply back to the server >> if ((ret = ffurl_write(rt->stream, tosend, >> RTMP_HANDSHAKE_PACKET_SIZE)) < 0) >> return ret; >> + >> + if (rt->encrypted) { >> +#if CONFIG_RTMPE_PROTOCOL >> + /* Set RC4 keys for encryption and update the keystreams. */ >> + ff_rtmpe_update_keystream(rt->stream); >> +#endif >> + } >> } else { >> if ((ret = ffurl_write(rt->stream, serverdata + 1, >> RTMP_HANDSHAKE_PACKET_SIZE)) < 0) >> @@ -1102,6 +1158,10 @@ static int rtmp_open(URLContext *s, const char >> *uri, int flags) >> if (!strcmp(proto, "rtmpt")) { >> /* open the http tunneling connection */ >> ff_url_join(buf, sizeof(buf), "rtmphttp", NULL, hostname, port, >> NULL); >> + } else if (!strcmp(proto, "rtmpe")) { >> + /* open the encrypted connection */ >> + ff_url_join(buf, sizeof(buf), "rtmpenc", NULL, hostname, port, >> NULL); >> + rt->encrypted = 1; >> } else { >> /* open the tcp connection */ >> if (port < 0) >> @@ -1442,3 +1502,21 @@ URLProtocol ff_rtmpt_protocol = { >> .flags = URL_PROTOCOL_FLAG_NETWORK, >> .priv_data_class = &rtmpt_class, >> }; >> + >> +static const AVClass rtmpe_class = { >> + .class_name = "rtmpe", >> + .item_name = av_default_item_name, >> + .option = rtmp_options, >> + .version = LIBAVUTIL_VERSION_INT, >> +}; >> + >> +URLProtocol ff_rtmpe_protocol = { >> + .name = "rtmpe", >> + .url_open = rtmp_open, >> + .url_read = rtmp_read, >> + .url_write = rtmp_write, >> + .url_close = rtmp_close, >> + .priv_data_size = sizeof(RTMPContext), >> + .flags = URL_PROTOCOL_FLAG_NETWORK, >> + .priv_data_class = &rtmpe_class, >> +}; >> diff --git a/libavformat/version.h b/libavformat/version.h >> index e2fa561..e98ad01 100644 >> --- a/libavformat/version.h >> +++ b/libavformat/version.h >> @@ -30,7 +30,7 @@ >> #include "libavutil/avutil.h" >> >> #define LIBAVFORMAT_VERSION_MAJOR 54 >> -#define LIBAVFORMAT_VERSION_MINOR 6 >> +#define LIBAVFORMAT_VERSION_MINOR 7 >> #define LIBAVFORMAT_VERSION_MICRO 0 >> >> #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, >> \ >> -- >> 1.7.7.5 (Apple Git-26) > > > Other than this, this looks quite promising in general. Thanks! -- Best regards, Samuel Pitoiset. _______________________________________________ libav-devel mailing list [email protected] https://lists.libav.org/mailman/listinfo/libav-devel
