324 lines
8.9 KiB
C++
324 lines
8.9 KiB
C++
/**
|
|
* secure.cpp
|
|
* Yet Another RTP Stack
|
|
* This file is part of the YATE Project http://YATE.null.ro
|
|
*
|
|
* Yet Another Telephony Engine - a fully featured software PBX and IVR
|
|
* Copyright (C) 2004-2014 Null Team
|
|
*
|
|
* This software is distributed under multiple licenses;
|
|
* see the COPYING file in the main directory for licensing
|
|
* information for this specific distribution.
|
|
*
|
|
* This use of this software may be subject to additional restrictions.
|
|
* See the LEGAL file in the main directory for details.
|
|
*
|
|
* This program 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.
|
|
*/
|
|
|
|
#include <yatertp.h>
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
using namespace TelEngine;
|
|
|
|
static const DataBlock s_16bit(0,2);
|
|
|
|
|
|
RTPSecure::RTPSecure()
|
|
: m_owner(0), m_rtpCipher(0),
|
|
m_rtpAuthLen(0), m_rtpEncrypted(false)
|
|
{
|
|
DDebug(DebugAll,"RTPSecure::RTPSecure() [%p]",this);
|
|
}
|
|
|
|
RTPSecure::RTPSecure(const String& suite)
|
|
: m_owner(0), m_rtpCipher(0),
|
|
m_rtpAuthLen(4), m_rtpEncrypted(true)
|
|
{
|
|
DDebug(DebugAll,"RTPSecure::RTPSecure('%s') [%p]",suite.c_str(),this);
|
|
if (suite == YSTRING("NULL")) {
|
|
m_rtpAuthLen = 0;
|
|
m_rtpEncrypted = false;
|
|
}
|
|
else if (suite == YSTRING("AES_CM_128_HMAC_SHA1_32"))
|
|
m_rtpAuthLen = 4;
|
|
else if (suite == YSTRING("AES_CM_128_HMAC_SHA1_80"))
|
|
m_rtpAuthLen = 10;
|
|
}
|
|
|
|
RTPSecure::RTPSecure(const RTPSecure& other)
|
|
: GenObject(),
|
|
m_owner(0), m_rtpCipher(0),
|
|
m_rtpAuthLen(other.m_rtpAuthLen), m_rtpEncrypted(other.m_rtpEncrypted)
|
|
{
|
|
DDebug(DebugAll,"RTPSecure::~RTPSecure(%p) [%p]",&other,this);
|
|
}
|
|
|
|
RTPSecure::~RTPSecure()
|
|
{
|
|
DDebug(DebugAll,"RTPSecure::~RTPSecure() [%p]",this);
|
|
TelEngine::destruct(m_rtpCipher);
|
|
}
|
|
|
|
void RTPSecure::owner(RTPBaseIO* newOwner)
|
|
{
|
|
m_owner = newOwner;
|
|
init();
|
|
}
|
|
|
|
bool RTPSecure::supported(RTPSession* session) const
|
|
{
|
|
if (m_owner && !session)
|
|
session = m_owner->session();
|
|
return session && session->checkCipher("aes_ctr");
|
|
}
|
|
|
|
void RTPSecure::init()
|
|
{
|
|
if (!m_owner)
|
|
return;
|
|
Debug(DebugInfo,"RTPSecure::init() encrypt=%s authlen=%d [%p]",
|
|
String::boolText(m_rtpEncrypted),m_rtpAuthLen,this);
|
|
m_owner->secLength(m_rtpAuthLen);
|
|
if ((m_rtpEncrypted || m_rtpAuthLen) && !m_rtpCipher && m_owner->session()) {
|
|
Cipher* cipher = m_owner->session()->createCipher("aes_ctr",Cipher::Bidir);
|
|
if (!cipher)
|
|
return;
|
|
cipher->setKey(m_masterKey);
|
|
deriveKey(*cipher,m_cipherKey,16,0);
|
|
deriveKey(*cipher,m_cipherSalt,14,2);
|
|
// add now the extra 16 bits since we need them for each packet
|
|
m_cipherSalt.append(s_16bit);
|
|
// prepare components of auth HMAC-SHA1
|
|
DataBlock authKey;
|
|
deriveKey(*cipher,authKey,20,1);
|
|
m_authOpad.clear();
|
|
m_authIpad.clear();
|
|
unsigned char* key = (unsigned char*)authKey.data();
|
|
unsigned char ipad[64];
|
|
unsigned char opad[64];
|
|
for (unsigned i = 0; i < 64; i++) {
|
|
unsigned char c = (i < authKey.length()) ? key[i] : 0;
|
|
ipad[i] = c ^ 0x36;
|
|
opad[i] = c ^ 0x5c;
|
|
}
|
|
// preinitialize the two partial digests
|
|
m_authIpad.update(ipad,sizeof(ipad));
|
|
m_authOpad.update(opad,sizeof(opad));
|
|
// finally, prepare the cipher for RTP processing
|
|
cipher->setKey(m_cipherKey);
|
|
m_rtpCipher = cipher;
|
|
DDebug(DebugInfo,"RTPSecure::init() got cipher=%p [%p]",cipher,this);
|
|
}
|
|
}
|
|
|
|
bool RTPSecure::setup(const String& cryptoSuite, const String& keyParams, const ObjList* paramList)
|
|
{
|
|
Debug(DebugInfo,"RTPSecure::setup('%s','%s',%p) [%p]",
|
|
cryptoSuite.c_str(),keyParams.c_str(),paramList,this);
|
|
m_rtpEncrypted = !paramList || (0 == paramList->find("UNENCRYPTED_SRTP"));
|
|
if (cryptoSuite.null() || cryptoSuite == YSTRING("NULL")) {
|
|
m_rtpAuthLen = 0;
|
|
m_rtpEncrypted = false;
|
|
}
|
|
else if (cryptoSuite == YSTRING("AES_CM_128_HMAC_SHA1_32"))
|
|
m_rtpAuthLen = 4;
|
|
else if (cryptoSuite == YSTRING("AES_CM_128_HMAC_SHA1_80"))
|
|
m_rtpAuthLen = 10;
|
|
else {
|
|
Debug(DebugMild,"Unknown SRTP crypto suite '%s'",cryptoSuite.c_str());
|
|
return false;
|
|
}
|
|
if (paramList && (0 != paramList->find("UNAUTHENTICATED_SRTP")))
|
|
m_rtpAuthLen = 0;
|
|
if (m_rtpEncrypted || m_rtpAuthLen) {
|
|
if (keyParams.null())
|
|
return false;
|
|
bool err = true;
|
|
ObjList* l = keyParams.split('|');
|
|
// HACK use a for so we can break out easily in case of error
|
|
for (;err;err = false) {
|
|
String* key = static_cast<String*>(l->get());
|
|
if (!key->startSkip("inline:",false))
|
|
break;
|
|
Base64 b64;
|
|
DataBlock saltedKey;
|
|
b64 << *key;
|
|
if (!b64.decode(saltedKey,false))
|
|
break;
|
|
if (saltedKey.length() != 30)
|
|
break;
|
|
char* sk = (char*)saltedKey.data();
|
|
m_masterKey.assign(sk,16);
|
|
m_masterSalt.assign(sk+16,14);
|
|
}
|
|
TelEngine::destruct(l);
|
|
if (err)
|
|
return false;
|
|
}
|
|
init();
|
|
return true;
|
|
}
|
|
|
|
bool RTPSecure::create(String& suite, String& keyParams, bool buildMaster)
|
|
{
|
|
if ((m_masterKey.null() || m_masterSalt.null()) && m_rtpAuthLen && !buildMaster)
|
|
return false;
|
|
m_rtpEncrypted = true;
|
|
switch (m_rtpAuthLen) {
|
|
case 0:
|
|
suite = "NULL";
|
|
m_rtpEncrypted = false;
|
|
break;
|
|
case 4:
|
|
suite = "AES_CM_128_HMAC_SHA1_32";
|
|
break;
|
|
case 10:
|
|
suite = "AES_CM_128_HMAC_SHA1_80";
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
bool needInit = m_masterKey.null() || m_masterSalt.null();
|
|
if (needInit) {
|
|
#if 0
|
|
// Key Derivation Test Vectors from RFC 3711 B.3.
|
|
unsigned char sk[30] = {
|
|
0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39,
|
|
0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6
|
|
};
|
|
#else
|
|
unsigned char sk[30];
|
|
for (unsigned int i = 0; i < sizeof(sk);) {
|
|
u_int16_t r = (u_int16_t)Random::random();
|
|
sk[i++] = r & 0xff;
|
|
sk[i++] = (r >> 8) & 0xff;
|
|
}
|
|
#endif
|
|
m_masterKey.assign(sk,16);
|
|
m_masterSalt.assign(sk+16,14);
|
|
}
|
|
Base64 b64;
|
|
b64 << m_masterKey << m_masterSalt;
|
|
String key;
|
|
b64.encode(key,0,false);
|
|
keyParams = "inline:" + key;
|
|
if (needInit)
|
|
init();
|
|
return true;
|
|
}
|
|
|
|
bool RTPSecure::deriveKey(Cipher& cipher, DataBlock& key, unsigned int len, unsigned char label, u_int64_t index)
|
|
{
|
|
if (!(len && m_masterSalt.length()))
|
|
return false;
|
|
unsigned int vLen = cipher.initVectorSize();
|
|
if (!vLen)
|
|
return false;
|
|
key = m_masterSalt;
|
|
if (vLen > key.length()) {
|
|
DataBlock tmp(0,vLen - key.length());
|
|
key += tmp;
|
|
}
|
|
else
|
|
vLen = key.length();
|
|
// initially point 16 bits before end of block
|
|
unsigned char* p = (vLen - 2) + (unsigned char*)key.data();
|
|
for (int i = 0; i < 6; i++) {
|
|
*--p ^= (index & 0xff);
|
|
index = index >> 8;
|
|
}
|
|
*--p ^= label;
|
|
cipher.initVector(key);
|
|
key.assign(0,len);
|
|
cipher.encrypt(key);
|
|
return true;
|
|
}
|
|
|
|
bool RTPSecure::rtpDecipher(unsigned char* data, int len, const void* secData, u_int32_t ssrc, u_int64_t seq)
|
|
{
|
|
if (!(m_rtpEncrypted && data))
|
|
return true;
|
|
if (!(len && m_rtpCipher))
|
|
return false;
|
|
DataBlock iv(m_cipherSalt);
|
|
int i;
|
|
// SSRC << 64
|
|
unsigned char* p = (iv.length() - 8) + (unsigned char*)iv.data();
|
|
for (i = 0; i < 4; i++) {
|
|
*--p ^= (ssrc & 0xff);
|
|
ssrc >>= 8;
|
|
}
|
|
// index << 16
|
|
p = (iv.length() - 2) + (unsigned char*)iv.data();
|
|
for (i = 0; i < 6; i++) {
|
|
*--p ^= (seq & 0xff);
|
|
seq >>= 8;
|
|
}
|
|
m_rtpCipher->initVector(iv);
|
|
m_rtpCipher->decrypt(data,len);
|
|
return true;
|
|
}
|
|
|
|
bool RTPSecure::rtpCheckIntegrity(const unsigned char* data, int len, const void* authData, u_int32_t ssrc, u_int64_t seq)
|
|
{
|
|
if (0 == m_rtpAuthLen)
|
|
return true;
|
|
if (!(len && data && authData))
|
|
return false;
|
|
|
|
// RFC 3711 4.2
|
|
u_int32_t roc = htonl((u_int32_t)(seq >> 16));
|
|
SHA1 h1(m_authIpad);
|
|
h1.update(data,len);
|
|
h1.update(&roc,sizeof(roc));
|
|
h1.finalize();
|
|
SHA1 hmac(m_authOpad);
|
|
hmac.update(h1.rawDigest(),h1.rawLength());
|
|
hmac.finalize();
|
|
#ifdef DEBUG
|
|
if (::memcmp(authData,hmac.rawDigest(),m_rtpAuthLen)) {
|
|
String s1,s2;
|
|
s1.hexify((void*)authData,m_rtpAuthLen);
|
|
s2.hexify((void*)hmac.rawDigest(),m_rtpAuthLen);
|
|
Debug(DebugMild,"SRTP HMAC recv: %s calc: %s seq: " FMT64U " [%p]",
|
|
s1.c_str(),s2.c_str(),seq,this);
|
|
return false;
|
|
}
|
|
return true;
|
|
#else
|
|
return 0 == ::memcmp(authData,hmac.rawDigest(),m_rtpAuthLen);
|
|
#endif
|
|
}
|
|
|
|
void RTPSecure::rtpEncipher(unsigned char* data, int len)
|
|
{
|
|
if (!(len && data && m_rtpEncrypted && m_rtpCipher && m_owner))
|
|
return;
|
|
// SRTP is symmetrical as it just XORs the data with a keystream
|
|
rtpDecipher(data,len,0,m_owner->ssrc(),m_owner->fullSeq());
|
|
}
|
|
|
|
void RTPSecure::rtpAddIntegrity(const unsigned char* data, int len, unsigned char* authData)
|
|
{
|
|
if (!(m_rtpAuthLen && len && data && authData && m_owner))
|
|
return;
|
|
|
|
// RFC 3711 4.2
|
|
u_int32_t roc = htonl(m_owner->rollover());
|
|
SHA1 h1(m_authIpad);
|
|
h1.update(data,len);
|
|
h1.update(&roc,sizeof(roc));
|
|
h1.finalize();
|
|
SHA1 hmac(m_authOpad);
|
|
hmac.update(h1.rawDigest(),h1.rawLength());
|
|
hmac.finalize();
|
|
::memcpy(authData,hmac.rawDigest(),m_rtpAuthLen);
|
|
}
|
|
|
|
/* vi: set ts=8 sw=4 sts=4 noet: */
|