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

Reply via email to