dtls主要用来交换srtp的
#在ICE完成后,开始dtls过程
void WebRtcTransport::MayRunDtlsTransport()
{
MS_TRACE();
// Do nothing if we have the same local DTLS role as the DTLS transport.
// NOTE: local role in DTLS transport can be NONE, but not ours.
if (this->dtlsTransport->GetLocalRole() == this->dtlsRole)
return;
// Check our local DTLS role.
switch (this->dtlsRole)
{
// If still 'auto' then transition to 'server' if ICE is 'connected' or
// 'completed'.
case RTC::DtlsTransport::Role::AUTO:
{
if (
this->iceServer->GetState() == RTC::IceServer::IceState::CONNECTED ||
this->iceServer->GetState() == RTC::IceServer::IceState::COMPLETED)
{
MS_DEBUG_TAG(
dtls, "transition from DTLS local role 'auto' to 'server' and running DTLS transport");
this->dtlsRole = RTC::DtlsTransport::Role::SERVER;
this->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER);
}
break;
}
// 'client' is only set if a 'connect' request was previously called with
// remote DTLS role 'server'.
//
// If 'client' then wait for ICE to be 'completed' (got USE-CANDIDATE).
//
// NOTE: This is the theory, however let's be more flexible as told here:
// https://bugs.chromium.org/p/webrtc/issues/detail?id=3661
case RTC::DtlsTransport::Role::CLIENT:
{
if (
this->iceServer->GetState() == RTC::IceServer::IceState::CONNECTED ||
this->iceServer->GetState() == RTC::IceServer::IceState::COMPLETED)
{
MS_DEBUG_TAG(dtls, "running DTLS transport in local role 'client'");
this->dtlsTransport->Run(RTC::DtlsTransport::Role::CLIENT);
}
break;
}
// If 'server' then run the DTLS transport if ICE is 'connected' (not yet
// USE-CANDIDATE) or 'completed'.
case RTC::DtlsTransport::Role::SERVER:
{
if (
this->iceServer->GetState() == RTC::IceServer::IceState::CONNECTED ||
this->iceServer->GetState() == RTC::IceServer::IceState::COMPLETED)
{
MS_DEBUG_TAG(dtls, "running DTLS transport in local role 'server'");
this->dtlsTransport->Run(RTC::DtlsTransport::Role::SERVER);
}
break;
}
case RTC::DtlsTransport::Role::NONE:
{
MS_ABORT("local DTLS role not set");
}
}
}
#dtls过程
void DtlsTransport::Run(Role localRole)
{
MS_TRACE();
MS_ASSERT(
localRole == Role::CLIENT || localRole == Role::SERVER,
"local DTLS role must be 'client' or 'server'");
Role previousLocalRole = this->localRole;
if (localRole == previousLocalRole)
{
MS_ERROR("same local DTLS role provided, doing nothing");
return;
}
// If the previous local DTLS role was 'client' or 'server' do reset.
if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
{
MS_DEBUG_TAG(dtls, "resetting DTLS due to local role change");
Reset();
}
// Update local role.
this->localRole = localRole;
// Set state and notify the listener.
this->state = DtlsState::CONNECTING;
this->listener->OnDtlsTransportConnecting(this);
switch (this->localRole)
{
case Role::CLIENT:
{
MS_DEBUG_TAG(dtls, "running [role:client]");
//设置主动连接
SSL_set_connect_state(this->ssl);
//开始握手
SSL_do_handshake(this->ssl);
//发送数据包
SendPendingOutgoingDtlsData();
//设置超时时间
SetTimeout();
break;
}
case Role::SERVER:
{
MS_DEBUG_TAG(dtls, "running [role:server]");
//等待握手
SSL_set_accept_state(this->ssl);
//握手
SSL_do_handshake(this->ssl);
break;
}
default:
{
MS_ABORT("invalid local DTLS role");
}
}
}
#dtls包处理
void DtlsTransport::ProcessDtlsData(const uint8_t* data, size_t len)
{
MS_TRACE();
int written;
int read;
if (!IsRunning())
{
MS_ERROR("cannot process data while not running");
return;
}
// Write the received DTLS data into the sslBioFromNetwork.
written = BIO_write(this->sslBioFromNetwork, (const void*)data, static_cast(len));
if (written != static_cast(len))
{
MS_WARN_TAG(
dtls,
"OpenSSL BIO_write() wrote less (%zu bytes) than given data (%zu bytes)",
static_cast(written),
len);
}
// Must call SSL_read() to process received DTLS data.
read = SSL_read(this->ssl, (void*)DtlsTransport::sslReadBuffer, SslReadBufferSize);
// Send data if it's ready.
SendPendingOutgoingDtlsData();
// Check SSL status and return if it is bad/closed.
if (!CheckStatus(read))
return;
// Set/update the DTLS timeout.
if (!SetTimeout())
return;
// Application data received. Notify to the listener.
if (read > 0)
{
// It is allowed to receive DTLS data even before validating remote fingerprint.
if (!this->handshakeDone)
{
MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
return;
}
// Notify the listener.
this->listener->OnDtlsTransportApplicationDataReceived(
this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast(read));
}
}
#什么时候handshake完毕?
inline void DtlsTransport::OnSslInfo(int where, int ret)
{
MS_TRACE();
int w = where & -SSL_ST_MASK;
const char* role;
if ((w & SSL_ST_CONNECT) != 0)
role = "client";
else if ((w & SSL_ST_ACCEPT) != 0)
role = "server";
else
role = "undefined";
if ((where & SSL_CB_LOOP) != 0)
{
MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(this->ssl));
}
else if ((where & SSL_CB_ALERT) != 0)
{
const char* alertType;
switch (*SSL_alert_type_string(ret))
{
case 'W':
alertType = "warning";
break;
case 'F':
alertType = "fatal";
break;
default:
alertType = "undefined";
}
if ((where & SSL_CB_READ) != 0)
{
MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
}
else if ((where & SSL_CB_WRITE) != 0)
{
MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
}
else
{
MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
}
}
else if ((where & SSL_CB_EXIT) != 0)
{
if (ret == 0)
MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl));
else if (ret < 0)
MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl));
}
else if ((where & SSL_CB_HANDSHAKE_START) != 0)
{
MS_DEBUG_TAG(dtls, "DTLS handshake start");
}
else if ((where & SSL_CB_HANDSHAKE_DONE) != 0)
{
MS_DEBUG_TAG(dtls, "DTLS handshake done");
this->handshakeDoneNow = true;
}
// NOTE: checking SSL_get_shutdown(this->ssl) & SSL_RECEIVED_SHUTDOWN here upon
// receipt of a close alert does not work (the flag is set after this callback).
}
可以看到是一个回调,在这里设置的
// Set SSL info callback.
SSL_CTX_set_info_callback(DtlsTransport::sslCtx, onSslInfo);
#dtls如何初始化?
void DtlsTransport::ClassInit()
{
MS_TRACE();
// Generate a X509 certificate and private key (unless PEM files are provided).
if (
Settings::configuration.dtlsCertificateFile.empty() ||
Settings::configuration.dtlsPrivateKeyFile.empty())
{
GenerateCertificateAndPrivateKey();
}
else
{
ReadCertificateAndPrivateKeyFromFiles();
}
// Create a global SSL_CTX.
CreateSslCtx();
// Generate certificate fingerprints.
GenerateFingerprints();
}
读取证书和私钥
void DtlsTransport::ReadCertificateAndPrivateKeyFromFiles()
{
MS_TRACE();
FILE* file{ nullptr };
file = fopen(Settings::configuration.dtlsCertificateFile.c_str(), "r");
if (file == nullptr)
{
MS_ERROR("error reading DTLS certificate file: %s", std::strerror(errno));
goto error;
}
DtlsTransport::certificate = PEM_read_X509(file, nullptr, nullptr, nullptr);
if (DtlsTransport::certificate == nullptr)
{
LOG_OPENSSL_ERROR("PEM_read_X509() failed");
goto error;
}
fclose(file);
file = fopen(Settings::configuration.dtlsPrivateKeyFile.c_str(), "r");
if (file == nullptr)
{
MS_ERROR("error reading DTLS private key file: %s", std::strerror(errno));
goto error;
}
DtlsTransport::privateKey = PEM_read_PrivateKey(file, nullptr, nullptr, nullptr);
if (DtlsTransport::privateKey == nullptr)
{
LOG_OPENSSL_ERROR("PEM_read_PrivateKey() failed");
goto error;
}
fclose(file);
return;
error:
MS_THROW_ERROR("error reading DTLS certificate and private key PEM files");
}
创建ssl_ctx和指纹
void DtlsTransport::CreateSslCtx()
{
MS_TRACE();
std::string dtlsSrtpProfiles;
EC_KEY* ecdh{ nullptr };
int ret;
/* Set the global DTLS context. */
// Both DTLS 1.0 and 1.2 (requires OpenSSL >= 1.1.0).
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
DtlsTransport::sslCtx = SSL_CTX_new(DTLS_method());
// Just DTLS 1.0 (requires OpenSSL >= 1.0.1).
#elif (OPENSSL_VERSION_NUMBER >= 0x10001000L)
DtlsTransport::sslCtx = SSL_CTX_new(DTLSv1_method());
#else
#error "too old OpenSSL version"
#endif
if (DtlsTransport::sslCtx == nullptr)
{
LOG_OPENSSL_ERROR("SSL_CTX_new() failed");
goto error;
}
ret = SSL_CTX_use_certificate(DtlsTransport::sslCtx, DtlsTransport::certificate);
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_use_certificate() failed");
goto error;
}
ret = SSL_CTX_use_PrivateKey(DtlsTransport::sslCtx, DtlsTransport::privateKey);
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_use_PrivateKey() failed");
goto error;
}
ret = SSL_CTX_check_private_key(DtlsTransport::sslCtx);
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_check_private_key() failed");
goto error;
}
// Set options.
SSL_CTX_set_options(
DtlsTransport::sslCtx,
SSL_OP_CIPHER_SERVER_PREFERENCE | SSL_OP_NO_TICKET | SSL_OP_SINGLE_ECDH_USE |
SSL_OP_NO_QUERY_MTU);
// Don't use sessions cache.
SSL_CTX_set_session_cache_mode(DtlsTransport::sslCtx, SSL_SESS_CACHE_OFF);
// Read always as much into the buffer as possible.
// NOTE: This is the default for DTLS, but a bug in non latest OpenSSL
// versions makes this call required.
SSL_CTX_set_read_ahead(DtlsTransport::sslCtx, 1);
SSL_CTX_set_verify_depth(DtlsTransport::sslCtx, 4);
// Require certificate from peer.
SSL_CTX_set_verify(
DtlsTransport::sslCtx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, onSslCertificateVerify);
// Set SSL info callback.
SSL_CTX_set_info_callback(DtlsTransport::sslCtx, onSslInfo);
// Set ciphers.
ret = SSL_CTX_set_cipher_list(
DtlsTransport::sslCtx, "ALL:!ADH:!LOW:!EXP:!MD5:!aNULL:!eNULL:@STRENGTH");
if (ret == 0)
{
LOG_OPENSSL_ERROR("SSL_CTX_set_cipher_list() failed");
goto error;
}
// Enable ECDH ciphers.
// DOC: http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters
// NOTE: https://code.google.com/p/chromium/issues/detail?id=406458
// NOTE: https://bugs.ruby-lang.org/issues/12324
//
// Nothing to be done in OpenSSL >= 1.1.0.
#if (OPENSSL_VERSION_NUMBER >= 0x10100000L)
// For OpenSSL >= 1.0.2.
#elif (OPENSSL_VERSION_NUMBER >= 0x10002000L)
SSL_CTX_set_ecdh_auto(DtlsTransport::sslCtx, 1);
// Older versions.
#else
ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
if (!ecdh)
{
LOG_OPENSSL_ERROR("EC_KEY_new_by_curve_name() failed");
goto error;
}
if (SSL_CTX_set_tmp_ecdh(DtlsTransport::sslCtx, ecdh) != 1)
{
LOG_OPENSSL_ERROR("SSL_CTX_set_tmp_ecdh() failed");
goto error;
}
EC_KEY_free(ecdh);
ecdh = nullptr;
#endif
// Set the "use_srtp" DTLS extension.
for (auto it = DtlsTransport::srtpProfiles.begin(); it != DtlsTransport::srtpProfiles.end(); ++it)
{
if (it != DtlsTransport::srtpProfiles.begin())
dtlsSrtpProfiles += ":";
SrtpProfileMapEntry* profileEntry = std::addressof(*it);
dtlsSrtpProfiles += profileEntry->name;
}
MS_DEBUG_2TAGS(dtls, srtp, "setting SRTP profiles for DTLS: %s", dtlsSrtpProfiles.c_str());
// NOTE: This function returns 0 on success.
ret = SSL_CTX_set_tlsext_use_srtp(DtlsTransport::sslCtx, dtlsSrtpProfiles.c_str());
if (ret != 0)
{
MS_ERROR("SSL_CTX_set_tlsext_use_srtp() failed when entering '%s'", dtlsSrtpProfiles.c_str());
LOG_OPENSSL_ERROR("SSL_CTX_set_tlsext_use_srtp() failed");
goto error;
}
return;
error:
if (DtlsTransport::sslCtx != nullptr)
{
SSL_CTX_free(DtlsTransport::sslCtx);
DtlsTransport::sslCtx = nullptr;
}
if (ecdh != nullptr)
EC_KEY_free(ecdh);
MS_THROW_ERROR("SSL context creation failed");
}
void DtlsTransport::GenerateFingerprints()
{
MS_TRACE();
for (auto& kv : DtlsTransport::string2FingerprintAlgorithm)
{
const std::string& algorithmString = kv.first;
FingerprintAlgorithm algorithm = kv.second;
uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
unsigned int size{ 0 };
char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
const EVP_MD* hashFunction;
int ret;
switch (algorithm)
{
case FingerprintAlgorithm::SHA1:
hashFunction = EVP_sha1();
break;
case FingerprintAlgorithm::SHA224:
hashFunction = EVP_sha224();
break;
case FingerprintAlgorithm::SHA256:
hashFunction = EVP_sha256();
break;
case FingerprintAlgorithm::SHA384:
hashFunction = EVP_sha384();
break;
case FingerprintAlgorithm::SHA512:
hashFunction = EVP_sha512();
break;
default:
MS_THROW_ERROR("unknown algorithm");
}
ret = X509_digest(DtlsTransport::certificate, hashFunction, binaryFingerprint, &size);
if (ret == 0)
{
MS_ERROR("X509_digest() failed");
MS_THROW_ERROR("Fingerprints generation failed");
}
// Convert to hexadecimal format in uppercase with colons.
for (unsigned int i{ 0 }; i < size; ++i)
{
std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
}
hexFingerprint[(size * 3) - 1] = '\0';
MS_DEBUG_TAG(dtls, "%-7s fingerprint: %s", algorithmString.c_str(), hexFingerprint);
// Store it in the vector.
DtlsTransport::Fingerprint fingerprint;
fingerprint.algorithm = DtlsTransport::GetFingerprintAlgorithm(algorithmString);
fingerprint.value = hexFingerprint;
DtlsTransport::localFingerprints.push_back(fingerprint);
}
}
#最后dtls handshake完了后交换的数据如下
inline void DtlsTransport::ExtractSrtpKeys(RTC::SrtpSession::Profile srtpProfile)
{
MS_TRACE();
size_t srtpKeyLength{ 0 };
size_t srtpSaltLength{ 0 };
size_t srtpMasterLength{ 0 };
switch (srtpProfile)
{
case RTC::SrtpSession::Profile::AES_CM_128_HMAC_SHA1_80:
case RTC::SrtpSession::Profile::AES_CM_128_HMAC_SHA1_32:
{
srtpKeyLength = SrtpMasterKeyLength;
srtpSaltLength = SrtpMasterSaltLength;
srtpMasterLength = SrtpMasterLength;
break;
}
case RTC::SrtpSession::Profile::AEAD_AES_256_GCM:
{
srtpKeyLength = SrtpAesGcm256MasterKeyLength;
srtpSaltLength = SrtpAesGcm256MasterSaltLength;
srtpMasterLength = SrtpAesGcm256MasterLength;
break;
}
case RTC::SrtpSession::Profile::AEAD_AES_128_GCM:
{
srtpKeyLength = SrtpAesGcm128MasterKeyLength;
srtpSaltLength = SrtpAesGcm128MasterSaltLength;
srtpMasterLength = SrtpAesGcm128MasterLength;
break;
}
default:
{
MS_ABORT("unknown SRTP profile");
}
}
uint8_t srtpMaterial[srtpMasterLength * 2];
uint8_t* srtpLocalKey;
uint8_t* srtpLocalSalt;
uint8_t* srtpRemoteKey;
uint8_t* srtpRemoteSalt;
uint8_t srtpLocalMasterKey[srtpMasterLength];
uint8_t srtpRemoteMasterKey[srtpMasterLength];
int ret;
ret = SSL_export_keying_material(
this->ssl, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0);
MS_ASSERT(ret != 0, "SSL_export_keying_material() failed");
switch (this->localRole)
{
case Role::SERVER:
{
srtpRemoteKey = srtpMaterial;
srtpLocalKey = srtpRemoteKey + srtpKeyLength;
srtpRemoteSalt = srtpLocalKey + srtpKeyLength;
srtpLocalSalt = srtpRemoteSalt + srtpSaltLength;
break;
}
case Role::CLIENT:
{
srtpLocalKey = srtpMaterial;
srtpRemoteKey = srtpLocalKey + srtpKeyLength;
srtpLocalSalt = srtpRemoteKey + srtpKeyLength;
srtpRemoteSalt = srtpLocalSalt + srtpSaltLength;
break;
}
default:
{
MS_ABORT("no DTLS role set");
}
}
// Create the SRTP local master key.
std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
// Create the SRTP remote master key.
std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
// Set state and notify the listener.
this->state = DtlsState::CONNECTED;
this->listener->OnDtlsTransportConnected(
this,
srtpProfile,
srtpLocalMasterKey,
srtpMasterLength,
srtpRemoteMasterKey,
srtpMasterLength,
this->remoteCert);
}