diff --git a/talk/media/sctp/sctpdataengine_unittest.cc b/talk/media/sctp/sctpdataengine_unittest.cc index c540e7c810..d406fa18cd 100644 --- a/talk/media/sctp/sctpdataengine_unittest.cc +++ b/talk/media/sctp/sctpdataengine_unittest.cc @@ -45,6 +45,11 @@ #include "webrtc/base/ssladapter.h" #include "webrtc/base/thread.h" +#ifdef HAVE_NSS_SSL_H +// TODO(thorcarpenter): Remove after webrtc switches over to BoringSSL. +#include "webrtc/base/nssstreamadapter.h" +#endif // HAVE_NSS_SSL_H + enum { MSG_PACKET = 1, }; @@ -218,6 +223,12 @@ class SctpDataMediaChannelTest : public testing::Test, // usrsctp uses the NSS random number generator on non-Android platforms, // so we need to initialize SSL. static void SetUpTestCase() { +#ifdef HAVE_NSS_SSL_H + // TODO(thorcarpenter): Remove after webrtc switches over to BoringSSL. + if (!rtc::NSSContext::InitializeSSL(NULL)) { + LOG(LS_WARNING) << "Unabled to initialize NSS."; + } +#endif // HAVE_NSS_SSL_H } virtual void SetUp() { diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn index 83f14b4152..027fa45a01 100644 --- a/webrtc/base/BUILD.gn +++ b/webrtc/base/BUILD.gn @@ -39,6 +39,14 @@ config("openssl_config") { ] } +config("nss_config") { + defines = [ + "SSL_USE_NSS", + "HAVE_NSS_SSL_H", + "SSL_USE_NSS_RNG", + ] +} + config("ios_config") { libs = [ "CFNetwork.framework", @@ -76,6 +84,15 @@ if (is_linux && !build_with_chromium) { deps = [ "//third_party/boringssl", ] + } else { + deps = [ + "//net/third_party/nss/ssl:libssl", + ] + + public_configs = [ + "//net/third_party/nss/ssl:ssl_config", + "//third_party/nss:system_nss_no_ssl_config", + ] } } } @@ -478,6 +495,31 @@ static_library("rtc_base") { "opensslstreamadapter.cc", "opensslstreamadapter.h", ] + } else { + public_configs += [ ":nss_config" ] + if (rtc_build_ssl) { + if (build_with_chromium) { + deps += [ "//crypto:platform" ] + } else { + deps += [ "//net/third_party/nss/ssl:libssl" ] + if (is_linux) { + deps += [ ":linux_system_ssl" ] + } else { + deps += [ + "//third_party/nss:nspr", + "//third_party/nss:nss", + ] + } + } + } else { + configs += [ "external_ssl_library" ] + } + sources += [ + "nssidentity.cc", + "nssidentity.h", + "nssstreamadapter.cc", + "nssstreamadapter.h", + ] } if (is_android) { diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp index 96dee9254a..6593729980 100644 --- a/webrtc/base/base.gyp +++ b/webrtc/base/base.gyp @@ -526,6 +526,51 @@ ], }], ], + }, { + 'sources': [ + 'nssidentity.cc', + 'nssidentity.h', + 'nssstreamadapter.cc', + 'nssstreamadapter.h', + ], + 'conditions': [ + ['use_legacy_ssl_defaults!=1', { + 'defines': [ + 'SSL_USE_NSS', + 'HAVE_NSS_SSL_H', + 'SSL_USE_NSS_RNG', + ], + 'direct_dependent_settings': { + 'defines': [ + 'SSL_USE_NSS', + 'HAVE_NSS_SSL_H', + 'SSL_USE_NSS_RNG', + ], + }, + }], + ['build_ssl==1', { + 'conditions': [ + # On some platforms, the rest of NSS is bundled. On others, + # it's pulled from the system. + ['OS == "mac" or OS == "ios"', { + 'dependencies': [ + '<(DEPTH)/net/third_party/nss/ssl.gyp:libssl', + '<(DEPTH)/third_party/nss/nss.gyp:nspr', + '<(DEPTH)/third_party/nss/nss.gyp:nss', + ], + }], + ['os_posix == 1 and OS != "mac" and OS != "ios" and OS != "android"', { + 'dependencies': [ + '<(DEPTH)/build/linux/system.gyp:ssl', + ], + }], + ], + }, { + 'include_dirs': [ + '<(ssl_root)', + ], + }], + ], }], ['OS == "android"', { 'link_settings': { diff --git a/webrtc/base/helpers.cc b/webrtc/base/helpers.cc index 0102c10e7b..e12ba107ea 100644 --- a/webrtc/base/helpers.cc +++ b/webrtc/base/helpers.cc @@ -16,6 +16,14 @@ #include "webrtc/base/sslconfig.h" #if defined(SSL_USE_OPENSSL) #include +#elif defined(SSL_USE_NSS_RNG) +// Hack: Define+undefine int64 and uint64 to avoid typedef conflict with NSS. +// TODO(kjellander): Remove when webrtc:4497 is completed. +#define uint64 foo_uint64 +#define int64 foo_int64 +#include "pk11func.h" +#undef uint64 +#undef int64 #else #if defined(WEBRTC_WIN) #define WIN32_LEAN_AND_MEAN diff --git a/webrtc/base/nssidentity.cc b/webrtc/base/nssidentity.cc new file mode 100644 index 0000000000..6511942a34 --- /dev/null +++ b/webrtc/base/nssidentity.cc @@ -0,0 +1,581 @@ +/* + * Copyright 2012 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_NSS_SSL_H + +#include "webrtc/base/nssidentity.h" + +#include "cert.h" +#include "cryptohi.h" +#include "keyhi.h" +#include "nss.h" +#include "pk11pub.h" +#include "sechash.h" + +#include "webrtc/base/logging.h" +#include "webrtc/base/helpers.h" +#include "webrtc/base/nssstreamadapter.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/base/stringutils.h" + +namespace rtc { + +// Certificate validity lifetime in seconds. +static const int CERTIFICATE_LIFETIME = 60*60*24*30; // 30 days, arbitrarily +// Certificate validity window in seconds. +// This is to compensate for slightly incorrect system clocks. +static const int CERTIFICATE_WINDOW = -60*60*24; + +NSSKeyPair::~NSSKeyPair() { + if (privkey_) + SECKEY_DestroyPrivateKey(privkey_); + if (pubkey_) + SECKEY_DestroyPublicKey(pubkey_); +} + +NSSKeyPair* NSSKeyPair::Generate(KeyType key_type) { + SECKEYPrivateKey* privkey = nullptr; + SECKEYPublicKey* pubkey = nullptr; + SSLKEAType ssl_kea_type; + if (key_type == KT_RSA) { + PK11RSAGenParams rsa_params; + rsa_params.keySizeInBits = 1024; + rsa_params.pe = 0x010001; // 65537 -- a common RSA public exponent. + + privkey = PK11_GenerateKeyPair( + NSSContext::GetSlot(), CKM_RSA_PKCS_KEY_PAIR_GEN, &rsa_params, &pubkey, + PR_FALSE /*permanent*/, PR_FALSE /*sensitive*/, nullptr); + + ssl_kea_type = ssl_kea_rsa; + } else if (key_type == KT_ECDSA) { + unsigned char param_buf[12]; // OIDs are small + SECItem ecdsa_params = {siBuffer, param_buf, sizeof(param_buf)}; + SECOidData* oid_data = SECOID_FindOIDByTag(SEC_OID_SECG_EC_SECP256R1); + if (!oid_data || oid_data->oid.len > sizeof(param_buf) - 2) { + LOG(LS_ERROR) << "oid_data incorrect: " << oid_data->oid.len; + return nullptr; + } + ecdsa_params.data[0] = SEC_ASN1_OBJECT_ID; + ecdsa_params.data[1] = oid_data->oid.len; + memcpy(ecdsa_params.data + 2, oid_data->oid.data, oid_data->oid.len); + ecdsa_params.len = oid_data->oid.len + 2; + + privkey = PK11_GenerateKeyPair( + NSSContext::GetSlot(), CKM_EC_KEY_PAIR_GEN, &ecdsa_params, &pubkey, + PR_FALSE /*permanent*/, PR_FALSE /*sensitive*/, nullptr); + + ssl_kea_type = ssl_kea_ecdh; + } else { + LOG(LS_ERROR) << "Key type requested not understood"; + return nullptr; + } + + if (!privkey) { + LOG(LS_ERROR) << "Couldn't generate key pair: " << PORT_GetError(); + return nullptr; + } + + return new NSSKeyPair(privkey, pubkey, ssl_kea_type); +} + +// Just make a copy. +NSSKeyPair* NSSKeyPair::GetReference() { + SECKEYPrivateKey* privkey = SECKEY_CopyPrivateKey(privkey_); + if (!privkey) + return nullptr; + + SECKEYPublicKey* pubkey = SECKEY_CopyPublicKey(pubkey_); + if (!pubkey) { + SECKEY_DestroyPrivateKey(privkey); + return nullptr; + } + + return new NSSKeyPair(privkey, pubkey, ssl_kea_type_); +} + +NSSCertificate::NSSCertificate(CERTCertificate* cert) + : certificate_(CERT_DupCertificate(cert)) { + ASSERT(certificate_ != nullptr); +} + +static void DeleteCert(SSLCertificate* cert) { + delete cert; +} + +NSSCertificate::NSSCertificate(CERTCertList* cert_list) { + // Copy the first cert into certificate_. + CERTCertListNode* node = CERT_LIST_HEAD(cert_list); + certificate_ = CERT_DupCertificate(node->cert); + + // Put any remaining certificates into the chain. + node = CERT_LIST_NEXT(node); + std::vector certs; + for (; !CERT_LIST_END(node, cert_list); node = CERT_LIST_NEXT(node)) { + certs.push_back(new NSSCertificate(node->cert)); + } + + if (!certs.empty()) + chain_.reset(new SSLCertChain(certs)); + + // The SSLCertChain constructor copies its input, so now we have to delete + // the originals. + std::for_each(certs.begin(), certs.end(), DeleteCert); +} + +NSSCertificate::NSSCertificate(CERTCertificate* cert, SSLCertChain* chain) + : certificate_(CERT_DupCertificate(cert)) { + ASSERT(certificate_ != nullptr); + if (chain) + chain_.reset(chain->Copy()); +} + +NSSCertificate::~NSSCertificate() { + if (certificate_) + CERT_DestroyCertificate(certificate_); +} + +NSSCertificate* NSSCertificate::FromPEMString(const std::string& pem_string) { + std::string der; + if (!SSLIdentity::PemToDer(kPemTypeCertificate, pem_string, &der)) + return nullptr; + + SECItem der_cert; + der_cert.data = reinterpret_cast(const_cast( + der.data())); + der_cert.len = checked_cast(der.size()); + CERTCertificate* cert = CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &der_cert, nullptr, PR_FALSE, PR_TRUE); + + if (!cert) + return nullptr; + + NSSCertificate* ret = new NSSCertificate(cert); + CERT_DestroyCertificate(cert); + return ret; +} + +NSSCertificate* NSSCertificate::GetReference() const { + return new NSSCertificate(certificate_, chain_.get()); +} + +std::string NSSCertificate::ToPEMString() const { + return SSLIdentity::DerToPem(kPemTypeCertificate, + certificate_->derCert.data, + certificate_->derCert.len); +} + +void NSSCertificate::ToDER(Buffer* der_buffer) const { + der_buffer->SetData(certificate_->derCert.data, certificate_->derCert.len); +} + +static bool Certifies(CERTCertificate* parent, CERTCertificate* child) { + // TODO(bemasc): Identify stricter validation checks to use here. In the + // context of some future identity standard, it might make sense to check + // the certificates' roles, expiration dates, self-signatures (if + // self-signed), certificate transparency logging, or many other attributes. + // NOTE: Future changes to this validation may reject some previously allowed + // certificate chains. Users should be advised not to deploy chained + // certificates except in controlled environments until the validity + // requirements are finalized. + + // Check that the parent's name is the same as the child's claimed issuer. + SECComparison name_status = + CERT_CompareName(&child->issuer, &parent->subject); + if (name_status != SECEqual) + return false; + + // Extract the parent's public key, or fail if the key could not be read + // (e.g. certificate is corrupted). + SECKEYPublicKey* parent_key = CERT_ExtractPublicKey(parent); + if (!parent_key) + return false; + + // Check that the parent's privkey was actually used to generate the child's + // signature. + SECStatus verified = CERT_VerifySignedDataWithPublicKey(&child->signatureWrap, + parent_key, nullptr); + SECKEY_DestroyPublicKey(parent_key); + return verified == SECSuccess; +} + +bool NSSCertificate::IsValidChain(const CERTCertList* cert_list) { + CERTCertListNode* child = CERT_LIST_HEAD(cert_list); + for (CERTCertListNode* parent = CERT_LIST_NEXT(child); + !CERT_LIST_END(parent, cert_list); + child = parent, parent = CERT_LIST_NEXT(parent)) { + if (!Certifies(parent->cert, child->cert)) + return false; + } + return true; +} + +bool NSSCertificate::GetDigestLength(const std::string& algorithm, + size_t* length) { + const SECHashObject* ho = nullptr; + + if (!GetDigestObject(algorithm, &ho)) + return false; + + *length = ho->length; + + return true; +} + +bool NSSCertificate::GetSignatureDigestAlgorithm(std::string* algorithm) const { + // The function sec_DecodeSigAlg in NSS provides this mapping functionality. + // Unfortunately it is private, so the functionality must be duplicated here. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=925165 . + SECOidTag sig_alg = SECOID_GetAlgorithmTag(&certificate_->signature); + switch (sig_alg) { + case SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_MD5; + break; + case SEC_OID_PKCS1_SHA1_WITH_RSA_ENCRYPTION: + case SEC_OID_ISO_SHA_WITH_RSA_SIGNATURE: + case SEC_OID_ISO_SHA1_WITH_RSA_SIGNATURE: + case SEC_OID_ANSIX9_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_BOGUS_DSA_SIGNATURE_WITH_SHA1_DIGEST: + case SEC_OID_ANSIX962_ECDSA_SHA1_SIGNATURE: + case SEC_OID_MISSI_DSS: + case SEC_OID_MISSI_KEA_DSS: + case SEC_OID_MISSI_KEA_DSS_OLD: + case SEC_OID_MISSI_DSS_OLD: + *algorithm = DIGEST_SHA_1; + break; + case SEC_OID_ANSIX962_ECDSA_SHA224_SIGNATURE: + case SEC_OID_PKCS1_SHA224_WITH_RSA_ENCRYPTION: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA224_DIGEST: + *algorithm = DIGEST_SHA_224; + break; + case SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE: + case SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION: + case SEC_OID_NIST_DSA_SIGNATURE_WITH_SHA256_DIGEST: + *algorithm = DIGEST_SHA_256; + break; + case SEC_OID_ANSIX962_ECDSA_SHA384_SIGNATURE: + case SEC_OID_PKCS1_SHA384_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_SHA_384; + break; + case SEC_OID_ANSIX962_ECDSA_SHA512_SIGNATURE: + case SEC_OID_PKCS1_SHA512_WITH_RSA_ENCRYPTION: + *algorithm = DIGEST_SHA_512; + break; + default: + // Unknown algorithm. There are several unhandled options that are less + // common and more complex. + algorithm->clear(); + return false; + } + return true; +} + +bool NSSCertificate::ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const { + const SECHashObject* ho = nullptr; + + if (!GetDigestObject(algorithm, &ho)) + return false; + + if (size < ho->length) // Sanity check for fit + return false; + + SECStatus rv = HASH_HashBuf(ho->type, digest, + certificate_->derCert.data, + certificate_->derCert.len); + if (rv != SECSuccess) + return false; + + *length = ho->length; + + return true; +} + +bool NSSCertificate::GetChain(SSLCertChain** chain) const { + if (!chain_) + return false; + + *chain = chain_->Copy(); + return true; +} + +bool NSSCertificate::Equals(const NSSCertificate* tocompare) const { + if (!certificate_->derCert.len) + return false; + if (!tocompare->certificate_->derCert.len) + return false; + + if (certificate_->derCert.len != tocompare->certificate_->derCert.len) + return false; + + return memcmp(certificate_->derCert.data, + tocompare->certificate_->derCert.data, + certificate_->derCert.len) == 0; +} + +bool NSSCertificate::GetDigestObject(const std::string& algorithm, + const SECHashObject** hop) { + const SECHashObject* ho; + HASH_HashType hash_type; + + if (algorithm == DIGEST_SHA_1) { + hash_type = HASH_AlgSHA1; + // HASH_AlgSHA224 is not supported in the chromium linux build system. +#if 0 + } else if (algorithm == DIGEST_SHA_224) { + hash_type = HASH_AlgSHA224; +#endif + } else if (algorithm == DIGEST_SHA_256) { + hash_type = HASH_AlgSHA256; + } else if (algorithm == DIGEST_SHA_384) { + hash_type = HASH_AlgSHA384; + } else if (algorithm == DIGEST_SHA_512) { + hash_type = HASH_AlgSHA512; + } else { + return false; + } + + ho = HASH_GetHashObject(hash_type); + + ASSERT(ho->length >= 20); // Can't happen + *hop = ho; + + return true; +} + +NSSIdentity::NSSIdentity(NSSKeyPair* keypair, NSSCertificate* cert) + : keypair_(keypair), certificate_(cert) { +} + +NSSIdentity* NSSIdentity::GenerateInternal(const SSLIdentityParams& params) { + std::string subject_name_string = "CN=" + params.common_name; + CERTName* subject_name = + CERT_AsciiToName(const_cast(subject_name_string.c_str())); + NSSIdentity* identity = nullptr; + CERTSubjectPublicKeyInfo* spki = nullptr; + CERTCertificateRequest* certreq = nullptr; + CERTValidity* validity = nullptr; + CERTCertificate* certificate = nullptr; + NSSKeyPair* keypair = NSSKeyPair::Generate(params.key_type); + SECItem inner_der; + SECStatus rv; + PLArenaPool* arena; + SECItem signed_cert; + PRTime now = PR_Now(); + PRTime not_before = + now + static_cast(params.not_before) * PR_USEC_PER_SEC; + PRTime not_after = + now + static_cast(params.not_after) * PR_USEC_PER_SEC; + + inner_der.len = 0; + inner_der.data = nullptr; + + if (!keypair) { + LOG(LS_ERROR) << "Couldn't generate key pair"; + goto fail; + } + + if (!subject_name) { + LOG(LS_ERROR) << "Couldn't convert subject name " << subject_name; + goto fail; + } + + spki = SECKEY_CreateSubjectPublicKeyInfo(keypair->pubkey()); + if (!spki) { + LOG(LS_ERROR) << "Couldn't create SPKI"; + goto fail; + } + + certreq = CERT_CreateCertificateRequest(subject_name, spki, nullptr); + if (!certreq) { + LOG(LS_ERROR) << "Couldn't create certificate signing request"; + goto fail; + } + + validity = CERT_CreateValidity(not_before, not_after); + if (!validity) { + LOG(LS_ERROR) << "Couldn't create validity"; + goto fail; + } + + unsigned long serial; + // Note: This serial in principle could collide, but it's unlikely + rv = PK11_GenerateRandom(reinterpret_cast(&serial), + sizeof(serial)); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't generate random serial"; + goto fail; + } + + certificate = CERT_CreateCertificate(serial, subject_name, validity, certreq); + if (!certificate) { + LOG(LS_ERROR) << "Couldn't create certificate"; + goto fail; + } + + arena = certificate->arena; + + SECOidTag sec_oid; + if (params.key_type == KT_RSA) { + sec_oid = SEC_OID_PKCS1_SHA256_WITH_RSA_ENCRYPTION; + } else if (params.key_type == KT_ECDSA) { + sec_oid = SEC_OID_ANSIX962_ECDSA_SHA256_SIGNATURE; + } else { + // We should not arrive here since NSSKeyPair::Generate would have failed. + // Play it safe in order to accomodate code changes. + LOG(LS_ERROR) << "Key type requested not understood"; + goto fail; + } + + rv = SECOID_SetAlgorithmID(arena, &certificate->signature, sec_oid, nullptr); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't set hashing algorithm"; + goto fail; + } + + // Set version to X509v3. + *(certificate->version.data) = 2; + certificate->version.len = 1; + + if (!SEC_ASN1EncodeItem(arena, &inner_der, certificate, + SEC_ASN1_GET(CERT_CertificateTemplate))) { + LOG(LS_ERROR) << "Couldn't encode certificate"; + goto fail; + } + + rv = SEC_DerSignData(arena, &signed_cert, inner_der.data, inner_der.len, + keypair->privkey(), sec_oid); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't sign certificate"; + goto fail; + } + certificate->derCert = signed_cert; + + identity = new NSSIdentity(keypair, new NSSCertificate(certificate)); + + goto done; + + fail: + delete keypair; + + done: + if (certificate) CERT_DestroyCertificate(certificate); + if (subject_name) CERT_DestroyName(subject_name); + if (spki) SECKEY_DestroySubjectPublicKeyInfo(spki); + if (certreq) CERT_DestroyCertificateRequest(certreq); + if (validity) CERT_DestroyValidity(validity); + return identity; +} + +NSSIdentity* NSSIdentity::Generate(const std::string& common_name, + KeyType key_type) { + SSLIdentityParams params; + params.common_name = common_name; + params.not_before = CERTIFICATE_WINDOW; + params.not_after = CERTIFICATE_LIFETIME; + params.key_type = key_type; + return GenerateInternal(params); +} + +NSSIdentity* NSSIdentity::GenerateForTest(const SSLIdentityParams& params) { + return GenerateInternal(params); +} + +SSLIdentity* NSSIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + std::string private_key_der; + if (!SSLIdentity::PemToDer( + kPemTypeRsaPrivateKey, private_key, &private_key_der)) + return nullptr; + + SECItem private_key_item; + private_key_item.data = reinterpret_cast( + const_cast(private_key_der.c_str())); + private_key_item.len = checked_cast(private_key_der.size()); + + const unsigned int key_usage = KU_KEY_ENCIPHERMENT | KU_DATA_ENCIPHERMENT | + KU_DIGITAL_SIGNATURE; + + SECKEYPrivateKey* privkey = nullptr; + SECStatus rv = PK11_ImportDERPrivateKeyInfoAndReturnKey( + NSSContext::GetSlot(), &private_key_item, nullptr, nullptr, PR_FALSE, + PR_FALSE, key_usage, &privkey, nullptr); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't import private key"; + return nullptr; + } + + SECKEYPublicKey* pubkey = SECKEY_ConvertToPublicKey(privkey); + if (rv != SECSuccess) { + SECKEY_DestroyPrivateKey(privkey); + LOG(LS_ERROR) << "Couldn't convert private key to public key"; + return nullptr; + } + + SSLKEAType ssl_kea_type; + if (rtc::starts_with(private_key.c_str(), + "-----BEGIN RSA PRIVATE KEY-----")) { + ssl_kea_type = ssl_kea_rsa; + } else { + // We might want to check more key types here. But since we're moving to + // Open/BoringSSL, don't bother. Besides, this will likely be correct for + // any future key type, causing a test to do more harm than good. + ssl_kea_type = ssl_kea_ecdh; + } + + // Assign to a scoped_ptr so we don't leak on error. + scoped_ptr keypair(new NSSKeyPair(privkey, pubkey, ssl_kea_type)); + + scoped_ptr cert(NSSCertificate::FromPEMString(certificate)); + if (!cert) { + LOG(LS_ERROR) << "Couldn't parse certificate"; + return nullptr; + } + + // TODO(ekr@rtfm.com): Check the public key against the certificate. + return new NSSIdentity(keypair.release(), cert.release()); +} + +NSSIdentity::~NSSIdentity() { + LOG(LS_INFO) << "Destroying NSS identity"; +} + +NSSIdentity* NSSIdentity::GetReference() const { + NSSKeyPair* keypair = keypair_->GetReference(); + if (!keypair) + return nullptr; + + NSSCertificate* certificate = certificate_->GetReference(); + if (!certificate) { + delete keypair; + return nullptr; + } + + return new NSSIdentity(keypair, certificate); +} + + +NSSCertificate &NSSIdentity::certificate() const { + return *certificate_; +} + + +} // rtc namespace + +#endif // HAVE_NSS_SSL_H diff --git a/webrtc/base/nssidentity.h b/webrtc/base/nssidentity.h new file mode 100644 index 0000000000..867f594b84 --- /dev/null +++ b/webrtc/base/nssidentity.h @@ -0,0 +1,143 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NSSIDENTITY_H_ +#define WEBRTC_BASE_NSSIDENTITY_H_ + +#include + +// Hack: Define+undefine int64 and uint64 to avoid typedef conflict with NSS. +// TODO(kjellander): Remove when webrtc:4497 is completed. +#define uint64 foo_uint64 +#define int64 foo_int64 +#include "cert.h" +#undef uint64 +#undef int64 +#include "nspr.h" +#include "hasht.h" +#include "keythi.h" + +#ifdef NSS_SSL_RELATIVE_PATH +#include "ssl.h" +#else +#include "net/third_party/nss/ssl/ssl.h" +#endif + +#include "webrtc/base/common.h" +#include "webrtc/base/logging.h" +#include "webrtc/base/scoped_ptr.h" +#include "webrtc/base/sslidentity.h" + +namespace rtc { + +class NSSKeyPair { + public: + NSSKeyPair(SECKEYPrivateKey* privkey, SECKEYPublicKey* pubkey) + : privkey_(privkey), pubkey_(pubkey), ssl_kea_type_(ssl_kea_null) {} + NSSKeyPair(SECKEYPrivateKey* privkey, + SECKEYPublicKey* pubkey, + SSLKEAType ssl_kea_type) + : privkey_(privkey), pubkey_(pubkey), ssl_kea_type_(ssl_kea_type) {} + ~NSSKeyPair(); + + // Generate a 1024-bit RSA key pair. + static NSSKeyPair* Generate(KeyType key_type); + NSSKeyPair* GetReference(); + + SECKEYPrivateKey* privkey() const { return privkey_; } + SECKEYPublicKey * pubkey() const { return pubkey_; } + SSLKEAType ssl_kea_type() const { return ssl_kea_type_; } + + private: + SECKEYPrivateKey* privkey_; + SECKEYPublicKey* pubkey_; + SSLKEAType ssl_kea_type_; + + DISALLOW_COPY_AND_ASSIGN(NSSKeyPair); +}; + + +class NSSCertificate : public SSLCertificate { + public: + static NSSCertificate* FromPEMString(const std::string& pem_string); + // The caller retains ownership of the argument to all the constructors, + // and the constructor makes a copy. + explicit NSSCertificate(CERTCertificate* cert); + explicit NSSCertificate(CERTCertList* cert_list); + ~NSSCertificate() override; + + NSSCertificate* GetReference() const override; + + std::string ToPEMString() const override; + + void ToDER(Buffer* der_buffer) const override; + + bool GetSignatureDigestAlgorithm(std::string* algorithm) const override; + + bool ComputeDigest(const std::string& algorithm, + unsigned char* digest, + size_t size, + size_t* length) const override; + + bool GetChain(SSLCertChain** chain) const override; + + CERTCertificate* certificate() { return certificate_; } + + // Performs minimal checks to determine if the list is a valid chain. This + // only checks that each certificate certifies the preceding certificate, + // and ignores many other certificate features such as expiration dates. + static bool IsValidChain(const CERTCertList* cert_list); + + // Helper function to get the length of a digest + static bool GetDigestLength(const std::string& algorithm, size_t* length); + + // Comparison. Only the certificate itself is considered, not the chain. + bool Equals(const NSSCertificate* tocompare) const; + + private: + NSSCertificate(CERTCertificate* cert, SSLCertChain* chain); + static bool GetDigestObject(const std::string& algorithm, + const SECHashObject** hash_object); + + CERTCertificate* certificate_; + scoped_ptr chain_; + + DISALLOW_COPY_AND_ASSIGN(NSSCertificate); +}; + +// Represents a SSL key pair and certificate for NSS. +class NSSIdentity : public SSLIdentity { + public: + static NSSIdentity* Generate(const std::string& common_name, + KeyType key_type); + static NSSIdentity* GenerateForTest(const SSLIdentityParams& params); + static SSLIdentity* FromPEMStrings(const std::string& private_key, + const std::string& certificate); + ~NSSIdentity() override; + + NSSIdentity* GetReference() const override; + NSSCertificate& certificate() const override; + + NSSKeyPair* keypair() const { return keypair_.get(); } + + private: + NSSIdentity(NSSKeyPair* keypair, NSSCertificate* cert); + + static NSSIdentity* GenerateInternal(const SSLIdentityParams& params); + + rtc::scoped_ptr keypair_; + rtc::scoped_ptr certificate_; + + DISALLOW_COPY_AND_ASSIGN(NSSIdentity); +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NSSIDENTITY_H_ diff --git a/webrtc/base/nssstreamadapter.cc b/webrtc/base/nssstreamadapter.cc new file mode 100644 index 0000000000..2e78adfc0e --- /dev/null +++ b/webrtc/base/nssstreamadapter.cc @@ -0,0 +1,1127 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include + +#if HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#if HAVE_NSS_SSL_H + +#include "webrtc/base/nssstreamadapter.h" + +#include "keyhi.h" +#include "nspr.h" +#include "nss.h" +#include "pk11pub.h" +#include "secerr.h" + +#ifdef NSS_SSL_RELATIVE_PATH +#include "ssl.h" +#include "sslerr.h" +#include "sslproto.h" +#else +#include "net/third_party/nss/ssl/ssl.h" +#include "net/third_party/nss/ssl/sslerr.h" +#include "net/third_party/nss/ssl/sslproto.h" +#endif + +#include "webrtc/base/nssidentity.h" +#include "webrtc/base/safe_conversions.h" +#include "webrtc/base/thread.h" + +namespace rtc { + +PRDescIdentity NSSStreamAdapter::nspr_layer_identity = PR_INVALID_IO_LAYER; + +#define UNIMPLEMENTED \ + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); \ + LOG(LS_ERROR) \ + << "Call to unimplemented function "<< __FUNCTION__; ASSERT(false) + +#ifdef SRTP_AES128_CM_HMAC_SHA1_80 +#define HAVE_DTLS_SRTP +#endif + +#ifdef HAVE_DTLS_SRTP +// SRTP cipher suite table +struct SrtpCipherMapEntry { + const char* external_name; + PRUint16 cipher_id; +}; + +// This isn't elegant, but it's better than an external reference +static const SrtpCipherMapEntry kSrtpCipherMap[] = { + {"AES_CM_128_HMAC_SHA1_80", SRTP_AES128_CM_HMAC_SHA1_80 }, + {"AES_CM_128_HMAC_SHA1_32", SRTP_AES128_CM_HMAC_SHA1_32 }, + {NULL, 0} +}; +#endif + +// Ciphers to enable to get ECDHE encryption with endpoints that support it. +static const uint32_t kEnabledCiphers[] = { + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256}; + +// Default cipher used between NSS stream adapters. +// This needs to be updated when the default of the SSL library changes. +static const char kDefaultSslCipher10[] = + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"; +static const char kDefaultSslCipher12[] = + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"; +static const char kDefaultSslEcCipher10[] = + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA"; +static const char kDefaultSslEcCipher12[] = + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"; + +// Implementation of NSPR methods +static PRStatus StreamClose(PRFileDesc *socket) { + ASSERT(!socket->lower); + socket->dtor(socket); + return PR_SUCCESS; +} + +static PRInt32 StreamRead(PRFileDesc *socket, void *buf, PRInt32 length) { + StreamInterface *stream = reinterpret_cast(socket->secret); + size_t read; + int error; + StreamResult result = stream->Read(buf, length, &read, &error); + if (result == SR_SUCCESS) { + return checked_cast(read); + } + + if (result == SR_EOS) { + return 0; + } + + if (result == SR_BLOCK) { + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + PR_SetError(PR_UNKNOWN_ERROR, error); + return -1; +} + +static PRInt32 StreamWrite(PRFileDesc *socket, const void *buf, + PRInt32 length) { + StreamInterface *stream = reinterpret_cast(socket->secret); + size_t written; + int error; + StreamResult result = stream->Write(buf, length, &written, &error); + if (result == SR_SUCCESS) { + return checked_cast(written); + } + + if (result == SR_BLOCK) { + LOG(LS_INFO) << + "NSSStreamAdapter: write to underlying transport would block"; + PR_SetError(PR_WOULD_BLOCK_ERROR, 0); + return -1; + } + + LOG(LS_ERROR) << "Write error"; + PR_SetError(PR_UNKNOWN_ERROR, error); + return -1; +} + +static PRInt32 StreamAvailable(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +PRInt64 StreamAvailable64(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamSync(PRFileDesc *socket) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PROffset32 StreamSeek(PRFileDesc *socket, PROffset32 offset, + PRSeekWhence how) { + UNIMPLEMENTED; + return -1; +} + +static PROffset64 StreamSeek64(PRFileDesc *socket, PROffset64 offset, + PRSeekWhence how) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamFileInfo(PRFileDesc *socket, PRFileInfo *info) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamFileInfo64(PRFileDesc *socket, PRFileInfo64 *info) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRInt32 StreamWritev(PRFileDesc *socket, const PRIOVec *iov, + PRInt32 iov_size, PRIntervalTime timeout) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamConnect(PRFileDesc *socket, const PRNetAddr *addr, + PRIntervalTime timeout) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRFileDesc *StreamAccept(PRFileDesc *sd, PRNetAddr *addr, + PRIntervalTime timeout) { + UNIMPLEMENTED; + return NULL; +} + +static PRStatus StreamBind(PRFileDesc *socket, const PRNetAddr *addr) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamListen(PRFileDesc *socket, PRIntn depth) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamShutdown(PRFileDesc *socket, PRIntn how) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +// Note: this is always nonblocking and ignores the timeout. +// TODO(ekr@rtfm.com): In future verify that the socket is +// actually in non-blocking mode. +// This function does not support peek. +static PRInt32 StreamRecv(PRFileDesc *socket, void *buf, PRInt32 amount, + PRIntn flags, PRIntervalTime to) { + ASSERT(flags == 0); + + if (flags != 0) { + PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); + return -1; + } + + return StreamRead(socket, buf, amount); +} + +// Note: this is always nonblocking and assumes a zero timeout. +// This function does not support peek. +static PRInt32 StreamSend(PRFileDesc *socket, const void *buf, + PRInt32 amount, PRIntn flags, + PRIntervalTime to) { + ASSERT(flags == 0); + + return StreamWrite(socket, buf, amount); +} + +static PRInt32 StreamRecvfrom(PRFileDesc *socket, void *buf, + PRInt32 amount, PRIntn flags, + PRNetAddr *addr, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamSendto(PRFileDesc *socket, const void *buf, + PRInt32 amount, PRIntn flags, + const PRNetAddr *addr, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRInt16 StreamPoll(PRFileDesc *socket, PRInt16 in_flags, + PRInt16 *out_flags) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamAcceptRead(PRFileDesc *sd, PRFileDesc **nd, + PRNetAddr **raddr, + void *buf, PRInt32 amount, PRIntervalTime t) { + UNIMPLEMENTED; + return -1; +} + +static PRInt32 StreamTransmitFile(PRFileDesc *sd, PRFileDesc *socket, + const void *headers, PRInt32 hlen, + PRTransmitFileFlags flags, PRIntervalTime t) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamGetPeerName(PRFileDesc *socket, PRNetAddr *addr) { + // TODO(ekr@rtfm.com): Modify to return unique names for each channel + // somehow, as opposed to always the same static address. The current + // implementation messes up the session cache, which is why it's off + // elsewhere + addr->inet.family = PR_AF_INET; + addr->inet.port = 0; + addr->inet.ip = 0; + + return PR_SUCCESS; +} + +static PRStatus StreamGetSockName(PRFileDesc *socket, PRNetAddr *addr) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRStatus StreamGetSockOption(PRFileDesc *socket, PRSocketOptionData *opt) { + switch (opt->option) { + case PR_SockOpt_Nonblocking: + opt->value.non_blocking = PR_TRUE; + return PR_SUCCESS; + default: + UNIMPLEMENTED; + break; + } + + return PR_FAILURE; +} + +// Imitate setting socket options. These are mostly noops. +static PRStatus StreamSetSockOption(PRFileDesc *socket, + const PRSocketOptionData *opt) { + switch (opt->option) { + case PR_SockOpt_Nonblocking: + return PR_SUCCESS; + case PR_SockOpt_NoDelay: + return PR_SUCCESS; + default: + UNIMPLEMENTED; + break; + } + + return PR_FAILURE; +} + +static PRInt32 StreamSendfile(PRFileDesc *out, PRSendFileData *in, + PRTransmitFileFlags flags, PRIntervalTime to) { + UNIMPLEMENTED; + return -1; +} + +static PRStatus StreamConnectContinue(PRFileDesc *socket, PRInt16 flags) { + UNIMPLEMENTED; + return PR_FAILURE; +} + +static PRIntn StreamReserved(PRFileDesc *socket) { + UNIMPLEMENTED; + return -1; +} + +static const struct PRIOMethods nss_methods = { + PR_DESC_LAYERED, + StreamClose, + StreamRead, + StreamWrite, + StreamAvailable, + StreamAvailable64, + StreamSync, + StreamSeek, + StreamSeek64, + StreamFileInfo, + StreamFileInfo64, + StreamWritev, + StreamConnect, + StreamAccept, + StreamBind, + StreamListen, + StreamShutdown, + StreamRecv, + StreamSend, + StreamRecvfrom, + StreamSendto, + StreamPoll, + StreamAcceptRead, + StreamTransmitFile, + StreamGetSockName, + StreamGetPeerName, + StreamReserved, + StreamReserved, + StreamGetSockOption, + StreamSetSockOption, + StreamSendfile, + StreamConnectContinue, + StreamReserved, + StreamReserved, + StreamReserved, + StreamReserved +}; + +NSSStreamAdapter::NSSStreamAdapter(StreamInterface *stream) + : SSLStreamAdapterHelper(stream), + ssl_fd_(NULL), + cert_ok_(false) { +} + +bool NSSStreamAdapter::Init() { + if (nspr_layer_identity == PR_INVALID_IO_LAYER) { + nspr_layer_identity = PR_GetUniqueIdentity("nssstreamadapter"); + } + PRFileDesc *pr_fd = PR_CreateIOLayerStub(nspr_layer_identity, &nss_methods); + if (!pr_fd) + return false; + pr_fd->secret = reinterpret_cast(stream()); + + PRFileDesc *ssl_fd; + if (ssl_mode_ == SSL_MODE_DTLS) { + ssl_fd = DTLS_ImportFD(NULL, pr_fd); + } else { + ssl_fd = SSL_ImportFD(NULL, pr_fd); + } + ASSERT(ssl_fd != NULL); // This should never happen + if (!ssl_fd) { + PR_Close(pr_fd); + return false; + } + + SECStatus rv; + // Turn on security. + rv = SSL_OptionSet(ssl_fd, SSL_SECURITY, PR_TRUE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error enabling security on SSL Socket"; + return false; + } + + // Disable SSLv2. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SSL2, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling SSL2"; + return false; + } + + // Disable caching. + // TODO(ekr@rtfm.com): restore this when I have the caching + // identity set. + rv = SSL_OptionSet(ssl_fd, SSL_NO_CACHE, PR_TRUE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling cache"; + return false; + } + + // Disable session tickets. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_SESSION_TICKETS, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error enabling tickets"; + return false; + } + + // Disable renegotiation. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_RENEGOTIATION, + SSL_RENEGOTIATE_NEVER); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling renegotiation"; + return false; + } + + // Disable false start. + rv = SSL_OptionSet(ssl_fd, SSL_ENABLE_FALSE_START, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling false start"; + return false; + } + + // Disable reusing of ECDHE keys. By default NSS, when in server mode, uses + // the same key for multiple connections, so disable this behaviour to get + // ephemeral keys. + rv = SSL_OptionSet(ssl_fd, SSL_REUSE_SERVER_ECDHE_KEY, PR_FALSE); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Error disabling ECDHE key reuse"; + return false; + } + + ssl_fd_ = ssl_fd; + + return true; +} + +NSSStreamAdapter::~NSSStreamAdapter() { + if (ssl_fd_) + PR_Close(ssl_fd_); +}; + + +int NSSStreamAdapter::BeginSSL() { + SECStatus rv; + + if (!Init()) { + Error("Init", -1, false); + return -1; + } + + ASSERT(state_ == SSL_CONNECTING); + // The underlying stream has been opened. If we are in peer-to-peer mode + // then a peer certificate must have been specified by now. + ASSERT(!ssl_server_name_.empty() || + peer_certificate_.get() != NULL || + !peer_certificate_digest_algorithm_.empty()); + LOG(LS_INFO) << "BeginSSL: " + << (!ssl_server_name_.empty() ? ssl_server_name_ : + "with peer"); + + if (role_ == SSL_CLIENT) { + LOG(LS_INFO) << "BeginSSL: as client"; + + rv = SSL_GetClientAuthDataHook(ssl_fd_, GetClientAuthDataHook, + this); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } else { + LOG(LS_INFO) << "BeginSSL: as server"; + NSSIdentity *identity; + + if (identity_.get()) { + identity = static_cast(identity_.get()); + } else { + LOG(LS_ERROR) << "Can't be an SSL server without an identity"; + Error("BeginSSL", -1, false); + return -1; + } + rv = SSL_ConfigSecureServer(ssl_fd_, identity->certificate().certificate(), + identity->keypair()->privkey(), + identity->keypair()->ssl_kea_type()); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // Insist on a certificate from the client + rv = SSL_OptionSet(ssl_fd_, SSL_REQUEST_CERTIFICATE, PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // TODO(juberti): Check for client_auth_enabled() + + rv = SSL_OptionSet(ssl_fd_, SSL_REQUIRE_CERTIFICATE, PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } + + // Set the version range. + SSLVersionRange vrange; + if (ssl_mode_ == SSL_MODE_DTLS) { + vrange.min = SSL_LIBRARY_VERSION_TLS_1_1; + switch (ssl_max_version_) { + case SSL_PROTOCOL_DTLS_10: + vrange.max = SSL_LIBRARY_VERSION_TLS_1_1; + break; + case SSL_PROTOCOL_DTLS_12: + default: + vrange.max = SSL_LIBRARY_VERSION_TLS_1_2; + break; + } + } else { + // SSL_MODE_TLS + vrange.min = SSL_LIBRARY_VERSION_TLS_1_0; + switch (ssl_max_version_) { + case SSL_PROTOCOL_TLS_10: + vrange.max = SSL_LIBRARY_VERSION_TLS_1_0; + break; + case SSL_PROTOCOL_TLS_11: + vrange.max = SSL_LIBRARY_VERSION_TLS_1_1; + break; + case SSL_PROTOCOL_TLS_12: + default: + vrange.max = SSL_LIBRARY_VERSION_TLS_1_2; + break; + } + } + + rv = SSL_VersionRangeSet(ssl_fd_, &vrange); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // SRTP +#ifdef HAVE_DTLS_SRTP + if (!srtp_ciphers_.empty()) { + rv = SSL_SetSRTPCiphers( + ssl_fd_, &srtp_ciphers_[0], + checked_cast(srtp_ciphers_.size())); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } +#endif + + // Enable additional ciphers. + for (size_t i = 0; i < ARRAY_SIZE(kEnabledCiphers); i++) { + rv = SSL_CipherPrefSet(ssl_fd_, kEnabledCiphers[i], PR_TRUE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + } + + // Certificate validation + rv = SSL_AuthCertificateHook(ssl_fd_, AuthCertificateHook, this); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + // Now start the handshake + rv = SSL_ResetHandshake(ssl_fd_, role_ == SSL_SERVER ? PR_TRUE : PR_FALSE); + if (rv != SECSuccess) { + Error("BeginSSL", -1, false); + return -1; + } + + return ContinueSSL(); +} + +int NSSStreamAdapter::ContinueSSL() { + LOG(LS_INFO) << "ContinueSSL"; + ASSERT(state_ == SSL_CONNECTING); + + // Clear the DTLS timer + Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT); + + SECStatus rv = SSL_ForceHandshake(ssl_fd_); + + if (rv == SECSuccess) { + LOG(LS_INFO) << "Handshake complete"; + + ASSERT(cert_ok_); + if (!cert_ok_) { + Error("ContinueSSL", -1, true); + return -1; + } + + state_ = SSL_CONNECTED; + StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0); + return 0; + } + + PRInt32 err = PR_GetError(); + switch (err) { + case SSL_ERROR_RX_MALFORMED_HANDSHAKE: + if (ssl_mode_ != SSL_MODE_DTLS) { + Error("ContinueSSL", -1, true); + return -1; + } else { + LOG(LS_INFO) << "Malformed DTLS message. Ignoring."; + FALLTHROUGH(); // Fall through + } + case PR_WOULD_BLOCK_ERROR: + LOG(LS_INFO) << "Would have blocked"; + if (ssl_mode_ == SSL_MODE_DTLS) { + PRIntervalTime timeout; + + SECStatus rv = DTLS_GetHandshakeTimeout(ssl_fd_, &timeout); + if (rv == SECSuccess) { + LOG(LS_INFO) << "Timeout is " << timeout << " ms"; + Thread::Current()->PostDelayed(PR_IntervalToMilliseconds(timeout), + this, MSG_DTLS_TIMEOUT, 0); + } + } + + return 0; + default: + LOG(LS_INFO) << "Error " << err; + break; + } + + Error("ContinueSSL", -1, true); + return -1; +} + +void NSSStreamAdapter::Cleanup() { + if (state_ != SSL_ERROR) { + state_ = SSL_CLOSED; + } + + if (ssl_fd_) { + PR_Close(ssl_fd_); + ssl_fd_ = NULL; + } + + identity_.reset(); + peer_certificate_.reset(); + + Thread::Current()->Clear(this, MSG_DTLS_TIMEOUT); +} + +bool NSSStreamAdapter::GetDigestLength(const std::string& algorithm, + size_t* length) { + return NSSCertificate::GetDigestLength(algorithm, length); +} + +StreamResult NSSStreamAdapter::Read(void* data, size_t data_len, + size_t* read, int* error) { + // SSL_CONNECTED sanity check. + switch (state_) { + case SSL_NONE: + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_CLOSED: + return SR_EOS; + + case SSL_ERROR: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + PRInt32 rv = PR_Read(ssl_fd_, data, checked_cast(data_len)); + + if (rv == 0) { + return SR_EOS; + } + + // Error + if (rv < 0) { + PRInt32 err = PR_GetError(); + + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return SR_BLOCK; + default: + Error("Read", -1, false); + *error = err; // libjingle semantics are that this is impl-specific + return SR_ERROR; + } + } + + // Success + *read = rv; + + return SR_SUCCESS; +} + +StreamResult NSSStreamAdapter::Write(const void* data, size_t data_len, + size_t* written, int* error) { + // SSL_CONNECTED sanity check. + switch (state_) { + case SSL_NONE: + case SSL_WAIT: + case SSL_CONNECTING: + return SR_BLOCK; + + case SSL_CONNECTED: + break; + + case SSL_ERROR: + case SSL_CLOSED: + default: + if (error) + *error = ssl_error_code_; + return SR_ERROR; + } + + PRInt32 rv = PR_Write(ssl_fd_, data, checked_cast(data_len)); + + // Error + if (rv < 0) { + PRInt32 err = PR_GetError(); + + switch (err) { + case PR_WOULD_BLOCK_ERROR: + return SR_BLOCK; + default: + Error("Write", -1, false); + *error = err; // libjingle semantics are that this is impl-specific + return SR_ERROR; + } + } + + // Success + *written = rv; + + return SR_SUCCESS; +} + +void NSSStreamAdapter::OnEvent(StreamInterface* stream, int events, + int err) { + int events_to_signal = 0; + int signal_error = 0; + ASSERT(stream == this->stream()); + if ((events & SE_OPEN)) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent SE_OPEN"; + if (state_ != SSL_WAIT) { + ASSERT(state_ == SSL_NONE); + events_to_signal |= SE_OPEN; + } else { + state_ = SSL_CONNECTING; + if (int err = BeginSSL()) { + Error("BeginSSL", err, true); + return; + } + } + } + if ((events & (SE_READ|SE_WRITE))) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent" + << ((events & SE_READ) ? " SE_READ" : "") + << ((events & SE_WRITE) ? " SE_WRITE" : ""); + if (state_ == SSL_NONE) { + events_to_signal |= events & (SE_READ|SE_WRITE); + } else if (state_ == SSL_CONNECTING) { + if (int err = ContinueSSL()) { + Error("ContinueSSL", err, true); + return; + } + } else if (state_ == SSL_CONNECTED) { + if (events & SE_WRITE) { + LOG(LS_INFO) << " -- onStreamWriteable"; + events_to_signal |= SE_WRITE; + } + if (events & SE_READ) { + LOG(LS_INFO) << " -- onStreamReadable"; + events_to_signal |= SE_READ; + } + } + } + if ((events & SE_CLOSE)) { + LOG(LS_INFO) << "NSSStreamAdapter::OnEvent(SE_CLOSE, " << err << ")"; + Cleanup(); + events_to_signal |= SE_CLOSE; + // SE_CLOSE is the only event that uses the final parameter to OnEvent(). + ASSERT(signal_error == 0); + signal_error = err; + } + if (events_to_signal) + StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error); +} + +void NSSStreamAdapter::OnMessage(Message* msg) { + // Process our own messages and then pass others to the superclass + if (MSG_DTLS_TIMEOUT == msg->message_id) { + LOG(LS_INFO) << "DTLS timeout expired"; + ContinueSSL(); + } else { + StreamInterface::OnMessage(msg); + } +} + +// Certificate verification callback. Called to check any certificate +SECStatus NSSStreamAdapter::AuthCertificateHook(void *arg, + PRFileDesc *fd, + PRBool checksig, + PRBool isServer) { + LOG(LS_INFO) << "NSSStreamAdapter::AuthCertificateHook"; + // SSL_PeerCertificate returns a pointer that is owned by the caller, and + // the NSSCertificate constructor copies its argument, so |raw_peer_cert| + // must be destroyed in this function. + CERTCertificate* raw_peer_cert = SSL_PeerCertificate(fd); + NSSCertificate peer_cert(raw_peer_cert); + CERT_DestroyCertificate(raw_peer_cert); + + NSSStreamAdapter *stream = reinterpret_cast(arg); + stream->cert_ok_ = false; + + // Read the peer's certificate chain. + CERTCertList* cert_list = SSL_PeerCertificateChain(fd); + ASSERT(cert_list != NULL); + + // If the peer provided multiple certificates, check that they form a valid + // chain as defined by RFC 5246 Section 7.4.2: "Each following certificate + // MUST directly certify the one preceding it.". This check does NOT + // verify other requirements, such as whether the chain reaches a trusted + // root, self-signed certificates have valid signatures, certificates are not + // expired, etc. + // Even if the chain is valid, the leaf certificate must still match a + // provided certificate or digest. + if (!NSSCertificate::IsValidChain(cert_list)) { + CERT_DestroyCertList(cert_list); + PORT_SetError(SEC_ERROR_BAD_SIGNATURE); + return SECFailure; + } + + if (stream->peer_certificate_.get()) { + LOG(LS_INFO) << "Checking against specified certificate"; + + // The peer certificate was specified + if (reinterpret_cast(stream->peer_certificate_.get())-> + Equals(&peer_cert)) { + LOG(LS_INFO) << "Accepted peer certificate"; + stream->cert_ok_ = true; + } + } else if (!stream->peer_certificate_digest_algorithm_.empty()) { + LOG(LS_INFO) << "Checking against specified digest"; + // The peer certificate digest was specified + unsigned char digest[64]; // Maximum size + size_t digest_length; + + if (!peer_cert.ComputeDigest( + stream->peer_certificate_digest_algorithm_, + digest, sizeof(digest), &digest_length)) { + LOG(LS_ERROR) << "Digest computation failed"; + } else { + Buffer computed_digest(digest, digest_length); + if (computed_digest == stream->peer_certificate_digest_value_) { + LOG(LS_INFO) << "Accepted peer certificate"; + stream->cert_ok_ = true; + } + } + } else { + // Other modes, but we haven't implemented yet + // TODO(ekr@rtfm.com): Implement real certificate validation + UNIMPLEMENTED; + } + + if (!stream->cert_ok_ && stream->ignore_bad_cert()) { + LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + stream->cert_ok_ = true; + } + + if (stream->cert_ok_) + stream->peer_certificate_.reset(new NSSCertificate(cert_list)); + + CERT_DestroyCertList(cert_list); + + if (stream->cert_ok_) + return SECSuccess; + + PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); + return SECFailure; +} + + +SECStatus NSSStreamAdapter::GetClientAuthDataHook(void *arg, PRFileDesc *fd, + CERTDistNames *caNames, + CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey) { + LOG(LS_INFO) << "Client cert requested"; + NSSStreamAdapter *stream = reinterpret_cast(arg); + + if (!stream->identity_.get()) { + LOG(LS_ERROR) << "No identity available"; + return SECFailure; + } + + NSSIdentity *identity = static_cast(stream->identity_.get()); + // Destroyed internally by NSS + *pRetCert = CERT_DupCertificate(identity->certificate().certificate()); + *pRetKey = SECKEY_CopyPrivateKey(identity->keypair()->privkey()); + + return SECSuccess; +} + +bool NSSStreamAdapter::GetSslCipher(std::string* cipher) { + ASSERT(state_ == SSL_CONNECTED); + if (state_ != SSL_CONNECTED) + return false; + + SSLChannelInfo channel_info; + SECStatus rv = SSL_GetChannelInfo(ssl_fd_, &channel_info, + sizeof(channel_info)); + if (rv == SECFailure) + return false; + + SSLCipherSuiteInfo ciphersuite_info; + rv = SSL_GetCipherSuiteInfo(channel_info.cipherSuite, &ciphersuite_info, + sizeof(ciphersuite_info)); + if (rv == SECFailure) + return false; + + *cipher = ciphersuite_info.cipherSuiteName; + return true; +} + +// RFC 5705 Key Exporter +bool NSSStreamAdapter::ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) { + SECStatus rv = SSL_ExportKeyingMaterial( + ssl_fd_, + label.c_str(), + checked_cast(label.size()), + use_context, + context, + checked_cast(context_len), + result, + checked_cast(result_len)); + + return rv == SECSuccess; +} + +bool NSSStreamAdapter::SetDtlsSrtpCiphers( + const std::vector& ciphers) { +#ifdef HAVE_DTLS_SRTP + std::vector internal_ciphers; + if (state_ != SSL_NONE) + return false; + + for (std::vector::const_iterator cipher = ciphers.begin(); + cipher != ciphers.end(); ++cipher) { + bool found = false; + for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; entry->cipher_id; + ++entry) { + if (*cipher == entry->external_name) { + found = true; + internal_ciphers.push_back(entry->cipher_id); + break; + } + } + + if (!found) { + LOG(LS_ERROR) << "Could not find cipher: " << *cipher; + return false; + } + } + + if (internal_ciphers.empty()) + return false; + + srtp_ciphers_ = internal_ciphers; + + return true; +#else + return false; +#endif +} + +bool NSSStreamAdapter::GetDtlsSrtpCipher(std::string* cipher) { +#ifdef HAVE_DTLS_SRTP + ASSERT(state_ == SSL_CONNECTED); + if (state_ != SSL_CONNECTED) + return false; + + PRUint16 selected_cipher; + + SECStatus rv = SSL_GetSRTPCipher(ssl_fd_, &selected_cipher); + if (rv == SECFailure) + return false; + + for (const SrtpCipherMapEntry *entry = kSrtpCipherMap; + entry->cipher_id; ++entry) { + if (selected_cipher == entry->cipher_id) { + *cipher = entry->external_name; + return true; + } + } + + ASSERT(false); // This should never happen +#endif + return false; +} + + +GlobalLockPod NSSContext::lock; +NSSContext *NSSContext::global_nss_context; + +// Static initialization and shutdown +NSSContext *NSSContext::Instance() { + lock.Lock(); + if (!global_nss_context) { + scoped_ptr new_ctx(new NSSContext(PK11_GetInternalSlot())); + if (new_ctx->slot_) + global_nss_context = new_ctx.release(); + } + lock.Unlock(); + + return global_nss_context; +} + +bool NSSContext::InitializeSSL(VerificationCallback callback) { + ASSERT(!callback); + + static bool initialized = false; + + if (!initialized) { + SECStatus rv; + + rv = NSS_NoDB_Init(NULL); + if (rv != SECSuccess) { + LOG(LS_ERROR) << "Couldn't initialize NSS error=" << + PORT_GetError(); + return false; + } + + NSS_SetDomesticPolicy(); + + initialized = true; + } + + return true; +} + +bool NSSContext::InitializeSSLThread() { + // Not needed + return true; +} + +bool NSSContext::CleanupSSL() { + // Not needed + return true; +} + +bool NSSStreamAdapter::HaveDtls() { + return true; +} + +bool NSSStreamAdapter::HaveDtlsSrtp() { +#ifdef HAVE_DTLS_SRTP + return true; +#else + return false; +#endif +} + +bool NSSStreamAdapter::HaveExporter() { + return true; +} + +std::string NSSStreamAdapter::GetDefaultSslCipher(SSLProtocolVersion version, + KeyType key_type) { + if (key_type == KT_RSA) { + switch (version) { + case SSL_PROTOCOL_TLS_10: + case SSL_PROTOCOL_TLS_11: + return kDefaultSslCipher10; + case SSL_PROTOCOL_TLS_12: + default: + return kDefaultSslCipher12; + } + } else if (key_type == KT_ECDSA) { + switch (version) { + case SSL_PROTOCOL_TLS_10: + case SSL_PROTOCOL_TLS_11: + return kDefaultSslEcCipher10; + case SSL_PROTOCOL_TLS_12: + default: + return kDefaultSslEcCipher12; + } + } else { + return std::string(); + } +} + +} // namespace rtc + +#endif // HAVE_NSS_SSL_H diff --git a/webrtc/base/nssstreamadapter.h b/webrtc/base/nssstreamadapter.h new file mode 100644 index 0000000000..04c310ecb8 --- /dev/null +++ b/webrtc/base/nssstreamadapter.h @@ -0,0 +1,125 @@ +/* + * Copyright 2004 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_NSSSTREAMADAPTER_H_ +#define WEBRTC_BASE_NSSSTREAMADAPTER_H_ + +#include +#include + +// Hack: Define+undefine int64 and uint64 to avoid typedef conflict with NSS. +// TODO(kjellander): Remove when webrtc:4497 is completed. +#define uint64 foo_uint64 +#define int64 foo_int64 +#include "nspr.h" +#undef uint64 +#undef int64 + +#include "nss.h" +#include "secmodt.h" + +#include "webrtc/base/buffer.h" +#include "webrtc/base/criticalsection.h" +#include "webrtc/base/nssidentity.h" +#include "webrtc/base/ssladapter.h" +#include "webrtc/base/sslstreamadapter.h" +#include "webrtc/base/sslstreamadapterhelper.h" + +namespace rtc { + +// Singleton +class NSSContext { + public: + explicit NSSContext(PK11SlotInfo* slot) : slot_(slot) {} + ~NSSContext() { + } + + static PK11SlotInfo *GetSlot() { + return Instance() ? Instance()->slot_: NULL; + } + + static NSSContext *Instance(); + static bool InitializeSSL(VerificationCallback callback); + static bool InitializeSSLThread(); + static bool CleanupSSL(); + + private: + PK11SlotInfo *slot_; // The PKCS-11 slot + static GlobalLockPod lock; // To protect the global context + static NSSContext *global_nss_context; // The global context +}; + + +class NSSStreamAdapter : public SSLStreamAdapterHelper { + public: + explicit NSSStreamAdapter(StreamInterface* stream); + ~NSSStreamAdapter() override; + bool Init(); + + StreamResult Read(void* data, + size_t data_len, + size_t* read, + int* error) override; + StreamResult Write(const void* data, + size_t data_len, + size_t* written, + int* error) override; + void OnMessage(Message* msg) override; + + bool GetSslCipher(std::string* cipher) override; + + // Key Extractor interface + bool ExportKeyingMaterial(const std::string& label, + const uint8* context, + size_t context_len, + bool use_context, + uint8* result, + size_t result_len) override; + + // DTLS-SRTP interface + bool SetDtlsSrtpCiphers(const std::vector& ciphers) override; + bool GetDtlsSrtpCipher(std::string* cipher) override; + + // Capabilities interfaces + static bool HaveDtls(); + static bool HaveDtlsSrtp(); + static bool HaveExporter(); + static std::string GetDefaultSslCipher(SSLProtocolVersion version, + KeyType key_type); + + protected: + // Override SSLStreamAdapter + void OnEvent(StreamInterface* stream, int events, int err) override; + + // Override SSLStreamAdapterHelper + int BeginSSL() override; + void Cleanup() override; + bool GetDigestLength(const std::string& algorithm, size_t* length) override; + + private: + int ContinueSSL(); + static SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd, + PRBool checksig, PRBool isServer); + static SECStatus GetClientAuthDataHook(void *arg, PRFileDesc *fd, + CERTDistNames *caNames, + CERTCertificate **pRetCert, + SECKEYPrivateKey **pRetKey); + + PRFileDesc *ssl_fd_; // NSS's SSL file descriptor + static bool initialized; // Was InitializeSSL() called? + bool cert_ok_; // Did we get and check a cert + std::vector srtp_ciphers_; // SRTP cipher list + + static PRDescIdentity nspr_layer_identity; // The NSPR layer identity +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_NSSSTREAMADAPTER_H_ diff --git a/webrtc/base/opensslstreamadapter.cc b/webrtc/base/opensslstreamadapter.cc index ed2505e8b7..3d1760721f 100644 --- a/webrtc/base/opensslstreamadapter.cc +++ b/webrtc/base/opensslstreamadapter.cc @@ -1041,7 +1041,7 @@ int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) { // the digest. // // TODO(jiayl): Verify the chain is a proper chain and report the chain to - // |stream->peer_certificate_|. + // |stream->peer_certificate_|, like what NSS does. if (depth > 0) { LOG(LS_INFO) << "Ignored chained certificate at depth " << depth; return 1; diff --git a/webrtc/base/ssladapter.cc b/webrtc/base/ssladapter.cc index 77ad90b504..d83a2779e8 100644 --- a/webrtc/base/ssladapter.cc +++ b/webrtc/base/ssladapter.cc @@ -24,7 +24,11 @@ #include "openssladapter.h" -#endif // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL +#elif SSL_USE_NSS // && !SSL_USE_CHANNEL && !SSL_USE_OPENSSL + +#include "nssstreamadapter.h" + +#endif // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS /////////////////////////////////////////////////////////////////////////////// @@ -58,7 +62,21 @@ bool CleanupSSL() { return OpenSSLAdapter::CleanupSSL(); } -#else // !SSL_USE_OPENSSL +#elif SSL_USE_NSS // !SSL_USE_OPENSSL + +bool InitializeSSL(VerificationCallback callback) { + return NSSContext::InitializeSSL(callback); +} + +bool InitializeSSLThread() { + return NSSContext::InitializeSSLThread(); +} + +bool CleanupSSL() { + return NSSContext::CleanupSSL(); +} + +#else // !SSL_USE_OPENSSL && !SSL_USE_NSS bool InitializeSSL(VerificationCallback callback) { return true; @@ -72,7 +90,7 @@ bool CleanupSSL() { return true; } -#endif // !SSL_USE_OPENSSL +#endif // !SSL_USE_OPENSSL && !SSL_USE_NSS /////////////////////////////////////////////////////////////////////////////// diff --git a/webrtc/base/sslconfig.h b/webrtc/base/sslconfig.h index 6aabad07a8..d824ab0627 100644 --- a/webrtc/base/sslconfig.h +++ b/webrtc/base/sslconfig.h @@ -13,7 +13,8 @@ // If no preference has been indicated, default to SChannel on Windows and // OpenSSL everywhere else, if it is available. -#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL) +#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL) && \ + !defined(SSL_USE_NSS) #if defined(WEBRTC_WIN) #define SSL_USE_SCHANNEL 1 @@ -22,6 +23,8 @@ #if defined(HAVE_OPENSSL_SSL_H) #define SSL_USE_OPENSSL 1 +#elif defined(HAVE_NSS_SSL_H) +#define SSL_USE_NSS 1 #endif #endif // !defined(WEBRTC_WIN) diff --git a/webrtc/base/sslidentity.cc b/webrtc/base/sslidentity.cc index 0da254fcaa..33b7387588 100644 --- a/webrtc/base/sslidentity.cc +++ b/webrtc/base/sslidentity.cc @@ -27,6 +27,10 @@ #include "webrtc/base/opensslidentity.h" +#elif SSL_USE_NSS // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + +#include "webrtc/base/nssidentity.h" + #endif // SSL_USE_SCHANNEL namespace rtc { @@ -139,7 +143,27 @@ SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, return OpenSSLIdentity::FromPEMStrings(private_key, certificate); } -#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL +#elif SSL_USE_NSS // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL + +SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string) { + return NSSCertificate::FromPEMString(pem_string); +} + +SSLIdentity* SSLIdentity::Generate(const std::string& common_name, + KeyType key_type) { + return NSSIdentity::Generate(common_name, key_type); +} + +SSLIdentity* SSLIdentity::GenerateForTest(const SSLIdentityParams& params) { + return NSSIdentity::GenerateForTest(params); +} + +SSLIdentity* SSLIdentity::FromPEMStrings(const std::string& private_key, + const std::string& certificate) { + return NSSIdentity::FromPEMStrings(private_key, certificate); +} + +#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS #error "No SSL implementation" diff --git a/webrtc/base/sslidentity_unittest.cc b/webrtc/base/sslidentity_unittest.cc index e8df41506b..b49d0d23a7 100644 --- a/webrtc/base/sslidentity_unittest.cc +++ b/webrtc/base/sslidentity_unittest.cc @@ -185,7 +185,11 @@ TEST_F(SSLIdentityTest, FixedDigestSHA1) { } // HASH_AlgSHA224 is not supported in the chromium linux build. +#if SSL_USE_NSS +TEST_F(SSLIdentityTest, DISABLED_FixedDigestSHA224) { +#else TEST_F(SSLIdentityTest, FixedDigestSHA224) { +#endif TestDigestForFixedCert(rtc::DIGEST_SHA_224, 28, kTestCertSha224); } @@ -202,7 +206,11 @@ TEST_F(SSLIdentityTest, FixedDigestSHA512) { } // HASH_AlgSHA224 is not supported in the chromium linux build. +#if SSL_USE_NSS +TEST_F(SSLIdentityTest, DISABLED_DigestSHA224) { +#else TEST_F(SSLIdentityTest, DigestSHA224) { +#endif TestDigestForGeneratedCert(rtc::DIGEST_SHA_224, 28); } @@ -256,6 +264,11 @@ TEST_F(SSLIdentityTest, FromPEMStringsRSA) { EXPECT_EQ(kCERT_PEM, identity->certificate().ToPEMString()); } +#if SSL_USE_OPENSSL +// This will not work on NSS as PK11_ImportDERPrivateKeyInfoAndReturnKey is not +// ready for EC keys. Furthermore, NSSIdentity::FromPEMStrings is currently +// hardwired for RSA (the header matching via kPemTypeRsaPrivateKey needs +// trivial generalization). TEST_F(SSLIdentityTest, FromPEMStringsEC) { static const char kRSA_PRIVATE_KEY_PEM[] = "-----BEGIN EC PRIVATE KEY-----\n" @@ -282,6 +295,7 @@ TEST_F(SSLIdentityTest, FromPEMStringsEC) { EXPECT_TRUE(identity); EXPECT_EQ(kCERT_PEM, identity->certificate().ToPEMString()); } +#endif TEST_F(SSLIdentityTest, PemDerConversion) { std::string der; diff --git a/webrtc/base/sslstreamadapter.cc b/webrtc/base/sslstreamadapter.cc index 42dea9c036..39426cdb74 100644 --- a/webrtc/base/sslstreamadapter.cc +++ b/webrtc/base/sslstreamadapter.cc @@ -23,7 +23,11 @@ #include "webrtc/base/opensslstreamadapter.h" -#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL +#elif SSL_USE_NSS // && !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + +#include "webrtc/base/nssstreamadapter.h" + +#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL && !SSL_USE_NSS /////////////////////////////////////////////////////////////////////////////// @@ -34,7 +38,9 @@ SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) { return NULL; #elif SSL_USE_OPENSSL // !SSL_USE_SCHANNEL return new OpenSSLStreamAdapter(stream); -#else // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL +#elif SSL_USE_NSS // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL + return new NSSStreamAdapter(stream); +#else // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS return NULL; #endif } @@ -84,7 +90,21 @@ std::string SSLStreamAdapter::GetDefaultSslCipher(SSLProtocolVersion version, KeyType key_type) { return OpenSSLStreamAdapter::GetDefaultSslCipher(version, key_type); } -#endif // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL +#elif SSL_USE_NSS +bool SSLStreamAdapter::HaveDtls() { + return NSSStreamAdapter::HaveDtls(); +} +bool SSLStreamAdapter::HaveDtlsSrtp() { + return NSSStreamAdapter::HaveDtlsSrtp(); +} +bool SSLStreamAdapter::HaveExporter() { + return NSSStreamAdapter::HaveExporter(); +} +std::string SSLStreamAdapter::GetDefaultSslCipher(SSLProtocolVersion version, + KeyType key_type) { + return NSSStreamAdapter::GetDefaultSslCipher(version, key_type); +} +#endif // !SSL_USE_SCHANNEL && !SSL_USE_OPENSSL && !SSL_USE_NSS /////////////////////////////////////////////////////////////////////////////// diff --git a/webrtc/base/sslstreamadapter_unittest.cc b/webrtc/base/sslstreamadapter_unittest.cc index c70abe6192..67658ba610 100644 --- a/webrtc/base/sslstreamadapter_unittest.cc +++ b/webrtc/base/sslstreamadapter_unittest.cc @@ -701,6 +701,24 @@ class SSLStreamAdapterTestDTLSFromPEMStrings : public SSLStreamAdapterTestDTLS { // Basic tests: TLS +// Test that we cannot read/write if we have not yet handshaked. +// This test only applies to NSS because OpenSSL has passthrough +// semantics for I/O before the handshake is started. +#if SSL_USE_NSS +TEST_P(SSLStreamAdapterTestTLS, TestNoReadWriteBeforeConnect) { + rtc::StreamResult rv; + char block[kBlockSize]; + size_t dummy; + + rv = client_ssl_->Write(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_BLOCK, rv); + + rv = client_ssl_->Read(block, sizeof(block), &dummy, NULL); + ASSERT_EQ(rtc::SR_BLOCK, rv); +} +#endif + + // Test that we can make a handshake work TEST_P(SSLStreamAdapterTestTLS, TestTLSConnect) { TestHandshake(); diff --git a/webrtc/base/sslstreamadapterhelper.h b/webrtc/base/sslstreamadapterhelper.h index c6979ba036..a28dba4dea 100644 --- a/webrtc/base/sslstreamadapterhelper.h +++ b/webrtc/base/sslstreamadapterhelper.h @@ -23,7 +23,7 @@ namespace rtc { // SSLStreamAdapterHelper : A stream adapter which implements much // of the logic that is common between the known implementations -// (OpenSSL and previously NSS) +// (NSS and OpenSSL) class SSLStreamAdapterHelper : public SSLStreamAdapter { public: explicit SSLStreamAdapterHelper(StreamInterface* stream); diff --git a/webrtc/build/sanitizers/lsan_suppressions_webrtc.cc b/webrtc/build/sanitizers/lsan_suppressions_webrtc.cc index 61fdbbc20b..6d19a34881 100644 --- a/webrtc/build/sanitizers/lsan_suppressions_webrtc.cc +++ b/webrtc/build/sanitizers/lsan_suppressions_webrtc.cc @@ -31,6 +31,23 @@ char kLSanDefaultSuppressions[] = // Leaks in Nvidia's libGL. "leak:libGL.so\n" +// TODO(earthdok): revisit NSS suppressions after the switch to BoringSSL +// NSS leaks in CertDatabaseNSSTest tests. http://crbug.com/51988 +"leak:net::NSSCertDatabase::ImportFromPKCS12\n" +"leak:net::NSSCertDatabase::ListCerts\n" +"leak:net::NSSCertDatabase::DeleteCertAndKey\n" +"leak:crypto::ScopedTestNSSDB::ScopedTestNSSDB\n" +// Another leak due to not shutting down NSS properly. http://crbug.com/124445 +"leak:error_get_my_stack\n" +// The NSS suppressions above will not fire when the fast stack unwinder is +// used, because it can't unwind through NSS libraries. Apply blanket +// suppressions for now. +"leak:libnssutil3\n" +"leak:libnspr4\n" +"leak:libnss3\n" +"leak:libplds4\n" +"leak:libnssckbi\n" + // XRandR has several one time leaks. "leak:libxrandr\n"