diff options
author | sheretov <sheretov@chromium.org> | 2015-01-21 06:31:50 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-01-21 14:33:46 +0000 |
commit | 57988d1f46e6ce2a2d65aa87c01b29b2732a110f (patch) | |
tree | 50a6646de29c93e9eebf22145ad09c0deefb1e14 /extensions | |
parent | 0dc6c55b60d7593da4ad9dc832c6cf0594f1b3bf (diff) | |
download | chromium_src-57988d1f46e6ce2a2d65aa87c01b29b2732a110f.zip chromium_src-57988d1f46e6ce2a2d65aa87c01b29b2732a110f.tar.gz chromium_src-57988d1f46e6ce2a2d65aa87c01b29b2732a110f.tar.bz2 |
The purpose of this CL is to re-land issue 792353002 (https://codereview.chromium.org/792353002/), which landed, but was reverted due to a ChromiumOS GN build failure. The failure was caused by a double inclusion of networking_private_credentials_getter_chromeos.cc.cc file in chrome_browser_extensions.gypi. This CL includes the original patch and the fix of the build failure.
Original CL description follows:
Refactoring of Cast-related crypto code to use the same certificate validation logic in chrome.networkingPrivate API and Cast Channel authentication.
Here's what's being done here:
* Code from cast_auth_util_nss/openssl formed the basis a common Cast device validation component in /src/extensions/common/cast/cast_cert_validator*, and is now being extensively cleaned up in response to rsleevi's comments in this CL.
* Both networking_private_crypto* and cast_auth_util* have been updated to use the new common code.
* The current D-Bus-based implementation of VerifyDestination is going away per discussion with ChromeOS team, and is replaced with in-Chrome code in networking_private crypto*.
BUG=442650
Review URL: https://codereview.chromium.org/854693002
Cr-Commit-Position: refs/heads/master@{#312369}
Diffstat (limited to 'extensions')
-rw-r--r-- | extensions/browser/BUILD.gn | 6 | ||||
-rw-r--r-- | extensions/browser/api/cast_channel/cast_auth_util.cc | 57 | ||||
-rw-r--r-- | extensions/browser/api/cast_channel/cast_auth_util_nss.cc | 142 | ||||
-rw-r--r-- | extensions/browser/api/cast_channel/cast_auth_util_openssl.cc | 144 | ||||
-rw-r--r-- | extensions/browser/api/cast_channel/cast_auth_util_unittest.cc | 3 | ||||
-rw-r--r-- | extensions/common/BUILD.gn | 9 | ||||
-rw-r--r-- | extensions/common/cast/cast_cert_validator.cc | 30 | ||||
-rw-r--r-- | extensions/common/cast/cast_cert_validator.h | 96 | ||||
-rw-r--r-- | extensions/common/cast/cast_cert_validator_nss.cc | 155 | ||||
-rw-r--r-- | extensions/common/cast/cast_cert_validator_openssl.cc | 158 | ||||
-rw-r--r-- | extensions/extensions.gyp | 53 |
11 files changed, 533 insertions, 320 deletions
diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index c043e73..117bbce 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn @@ -525,12 +525,6 @@ source_set("browser") { "//extensions/common/api/cast_channel:cast_channel_proto", ] - if (use_openssl) { - sources += [ "api/cast_channel/cast_auth_util_openssl.cc" ] - } else { - sources += [ "api/cast_channel/cast_auth_util_nss.cc" ] - } - if (is_chromeos) { deps += [ "//chromeos" ] sources += [ diff --git a/extensions/browser/api/cast_channel/cast_auth_util.cc b/extensions/browser/api/cast_channel/cast_auth_util.cc index e863a57..e2beca1 100644 --- a/extensions/browser/api/cast_channel/cast_auth_util.cc +++ b/extensions/browser/api/cast_channel/cast_auth_util.cc @@ -4,11 +4,14 @@ #include "extensions/browser/api/cast_channel/cast_auth_util.h" +#include <vector> + #include "base/logging.h" #include "base/strings/string_number_conversions.h" #include "base/strings/stringprintf.h" #include "extensions/browser/api/cast_channel/cast_message_util.h" #include "extensions/common/api/cast_channel/cast_channel.pb.h" +#include "extensions/common/cast/cast_cert_validator.h" namespace extensions { namespace core_api { @@ -20,6 +23,8 @@ const char* const kParseErrorPrefix = "Failed to parse auth message: "; const unsigned char kAudioOnlyPolicy[] = {0x06, 0x0A, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xD6, 0x79, 0x02, 0x05, 0x02}; +namespace cast_crypto = ::extensions::core_api::cast_crypto; + // Extracts an embedded DeviceAuthMessage payload from an auth challenge reply // message. AuthResult ParseAuthMessage(const CastMessage& challenge_reply, @@ -55,6 +60,33 @@ AuthResult ParseAuthMessage(const CastMessage& challenge_reply, return AuthResult(); } +AuthResult TranslateVerificationResult( + const cast_crypto::VerificationResult& result) { + AuthResult translated; + translated.error_message = result.error_message; + translated.nss_error_code = result.library_error_code; + switch (result.error_type) { + case cast_crypto::VerificationResult::ERROR_NONE: + translated.error_type = AuthResult::ERROR_NONE; + break; + case cast_crypto::VerificationResult::ERROR_CERT_INVALID: + translated.error_type = AuthResult::ERROR_CERT_PARSING_FAILED; + break; + case cast_crypto::VerificationResult::ERROR_CERT_UNTRUSTED: + translated.error_type = AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA; + break; + case cast_crypto::VerificationResult::ERROR_SIGNATURE_INVALID: + translated.error_type = AuthResult::ERROR_SIGNED_BLOBS_MISMATCH; + break; + case cast_crypto::VerificationResult::ERROR_INTERNAL: + translated.error_type = AuthResult::ERROR_UNEXPECTED_AUTH_LIBRARY_RESULT; + break; + default: + translated.error_type = AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA; + }; + return translated; +} + } // namespace AuthResult::AuthResult() @@ -113,6 +145,31 @@ AuthResult AuthenticateChallengeReply(const CastMessage& challenge_reply, return result; } +// This function does the following +// * Verifies that the trusted CA |response.intermediate_certificate| is +// whitelisted for use. +// * Verifies that |response.client_auth_certificate| is signed +// by the trusted CA certificate. +// * Verifies that |response.signature| matches the signature +// of |peer_cert| by |response.client_auth_certificate|'s public +// key. +AuthResult VerifyCredentials(const AuthResponse& response, + const std::string& peer_cert) { + // Verify the certificate + scoped_ptr<cast_crypto::CertVerificationContext> verification_context; + cast_crypto::VerificationResult ret = cast_crypto::VerifyDeviceCert( + response.client_auth_certificate(), + std::vector<std::string>(response.intermediate_certificate().begin(), + response.intermediate_certificate().end()), + &verification_context); + + if (ret.Success()) + ret = verification_context->VerifySignatureOverData(response.signature(), + peer_cert); + + return TranslateVerificationResult(ret); +} + } // namespace cast_channel } // namespace core_api } // namespace extensions diff --git a/extensions/browser/api/cast_channel/cast_auth_util_nss.cc b/extensions/browser/api/cast_channel/cast_auth_util_nss.cc deleted file mode 100644 index 97f16d7..0000000 --- a/extensions/browser/api/cast_channel/cast_auth_util_nss.cc +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "extensions/browser/api/cast_channel/cast_auth_util.h" - -#include <cert.h> -#include <cryptohi.h> -#include <pk11pub.h> -#include <seccomon.h> -#include <string> - -#include "base/logging.h" -#include "base/strings/string_piece.h" -#include "crypto/nss_util.h" -#include "crypto/scoped_nss_types.h" -#include "extensions/browser/api/cast_channel/cast_auth_ica.h" -#include "extensions/browser/api/cast_channel/cast_message_util.h" -#include "extensions/common/api/cast_channel/cast_channel.pb.h" -#include "net/base/hash_value.h" -#include "net/cert/x509_certificate.h" - -namespace extensions { -namespace core_api { -namespace cast_channel { -namespace { - -typedef scoped_ptr< - CERTCertificate, - crypto::NSSDestroyer<CERTCertificate, CERT_DestroyCertificate> > - ScopedCERTCertificate; - -} // namespace - -// Authenticates the given credentials: -// 1. |signature| verification of |peer_cert| using |certificate|. -// 2. |certificate| is signed by a trusted CA. -AuthResult VerifyCredentials(const AuthResponse& response, - const std::string& peer_cert) { - const std::string kErrorPrefix("Failed to verify credentials: "); - const std::string& certificate = response.client_auth_certificate(); - const std::string& signature = response.signature(); - - // If the list of intermediates is empty then use kPublicKeyICA1 as - // the trusted CA (legacy case). - // Otherwise, use the first intermediate in the list as long as it - // is in the allowed list of intermediates. - int num_intermediates = response.intermediate_certificate_size(); - - VLOG(1) << "Response has " << num_intermediates << " intermediates"; - - base::StringPiece ica; - if (num_intermediates <= 0) { - ica = GetDefaultTrustedICAPublicKey(); - } else { - ica = GetTrustedICAPublicKey(response.intermediate_certificate(0)); - } - if (ica.empty()) { - return AuthResult::CreateWithParseError( - "Disallowed intermediate cert", - AuthResult::ERROR_FINGERPRINT_NOT_FOUND); - } - - SECItem trusted_ca_key_der; - trusted_ca_key_der.type = SECItemType::siDERCertBuffer; - trusted_ca_key_der.data = - const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(ica.data())); - trusted_ca_key_der.len = ica.size(); - - crypto::EnsureNSSInit(); - SECItem der_cert; - der_cert.type = siDERCertBuffer; - // Make a copy of certificate string so it is safe to type cast. - der_cert.data = reinterpret_cast<unsigned char*>(const_cast<char*>( - certificate.data())); - der_cert.len = certificate.length(); - - // Parse into a certificate structure. - ScopedCERTCertificate cert(CERT_NewTempCertificate( - CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE)); - if (!cert.get()) { - return AuthResult::CreateWithNSSError( - "Failed to parse certificate.", - AuthResult::ERROR_CERT_PARSING_FAILED, PORT_GetError()); - } - - // Check that the certificate is signed by trusted CA. - // NOTE: We const_cast trusted_ca_key_der since on some platforms - // SECKEY_ImportDERPublicKey API takes in SECItem* and not const - // SECItem*. - crypto::ScopedSECKEYPublicKey ca_public_key( - SECKEY_ImportDERPublicKey(&trusted_ca_key_der, CKK_RSA)); - if (!ca_public_key) { - return AuthResult::CreateWithNSSError( - "Failed to import public key from CA certificate.", - AuthResult::ERROR_CERT_PARSING_FAILED, PORT_GetError()); - } - SECStatus verified = CERT_VerifySignedDataWithPublicKey( - &cert->signatureWrap, ca_public_key.get(), NULL); - if (verified != SECSuccess) { - return AuthResult::CreateWithNSSError( - "Cert not signed by trusted CA", - AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA, PORT_GetError()); - } - - VLOG(1) << "Cert signed by trusted CA"; - - // Verify that the |signature| matches |peer_cert|. - crypto::ScopedSECKEYPublicKey public_key(CERT_ExtractPublicKey(cert.get())); - if (!public_key.get()) { - return AuthResult::CreateWithNSSError( - "Unable to extract public key from certificate", - AuthResult::ERROR_CANNOT_EXTRACT_PUBLIC_KEY, PORT_GetError()); - } - SECItem signature_item; - signature_item.type = siBuffer; - signature_item.data = reinterpret_cast<unsigned char*>( - const_cast<char*>(signature.data())); - signature_item.len = signature.length(); - verified = VFY_VerifyDataDirect( - reinterpret_cast<unsigned char*>(const_cast<char*>(peer_cert.data())), - peer_cert.size(), - public_key.get(), - &signature_item, - SEC_OID_PKCS1_RSA_ENCRYPTION, - SEC_OID_SHA1, NULL, NULL); - - if (verified != SECSuccess) { - return AuthResult::CreateWithNSSError( - "Signed blobs did not match", - AuthResult::ERROR_SIGNED_BLOBS_MISMATCH, - PORT_GetError()); - } - - VLOG(1) << "Signature verification succeeded"; - - return AuthResult(); -} - -} // namespace cast_channel -} // namespace core_api -} // namespace extensions diff --git a/extensions/browser/api/cast_channel/cast_auth_util_openssl.cc b/extensions/browser/api/cast_channel/cast_auth_util_openssl.cc deleted file mode 100644 index b662840..0000000 --- a/extensions/browser/api/cast_channel/cast_auth_util_openssl.cc +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2014 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "extensions/browser/api/cast_channel/cast_auth_util.h" - -#include <openssl/evp.h> -#include <openssl/rsa.h> -#include <openssl/x509.h> -#include <stddef.h> - -#include "base/logging.h" -#include "base/strings/stringprintf.h" -#include "crypto/openssl_util.h" -#include "crypto/scoped_openssl_types.h" -#include "extensions/browser/api/cast_channel/cast_auth_ica.h" -#include "extensions/browser/api/cast_channel/cast_message_util.h" -#include "extensions/common/api/cast_channel/cast_channel.pb.h" -#include "net/cert/x509_certificate.h" -#include "net/cert/x509_util_openssl.h" - -namespace extensions { -namespace core_api { -namespace cast_channel { -namespace { - -typedef crypto::ScopedOpenSSL<X509, X509_free>::Type ScopedX509; - -} // namespace - -// This function does the following -// * Verifies that the trusted CA |response.intermediate_certificate| is -// whitelisted for use. -// * Verifies that |response.client_auth_certificate| is signed -// by the trusted CA certificate. -// * Verifies that |response.signature| matches the signature -// of |peer_cert| by |response.client_auth_certificate|'s public -// key. -// -// TODO(kmarshall): Report fine-grained errors from OpenSSL. -AuthResult VerifyCredentials(const AuthResponse& response, - const std::string& peer_cert) { - crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); - - // Get the public key of the ICA that was used to sign the client's cert. - base::StringPiece ca_public_key_bytes; - if (response.intermediate_certificate().size() <= 0) { - ca_public_key_bytes = GetDefaultTrustedICAPublicKey(); - } else { - ca_public_key_bytes = - GetTrustedICAPublicKey(response.intermediate_certificate(0)); - if (ca_public_key_bytes.empty()) { - LOG(ERROR) << "Couldn't find trusted ICA."; - return AuthResult::CreateWithParseError( - "failed to verify credentials: cert not signed by trusted CA", - AuthResult::ERROR_FINGERPRINT_NOT_FOUND); - } - } - - // Parse the CA public key. - const uint8_t* ca_ptr = - reinterpret_cast<const uint8_t*>(ca_public_key_bytes.data()); - const uint8_t* ca_public_key_end = ca_ptr + ca_public_key_bytes.size(); - crypto::ScopedRSA ca_public_key_rsa( - d2i_RSAPublicKey(NULL, &ca_ptr, ca_public_key_bytes.size())); - if (!ca_public_key_rsa || ca_ptr != ca_public_key_end) { - LOG(ERROR) << "Failed to import trusted public key."; - return AuthResult::CreateWithParseError( - "failed to import trusted public key.", - AuthResult::ERROR_CERT_PARSING_FAILED); - } - crypto::ScopedEVP_PKEY ca_public_key(EVP_PKEY_new()); - if (!ca_public_key || - !EVP_PKEY_set1_RSA(ca_public_key.get(), ca_public_key_rsa.get())) { - LOG(ERROR) << "Failed to initialize EVP_PKEY"; - return AuthResult::CreateWithParseError( - "failed to initialize EVP_PKEY.", - AuthResult::ERROR_CANNOT_EXTRACT_PUBLIC_KEY); - } - - // Parse the client auth certificate. - const uint8_t* client_cert_ptr = reinterpret_cast<const uint8_t*>( - response.client_auth_certificate().data()); - const uint8_t* client_cert_end = - client_cert_ptr + - response.client_auth_certificate().size(); - const ScopedX509 client_cert( - d2i_X509(NULL, &client_cert_ptr, - response.client_auth_certificate().size())); - if (!client_cert || client_cert_ptr != client_cert_end) { - LOG(ERROR) << "Failed to parse certificate."; - return AuthResult::CreateWithParseError( - "failed to parse client_auth_certificate.", - AuthResult::ERROR_CERT_PARSING_FAILED); - } - - // Verify that the client auth certificate was signed by a trusted CA. - if (X509_verify(client_cert.get(), ca_public_key.get()) <= 0) { - LOG(ERROR) << "Certificate is not issued by a trusted CA."; - return AuthResult::CreateWithParseError( - "cert not signed by trusted CA", - AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA); - } - - // Get the client auth certificate's public key. - const crypto::ScopedEVP_PKEY client_public_key( - X509_get_pubkey(client_cert.get())); - const int client_public_key_type = EVP_PKEY_id(client_public_key.get()); - if (client_public_key_type != EVP_PKEY_RSA) { - LOG(ERROR) << "Expected RSA key type for client certificate, got " - << client_public_key_type << " instead."; - return AuthResult::CreateWithParseError( - "couldn't extract public_key from client cert.", - AuthResult::ERROR_CANNOT_EXTRACT_PUBLIC_KEY); - } - - // Check that the SSL peer certificate was signed using the client's public - // key. - const crypto::ScopedEVP_MD_CTX ctx(EVP_MD_CTX_create()); - if (!ctx || - !EVP_DigestVerifyInit(ctx.get(), NULL, EVP_sha1(), NULL, - client_public_key.get()) || - !EVP_DigestVerifyUpdate(ctx.get(), peer_cert.data(), peer_cert.size())) { - return AuthResult::CreateWithParseError( - "error initializing payload verification operation.", - AuthResult::ERROR_UNEXPECTED_AUTH_LIBRARY_RESULT); - } - const std::string& signature = response.signature(); - if (EVP_DigestVerifyFinal( - ctx.get(), - reinterpret_cast<uint8_t*>(const_cast<char*>(signature.data())), - signature.size()) <= 0) { - return AuthResult::CreateWithParseError( - "payload verification failed.", - AuthResult::ERROR_SIGNED_BLOBS_MISMATCH); - } - - return AuthResult(); -} - -} // namespace cast_channel -} // namespace core_api -} // namespace extensions - diff --git a/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc b/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc index 0e3bcd3..6d56f683 100644 --- a/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc +++ b/extensions/browser/api/cast_channel/cast_auth_util_unittest.cc @@ -354,8 +354,7 @@ TEST_F(CastAuthUtilTest, VerifyBadCA) { AuthResult result = VerifyCredentials( auth_response, CreatePeerCert()); EXPECT_FALSE(result.success()); - EXPECT_EQ(AuthResult::ERROR_FINGERPRINT_NOT_FOUND, - result.error_type); + EXPECT_EQ(AuthResult::ERROR_CERT_NOT_SIGNED_BY_TRUSTED_CA, result.error_type); } TEST_F(CastAuthUtilTest, VerifyBadClientAuthCert) { diff --git a/extensions/common/BUILD.gn b/extensions/common/BUILD.gn index f6dcd00..76e932d 100644 --- a/extensions/common/BUILD.gn +++ b/extensions/common/BUILD.gn @@ -2,6 +2,7 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +import("//build/config/crypto.gni") import("//build/config/features.gni") import("//third_party/mojo/src/mojo/public/tools/bindings/mojom.gni") @@ -41,6 +42,8 @@ if (enable_extensions) { "api/sockets/sockets_manifest_handler.h", "api/sockets/sockets_manifest_permission.cc", "api/sockets/sockets_manifest_permission.h", + "cast/cast_cert_validator.cc", + "cast/cast_cert_validator.h", "common_manifest_handlers.cc", "common_manifest_handlers.h", "csp_validator.cc", @@ -249,6 +252,12 @@ if (enable_extensions) { "//url", ] + if (use_openssl) { + sources += [ "cast/cast_cert_validator_openssl.cc" ] + } else { + sources += [ "cast/cast_cert_validator_nss.cc" ] + } + if (enable_nacl) { sources += [ "manifest_handlers/nacl_modules_handler.cc", diff --git a/extensions/common/cast/cast_cert_validator.cc b/extensions/common/cast/cast_cert_validator.cc new file mode 100644 index 0000000..e4f0440 --- /dev/null +++ b/extensions/common/cast/cast_cert_validator.cc @@ -0,0 +1,30 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/cast/cast_cert_validator.h" + +namespace extensions { +namespace core_api { +namespace cast_crypto { + +VerificationResult::VerificationResult() + : VerificationResult("", ERROR_NONE, 0) { +} + +VerificationResult::VerificationResult(const std::string& in_error_message, + ErrorType in_error_type) + : VerificationResult(in_error_message, in_error_type, 0) { +} + +VerificationResult::VerificationResult(const std::string& in_error_message, + ErrorType in_error_type, + int in_error_code) + : error_type(in_error_type), + error_message(in_error_message), + library_error_code(in_error_code) { +} + +} // namespace cast_crypto +} // namespace core_api +} // namespace extensions diff --git a/extensions/common/cast/cast_cert_validator.h b/extensions/common/cast/cast_cert_validator.h new file mode 100644 index 0000000..b9d2cf6 --- /dev/null +++ b/extensions/common/cast/cast_cert_validator.h @@ -0,0 +1,96 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXTENSIONS_COMMON_CAST_CAST_CERT_VALIDATOR_H_ +#define EXTENSIONS_COMMON_CAST_CAST_CERT_VALIDATOR_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_piece.h" + +namespace extensions { +namespace core_api { +namespace cast_crypto { + +// Status of a certificate or certificate verification operation. +struct VerificationResult { + // Mapped to extensions::core_api::cast_channel::AuthResult::ErrorType in + // cast_auto_util.cc. Update the mapping code when modifying this enum. + enum ErrorType { + // Verification has succeeded. + ERROR_NONE = 0, + // There was a problem with the certificate, such as invalid or corrupt + // certificate data or invalid issuing certificate signature. + ERROR_CERT_INVALID, + // Certificate may be valid, but not trusted in this context. + ERROR_CERT_UNTRUSTED, + // Signature verification failed + ERROR_SIGNATURE_INVALID, + // Catch-all for internal errors that are not covered by the other error + // types. + ERROR_INTERNAL + }; + + // Constructs a VerificationResult that corresponds to success. + VerificationResult(); + + // Construct error-related objects + VerificationResult(const std::string& error_message, ErrorType error_type); + VerificationResult(const std::string& error_message, + ErrorType error_type, + int error_code); + + bool Success() const { return error_type == ERROR_NONE; } + bool Failure() const { return error_type != ERROR_NONE; } + + // Generates a string representation of this object for logging. + std::string GetLogString() const; + + ErrorType error_type; + // Human-readable description of the problem if error_type != ERROR_NONE + std::string error_message; + // May contain the underlying crypto library error code. + int library_error_code; +}; + +// An object of this type is returned by the VerifyCert function, and can be +// used for additional certificate-related operations, using the verified +// certificate. +class CertVerificationContext { + public: + CertVerificationContext() {} + virtual ~CertVerificationContext() {} + + // Use the public key from the verified certificate to verify a + // sha1WithRSAEncryption |signature| over arbitrary |data|. Both |signature| + // and |data| hold raw binary data. + virtual VerificationResult VerifySignatureOverData( + const base::StringPiece& signature, + const base::StringPiece& data) const = 0; + + // Retrieve the Common Name attribute of the subject's distinguished name from + // the verified certificate, if present. Returns an empty string if no Common + // Name is found. + virtual std::string GetCommonName() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(CertVerificationContext); +}; + +// Verify a cast device certificate, using optional intermediate certificate +// authority certificates. |context| will be populated with an instance of +// CertVerificationContext, which allows to perform additional verification +// steps as required. +VerificationResult VerifyDeviceCert( + const base::StringPiece& device_cert, + const std::vector<std::string>& ica_certs, + scoped_ptr<CertVerificationContext>* context); + +} // namespace cast_crypto +} // namespace core_api +} // namespace extensions + +#endif // EXTENSIONS_COMMON_CAST_CAST_CERT_VALIDATOR_H_ diff --git a/extensions/common/cast/cast_cert_validator_nss.cc b/extensions/common/cast/cast_cert_validator_nss.cc new file mode 100644 index 0000000..425da28 --- /dev/null +++ b/extensions/common/cast/cast_cert_validator_nss.cc @@ -0,0 +1,155 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/cast/cast_cert_validator.h" + +#include <cert.h> +#include <cryptohi.h> +#include <pk11pub.h> +#include <seccomon.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "crypto/nss_util.h" +#include "crypto/scoped_nss_types.h" +#include "extensions/browser/api/cast_channel/cast_auth_ica.h" + +namespace extensions { +namespace core_api { +namespace cast_crypto { + +namespace { + +typedef scoped_ptr< + CERTCertificate, + crypto::NSSDestroyer<CERTCertificate, CERT_DestroyCertificate>> + ScopedCERTCertificate; + +class CertVerificationContextNSS : public CertVerificationContext { + public: + explicit CertVerificationContextNSS(CERTCertificate* certificate) + : certificate_(certificate) {} + + VerificationResult VerifySignatureOverData( + const base::StringPiece& signature, + const base::StringPiece& data) const override { + // Retrieve public key object + crypto::ScopedSECKEYPublicKey public_key_obj( + CERT_ExtractPublicKey(certificate_.get())); + if (!public_key_obj.get()) { + return VerificationResult( + "Failed to extract device certificate public key.", + VerificationResult::ERROR_CERT_INVALID); + } + // Verify signature. + SECItem signature_item; + signature_item.type = siBuffer; + signature_item.data = + reinterpret_cast<unsigned char*>(const_cast<char*>(signature.data())); + signature_item.len = signature.length(); + if (VFY_VerifyDataDirect( + reinterpret_cast<unsigned char*>(const_cast<char*>(data.data())), + data.size(), public_key_obj.get(), &signature_item, + SEC_OID_PKCS1_RSA_ENCRYPTION, SEC_OID_SHA1, NULL, + NULL) != SECSuccess) { + return VerificationResult("Signature verification failed.", + VerificationResult::ERROR_SIGNATURE_INVALID, + PORT_GetError()); + } + return VerificationResult(); + } + + std::string GetCommonName() const override { + char* common_name = CERT_GetCommonName(&certificate_->subject); + if (!common_name) + return std::string(); + + std::string result(common_name); + PORT_Free(common_name); + return result; + } + + private: + ScopedCERTCertificate certificate_; +}; + +} // namespace + +VerificationResult VerifyDeviceCert( + const base::StringPiece& device_cert, + const std::vector<std::string>& ica_certs, + scoped_ptr<CertVerificationContext>* context) { + crypto::EnsureNSSInit(); + + // If the list of intermediates is empty then use kPublicKeyICA1 as + // the trusted CA (legacy case). + // Otherwise, use the first intermediate in the list as long as it + // is in the allowed list of intermediates. + base::StringPiece ica_public_key_der = + (ica_certs.size() == 0) + ? cast_channel::GetDefaultTrustedICAPublicKey() + : cast_channel::GetTrustedICAPublicKey(ica_certs[0]); + + if (ica_public_key_der.empty()) { + return VerificationResult( + "Device certificate is not signed by a trusted CA", + VerificationResult::ERROR_CERT_UNTRUSTED); + } + // Initialize the ICA public key. + SECItem ica_public_key_der_item; + ica_public_key_der_item.type = SECItemType::siDERCertBuffer; + ica_public_key_der_item.data = const_cast<uint8_t*>( + reinterpret_cast<const uint8_t*>(ica_public_key_der.data())); + ica_public_key_der_item.len = ica_public_key_der.size(); + + crypto::ScopedSECKEYPublicKey ica_public_key_obj( + SECKEY_ImportDERPublicKey(&ica_public_key_der_item, CKK_RSA)); + if (!ica_public_key_obj) { + return VerificationResult("Failed to import trusted public key.", + VerificationResult::ERROR_INTERNAL, + PORT_GetError()); + } + SECItem device_cert_der_item; + device_cert_der_item.type = siDERCertBuffer; + // Make a copy of certificate string so it is safe to type cast. + device_cert_der_item.data = + reinterpret_cast<unsigned char*>(const_cast<char*>(device_cert.data())); + device_cert_der_item.len = device_cert.length(); + + // Parse into a certificate structure. + ScopedCERTCertificate device_cert_obj(CERT_NewTempCertificate( + CERT_GetDefaultCertDB(), &device_cert_der_item, NULL, PR_FALSE, PR_TRUE)); + if (!device_cert_obj.get()) { + return VerificationResult("Failed to parse device certificate.", + VerificationResult::ERROR_CERT_INVALID, + PORT_GetError()); + } + if (CERT_VerifySignedDataWithPublicKey(&device_cert_obj->signatureWrap, + ica_public_key_obj.get(), + NULL) != SECSuccess) { + return VerificationResult("Signature verification failed.", + VerificationResult::ERROR_SIGNATURE_INVALID, + PORT_GetError()); + } + if (context) { + scoped_ptr<CertVerificationContext> tmp_context( + new CertVerificationContextNSS(device_cert_obj.release())); + tmp_context.swap(*context); + } + + return VerificationResult(); +} + +std::string VerificationResult::GetLogString() const { + std::string nssError = "NSS Error Code: "; + nssError += base::IntToString(library_error_code); + return error_message.size() + ? std::string("Error: ") + error_message + ", " + nssError + : nssError; +} + +} // namespace cast_crypto +} // namespace core_api +} // namespace extensions diff --git a/extensions/common/cast/cast_cert_validator_openssl.cc b/extensions/common/cast/cast_cert_validator_openssl.cc new file mode 100644 index 0000000..8c2e4c0 --- /dev/null +++ b/extensions/common/cast/cast_cert_validator_openssl.cc @@ -0,0 +1,158 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "extensions/common/cast/cast_cert_validator.h" + +#include <openssl/digest.h> +#include <openssl/evp.h> +#include <openssl/rsa.h> +#include <openssl/x509.h> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "crypto/openssl_util.h" +#include "crypto/scoped_openssl_types.h" +#include "extensions/browser/api/cast_channel/cast_auth_ica.h" +#include "net/cert/x509_certificate.h" +#include "net/cert/x509_util_openssl.h" + +namespace extensions { +namespace core_api { +namespace cast_crypto { +namespace { + +typedef crypto::ScopedOpenSSL<X509, X509_free>::Type ScopedX509; + +class CertVerificationContextOpenSSL : public CertVerificationContext { + public: + // Takes ownership of the passed-in x509 object + explicit CertVerificationContextOpenSSL(X509* x509) : x509_(x509) {} + + VerificationResult VerifySignatureOverData( + const base::StringPiece& signature, + const base::StringPiece& data) const override { + // Retrieve public key object. + crypto::ScopedEVP_PKEY public_key(X509_get_pubkey(x509_.get())); + if (!public_key) { + return VerificationResult( + "Failed to extract device certificate public key.", + VerificationResult::ERROR_CERT_INVALID); + } + // Make sure the key is RSA. + const int public_key_type = EVP_PKEY_id(public_key.get()); + if (public_key_type != EVP_PKEY_RSA) { + return VerificationResult( + std::string("Expected RSA key type for client certificate, got ") + + base::IntToString(public_key_type) + " instead.", + VerificationResult::ERROR_CERT_INVALID); + } + // Verify signature. + const crypto::ScopedEVP_MD_CTX ctx(EVP_MD_CTX_create()); + if (!ctx || + !EVP_DigestVerifyInit(ctx.get(), NULL, EVP_sha1(), NULL, + public_key.get()) || + !EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) || + !EVP_DigestVerifyFinal( + ctx.get(), reinterpret_cast<const uint8_t*>(signature.data()), + signature.size())) { + return VerificationResult("Signature verification failed.", + VerificationResult::ERROR_SIGNATURE_INVALID); + } + return VerificationResult(); + } + + std::string GetCommonName() const override { + int common_name_length = X509_NAME_get_text_by_NID( + x509_->cert_info->subject, NID_commonName, NULL, 0); + if (common_name_length < 0) + return std::string(); + std::string common_name; + common_name_length = X509_NAME_get_text_by_NID( + x509_->cert_info->subject, NID_commonName, + WriteInto(&common_name, static_cast<size_t>(common_name_length) + 1), + common_name_length + 1); + if (common_name_length < 0) + return std::string(); + return common_name; + } + + private: + ScopedX509 x509_; +}; + +} // namespace + +VerificationResult VerifyDeviceCert( + const base::StringPiece& device_cert, + const std::vector<std::string>& ica_certs, + scoped_ptr<CertVerificationContext>* context) { + crypto::EnsureOpenSSLInit(); + crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); + + // If the list of intermediates is empty then use kPublicKeyICA1 as + // the trusted CA (legacy case). + // Otherwise, use the first intermediate in the list as long as it + // is in the allowed list of intermediates. + base::StringPiece ica_public_key_der = + (ica_certs.size() == 0) + ? cast_channel::GetDefaultTrustedICAPublicKey() + : cast_channel::GetTrustedICAPublicKey(ica_certs[0]); + + if (ica_public_key_der.empty()) { + return VerificationResult( + "Device certificate is not signed by a trusted CA", + VerificationResult::ERROR_CERT_UNTRUSTED); + } + // Initialize the ICA public key. + const uint8_t* ica_public_key_der_ptr = + reinterpret_cast<const uint8_t*>(ica_public_key_der.data()); + const uint8_t* ica_public_key_der_end = + ica_public_key_der_ptr + ica_public_key_der.size(); + crypto::ScopedRSA ica_public_key_rsa(d2i_RSAPublicKey( + NULL, &ica_public_key_der_ptr, ica_public_key_der.size())); + if (!ica_public_key_rsa || ica_public_key_der_ptr != ica_public_key_der_end) { + return VerificationResult("Failed to import trusted public key.", + VerificationResult::ERROR_INTERNAL); + } + crypto::ScopedEVP_PKEY ica_public_key_evp(EVP_PKEY_new()); + if (!ica_public_key_evp || + !EVP_PKEY_set1_RSA(ica_public_key_evp.get(), ica_public_key_rsa.get())) { + return VerificationResult("Failed to import trusted public key.", + VerificationResult::ERROR_INTERNAL); + } + // Parse the device certificate. + const uint8_t* device_cert_der_ptr = + reinterpret_cast<const uint8_t*>(device_cert.data()); + const uint8_t* device_cert_der_end = device_cert_der_ptr + device_cert.size(); + ScopedX509 device_cert_x509( + d2i_X509(NULL, &device_cert_der_ptr, device_cert.size())); + if (!device_cert_x509 || device_cert_der_ptr != device_cert_der_end) { + return VerificationResult("Failed to parse device certificate.", + VerificationResult::ERROR_CERT_INVALID); + } + // Verify device certificate. + if (X509_verify(device_cert_x509.get(), ica_public_key_evp.get()) != 1) { + return VerificationResult( + "Device certificate signature verification failed.", + VerificationResult::ERROR_CERT_INVALID); + } + + if (context) { + scoped_ptr<CertVerificationContext> tmp_context( + new CertVerificationContextOpenSSL(device_cert_x509.release())); + tmp_context.swap(*context); + } + + return VerificationResult(); +} + +std::string VerificationResult::GetLogString() const { + return error_message; +} + +} // namespace cast_crypto +} // namespace core_api +} // namespace extensions diff --git a/extensions/extensions.gyp b/extensions/extensions.gyp index e7e9b37..d52564d 100644 --- a/extensions/extensions.gyp +++ b/extensions/extensions.gyp @@ -84,6 +84,8 @@ 'common/api/sockets/sockets_manifest_handler.h', 'common/api/sockets/sockets_manifest_permission.cc', 'common/api/sockets/sockets_manifest_permission.h', + 'common/cast/cast_cert_validator.cc', + 'common/cast/cast_cert_validator.h', 'common/common_manifest_handlers.cc', 'common/common_manifest_handlers.h', 'common/csp_validator.cc', @@ -281,6 +283,31 @@ 'common/manifest_handlers/nacl_modules_handler.h', ], }], + ['use_openssl==1', { + 'sources': [ + 'common/cast/cast_cert_validator_openssl.cc', + ], + 'dependencies': [ + '../third_party/boringssl/boringssl.gyp:boringssl', + ], + }, { + 'sources': [ + 'common/cast/cast_cert_validator_nss.cc', + ], + 'conditions': [ + ['os_posix == 1 and OS != "mac" and OS != "ios" and OS != "android"', { + 'dependencies': [ + '../build/linux/system.gyp:ssl', + ], + }], + ['OS == "mac" or OS == "ios" or OS == "win"', { + 'dependencies': [ + '../third_party/nss/nss.gyp:nspr', + '../third_party/nss/nss.gyp:nss', + ], + }], + ], + }], ], }, { @@ -824,32 +851,6 @@ 'browser/api/vpn_provider/vpn_service_factory.h' ] }], - ['use_openssl==1', { - 'sources': [ - 'browser/api/cast_channel/cast_auth_util_openssl.cc', - ], - 'dependencies': [ - '../third_party/boringssl/boringssl.gyp:boringssl', - ], - }, { - 'sources': [ - # cast_auth_util_nss.cc uses NSS functions. - 'browser/api/cast_channel/cast_auth_util_nss.cc', - ], - 'conditions': [ - ['os_posix == 1 and OS != "mac" and OS != "ios" and OS != "android"', { - 'dependencies': [ - '../build/linux/system.gyp:ssl', - ], - }], - ['OS == "mac" or OS == "ios" or OS == "win"', { - 'dependencies': [ - '../third_party/nss/nss.gyp:nspr', - '../third_party/nss/nss.gyp:nss', - ], - }], - ], - }], ['OS != "linux"', { 'sources': [ 'browser/api/audio/audio_service.cc', |