Index: webrtc/p2p/base/dtlstransportchannel.cc |
diff --git a/webrtc/p2p/base/dtlstransportchannel.cc b/webrtc/p2p/base/dtlstransportchannel.cc |
index 3dde8fea0535199e31c6ff75c4c9575d21b37b16..d95bdcd916b1814ae4eda1eaeebd4f333e59f2ac 100644 |
--- a/webrtc/p2p/base/dtlstransportchannel.cc |
+++ b/webrtc/p2p/base/dtlstransportchannel.cc |
@@ -179,7 +179,7 @@ bool DtlsTransportChannelWrapper::SetSslMaxProtocolVersion( |
} |
bool DtlsTransportChannelWrapper::SetSslRole(rtc::SSLRole role) { |
- if (dtls_state() == DTLS_TRANSPORT_CONNECTED) { |
+ if (dtls_) { |
if (ssl_role_ != role) { |
LOG(LS_ERROR) << "SSL Role can't be reversed after the session is setup."; |
return false; |
@@ -235,12 +235,33 @@ bool DtlsTransportChannelWrapper::SetRemoteFingerprint( |
} |
// At this point we know we are doing DTLS |
+ bool fingerprint_changing = remote_fingerprint_value_.size() > 0u; |
remote_fingerprint_value_ = std::move(remote_fingerprint_value); |
remote_fingerprint_algorithm_ = digest_alg; |
- if (dtls_) { |
- // If the fingerprint is changing, we'll tear down the DTLS association and |
- // create a new one, resetting our state. |
+ if (dtls_ && !fingerprint_changing) { |
+ // This can occur if DTLS is set up before a remote fingerprint is |
+ // received. For instance, if we set up DTLS due to receiving an early |
+ // ClientHello. |
+ rtc::SSLPeerCertificateDigestError err; |
+ if (!dtls_->SetPeerCertificateDigest( |
+ remote_fingerprint_algorithm_, |
+ reinterpret_cast<unsigned char*>(remote_fingerprint_value_.data()), |
+ remote_fingerprint_value_.size(), &err)) { |
+ LOG_J(LS_ERROR, this) << "Couldn't set DTLS certificate digest."; |
+ set_dtls_state(DTLS_TRANSPORT_FAILED); |
+ // If the error is "verification failed", don't return false, because |
+ // this means the fingerprint was formatted correctly but didn't match |
+ // the certificate from the DTLS handshake. Thus the DTLS state should go |
+ // to "failed", but SetRemoteDescription shouldn't fail. |
+ return err == rtc::SSLPeerCertificateDigestError::VERIFICATION_FAILED; |
+ } |
+ return true; |
+ } |
+ |
+ // If the fingerprint is changing, we'll tear down the DTLS association and |
+ // create a new one, resetting our state. |
+ if (dtls_ && fingerprint_changing) { |
dtls_.reset(nullptr); |
set_dtls_state(DTLS_TRANSPORT_NEW); |
set_writable(false); |
@@ -282,7 +303,8 @@ bool DtlsTransportChannelWrapper::SetupDtls() { |
dtls_->SignalEvent.connect(this, &DtlsTransportChannelWrapper::OnDtlsEvent); |
dtls_->SignalSSLHandshakeError.connect( |
this, &DtlsTransportChannelWrapper::OnDtlsHandshakeError); |
- if (!dtls_->SetPeerCertificateDigest( |
+ if (remote_fingerprint_value_.size() && |
+ !dtls_->SetPeerCertificateDigest( |
remote_fingerprint_algorithm_, |
reinterpret_cast<unsigned char*>(remote_fingerprint_value_.data()), |
remote_fingerprint_value_.size())) { |
@@ -401,6 +423,10 @@ int DtlsTransportChannelWrapper::SendPacket( |
} |
} |
+bool DtlsTransportChannelWrapper::IsDtlsConnected() { |
+ return dtls_ && dtls_->IsTlsConnected(); |
+} |
+ |
// The state transition logic here is as follows: |
// (1) If we're not doing DTLS-SRTP, then the state is just the |
// state of the underlying impl() |
@@ -481,6 +507,14 @@ void DtlsTransportChannelWrapper::OnReadPacket( |
LOG_J(LS_INFO, this) << "Caching DTLS ClientHello packet until DTLS is " |
<< "started."; |
cached_client_hello_.SetData(data, size); |
+ // If we haven't started setting up DTLS yet (because we don't have a |
+ // remote fingerprint/role), we can use the client hello as a clue that |
+ // the peer has chosen the client role, and proceed with the handshake. |
+ // The fingerprint will be verified when it's set. |
+ if (!dtls_ && local_certificate_) { |
+ SetSslRole(rtc::SSL_SERVER); |
+ SetupDtls(); |
+ } |
} else { |
LOG_J(LS_INFO, this) << "Not a DTLS ClientHello packet; dropping."; |
} |
@@ -554,8 +588,20 @@ void DtlsTransportChannelWrapper::OnDtlsEvent(rtc::StreamInterface* dtls, |
if (sig & rtc::SE_READ) { |
char buf[kMaxDtlsPacketLen]; |
size_t read; |
- if (dtls_->Read(buf, sizeof(buf), &read, NULL) == rtc::SR_SUCCESS) { |
+ int read_error; |
+ rtc::StreamResult ret = dtls_->Read(buf, sizeof(buf), &read, &read_error); |
+ if (ret == rtc::SR_SUCCESS) { |
SignalReadPacket(this, buf, read, rtc::CreatePacketTime(0), 0); |
+ } else if (ret == rtc::SR_EOS) { |
+ // Remote peer shut down the association with no error. |
+ LOG_J(LS_INFO, this) << "DTLS channel closed"; |
+ set_writable(false); |
+ set_dtls_state(DTLS_TRANSPORT_CLOSED); |
+ } else if (ret == rtc::SR_ERROR) { |
+ // Remote peer shut down the association with an error. |
+ LOG_J(LS_INFO, this) << "DTLS channel error, code=" << read_error; |
+ set_writable(false); |
+ set_dtls_state(DTLS_TRANSPORT_FAILED); |
} |
} |
if (sig & rtc::SE_CLOSE) { |