Add support for RTP packet authentication

By default authentication is disabled for RTP but in can be enabled
by giving RCE_SRTP_AUTHENTICATE_RTP when creating a media stream.

When security layer gets a packet, outgoing or incoming, it will add
an authentication tag to the packet or verify the tag, respectively.

This implementation is not perfect. The largest issue is
that there is no proper packet dispatcher so late ZRTP packets
can cause some real trouble.

This and a few other issues will be addresses later on when the
architecture of packet reception and the whole socket layer is improved.
This commit is contained in:
Aaro Altonen 2020-07-31 09:56:06 +03:00
parent 7346b95c2f
commit 693c5331c3
5 changed files with 116 additions and 29 deletions

View File

@ -23,6 +23,7 @@
#define AES_KEY_LENGTH 16 /* 128 bits */
#define HMAC_KEY_LENGTH 32 /* 256 bits */
#define SALT_LENGTH 14 /* 112 bits */
#define AUTH_TAG_LENGTH 8
namespace uvg_rtp {
@ -141,10 +142,14 @@ namespace uvg_rtp {
rtp_error_t encrypt(std::vector<std::pair<size_t, uint8_t *>>& buffers);
/* Decrypt the payload payload of "frame" using the private session key
*
* If RTP packet authentication has been enabled during stream creation,
* decrypt() authenticates the received packet.
*
* Return RTP_OK on success
* Return RTP_INVALID_VALUE if "frame" is nullptr
* Return RTP_NOT_INITIALIZED if SRTP has not been initialized */
* Return RTP_NOT_INITIALIZED if SRTP has not been initialized
* Return RTP_AUTH_TAG_MISMATCH if authentication tags do not match */
rtp_error_t decrypt(uint8_t *buffer, size_t len);
/* Authenticate "frame" using the private session key
@ -177,5 +182,11 @@ namespace uvg_rtp {
/* If NULL cipher is enabled, it means that RTP packets are not
* encrypted but other security mechanisms described in RFC 3711 may be used */
bool use_null_cipher_;
/* By default RTP packet authentication is disabled but by
* giving RCE_SRTP_AUTHENTICATE_RTP to create_stream() user can enable it.
*
* The authentication tag will occupy the last 8 bytes of the RTP packet */
bool authenticate_rtp_;
};
};

View File

@ -36,22 +36,23 @@ const int MAX_PACKET = 65536;
const int MAX_PAYLOAD = 1443;
typedef enum RTP_ERROR {
RTP_INTERRUPTED = 2,
RTP_NOT_READY = 1,
RTP_OK = 0,
RTP_GENERIC_ERROR = -1,
RTP_SOCKET_ERROR = -2,
RTP_BIND_ERROR = -3,
RTP_INVALID_VALUE = -4,
RTP_SEND_ERROR = -5,
RTP_MEMORY_ERROR = -6,
RTP_SSRC_COLLISION = -7,
RTP_INITIALIZED = -8, /* object already initialized */
RTP_NOT_INITIALIZED = -9, /* object has not been initialized */
RTP_NOT_SUPPORTED = -10, /* method/version/extension not supported */
RTP_RECV_ERROR = -11, /* recv(2) or one of its derivatives failed */
RTP_TIMEOUT = -12, /* operation timed out */
RTP_NOT_FOUND = -13, /* object not found */
RTP_INTERRUPTED = 2,
RTP_NOT_READY = 1,
RTP_OK = 0,
RTP_GENERIC_ERROR = -1,
RTP_SOCKET_ERROR = -2,
RTP_BIND_ERROR = -3,
RTP_INVALID_VALUE = -4,
RTP_SEND_ERROR = -5,
RTP_MEMORY_ERROR = -6,
RTP_SSRC_COLLISION = -7,
RTP_INITIALIZED = -8, /* object already initialized */
RTP_NOT_INITIALIZED = -9, /* object has not been initialized */
RTP_NOT_SUPPORTED = -10, /* method/version/extension not supported */
RTP_RECV_ERROR = -11, /* recv(2) or one of its derivatives failed */
RTP_TIMEOUT = -12, /* operation timed out */
RTP_NOT_FOUND = -13, /* object not found */
RTP_AUTH_TAG_MISMATCH = -14, /* authentication tag does not match the RTP packet contents */
} rtp_error_t;
typedef enum RTP_FORMAT {
@ -181,7 +182,15 @@ enum RTP_CTX_ENABLE_FLAGS {
/* Disable RTP payload encryption */
RCE_SRTP_NULL_CIPHER = 1 << 12,
RCE_LAST = 1 << 13,
/* Enable RTP packet authentication
*
* This flag forces the security layer to add authentication tag
* to each outgoing RTP packet for all streams that have SRTP enabled.
*
* NOTE: this flag must be coupled with at least RCE_SRTP */
RCE_SRTP_AUTHENTICATE_RTP = 1 << 13,
RCE_LAST = 1 << 14,
};
/* These options are given to configuration() */

View File

@ -178,6 +178,9 @@ rtp_error_t uvg_rtp::media_stream::init(uvg_rtp::zrtp *zrtp)
return ret;
}
if (ctx_config_.flags & RCE_SRTP_AUTHENTICATE_RTP)
rtp_->set_payload_size(MAX_PAYLOAD - AUTH_TAG_LENGTH);
socket_.set_srtp(srtp_);
sender_ = new uvg_rtp::sender(socket_, ctx_config_, fmt_, rtp_);
@ -218,6 +221,9 @@ rtp_error_t uvg_rtp::media_stream::add_srtp_ctx(uint8_t *key, uint8_t *salt)
return ret;
}
if (ctx_config_.flags & RCE_SRTP_AUTHENTICATE_RTP)
rtp_->set_payload_size(MAX_PAYLOAD - AUTH_TAG_LENGTH);
socket_.set_srtp(srtp_);
sender_ = new uvg_rtp::sender(socket_, ctx_config_, fmt_, rtp_);

View File

@ -245,6 +245,11 @@ rtp_error_t uvg_rtp::socket::__sendtov(
return RTP_SEND_ERROR;
}
#ifdef __RTP_CRYPTO__
if (srtp_ && flags_ & RCE_SRTP_AUTHENTICATE_RTP)
delete buffers.at(buffers.size() - 1).second;
#endif
set_bytes(bytes_sent, sent_bytes);
return RTP_OK;

View File

@ -10,7 +10,8 @@
uvg_rtp::srtp::srtp():
srtp_ctx_(),
use_null_cipher_(false)
use_null_cipher_(false),
authenticate_rtp_(false)
{
}
@ -76,6 +77,7 @@ rtp_error_t uvg_rtp::srtp::__init(int type, int flags)
srtp_ctx_.replay = nullptr;
use_null_cipher_ = !!(flags & RCE_SRTP_NULL_CIPHER);
authenticate_rtp_ = !!(flags & RCE_SRTP_AUTHENTICATE_RTP);
/* Local aka encryption keys */
(void)derive_key(
@ -192,15 +194,44 @@ rtp_error_t uvg_rtp::srtp::__encrypt(uint32_t ssrc, uint16_t seq, uint8_t *buffe
rtp_error_t uvg_rtp::srtp::encrypt(uvg_rtp::frame::rtp_frame *frame)
{
return __encrypt(ntohl(frame->header.ssrc), ntohs(frame->header.seq), frame->payload, frame->payload_len);
/* TODO: authentication for complete RTP frame requires co-operation
* with the allocator of that frame -> no good solution for that right now */
return __encrypt(
ntohl(frame->header.ssrc),
ntohs(frame->header.seq),
frame->payload,
frame->payload_len
);
}
rtp_error_t uvg_rtp::srtp::encrypt(std::vector<std::pair<size_t, uint8_t *>>& buffers)
{
auto frame = (uvg_rtp::frame::rtp_frame *)buffers.at(0).second;
auto rtp = buffers.at(buffers.size() - 1);
auto frame = (uvg_rtp::frame::rtp_frame *)buffers.at(0).second;
auto rtp = buffers.at(buffers.size() - 1);
uint64_t *digest = new uint64_t;
return __encrypt(ntohl(frame->header.ssrc), ntohs(frame->header.seq), rtp.second, rtp.first);
rtp_error_t ret = __encrypt(
ntohl(frame->header.ssrc),
ntohs(frame->header.seq),
rtp.second,
rtp.first
);
if (!authenticate_rtp_)
return ret;
/* create authentication tag for the packet and push it to the vector buffer */
auto hmac_sha1 = uvg_rtp::crypto::hmac::sha1(key_ctx_.local.auth_key, AES_KEY_LENGTH);
for (auto& buffer : buffers)
hmac_sha1.update((uint8_t *)buffer.second, buffer.first);
hmac_sha1.update((uint8_t *)&srtp_ctx_.roc, sizeof(srtp_ctx_.roc));
hmac_sha1.final((uint8_t *)digest);
buffers.push_back(std::make_pair(sizeof(uint64_t), (uint8_t *)digest));
return ret;
}
rtp_error_t uvg_rtp::srtp::decrypt(uint8_t *buffer, size_t len)
@ -208,11 +239,12 @@ rtp_error_t uvg_rtp::srtp::decrypt(uint8_t *buffer, size_t len)
if (use_null_cipher_)
return RTP_OK;
uint8_t iv[16] = { 0 };
auto hdr = (uvg_rtp::frame::rtp_header *)buffer;
uint16_t seq = ntohs(hdr->seq);
uint32_t ssrc = ntohl(hdr->ssrc);
uint64_t index = (((uint64_t)srtp_ctx_.roc) << 16) + seq;
uint8_t iv[16] = { 0 };
auto hdr = (uvg_rtp::frame::rtp_header *)buffer;
uint16_t seq = ntohs(hdr->seq);
uint32_t ssrc = ntohl(hdr->ssrc);
uint64_t index = (((uint64_t)srtp_ctx_.roc) << 16) + seq;
uint64_t digest = 0;
/* Sequence number has wrapped around, update Roll-over Counter */
if (seq == 0xffff)
@ -225,8 +257,32 @@ rtp_error_t uvg_rtp::srtp::decrypt(uint8_t *buffer, size_t len)
uint8_t *payload = buffer + sizeof(uvg_rtp::frame::rtp_header);
uvg_rtp::crypto::aes::ctr ctr(key_ctx_.remote.enc_key, sizeof(key_ctx_.remote.enc_key), iv);
ctr.decrypt(payload, payload, len - sizeof(uvg_rtp::frame::rtp_header));
/* exit early if RTP packet authentication is disabled... */
if (!authenticate_rtp_) {
ctr.decrypt(payload, payload, len - sizeof(uvg_rtp::frame::rtp_header));
return RTP_OK;
}
/* ... otherwise calculate authentication tag for the packet
* and compare it against the one we received */
auto hmac_sha1 = uvg_rtp::crypto::hmac::sha1(key_ctx_.remote.auth_key, AES_KEY_LENGTH);
hmac_sha1.update((uint8_t *)buffer, len - AUTH_TAG_LENGTH);
hmac_sha1.update((uint8_t *)&srtp_ctx_.roc, sizeof(srtp_ctx_.roc));
hmac_sha1.final((uint8_t *)&digest);
if (memcmp(&digest, &buffer[len - AUTH_TAG_LENGTH], AUTH_TAG_LENGTH)) {
LOG_ERROR("Authentication tag mismatch!");
return RTP_AUTH_TAG_MISMATCH;
}
size_t size_ = len - sizeof(uvg_rtp::frame::rtp_header) - 8;
uint8_t *new_buffer = new uint8_t[size_];
ctr.decrypt(new_buffer, payload, size_);
memset(payload, 0, len);
memcpy(payload, new_buffer, size_);
return RTP_OK;
}
#endif