| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <webrtc/DTLS.h> |
| |
| #include <webrtc/RTPSocketHandler.h> |
| |
| #include <https/SafeCallbackable.h> |
| #include <https/SSLSocket.h> |
| #include <https/Support.h> |
| |
| #include <android-base/logging.h> |
| |
| #include <sys/socket.h> |
| #include <unistd.h> |
| |
| #include <sstream> |
| |
| static int gDTLSInstanceIndex; |
| |
| // static |
| void DTLS::Init() { |
| SSL_library_init(); |
| SSL_load_error_strings(); |
| OpenSSL_add_ssl_algorithms(); |
| |
| auto err = srtp_init(); |
| CHECK_EQ(err, srtp_err_status_ok); |
| |
| gDTLSInstanceIndex = SSL_get_ex_new_index( |
| 0, const_cast<char *>("DTLSInstance index"), NULL, NULL, NULL); |
| |
| } |
| |
| bool DTLS::useCertificate(std::shared_ptr<X509> cert) { |
| // I'm assuming that ownership of the certificate is transferred, so I'm |
| // adding an extra reference... |
| CHECK_EQ(1, X509_up_ref(cert.get())); |
| |
| return cert != nullptr && 1 == SSL_CTX_use_certificate(mCtx, cert.get()); |
| } |
| |
| bool DTLS::usePrivateKey(std::shared_ptr<EVP_PKEY> key) { |
| // I'm assuming that ownership of the key in SSL_CTX_use_PrivateKey is |
| // transferred, so I'm adding an extra reference... |
| CHECK_EQ(1, EVP_PKEY_up_ref(key.get())); |
| |
| return key != nullptr |
| && 1 == SSL_CTX_use_PrivateKey(mCtx, key.get()) |
| && 1 == SSL_CTX_check_private_key(mCtx); |
| } |
| |
| DTLS::DTLS( |
| std::shared_ptr<RTPSocketHandler> handler, |
| DTLS::Mode mode, |
| std::shared_ptr<X509> cert, |
| std::shared_ptr<EVP_PKEY> key, |
| const std::string &remoteFingerprint, |
| bool useSRTP) |
| : mState(State::UNINITIALIZED), |
| mHandler(handler), |
| mMode(mode), |
| mRemoteFingerprint(remoteFingerprint), |
| mUseSRTP(useSRTP), |
| mCtx(nullptr), |
| mSSL(nullptr), |
| mBioR(nullptr), |
| mBioW(nullptr), |
| mSRTPInbound(nullptr), |
| mSRTPOutbound(nullptr) { |
| mCtx = SSL_CTX_new(DTLSv1_2_method()); |
| CHECK(mCtx); |
| |
| int result = SSL_CTX_set_cipher_list( |
| mCtx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); |
| |
| CHECK_EQ(result, 1); |
| |
| SSL_CTX_set_verify( |
| mCtx, |
| SSL_VERIFY_PEER |
| | SSL_VERIFY_CLIENT_ONCE |
| | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, |
| &DTLS::OnVerifyPeerCertificate); |
| |
| CHECK(useCertificate(cert)); |
| CHECK(usePrivateKey(key)); |
| |
| if (mUseSRTP) { |
| result = SSL_CTX_set_tlsext_use_srtp(mCtx, "SRTP_AES128_CM_SHA1_80"); |
| CHECK_EQ(result, 0); |
| } |
| |
| mSSL = SSL_new(mCtx); |
| CHECK(mSSL); |
| |
| SSL_set_ex_data(mSSL, gDTLSInstanceIndex, this); |
| |
| mBioR = BIO_new(BIO_s_mem()); |
| CHECK(mBioR); |
| |
| mBioW = BIO_new(BIO_s_mem()); |
| CHECK(mBioW); |
| |
| SSL_set_bio(mSSL, mBioR, mBioW); |
| |
| if (mode == Mode::CONNECT) { |
| SSL_set_connect_state(mSSL); |
| } else { |
| SSL_set_accept_state(mSSL); |
| } |
| } |
| |
| DTLS::~DTLS() { |
| if (mSRTPOutbound) { |
| srtp_dealloc(mSRTPOutbound); |
| mSRTPOutbound = nullptr; |
| } |
| |
| if (mSRTPInbound) { |
| srtp_dealloc(mSRTPInbound); |
| mSRTPInbound = nullptr; |
| } |
| |
| if (mSSL) { |
| SSL_shutdown(mSSL); |
| } |
| |
| SSL_free(mSSL); |
| mSSL = nullptr; |
| |
| mBioW = mBioR = nullptr; |
| |
| SSL_CTX_free(mCtx); |
| mCtx = nullptr; |
| } |
| |
| // static |
| int DTLS::OnVerifyPeerCertificate(int /* ok */, X509_STORE_CTX *ctx) { |
| LOG(VERBOSE) << "OnVerifyPeerCertificate"; |
| |
| SSL *ssl = static_cast<SSL *>(X509_STORE_CTX_get_ex_data( |
| ctx, SSL_get_ex_data_X509_STORE_CTX_idx())); |
| |
| DTLS *me = static_cast<DTLS *>(SSL_get_ex_data(ssl, gDTLSInstanceIndex)); |
| |
| std::unique_ptr<X509, std::function<void(X509 *)>> cert( |
| SSL_get_peer_certificate(ssl), X509_free); |
| |
| if (!cert) { |
| LOG(ERROR) << "SSLSocket::isPeerCertificateValid no certificate."; |
| |
| return 0; |
| } |
| |
| auto spacePos = me->mRemoteFingerprint.find(" "); |
| CHECK(spacePos != std::string::npos); |
| auto digestName = me->mRemoteFingerprint.substr(0, spacePos); |
| CHECK(!strcasecmp(digestName.c_str(), "sha-256")); |
| |
| const EVP_MD *digest = EVP_get_digestbyname("sha256"); |
| |
| unsigned char md[EVP_MAX_MD_SIZE]; |
| unsigned int n; |
| int res = X509_digest(cert.get(), digest, md, &n); |
| CHECK_EQ(res, 1); |
| |
| std::stringstream ss; |
| for (unsigned int i = 0; i < n; ++i) { |
| if (i > 0) { |
| ss << ":"; |
| } |
| |
| auto byte = md[i]; |
| |
| auto nibble = byte >> 4; |
| ss << (char)((nibble < 10) ? ('0' + nibble) : ('A' + nibble - 10)); |
| |
| nibble = byte & 0x0f; |
| ss << (char)((nibble < 10) ? ('0' + nibble) : ('A' + nibble - 10)); |
| } |
| |
| LOG(VERBOSE) |
| << "Client offered certificate w/ fingerprint " |
| << ss.str(); |
| |
| LOG(VERBOSE) << "should be: " << me->mRemoteFingerprint; |
| |
| auto remoteFingerprintHash = me->mRemoteFingerprint.substr(spacePos + 1); |
| bool match = !strcasecmp(remoteFingerprintHash.c_str(), ss.str().c_str()); |
| |
| if (!match) { |
| LOG(ERROR) |
| << "The peer's certificate's fingerprint does not match that " |
| << "published in the SDP!"; |
| } |
| |
| return match; |
| } |
| |
| void DTLS::connect(const sockaddr_storage &remoteAddr) { |
| CHECK_EQ(static_cast<int>(mState), static_cast<int>(State::UNINITIALIZED)); |
| |
| mRemoteAddr = remoteAddr; |
| mState = State::CONNECTING; |
| |
| tryConnecting(); |
| } |
| |
| void DTLS::doTheThing(int res) { |
| LOG(VERBOSE) << "doTheThing(" << res << ")"; |
| |
| int err = SSL_get_error(mSSL, res); |
| |
| switch (err) { |
| case SSL_ERROR_WANT_READ: |
| { |
| LOG(VERBOSE) << "SSL_ERROR_WANT_READ"; |
| |
| queueOutputDataFromDTLS(); |
| break; |
| } |
| |
| case SSL_ERROR_WANT_WRITE: |
| { |
| LOG(VERBOSE) << "SSL_ERROR_WANT_WRITE"; |
| break; |
| } |
| |
| case SSL_ERROR_NONE: |
| { |
| LOG(VERBOSE) << "SSL_ERROR_NONE"; |
| break; |
| } |
| |
| case SSL_ERROR_SYSCALL: |
| default: |
| { |
| LOG(ERROR) |
| << "DTLS stack returned error " |
| << err |
| << " (" |
| << SSL_state_string_long(mSSL) |
| << ")"; |
| } |
| } |
| } |
| |
| void DTLS::queueOutputDataFromDTLS() { |
| auto handler = mHandler.lock(); |
| |
| if (!handler) { |
| return; |
| } |
| |
| int n; |
| |
| do { |
| char buf[RTPSocketHandler::kMaxUDPPayloadSize]; |
| n = BIO_read(mBioW, buf, sizeof(buf)); |
| |
| if (n > 0) { |
| LOG(VERBOSE) << "queueing " << n << " bytes of output data from DTLS."; |
| |
| handler->queueDatagram( |
| mRemoteAddr, buf, static_cast<size_t>(n)); |
| } else if (BIO_should_retry(mBioW)) { |
| continue; |
| } else { |
| CHECK(!"Should not be here"); |
| } |
| } while (n > 0); |
| } |
| |
| void DTLS::tryConnecting() { |
| CHECK_EQ(static_cast<int>(mState), static_cast<int>(State::CONNECTING)); |
| |
| int res = |
| (mMode == Mode::CONNECT) |
| ? SSL_connect(mSSL) : SSL_accept(mSSL); |
| |
| if (res != 1) { |
| doTheThing(res); |
| } else { |
| queueOutputDataFromDTLS(); |
| |
| LOG(INFO) << "DTLS connection established."; |
| mState = State::CONNECTED; |
| |
| auto handler = mHandler.lock(); |
| if (handler) { |
| if (mUseSRTP) { |
| getKeyingMaterial(); |
| } |
| |
| handler->notifyDTLSConnected(); |
| } |
| } |
| } |
| |
| void DTLS::inject(const uint8_t *data, size_t size) { |
| LOG(VERBOSE) << "injecting " << size << " bytes into DTLS stack."; |
| |
| auto n = BIO_write(mBioR, data, size); |
| CHECK_EQ(n, static_cast<int>(size)); |
| |
| if (mState == State::CONNECTING) { |
| if (!SSL_is_init_finished(mSSL)) { |
| tryConnecting(); |
| } |
| } |
| } |
| |
| void DTLS::getKeyingMaterial() { |
| static constexpr char kLabel[] = "EXTRACTOR-dtls_srtp"; |
| |
| // These correspond to the chosen option SRTP_AES128_CM_SHA1_80, passed |
| // to SSL_CTX_set_tlsext_use_srtp before. c/f RFC 5764 4.1.2 |
| |
| uint8_t material[(SRTP_AES_128_KEY_LEN + SRTP_SALT_LEN) * 2]; |
| |
| auto res = SSL_export_keying_material( |
| mSSL, |
| material, |
| sizeof(material), |
| kLabel, |
| strlen(kLabel), |
| nullptr /* context */, |
| 0 /* contextlen */, |
| 0 /* use_context */); |
| |
| CHECK_EQ(res, 1); |
| |
| // LOG(INFO) << "keying material:"; |
| // hexdump(material, sizeof(material)); |
| |
| size_t offset = 0; |
| const uint8_t *clientKey = &material[offset]; |
| offset += SRTP_AES_128_KEY_LEN; |
| const uint8_t *serverKey = &material[offset]; |
| offset += SRTP_AES_128_KEY_LEN; |
| const uint8_t *clientSalt = &material[offset]; |
| offset += SRTP_SALT_LEN; |
| const uint8_t *serverSalt = &material[offset]; |
| offset += SRTP_SALT_LEN; |
| |
| CHECK_EQ(offset, sizeof(material)); |
| |
| std::string sendKey( |
| reinterpret_cast<const char *>(clientKey), SRTP_AES_128_KEY_LEN); |
| |
| sendKey.append( |
| reinterpret_cast<const char *>(clientSalt), SRTP_SALT_LEN); |
| |
| std::string receiveKey( |
| reinterpret_cast<const char *>(serverKey), SRTP_AES_128_KEY_LEN); |
| |
| receiveKey.append( |
| reinterpret_cast<const char *>(serverSalt), SRTP_SALT_LEN); |
| |
| if (mMode == Mode::CONNECT) { |
| CreateSRTPSession(&mSRTPInbound, receiveKey, ssrc_any_inbound); |
| CreateSRTPSession(&mSRTPOutbound, sendKey, ssrc_any_outbound); |
| } else { |
| CreateSRTPSession(&mSRTPInbound, sendKey, ssrc_any_inbound); |
| CreateSRTPSession(&mSRTPOutbound, receiveKey, ssrc_any_outbound); |
| } |
| } |
| |
| // static |
| void DTLS::CreateSRTPSession( |
| srtp_t *session, |
| const std::string &keyAndSalt, |
| srtp_ssrc_type_t direction) { |
| srtp_policy_t policy; |
| memset(&policy, 0, sizeof(policy)); |
| |
| srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp); |
| srtp_crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); |
| |
| policy.ssrc.type = direction; |
| policy.ssrc.value = 0; |
| |
| policy.key = |
| const_cast<unsigned char *>( |
| reinterpret_cast<const unsigned char *>(keyAndSalt.c_str())); |
| |
| policy.allow_repeat_tx = 1; |
| policy.next = nullptr; |
| |
| auto ret = srtp_create(session, &policy); |
| CHECK_EQ(ret, srtp_err_status_ok); |
| } |
| |
| size_t DTLS::protect(void *data, size_t size, bool isRTP) { |
| int len = static_cast<int>(size); |
| |
| auto ret = |
| isRTP |
| ? srtp_protect(mSRTPOutbound, data, &len) |
| : srtp_protect_rtcp(mSRTPOutbound, data, &len); |
| |
| CHECK_EQ(ret, srtp_err_status_ok); |
| |
| return static_cast<size_t>(len); |
| } |
| |
| size_t DTLS::unprotect(void *data, size_t size, bool isRTP) { |
| int len = static_cast<int>(size); |
| |
| auto ret = |
| isRTP |
| ? srtp_unprotect(mSRTPInbound, data, &len) |
| : srtp_unprotect_rtcp(mSRTPInbound, data, &len); |
| |
| if (ret == srtp_err_status_replay_fail) { |
| LOG(WARNING) |
| << "srtp_unprotect" |
| << (isRTP ? "" : "_rtcp") |
| << " returned srtp_err_status_replay_fail, ignoring packet."; |
| |
| return 0; |
| } |
| |
| CHECK_EQ(ret, srtp_err_status_ok); |
| |
| return static_cast<size_t>(len); |
| } |
| |
| ssize_t DTLS::readApplicationData(void *data, size_t size) { |
| auto res = SSL_read(mSSL, data, size); |
| |
| if (res < 0) { |
| doTheThing(res); |
| return -1; |
| } |
| |
| return res; |
| } |
| |
| ssize_t DTLS::writeApplicationData(const void *data, size_t size) { |
| auto res = SSL_write(mSSL, data, size); |
| |
| queueOutputDataFromDTLS(); |
| |
| // May have to queue the data and "doTheThing" on failure... |
| CHECK_EQ(res, static_cast<int>(size)); |
| |
| return res; |
| } |