diff options
30 files changed, 797 insertions, 737 deletions
diff --git a/chrome/browser/extensions/api/networking_private/crypto_verify_impl.cc b/chrome/browser/extensions/api/networking_private/crypto_verify_impl.cc index da34f3f..1960a12 100644 --- a/chrome/browser/extensions/api/networking_private/crypto_verify_impl.cc +++ b/chrome/browser/extensions/api/networking_private/crypto_verify_impl.cc @@ -38,8 +38,8 @@ bool DecodeAndVerifyCredentials( return false; } *verified = networking_private_crypto::VerifyCredentials( - credentials.certificate, decoded_signed_data, credentials.unsigned_data, - credentials.device_bssid); + credentials.certificate, credentials.intermediate_certificates, + decoded_signed_data, credentials.unsigned_data, credentials.device_bssid); return true; } @@ -158,6 +158,8 @@ void VerifyAndEncryptCredentialsCompleted( CryptoVerifyImpl::Credentials::Credentials( const VerificationProperties& properties) { certificate = properties.certificate; + if (properties.intermediate_certificates.get()) + intermediate_certificates = *properties.intermediate_certificates; signed_data = properties.signed_data; std::vector<std::string> data_parts; diff --git a/chrome/browser/extensions/api/networking_private/crypto_verify_impl.h b/chrome/browser/extensions/api/networking_private/crypto_verify_impl.h index bacdc5a..b35021a 100644 --- a/chrome/browser/extensions/api/networking_private/crypto_verify_impl.h +++ b/chrome/browser/extensions/api/networking_private/crypto_verify_impl.h @@ -29,6 +29,7 @@ class CryptoVerifyImpl : public NetworkingPrivateDelegate::VerifyDelegate { ~Credentials(); std::string certificate; + std::vector<std::string> intermediate_certificates; std::string signed_data; std::string unsigned_data; std::string device_bssid; diff --git a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc index ac57b5d..d2aa61c 100644 --- a/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc +++ b/chrome/browser/extensions/api/networking_private/networking_private_chromeos_apitest.cc @@ -9,6 +9,9 @@ #include "chrome/browser/browser_process.h" #include "chrome/browser/chromeos/login/helper.h" #include "chrome/browser/chromeos/net/network_portal_detector_test_impl.h" +#include "chrome/browser/extensions/api/networking_private/networking_private_chromeos.h" +#include "chrome/browser/extensions/api/networking_private/networking_private_credentials_getter.h" +#include "chrome/browser/extensions/api/networking_private/networking_private_delegate_factory.h" #include "chrome/browser/extensions/extension_apitest.h" #include "chromeos/chromeos_switches.h" #include "chromeos/dbus/cryptohome_client.h" @@ -61,6 +64,10 @@ using chromeos::ShillManagerClient; using chromeos::ShillProfileClient; using chromeos::ShillServiceClient; +using extensions::NetworkingPrivateDelegate; +using extensions::NetworkingPrivateDelegateFactory; +using extensions::NetworkingPrivateChromeOS; + namespace { const char kUser1ProfilePath[] = "/profile/user1/shill"; @@ -68,6 +75,33 @@ const char kWifiDevicePath[] = "/device/stub_wifi_device1"; const char kCellularDevicePath[] = "/device/stub_cellular_device1"; const char kIPConfigPath[] = "/ipconfig/ipconfig1"; +// Stub Verify* methods implementation to satisfy expectations of +// networking_private_apitest. +class CryptoVerifyStub : public NetworkingPrivateDelegate::VerifyDelegate { + void VerifyDestination( + const VerificationProperties& verification_properties, + const BoolCallback& success_callback, + const FailureCallback& failure_callback) override { + success_callback.Run(true); + } + + void VerifyAndEncryptCredentials( + const std::string& guid, + const VerificationProperties& verification_properties, + const StringCallback& success_callback, + const FailureCallback& failure_callback) override { + success_callback.Run("encrypted_credentials"); + } + + void VerifyAndEncryptData( + const VerificationProperties& verification_properties, + const std::string& data, + const StringCallback& success_callback, + const FailureCallback& failure_callback) override { + success_callback.Run("encrypted_data"); + } +}; + class TestListener : public content::NotificationObserver { public: TestListener(const std::string& message, const base::Closure& callback) @@ -196,6 +230,12 @@ class NetworkingPrivateChromeOSApiTest : public ExtensionApiTest { true /* add_to_visible */); } + static KeyedService* CreateNetworkingPrivateServiceClient( + content::BrowserContext* profile) { + scoped_ptr<CryptoVerifyStub> crypto_verify(new CryptoVerifyStub); + return new NetworkingPrivateChromeOS(profile, crypto_verify.Pass()); + } + void SetUpOnMainThread() override { detector_ = new NetworkPortalDetectorTestImpl(); NetworkPortalDetector::InitializeForTesting(detector_); @@ -203,6 +243,9 @@ class NetworkingPrivateChromeOSApiTest : public ExtensionApiTest { ExtensionApiTest::SetUpOnMainThread(); content::RunAllPendingInMessageLoop(); + NetworkingPrivateDelegateFactory::GetInstance()->SetTestingFactory( + profile(), &CreateNetworkingPrivateServiceClient); + InitializeSanitizedUsername(); DBusThreadManager* dbus_manager = DBusThreadManager::Get(); diff --git a/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_chromeos.cc b/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_chromeos.cc new file mode 100644 index 0000000..a47b2e9 --- /dev/null +++ b/chrome/browser/extensions/api/networking_private/networking_private_credentials_getter_chromeos.cc @@ -0,0 +1,43 @@ +// 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 "chrome/browser/extensions/api/networking_private/networking_private_credentials_getter.h" + +const char kErrorNotImplemented[] = "Error.NotImplemented"; + +namespace extensions { + +class NetworkingPrivateCredentialsGetterChromeos + : public NetworkingPrivateCredentialsGetter { + public: + NetworkingPrivateCredentialsGetterChromeos() {} + + void Start(const std::string& network_guid, + const std::string& public_key, + const CredentialsCallback& callback) override; + + private: + ~NetworkingPrivateCredentialsGetterChromeos() override; + + DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateCredentialsGetterChromeos); +}; + +NetworkingPrivateCredentialsGetterChromeos:: + ~NetworkingPrivateCredentialsGetterChromeos() { +} + +void NetworkingPrivateCredentialsGetterChromeos::Start( + const std::string& network_guid, + const std::string& public_key, + const CredentialsCallback& callback) { + // TODO(sheretov) add credential slurping from sync. + callback.Run(std::string(), kErrorNotImplemented); +} + +NetworkingPrivateCredentialsGetter* +NetworkingPrivateCredentialsGetter::Create() { + return new NetworkingPrivateCredentialsGetterChromeos(); +} + +} // namespace extensions diff --git a/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.cc b/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.cc deleted file mode 100644 index a4d06a1..0000000 --- a/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.cc +++ /dev/null @@ -1,114 +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 "chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.h" - -#include "base/bind.h" -#include "chrome/browser/extensions/api/networking_private/networking_private_api.h" -#include "chrome/common/extensions/api/networking_private.h" -#include "chromeos/dbus/dbus_thread_manager.h" -#include "chromeos/dbus/shill_manager_client.h" -#include "chromeos/network/network_handler.h" -#include "chromeos/network/network_state.h" -#include "chromeos/network/network_state_handler.h" - -using chromeos::ShillManagerClient; - -namespace { - -ShillManagerClient* GetShillManagerClient() { - return chromeos::DBusThreadManager::Get()->GetShillManagerClient(); -} - -bool GetServicePathFromGuid(const std::string& guid, - std::string* service_path, - std::string* error) { - const chromeos::NetworkState* network = chromeos::NetworkHandler::Get() - ->network_state_handler() - ->GetNetworkStateFromGuid(guid); - if (!network) { - *error = extensions::networking_private::kErrorInvalidNetworkGuid; - return false; - } - *service_path = network->path(); - return true; -} - -ShillManagerClient::VerificationProperties ConvertVerificationProperties( - const extensions::api::networking_private::VerificationProperties& input) { - ShillManagerClient::VerificationProperties output; - output.certificate = input.certificate; - output.public_key = input.public_key; - output.nonce = input.nonce; - output.signed_data = input.signed_data; - output.device_serial = input.device_serial; - output.device_ssid = input.device_ssid; - output.device_bssid = input.device_bssid; - return output; -} - -void ShillFailureCallback( - const extensions::NetworkingPrivateDelegate::FailureCallback& callback, - const std::string& error_name, - const std::string& error_message) { - callback.Run(error_name); -} - -} // namespace - -namespace extensions { - -NetworkingPrivateVerifyDelegateChromeOS:: - NetworkingPrivateVerifyDelegateChromeOS() { -} - -NetworkingPrivateVerifyDelegateChromeOS:: - ~NetworkingPrivateVerifyDelegateChromeOS() { -} - -void NetworkingPrivateVerifyDelegateChromeOS::VerifyDestination( - const VerificationProperties& verification_properties, - const BoolCallback& success_callback, - const FailureCallback& failure_callback) { - ShillManagerClient::VerificationProperties verification_property_struct = - ConvertVerificationProperties(verification_properties); - - GetShillManagerClient()->VerifyDestination( - verification_property_struct, success_callback, - base::Bind(&ShillFailureCallback, failure_callback)); -} - -void NetworkingPrivateVerifyDelegateChromeOS::VerifyAndEncryptCredentials( - const std::string& guid, - const VerificationProperties& verification_properties, - const StringCallback& success_callback, - const FailureCallback& failure_callback) { - std::string service_path, error; - if (!GetServicePathFromGuid(guid, &service_path, &error)) { - failure_callback.Run(error); - return; - } - - ShillManagerClient::VerificationProperties verification_property_struct = - ConvertVerificationProperties(verification_properties); - - GetShillManagerClient()->VerifyAndEncryptCredentials( - verification_property_struct, service_path, success_callback, - base::Bind(&ShillFailureCallback, failure_callback)); -} - -void NetworkingPrivateVerifyDelegateChromeOS::VerifyAndEncryptData( - const VerificationProperties& verification_properties, - const std::string& data, - const StringCallback& success_callback, - const FailureCallback& failure_callback) { - ShillManagerClient::VerificationProperties verification_property_struct = - ConvertVerificationProperties(verification_properties); - - GetShillManagerClient()->VerifyAndEncryptData( - verification_property_struct, data, success_callback, - base::Bind(&ShillFailureCallback, failure_callback)); -} - -} // namespace extensions diff --git a/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.h b/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.h deleted file mode 100644 index dbef880..0000000 --- a/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.h +++ /dev/null @@ -1,38 +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. - -#ifndef CHROME_BROWSER_EXTENSIONS_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_VERIFY_DELEGATE_CHROMEOS_H_ -#define CHROME_BROWSER_EXTENSIONS_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_VERIFY_DELEGATE_CHROMEOS_H_ - -#include "chrome/browser/extensions/api/networking_private/networking_private_delegate.h" - -namespace extensions { - -class NetworkingPrivateVerifyDelegateChromeOS - : public NetworkingPrivateDelegate::VerifyDelegate { - public: - NetworkingPrivateVerifyDelegateChromeOS(); - ~NetworkingPrivateVerifyDelegateChromeOS() override; - - void VerifyDestination(const VerificationProperties& verification_properties, - const BoolCallback& success_callback, - const FailureCallback& failure_callback) override; - void VerifyAndEncryptCredentials( - const std::string& guid, - const VerificationProperties& verification_properties, - const StringCallback& success_callback, - const FailureCallback& failure_callback) override; - void VerifyAndEncryptData( - const VerificationProperties& verification_properties, - const std::string& data, - const StringCallback& success_callback, - const FailureCallback& failure_callback) override; - - private: - DISALLOW_COPY_AND_ASSIGN(NetworkingPrivateVerifyDelegateChromeOS); -}; - -} // namespace extensions - -#endif // CHROME_BROWSER_EXTENSIONS_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_VERIFY_DELEGATE_CHROMEOS_H_ diff --git a/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.cc b/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.cc index f2884d0..44226d5 100644 --- a/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.cc +++ b/chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.cc @@ -4,11 +4,7 @@ #include "chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.h" -#if defined(OS_CHROMEOS) -#include "chrome/browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.h" -#elif defined(OS_WIN) || defined(OSMACOSX) #include "chrome/browser/extensions/api/networking_private/crypto_verify_impl.h" -#endif namespace extensions { @@ -22,9 +18,7 @@ NetworkingPrivateVerifyDelegateFactoryImpl:: scoped_ptr<NetworkingPrivateDelegate::VerifyDelegate> NetworkingPrivateVerifyDelegateFactoryImpl::CreateDelegate() { -#if defined(OS_CHROMEOS) - return make_scoped_ptr(new NetworkingPrivateVerifyDelegateChromeOS()); -#elif defined(OS_WIN) || defined(OSMACOSX) +#if defined(OS_CHROMEOS) || defined(OS_WIN) || defined(OS_MACOSX) return make_scoped_ptr(new CryptoVerifyImpl()); #else return nullptr; diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi index 7da6df5..7373c5a 100644 --- a/chrome/chrome_browser_extensions.gypi +++ b/chrome/chrome_browser_extensions.gypi @@ -24,6 +24,8 @@ 'browser/extensions/api/log_private/syslog_parser.cc', 'browser/extensions/api/log_private/syslog_parser.h', 'browser/extensions/api/messaging/native_message_host_chromeos.cc', + 'browser/extensions/api/networking_private/crypto_verify_impl.cc', + 'browser/extensions/api/networking_private/crypto_verify_impl.h', 'browser/extensions/api/terminal/terminal_extension_helper.cc', 'browser/extensions/api/terminal/terminal_extension_helper.h', 'browser/extensions/api/terminal/terminal_private_api.cc', @@ -339,6 +341,7 @@ 'browser/extensions/api/networking_private/networking_private_api.h', 'browser/extensions/api/networking_private/networking_private_chromeos.cc', 'browser/extensions/api/networking_private/networking_private_chromeos.h', + 'browser/extensions/api/networking_private/networking_private_credentials_getter_chromeos.cc', 'browser/extensions/api/networking_private/networking_private_delegate.cc', 'browser/extensions/api/networking_private/networking_private_delegate.h', 'browser/extensions/api/networking_private/networking_private_delegate_observer.h', @@ -348,8 +351,6 @@ 'browser/extensions/api/networking_private/networking_private_event_router_factory.h', 'browser/extensions/api/networking_private/networking_private_delegate_factory.cc', 'browser/extensions/api/networking_private/networking_private_delegate_factory.h', - 'browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.cc', - 'browser/extensions/api/networking_private/networking_private_verify_delegate_chromeos.h', 'browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.cc', 'browser/extensions/api/networking_private/networking_private_verify_delegate_factory_impl.h', 'browser/extensions/api/notification_provider/notification_provider_api.cc', diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 83a320d..028de4f 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -252,16 +252,23 @@ 'common/service_process_util_win.cc', ], 'chrome_common_win_mac_sources': [ - 'common/extensions/api/networking_private/networking_private_crypto_nss.cc', - 'common/extensions/api/networking_private/networking_private_crypto_openssl.cc', - 'common/extensions/api/networking_private/networking_private_crypto.cc', - 'common/extensions/api/networking_private/networking_private_crypto.h', 'common/media_galleries/itunes_library.cc', 'common/media_galleries/itunes_library.h', 'common/media_galleries/picasa_types.cc', 'common/media_galleries/picasa_types.h', 'common/media_galleries/pmp_constants.h', ], + 'chrome_common_networking_private_sources_openssl' : [ + 'common/extensions/api/networking_private/networking_private_crypto_openssl.cc', + 'common/extensions/api/networking_private/networking_private_crypto.cc', + 'common/extensions/api/networking_private/networking_private_crypto.h', + ], + 'chrome_common_networking_private_sources_nss' : [ + 'common/extensions/api/networking_private/networking_private_crypto_nss.cc', + 'common/extensions/api/networking_private/networking_private_crypto.cc', + 'common/extensions/api/networking_private/networking_private_crypto.h', + ], + 'chrome_common_mac_sources': [ 'common/media_galleries/iphoto_library.cc', 'common/media_galleries/iphoto_library.h', @@ -344,12 +351,15 @@ ['OS=="win" or OS=="mac"', { 'sources': [ '<@(chrome_common_win_mac_sources)' ], }], - ['(OS=="win" or OS=="mac") and use_openssl==1', { - # networking_private_crypto_openssl.cc depends on boringssl. + ['(OS=="win" or OS=="mac" or chromeos==1) and use_openssl==1', { + 'sources': [ '<@(chrome_common_networking_private_sources_openssl)' ], 'dependencies': [ '../third_party/boringssl/boringssl.gyp:boringssl', ], }], + ['(OS=="win" or OS=="mac" or chromeos==1) and use_openssl!=1', { + 'sources': [ '<@(chrome_common_networking_private_sources_nss)' ], + }], ['OS=="mac"', { 'sources': [ '<@(chrome_common_mac_sources)' ], }], @@ -493,17 +503,6 @@ ['safe_browsing==2', { 'defines': [ 'MOBILE_SAFE_BROWSING' ], }], - ['use_openssl==1', { - 'sources!': [ - 'common/extensions/api/networking_private/networking_private_crypto_nss.cc', - ], - }, - { # else !use_openssl - 'sources!': [ - 'common/extensions/api/networking_private/networking_private_crypto_openssl.cc', - ], - }, - ], ], 'target_conditions': [ ['OS == "ios"', { diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi index 4ec1b5e..7e80b91 100644 --- a/chrome/chrome_tests_unit.gypi +++ b/chrome/chrome_tests_unit.gypi @@ -2418,6 +2418,11 @@ ['OS=="win" or OS=="mac"', { 'sources': [ '<@(chrome_unit_tests_win_mac_sources)' ], }], + ['OS=="win" or OS=="mac" or chromeos==1', { + 'sources': [ + 'common/extensions/api/networking_private/networking_private_crypto_unittest.cc', + ], + }], ['enable_rlz!=0', { 'dependencies': [ '../rlz/rlz.gyp:test_support_rlz', diff --git a/chrome/common/BUILD.gn b/chrome/common/BUILD.gn index 6fd6111..d6a03e0 100644 --- a/chrome/common/BUILD.gn +++ b/chrome/common/BUILD.gn @@ -114,15 +114,23 @@ static_library("common") { if (is_win || is_mac) { sources += rebase_path(gypi_values.chrome_common_win_mac_sources, ".", "//chrome") + deps += [ "//breakpad:client" ] + } + if (is_win || is_mac || is_chromeos) { if (use_openssl) { - sources -= [ "extensions/api/networking_private/networking_private_crypto_nss.cc" ] + sources += rebase_path( + gypi_values.chrome_common_networking_private_sources_openssl, + ".", + "//chrome") # networking_private_crypto_openssl.cc depends on boringssl. deps += [ "//third_party/boringssl" ] } else { - sources -= [ "extensions/api/networking_private/networking_private_crypto_openssl.cc" ] + sources += + rebase_path(gypi_values.chrome_common_networking_private_sources_nss, + ".", + "//chrome") } - deps += [ "//breakpad:client" ] } if (is_mac) { sources += diff --git a/chrome/common/extensions/api/networking_private.json b/chrome/common/extensions/api/networking_private.json index bac9fea..8e73463 100644 --- a/chrome/common/extensions/api/networking_private.json +++ b/chrome/common/extensions/api/networking_private.json @@ -27,11 +27,17 @@ "properties": { "certificate": { "type": "string", - "description": "A string containing a PEM-encoded X.509 certificate for use in verifying the signed data." + "description": "A string containing a PEM-encoded (including the \"BEGIN CERTIFICATE\" header and \"END CERTIFICATE\" footer) X.509 certificate for use in verifying the signed data." + }, + "intermediateCertificates": { + "type": "array", + "items": { "type": "string" }, + "optional": true, + "description": "An array of PEM-encoded X.509 intermediate certificate authority certificates. Each PEM-encoded certificate is expected to have the \"BEGIN CERTIFICATE\" header and \"END CERTIFICATE\" footer." }, "publicKey": { "type": "string", - "description": "A string containing a PEM-encoded RSA public key to be used to compare with the one in signedData" + "description": "A string containing a Base64-encoded RSAPublicKey ASN.1 structure, representing the public key to be used by verifyAndEncryptCredentials and verifyAndEncryptData methods." }, "nonce": { "type": "string", diff --git a/chrome/common/extensions/api/networking_private/networking_private_crypto.cc b/chrome/common/extensions/api/networking_private/networking_private_crypto.cc index 34c2776..66af2f9 100644 --- a/chrome/common/extensions/api/networking_private/networking_private_crypto.cc +++ b/chrome/common/extensions/api/networking_private/networking_private_crypto.cc @@ -4,33 +4,82 @@ #include "chrome/common/extensions/api/networking_private/networking_private_crypto.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "extensions/common/cast/cast_cert_validator.h" +#include "net/cert/pem_tokenizer.h" + +namespace { + +namespace cast_crypto = ::extensions::core_api::cast_crypto; + +} // namespace + namespace networking_private_crypto { -const uint8 kTrustedCAPublicKeyDER[] = { - 0x30, 0x82, 0x01, 0x0a, 0x02, 0x82, 0x01, 0x01, 0x00, 0xbc, 0x22, 0x80, - 0xbd, 0x80, 0xf6, 0x3a, 0x21, 0x00, 0x3b, 0xae, 0x76, 0x5e, 0x35, 0x7f, - 0x3d, 0xc3, 0x64, 0x5c, 0x55, 0x94, 0x86, 0x34, 0x2f, 0x05, 0x87, 0x28, - 0xcd, 0xf7, 0x69, 0x8c, 0x17, 0xb3, 0x50, 0xa7, 0xb8, 0x82, 0xfa, 0xdf, - 0xc7, 0x43, 0x2d, 0xd6, 0x7e, 0xab, 0xa0, 0x6f, 0xb7, 0x13, 0x72, 0x80, - 0xa4, 0x47, 0x15, 0xc1, 0x20, 0x99, 0x50, 0xcd, 0xec, 0x14, 0x62, 0x09, - 0x5b, 0xa4, 0x98, 0xcd, 0xd2, 0x41, 0xb6, 0x36, 0x4e, 0xff, 0xe8, 0x2e, - 0x32, 0x30, 0x4a, 0x81, 0xa8, 0x42, 0xa3, 0x6c, 0x9b, 0x33, 0x6e, 0xca, - 0xb2, 0xf5, 0x53, 0x66, 0xe0, 0x27, 0x53, 0x86, 0x1a, 0x85, 0x1e, 0xa7, - 0x39, 0x3f, 0x4a, 0x77, 0x8e, 0xfb, 0x54, 0x66, 0x66, 0xfb, 0x58, 0x54, - 0xc0, 0x5e, 0x39, 0xc7, 0xf5, 0x50, 0x06, 0x0b, 0xe0, 0x8a, 0xd4, 0xce, - 0xe1, 0x6a, 0x55, 0x1f, 0x8b, 0x17, 0x00, 0xe6, 0x69, 0xa3, 0x27, 0xe6, - 0x08, 0x25, 0x69, 0x3c, 0x12, 0x9d, 0x8d, 0x05, 0x2c, 0xd6, 0x2e, 0xa2, - 0x31, 0xde, 0xb4, 0x52, 0x50, 0xd6, 0x20, 0x49, 0xde, 0x71, 0xa0, 0xf9, - 0xad, 0x20, 0x40, 0x12, 0xf1, 0xdd, 0x25, 0xeb, 0xd5, 0xe6, 0xb8, 0x36, - 0xf4, 0xd6, 0x8f, 0x7f, 0xca, 0x43, 0xdc, 0xd7, 0x10, 0x5b, 0xe6, 0x3f, - 0x51, 0x8a, 0x85, 0xb3, 0xf3, 0xff, 0xf6, 0x03, 0x2d, 0xcb, 0x23, 0x4f, - 0x9c, 0xad, 0x18, 0xe7, 0x93, 0x05, 0x8c, 0xac, 0x52, 0x9a, 0xf7, 0x4c, - 0xe9, 0x99, 0x7a, 0xbe, 0x6e, 0x7e, 0x4d, 0x0a, 0xe3, 0xc6, 0x1c, 0xa9, - 0x93, 0xfa, 0x3a, 0xa5, 0x91, 0x5d, 0x1c, 0xbd, 0x66, 0xeb, 0xcc, 0x60, - 0xdc, 0x86, 0x74, 0xca, 0xcf, 0xf8, 0x92, 0x1c, 0x98, 0x7d, 0x57, 0xfa, - 0x61, 0x47, 0x9e, 0xab, 0x80, 0xb7, 0xe4, 0x48, 0x80, 0x2a, 0x92, 0xc5, - 0x1b, 0x02, 0x03, 0x01, 0x00, 0x01}; - -const size_t kTrustedCAPublicKeyDERLength = sizeof(kTrustedCAPublicKeyDER); +bool VerifyCredentials( + const std::string& certificate, + const std::vector<std::string>& intermediate_certificates, + const std::string& signature, + const std::string& data, + const std::string& connected_mac) { + static const char kErrorPrefix[] = "Device verification failed. "; + + std::vector<std::string> headers; + headers.push_back("CERTIFICATE"); + + // Convert certificate from PEM to raw DER + net::PEMTokenizer pem_tok(certificate, headers); + if (!pem_tok.GetNext()) { + LOG(ERROR) << kErrorPrefix << "Failed to parse device certificate."; + return false; + } + std::string der_certificate = pem_tok.data(); + + // Convert intermediate certificates from PEM to raw DER + std::vector<std::string> der_intermediate_certificates; + for (size_t idx = 0; idx < intermediate_certificates.size(); ++idx) { + net::PEMTokenizer ica_pem_tok(intermediate_certificates[idx], headers); + if (ica_pem_tok.GetNext()) { + der_intermediate_certificates.push_back(ica_pem_tok.data()); + } else { + LOG(WARNING) << "Failed to parse intermediate certificates."; + } + } + + // Verify device certificate + scoped_ptr<cast_crypto::CertVerificationContext> verification_context; + cast_crypto::VerificationResult verification_result = + cast_crypto::VerifyDeviceCert(der_certificate, + der_intermediate_certificates, + &verification_context); + + if (verification_result.Failure()) { + LOG(ERROR) << kErrorPrefix << verification_result.GetLogString(); + return false; + } + + // Check that the device listed in the certificate is correct. + // Something like evt_e161 001a11ffacdf + std::string common_name = verification_context->GetCommonName(); + std::string translated_mac; + base::RemoveChars(connected_mac, ":", &translated_mac); + if (!EndsWith(common_name, translated_mac, false)) { + LOG(ERROR) << kErrorPrefix << "MAC addresses don't match."; + return false; + } + + // Use the public key from verified certificate to verify |signature| over + // |data|. + verification_result = + verification_context->VerifySignatureOverData(signature, data); + + if (verification_result.Failure()) { + LOG(ERROR) << kErrorPrefix << verification_result.GetLogString(); + return false; + } + return true; +} } // namespace networking_private_crypto diff --git a/chrome/common/extensions/api/networking_private/networking_private_crypto.h b/chrome/common/extensions/api/networking_private/networking_private_crypto.h index e613293..8940c0b 100644 --- a/chrome/common/extensions/api/networking_private/networking_private_crypto.h +++ b/chrome/common/extensions/api/networking_private/networking_private_crypto.h @@ -20,10 +20,12 @@ namespace networking_private_crypto { // 2) The certificate is a valid PEM encoded certificate signed by trusted CA. // 3) |signature| is a valid signature for |data|, using the public key in // |certificate| -bool VerifyCredentials(const std::string& certificate, - const std::string& signature, - const std::string& data, - const std::string& connected_mac); +bool VerifyCredentials( + const std::string& certificate, + const std::vector<std::string>& intermediate_certificates, + const std::string& signature, + const std::string& data, + const std::string& connected_mac); // Encrypt |data| with |public_key|. |public_key| is a DER-encoded // RSAPublicKey. |data| is some string of bytes that is smaller than the @@ -44,12 +46,6 @@ bool DecryptByteString(const std::string& private_key_pem, const std::vector<uint8_t>& encrypted_data, std::string* decrypted_output); -// The trusted public key as a DER-encoded PKCS#1 RSAPublicKey structure. -extern const uint8_t kTrustedCAPublicKeyDER[]; - -// The length of |kTrustedCAPublicKeyDER| in bytes. -extern const size_t kTrustedCAPublicKeyDERLength; - } // namespace networking_private_crypto #endif // CHROME_COMMON_EXTENSIONS_API_NETWORKING_PRIVATE_NETWORKING_PRIVATE_CRYPTO_H_ diff --git a/chrome/common/extensions/api/networking_private/networking_private_crypto_nss.cc b/chrome/common/extensions/api/networking_private/networking_private_crypto_nss.cc index 2cdc33b..0839762 100644 --- a/chrome/common/extensions/api/networking_private/networking_private_crypto_nss.cc +++ b/chrome/common/extensions/api/networking_private/networking_private_crypto_nss.cc @@ -46,94 +46,6 @@ bool GetDERFromPEM(const std::string& pem_data, namespace networking_private_crypto { -bool VerifyCredentials(const std::string& certificate, - const std::string& signature, - const std::string& data, - const std::string& connected_mac) { - crypto::EnsureNSSInit(); - - std::vector<uint8_t> cert_data; - if (!GetDERFromPEM(certificate, "CERTIFICATE", &cert_data)) { - LOG(ERROR) << "Failed to parse certificate."; - return false; - } - SECItem der_cert; - der_cert.type = siDERCertBuffer; - der_cert.data = cert_data.data(); - der_cert.len = cert_data.size(); - - // Parse into a certificate structure. - typedef scoped_ptr< - CERTCertificate, - crypto::NSSDestroyer<CERTCertificate, CERT_DestroyCertificate> > - ScopedCERTCertificate; - ScopedCERTCertificate cert(CERT_NewTempCertificate( - CERT_GetDefaultCertDB(), &der_cert, NULL, PR_FALSE, PR_TRUE)); - if (!cert.get()) { - LOG(ERROR) << "Failed to parse certificate."; - return false; - } - - // Check that the certificate is signed by trusted CA. - SECItem trusted_ca_key_der_item; - trusted_ca_key_der_item.type = siDERCertBuffer; - trusted_ca_key_der_item.data = - const_cast<unsigned char*>(kTrustedCAPublicKeyDER); - trusted_ca_key_der_item.len = kTrustedCAPublicKeyDERLength; - crypto::ScopedSECKEYPublicKey ca_public_key( - SECKEY_ImportDERPublicKey(&trusted_ca_key_der_item, CKK_RSA)); - SECStatus verified = CERT_VerifySignedDataWithPublicKey( - &cert->signatureWrap, ca_public_key.get(), NULL); - if (verified != SECSuccess) { - LOG(ERROR) << "Certificate is not issued by the trusted CA."; - return false; - } - - // Check that the device listed in the certificate is correct. - // Something like evt_e161 001a11ffacdf - char* common_name = CERT_GetCommonName(&cert->subject); - if (!common_name) { - LOG(ERROR) << "Certificate does not have common name."; - return false; - } - - std::string subject_name(common_name); - PORT_Free(common_name); - std::string translated_mac; - base::RemoveChars(connected_mac, ":", &translated_mac); - if (!EndsWith(subject_name, translated_mac, false)) { - LOG(ERROR) << "MAC addresses don't match."; - return false; - } - - // Make sure that the certificate matches the unsigned data presented. - // Verify that the |signature| matches |data|. - crypto::ScopedSECKEYPublicKey public_key(CERT_ExtractPublicKey(cert.get())); - if (!public_key.get()) { - LOG(ERROR) << "Unable to extract public key from certificate."; - return false; - } - SECItem signature_item; - signature_item.type = siBuffer; - signature_item.data = - reinterpret_cast<unsigned char*>(const_cast<char*>(signature.c_str())); - signature_item.len = static_cast<unsigned int>(signature.size()); - verified = VFY_VerifyDataDirect( - reinterpret_cast<unsigned char*>(const_cast<char*>(data.c_str())), - data.size(), - public_key.get(), - &signature_item, - SEC_OID_PKCS1_RSA_ENCRYPTION, - SEC_OID_SHA1, - NULL, - NULL); - if (verified != SECSuccess) { - LOG(ERROR) << "Signed blobs did not match."; - return false; - } - return true; -} - bool EncryptByteString(const std::vector<uint8_t>& pub_key_der, const std::string& data, std::vector<uint8_t>* encrypted_output) { diff --git a/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc b/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc index e08b51d..2a22065 100644 --- a/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc +++ b/chrome/common/extensions/api/networking_private/networking_private_crypto_openssl.cc @@ -10,7 +10,6 @@ #include <openssl/x509.h> #include "base/logging.h" -#include "base/strings/string_util.h" #include "crypto/openssl_util.h" #include "crypto/rsa_private_key.h" #include "crypto/scoped_openssl_types.h" @@ -18,8 +17,6 @@ namespace { -typedef crypto::ScopedOpenSSL<X509, X509_free>::Type ScopedX509; - // Parses |pem_data| for a PEM block of |pem_type|. // Returns true if a |pem_type| block is found, storing the decoded result in // |der_output|. @@ -41,106 +38,6 @@ bool GetDERFromPEM(const std::string& pem_data, namespace networking_private_crypto { -bool VerifyCredentials(const std::string& certificate, - const std::string& signature, - const std::string& data, - const std::string& connected_mac) { - crypto::EnsureOpenSSLInit(); - crypto::OpenSSLErrStackTracer err_tracer(FROM_HERE); - - std::vector<uint8_t> cert_data; - if (!GetDERFromPEM(certificate, "CERTIFICATE", &cert_data)) { - LOG(ERROR) << "Failed to parse certificate."; - return false; - } - - // Parse into an OpenSSL X509. - const uint8_t* ptr = cert_data.empty() ? NULL : &cert_data[0]; - const uint8_t* end = ptr + cert_data.size(); - ScopedX509 cert(d2i_X509(NULL, &ptr, cert_data.size())); - if (!cert || ptr != end) { - LOG(ERROR) << "Failed to parse certificate."; - return false; - } - - // Import the trusted public key. - ptr = kTrustedCAPublicKeyDER; - crypto::ScopedRSA ca_public_key_rsa( - d2i_RSAPublicKey(NULL, &ptr, kTrustedCAPublicKeyDERLength)); - if (!ca_public_key_rsa || - ptr != kTrustedCAPublicKeyDER + kTrustedCAPublicKeyDERLength) { - NOTREACHED(); - LOG(ERROR) << "Failed to import trusted public key."; - return false; - } - 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 false; - } - - // Check that the certificate is signed by the trusted public key. - if (X509_verify(cert.get(), ca_public_key.get()) <= 0) { - LOG(ERROR) << "Certificate is not issued by the trusted CA."; - return false; - } - - // Check that the device listed in the certificate is correct. - // Something like evt_e161 001a11ffacdf - std::string common_name; - int common_name_length = X509_NAME_get_text_by_NID( - cert->cert_info->subject, NID_commonName, NULL, 0); - if (common_name_length < 0) { - LOG(ERROR) << "Certificate does not have common name."; - return false; - } - if (common_name_length > 0) { - common_name_length = X509_NAME_get_text_by_NID( - cert->cert_info->subject, - NID_commonName, - WriteInto(&common_name, common_name_length + 1), - common_name_length + 1); - DCHECK_EQ((int)common_name.size(), common_name_length); - if (common_name_length < 0) { - LOG(ERROR) << "Certificate does not have common name."; - return false; - } - common_name.resize(common_name_length); - } - - std::string translated_mac; - base::RemoveChars(connected_mac, ":", &translated_mac); - if (!EndsWith(common_name, translated_mac, false)) { - LOG(ERROR) << "MAC addresses don't match."; - return false; - } - - // Make sure that the certificate matches the unsigned data presented. - // Verify that the |signature| matches |data|. - crypto::ScopedEVP_PKEY public_key(X509_get_pubkey(cert.get())); - if (!public_key) { - LOG(ERROR) << "Unable to extract public key from certificate."; - return false; - } - - crypto::ScopedEVP_MD_CTX ctx(EVP_MD_CTX_create()); - if (!ctx) { - LOG(ERROR) << "Unable to allocate EVP_MD_CTX."; - return false; - } - if (EVP_DigestVerifyInit( - ctx.get(), NULL, EVP_sha1(), NULL, public_key.get()) <= 0 || - EVP_DigestVerifyUpdate(ctx.get(), data.data(), data.size()) <= 0 || - EVP_DigestVerifyFinal(ctx.get(), - reinterpret_cast<const uint8_t*>(signature.data()), - signature.size()) <= 0) { - LOG(ERROR) << "Signed blobs did not match."; - return false; - } - return true; -} - bool EncryptByteString(const std::vector<uint8_t>& pub_key_der, const std::string& data, std::vector<uint8_t>* encrypted_output) { diff --git a/chrome/common/extensions/api/networking_private/networking_private_crypto_unittest.cc b/chrome/common/extensions/api/networking_private/networking_private_crypto_unittest.cc index d482d59..15bd935 100644 --- a/chrome/common/extensions/api/networking_private/networking_private_crypto_unittest.cc +++ b/chrome/common/extensions/api/networking_private/networking_private_crypto_unittest.cc @@ -46,6 +46,30 @@ TEST_F(NetworkingPrivateCryptoTest, VerifyCredentials) { "wM9asRj3tJA5VRFbLbsit1VI7IaRCk9rsSKkpBUaVeKbPLz+y/Z6JonXXT6AxsfgUSKDd4B7" "MYLrTwMQfGuUaaaKko6ldKIrovjrcPloQr1Hxb2bipFcjLmG7nxQLoS6vQ==" "-----END CERTIFICATE-----"; + static const char kICAData[] = + "-----BEGIN CERTIFICATE-----" + "MIIDzTCCArWgAwIBAgIBAzANBgkqhkiG9w0BAQUFADB1MQswCQYDVQQGEwJVUzET" + "MBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzETMBEG" + "A1UECgwKR29vZ2xlIEluYzENMAsGA1UECwwEQ2FzdDEVMBMGA1UEAwwMQ2FzdCBS" + "b290IENBMB4XDTE0MDQwMjIwNTg1NFoXDTE5MDQwMjIwNTg1NFowfTELMAkGA1UE" + "BhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU1vdW50YWluIFZp" + "ZXcxEzARBgNVBAoMCkdvb2dsZSBJbmMxEjAQBgNVBAsMCUdvb2dsZSBUVjEYMBYG" + "A1UEAwwPRXVyZWthIEdlbjEgSUNBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB" + "CgKCAQEAvCKAvYD2OiEAO652XjV/PcNkXFWUhjQvBYcozfdpjBezUKe4gvrfx0Mt" + "1n6roG+3E3KApEcVwSCZUM3sFGIJW6SYzdJBtjZO/+guMjBKgahCo2ybM27KsvVT" + "ZuAnU4YahR6nOT9Kd477VGZm+1hUwF45x/VQBgvgitTO4WpVH4sXAOZpoyfmCCVp" + "PBKdjQUs1i6iMd60UlDWIEnecaD5rSBAEvHdJevV5rg29NaPf8pD3NcQW+Y/UYqF" + "s/P/9gMtyyNPnK0Y55MFjKxSmvdM6Zl6vm5+TQrjxhypk/o6pZFdHL1m68xg3IZ0" + "ys/4khyYfVf6YUeeq4C35EiAKpLFGwIDAQABo2AwXjAPBgNVHRMECDAGAQH/AgEA" + "MB0GA1UdDgQWBBQyr35sod0oQuWz4VmnWjnJ/4pinzAfBgNVHSMEGDAWgBR8mh59" + "33lUvNfMXsqZhkV5ZXQoGTALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEB" + "ABPENY9iGt6qsc5yq4JOO6EEqYbKVtkSf1AqW2yJc4M4EZ65eA6bpj9EVIKvDxYq" + "NI7q40f7jCXiS+Y73OXFaC3Xue8+DV7WVjAvf9QYy79ohnbqadA4U/Sb7vw4AzwT" + "KCMlH2fUJ5PCNFfTj6lAkeZOhxtegnEMTIB8zvXEb42H0hN4UxRRhCeKS9tIlAmI" + "Ql1ib0jTDDN6IgQYslrx0dyZzBAsRocq/d3ycXX71iMykoIHZ7rNJ2bDMddRdFk2" + "D0Ljj4fZjrQNyD4mot/9mqSrF1Q2/AdWQO3pJONcXRWRynJ4Ian3sWdq2B5Dq8Iz" + "kqrjM7lOq9YEQ+hMRdmOHP4=" + "-----END CERTIFICATE-----"; static const char kName[] = "eureka8997"; static const char kSsdpUdn[] = "c5b2a83b-5958-7ce6-b179-e1f44699429b"; static const char kHotspotBssid[] = "00:1A:11:FF:AC:DF"; @@ -91,26 +115,43 @@ TEST_F(NetworkingPrivateCryptoTest, VerifyCredentials) { // Checking basic verification operation. EXPECT_TRUE(networking_private_crypto::VerifyCredentials( - kCertData, signed_data, unsigned_data, kHotspotBssid)); + kCertData, std::vector<std::string>(), signed_data, unsigned_data, + kHotspotBssid)); + + // Checking verification operation with an ICA + std::vector<std::string> icas; + icas.push_back(kICAData); + EXPECT_TRUE(networking_private_crypto::VerifyCredentials( + kCertData, icas, signed_data, unsigned_data, kHotspotBssid)); // Checking that verification fails when the certificate is signed, but // subject is malformed. EXPECT_FALSE(networking_private_crypto::VerifyCredentials( - kBadSubjectCertData, signed_data, unsigned_data, kHotspotBssid)); + kBadSubjectCertData, std::vector<std::string>(), signed_data, + unsigned_data, kHotspotBssid)); // Checking that verification fails when certificate has invalid format. EXPECT_FALSE(networking_private_crypto::VerifyCredentials( - kBadCertData, signed_data, unsigned_data, kHotspotBssid)); + kBadCertData, std::vector<std::string>(), signed_data, unsigned_data, + kHotspotBssid)); + + // Checking that verification fails if we supply a bad ICA. + std::vector<std::string> bad_icas; + bad_icas.push_back(kCertData); + EXPECT_FALSE(networking_private_crypto::VerifyCredentials( + kCertData, bad_icas, signed_data, unsigned_data, kHotspotBssid)); // Checking that verification fails when Hotspot Bssid is invalid. EXPECT_FALSE(networking_private_crypto::VerifyCredentials( - kCertData, signed_data, unsigned_data, kBadHotspotBssid)); + kCertData, std::vector<std::string>(), signed_data, unsigned_data, + kBadHotspotBssid)); // Checking that verification fails when there is bad nonce in unsigned_data. unsigned_data = base::StringPrintf( "%s,%s,%s,%s,%s", kName, kSsdpUdn, kHotspotBssid, kPublicKey, kBadNonce); EXPECT_FALSE(networking_private_crypto::VerifyCredentials( - kCertData, signed_data, unsigned_data, kHotspotBssid)); + kCertData, std::vector<std::string>(), signed_data, unsigned_data, + kHotspotBssid)); } // Test that networking_private_crypto::EncryptByteString behaves as expected. diff --git a/chrome/test/data/extensions/api_test/networking/test.js b/chrome/test/data/extensions/api_test/networking/test.js index 9024129..6bd2292 100644 --- a/chrome/test/data/extensions/api_test/networking/test.js +++ b/chrome/test/data/extensions/api_test/networking/test.js @@ -15,6 +15,7 @@ var assertEq = chrome.test.assertEq; // Test properties for the verification API. var verificationProperties = { "certificate": "certificate", + "intermediateCertificates": ["ica1", "ica2", "ica3"], "publicKey": "cHVibGljX2tleQ==", // Base64("public_key") "nonce": "nonce", "signedData": "c2lnbmVkX2RhdGE=", // Base64("signed_data") diff --git a/chrome/test/data/extensions/api_test/networking_private/test.js b/chrome/test/data/extensions/api_test/networking_private/test.js index 0af2e86..336018d 100644 --- a/chrome/test/data/extensions/api_test/networking_private/test.js +++ b/chrome/test/data/extensions/api_test/networking_private/test.js @@ -14,6 +14,7 @@ var kGuid = 'SOME_GUID'; // Test properties for the verification API. var verificationProperties = { "certificate": "certificate", + "intermediateCertificates": ["ica1", "ica2", "ica3"], "publicKey": "cHVibGljX2tleQ==", // Base64("public_key") "nonce": "nonce", "signedData": "c2lnbmVkX2RhdGE=", // Base64("signed_data") diff --git a/extensions/browser/BUILD.gn b/extensions/browser/BUILD.gn index e5312b5..4a20a2b 100644 --- a/extensions/browser/BUILD.gn +++ b/extensions/browser/BUILD.gn @@ -536,12 +536,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 edd9a7d..4177a77b 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', + ], + }], + ], + }], ], }, { @@ -835,32 +862,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', |