summaryrefslogtreecommitdiffstats
path: root/extensions
diff options
context:
space:
mode:
authorsheretov <sheretov@chromium.org>2015-01-21 06:31:50 -0800
committerCommit bot <commit-bot@chromium.org>2015-01-21 14:33:46 +0000
commit57988d1f46e6ce2a2d65aa87c01b29b2732a110f (patch)
tree50a6646de29c93e9eebf22145ad09c0deefb1e14 /extensions
parent0dc6c55b60d7593da4ad9dc832c6cf0594f1b3bf (diff)
downloadchromium_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.gn6
-rw-r--r--extensions/browser/api/cast_channel/cast_auth_util.cc57
-rw-r--r--extensions/browser/api/cast_channel/cast_auth_util_nss.cc142
-rw-r--r--extensions/browser/api/cast_channel/cast_auth_util_openssl.cc144
-rw-r--r--extensions/browser/api/cast_channel/cast_auth_util_unittest.cc3
-rw-r--r--extensions/common/BUILD.gn9
-rw-r--r--extensions/common/cast/cast_cert_validator.cc30
-rw-r--r--extensions/common/cast/cast_cert_validator.h96
-rw-r--r--extensions/common/cast/cast_cert_validator_nss.cc155
-rw-r--r--extensions/common/cast/cast_cert_validator_openssl.cc158
-rw-r--r--extensions/extensions.gyp53
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',