- improved SRTP support

This commit is contained in:
Dmytro Bogovych 2025-08-25 17:30:28 +03:00
parent e6cb2a22f7
commit d4a47807d8
9 changed files with 267 additions and 105 deletions

View File

@ -203,6 +203,7 @@ set (RTPHONE_SOURCES
${E}/helper/HL_Time.cpp ${E}/helper/HL_Time.cpp
${E}/helper/HL_Time.h ${E}/helper/HL_Time.h
${E}/helper/HL_Types.h ${E}/helper/HL_Types.h
${E}/helper/HL_Types.cpp
${E}/helper/HL_Usb.cpp ${E}/helper/HL_Usb.cpp
${E}/helper/HL_Usb.h ${E}/helper/HL_Usb.h
${E}/helper/HL_Uuid.cpp ${E}/helper/HL_Uuid.cpp

View File

@ -303,26 +303,12 @@ std::string AudioProvider::createCryptoAttribute(SrtpSuite suite)
if (!mActiveStream) if (!mActiveStream)
return ""; return "";
// Use tag 1 - it is ok, as we use only single crypto attribute
int srtpTag = 1;
// Print key to base64 string // Print key to base64 string
PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first; PByteBuffer keyBuffer = mActiveStream->srtp().outgoingKey(suite).first;
resip::Data d(keyBuffer->data(), keyBuffer->size()); resip::Data d(keyBuffer->data(), keyBuffer->size());
resip::Data keyText = d.base64encode(); resip::Data keyText = d.base64encode();
// Create "crypto" attribute value return std::format("{} {} inline:{}", 1, toString(suite), keyText.c_str());
char buffer[512];
const char* suiteName = NULL;
switch (suite)
{
case SRTP_AES_128_AUTH_80: suiteName = SRTP_SUITE_NAME_1; break;
case SRTP_AES_256_AUTH_80: suiteName = SRTP_SUITE_NAME_2; break;
default: assert(0);
}
sprintf(buffer, "%d %s inline:%s", srtpTag, suiteName, keyText.c_str());
return buffer;
} }
SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key) SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBuffer& key)
@ -343,15 +329,7 @@ SrtpSuite AudioProvider::processCryptoAttribute(const resip::Data& value, ByteBu
resip::Data rawkey = keyText.base64decode(); resip::Data rawkey = keyText.base64decode();
key = ByteBuffer(rawkey.c_str(), rawkey.size()); key = ByteBuffer(rawkey.c_str(), rawkey.size());
// Open srtp return toSrtpSuite(suite);
SrtpSuite result = SRTP_NONE;
if (strcmp(suite, SRTP_SUITE_NAME_1) == 0)
result = SRTP_AES_128_AUTH_80;
else
if (strcmp(suite, SRTP_SUITE_NAME_2) == 0)
result = SRTP_AES_256_AUTH_80;
return result;
} }
void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs) void AudioProvider::findRfc2833(const resip::SdpContents::Session::Medium::CodecContainer& codecs)

View File

@ -0,0 +1 @@
#include "HL_Types.h"

View File

@ -1,4 +1,4 @@
/* Copyright(C) 2007-2014 VoIP objects (voipobjects.com) /* Copyright(C) 2007-2025 VoIP objects (voipobjects.com)
* This Source Code Form is subject to the terms of the Mozilla Public * This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -26,4 +26,135 @@ enum SdpDirection
Sdp_Offer Sdp_Offer
}; };
#include <unordered_map>
#include <utility>
#include <stdexcept>
#include <map>
template<
class K, class V,
class HashK = std::hash<K>, class EqK = std::equal_to<K>,
class HashV = std::hash<V>, class EqV = std::equal_to<V>
>
class BiMap {
public:
using key_type = K;
using mapped_type = V;
BiMap(const std::map<K,V>& initializers) {
for (const auto& item: initializers) {
insert(item.first, item.second);
}
}
// Insert a new (key, value) pair. Returns false if either key or value already exists.
bool insert(const K& k, const V& v) {
if (contains_key(k) || contains_value(v)) return false;
auto ok = forward_.emplace(k, v);
try {
auto ov = reverse_.emplace(v, k);
if (!ov.second) { // shouldn't happen given the guard above
forward_.erase(k);
return false;
}
} catch (...) {
forward_.erase(k);
throw;
}
return ok.second;
}
bool insert(K&& k, V&& v) {
if (contains_key(k) || contains_value(v)) return false;
auto ok = forward_.emplace(std::move(k), std::move(v));
try {
auto ov = reverse_.emplace(ok.first->second, ok.first->first); // use stored refs
if (!ov.second) {
forward_.erase(ok.first);
return false;
}
} catch (...) {
forward_.erase(ok.first);
throw;
}
return ok.second;
}
// Replace value for existing key (and update reverse map). Returns false if value is already bound elsewhere.
bool replace_by_key(const K& k, const V& new_v) {
auto it = forward_.find(k);
if (it == forward_.end()) return false;
if (contains_value(new_v)) return false;
// remove old reverse, insert new reverse, then update forward
reverse_.erase(it->second);
reverse_.emplace(new_v, k);
it->second = new_v;
return true;
}
// Replace key for existing value (and update forward map). Returns false if key is already bound elsewhere.
bool replace_by_value(const V& v, const K& new_k) {
auto it = reverse_.find(v);
if (it == reverse_.end()) return false;
if (contains_key(new_k)) return false;
forward_.erase(it->second);
forward_.emplace(new_k, v);
it->second = new_k;
return true;
}
// Erase by key/value. Return number erased (0 or 1).
size_t erase_key(const K& k) {
auto it = forward_.find(k);
if (it == forward_.end()) return 0;
reverse_.erase(it->second);
forward_.erase(it);
return 1;
}
size_t erase_value(const V& v) {
auto it = reverse_.find(v);
if (it == reverse_.end()) return 0;
forward_.erase(it->second);
reverse_.erase(it);
return 1;
}
// Lookup
bool contains_key(const K& k) const { return forward_.find(k) != forward_.end(); }
bool contains_value(const V& v) const { return reverse_.find(v) != reverse_.end(); }
const V* find_by_key(const K& k) const {
auto it = forward_.find(k);
return (it == forward_.end()) ? nullptr : &it->second;
}
const K* find_by_value(const V& v) const {
auto it = reverse_.find(v);
return (it == reverse_.end()) ? nullptr : &it->second;
}
// at() variants throw std::out_of_range on missing entries
const V& at_key(const K& k) const { return forward_.at(k); }
const K& at_value(const V& v) const { return reverse_.at(v); }
void clear() noexcept {
forward_.clear();
reverse_.clear();
}
bool empty() const noexcept { return forward_.empty(); }
size_t size() const noexcept { return forward_.size(); }
// Reserve buckets for performance (optional)
void reserve(size_t n) {
forward_.reserve(n);
reverse_.reserve(n);
}
private:
std::unordered_map<K, V, HashK, EqK> forward_;
std::unordered_map<V, K, HashV, EqV> reverse_;
};
#endif #endif

View File

@ -10,6 +10,50 @@
#include "../helper/HL_Rtp.h" #include "../helper/HL_Rtp.h"
#include <openssl/rand.h> #include <openssl/rand.h>
#include <assert.h> #include <assert.h>
#include <format>
BiMap<SrtpSuite, std::string_view> SrtpSuiteNames{{
{SRTP_AES_256_AUTH_80, "AES_CM_256_HMAC_SHA1_80"},
{SRTP_AES_128_AUTH_80, "AES_CM_128_HMAC_SHA1_80"},
{SRTP_AES_192_AUTH_80, "AES_CM_192_HMAC_SHA1_80"},
{SRTP_AES_256_AUTH_32, "AES_CM_256_HMAC_SHA1_32"},
{SRTP_AES_192_AUTH_32, "AES_CM_192_HMAC_SHA1_32"},
{SRTP_AES_128_AUTH_32, "AES_CM_128_HMAC_SHA1_32"},
{SRTP_AES_128_AUTH_NULL, "AES_CM_128_NULL_AUTH"},
{SRTP_AED_AES_256_GCM, "AEAD_AES_256_GCM"},
{SRTP_AED_AES_128_GCM, "AEAD_AES_128_GCM"}}};
extern SrtpSuite toSrtpSuite(const std::string_view& s)
{
auto* suite = SrtpSuiteNames.find_by_value(s);
return !suite ? SRTP_NONE : *suite;
}
extern std::string_view toString(SrtpSuite suite)
{
auto* s = SrtpSuiteNames.find_by_key(suite);
return s ? *s : std::string_view();
}
typedef void (*set_srtp_policy_function) (srtp_crypto_policy_t*);
set_srtp_policy_function findPolicyFunction(SrtpSuite suite)
{
switch (suite)
{
case SRTP_AES_128_AUTH_80: return &srtp_crypto_policy_set_rtp_default; break;
case SRTP_AES_192_AUTH_80: return &srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80; break;
case SRTP_AES_256_AUTH_80: return &srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80; break;
case SRTP_AES_128_AUTH_32: return &srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32; break;
case SRTP_AES_192_AUTH_32: return &srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32; break;
case SRTP_AES_256_AUTH_32: return &srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32; break;
case SRTP_AES_128_AUTH_NULL: return &srtp_crypto_policy_set_aes_cm_128_null_auth; break;
case SRTP_AED_AES_256_GCM: return &srtp_crypto_policy_set_aes_gcm_256_16_auth; break;
case SRTP_AED_AES_128_GCM: return &srtp_crypto_policy_set_aes_gcm_128_16_auth; break;
default:
throw std::runtime_error(std::format("SRTP suite {} is not supported", toString(suite)));
}
}
// --- SrtpStream --- // --- SrtpStream ---
static void configureSrtpStream(SrtpStream& s, uint16_t ssrc, SrtpSuite suite) static void configureSrtpStream(SrtpStream& s, uint16_t ssrc, SrtpSuite suite)
@ -17,21 +61,11 @@ static void configureSrtpStream(SrtpStream& s, uint16_t ssrc, SrtpSuite suite)
s.second.ssrc.type = ssrc_specific; s.second.ssrc.type = ssrc_specific;
s.second.ssrc.value = ntohl(ssrc); s.second.ssrc.value = ntohl(ssrc);
s.second.next = nullptr; s.second.next = nullptr;
switch (suite) set_srtp_policy_function func = findPolicyFunction(suite);
{ if (!func)
case SRTP_AES_128_AUTH_80: throw std::runtime_error(std::format("SRTP suite {} is not supported", toString(suite)));
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&s.second.rtp); func(&s.second.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&s.second.rtcp); func(&s.second.rtcp);
break;
case SRTP_AES_256_AUTH_80:
srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&s.second.rtp);
srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&s.second.rtcp);
break;
default:
assert(0);
}
} }
SrtpSession::SrtpSession() SrtpSession::SrtpSession()
@ -45,14 +79,19 @@ SrtpSession::SrtpSession()
memset(&mOutboundPolicy, 0, sizeof mOutboundPolicy); memset(&mOutboundPolicy, 0, sizeof mOutboundPolicy);
mOutboundPolicy.ssrc.type = ssrc_specific; mOutboundPolicy.ssrc.type = ssrc_specific;
// Generate outgoing keys // Generate outgoing keys for all ciphers
mOutgoingKey[SRTP_AES_128_AUTH_80-1].first = std::make_shared<ByteBuffer>(); auto putKey = [this](SrtpSuite suite, size_t length){
mOutgoingKey[SRTP_AES_128_AUTH_80-1].first->resize(30); auto key = std::make_shared<ByteBuffer>();
RAND_bytes((unsigned char*)mOutgoingKey[SRTP_AES_128_AUTH_80-1].first->mutableData(), 30); key->resize(length);
RAND_bytes(key->mutableData(), key->size());
mOutgoingKey[suite].first = key;
};
putKey(SRTP_AES_128_AUTH_80, 30); putKey(SRTP_AES_128_AUTH_32, 30);
putKey(SRTP_AES_192_AUTH_80, 38); putKey(SRTP_AES_192_AUTH_32, 38);
putKey(SRTP_AES_256_AUTH_80, 46); putKey(SRTP_AES_256_AUTH_32, 46);
putKey(SRTP_AED_AES_128_GCM, 28);
putKey(SRTP_AED_AES_256_GCM, 44);
mOutgoingKey[SRTP_AES_256_AUTH_80-1].first = std::make_shared<ByteBuffer>();
mOutgoingKey[SRTP_AES_256_AUTH_80-1].first->resize(46);
RAND_bytes((unsigned char*)mOutgoingKey[SRTP_AES_256_AUTH_80-1].first->mutableData(), 46);
} }
SrtpSession::~SrtpSession() SrtpSession::~SrtpSession()
@ -106,27 +145,17 @@ void SrtpSession::open(ByteBuffer& incomingKey, SrtpSuite suite)
// Save key // Save key
mIncomingKey.first = std::make_shared<ByteBuffer>(incomingKey); mIncomingKey.first = std::make_shared<ByteBuffer>(incomingKey);
// Update policy auto policyFunction = findPolicyFunction(suite);
switch (suite) if (!policyFunction)
{ throw std::runtime_error(std::format("SRTP suite {} not found", toString(suite)));
case SRTP_AES_128_AUTH_80:
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&mInboundPolicy.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&mInboundPolicy.rtcp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&mOutboundPolicy.rtp);
srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&mOutboundPolicy.rtcp);
break;
case SRTP_AES_256_AUTH_80: // Configure policies
srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&mInboundPolicy.rtp); policyFunction(&mInboundPolicy.rtp);
srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&mInboundPolicy.rtcp); policyFunction(&mInboundPolicy.rtcp);
srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&mOutboundPolicy.rtp); policyFunction(&mOutboundPolicy.rtp);
srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(&mOutboundPolicy.rtcp); policyFunction(&mOutboundPolicy.rtcp);
break;
case SRTP_NONE: mOutboundPolicy.key = (unsigned char*)mOutgoingKey[int(suite)].first->mutableData();
break;
}
mOutboundPolicy.key = (unsigned char*)mOutgoingKey[int(suite)-1].first->mutableData();
mInboundPolicy.key = (unsigned char*)mIncomingKey.first->mutableData(); mInboundPolicy.key = (unsigned char*)mIncomingKey.first->mutableData();
// Create SRTP session // Create SRTP session

View File

@ -11,63 +11,77 @@
#include <map> #include <map>
#include "libsrtp/include/srtp.h" #include "libsrtp/include/srtp.h"
#include "../helper/HL_Sync.h" #include "HL_Sync.h"
#include "../helper/HL_ByteBuffer.h" #include "HL_ByteBuffer.h"
#include "HL_Types.h"
#define SRTP_SUITE_NAME_2 "AES_CM_256_HMAC_SHA1_80" #define NAME_SRTP_AES_256_AUTH_80 "AES_CM_256_HMAC_SHA1_80"
#define SRTP_SUITE_NAME_1 "AES_CM_128_HMAC_SHA1_80" #define NAME_SRTP_AES_128_AUTH_80 "AES_CM_128_HMAC_SHA1_80"
enum SrtpSuite enum SrtpSuite
{ {
SRTP_NONE, SRTP_NONE,
SRTP_AES_128_AUTH_80, SRTP_AES_128_AUTH_80,
SRTP_AES_256_AUTH_80, SRTP_AES_256_AUTH_80,
SRTP_LAST = SRTP_AES_256_AUTH_80 SRTP_AES_192_AUTH_80,
SRTP_AES_128_AUTH_32,
SRTP_AES_256_AUTH_32,
SRTP_AES_192_AUTH_32,
SRTP_AES_128_AUTH_NULL,
SRTP_AED_AES_256_GCM,
SRTP_AED_AES_128_GCM,
SRTP_LAST = SRTP_AED_AES_128_GCM
// ToDo:
// a=crypto:1 AEAD_AES_256_GCM_8 inline:tN2A0vRjFBimpQsW2GasuJuPe7hKE26gki30APC8DVuySqCOYTs8lYBPR5I=
// a=crypto:3 AEAD_AES_128_GCM_8 inline:Ok7VL8SmBHSbZLw4dK6iQgpliYKGdY9BHLJcRw==
}; };
extern SrtpSuite toSrtpSuite(const std::string_view& s);
extern std::string_view toString(SrtpSuite suite);
typedef std::pair<PByteBuffer, PByteBuffer> SrtpKeySalt; typedef std::pair<PByteBuffer, PByteBuffer> SrtpKeySalt;
typedef std::pair<unsigned, srtp_policy_t> SrtpStream; typedef std::pair<unsigned, srtp_policy_t> SrtpStream;
class SrtpSession class SrtpSession
{ {
public: public:
SrtpSession(); SrtpSession();
~SrtpSession(); ~SrtpSession();
enum SsrcDirection enum SsrcDirection
{ {
sdIncoming, sdIncoming,
sdOutgoing sdOutgoing
}; };
SrtpKeySalt& outgoingKey(SrtpSuite suite); SrtpKeySalt& outgoingKey(SrtpSuite suite);
void open(ByteBuffer& incomingKey, SrtpSuite suite); void open(ByteBuffer& incomingKey, SrtpSuite suite);
void close(); void close();
bool active(); bool active();
/* bufferPtr is RTP packet data i.e. header + payload. Buffer must be big enough to hold encrypted data. */ /* bufferPtr is RTP packet data i.e. header + payload. Buffer must be big enough to hold encrypted data. */
bool protectRtp(void* buffer, int* length); bool protectRtp(void* buffer, int* length);
bool protectRtcp(void* buffer, int* length); bool protectRtcp(void* buffer, int* length);
bool unprotectRtp(const void* src, size_t srcLength, void* dst, size_t* dstLength); bool unprotectRtp(const void* src, size_t srcLength, void* dst, size_t* dstLength);
bool unprotectRtcp(const void* src, size_t srcLength, void* dst, size_t* dstLength); bool unprotectRtcp(const void* src, size_t srcLength, void* dst, size_t* dstLength);
static void initSrtp(); static void initSrtp();
protected: protected:
srtp_t mInboundSession, srtp_t mInboundSession,
mOutboundSession; mOutboundSession;
SrtpKeySalt mIncomingKey, SrtpKeySalt mIncomingKey,
mOutgoingKey[SRTP_LAST]; mOutgoingKey[SRTP_LAST];
srtp_policy_t mInboundPolicy; srtp_policy_t mInboundPolicy;
srtp_policy_t mOutboundPolicy; srtp_policy_t mOutboundPolicy;
SrtpSuite mSuite; SrtpSuite mSuite;
typedef std::map<unsigned, SrtpStream> SrtpStreamMap; typedef std::map<unsigned, SrtpStream> SrtpStreamMap;
SrtpStreamMap mIncomingMap, mOutgoingMap; SrtpStreamMap mIncomingMap, mOutgoingMap;
Mutex mGuard; Mutex mGuard;
void addSsrc(unsigned ssrc, SsrcDirection d); void addSsrc(unsigned ssrc, SsrcDirection d);
}; };
#endif #endif

View File

@ -304,3 +304,10 @@ Logger::operator << (const std::string& data)
*mStream << data.c_str(); *mStream << data.c_str();
return *this; return *this;
} }
Logger&
Logger::operator << (const std::string_view& data)
{
*mStream << data;
return *this;
}

View File

@ -121,6 +121,7 @@ public:
Logger& operator << (const int data); Logger& operator << (const int data);
Logger& operator << (const float data); Logger& operator << (const float data);
Logger& operator << (const std::string& data); Logger& operator << (const std::string& data);
Logger& operator << (const std::string_view& data);
Logger& operator << (const int64_t data); Logger& operator << (const int64_t data);
Logger& operator << (const unsigned int data); Logger& operator << (const unsigned int data);
Logger& operator << (const uint64_t data); Logger& operator << (const uint64_t data);