123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913 |
- /*
- * SRT - Secure, Reliable, Transport
- * Copyright (c) 2018 Haivision Systems Inc.
- *
- * 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
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- */
- /*****************************************************************************
- written by
- Haivision Systems Inc.
- *****************************************************************************/
- #include "platform_sys.h"
- #include <cstring>
- #include <string>
- #include <sstream>
- #include <iterator>
- #include "udt.h"
- #include "utilities.h"
- #include <haicrypt.h>
- #include "crypto.h"
- #include "logging.h"
- #include "core.h"
- #include "api.h"
- using namespace srt_logging;
- #define SRT_MAX_KMRETRY 10
-
- //#define SRT_CMD_KMREQ 3 /* HaiCryptTP SRT Keying Material */
- //#define SRT_CMD_KMRSP 4 /* HaiCryptTP SRT Keying Material ACK */
- #define SRT_CMD_KMREQ_SZ HCRYPT_MSG_KM_MAX_SZ /* */
- #if SRT_CMD_KMREQ_SZ > SRT_CMD_MAXSZ
- #error SRT_CMD_MAXSZ too small
- #endif
- /* Key Material Request (Network Order)
- See HaiCryptTP SRT (hcrypt_xpt_srt.c)
- */
- // 10* HAICRYPT_DEF_KM_PRE_ANNOUNCE
- const int SRT_CRYPT_KM_PRE_ANNOUNCE SRT_ATR_UNUSED = 0x10000;
- namespace srt_logging
- {
- std::string KmStateStr(SRT_KM_STATE state)
- {
- switch (state)
- {
- #define TAKE(val) case SRT_KM_S_##val : return #val
- TAKE(UNSECURED);
- TAKE(SECURED);
- TAKE(SECURING);
- TAKE(NOSECRET);
- TAKE(BADSECRET);
- #ifdef ENABLE_AEAD_API_PREVIEW
- TAKE(BADCRYPTOMODE);
- #endif
- #undef TAKE
- default:
- {
- char buf[256];
- #if defined(_MSC_VER) && _MSC_VER < 1900
- _snprintf(buf, sizeof(buf) - 1, "??? (%d)", state);
- #else
- snprintf(buf, sizeof(buf), "??? (%d)", state);
- #endif
- return buf;
- }
- }
- }
- } // namespace
- using srt_logging::KmStateStr;
- void srt::CCryptoControl::globalInit()
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- // We need to force the Cryspr to be initialized during startup to avoid the
- // possibility of multiple threads initialzing the same static data later on.
- HaiCryptCryspr_Get_Instance();
- #endif
- }
- bool srt::CCryptoControl::isAESGCMSupported()
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- return HaiCrypt_IsAESGCM_Supported() != 0;
- #else
- return false;
- #endif
- }
- #if ENABLE_LOGGING
- std::string srt::CCryptoControl::FormatKmMessage(std::string hdr, int cmd, size_t srtlen)
- {
- std::ostringstream os;
- os << hdr << ": cmd=" << cmd << "(" << (cmd == SRT_CMD_KMREQ ? "KMREQ":"KMRSP") <<") len="
- << size_t(srtlen*sizeof(int32_t)) << " KmState: SND="
- << KmStateStr(m_SndKmState)
- << " RCV=" << KmStateStr(m_RcvKmState);
- return os.str();
- }
- #endif
- void srt::CCryptoControl::updateKmState(int cmd, size_t srtlen SRT_ATR_UNUSED)
- {
- if (cmd == SRT_CMD_KMREQ)
- {
- if ( SRT_KM_S_UNSECURED == m_SndKmState)
- {
- m_SndKmState = SRT_KM_S_SECURING;
- }
- LOGP(cnlog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen));
- }
- else
- {
- LOGP(cnlog.Note, FormatKmMessage("sendSrtMsg", cmd, srtlen));
- }
- }
- void srt::CCryptoControl::createFakeSndContext()
- {
- if (!m_iSndKmKeyLen)
- m_iSndKmKeyLen = 16;
- if (!createCryptoCtx((m_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, false))
- {
- HLOGC(cnlog.Debug, log << "Error: Can't create fake crypto context for sending - sending will return ERROR!");
- m_hSndCrypto = 0;
- }
- }
- int srt::CCryptoControl::processSrtMsg_KMREQ(
- const uint32_t* srtdata SRT_ATR_UNUSED,
- size_t bytelen SRT_ATR_UNUSED,
- int hsv SRT_ATR_UNUSED,
- uint32_t pw_srtdata_out[], size_t& w_srtlen)
- {
- //Receiver
- /* All 32-bit msg fields swapped on reception
- * But HaiCrypt expect network order message
- * Re-swap to cancel it.
- */
- #ifdef SRT_ENABLE_ENCRYPTION
- w_srtlen = bytelen/sizeof(srtdata[SRT_KMR_KMSTATE]);
- HtoNLA((pw_srtdata_out), srtdata, w_srtlen);
- unsigned char* kmdata = reinterpret_cast<unsigned char*>(pw_srtdata_out);
- // The side that has received KMREQ is always an HSD_RESPONDER, regardless of
- // what has called this function. The HSv5 handshake only enforces bidirectional
- // connection.
- const bool bidirectional = hsv > CUDT::HS_VERSION_UDT4;
- // Local macro to return rejection appropriately.
- // CHANGED. The first version made HSv5 reject the connection.
- // This isn't well handled by applications, so the connection is
- // still established, but unable to handle any transport.
- //#define KMREQ_RESULT_REJECTION() if (bidirectional) { return SRT_CMD_NONE; } else { w_srtlen = 1; goto HSv4_ErrorReport; }
- #define KMREQ_RESULT_REJECTION() { w_srtlen = 1; goto HSv4_ErrorReport; }
- int rc = HAICRYPT_OK; // needed before 'goto' run from KMREQ_RESULT_REJECTION macro
- bool wasb4 SRT_ATR_UNUSED = false;
- size_t sek_len = 0;
- const bool bUseGCM =
- (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO && kmdata[HCRYPT_MSG_KM_OFS_CIPHER] == HCRYPT_CIPHER_AES_GCM) ||
- (m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM);
- // What we have to do:
- // If encryption is on (we know that by having m_KmSecret nonempty), create
- // the crypto context (if bidirectional, create for both sending and receiving).
- // Both crypto contexts should be set with the same length of the key.
- // The problem with interpretinting this should be reported as SRT_CMD_NONE,
- // should be appropriately handled by the caller, as it expects that this
- // function normally return SRT_CMD_KMRSP.
- if ( bytelen <= HCRYPT_MSG_KM_OFS_SALT ) //Sanity on message
- {
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: size of the KM (" << bytelen << ") is too small, must be >" << HCRYPT_MSG_KM_OFS_SALT);
- m_RcvKmState = SRT_KM_S_BADSECRET;
- KMREQ_RESULT_REJECTION();
- }
- HLOGC(cnlog.Debug, log << "KMREQ: getting SEK and creating receiver crypto");
- sek_len = hcryptMsg_KM_GetSekLen(kmdata);
- if ( sek_len == 0 )
- {
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Received SEK is empty - REJECTING!");
- m_RcvKmState = SRT_KM_S_BADSECRET;
- KMREQ_RESULT_REJECTION();
- }
- // Write the key length
- m_iRcvKmKeyLen = sek_len;
- // Overwrite the key length anyway - it doesn't make sense to somehow
- // keep the original setting because it will only make KMX impossible.
- #if ENABLE_HEAVY_LOGGING
- if (m_iSndKmKeyLen != m_iRcvKmKeyLen)
- {
- LOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: Agent's PBKEYLEN=" << m_iSndKmKeyLen
- << " overwritten by Peer's PBKEYLEN=" << m_iRcvKmKeyLen);
- }
- #endif
- m_iSndKmKeyLen = m_iRcvKmKeyLen;
- // This is checked only now so that the SRTO_PBKEYLEN return always the correct value,
- // even if encryption is not possible because Agent didn't set a password, or supplied
- // a wrong password.
- if (m_KmSecret.len == 0) //We have a shared secret <==> encryption is on
- {
- LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Agent does not declare encryption - won't decrypt incoming packets!");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- KMREQ_RESULT_REJECTION();
- }
- wasb4 = m_hRcvCrypto;
- if (!createCryptoCtx((m_hRcvCrypto), m_iRcvKmKeyLen, HAICRYPT_CRYPTO_DIR_RX, bUseGCM))
- {
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create RCV CRYPTO CTX - must reject...");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- KMREQ_RESULT_REJECTION();
- }
- // Deduce resulting mode.
- m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR;
- if (!wasb4)
- {
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: created RX ENC with KeyLen=" << m_iRcvKmKeyLen);
- }
- // We have both sides set with password, so both are pending for security
- m_RcvKmState = SRT_KM_S_SECURING;
- // m_SndKmState is set to SECURING or UNSECURED in init(),
- // or it might have been set to SECURED, NOSECRET or BADSECRET in the previous
- // handshake iteration (handshakes may be sent multiple times for the same connection).
- rc = HaiCrypt_Rx_Process(m_hRcvCrypto, kmdata, bytelen, NULL, NULL, 0);
- switch(rc >= 0 ? HAICRYPT_OK : rc)
- {
- case HAICRYPT_OK:
- m_RcvKmState = SRT_KM_S_SECURED;
- HLOGC(cnlog.Debug, log << "KMREQ/rcv: (snd) Rx process successful - SECURED.");
- //Send back the whole message to confirm
- break;
- case HAICRYPT_ERROR_WRONG_SECRET: //Unmatched shared secret to decrypt wrapped key
- m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET;
- //Send status KMRSP message to tel error
- w_srtlen = 1;
- LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADSECRET");
- break;
- case HAICRYPT_ERROR_CIPHER:
- #ifdef ENABLE_AEAD_API_PREVIEW
- m_RcvKmState = m_SndKmState = SRT_KM_S_BADCRYPTOMODE;
- #else
- m_RcvKmState = m_SndKmState = SRT_KM_S_BADSECRET; // Use "bad secret" as a fallback.
- #endif
- w_srtlen = 1;
- LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure - BADCRYPTOMODE");
- break;
- case HAICRYPT_ERROR: //Other errors
- default:
- m_RcvKmState = m_SndKmState = SRT_KM_S_NOSECRET;
- w_srtlen = 1;
- LOGC(cnlog.Warn, log << "KMREQ/rcv: (snd) Rx process failure (IPE) - NOSECRET");
- break;
- }
- LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen));
- // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE,
- // until the next KMREQ is received as a key regeneration.
- m_bErrorReported = false;
- if (w_srtlen == 1)
- goto HSv4_ErrorReport;
- // Configure the sender context also, if it succeeded to configure the
- // receiver context and we are using bidirectional mode.
- if (bidirectional)
- {
- // Note: 'bidirectional' means that we want a bidirectional key update,
- // which happens only and exclusively with HSv5 handshake - not when the
- // usual key update through UMSG_EXT+SRT_CMD_KMREQ was done (which is used
- // in HSv4 versions also to initialize the first key, unlike HSv5).
- if (m_RcvKmState == SRT_KM_S_SECURED)
- {
- if (m_SndKmState == SRT_KM_S_SECURING && !m_hSndCrypto)
- {
- m_iSndKmKeyLen = m_iRcvKmKeyLen;
- if (HaiCrypt_Clone(m_hRcvCrypto, HAICRYPT_CRYPTO_DIR_TX, &m_hSndCrypto) != HAICRYPT_OK)
- {
- LOGC(cnlog.Error, log << "processSrtMsg_KMREQ: Can't create SND CRYPTO CTX - WILL NOT SEND-ENCRYPT correctly!");
- if (hasPassphrase())
- m_SndKmState = SRT_KM_S_BADSECRET;
- else
- m_SndKmState = SRT_KM_S_NOSECRET;
- }
- else
- {
- m_SndKmState = SRT_KM_S_SECURED;
- }
- LOGC(cnlog.Note, log << FormatKmMessage("processSrtMsg_KMREQ", SRT_CMD_KMREQ, bytelen)
- << " SndKeyLen=" << m_iSndKmKeyLen
- << " TX CRYPTO CTX CLONED FROM RX"
- );
- // Write the KM message into the field from which it will be next sent.
- memcpy((m_SndKmMsg[0].Msg), kmdata, bytelen);
- m_SndKmMsg[0].MsgLen = bytelen;
- m_SndKmMsg[0].iPeerRetry = 0; // Don't start sending them upon connection :)
- }
- else
- {
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT cloning RX to TX crypto: already in "
- << KmStateStr(m_SndKmState) << " state");
- }
- }
- else
- {
- HLOGP(cnlog.Debug, "processSrtMsg_KMREQ: NOT SECURED - not replaying failed security association to TX CRYPTO CTX");
- }
- }
- else
- {
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMREQ: NOT REPLAYING the key update to TX CRYPTO CTX.");
- }
- return SRT_CMD_KMRSP;
- HSv4_ErrorReport:
- if (bidirectional && hasPassphrase())
- {
- // If the Forward KMX process has failed, the reverse-KMX process was not done at all.
- // This will lead to incorrect object configuration and will fail to properly declare
- // the transmission state.
- // Create the "fake crypto" with the passphrsae you currently have.
- createFakeSndContext();
- }
- #undef KMREQ_RESULT_REJECTION
- #else
- // It's ok that this is reported as error because this happens in a scenario,
- // when non-encryption-enabled SRT application is contacted by encryption-enabled SRT
- // application which tries to make a security association.
- LOGC(cnlog.Warn, log << "processSrtMsg_KMREQ: Encryption not enabled at compile time - must reject...");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- #endif
- w_srtlen = 1;
- pw_srtdata_out[SRT_KMR_KMSTATE] = m_RcvKmState;
- return SRT_CMD_KMRSP;
- }
- int srt::CCryptoControl::processSrtMsg_KMRSP(const uint32_t* srtdata, size_t len, int /* XXX unused? hsv*/)
- {
- /* All 32-bit msg fields (if present) swapped on reception
- * But HaiCrypt expect network order message
- * Re-swap to cancel it.
- */
- uint32_t srtd[SRTDATA_MAXSIZE];
- size_t srtlen = len/sizeof(uint32_t);
- HtoNLA(srtd, srtdata, srtlen);
- int retstatus = -1;
- // Unused?
- //bool bidirectional = hsv > CUDT::HS_VERSION_UDT4;
- // Since now, when CCryptoControl::decrypt() encounters an error, it will print it, ONCE,
- // until the next KMREQ is received as a key regeneration.
- m_bErrorReported = false;
- if (srtlen == 1) // Error report. Set accordingly.
- {
- SRT_KM_STATE peerstate = SRT_KM_STATE(srtd[SRT_KMR_KMSTATE]); /* Bad or no passphrase */
- m_SndKmMsg[0].iPeerRetry = 0;
- m_SndKmMsg[1].iPeerRetry = 0;
- switch (peerstate)
- {
- case SRT_KM_S_BADSECRET:
- m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET;
- retstatus = -1;
- break;
- // Default embraces two cases:
- // NOSECRET: this KMRSP was sent by secured Peer, but Agent supplied no password.
- // UNSECURED: this KMRSP was sent by unsecure Peer because Agent sent KMREQ.
- case SRT_KM_S_NOSECRET:
- // This means that the peer did not set the password, while Agent did.
- m_RcvKmState = SRT_KM_S_UNSECURED;
- m_SndKmState = SRT_KM_S_NOSECRET;
- retstatus = -1;
- break;
- case SRT_KM_S_UNSECURED:
- // This means that KMRSP was sent without KMREQ, to inform the Agent,
- // that the Peer, unlike Agent, does use password. Agent can send then,
- // but can't decrypt what Peer would send.
- m_RcvKmState = SRT_KM_S_NOSECRET;
- m_SndKmState = SRT_KM_S_UNSECURED;
- retstatus = 0;
- break;
- #ifdef ENABLE_AEAD_API_PREVIEW
- case SRT_KM_S_BADCRYPTOMODE:
- // The peer expects to use a different cryptographic mode (e.g. AES-GCM, not AES-CTR).
- m_RcvKmState = SRT_KM_S_BADCRYPTOMODE;
- m_SndKmState = SRT_KM_S_BADCRYPTOMODE;
- retstatus = -1;
- break;
- #endif
- default:
- LOGC(cnlog.Fatal, log << "processSrtMsg_KMRSP: IPE: unknown peer error state: "
- << KmStateStr(peerstate) << " (" << int(peerstate) << ")");
- m_RcvKmState = SRT_KM_S_NOSECRET;
- m_SndKmState = SRT_KM_S_NOSECRET;
- retstatus = -1; //This is IPE
- break;
- }
- LOGC(cnlog.Warn, log << "processSrtMsg_KMRSP: received failure report. STATE: " << KmStateStr(m_RcvKmState));
- }
- else
- {
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: received key response len=" << len);
- // XXX INSECURE << ": [" << FormatBinaryString((uint8_t*)srtd, len) << "]";
- bool key1 = getKmMsg_acceptResponse(0, srtd, len);
- bool key2 = true;
- if ( !key1 )
- key2 = getKmMsg_acceptResponse(1, srtd, len); // <--- NOTE SEQUENCING!
- if (key1 || key2)
- {
- m_SndKmState = m_RcvKmState = SRT_KM_S_SECURED;
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: KM response matches " << (key1 ? "EVEN" : "ODD") << " key");
- retstatus = 1;
- }
- else
- {
- retstatus = -1;
- LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: IPE??? KM response key matches no key");
- /* XXX INSECURE
- LOGC(cnlog.Error, log << "processSrtMsg_KMRSP: KM response: [" << FormatBinaryString((uint8_t*)srtd, len)
- << "] matches no key 0=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[0].Msg, m_SndKmMsg[0].MsgLen)
- << "] 1=[" << FormatBinaryString((uint8_t*)m_SndKmMsg[1].Msg, m_SndKmMsg[1].MsgLen) << "]");
- */
- m_SndKmState = m_RcvKmState = SRT_KM_S_BADSECRET;
- }
- HLOGC(cnlog.Debug, log << "processSrtMsg_KMRSP: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry
- << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry);
- }
- LOGP(cnlog.Note, FormatKmMessage("processSrtMsg_KMRSP", SRT_CMD_KMRSP, len));
- return retstatus;
- }
- void srt::CCryptoControl::sendKeysToPeer(CUDT* sock SRT_ATR_UNUSED, int iSRTT SRT_ATR_UNUSED)
- {
- sync::ScopedLock lck(m_mtxLock);
- if (!m_hSndCrypto || m_SndKmState == SRT_KM_S_UNSECURED)
- {
- HLOGC(cnlog.Debug, log << "sendKeysToPeer: NOT sending/regenerating keys: "
- << (m_hSndCrypto ? "CONNECTION UNSECURED" : "NO TX CRYPTO CTX created"));
- return;
- }
- #ifdef SRT_ENABLE_ENCRYPTION
- srt::sync::steady_clock::time_point now = srt::sync::steady_clock::now();
- /*
- * Crypto Key Distribution to peer:
- * If...
- * - we want encryption; and
- * - we have not tried more than CSRTCC_MAXRETRY times (peer may not be SRT); and
- * - and did not get answer back from peer; and
- * - last sent Keying Material req should have been replied (RTT*1.5 elapsed);
- * then (re-)send handshake request.
- */
- if (((m_SndKmMsg[0].iPeerRetry > 0) || (m_SndKmMsg[1].iPeerRetry > 0))
- && ((m_SndKmLastTime + srt::sync::microseconds_from((iSRTT * 3)/2)) <= now))
- {
- for (int ki = 0; ki < 2; ki++)
- {
- if (m_SndKmMsg[ki].iPeerRetry > 0 && m_SndKmMsg[ki].MsgLen > 0)
- {
- m_SndKmMsg[ki].iPeerRetry--;
- HLOGC(cnlog.Debug, log << "sendKeysToPeer: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen
- << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry);
- m_SndKmLastTime = now;
- sock->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t));
- }
- }
- }
- #endif
- }
- void srt::CCryptoControl::regenCryptoKm(CUDT* sock SRT_ATR_UNUSED, bool bidirectional SRT_ATR_UNUSED)
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- sync::ScopedLock lck(m_mtxLock);
- if (!m_hSndCrypto)
- return;
- void *out_p[2];
- size_t out_len_p[2];
- int nbo = HaiCrypt_Tx_ManageKeys(m_hSndCrypto, out_p, out_len_p, 2);
- int sent = 0;
- HLOGC(cnlog.Debug, log << "regenCryptoKm: regenerating crypto keys nbo=" << nbo <<
- " THEN=" << (sock ? "SEND" : "KEEP") << " DIR=" << (bidirectional ? "BOTH" : "SENDER"));
- for (int i = 0; i < nbo && i < 2; i++)
- {
- /*
- * New connection keying material
- * or regenerated after crypto_cfg.km_refresh_rate_pkt packets .
- * Send to peer
- */
- // XXX Need to make it clearer and less hardcoded values
- int kix = hcryptMsg_KM_GetKeyIndex((unsigned char *)(out_p[i]));
- int ki = kix & 0x1;
- if ((out_len_p[i] != m_SndKmMsg[ki].MsgLen)
- || (0 != memcmp(out_p[i], m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen)))
- {
- uint8_t* oldkey SRT_ATR_UNUSED = m_SndKmMsg[ki].Msg;
- HLOGC(cnlog.Debug, log << "new key[" << ki << "] index=" << kix
- << " OLD=[" << m_SndKmMsg[ki].MsgLen << "]"
- << FormatBinaryString(m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen)
- << " NEW=[" << out_len_p[i] << "]"
- << FormatBinaryString((const uint8_t*)out_p[i], out_len_p[i]));
- /* New Keying material, send to peer */
- memcpy((m_SndKmMsg[ki].Msg), out_p[i], out_len_p[i]);
- m_SndKmMsg[ki].MsgLen = out_len_p[i];
- m_SndKmMsg[ki].iPeerRetry = SRT_MAX_KMRETRY;
- if (bidirectional && !sock)
- {
- // "Send" this key also to myself, just to be applied to the receiver crypto,
- // exactly the same way how this key is interpreted on the peer side into its receiver crypto
- int rc = HaiCrypt_Rx_Process(m_hRcvCrypto, m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen, NULL, NULL, 0);
- if ( rc < 0 )
- {
- LOGC(cnlog.Fatal, log << "regenCryptoKm: IPE: applying key generated in snd crypto into rcv crypto: failed code=" << rc);
- // The party won't be able to decrypt incoming data!
- // Not sure if anything has to be reported.
- }
- }
- if (sock)
- {
- HLOGC(cnlog.Debug, log << "regenCryptoKm: SENDING ki=" << ki << " len=" << m_SndKmMsg[ki].MsgLen
- << " retry(updated)=" << m_SndKmMsg[ki].iPeerRetry);
- sock->sendSrtMsg(SRT_CMD_KMREQ, (uint32_t *)m_SndKmMsg[ki].Msg, m_SndKmMsg[ki].MsgLen / sizeof(uint32_t));
- sent++;
- }
- }
- else if (out_len_p[i] == 0)
- {
- HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": not generated");
- }
- else
- {
- HLOGC(cnlog.Debug, log << "no key[" << ki << "] index=" << kix << ": key unchanged");
- }
- }
- HLOGC(cnlog.Debug, log << "regenCryptoKm: key[0]: len=" << m_SndKmMsg[0].MsgLen << " retry=" << m_SndKmMsg[0].iPeerRetry
- << "; key[1]: len=" << m_SndKmMsg[1].MsgLen << " retry=" << m_SndKmMsg[1].iPeerRetry);
- if (sent)
- m_SndKmLastTime = srt::sync::steady_clock::now();
- #endif
- }
- srt::CCryptoControl::CCryptoControl(SRTSOCKET id)
- : m_SocketID(id)
- , m_iSndKmKeyLen(0)
- , m_iRcvKmKeyLen(0)
- , m_SndKmState(SRT_KM_S_UNSECURED)
- , m_RcvKmState(SRT_KM_S_UNSECURED)
- , m_KmRefreshRatePkt(0)
- , m_KmPreAnnouncePkt(0)
- , m_iCryptoMode(CSrtConfig::CIPHER_MODE_AUTO)
- , m_bErrorReported(false)
- {
- m_KmSecret.len = 0;
- //send
- m_SndKmMsg[0].MsgLen = 0;
- m_SndKmMsg[0].iPeerRetry = 0;
- m_SndKmMsg[1].MsgLen = 0;
- m_SndKmMsg[1].iPeerRetry = 0;
- m_hSndCrypto = NULL;
- //recv
- m_hRcvCrypto = NULL;
- }
- bool srt::CCryptoControl::init(HandshakeSide side, const CSrtConfig& cfg, bool bidirectional SRT_ATR_UNUSED)
- {
- // NOTE: initiator creates m_hSndCrypto. When bidirectional,
- // it creates also m_hRcvCrypto with the same key length.
- // Acceptor creates nothing - it will create appropriate
- // contexts when receiving KMREQ from the initiator.
- HLOGC(cnlog.Debug, log << "CCryptoControl::init: HS SIDE:"
- << (side == HSD_INITIATOR ? "INITIATOR" : "RESPONDER")
- << " DIRECTION:" << (bidirectional ? "BOTH" : (side == HSD_INITIATOR) ? "SENDER" : "RECEIVER"));
- // Set UNSECURED state as default
- m_RcvKmState = SRT_KM_S_UNSECURED;
- m_iCryptoMode = cfg.iCryptoMode;
- #ifdef SRT_ENABLE_ENCRYPTION
- if (!cfg.bTSBPD && m_iCryptoMode == CSrtConfig::CIPHER_MODE_AUTO)
- m_iCryptoMode = CSrtConfig::CIPHER_MODE_AES_CTR;
- const bool bUseGCM = m_iCryptoMode == CSrtConfig::CIPHER_MODE_AES_GCM;
- if (bUseGCM && !isAESGCMSupported())
- {
- LOGC(cnlog.Warn, log << "CCryptoControl: AES GCM is not supported by the crypto service provider.");
- return false;
- }
- #endif
- // Set security-pending state, if a password was set.
- m_SndKmState = hasPassphrase() ? SRT_KM_S_SECURING : SRT_KM_S_UNSECURED;
- m_KmPreAnnouncePkt = cfg.uKmPreAnnouncePkt;
- m_KmRefreshRatePkt = cfg.uKmRefreshRatePkt;
- if (side == HSD_INITIATOR)
- {
- if (hasPassphrase())
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- if (m_iSndKmKeyLen == 0)
- {
- HLOGC(cnlog.Debug, log << "CCryptoControl::init: PBKEYLEN still 0, setting default 16");
- m_iSndKmKeyLen = 16;
- }
- bool ok = createCryptoCtx((m_hSndCrypto), m_iSndKmKeyLen, HAICRYPT_CRYPTO_DIR_TX, bUseGCM);
- HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating SND crypto context: " << ok);
- if (ok && bidirectional)
- {
- m_iRcvKmKeyLen = m_iSndKmKeyLen;
- const int st = HaiCrypt_Clone(m_hSndCrypto, HAICRYPT_CRYPTO_DIR_RX, &m_hRcvCrypto);
- HLOGC(cnlog.Debug, log << "CCryptoControl::init: creating CLONED RCV crypto context: status=" << st);
- ok = st == 0;
- }
- // Note: this is sanity check, it should never happen.
- if (!ok)
- {
- m_SndKmState = SRT_KM_S_NOSECRET; // wanted to secure, but error occurred.
- if (bidirectional)
- m_RcvKmState = SRT_KM_S_NOSECRET;
- return false;
- }
- regenCryptoKm(
- NULL, // Do not send the key (the KM msg will be attached to the HSv5 handshake)
- bidirectional // replicate the key to the receiver context, if bidirectional
- );
- m_iCryptoMode = bUseGCM ? CSrtConfig::CIPHER_MODE_AES_GCM : CSrtConfig::CIPHER_MODE_AES_CTR;
- #else
- // This error would be a consequence of setting the passphrase, while encryption
- // is turned off at compile time. Setting the password itself should be not allowed
- // so this could only happen as a consequence of an IPE.
- LOGC(cnlog.Error, log << "CCryptoControl::init: IPE: encryption not supported");
- return false;
- #endif
- }
- else
- {
- HLOGC(cnlog.Debug, log << "CCryptoControl::init: CAN'T CREATE crypto: key length for SND = " << m_iSndKmKeyLen);
- }
- }
- else
- {
- HLOGC(cnlog.Debug, log << "CCryptoControl::init: NOT creating crypto contexts - will be created upon reception of KMREQ");
- }
- return true;
- }
- void srt::CCryptoControl::close()
- {
- /* Wipeout secrets */
- sync::ScopedLock lck(m_mtxLock);
- memset(&m_KmSecret, 0, sizeof(m_KmSecret));
- }
- std::string srt::CCryptoControl::CONID() const
- {
- if (m_SocketID == 0)
- return "";
- std::ostringstream os;
- os << "@" << m_SocketID << ":";
- return os.str();
- }
- #ifdef SRT_ENABLE_ENCRYPTION
- #if ENABLE_HEAVY_LOGGING
- namespace srt {
- static std::string CryptoFlags(int flg)
- {
- using namespace std;
- vector<string> f;
- if (flg & HAICRYPT_CFG_F_CRYPTO)
- f.push_back("crypto");
- if (flg & HAICRYPT_CFG_F_TX)
- f.push_back("TX");
- if (flg & HAICRYPT_CFG_F_FEC)
- f.push_back("fec");
- ostringstream os;
- copy(f.begin(), f.end(), ostream_iterator<string>(os, "|"));
- return os.str();
- }
- } // namespace srt
- #endif // ENABLE_HEAVY_LOGGING
- bool srt::CCryptoControl::createCryptoCtx(HaiCrypt_Handle& w_hCrypto, size_t keylen, HaiCrypt_CryptoDir cdir, bool bAESGCM)
- {
- if (w_hCrypto)
- {
- // XXX You can check here if the existing handle represents
- // a correctly defined crypto. But this doesn't seem to be
- // necessary - the whole CCryptoControl facility seems to be valid only
- // within the frames of one connection.
- return true;
- }
- if ((m_KmSecret.len <= 0) || (keylen <= 0))
- {
- LOGC(cnlog.Error, log << CONID() << "cryptoCtx: IPE missing secret (" << m_KmSecret.len << ") or key length (" << keylen << ")");
- return false;
- }
- HaiCrypt_Cfg crypto_cfg;
- memset(&crypto_cfg, 0, sizeof(crypto_cfg));
- #if 0//test key refresh (fast rate)
- m_KmRefreshRatePkt = 2000;
- m_KmPreAnnouncePkt = 500;
- #endif
- crypto_cfg.flags = HAICRYPT_CFG_F_CRYPTO | (cdir == HAICRYPT_CRYPTO_DIR_TX ? HAICRYPT_CFG_F_TX : 0) | (bAESGCM ? HAICRYPT_CFG_F_GCM : 0);
- crypto_cfg.xport = HAICRYPT_XPT_SRT;
- crypto_cfg.cryspr = HaiCryptCryspr_Get_Instance();
- crypto_cfg.key_len = (size_t)keylen;
- crypto_cfg.data_max_len = HAICRYPT_DEF_DATA_MAX_LENGTH; //MTU
- crypto_cfg.km_tx_period_ms = 0;//No HaiCrypt KM inject period, handled in SRT;
- crypto_cfg.km_refresh_rate_pkt = m_KmRefreshRatePkt == 0 ? HAICRYPT_DEF_KM_REFRESH_RATE : m_KmRefreshRatePkt;
- crypto_cfg.km_pre_announce_pkt = m_KmPreAnnouncePkt == 0 ? SRT_CRYPT_KM_PRE_ANNOUNCE : m_KmPreAnnouncePkt;
- crypto_cfg.secret = m_KmSecret;
- HLOGC(cnlog.Debug, log << "CRYPTO CFG: flags=" << CryptoFlags(crypto_cfg.flags) << " xport=" << crypto_cfg.xport << " cryspr=" << crypto_cfg.cryspr
- << " keylen=" << crypto_cfg.key_len << " passphrase_length=" << crypto_cfg.secret.len);
- if (HaiCrypt_Create(&crypto_cfg, (&w_hCrypto)) != HAICRYPT_OK)
- {
- LOGC(cnlog.Error, log << CONID() << "cryptoCtx: could not create " << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " crypto ctx");
- return false;
- }
- HLOGC(cnlog.Debug, log << CONID() << "cryptoCtx: CREATED crypto for dir=" << (cdir == HAICRYPT_CRYPTO_DIR_TX ? "tx" : "rx") << " keylen=" << keylen);
- return true;
- }
- #else
- bool srt::CCryptoControl::createCryptoCtx(HaiCrypt_Handle&, size_t, HaiCrypt_CryptoDir, bool)
- {
- return false;
- }
- #endif // SRT_ENABLE_ENCRYPTION
- srt::EncryptionStatus srt::CCryptoControl::encrypt(CPacket& w_packet SRT_ATR_UNUSED)
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- // Encryption not enabled - do nothing.
- if ( getSndCryptoFlags() == EK_NOENC )
- return ENCS_CLEAR;
- // Note that in case of GCM the header has to zero Retransmitted Packet Flag (R).
- // If TSBPD is disabled, timestamp also has to be zeroed.
- int rc = HaiCrypt_Tx_Data(m_hSndCrypto, ((uint8_t*)w_packet.getHeader()), ((uint8_t*)w_packet.m_pcData), w_packet.getLength());
- if (rc < 0)
- {
- return ENCS_FAILED;
- }
- else if ( rc > 0 )
- {
- // XXX what happens if the encryption is said to be "succeeded",
- // but the length is 0? Shouldn't this be treated as unwanted?
- w_packet.setLength(rc);
- }
- return ENCS_CLEAR;
- #else
- return ENCS_NOTSUP;
- #endif
- }
- srt::EncryptionStatus srt::CCryptoControl::decrypt(CPacket& w_packet SRT_ATR_UNUSED)
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- if (w_packet.getMsgCryptoFlags() == EK_NOENC)
- {
- HLOGC(cnlog.Debug, log << "CPacket::decrypt: packet not encrypted");
- return ENCS_CLEAR; // not encrypted, no need do decrypt, no flags to be modified
- }
- if (m_RcvKmState == SRT_KM_S_UNSECURED)
- {
- if (m_KmSecret.len != 0)
- {
- // We were unaware that the peer has set password,
- // but now here we are.
- m_RcvKmState = SRT_KM_S_SECURING;
- LOGC(cnlog.Note, log << "SECURITY UPDATE: Peer has surprised Agent with encryption, but KMX is pending - current packet size="
- << w_packet.getLength() << " dropped");
- return ENCS_FAILED;
- }
- else
- {
- // Peer has set a password, but Agent did not,
- // which means that it will be unable to decrypt
- // sent payloads anyway.
- m_RcvKmState = SRT_KM_S_NOSECRET;
- LOGP(cnlog.Warn, "SECURITY FAILURE: Agent has no PW, but Peer sender has declared one, can't decrypt");
- // This only informs about the state change; it will be also caught by the condition below
- }
- }
- if (m_RcvKmState != SRT_KM_S_SECURED)
- {
- // If not "secured", it means that it won't be able to decrypt packets,
- // so there's no point to even try to send them to HaiCrypt_Rx_Data.
- // Actually the current conditions concerning m_hRcvCrypto are such that this object
- // is cretaed in case of SRT_KM_S_BADSECRET, so it will simply fail to decrypt,
- // but with SRT_KM_S_NOSECRET m_hRcvCrypto is not even created (is NULL), which
- // will then cause an error to be reported, misleadingly. Simply don't try to
- // decrypt anything as long as you are not sure that the connection is secured.
- // This problem will occur every time a packet comes in, it's worth reporting,
- // but not with every single packet arriving. Print it once and turn off the flag;
- // it will be restored at the next attempt of KMX.
- if (!m_bErrorReported)
- {
- m_bErrorReported = true;
- LOGC(cnlog.Error, log << "SECURITY STATUS: " << KmStateStr(m_RcvKmState) << " - can't decrypt w_packet.");
- }
- HLOGC(cnlog.Debug, log << "Packet still not decrypted, status=" << KmStateStr(m_RcvKmState)
- << " - dropping size=" << w_packet.getLength());
- return ENCS_FAILED;
- }
- const int rc = HaiCrypt_Rx_Data(m_hRcvCrypto, ((uint8_t *)w_packet.getHeader()), ((uint8_t *)w_packet.m_pcData), w_packet.getLength());
- if (rc <= 0)
- {
- LOGC(cnlog.Note, log << "decrypt ERROR: HaiCrypt_Rx_Data failure=" << rc << " - returning failed decryption");
- // -1: decryption failure
- // 0: key not received yet
- return ENCS_FAILED;
- }
- // Otherwise: rc == decrypted text length.
- w_packet.setLength(rc); /* In case clr txt size is different from cipher txt */
- // Decryption succeeded. Update flags.
- w_packet.setMsgCryptoFlags(EK_NOENC);
- HLOGC(cnlog.Debug, log << "decrypt: successfully decrypted, resulting length=" << rc);
- return ENCS_CLEAR;
- #else
- return ENCS_NOTSUP;
- #endif
- }
- srt::CCryptoControl::~CCryptoControl()
- {
- #ifdef SRT_ENABLE_ENCRYPTION
- close();
- if (m_hSndCrypto)
- {
- HaiCrypt_Close(m_hSndCrypto);
- }
- if (m_hRcvCrypto)
- {
- HaiCrypt_Close(m_hRcvCrypto);
- }
- #endif
- }
|