Commit acd554c1 authored by Samuel Pitoiset's avatar Samuel Pitoiset Committed by Martin Storsjö

RTMPE protocol support

This adds two protocols, but one of them is an internal implementation
detail just used as an abstraction layer/generalization in the code. The
RTMPE protocol implementation uses ffrtmpcrypt:// as an alternative to the
tcp:// protocol. This allows moving most of the lower level logic out
from the higher level generic rtmp code.
Signed-off-by: 's avatarMartin Storsjö <martin@martin.st>
parent 0e31088b
......@@ -39,6 +39,7 @@ version <next>:
- RTMPTS protocol support
- JPEG 2000 encoding support through OpenJPEG
- G.723.1 demuxer and decoder
- RTMPE protocol support
version 0.8:
......
......@@ -950,6 +950,7 @@ CONFIG_LIST="
fastdiv
fft
frei0r
gcrypt
gnutls
gpl
gray
......@@ -982,6 +983,7 @@ CONFIG_LIST="
mdct
memalign_hack
mpegaudiodsp
nettle
network
nonfree
openssl
......@@ -1543,6 +1545,9 @@ vfwcap_indev_extralibs="-lavicap32"
x11_grab_device_indev_deps="x11grab XShmCreateImage"
# protocols
ffrtmpcrypt_protocol_deps="!librtmp_protocol"
ffrtmpcrypt_protocol_deps_any="gcrypt nettle openssl"
ffrtmpcrypt_protocol_select="tcp_protocol"
ffrtmphttp_protocol_deps="!librtmp_protocol"
ffrtmphttp_protocol_select="http_protocol"
gopher_protocol_deps="network"
......@@ -1560,6 +1565,7 @@ mmsh_protocol_select="http_protocol"
mmst_protocol_deps="network"
rtmp_protocol_deps="!librtmp_protocol"
rtmp_protocol_select="tcp_protocol"
rtmpe_protocol_select="ffrtmpcrypt_protocol"
rtmps_protocol_deps="!librtmp_protocol"
rtmps_protocol_select="tls_protocol"
rtmpt_protocol_select="ffrtmphttp_protocol"
......@@ -3014,6 +3020,11 @@ enabled openssl && { check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto
check_lib openssl/ssl.h SSL_library_init -lssl -lcrypto -lws2_32 -lgdi32 ||
die "ERROR: openssl not found"; }
if enabled gnutls; then
{ check_lib nettle/bignum.h nettle_mpz_get_str_256 -lnettle -lhogweed -lgmp && enable nettle; } ||
{ check_lib gcrypt.h gcry_mpi_new -lgcrypt && enable gcrypt; }
fi
# libdc1394 check
if enabled libdc1394; then
{ check_lib dc1394/dc1394.h dc1394_new -ldc1394 -lraw1394 &&
......
......@@ -844,7 +844,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 X
@item RTMPT @tab X
@item RTMPTE @tab E
......
......@@ -247,6 +247,15 @@ For example to read with @command{avplay} a multimedia resource named
avplay rtmp://myserver/vod/sample
@end example
@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 rtmps
Real-Time Messaging Protocol over a secure SSL connection.
......
......@@ -341,6 +341,7 @@ OBJS-$(CONFIG_LIBRTMP) += librtmp.o
OBJS-$(CONFIG_APPLEHTTP_PROTOCOL) += hlsproto.o
OBJS-$(CONFIG_CONCAT_PROTOCOL) += concat.o
OBJS-$(CONFIG_CRYPTO_PROTOCOL) += crypto.o
OBJS-$(CONFIG_FFRTMPCRYPT_PROTOCOL) += rtmpcrypt.o rtmpdh.o
OBJS-$(CONFIG_FFRTMPHTTP_PROTOCOL) += rtmphttp.o
OBJS-$(CONFIG_FILE_PROTOCOL) += file.o
OBJS-$(CONFIG_GOPHER_PROTOCOL) += gopher.o
......@@ -353,6 +354,7 @@ 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_RTMPS_PROTOCOL) += rtmpproto.o rtmppkt.o
OBJS-$(CONFIG_RTMPT_PROTOCOL) += rtmpproto.o rtmppkt.o
OBJS-$(CONFIG_RTMPTS_PROTOCOL) += rtmpproto.o rtmppkt.o
......
......@@ -247,6 +247,7 @@ void av_register_all(void)
#endif
REGISTER_PROTOCOL (CONCAT, concat);
REGISTER_PROTOCOL (CRYPTO, crypto);
REGISTER_PROTOCOL (FFRTMPCRYPT, ffrtmpcrypt);
REGISTER_PROTOCOL (FFRTMPHTTP, ffrtmphttp);
REGISTER_PROTOCOL (FILE, file);
REGISTER_PROTOCOL (GOPHER, gopher);
......@@ -259,6 +260,7 @@ void av_register_all(void)
REGISTER_PROTOCOL (MD5, md5);
REGISTER_PROTOCOL (PIPE, pipe);
REGISTER_PROTOCOL (RTMP, rtmp);
REGISTER_PROTOCOL (RTMPE, rtmpe);
REGISTER_PROTOCOL (RTMPS, rtmps);
REGISTER_PROTOCOL (RTMPT, rtmpt);
REGISTER_PROTOCOL (RTMPTS, rtmpts);
......
/*
* 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/blowfish.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/rc4.h"
#include "libavutil/xtea.h"
#include "internal.h"
#include "rtmp.h"
#include "rtmpdh.h"
#include "rtmpcrypt.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 const uint8_t rtmpe8_keys[16][16] = {
{ 0xbf, 0xf0, 0x34, 0xb2, 0x11, 0xd9, 0x08, 0x1f,
0xcc, 0xdf, 0xb7, 0x95, 0x74, 0x8d, 0xe7, 0x32 },
{ 0x08, 0x6a, 0x5e, 0xb6, 0x17, 0x43, 0x09, 0x0e,
0x6e, 0xf0, 0x5a, 0xb8, 0xfe, 0x5a, 0x39, 0xe2 },
{ 0x7b, 0x10, 0x95, 0x6f, 0x76, 0xce, 0x05, 0x21,
0x23, 0x88, 0xa7, 0x3a, 0x44, 0x01, 0x49, 0xa1 },
{ 0xa9, 0x43, 0xf3, 0x17, 0xeb, 0xf1, 0x1b, 0xb2,
0xa6, 0x91, 0xa5, 0xee, 0x17, 0xf3, 0x63, 0x39 },
{ 0x7a, 0x30, 0xe0, 0x0a, 0xb5, 0x29, 0xe2, 0x2c,
0xa0, 0x87, 0xae, 0xa5, 0xc0, 0xcb, 0x79, 0xac },
{ 0xbd, 0xce, 0x0c, 0x23, 0x2f, 0xeb, 0xde, 0xff,
0x1c, 0xfa, 0xae, 0x16, 0x11, 0x23, 0x23, 0x9d },
{ 0x55, 0xdd, 0x3f, 0x7b, 0x77, 0xe7, 0xe6, 0x2e,
0x9b, 0xb8, 0xc4, 0x99, 0xc9, 0x48, 0x1e, 0xe4 },
{ 0x40, 0x7b, 0xb6, 0xb4, 0x71, 0xe8, 0x91, 0x36,
0xa7, 0xae, 0xbf, 0x55, 0xca, 0x33, 0xb8, 0x39 },
{ 0xfc, 0xf6, 0xbd, 0xc3, 0xb6, 0x3c, 0x36, 0x97,
0x7c, 0xe4, 0xf8, 0x25, 0x04, 0xd9, 0x59, 0xb2 },
{ 0x28, 0xe0, 0x91, 0xfd, 0x41, 0x95, 0x4c, 0x4c,
0x7f, 0xb7, 0xdb, 0x00, 0xe3, 0xa0, 0x66, 0xf8 },
{ 0x57, 0x84, 0x5b, 0x76, 0x4f, 0x25, 0x1b, 0x03,
0x46, 0xd4, 0x5b, 0xcd, 0xa2, 0xc3, 0x0d, 0x29 },
{ 0x0a, 0xcc, 0xee, 0xf8, 0xda, 0x55, 0xb5, 0x46,
0x03, 0x47, 0x34, 0x52, 0x58, 0x63, 0x71, 0x3b },
{ 0xb8, 0x20, 0x75, 0xdc, 0xa7, 0x5f, 0x1f, 0xee,
0xd8, 0x42, 0x68, 0xe8, 0xa7, 0x2a, 0x44, 0xcc },
{ 0x07, 0xcf, 0x6e, 0x9e, 0xa1, 0x6d, 0x7b, 0x25,
0x9f, 0xa7, 0xae, 0x6c, 0xd9, 0x2f, 0x56, 0x29 },
{ 0xfe, 0xb1, 0xea, 0xe4, 0x8c, 0x8c, 0x3c, 0xe1,
0x4e, 0x00, 0x64, 0xa7, 0x6a, 0x38, 0x7c, 0x2a },
{ 0x89, 0x3a, 0x94, 0x27, 0xcc, 0x30, 0x13, 0xa2,
0xf1, 0x06, 0x38, 0x5b, 0xa8, 0x29, 0xf9, 0x27 }
};
static const uint8_t rtmpe9_keys[16][24] = {
{ 0x79, 0x34, 0x77, 0x4c, 0x67, 0xd1, 0x38, 0x3a, 0xdf, 0xb3, 0x56, 0xbe,
0x8b, 0x7b, 0xd0, 0x24, 0x38, 0xe0, 0x73, 0x58, 0x41, 0x5d, 0x69, 0x67, },
{ 0x46, 0xf6, 0xb4, 0xcc, 0x01, 0x93, 0xe3, 0xa1, 0x9e, 0x7d, 0x3c, 0x65,
0x55, 0x86, 0xfd, 0x09, 0x8f, 0xf7, 0xb3, 0xc4, 0x6f, 0x41, 0xca, 0x5c, },
{ 0x1a, 0xe7, 0xe2, 0xf3, 0xf9, 0x14, 0x79, 0x94, 0xc0, 0xd3, 0x97, 0x43,
0x08, 0x7b, 0xb3, 0x84, 0x43, 0x2f, 0x9d, 0x84, 0x3f, 0x21, 0x01, 0x9b, },
{ 0xd3, 0xe3, 0x54, 0xb0, 0xf7, 0x1d, 0xf6, 0x2b, 0x5a, 0x43, 0x4d, 0x04,
0x83, 0x64, 0x3e, 0x0d, 0x59, 0x2f, 0x61, 0xcb, 0xb1, 0x6a, 0x59, 0x0d, },
{ 0xc8, 0xc1, 0xe9, 0xb8, 0x16, 0x56, 0x99, 0x21, 0x7b, 0x5b, 0x36, 0xb7,
0xb5, 0x9b, 0xdf, 0x06, 0x49, 0x2c, 0x97, 0xf5, 0x95, 0x48, 0x85, 0x7e, },
{ 0xeb, 0xe5, 0xe6, 0x2e, 0xa4, 0xba, 0xd4, 0x2c, 0xf2, 0x16, 0xe0, 0x8f,
0x66, 0x23, 0xa9, 0x43, 0x41, 0xce, 0x38, 0x14, 0x84, 0x95, 0x00, 0x53, },
{ 0x66, 0xdb, 0x90, 0xf0, 0x3b, 0x4f, 0xf5, 0x6f, 0xe4, 0x9c, 0x20, 0x89,
0x35, 0x5e, 0xd2, 0xb2, 0xc3, 0x9e, 0x9f, 0x7f, 0x63, 0xb2, 0x28, 0x81, },
{ 0xbb, 0x20, 0xac, 0xed, 0x2a, 0x04, 0x6a, 0x19, 0x94, 0x98, 0x9b, 0xc8,
0xff, 0xcd, 0x93, 0xef, 0xc6, 0x0d, 0x56, 0xa7, 0xeb, 0x13, 0xd9, 0x30, },
{ 0xbc, 0xf2, 0x43, 0x82, 0x09, 0x40, 0x8a, 0x87, 0x25, 0x43, 0x6d, 0xe6,
0xbb, 0xa4, 0xb9, 0x44, 0x58, 0x3f, 0x21, 0x7c, 0x99, 0xbb, 0x3f, 0x24, },
{ 0xec, 0x1a, 0xaa, 0xcd, 0xce, 0xbd, 0x53, 0x11, 0xd2, 0xfb, 0x83, 0xb6,
0xc3, 0xba, 0xab, 0x4f, 0x62, 0x79, 0xe8, 0x65, 0xa9, 0x92, 0x28, 0x76, },
{ 0xc6, 0x0c, 0x30, 0x03, 0x91, 0x18, 0x2d, 0x7b, 0x79, 0xda, 0xe1, 0xd5,
0x64, 0x77, 0x9a, 0x12, 0xc5, 0xb1, 0xd7, 0x91, 0x4f, 0x96, 0x4c, 0xa3, },
{ 0xd7, 0x7c, 0x2a, 0xbf, 0xa6, 0xe7, 0x85, 0x7c, 0x45, 0xad, 0xff, 0x12,
0x94, 0xd8, 0xde, 0xa4, 0x5c, 0x3d, 0x79, 0xa4, 0x44, 0x02, 0x5d, 0x22, },
{ 0x16, 0x19, 0x0d, 0x81, 0x6a, 0x4c, 0xc7, 0xf8, 0xb8, 0xf9, 0x4e, 0xcd,
0x2c, 0x9e, 0x90, 0x84, 0xb2, 0x08, 0x25, 0x60, 0xe1, 0x1e, 0xae, 0x18, },
{ 0xe9, 0x7c, 0x58, 0x26, 0x1b, 0x51, 0x9e, 0x49, 0x82, 0x60, 0x61, 0xfc,
0xa0, 0xa0, 0x1b, 0xcd, 0xf5, 0x05, 0xd6, 0xa6, 0x6d, 0x07, 0x88, 0xa3, },
{ 0x2b, 0x97, 0x11, 0x8b, 0xd9, 0x4e, 0xd9, 0xdf, 0x20, 0xe3, 0x9c, 0x10,
0xe6, 0xa1, 0x35, 0x21, 0x11, 0xf9, 0x13, 0x0d, 0x0b, 0x24, 0x65, 0xb2, },
{ 0x53, 0x6a, 0x4c, 0x54, 0xac, 0x8b, 0x9b, 0xb8, 0x97, 0x29, 0xfc, 0x60,
0x2c, 0x5b, 0x3a, 0x85, 0x68, 0xb5, 0xaa, 0x6a, 0x44, 0xcd, 0x3f, 0xa7, },
};
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 = ff_rtmp_calc_digest_pos(buf, 768, 632, 8);
if (offset < 0)
return offset;
/* generate a Diffie-Hellmann public key */
if ((ret = ff_dh_generate_public_key(rt->dh)) < 0)
return ret;
/* write the public key into the handshake buffer */
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, int type)
{
RTMPEContext *rt = h->priv_data;
uint8_t secret_key[128], digest[32];
int server_pos, client_pos;
int ret;
if (type) {
if ((server_pos = ff_rtmp_calc_digest_pos(serverdata, 1532, 632, 772)) < 0)
return server_pos;
} else {
if ((server_pos = ff_rtmp_calc_digest_pos(serverdata, 768, 632, 8)) < 0)
return server_pos;
}
if ((client_pos = ff_rtmp_calc_digest_pos(clientdata, 768, 632, 8)) < 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;
}
static void rtmpe8_sig(const uint8_t *in, uint8_t *out, int key_id)
{
struct AVXTEA ctx;
av_xtea_init(&ctx, rtmpe8_keys[key_id]);
av_xtea_crypt(&ctx, out, in, 1, NULL, 0);
}
static void rtmpe9_sig(const uint8_t *in, uint8_t *out, int key_id)
{
struct AVBlowfish ctx;
uint32_t xl, xr;
xl = AV_RL32(in);
xr = AV_RL32(in + 4);
av_blowfish_init(&ctx, rtmpe9_keys[key_id], 24);
av_blowfish_crypt_ecb(&ctx, &xl, &xr, 0);
AV_WL32(out, xl);
AV_WL32(out + 4, xr);
}
void ff_rtmpe_encrypt_sig(URLContext *h, uint8_t *sig, const uint8_t *digest,
int type)
{
int i;
for (i = 0; i < 32; i += 8) {
if (type == 8) {
/* RTMPE type 8 uses XTEA on the signature */
rtmpe8_sig(sig + i, sig + i, digest[i] % 15);
} else if (type == 9) {
/* RTMPE type 9 uses Blowfish on the signature */
rtmpe9_sig(sig + i, sig + i, digest[i] % 15);
}
}
}
int ff_rtmpe_update_keystream(URLContext *h)
{
RTMPEContext *rt = h->priv_data;
char buf[RTMP_HANDSHAKE_PACKET_SIZE];
/* skip past 1536 bytes of the RC4 bytestream */
av_rc4_crypt(&rt->key_in, buf, NULL, sizeof(buf), NULL, 1);
av_rc4_crypt(&rt->key_out, buf, NULL, sizeof(buf), NULL, 1);
/* the next requests will be encrypted using RC4 keys */
rt->handshaked = 1;
return 0;
}
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;
rt->stream->flags |= h->flags & AVIO_FLAG_NONBLOCK;
ret = ffurl_read(rt->stream, buf, size);
rt->stream->flags &= ~AVIO_FLAG_NONBLOCK;
if (ret < 0 && ret != AVERROR_EOF)
return ret;
if (rt->handshaked && ret > 0) {
/* decrypt data received by the server */
av_rc4_crypt(&rt->key_in, buf, buf, ret, NULL, 1);
}
return ret;
}
static int rtmpe_write(URLContext *h, const uint8_t *buf, int size)
{
RTMPEContext *rt = h->priv_data;
int ret;
if (rt->handshaked) {
/* encrypt data to send to the server */
av_rc4_crypt(&rt->key_out, buf, buf, size, NULL, 1);
}
if ((ret = ffurl_write(rt->stream, buf, size)) < 0)
return ret;
return size;
}
URLProtocol ff_ffrtmpcrypt_protocol = {
.name = "ffrtmpcrypt",
.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,
};
/*
* 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_RTMPCRYPT_H
#define AVFORMAT_RTMPCRYPT_H
#include <stdint.h>
#include "url.h"
/**
* Initialize the Diffie-Hellmann context and generate the public key.
*
* @param h an URLContext
* @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 h an URLContext
* @param serverdata server data (1536 bytes)
* @param clientdata client data (1536 bytes)
* @param type the position of the server digest
* @return zero on success, negative value otherwise
*/
int ff_rtmpe_compute_secret_key(URLContext *h, const uint8_t *serverdata,
const uint8_t *clientdata, int type);
/**
* Encrypt the signature.
*
* @param h an URLContext
* @param signature the signature to encrypt
* @param digest the digest used for finding the encryption key
* @param type type of encryption (8 for XTEA, 9 for Blowfish)
*/
void ff_rtmpe_encrypt_sig(URLContext *h, uint8_t *signature,
const uint8_t *digest, int type);
/**
* Update the keystream and set RC4 keys for encryption.
*
* @param h an URLContext
* @return zero on success, negative value otherwise
*/
int ff_rtmpe_update_keystream(URLContext *h);
#endif /* AVFORMAT_RTMPCRYPT_H */
/*
* 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 "config.h"
#include "rtmpdh.h"
#define P1024 \
"FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1" \
"29024E088A67CC74020BBEA63B139B22514A08798E3404DD" \
"EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245" \
"E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED" \
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381" \
"FFFFFFFFFFFFFFFF"
#define Q1024 \
"7FFFFFFFFFFFFFFFE487ED5110B4611A62633145C06E0E68" \
"948127044533E63A0105DF531D89CD9128A5043CC71A026E" \
"F7CA8CD9E69D218D98158536F92F8A1BA7F09AB6B6A8E122" \
"F242DABB312F3F637A262174D31BF6B585FFAE5B7A035BF6" \
"F71C35FDAD44CFD2D74F9208BE258FF324943328F67329C0" \
"FFFFFFFFFFFFFFFF"
#if CONFIG_NETTLE || CONFIG_GCRYPT
#if CONFIG_NETTLE
#define bn_new(bn) \
do { \
bn = av_malloc(sizeof(*bn)); \
if (bn) \
mpz_init2(bn, 1); \
} while (0)
#define bn_free(bn) \
do { \
mpz_clear(bn); \
av_free(bn); \
} while (0)
#define bn_set_word(bn, w) mpz_set_ui(bn, w)
#define bn_cmp(a, b) mpz_cmp(a, b)
#define bn_copy(to, from) mpz_set(to, from)
#define bn_sub_word(bn, w) mpz_sub_ui(bn, bn, w)
#define bn_cmp_1(bn) mpz_cmp_ui(bn, 1)
#define bn_num_bytes(bn) (mpz_sizeinbase(bn, 2) + 7) / 8
#define bn_bn2bin(bn, buf, len) nettle_mpz_get_str_256(len, buf, bn)
#define bn_bin2bn(bn, buf, len) \
do { \
bn_new(bn); \
if (bn) \
nettle_mpz_set_str_256_u(bn, len, buf); \
} while (0)
#define bn_hex2bn(bn, buf, ret) \
do { \
bn_new(bn); \
if (bn) \
ret = (mpz_set_str(bn, buf, 16) == 0); \
} while (0)
#define bn_modexp(bn, y, q, p) mpz_powm(bn, y, q, p)
#define bn_random(bn, num_bytes) mpz_random(bn, num_bytes);
#elif CONFIG_GCRYPT
#define bn_new(bn) bn = gcry_mpi_new(1)
#define bn_free(bn) gcry_mpi_release(bn)
#define bn_set_word(bn, w) gcry_mpi_set_ui(bn, w)
#define bn_cmp(a, b) gcry_mpi_cmp(a, b)
#define bn_copy(to, from) gcry_mpi_set(to, from)
#define bn_sub_word(bn, w) gcry_mpi_sub_ui(bn, bn, w)
#define bn_cmp_1(bn) gcry_mpi_cmp_ui(bn, 1)
#define bn_num_bytes(bn) (gcry_mpi_get_nbits(bn) + 7) / 8
#define bn_bn2bin(bn, buf, len) gcry_mpi_print(GCRYMPI_FMT_USG, buf, len, NULL, bn)
#define bn_bin2bn(bn, buf, len) gcry_mpi_scan(&bn, GCRYMPI_FMT_USG, buf, len, NULL)
#define bn_hex2bn(bn, buf, ret) ret = (gcry_mpi_scan(&bn, GCRYMPI_FMT_HEX, buf, 0, 0) == 0)
#define bn_modexp(bn, y, q, p) gcry_mpi_powm(bn, y, q, p)
#define bn_random(bn, num_bytes) gcry_mpi_randomize(bn, num_bytes, GCRY_WEAK_RANDOM)
#endif
#define MAX_BYTES 18000
#define dh_new() av_malloc(sizeof(FF_DH))
static FFBigNum dh_generate_key(FF_DH *dh)
{
int num_bytes;
num_bytes = bn_num_bytes(dh->p) - 1;
if (num_bytes <= 0 || num_bytes > MAX_BYTES)
return NULL;
bn_new(dh->priv_key);
if (!dh->priv_key)
return NULL;
bn_random(dh->priv_key, num_bytes);
bn_new(dh->pub_key);
if (!dh->pub_key) {
bn_free(dh->priv_key);
return NULL;
}
bn_modexp(dh->pub_key, dh->g, dh->priv_key, dh->p);
return dh->pub_key;
}
static int dh_compute_key(FF_DH *dh, FFBigNum pub_key_bn,
uint32_t pub_key_len, uint8_t *secret_key)
{
FFBigNum k;
int num_bytes;
num_bytes = bn_num_bytes(dh->p);
if (num_bytes <= 0 || num_bytes > MAX_BYTES)
return -1;
bn_new(k);
if (!k)
return -1;
bn_modexp(k, pub_key_bn, dh->priv_key, dh->p);
bn_bn2bin(k, secret_key, pub_key_len);
bn_free(k);
/* return the length of the shared secret key like DH_compute_key */
return pub_key_len;
}
void ff_dh_free(FF_DH *dh)
{
bn_free(dh->p);
bn_free(dh->g);
bn_free(dh->pub_key);
bn_free(dh->priv_key);
av_free(dh);
}
#elif CONFIG_OPENSSL
#define bn_new(bn) bn = BN_new()
#define bn_free(bn) BN_free(bn)
#define bn_set_word(bn, w) BN_set_word(bn, w)
#define bn_cmp(a, b) BN_cmp(a, b)
#define bn_copy(to, from) BN_copy(to, from)
#define bn_sub_word(bn, w) BN_sub_word(bn, w)
#define bn_cmp_1(bn) BN_cmp(bn, BN_value_one())
#define bn_num_bytes(bn) BN_num_bytes(bn)
#define bn_bn2bin(bn, buf, len) BN_bn2bin(bn, buf)
#define bn_bin2bn(bn, buf, len) bn = BN_bin2bn(buf, len, 0)
#define bn_hex2bn(bn, buf, ret) ret = BN_hex2bn(&bn, buf)
#define bn_modexp(bn, y, q, p) \
do { \
BN_CTX *ctx = BN_CTX_new(); \
if (!ctx) \
return AVERROR(ENOMEM); \
if (!BN_mod_exp(bn, y, q, p, ctx)) { \
BN_CTX_free(ctx); \
return AVERROR(EINVAL); \
} \
BN_CTX_free(ctx); \
} while (0)
#define dh_new() DH_new()
#define dh_generate_key(dh) DH_generate_key(dh)
#define dh_compute_key(dh, pub, len, secret) DH_compute_key(secret, pub, dh)
void ff_dh_free(FF_DH *dh)
{
DH_free(dh);
}
#endif
static int dh_is_valid_public_key(FFBigNum y, FFBigNum p, FFBigNum q)
{
FFBigNum bn = NULL;
int ret = AVERROR(EINVAL);
bn_new(bn);
if (!bn)
return AVERROR(ENOMEM);
/* y must lie in [2, p - 1] */
bn_set_word(bn, 1);
if (!bn_cmp(y, bn))
goto fail;
/* bn = p - 2 */
bn_copy(bn, p);
bn_sub_word(bn, 1);
if (!bn_cmp(y, bn))
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 */
bn_modexp(bn, y, q, p);
if (bn_cmp_1(bn))
goto fail;
ret = 0;
fail:
bn_free(bn);
return ret;
}
av_cold FF_DH *ff_dh_init(int key_len)
{
FF_DH *dh;
int ret;
if (!(dh = dh_new()))
return NULL;
bn_new(dh->g);
if (!dh->g)
goto fail;
bn_hex2bn(dh->p, P1024, ret);
if (!ret)
goto fail;
bn_set_word(dh->g, 2);
dh->length = key_len;
return dh;
fail:
ff_dh_free(dh);
return NULL;
}
int ff_dh_generate_public_key(FF_DH *dh)
{
int ret = 0;
while (!ret) {
FFBigNum q1 = NULL;
if (!dh_generate_key(dh))
return AVERROR(EINVAL);
bn_hex2bn(q1, Q1024, ret);
if (!ret)
return AVERROR(ENOMEM);
ret = dh_is_valid_public_key(dh->pub_key, dh->p, q1);
bn_free(q1);
if (!ret) {
/* the public key is valid */
break;
}
}
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 = 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);
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_key)
{
FFBigNum q1 = NULL, pub_key_bn = NULL;
int ret;
/* convert the big-endian form of the public key into a bignum */
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 */
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 = dh_compute_key(dh, pub_key_bn, pub_key_len,
secret_key)) < 0) {
ret = AVERROR(EINVAL);
goto fail;
}
fail:
bn_free(pub_key_bn);
bn_free(q1);
return ret;
}
/*
* 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 "avformat.h"
#include "config.h"
#if CONFIG_NETTLE || CONFIG_GCRYPT
#if CONFIG_NETTLE
#include <gmp.h>
#include <nettle/bignum.h>
typedef mpz_ptr FFBigNum;
#elif CONFIG_GCRYPT
#include <gcrypt.h>
typedef gcry_mpi_t FFBigNum;
#endif
typedef struct FF_DH {
FFBigNum p;
FFBigNum g;
FFBigNum pub_key;
FFBigNum priv_key;
long length;
} FF_DH;
#elif CONFIG_OPENSSL
#include <openssl/bn.h>
#include <openssl/dh.h>
typedef BIGNUM *FFBigNum;
typedef DH FF_DH;
#endif
/**
* Initialize a Diffie-Hellmann context.
*
* @param key_len length of the key
* @return a new Diffie-Hellmann context on success, NULL otherwise
*/
FF_DH *ff_dh_init(int key_len);
/**
* Free a Diffie-Hellmann context.
*
* @param dh a Diffie-Hellmann context to free
*/
void ff_dh_free(FF_DH *dh);
/**
* Generate a public key.
*
* @param dh a Diffie-Hellmann context
* @return zero on success, negative value otherwise
*/
int ff_dh_generate_public_key(FF_DH *dh);
/**
* Write the public key into the given buffer.
*
* @param dh a Diffie-Hellmann context, containing the public key to write
* @param pub_key the buffer where the public key is written
* @param pub_key_len the length of the buffer
* @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 a Diffie-Hellmann context, containing the private key
* @param pub_key the buffer containing the public key
* @param pub_key_len the length of the buffer
* @param secret_key the buffer where the secret key is written
* @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_key);
#endif /* AVFORMAT_RTMPDH_H */
......@@ -37,6 +37,7 @@
#include "flv.h"
#include "rtmp.h"
#include "rtmpcrypt.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
......@@ -649,13 +651,17 @@ int ff_rtmp_calc_digest_pos(const uint8_t *buf, int off, int mod_val,
* will be stored) into that packet.
*
* @param buf handshake data (1536 bytes)
* @param encrypted use an encrypted connection (RTMPE)
* @return offset to the digest inside input data
*/
static int rtmp_handshake_imprint_with_digest(uint8_t *buf)
static int rtmp_handshake_imprint_with_digest(uint8_t *buf, int encrypted)
{
int ret, digest_pos;
digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12);
if (encrypted)
digest_pos = ff_rtmp_calc_digest_pos(buf, 772, 728, 776);
else
digest_pos = ff_rtmp_calc_digest_pos(buf, 8, 728, 12);
ret = ff_rtmp_calc_digest(buf, RTMP_HANDSHAKE_PACKET_SIZE, digest_pos,
rtmp_player_key, PLAYER_KEY_OPEN_PART_LEN,
......@@ -712,8 +718,9 @@ 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];
int ret;
uint8_t digest[32], signature[32];
int encrypted = rt->encrypted && CONFIG_FFRTMPCRYPT_PROTOCOL;
int ret, type = 0;
av_log(s, AV_LOG_DEBUG, "Handshaking...\n");
......@@ -721,7 +728,24 @@ 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;
client_pos = rtmp_handshake_imprint_with_digest(tosend + 1);
if (encrypted) {
/* 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;
}
client_pos = rtmp_handshake_imprint_with_digest(tosend + 1, encrypted);
if (client_pos < 0)
return client_pos;
......@@ -743,6 +767,7 @@ 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]);
......@@ -752,6 +777,7 @@ static int rtmp_handshake(URLContext *s, RTMPContext *rt)
return server_pos;
if (!server_pos) {
type = 1;
server_pos = rtmp_validate_digest(serverdata + 1, 8);
if (server_pos < 0)
return server_pos;
......@@ -769,11 +795,22 @@ 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);
0, digest, 32, signature);
if (ret < 0)
return ret;
if (memcmp(digest, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) {
if (encrypted) {
/* 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, type)) < 0)
return ret;
/* Encrypt the signature received by the server. */
ff_rtmpe_encrypt_sig(rt->stream, signature, digest, serverdata[0]);
}
if (memcmp(signature, clientdata + RTMP_HANDSHAKE_PACKET_SIZE - 32, 32)) {
av_log(s, AV_LOG_ERROR, "Signature mismatch\n");
return AVERROR(EIO);
}
......@@ -792,14 +829,47 @@ static int rtmp_handshake(URLContext *s, RTMPContext *rt)
if (ret < 0)
return ret;
if (encrypted) {
/* Encrypt the signature to be send to the server. */
ff_rtmpe_encrypt_sig(rt->stream, tosend +
RTMP_HANDSHAKE_PACKET_SIZE - 32, digest,
serverdata[0]);
}
// write reply back to the server
if ((ret = ffurl_write(rt->stream, tosend,
RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
return ret;
if (encrypted) {
/* Set RC4 keys for encryption and update the keystreams. */
if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
return ret;
}
} else {
if (encrypted) {
/* 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, 1)) < 0)
return ret;
if (serverdata[0] == 9) {
/* Encrypt the signature received by the server. */
ff_rtmpe_encrypt_sig(rt->stream, signature, digest,
serverdata[0]);
}
}
if ((ret = ffurl_write(rt->stream, serverdata + 1,
RTMP_HANDSHAKE_PACKET_SIZE)) < 0)
return ret;
if (encrypted) {
/* Set RC4 keys for encryption and update the keystreams. */
if ((ret = ff_rtmpe_update_keystream(rt->stream)) < 0)
return ret;
}
}
return 0;
......@@ -1122,6 +1192,10 @@ static int rtmp_open(URLContext *s, const char *uri, int flags)
if (port < 0)
port = RTMPS_DEFAULT_PORT;
ff_url_join(buf, sizeof(buf), "tls", NULL, hostname, port, NULL);
} else if (!strcmp(proto, "rtmpe")) {
/* open the encrypted connection */
ff_url_join(buf, sizeof(buf), "ffrtmpcrypt", NULL, hostname, port, NULL);
rt->encrypted = 1;
} else {
/* open the tcp connection */
if (port < 0)
......@@ -1446,6 +1520,24 @@ URLProtocol ff_rtmp_protocol = {
.priv_data_class= &rtmp_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,
};
static const AVClass rtmps_class = {
.class_name = "rtmps",
.item_name = av_default_item_name,
......
......@@ -30,7 +30,7 @@
#include "libavutil/avutil.h"
#define LIBAVFORMAT_VERSION_MAJOR 54
#define LIBAVFORMAT_VERSION_MINOR 10
#define LIBAVFORMAT_VERSION_MINOR 11
#define LIBAVFORMAT_VERSION_MICRO 0
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment