diff options
author | atwilson@chromium.org <atwilson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-02 17:28:39 +0000 |
---|---|---|
committer | atwilson@chromium.org <atwilson@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-02-02 17:28:39 +0000 |
commit | 1b9a4a1237bed2d1cf28ffb5e40f7154d8542d4e (patch) | |
tree | fb62afafcdd2dd405e63fe409e491159d3094dd7 | |
parent | 5612c2ae47ca0145af9e7adc40ead427ee7196c7 (diff) | |
download | chromium_src-1b9a4a1237bed2d1cf28ffb5e40f7154d8542d4e.zip chromium_src-1b9a4a1237bed2d1cf28ffb5e40f7154d8542d4e.tar.gz chromium_src-1b9a4a1237bed2d1cf28ffb5e40f7154d8542d4e.tar.bz2 |
Added support for signed policy blobs on desktop.
PolicyFetchRequests on desktop now request signed policy blobs. Also added
plumbing for injecting a google-supplied key to verify the blob signing keys to
protect against local tampering.
BUG=275291
TBR=joaodasilva@chromium.org
Review URL: https://codereview.chromium.org/116273002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@248416 0039d316-1c4b-4281-b951-d872f2087c98
42 files changed, 777 insertions, 147 deletions
diff --git a/build/ios/grit_whitelist.txt b/build/ios/grit_whitelist.txt index ba61f04..c5dcff8 100644 --- a/build/ios/grit_whitelist.txt +++ b/build/ios/grit_whitelist.txt @@ -735,6 +735,7 @@ IDS_POLICY_STORE_STATUS_VALIDATION_ERROR IDS_POLICY_SUBKEY_ERROR IDS_POLICY_TYPE_ERROR IDS_POLICY_VALIDATION_BAD_INITIAL_SIGNATURE +IDS_POLICY_VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE IDS_POLICY_VALIDATION_BAD_SIGNATURE IDS_POLICY_VALIDATION_BAD_TIMESTAMP IDS_POLICY_VALIDATION_BAD_USERNAME diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc index c472757..aa86001 100644 --- a/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc +++ b/chrome/browser/chromeos/policy/device_cloud_policy_manager_chromeos.cc @@ -251,6 +251,7 @@ scoped_ptr<CloudPolicyClient> DeviceCloudPolicyManagerChromeOS::CreateClient() { return make_scoped_ptr( new CloudPolicyClient(GetMachineID(), GetMachineModel(), + kPolicyVerificationKeyHash, USER_AFFILIATION_NONE, device_status_provider_.get(), device_management_service_, diff --git a/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc b/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc index 16468bb..a4a2e99 100644 --- a/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc +++ b/chrome/browser/chromeos/policy/device_cloud_policy_store_chromeos.cc @@ -46,7 +46,10 @@ void DeviceCloudPolicyStoreChromeOS::Store( } scoped_ptr<DeviceCloudPolicyValidator> validator(CreateValidator(policy)); - validator->ValidateSignature(*owner_key->public_key(), true); + validator->ValidateSignature(owner_key->public_key_as_string(), + GetPolicyVerificationKey(), + std::string(), + true); validator->ValidateAgainstCurrentPolicy( device_settings_service_->policy_data(), CloudPolicyValidatorBase::TIMESTAMP_REQUIRED, @@ -74,7 +77,7 @@ void DeviceCloudPolicyStoreChromeOS::InstallInitialPolicy( } scoped_ptr<DeviceCloudPolicyValidator> validator(CreateValidator(policy)); - validator->ValidateInitialKey(); + validator->ValidateInitialKey(GetPolicyVerificationKey()); validator.release()->StartValidation( base::Bind(&DeviceCloudPolicyStoreChromeOS::OnPolicyToStoreValidated, weak_factory_.GetWeakPtr())); diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc index ed3546b..ef4b832 100644 --- a/chrome/browser/chromeos/policy/device_local_account_policy_service.cc +++ b/chrome/browser/chromeos/policy/device_local_account_policy_service.cc @@ -65,6 +65,7 @@ scoped_ptr<CloudPolicyClient> CreateClient( scoped_ptr<CloudPolicyClient> client( new CloudPolicyClient(std::string(), std::string(), + kPolicyVerificationKeyHash, USER_AFFILIATION_MANAGED, NULL, device_management_service, request_context)); client->SetupRegistration(policy_data->request_token(), diff --git a/chrome/browser/chromeos/policy/device_local_account_policy_store.cc b/chrome/browser/chromeos/policy/device_local_account_policy_store.cc index 327b11e..fa13d73 100644 --- a/chrome/browser/chromeos/policy/device_local_account_policy_store.cc +++ b/chrome/browser/chromeos/policy/device_local_account_policy_store.cc @@ -190,7 +190,10 @@ void DeviceLocalAccountPolicyStore::Validate( : CloudPolicyValidatorBase::TIMESTAMP_NOT_REQUIRED, CloudPolicyValidatorBase::DM_TOKEN_REQUIRED); validator->ValidatePayload(); - validator->ValidateSignature(*key->public_key(), false); + validator->ValidateSignature(key->public_key_as_string(), + GetPolicyVerificationKey(), + std::string(), + false); validator.release()->StartValidation(callback); } diff --git a/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc b/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc index 56b013c..3c25bac 100644 --- a/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc +++ b/chrome/browser/chromeos/policy/enrollment_handler_chromeos.cc @@ -115,7 +115,7 @@ void EnrollmentHandlerChromeOS::OnPolicyFetched(CloudPolicyClient* client) { CloudPolicyValidatorBase::DM_TOKEN_REQUIRED); validator->ValidatePolicyType(dm_protocol::kChromeDevicePolicyType); validator->ValidatePayload(); - validator->ValidateInitialKey(); + validator->ValidateInitialKey(GetPolicyVerificationKey()); validator.release()->StartValidation( base::Bind(&EnrollmentHandlerChromeOS::PolicyValidated, weak_ptr_factory_.GetWeakPtr())); diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc index b2abb55..83d5954 100644 --- a/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc +++ b/chrome/browser/chromeos/policy/user_cloud_policy_manager_chromeos.cc @@ -119,7 +119,8 @@ void UserCloudPolicyManagerChromeOS::Connect( device_management_service->GetServerUrl()))); } scoped_ptr<CloudPolicyClient> cloud_policy_client( - new CloudPolicyClient(std::string(), std::string(), user_affiliation, + new CloudPolicyClient(std::string(), std::string(), + kPolicyVerificationKeyHash, user_affiliation, NULL, device_management_service, request_context)); core()->Connect(cloud_policy_client.Pass()); diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc b/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc index 03d949b..467aec7 100644 --- a/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc +++ b/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.cc @@ -18,6 +18,7 @@ #include "chrome/browser/chromeos/policy/user_policy_token_loader.h" #include "chromeos/dbus/cryptohome_client.h" #include "chromeos/dbus/session_manager_client.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" #include "google_apis/gaia/gaia_auth_util.h" #include "policy/proto/cloud_policy.pb.h" #include "policy/proto/device_management_local.pb.h" @@ -255,7 +256,11 @@ void UserCloudPolicyStoreChromeOS::LoadImmediately() { CloudPolicyValidatorBase::TIMESTAMP_REQUIRED); validator->ValidateUsername(username_); const bool allow_rotation = false; - validator->ValidateSignature(policy_key_, allow_rotation); + validator->ValidateSignature( + policy_key_, + GetPolicyVerificationKey(), + std::string(), // No signature verification needed. + allow_rotation); validator->RunValidation(); OnRetrievedPolicyValidated(validator.get()); } @@ -268,10 +273,13 @@ void UserCloudPolicyStoreChromeOS::ValidatePolicyForStore( CloudPolicyValidatorBase::TIMESTAMP_REQUIRED); validator->ValidateUsername(username_); if (policy_key_.empty()) { - validator->ValidateInitialKey(); + validator->ValidateInitialKey(GetPolicyVerificationKey()); } else { const bool allow_rotation = true; - validator->ValidateSignature(policy_key_, allow_rotation); + validator->ValidateSignature(policy_key_, + GetPolicyVerificationKey(), + std::string(), + allow_rotation); } // Start validation. The Validator will delete itself once validation is @@ -288,7 +296,7 @@ void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated( UMA_HISTOGRAM_ENUMERATION( "Enterprise.UserPolicyValidationStoreStatus", validation_status_, - UserCloudPolicyValidator::VALIDATION_POLICY_PARSE_ERROR + 1); + UserCloudPolicyValidator::VALIDATION_STATUS_SIZE); if (!validator->success()) { status_ = STATUS_VALIDATION_ERROR; @@ -367,7 +375,10 @@ void UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy( CloudPolicyValidatorBase::TIMESTAMP_REQUIRED); validator->ValidateUsername(username_); const bool allow_rotation = false; - validator->ValidateSignature(policy_key_, allow_rotation); + validator->ValidateSignature(policy_key_, + GetPolicyVerificationKey(), + std::string(), + allow_rotation); // Start validation. The Validator will delete itself once validation is // complete. validator.release()->StartValidation( @@ -382,7 +393,7 @@ void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated( UMA_HISTOGRAM_ENUMERATION( "Enterprise.UserPolicyValidationLoadStatus", validation_status_, - UserCloudPolicyValidator::VALIDATION_POLICY_PARSE_ERROR + 1); + UserCloudPolicyValidator::VALIDATION_STATUS_SIZE); if (!validator->success()) { status_ = STATUS_VALIDATION_ERROR; @@ -475,7 +486,7 @@ void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir( void UserCloudPolicyStoreChromeOS::ReloadPolicyKey( const base::Closure& callback) { - std::vector<uint8>* key = new std::vector<uint8>(); + std::string* key = new std::string(); background_task_runner()->PostTaskAndReply( FROM_HERE, base::Bind(&UserCloudPolicyStoreChromeOS::LoadPolicyKey, @@ -489,7 +500,7 @@ void UserCloudPolicyStoreChromeOS::ReloadPolicyKey( // static void UserCloudPolicyStoreChromeOS::LoadPolicyKey(const base::FilePath& path, - std::vector<uint8>* key) { + std::string* key) { if (!base::PathExists(path)) { // There is no policy key the first time that a user fetches policy. If // |path| does not exist then that is the most likely scenario, so there's @@ -499,17 +510,18 @@ void UserCloudPolicyStoreChromeOS::LoadPolicyKey(const base::FilePath& path, } int64 size; + key->clear(); if (!base::GetFileSize(path, &size)) { LOG(ERROR) << "Could not get size of " << path.value(); } else if (size == 0 || size > kKeySizeLimit) { LOG(ERROR) << "Key at " << path.value() << " has bad size " << size; } else { - key->resize(size); - int read_size = base::ReadFile( - path, reinterpret_cast<char*>(vector_as_array(key)), size); + char buf[size]; + int read_size = base::ReadFile(path, buf, size); if (read_size != size) { LOG(ERROR) << "Failed to read key at " << path.value(); - key->clear(); + } else { + key->append(buf, size); } } @@ -518,9 +530,9 @@ void UserCloudPolicyStoreChromeOS::LoadPolicyKey(const base::FilePath& path, } void UserCloudPolicyStoreChromeOS::OnPolicyKeyReloaded( - std::vector<uint8>* key, + std::string* key, const base::Closure& callback) { - policy_key_.swap(*key); + policy_key_ = *key; policy_key_loaded_ = true; callback.Run(); } diff --git a/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.h b/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.h index 1e78869..36542cd 100644 --- a/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.h +++ b/chrome/browser/chromeos/policy/user_cloud_policy_store_chromeos.h @@ -105,10 +105,10 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase { // Reads the contents of |path| into |key|. static void LoadPolicyKey(const base::FilePath& path, - std::vector<uint8>* key); + std::string* key); // Callback for the key reloading. - void OnPolicyKeyReloaded(std::vector<uint8>* key, + void OnPolicyKeyReloaded(std::string* key, const base::Closure& callback); // Invokes |callback| after creating |policy_key_|, if it hasn't been created @@ -135,7 +135,7 @@ class UserCloudPolicyStoreChromeOS : public UserCloudPolicyStoreBase { bool policy_key_loaded_; base::FilePath policy_key_path_; - std::vector<uint8> policy_key_; + std::string policy_key_; DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStoreChromeOS); }; diff --git a/chrome/browser/chromeos/settings/device_settings_service.h b/chrome/browser/chromeos/settings/device_settings_service.h index 051d831..7e77c3e 100644 --- a/chrome/browser/chromeos/settings/device_settings_service.h +++ b/chrome/browser/chromeos/settings/device_settings_service.h @@ -15,6 +15,7 @@ #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/observer_list.h" +#include "base/stl_util.h" #include "chromeos/dbus/session_manager_client.h" #include "chromeos/tpm_token_loader.h" #include "components/policy/core/common/cloud/cloud_policy_validator.h" @@ -46,6 +47,12 @@ class OwnerKey : public base::RefCountedThreadSafe<OwnerKey> { const std::vector<uint8>* public_key() { return public_key_.get(); } + + std::string public_key_as_string() { + return std::string(reinterpret_cast<const char*>( + vector_as_array(public_key_.get())), public_key_->size()); + } + crypto::RSAPrivateKey* private_key() { return private_key_.get(); } diff --git a/chrome/browser/chromeos/settings/session_manager_operation.cc b/chrome/browser/chromeos/settings/session_manager_operation.cc index e075a33..d7f994c 100644 --- a/chrome/browser/chromeos/settings/session_manager_operation.cc +++ b/chrome/browser/chromeos/settings/session_manager_operation.cc @@ -182,7 +182,10 @@ void SessionManagerOperation::ValidateDeviceSettings( policy::CloudPolicyValidatorBase::DM_TOKEN_NOT_REQUIRED); validator->ValidatePolicyType(policy::dm_protocol::kChromeDevicePolicyType); validator->ValidatePayload(); - validator->ValidateSignature(*owner_key_->public_key(), false); + validator->ValidateSignature(owner_key_->public_key_as_string(), + policy::GetPolicyVerificationKey(), + std::string(), + false); validator->StartValidation( base::Bind(&SessionManagerOperation::ReportValidatorStatus, weak_factory_.GetWeakPtr())); diff --git a/chrome/browser/chromeos/settings/session_manager_operation_unittest.cc b/chrome/browser/chromeos/settings/session_manager_operation_unittest.cc index 980399c..40a434b 100644 --- a/chrome/browser/chromeos/settings/session_manager_operation_unittest.cc +++ b/chrome/browser/chromeos/settings/session_manager_operation_unittest.cc @@ -263,7 +263,16 @@ TEST_F(SessionManagerOperationTest, SignAndStoreSettings) { validator->ValidatePayload(); std::vector<uint8> public_key; policy_.GetSigningKey()->ExportPublicKey(&public_key); - validator->ValidateSignature(public_key, false); + // Convert from bytes to string format (which is what ValidateSignature() + // takes). + std::string public_key_as_string = std::string( + reinterpret_cast<const char*>(vector_as_array(&public_key)), + public_key.size()); + validator->ValidateSignature( + public_key_as_string, + policy::GetPolicyVerificationKey(), + policy::PolicyBuilder::GetTestSigningKeySignature(), + false); validator->StartValidation( base::Bind(&SessionManagerOperationTest::CheckSuccessfulValidation, base::Unretained(this))); diff --git a/chrome/browser/policy/cloud/policy_header_service_factory.cc b/chrome/browser/policy/cloud/policy_header_service_factory.cc index 7395ae5..c7ce7fa 100644 --- a/chrome/browser/policy/cloud/policy_header_service_factory.cc +++ b/chrome/browser/policy/cloud/policy_header_service_factory.cc @@ -104,8 +104,10 @@ PolicyHeaderServiceFactory::BuildServiceInstanceFor( #endif scoped_ptr<PolicyHeaderService> service = make_scoped_ptr( - new PolicyHeaderService( - device_management_service->GetServerUrl(), user_store, device_store)); + new PolicyHeaderService(device_management_service->GetServerUrl(), + kPolicyVerificationKeyHash, + user_store, + device_store)); return new PolicyHeaderServiceWrapper(service.Pass()); } diff --git a/chrome/browser/policy/cloud/user_cloud_policy_manager_factory.cc b/chrome/browser/policy/cloud/user_cloud_policy_manager_factory.cc index 2c56cc4..517e339 100644 --- a/chrome/browser/policy/cloud/user_cloud_policy_manager_factory.cc +++ b/chrome/browser/policy/cloud/user_cloud_policy_manager_factory.cc @@ -13,6 +13,7 @@ #include "components/browser_context_keyed_service/browser_context_dependency_manager.h" #include "components/browser_context_keyed_service/browser_context_keyed_service.h" #include "components/policy/core/common/cloud/cloud_external_data_manager.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" #include "components/policy/core/common/cloud/user_cloud_policy_manager.h" #include "components/policy/core/common/cloud/user_cloud_policy_store.h" #include "content/public/browser/browser_context.h" @@ -143,7 +144,9 @@ UserCloudPolicyManagerFactory::CreateManagerForOriginalBrowserContext( DCHECK(!testing_factory_); scoped_ptr<UserCloudPolicyStore> store( - UserCloudPolicyStore::Create(context->GetPath(), background_task_runner)); + UserCloudPolicyStore::Create(context->GetPath(), + GetPolicyVerificationKey(), + background_task_runner)); if (force_immediate_load) store->LoadImmediately(); diff --git a/components/policy.gypi b/components/policy.gypi index b70e547..ce934ac 100644 --- a/components/policy.gypi +++ b/components/policy.gypi @@ -199,6 +199,7 @@ 'policy/proto/chrome_extension_policy.proto', 'policy/proto/device_management_backend.proto', 'policy/proto/device_management_local.proto', + 'policy/proto/policy_signing_key.proto', ], 'variables': { 'proto_in_dir': 'policy/proto', diff --git a/components/policy/core/DEPS b/components/policy/core/DEPS index 7b46c50..678a142 100644 --- a/components/policy/core/DEPS +++ b/components/policy/core/DEPS @@ -8,6 +8,7 @@ include_rules = [ # components/policy/resources/policy_templates.json. "+policy/policy_constants.h", "+policy/proto/chrome_extension_policy.pb.h", + "+policy/proto/policy_signing_key.pb.h", "+policy/proto/cloud_policy.pb.h", "+policy/proto/device_management_backend.pb.h", ] diff --git a/components/policy/core/browser/cloud/message_util.cc b/components/policy/core/browser/cloud/message_util.cc index ea5aa03..f394762 100644 --- a/components/policy/core/browser/cloud/message_util.cc +++ b/components/policy/core/browser/cloud/message_util.cc @@ -73,6 +73,10 @@ int GetIDSForValidationStatus(CloudPolicyValidatorBase::Status status) { return IDS_POLICY_VALIDATION_BAD_USERNAME; case CloudPolicyValidatorBase::VALIDATION_POLICY_PARSE_ERROR: return IDS_POLICY_VALIDATION_POLICY_PARSE_ERROR; + case CloudPolicyValidatorBase::VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE: + return IDS_POLICY_VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE; + case CloudPolicyValidatorBase::VALIDATION_STATUS_SIZE: + NOTREACHED(); } NOTREACHED() << "Unhandled validation status " << status; return IDS_POLICY_VALIDATION_UNKNOWN_ERROR; diff --git a/components/policy/core/common/cloud/cloud_policy_client.cc b/components/policy/core/common/cloud/cloud_policy_client.cc index efd95f2..c090d50 100644 --- a/components/policy/core/common/cloud/cloud_policy_client.cc +++ b/components/policy/core/common/cloud/cloud_policy_client.cc @@ -50,12 +50,14 @@ CloudPolicyClient::StatusProvider::~StatusProvider() {} CloudPolicyClient::CloudPolicyClient( const std::string& machine_id, const std::string& machine_model, + const std::string& verification_key_hash, UserAffiliation user_affiliation, StatusProvider* status_provider, DeviceManagementService* service, scoped_refptr<net::URLRequestContextGetter> request_context) : machine_id_(machine_id), machine_model_(machine_model), + verification_key_hash_(verification_key_hash), user_affiliation_(user_affiliation), device_mode_(DEVICE_MODE_NOT_SET), submit_machine_id_(false), @@ -161,16 +163,14 @@ void CloudPolicyClient::FetchPolicy() { if (!it->second.empty()) fetch_request->set_settings_entity_id(it->second); -#if defined(OS_CHROMEOS) - // All policy types on ChromeOS ask for a signed policy blob. + // Request signed policy blobs to help prevent tampering on the client. fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA); -#else - // Don't request signed blobs for desktop policy. - fetch_request->set_signature_type(em::PolicyFetchRequest::NONE); -#endif if (public_key_version_valid_) fetch_request->set_public_key_version(public_key_version_); + if (!verification_key_hash_.empty()) + fetch_request->set_verification_key_hash(verification_key_hash_); + // These fields are included only in requests for chrome policy. if (IsChromePolicy(it->first)) { if (submit_machine_id_ && !machine_id_.empty()) diff --git a/components/policy/core/common/cloud/cloud_policy_client.h b/components/policy/core/common/cloud/cloud_policy_client.h index c768165..fdd6a6d 100644 --- a/components/policy/core/common/cloud/cloud_policy_client.h +++ b/components/policy/core/common/cloud/cloud_policy_client.h @@ -87,9 +87,12 @@ class POLICY_EXPORT CloudPolicyClient { // |provider| and |service| are weak pointers and it's the caller's // responsibility to keep them valid for the lifetime of CloudPolicyClient. + // |verification_key_hash| contains an identifier telling the DMServer which + // verification key to use. CloudPolicyClient( const std::string& machine_id, const std::string& machine_model, + const std::string& verification_key_hash, UserAffiliation user_affiliation, StatusProvider* provider, DeviceManagementService* service, @@ -255,6 +258,7 @@ class POLICY_EXPORT CloudPolicyClient { // Data necessary for constructing policy requests. const std::string machine_id_; const std::string machine_model_; + const std::string verification_key_hash_; const UserAffiliation user_affiliation_; NamespaceSet namespaces_to_fetch_; diff --git a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc index 64666ee..8502c38 100644 --- a/components/policy/core/common/cloud/cloud_policy_client_unittest.cc +++ b/components/policy/core/common/cloud/cloud_policy_client_unittest.cc @@ -83,11 +83,8 @@ class CloudPolicyClientTest : public testing::Test { em::PolicyFetchRequest* policy_fetch_request = policy_request_.mutable_policy_request()->add_request(); policy_fetch_request->set_policy_type(dm_protocol::kChromeUserPolicyType); -#if defined(OS_CHROMEOS) policy_fetch_request->set_signature_type(em::PolicyFetchRequest::SHA1_RSA); -#else - policy_fetch_request->set_signature_type(em::PolicyFetchRequest::NONE); -#endif + policy_fetch_request->set_verification_key_hash(kPolicyVerificationKeyHash); policy_response_.mutable_policy_response()->add_response()->set_policy_data( CreatePolicyData("fake-policy-data")); @@ -122,6 +119,7 @@ class CloudPolicyClientTest : public testing::Test { request_context_ = new net::TestURLRequestContextGetter( loop_.message_loop_proxy()); client_.reset(new CloudPolicyClient(kMachineID, kMachineModel, + kPolicyVerificationKeyHash, user_affiliation, &status_provider_, &service_, request_context_)); diff --git a/components/policy/core/common/cloud/cloud_policy_constants.cc b/components/policy/core/common/cloud/cloud_policy_constants.cc index c88f1fa..56b9411 100644 --- a/components/policy/core/common/cloud/cloud_policy_constants.cc +++ b/components/policy/core/common/cloud/cloud_policy_constants.cc @@ -4,6 +4,7 @@ #include "components/policy/core/common/cloud/cloud_policy_constants.h" +#include "base/basictypes.h" #include "base/command_line.h" #include "components/policy/core/common/policy_switches.h" @@ -49,6 +50,56 @@ const char kChromeExtensionPolicyType[] = "google/chrome/extension"; } // namespace dm_protocol +#if !defined(OS_CHROMEOS) +const uint8 kPolicyVerificationKey[] = { + 0x30, 0x82, 0x01, 0x22, 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, + 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0F, 0x00, 0x30, 0x82, + 0x01, 0x0A, 0x02, 0x82, 0x01, 0x01, 0x00, 0xA7, 0xB3, 0xF9, 0x0D, 0xC7, 0xC7, + 0x8D, 0x84, 0x3D, 0x4B, 0x80, 0xDD, 0x9A, 0x2F, 0xF8, 0x69, 0xD4, 0xD1, 0x14, + 0x5A, 0xCA, 0x04, 0x4B, 0x1C, 0xBC, 0x28, 0xEB, 0x5E, 0x10, 0x01, 0x36, 0xFD, + 0x81, 0xEB, 0xE4, 0x3C, 0x16, 0x40, 0xA5, 0x8A, 0xE6, 0x08, 0xEE, 0xEF, 0x39, + 0x1F, 0x6B, 0x10, 0x29, 0x50, 0x84, 0xCE, 0xEE, 0x33, 0x5C, 0x48, 0x4A, 0x33, + 0xB0, 0xC8, 0x8A, 0x66, 0x0D, 0x10, 0x11, 0x9D, 0x6B, 0x55, 0x4C, 0x9A, 0x62, + 0x40, 0x9A, 0xE2, 0xCA, 0x21, 0x01, 0x1F, 0x10, 0x1E, 0x7B, 0xC6, 0x89, 0x94, + 0xDA, 0x39, 0x69, 0xBE, 0x27, 0x28, 0x50, 0x5E, 0xA2, 0x55, 0xB9, 0x12, 0x3C, + 0x79, 0x6E, 0xDF, 0x24, 0xBF, 0x34, 0x88, 0xF2, 0x5E, 0xD0, 0xC4, 0x06, 0xEE, + 0x95, 0x6D, 0xC2, 0x14, 0xBF, 0x51, 0x7E, 0x3F, 0x55, 0x10, 0x85, 0xCE, 0x33, + 0x8F, 0x02, 0x87, 0xFC, 0xD2, 0xDD, 0x42, 0xAF, 0x59, 0xBB, 0x69, 0x3D, 0xBC, + 0x77, 0x4B, 0x3F, 0xC7, 0x22, 0x0D, 0x5F, 0x72, 0xC7, 0x36, 0xB6, 0x98, 0x3D, + 0x03, 0xCD, 0x2F, 0x68, 0x61, 0xEE, 0xF4, 0x5A, 0xF5, 0x07, 0xAE, 0xAE, 0x79, + 0xD1, 0x1A, 0xB2, 0x38, 0xE0, 0xAB, 0x60, 0x5C, 0x0C, 0x14, 0xFE, 0x44, 0x67, + 0x2C, 0x8A, 0x08, 0x51, 0x9C, 0xCD, 0x3D, 0xDB, 0x13, 0x04, 0x57, 0xC5, 0x85, + 0xB6, 0x2A, 0x0F, 0x02, 0x46, 0x0D, 0x2D, 0xCA, 0xE3, 0x3F, 0x84, 0x9E, 0x8B, + 0x8A, 0x5F, 0xFC, 0x4D, 0xAA, 0xBE, 0xBD, 0xE6, 0x64, 0x9F, 0x26, 0x9A, 0x2B, + 0x97, 0x69, 0xA9, 0xBA, 0x0B, 0xBD, 0x48, 0xE4, 0x81, 0x6B, 0xD4, 0x4B, 0x78, + 0xE6, 0xAF, 0x95, 0x66, 0xC1, 0x23, 0xDA, 0x23, 0x45, 0x36, 0x6E, 0x25, 0xF3, + 0xC7, 0xC0, 0x61, 0xFC, 0xEC, 0x66, 0x9D, 0x31, 0xD4, 0xD6, 0xB6, 0x36, 0xE3, + 0x7F, 0x81, 0x87, 0x02, 0x03, 0x01, 0x00, 0x01 +}; +#endif + +const char kPolicyVerificationKeyHash[] = "1:356l7w"; + +std::string GetPolicyVerificationKey() { +#if defined(OS_CHROMEOS) + // TODO(atwilson): Enable this for ChromeOS. We don't verify the extra + // verification signatures on ChromeOS to simplify testing, and because + // ChromeOS doesn't need the extra verification because it already stores + // the policy blob securely. + return std::string(); +#else + // Allow disabling key verification until we update the test servers with + // verification signatures for all QA domains (http://crbug.com/328038). + CommandLine* command_line = CommandLine::ForCurrentProcess(); + if (command_line->HasSwitch(switches::kSkipPolicyKeyVerification)) { + return std::string(); + } else { + return std::string(reinterpret_cast<const char*>(kPolicyVerificationKey), + sizeof(kPolicyVerificationKey)); + } +#endif +} + const char* GetChromeUserPolicyType() { #if defined(OS_ANDROID) || defined(OS_IOS) CommandLine* command_line = CommandLine::ForCurrentProcess(); diff --git a/components/policy/core/common/cloud/cloud_policy_constants.h b/components/policy/core/common/cloud/cloud_policy_constants.h index c0a7e1b..c841d76 100644 --- a/components/policy/core/common/cloud/cloud_policy_constants.h +++ b/components/policy/core/common/cloud/cloud_policy_constants.h @@ -51,6 +51,11 @@ enum PolicyFetchStatus { } // namespace dm_protocol +// Information about the verification key used to verify that policy signing +// keys are valid. +POLICY_EXPORT std::string GetPolicyVerificationKey(); +POLICY_EXPORT extern const char kPolicyVerificationKeyHash[]; + // Describes the affiliation of a user w.r.t. the device owner. enum UserAffiliation { // User is on the same domain the device was registered with. diff --git a/components/policy/core/common/cloud/cloud_policy_validator.cc b/components/policy/core/common/cloud/cloud_policy_validator.cc index 5bbc6bc..f85561d 100644 --- a/components/policy/core/common/cloud/cloud_policy_validator.cc +++ b/components/policy/core/common/cloud/cloud_policy_validator.cc @@ -6,6 +6,7 @@ #include "base/bind_helpers.h" #include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" #include "base/sequenced_task_runner.h" #include "base/stl_util.h" #include "components/policy/core/common/cloud/cloud_policy_constants.h" @@ -28,6 +29,23 @@ const uint8 kSignatureAlgorithm[] = { 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00 }; +const char kMetricPolicyKeyVerification[] = "Enterprise.PolicyKeyVerification"; + +enum MetricPolicyKeyVerification { + // UMA metric recorded when the client has no verification key. + METRIC_POLICY_KEY_VERIFICATION_KEY_MISSING, + // Recorded when the policy being verified has no key signature (e.g. policy + // fetched before the server supported the verification key). + METRIC_POLICY_KEY_VERIFICATION_SIGNATURE_MISSING, + // Recorded when the key signature did not match the expected value (in + // theory, this should only happen after key rotation or if the policy cached + // on disk has been modified). + METRIC_POLICY_KEY_VERIFICATION_FAILED, + // Recorded when key verification succeeded. + METRIC_POLICY_KEY_VERIFICATION_SUCCEEDED, + METRIC_POLICY_KEY_VERIFICATION_SIZE // Must be the last. +}; + } // namespace CloudPolicyValidatorBase::~CloudPolicyValidatorBase() {} @@ -83,16 +101,22 @@ void CloudPolicyValidatorBase::ValidatePayload() { validation_flags_ |= VALIDATE_PAYLOAD; } -void CloudPolicyValidatorBase::ValidateSignature(const std::vector<uint8>& key, - bool allow_key_rotation) { +void CloudPolicyValidatorBase::ValidateSignature( + const std::string& key, + const std::string& verification_key, + const std::string& key_signature, + bool allow_key_rotation) { validation_flags_ |= VALIDATE_SIGNATURE; - key_ = std::string(reinterpret_cast<const char*>(vector_as_array(&key)), - key.size()); + set_verification_key(verification_key); + key_ = key; + key_signature_ = key_signature; allow_key_rotation_ = allow_key_rotation; } -void CloudPolicyValidatorBase::ValidateInitialKey() { +void CloudPolicyValidatorBase::ValidateInitialKey( + const std::string& verification_key) { validation_flags_ |= VALIDATE_INITIAL_KEY; + set_verification_key(verification_key); } void CloudPolicyValidatorBase::ValidateAgainstCurrentPolicy( @@ -210,6 +234,65 @@ void CloudPolicyValidatorBase::RunChecks() { } } +// Verifies the |new_public_key_verification_signature| for the |new_public_key| +// in the policy blob. +bool CloudPolicyValidatorBase::CheckNewPublicKeyVerificationSignature() { + // If there's no local verification key, then just return true (no + // validation possible). + if (verification_key_.empty()) { + UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification, + METRIC_POLICY_KEY_VERIFICATION_KEY_MISSING, + METRIC_POLICY_KEY_VERIFICATION_SIZE); + return true; + } + + if (!policy_->has_new_public_key_verification_signature()) { + // Policy does not contain a verification signature, so log an error. + LOG(ERROR) << "Policy is missing public_key_verification_signature"; + UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification, + METRIC_POLICY_KEY_VERIFICATION_SIGNATURE_MISSING, + METRIC_POLICY_KEY_VERIFICATION_SIZE); + // TODO(atwilson): Return an error on failed signature verification once + // our test servers and unittests are returning policy with a verification + // signature (http://crbug.com/275291). + return true; + } + + if (!CheckVerificationKeySignature( + policy_->new_public_key(), + verification_key_, + policy_->new_public_key_verification_signature())) { + LOG(ERROR) << "Signature verification failed"; + UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification, + METRIC_POLICY_KEY_VERIFICATION_FAILED, + METRIC_POLICY_KEY_VERIFICATION_SIZE); + // TODO(atwilson): Update this code to include the domain name in the + // signature, and return an error once the server starts returning this. + return true; + } + // Signature verification succeeded - return success to the caller. + UMA_HISTOGRAM_ENUMERATION(kMetricPolicyKeyVerification, + METRIC_POLICY_KEY_VERIFICATION_SUCCEEDED, + METRIC_POLICY_KEY_VERIFICATION_SIZE); + return true; +} + +bool CloudPolicyValidatorBase::CheckVerificationKeySignature( + const std::string& key, + const std::string& verification_key, + const std::string& signature) { + // TODO(atwilson): Update this routine to include the domain name in the + // signed data. + return VerifySignature(key, verification_key, signature); +} + +void CloudPolicyValidatorBase::set_verification_key( + const std::string& verification_key) { + // Make sure we aren't overwriting the verification key with a different key. + DCHECK(verification_key_.empty() || verification_key_ == verification_key); + verification_key_ = verification_key; +} + CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckSignature() { const std::string* signature_key = &key_; if (policy_->has_new_public_key() && allow_key_rotation_) { @@ -217,9 +300,14 @@ CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckSignature() { if (!policy_->has_new_public_key_signature() || !VerifySignature(policy_->new_public_key(), key_, policy_->new_public_key_signature())) { - LOG(ERROR) << "New public key signature verification failed"; + LOG(ERROR) << "New public key rotation signature verification failed"; return VALIDATION_BAD_SIGNATURE; } + + if (!CheckNewPublicKeyVerificationSignature()) { + LOG(ERROR) << "New public key root verification failed"; + return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE; + } } if (!policy_->has_policy_data_signature() || @@ -229,6 +317,16 @@ CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckSignature() { return VALIDATION_BAD_SIGNATURE; } + // If a key verification signature is available, then verify the base signing + // key as well. + if (!key_signature_.empty() && + !CheckVerificationKeySignature(key_, verification_key_, key_signature_)) { + LOG(ERROR) << "Verification key signature verification failed"; + // TODO(atwilson): Update to return an error once the server is properly + // generating a verification signature (http://crbug.com/275291). + // return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE; + } + return VALIDATION_OK; } @@ -241,6 +339,10 @@ CloudPolicyValidatorBase::Status CloudPolicyValidatorBase::CheckInitialKey() { return VALIDATION_BAD_INITIAL_SIGNATURE; } + if (!CheckNewPublicKeyVerificationSignature()) { + LOG(ERROR) << "Initial policy root signature validation failed"; + return VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE; + } return VALIDATION_OK; } diff --git a/components/policy/core/common/cloud/cloud_policy_validator.h b/components/policy/core/common/cloud/cloud_policy_validator.h index 3d2f2aa..541fa78 100644 --- a/components/policy/core/common/cloud/cloud_policy_validator.h +++ b/components/policy/core/common/cloud/cloud_policy_validator.h @@ -46,9 +46,9 @@ namespace policy { // RunValidation() can be used to perform validation on the current thread. class POLICY_EXPORT CloudPolicyValidatorBase { public: - // Validation result codes. These values are also used for UMA histograms; - // they must stay stable, and the UMA counters must be updated if new elements - // are appended at the end. + // Validation result codes. These values are also used for UMA histograms by + // UserCloudPolicyStoreChromeOS and must stay stable - new elements should + // be added at the end before VALIDATION_STATUS_SIZE. enum Status { // Indicates successful validation. VALIDATION_OK, @@ -72,6 +72,10 @@ class POLICY_EXPORT CloudPolicyValidatorBase { VALIDATION_BAD_USERNAME, // Policy payload protobuf parse error. VALIDATION_POLICY_PARSE_ERROR, + // Policy key signature could not be verified using the hard-coded + // verification key. + VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE, + VALIDATION_STATUS_SIZE // MUST BE LAST }; enum ValidateDMTokenOption { @@ -143,19 +147,25 @@ class POLICY_EXPORT CloudPolicyValidatorBase { // Validates that the payload can be decoded successfully. void ValidatePayload(); - // Verifies that the signature on the policy blob verifies against |key|. If | + // Verifies that the signature on the policy blob verifies against |key|. If // |allow_key_rotation| is true and there is a key rotation present in the // policy blob, this checks the signature on the new key against |key| and the - // policy blob against the new key. - void ValidateSignature(const std::vector<uint8>& key, + // policy blob against the new key. New key is also validated using the passed + // |verification_key| and the |new_public_key_verification_signature| field. + // If |key_signature| is non-empty, then |key| is also verified against that + // signature (useful when dealing with cached keys from untrusted sources). + void ValidateSignature(const std::string& key, + const std::string& verification_key, + const std::string& key_signature, bool allow_key_rotation); - // Similar to StartSignatureVerification(), this checks the signature on the + // Similar to ValidateSignature(), this checks the signature on the // policy blob. However, this variant expects a new policy key set in the // policy blob and makes sure the policy is signed using that key. This should // be called at setup time when there is no existing policy key present to - // check against. - void ValidateInitialKey(); + // check against. New key is validated using the passed |verification_key| and + // the new_public_key_verification_signature field. + void ValidateInitialKey(const std::string& verification_key); // Convenience helper that configures timestamp and token validation based on // the current policy blob. |policy_data| may be NULL, in which case the @@ -195,6 +205,7 @@ class POLICY_EXPORT CloudPolicyValidatorBase { VALIDATE_PAYLOAD = 1 << 6, VALIDATE_SIGNATURE = 1 << 7, VALIDATE_INITIAL_KEY = 1 << 8, + VALIDATE_SIGNED_KEY = 1 << 9, }; // Performs validation, called on a background thread. @@ -210,6 +221,21 @@ class POLICY_EXPORT CloudPolicyValidatorBase { // Invokes all the checks and reports the result. void RunChecks(); + // Helper routine that verifies that the new public key in the policy blob + // is properly signed by the |verification_key_|. + bool CheckNewPublicKeyVerificationSignature(); + + // Helper routine that performs a verification-key-based signature check, + // which includes the domain name associated with this policy. Returns true + // if the verification succeeds, or if |signature| is empty. + bool CheckVerificationKeySignature(const std::string& key_to_verify, + const std::string& server_key, + const std::string& signature); + + // Sets the key used to verify new public keys, and ensures that callers + // don't try to set conflicting keys. + void set_verification_key(const std::string& verification_key); + // Helper functions implementing individual checks. Status CheckTimestamp(); Status CheckUsername(); @@ -242,6 +268,8 @@ class POLICY_EXPORT CloudPolicyValidatorBase { std::string policy_type_; std::string settings_entity_id_; std::string key_; + std::string key_signature_; + std::string verification_key_; bool allow_key_rotation_; scoped_refptr<base::SequencedTaskRunner> background_task_runner_; diff --git a/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc b/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc index c0e165e..18dc7be 100644 --- a/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc +++ b/components/policy/core/common/cloud/cloud_policy_validator_unittest.cc @@ -8,6 +8,7 @@ #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy.h" +#include "base/stl_util.h" #include "base/strings/string_util.h" #include "components/policy/core/common/cloud/cloud_policy_constants.h" #include "components/policy/core/common/cloud/cloud_policy_validator.h" @@ -57,11 +58,18 @@ class CloudPolicyValidatorTest : public testing::Test { } scoped_ptr<UserCloudPolicyValidator> CreateValidator() { - std::vector<uint8> public_key; + std::vector<uint8> public_key_bytes; EXPECT_TRUE( - PolicyBuilder::CreateTestSigningKey()->ExportPublicKey(&public_key)); + PolicyBuilder::CreateTestSigningKey()->ExportPublicKey( + &public_key_bytes)); policy_.Build(); + // Convert from bytes to string format (which is what ValidateSignature() + // takes). + std::string public_key = std::string( + reinterpret_cast<const char*>(vector_as_array(&public_key_bytes)), + public_key_bytes.size()); + UserCloudPolicyValidator* validator = UserCloudPolicyValidator::Create( policy_.GetCopy(), base::MessageLoopProxy::current()); validator->ValidateTimestamp(timestamp_, timestamp_, @@ -71,9 +79,12 @@ class CloudPolicyValidatorTest : public testing::Test { validator->ValidateDMToken(existing_dm_token_, ignore_missing_dm_token_); validator->ValidatePolicyType(dm_protocol::kChromeUserPolicyType); validator->ValidatePayload(); - validator->ValidateSignature(public_key, allow_key_rotation_); + validator->ValidateSignature(public_key, + GetPolicyVerificationKey(), + PolicyBuilder::GetTestSigningKeySignature(), + allow_key_rotation_); if (allow_key_rotation_) - validator->ValidateInitialKey(); + validator->ValidateInitialKey(GetPolicyVerificationKey()); return make_scoped_ptr(validator); } diff --git a/components/policy/core/common/cloud/mock_cloud_policy_client.cc b/components/policy/core/common/cloud/mock_cloud_policy_client.cc index bbaec1f..e36bebc 100644 --- a/components/policy/core/common/cloud/mock_cloud_policy_client.cc +++ b/components/policy/core/common/cloud/mock_cloud_policy_client.cc @@ -13,6 +13,7 @@ namespace policy { MockCloudPolicyClient::MockCloudPolicyClient() : CloudPolicyClient(std::string(), std::string(), + std::string(), USER_AFFILIATION_NONE, NULL, NULL, diff --git a/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc b/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc index 74166ca..a65a206 100644 --- a/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc +++ b/components/policy/core/common/cloud/mock_user_cloud_policy_store.cc @@ -8,6 +8,8 @@ namespace policy { MockUserCloudPolicyStore::MockUserCloudPolicyStore() : UserCloudPolicyStore(base::FilePath(), + base::FilePath(), + std::string(), scoped_refptr<base::SequencedTaskRunner>()) {} MockUserCloudPolicyStore::~MockUserCloudPolicyStore() {} diff --git a/components/policy/core/common/cloud/policy_builder.cc b/components/policy/core/common/cloud/policy_builder.cc index 3d00067..09c09fe 100644 --- a/components/policy/core/common/cloud/policy_builder.cc +++ b/components/policy/core/common/cloud/policy_builder.cc @@ -144,6 +144,13 @@ void PolicyBuilder::SetDefaultNewSigningKey() { raw_new_signing_key_.swap(key); } +void PolicyBuilder::SetDefaultInitialSigningKey() { + std::vector<uint8> key(kSigningKey, + kSigningKey + arraysize(kSigningKey)); + raw_new_signing_key_.swap(key); + UnsetSigningKey(); +} + void PolicyBuilder::UnsetNewSigningKey() { raw_new_signing_key_.clear(); } @@ -169,6 +176,10 @@ void PolicyBuilder::Build() { policy_.mutable_new_public_key_signature()); } } else { + // No new signing key, so clear the old public key (this allows us to + // reuse the same PolicyBuilder to build multiple policy blobs). + policy_.clear_new_public_key(); + policy_.clear_new_public_key_signature(); policy_signing_key = GetSigningKey(); } @@ -205,6 +216,18 @@ scoped_ptr<crypto::RSAPrivateKey> PolicyBuilder::CreateTestOtherSigningKey() { crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(raw_new_signing_key)); } +// static +std::string PolicyBuilder::GetTestSigningKeySignature() { + // TODO(atwilson): Return a real verification signature when one is available. + return std::string(); +} + +// static +std::string PolicyBuilder::GetTestOtherSigningKeySignature() { + // TODO(atwilson): Return a real verification signature when one is available. + return std::string(); +} + void PolicyBuilder::SignData(const std::string& data, crypto::RSAPrivateKey* key, std::string* signature) { diff --git a/components/policy/core/common/cloud/policy_builder.h b/components/policy/core/common/cloud/policy_builder.h index 2298b3a..20114c6 100644 --- a/components/policy/core/common/cloud/policy_builder.h +++ b/components/policy/core/common/cloud/policy_builder.h @@ -61,6 +61,11 @@ class PolicyBuilder { void SetDefaultSigningKey(); void UnsetSigningKey(); + // Sets the default initial signing key - the resulting policy will be signed + // by the default signing key, and will have that key set as the + // new_public_key field, as if it were an initial key provision. + void SetDefaultInitialSigningKey(); + scoped_ptr<crypto::RSAPrivateKey> GetNewSigningKey(); void SetDefaultNewSigningKey(); void UnsetNewSigningKey(); @@ -79,6 +84,11 @@ class PolicyBuilder { static scoped_ptr<crypto::RSAPrivateKey> CreateTestSigningKey(); static scoped_ptr<crypto::RSAPrivateKey> CreateTestOtherSigningKey(); + // Verification signatures for the two hard-coded testing keys above. These + // signatures are valid only for the kFakeDomain domain. + static std::string GetTestSigningKeySignature(); + static std::string GetTestOtherSigningKeySignature(); + private: // Produces |key|'s signature over |data| and stores it in |signature|. void SignData(const std::string& data, diff --git a/components/policy/core/common/cloud/policy_header_service.cc b/components/policy/core/common/cloud/policy_header_service.cc index 7953f56..3ba5b03 100644 --- a/components/policy/core/common/cloud/policy_header_service.cc +++ b/components/policy/core/common/cloud/policy_header_service.cc @@ -12,14 +12,19 @@ namespace { const char kUserDMTokenKey[] = "user_dmtoken"; +const char kUserPolicyTokenKey[] = "user_policy_token"; +const char kVerificationKeyHashKey[] = "verification_key_hash"; } namespace policy { -PolicyHeaderService::PolicyHeaderService(const std::string& server_url, - CloudPolicyStore* user_policy_store, - CloudPolicyStore* device_policy_store) +PolicyHeaderService::PolicyHeaderService( + const std::string& server_url, + const std::string& verification_key_hash, + CloudPolicyStore* user_policy_store, + CloudPolicyStore* device_policy_store) : server_url_(server_url), + verification_key_hash_(verification_key_hash), user_policy_store_(user_policy_store), device_policy_store_(device_policy_store) { user_policy_store_->AddObserver(this); @@ -54,10 +59,20 @@ std::string PolicyHeaderService::CreateHeaderValue() { // { // user_dmtoken: <dm_token> // user_policy_token: <policy_token> + // verification_key_hash: <key_hash> // } std::string user_dm_token = user_policy_store_->policy()->request_token(); base::DictionaryValue value; value.SetString(kUserDMTokenKey, user_dm_token); + // TODO(atwilson): Enable this once policy token is available. + //if (user_policy_store_->policy()->has_policy_token()) { + // value.SetString(kUserPolicyTokenKey, + // user_policy_store_->policy()->policy_token()); + //} + value.SetString(kUserPolicyTokenKey, ""); + if (!verification_key_hash_.empty()) + value.SetString(kVerificationKeyHashKey, verification_key_hash_); + // TODO(atwilson): add user_policy_token once the server starts sending it // down (http://crbug.com/326799). std::string json; diff --git a/components/policy/core/common/cloud/policy_header_service.h b/components/policy/core/common/cloud/policy_header_service.h index b797c2a..e55c08b 100644 --- a/components/policy/core/common/cloud/policy_header_service.h +++ b/components/policy/core/common/cloud/policy_header_service.h @@ -30,6 +30,7 @@ class POLICY_EXPORT PolicyHeaderService : public CloudPolicyStore::Observer { // device policy. Both |user_policy_store| and |device_policy_store| must // outlive this object. PolicyHeaderService(const std::string& server_url, + const std::string& verification_key_hash, CloudPolicyStore* user_policy_store, CloudPolicyStore* device_policy_store); virtual ~PolicyHeaderService(); @@ -56,6 +57,9 @@ class POLICY_EXPORT PolicyHeaderService : public CloudPolicyStore::Observer { // URL of the policy server. std::string server_url_; + // Identifier for the verification key this Chrome instance is using. + std::string verification_key_hash_; + // Weak pointers to User-/Device-level policy stores. CloudPolicyStore* user_policy_store_; CloudPolicyStore* device_policy_store_; diff --git a/components/policy/core/common/cloud/policy_header_service_unittest.cc b/components/policy/core/common/cloud/policy_header_service_unittest.cc index b8c4e5b..c5cf639 100644 --- a/components/policy/core/common/cloud/policy_header_service_unittest.cc +++ b/components/policy/core/common/cloud/policy_header_service_unittest.cc @@ -6,6 +6,7 @@ #include "base/json/json_reader.h" #include "base/test/test_simple_task_runner.h" #include "base/values.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" #include "components/policy/core/common/cloud/mock_cloud_policy_store.h" #include "components/policy/core/common/cloud/policy_header_io_helper.h" #include "components/policy/core/common/cloud/policy_header_service.h" @@ -37,8 +38,10 @@ class PolicyHeaderServiceTest : public testing::Test { virtual ~PolicyHeaderServiceTest() {} virtual void SetUp() OVERRIDE { - service_.reset(new PolicyHeaderService( - kDMServerURL, &user_store_, &device_store_)); + service_.reset(new PolicyHeaderService(kDMServerURL, + kPolicyVerificationKeyHash, + &user_store_, + &device_store_)); helper_ = service_->CreatePolicyHeaderIOHelper(task_runner_).Pass(); } diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.cc b/components/policy/core/common/cloud/user_cloud_policy_manager.cc index 6e47339..ed9e590 100644 --- a/components/policy/core/common/cloud/user_cloud_policy_manager.cc +++ b/components/policy/core/common/cloud/user_cloud_policy_manager.cc @@ -72,6 +72,7 @@ UserCloudPolicyManager::CreateCloudPolicyClient( new CloudPolicyClient( std::string(), std::string(), + kPolicyVerificationKeyHash, USER_AFFILIATION_NONE, NULL, device_management_service, diff --git a/components/policy/core/common/cloud/user_cloud_policy_manager.h b/components/policy/core/common/cloud/user_cloud_policy_manager.h index 5c4068e..7d5f3f5 100644 --- a/components/policy/core/common/cloud/user_cloud_policy_manager.h +++ b/components/policy/core/common/cloud/user_cloud_policy_manager.h @@ -73,7 +73,8 @@ class POLICY_EXPORT UserCloudPolicyManager : public CloudPolicyManager { // Creates a CloudPolicyClient for this client. Used in situations where // callers want to create a DMToken without actually initializing the - // profile's policy infrastructure. + // profile's policy infrastructure (for example, during signin when we + // want to check if the user's domain requires policy). static scoped_ptr<CloudPolicyClient> CreateCloudPolicyClient( DeviceManagementService* device_management_service, scoped_refptr<net::URLRequestContextGetter> request_context); diff --git a/components/policy/core/common/cloud/user_cloud_policy_store.cc b/components/policy/core/common/cloud/user_cloud_policy_store.cc index c800328..7b4d735 100644 --- a/components/policy/core/common/cloud/user_cloud_policy_store.cc +++ b/components/policy/core/common/cloud/user_cloud_policy_store.cc @@ -7,9 +7,11 @@ #include "base/bind.h" #include "base/file_util.h" #include "base/location.h" +#include "base/metrics/histogram.h" #include "base/task_runner_util.h" #include "policy/proto/cloud_policy.pb.h" #include "policy/proto/device_management_backend.pb.h" +#include "policy/proto/policy_signing_key.pb.h" namespace em = enterprise_management; @@ -32,6 +34,7 @@ enum PolicyLoadStatus { struct PolicyLoadResult { PolicyLoadStatus status; em::PolicyFetchResponse policy; + em::PolicySigningKey key; }; namespace { @@ -42,69 +45,130 @@ const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy"); const base::FilePath::CharType kPolicyCacheFile[] = FILE_PATH_LITERAL("User Policy"); +// File in the above directory for storing policy signing key data. +const base::FilePath::CharType kKeyCacheFile[] = + FILE_PATH_LITERAL("Signing Key"); + +const char kMetricPolicyHasVerifiedCachedKey[] = + "Enterprise.PolicyHasVerifiedCachedKey"; + // Loads policy from the backing file. Returns a PolicyLoadResult with the // results of the fetch. -policy::PolicyLoadResult LoadPolicyFromDisk(const base::FilePath& path) { +policy::PolicyLoadResult LoadPolicyFromDisk( + const base::FilePath& policy_path, + const base::FilePath& key_path) { policy::PolicyLoadResult result; - // If the backing file does not exist, just return. - if (!base::PathExists(path)) { + // If the backing file does not exist, just return. We don't verify the key + // path here, because the key is optional (the validation code will fail if + // the key does not exist but the loaded policy is unsigned). + if (!base::PathExists(policy_path)) { result.status = policy::LOAD_RESULT_NO_POLICY_FILE; return result; } std::string data; - if (!base::ReadFileToString(path, &data) || - !result.policy.ParseFromArray(data.c_str(), data.size())) { - LOG(WARNING) << "Failed to read or parse policy data from " << path.value(); + // TODO(atwilson): Enforce a policy/key maxsize when ReadFileToString() can + // accept a max_size (http://crbug.com/339417). + if (!base::ReadFileToString(policy_path, &data) || + !result.policy.ParseFromString(data)) { + LOG(WARNING) << "Failed to read or parse policy data from " + << policy_path.value(); result.status = policy::LOAD_RESULT_LOAD_ERROR; return result; } + if (!base::ReadFileToString(key_path, &data) || + !result.key.ParseFromString(data)) { + // Log an error on missing key data, but do not trigger a load failure + // for now since there are still old unsigned cached policy blobs in the + // wild with no associated key (see kMetricPolicyHasVerifiedCachedKey UMA + // stat below). + LOG(ERROR) << "Failed to read or parse key data from " << key_path.value(); + result.key.clear_signing_key(); + } + + // Track the occurrence of valid cached keys - when this ratio gets high + // enough, we can update the code to reject unsigned policy or unverified + // keys. + UMA_HISTOGRAM_BOOLEAN(kMetricPolicyHasVerifiedCachedKey, + result.key.has_signing_key()); + result.status = policy::LOAD_RESULT_SUCCESS; return result; } +bool WriteStringToFile(const base::FilePath path, const std::string& data) { + if (!base::CreateDirectory(path.DirName())) { + DLOG(WARNING) << "Failed to create directory " << path.DirName().value(); + return false; + } + + int size = data.size(); + if (file_util::WriteFile(path, data.c_str(), size) != size) { + DLOG(WARNING) << "Failed to write " << path.value(); + return false; + } + + return true; +} + // Stores policy to the backing file (must be called via a task on // the background thread). void StorePolicyToDiskOnBackgroundThread( - const base::FilePath& path, + const base::FilePath& policy_path, + const base::FilePath& key_path, const em::PolicyFetchResponse& policy) { - DVLOG(1) << "Storing policy to " << path.value(); + DVLOG(1) << "Storing policy to " << policy_path.value(); std::string data; if (!policy.SerializeToString(&data)) { DLOG(WARNING) << "Failed to serialize policy data"; return; } - if (!base::CreateDirectory(path.DirName())) { - DLOG(WARNING) << "Failed to create directory " << path.DirName().value(); + if (!WriteStringToFile(policy_path, data)) return; - } - int size = data.size(); - if (file_util::WriteFile(path, data.c_str(), size) != size) { - DLOG(WARNING) << "Failed to write " << path.value(); + if (policy.has_new_public_key()) { + // Write the new public key and its verification signature to a file. + em::PolicySigningKey key_info; + key_info.set_signing_key(policy.new_public_key()); + key_info.set_signing_key_signature( + policy.new_public_key_verification_signature()); + std::string key_data; + if (!key_info.SerializeToString(&key_data)) { + DLOG(WARNING) << "Failed to serialize policy signing key"; + return; + } + + WriteStringToFile(key_path, key_data); } } } // namespace UserCloudPolicyStore::UserCloudPolicyStore( - const base::FilePath& path, + const base::FilePath& policy_path, + const base::FilePath& key_path, + const std::string& verification_key, scoped_refptr<base::SequencedTaskRunner> background_task_runner) : UserCloudPolicyStoreBase(background_task_runner), weak_factory_(this), - backing_file_path_(path) {} + policy_path_(policy_path), + key_path_(key_path), + verification_key_(verification_key) {} UserCloudPolicyStore::~UserCloudPolicyStore() {} // static scoped_ptr<UserCloudPolicyStore> UserCloudPolicyStore::Create( const base::FilePath& profile_path, + const std::string& verification_key, scoped_refptr<base::SequencedTaskRunner> background_task_runner) { - base::FilePath path = + base::FilePath policy_path = profile_path.Append(kPolicyDir).Append(kPolicyCacheFile); - return make_scoped_ptr( - new UserCloudPolicyStore(path, background_task_runner)); + base::FilePath key_path = + profile_path.Append(kPolicyDir).Append(kKeyCacheFile); + return make_scoped_ptr(new UserCloudPolicyStore( + policy_path, key_path, verification_key, background_task_runner)); } void UserCloudPolicyStore::SetSigninUsername(const std::string& username) { @@ -116,7 +180,7 @@ void UserCloudPolicyStore::LoadImmediately() { // Cancel any pending Load/Store/Validate operations. weak_factory_.InvalidateWeakPtrs(); // Load the policy from disk... - PolicyLoadResult result = LoadPolicyFromDisk(backing_file_path_); + PolicyLoadResult result = LoadPolicyFromDisk(policy_path_, key_path_); // ...and install it, reporting success/failure to any observers. PolicyLoaded(false, result); } @@ -124,10 +188,13 @@ void UserCloudPolicyStore::LoadImmediately() { void UserCloudPolicyStore::Clear() { background_task_runner()->PostTask( FROM_HERE, - base::Bind( - base::IgnoreResult(&base::DeleteFile), backing_file_path_, false)); + base::Bind(base::IgnoreResult(&base::DeleteFile), policy_path_, false)); + background_task_runner()->PostTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&base::DeleteFile), key_path_, false)); policy_.reset(); policy_map_.Clear(); + policy_key_.clear(); NotifyStoreLoaded(); } @@ -141,7 +208,7 @@ void UserCloudPolicyStore::Load() { base::PostTaskAndReplyWithResult( background_task_runner(), FROM_HERE, - base::Bind(&LoadPolicyFromDisk, backing_file_path_), + base::Bind(&LoadPolicyFromDisk, policy_path_, key_path_), base::Bind(&UserCloudPolicyStore::PolicyLoaded, weak_factory_.GetWeakPtr(), true)); } @@ -163,11 +230,16 @@ void UserCloudPolicyStore::PolicyLoaded(bool validate_in_background, // Found policy on disk - need to validate it before it can be used. scoped_ptr<em::PolicyFetchResponse> cloud_policy( new em::PolicyFetchResponse(result.policy)); + scoped_ptr<em::PolicySigningKey> key( + new em::PolicySigningKey(result.key)); Validate(cloud_policy.Pass(), + key.Pass(), validate_in_background, base::Bind( &UserCloudPolicyStore::InstallLoadedPolicyAfterValidation, - weak_factory_.GetWeakPtr())); + weak_factory_.GetWeakPtr(), + result.key.has_signing_key() ? + result.key.signing_key() : std::string())); break; } default: @@ -176,6 +248,7 @@ void UserCloudPolicyStore::PolicyLoaded(bool validate_in_background, } void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation( + const std::string& signing_key, UserCloudPolicyValidator* validator) { validation_status_ = validator->status(); if (!validator->success()) { @@ -190,6 +263,8 @@ void UserCloudPolicyStore::InstallLoadedPolicyAfterValidation( DVLOG(1) << "Device ID: " << validator->policy_data()->device_id(); InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); + // Policy validation succeeded, so we know the signing key is good. + policy_key_ = signing_key; status_ = STATUS_OK; NotifyStoreLoaded(); } @@ -201,6 +276,7 @@ void UserCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) { scoped_ptr<em::PolicyFetchResponse> policy_copy( new em::PolicyFetchResponse(policy)); Validate(policy_copy.Pass(), + scoped_ptr<em::PolicySigningKey>(), true, base::Bind(&UserCloudPolicyStore::StorePolicyAfterValidation, weak_factory_.GetWeakPtr())); @@ -208,16 +284,73 @@ void UserCloudPolicyStore::Store(const em::PolicyFetchResponse& policy) { void UserCloudPolicyStore::Validate( scoped_ptr<em::PolicyFetchResponse> policy, + scoped_ptr<em::PolicySigningKey> cached_key, bool validate_in_background, const UserCloudPolicyValidator::CompletionCallback& callback) { + + const bool signed_policy = policy->has_policy_data_signature(); + // Configure the validator. scoped_ptr<UserCloudPolicyValidator> validator = CreateValidator( policy.Pass(), CloudPolicyValidatorBase::TIMESTAMP_NOT_BEFORE); // Validate the username if the user is signed in. - if (!signin_username_.empty()) + if (!signin_username_.empty()) { + DVLOG(1) << "Validating username: " << signin_username_; validator->ValidateUsername(signin_username_); + } + + // There are 4 cases: + // + // 1) Validation after loading from cache with no cached key. + // Action: Don't validate signature (migration from previously cached + // unsigned blob). + // + // 2) Validation after loading from cache with a cached key + // Action: Validate signature on policy blob but don't allow key rotation. + // + // 3) Validation after loading new policy from the server with no cached key + // Action: Validate as initial key provisioning (case where we are migrating + // from unsigned policy) + // + // 4) Validation after loading new policy from the server with a cached key + // Action: Validate as normal, and allow key rotation. + if (cached_key) { + // Loading from cache should not change the cached keys. + DCHECK(policy_key_.empty() || policy_key_ == cached_key->signing_key()); + if (!signed_policy || !cached_key->has_signing_key()) { + // Case #1 - loading from cache with no signing key. + // TODO(atwilson): Reject policy with no cached key once + // kMetricPolicyHasVerifiedCachedKey rises to a high enough level. + DLOG(WARNING) << "Allowing unsigned cached blob for migration"; + } else { + // Case #2 - loading from cache with a cached key - just do normal + // signature validation using this key. We're loading from cache so don't + // allow key rotation. + const bool no_rotation = false; + validator->ValidateSignature(cached_key->signing_key(), + verification_key_, + cached_key->signing_key_signature(), + no_rotation); + } + } else { + // No passed cached_key - this is not validating the initial policy load + // from cache, but rather an update from the server. + if (policy_key_.empty()) { + // Case #3 - no valid existing policy key, so this new policy fetch should + // include an initial key provision. + validator->ValidateInitialKey(verification_key_); + } else { + // Case #4 - verify new policy with existing key. We always allow key + // rotation - the verification key will prevent invalid policy from being + // injected. |policy_key_| is already known to be valid, so no + // verification signature is passed in. + const bool allow_rotation = true; + validator->ValidateSignature( + policy_key_, verification_key_, std::string(), allow_rotation); + } + } if (validate_in_background) { // Start validation in the background. The Validator will free itself once @@ -245,8 +378,12 @@ void UserCloudPolicyStore::StorePolicyAfterValidation( background_task_runner()->PostTask( FROM_HERE, base::Bind(&StorePolicyToDiskOnBackgroundThread, - backing_file_path_, *validator->policy())); + policy_path_, key_path_, *validator->policy())); InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); + + // If the key was rotated, update our local cache of the key. + if (validator->policy()->has_new_public_key()) + policy_key_ = validator->policy()->new_public_key(); status_ = STATUS_OK; NotifyStoreLoaded(); } diff --git a/components/policy/core/common/cloud/user_cloud_policy_store.h b/components/policy/core/common/cloud/user_cloud_policy_store.h index 4dc7e24..e7ef91e 100644 --- a/components/policy/core/common/cloud/user_cloud_policy_store.h +++ b/components/policy/core/common/cloud/user_cloud_policy_store.h @@ -13,6 +13,7 @@ #include "base/memory/weak_ptr.h" #include "components/policy/core/common/cloud/user_cloud_policy_store_base.h" #include "components/policy/policy_export.h" +#include "policy/proto/policy_signing_key.pb.h" namespace base { class SequencedTaskRunner; @@ -29,6 +30,8 @@ class POLICY_EXPORT UserCloudPolicyStore : public UserCloudPolicyStoreBase { // it) user. UserCloudPolicyStore( const base::FilePath& policy_file, + const base::FilePath& key_file, + const std::string& verification_key, scoped_refptr<base::SequencedTaskRunner> background_task_runner); virtual ~UserCloudPolicyStore(); @@ -36,6 +39,7 @@ class POLICY_EXPORT UserCloudPolicyStore : public UserCloudPolicyStoreBase { // |profile_path|. static scoped_ptr<UserCloudPolicyStore> Create( const base::FilePath& profile_path, + const std::string& verification_key, scoped_refptr<base::SequencedTaskRunner> background_task_runner); // Sets the username from signin for validation of the policy. @@ -53,6 +57,10 @@ class POLICY_EXPORT UserCloudPolicyStore : public UserCloudPolicyStoreBase { virtual void Store( const enterprise_management::PolicyFetchResponse& policy) OVERRIDE; + // The key used to sign the current policy (empty if there either is no + // loaded policy yet, or if the policy is unsigned). + const std::string& policy_key() { return policy_key_; } + protected: std::string signin_username_; @@ -69,12 +77,14 @@ class POLICY_EXPORT UserCloudPolicyStore : public UserCloudPolicyStoreBase { // thread). void Validate( scoped_ptr<enterprise_management::PolicyFetchResponse> policy, + scoped_ptr<enterprise_management::PolicySigningKey> key, bool validate_in_background, const UserCloudPolicyValidator::CompletionCallback& callback); // Callback invoked to install a just-loaded policy after validation has // finished. - void InstallLoadedPolicyAfterValidation(UserCloudPolicyValidator* validator); + void InstallLoadedPolicyAfterValidation(const std::string& signing_key, + UserCloudPolicyValidator* validator); // Callback invoked to store the policy after validation has finished. void StorePolicyAfterValidation(UserCloudPolicyValidator* validator); @@ -82,8 +92,17 @@ class POLICY_EXPORT UserCloudPolicyStore : public UserCloudPolicyStoreBase { // WeakPtrFactory used to create callbacks for validating and storing policy. base::WeakPtrFactory<UserCloudPolicyStore> weak_factory_; + // The key used to verify signatures of cached policy. + std::string policy_key_; + // Path to file where we store persisted policy. - base::FilePath backing_file_path_; + base::FilePath policy_path_; + + // Path to file where we store the signing key for the policy blob. + base::FilePath key_path_; + + // The hard-coded key used to verify new signing keys. + const std::string verification_key_; DISALLOW_COPY_AND_ASSIGN(UserCloudPolicyStore); }; diff --git a/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc b/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc index c7378c7..0162e0a 100644 --- a/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc +++ b/components/policy/core/common/cloud/user_cloud_policy_store_unittest.cc @@ -9,6 +9,7 @@ #include "base/message_loop/message_loop.h" #include "base/message_loop/message_loop_proxy.h" #include "base/run_loop.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" #include "components/policy/core/common/cloud/mock_cloud_external_data_manager.h" #include "components/policy/core/common/cloud/mock_cloud_policy_store.h" #include "components/policy/core/common/cloud/policy_builder.h" @@ -32,6 +33,8 @@ void RunUntilIdle() { run_loop.RunUntilIdle(); } +} // namespace + class UserCloudPolicyStoreTest : public testing::Test { public: UserCloudPolicyStoreTest() {} @@ -39,15 +42,21 @@ class UserCloudPolicyStoreTest : public testing::Test { virtual void SetUp() OVERRIDE { ASSERT_TRUE(tmp_dir_.CreateUniqueTempDir()); store_.reset( - new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + new UserCloudPolicyStore(policy_file(), + key_file(), + GetPolicyVerificationKey(), + loop_.message_loop_proxy())); external_data_manager_.reset(new MockCloudExternalDataManager); external_data_manager_->SetPolicyStore(store_.get()); store_->SetSigninUsername(PolicyBuilder::kFakeUsername); store_->AddObserver(&observer_); - policy_.payload().mutable_passwordmanagerenabled()->set_value(true); - policy_.payload().mutable_urlblacklist()->mutable_value()->add_entries( - "chromium.org"); + // Install an initial public key, so that by default the validation of + // the stored/loaded policy blob succeeds (it looks like a new key + // provision). + policy_.SetDefaultInitialSigningKey(); + + InitPolicyPayload(&policy_.payload()); policy_.Build(); } @@ -59,10 +68,20 @@ class UserCloudPolicyStoreTest : public testing::Test { RunUntilIdle(); } + void InitPolicyPayload(enterprise_management::CloudPolicySettings* payload) { + payload->mutable_passwordmanagerenabled()->set_value(true); + payload->mutable_urlblacklist()->mutable_value()->add_entries( + "chromium.org"); + } + base::FilePath policy_file() { return tmp_dir_.path().AppendASCII("policy"); } + base::FilePath key_file() { + return tmp_dir_.path().AppendASCII("policy_key"); + } + // Verifies that store_->policy_map() has the appropriate entries. void VerifyPolicyMap(CloudPolicyStore* store) { EXPECT_EQ(2U, store->policy_map().size()); @@ -81,6 +100,18 @@ class UserCloudPolicyStoreTest : public testing::Test { Eq(error))))); } + void StorePolicyAndEnsureLoaded( + const enterprise_management::PolicyFetchResponse& policy) { + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->Store(policy); + RunUntilIdle(); + Mock::VerifyAndClearExpectations(external_data_manager_.get()); + Mock::VerifyAndClearExpectations(&observer_); + ASSERT_TRUE(store_->policy()); + } + UserPolicyBuilder policy_; MockCloudPolicyStoreObserver observer_; scoped_ptr<UserCloudPolicyStore> store_; @@ -162,20 +193,54 @@ TEST_F(UserCloudPolicyStoreTest, LoadImmediatelyWithInvalidFile) { EXPECT_TRUE(store_->policy_map().empty()); } +// Load file from cache with no key data, then migrate to have a key. +TEST_F(UserCloudPolicyStoreTest, Migration) { + UserPolicyBuilder unsigned_builder; + unsigned_builder.UnsetSigningKey(); + InitPolicyPayload(&unsigned_builder.payload()); + unsigned_builder.Build(); + // Policy should be unsigned. + EXPECT_FALSE(unsigned_builder.policy().has_policy_data_signature()); + + // Write policy to disk. + std::string data; + ASSERT_TRUE(unsigned_builder.policy().SerializeToString(&data)); + ASSERT_TRUE(base::CreateDirectory(policy_file().DirName())); + int size = data.size(); + ASSERT_EQ(size, file_util::WriteFile(policy_file(), data.c_str(), size)); + + // Now make sure the data can get loaded. + Sequence s; + EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); + EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); + store_->LoadImmediately(); // Should load without running the message loop. + Mock::VerifyAndClearExpectations(external_data_manager_.get()); + Mock::VerifyAndClearExpectations(&observer_); + + ASSERT_TRUE(store_->policy()); + EXPECT_EQ(unsigned_builder.policy_data().SerializeAsString(), + store_->policy()->SerializeAsString()); + VerifyPolicyMap(store_.get()); + EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); + EXPECT_TRUE(store_->policy_key().empty()); + EXPECT_FALSE(base::PathExists(key_file())); + + // Now mimic a new policy coming down - this should result in a new key + // being installed. + StorePolicyAndEnsureLoaded(policy_.policy()); + EXPECT_EQ(policy_.policy().new_public_key(), store_->policy_key()); + EXPECT_TRUE(base::PathExists(key_file())); +} + TEST_F(UserCloudPolicyStoreTest, Store) { EXPECT_FALSE(store_->policy()); EXPECT_TRUE(store_->policy_map().empty()); // Store a simple policy and make sure it ends up as the currently active // policy. - Sequence s; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); - store_->Store(policy_.policy()); - RunUntilIdle(); + StorePolicyAndEnsureLoaded(policy_.policy()); // Policy should be decoded and stored. - ASSERT_TRUE(store_->policy()); EXPECT_EQ(policy_.policy_data().SerializeAsString(), store_->policy()->SerializeAsString()); VerifyPolicyMap(store_.get()); @@ -188,18 +253,9 @@ TEST_F(UserCloudPolicyStoreTest, StoreThenClear) { // Store a simple policy and make sure the file exists. // policy. - Sequence s1; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s1); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s1); - store_->Store(policy_.policy()); - RunUntilIdle(); - - EXPECT_TRUE(store_->policy()); + StorePolicyAndEnsureLoaded(policy_.policy()); EXPECT_FALSE(store_->policy_map().empty()); - Mock::VerifyAndClearExpectations(external_data_manager_.get()); - Mock::VerifyAndClearExpectations(&observer_); - // Policy file should exist. ASSERT_TRUE(base::PathExists(policy_file())); @@ -218,6 +274,47 @@ TEST_F(UserCloudPolicyStoreTest, StoreThenClear) { EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status()); } +TEST_F(UserCloudPolicyStoreTest, StoreRotatedKey) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Store a simple policy and make sure it ends up as the currently active + // policy. + StorePolicyAndEnsureLoaded(policy_.policy()); + EXPECT_FALSE(policy_.policy().has_new_public_key_signature()); + std::string original_policy_key = policy_.policy().new_public_key(); + EXPECT_EQ(original_policy_key, store_->policy_key()); + + // Now do key rotation. + policy_.SetDefaultSigningKey(); + policy_.SetDefaultNewSigningKey(); + policy_.Build(); + EXPECT_TRUE(policy_.policy().has_new_public_key_signature()); + EXPECT_NE(original_policy_key, policy_.policy().new_public_key()); + StorePolicyAndEnsureLoaded(policy_.policy()); + EXPECT_EQ(policy_.policy().new_public_key(), store_->policy_key()); +} + +TEST_F(UserCloudPolicyStoreTest, ProvisionKeyTwice) { + EXPECT_FALSE(store_->policy()); + EXPECT_TRUE(store_->policy_map().empty()); + + // Store a simple policy and make sure it ends up as the currently active + // policy. + StorePolicyAndEnsureLoaded(policy_.policy()); + + // Now try sending down policy signed with a different key (i.e. do key + // rotation with a key not signed with the original signing key). + policy_.UnsetSigningKey(); + policy_.SetDefaultNewSigningKey(); + policy_.Build(); + EXPECT_FALSE(policy_.policy().has_new_public_key_signature()); + + ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); + store_->Store(policy_.policy()); + RunUntilIdle(); +} + TEST_F(UserCloudPolicyStoreTest, StoreTwoTimes) { EXPECT_FALSE(store_->policy()); EXPECT_TRUE(store_->policy_map().empty()); @@ -225,28 +322,20 @@ TEST_F(UserCloudPolicyStoreTest, StoreTwoTimes) { // Store a simple policy then store a second policy before the first one // finishes validating, and make sure the second policy ends up as the active // policy. - Sequence s1; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s1); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s1); - UserPolicyBuilder first_policy; + first_policy.SetDefaultInitialSigningKey(); first_policy.payload().mutable_passwordmanagerenabled()->set_value(false); first_policy.Build(); - store_->Store(first_policy.policy()); - RunUntilIdle(); - - Mock::VerifyAndClearExpectations(external_data_manager_.get()); - Mock::VerifyAndClearExpectations(&observer_); - - Sequence s2; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s2); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s2); + StorePolicyAndEnsureLoaded(first_policy.policy()); - store_->Store(policy_.policy()); - RunUntilIdle(); + // Rebuild policy with the same signing key as |first_policy| (no rotation). + policy_.UnsetNewSigningKey(); + policy_.SetDefaultSigningKey(); + policy_.Build(); + ASSERT_FALSE(policy_.policy().has_new_public_key()); + StorePolicyAndEnsureLoaded(policy_.policy()); // Policy should be decoded and stored. - ASSERT_TRUE(store_->policy()); EXPECT_EQ(policy_.policy_data().SerializeAsString(), store_->policy()->SerializeAsString()); VerifyPolicyMap(store_.get()); @@ -256,15 +345,15 @@ TEST_F(UserCloudPolicyStoreTest, StoreTwoTimes) { TEST_F(UserCloudPolicyStoreTest, StoreThenLoad) { // Store a simple policy and make sure it can be read back in. // policy. - Sequence s; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); - store_->Store(policy_.policy()); - RunUntilIdle(); + StorePolicyAndEnsureLoaded(policy_.policy()); + EXPECT_FALSE(store_->policy_key().empty()); // Now, make sure the policy can be read back in from a second store. scoped_ptr<UserCloudPolicyStore> store2( - new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + new UserCloudPolicyStore(policy_file(), + key_file(), + GetPolicyVerificationKey(), + loop_.message_loop_proxy())); store2->SetSigninUsername(PolicyBuilder::kFakeUsername); store2->AddObserver(&observer_); EXPECT_CALL(observer_, OnStoreLoaded(store2.get())); @@ -277,20 +366,21 @@ TEST_F(UserCloudPolicyStoreTest, StoreThenLoad) { VerifyPolicyMap(store2.get()); EXPECT_EQ(CloudPolicyStore::STATUS_OK, store2->status()); store2->RemoveObserver(&observer_); + // Make sure that we properly resurrected the keys. + EXPECT_EQ(store2->policy_key(), store_->policy_key()); } TEST_F(UserCloudPolicyStoreTest, StoreThenLoadImmediately) { // Store a simple policy and make sure it can be read back in. // policy. - Sequence s; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); - store_->Store(policy_.policy()); - RunUntilIdle(); + StorePolicyAndEnsureLoaded(policy_.policy()); // Now, make sure the policy can be read back in from a second store. scoped_ptr<UserCloudPolicyStore> store2( - new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + new UserCloudPolicyStore(policy_file(), + key_file(), + GetPolicyVerificationKey(), + loop_.message_loop_proxy())); store2->SetSigninUsername(PolicyBuilder::kFakeUsername); store2->AddObserver(&observer_); EXPECT_CALL(observer_, OnStoreLoaded(store2.get())); @@ -316,18 +406,28 @@ TEST_F(UserCloudPolicyStoreTest, StoreValidationError) { ASSERT_FALSE(store_->policy()); } -TEST_F(UserCloudPolicyStoreTest, LoadValidationError) { - // Force a validation error by changing the username after policy is stored. - Sequence s; - EXPECT_CALL(*external_data_manager_, OnPolicyStoreLoaded()).InSequence(s); - EXPECT_CALL(observer_, OnStoreLoaded(store_.get())).InSequence(s); +TEST_F(UserCloudPolicyStoreTest, StoreUnsigned) { + // Create unsigned policy, try to store it, should get a validation error. + policy_.policy().mutable_policy_data_signature()->clear(); + + // Store policy. + ExpectError(store_.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); store_->Store(policy_.policy()); RunUntilIdle(); + ASSERT_FALSE(store_->policy()); +} + +TEST_F(UserCloudPolicyStoreTest, LoadValidationError) { + // Force a validation error by changing the username after policy is stored. + StorePolicyAndEnsureLoaded(policy_.policy()); // Sign out, and sign back in as a different user, and try to load the profile // data (should fail due to mismatched username). scoped_ptr<UserCloudPolicyStore> store2( - new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + new UserCloudPolicyStore(policy_file(), + key_file(), + GetPolicyVerificationKey(), + loop_.message_loop_proxy())); store2->SetSigninUsername("foobar@foobar.com"); store2->AddObserver(&observer_); ExpectError(store2.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); @@ -340,7 +440,10 @@ TEST_F(UserCloudPolicyStoreTest, LoadValidationError) { // Sign out - we should be able to load the policy (don't check usernames // when signed out). scoped_ptr<UserCloudPolicyStore> store3( - new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + new UserCloudPolicyStore(policy_file(), + key_file(), + GetPolicyVerificationKey(), + loop_.message_loop_proxy())); store3->AddObserver(&observer_); EXPECT_CALL(observer_, OnStoreLoaded(store3.get())); store3->Load(); @@ -351,7 +454,10 @@ TEST_F(UserCloudPolicyStoreTest, LoadValidationError) { // Now start a signin as a different user - this should fail validation. scoped_ptr<UserCloudPolicyStore> store4( - new UserCloudPolicyStore(policy_file(), loop_.message_loop_proxy())); + new UserCloudPolicyStore(policy_file(), + key_file(), + GetPolicyVerificationKey(), + loop_.message_loop_proxy())); store4->SetSigninUsername("foobar@foobar.com"); store4->AddObserver(&observer_); ExpectError(store4.get(), CloudPolicyStore::STATUS_VALIDATION_ERROR); @@ -362,6 +468,8 @@ TEST_F(UserCloudPolicyStoreTest, LoadValidationError) { store4->RemoveObserver(&observer_); } -} // namespace +// TODO(atwilson): Add a test to detect tampered policy +// (new_public_key_verification_signature() doesn't match the right key - +// http://crbug.com/275291). } // namespace policy diff --git a/components/policy/core/common/policy_switches.cc b/components/policy/core/common/policy_switches.cc index 7808085..3690ee5 100644 --- a/components/policy/core/common/policy_switches.cc +++ b/components/policy/core/common/policy_switches.cc @@ -24,6 +24,9 @@ const char kDisableCloudPolicyPush[] = "disable-cloud-policy-push"; // Enables fetching and storing cloud policy for components. const char kEnableComponentCloudPolicy[] = "enable-component-cloud-policy"; +// Disables the verification of policy signing keys. +const char kSkipPolicyKeyVerification[] = "skip-policy-key-verification"; + #if defined(OS_ANDROID) || defined(OS_IOS) // Registers for cloud policy using the BROWSER client type instead of the // ANDROID_BROWSER or IOS_BROWSER types. diff --git a/components/policy/core/common/policy_switches.h b/components/policy/core/common/policy_switches.h index a0728c1..e14dea0 100644 --- a/components/policy/core/common/policy_switches.h +++ b/components/policy/core/common/policy_switches.h @@ -16,6 +16,7 @@ POLICY_EXPORT extern const char kCloudPolicyInvalidationDelay[]; POLICY_EXPORT extern const char kDeviceManagementUrl[]; POLICY_EXPORT extern const char kDisableCloudPolicyPush[]; POLICY_EXPORT extern const char kEnableComponentCloudPolicy[]; +POLICY_EXPORT extern const char kSkipPolicyKeyVerification[]; #if defined(OS_ANDROID) || defined(OS_IOS) POLICY_EXPORT extern const char kFakeCloudPolicyType[]; diff --git a/components/policy/proto/device_management_backend.proto b/components/policy/proto/device_management_backend.proto index 6754abb..51ee3a6 100644 --- a/components/policy/proto/device_management_backend.proto +++ b/components/policy/proto/device_management_backend.proto @@ -172,6 +172,13 @@ message PolicyFetchRequest { // payload delivered with the invalidation. The server interprets this value // and the value of invalidation_version to fetch the up-to-date policy. optional bytes invalidation_payload = 8; + + // Hash string for the chrome policy verification public key which is embedded + // into Chrome binary. Matching private key will be used by the server + // to sign per-domain policy keys during key rotation. If server does not + // have the key which matches this hash string, that could indicate malicious + // or out-of-date Chrome client. + optional string verification_key_hash = 9; } // This message is included in serialized form in PolicyFetchResponse @@ -283,6 +290,27 @@ message PolicyFetchResponse { // on old public key), then |new_public_key_signature| is empty. optional bytes new_public_key = 5; optional bytes new_public_key_signature = 6; + + // If new_public_key is specified, this field contains a signature + // of that key, signed using a key only available to DMServer. + // The public key portion of this well-known key is embedded into the + // Chrome binary. The hash of that embedded key is passed to DMServer + // as verification_key_hash field in PolicyFetchRequest. DMServer will + // pick a private key on the server which matches the hash (matches public + // key on the client). If DMServer is unable to find matching key, it will + // return an error instead of policy data. + // In case hash was not specified, DMServer will leave verification signature + // field empty (legacy behavior). + // In addition to the checks between new_public_key + // and new_public_key_signature described above, Chrome also verifies + // new_public_key with the embedded public key and + // new_public_key_verification_signature. + optional bytes new_public_key_verification_signature = 7; + + // Server-provided identifier of the fetched policy. This is to be used + // by the client when requesting Policy Posture assertion through an API + // call or SAML flow. + optional bytes policy_token = 8; } // Request from device to server for reading policies. diff --git a/components/policy/proto/policy_signing_key.proto b/components/policy/proto/policy_signing_key.proto new file mode 100644 index 0000000..c038bd3 --- /dev/null +++ b/components/policy/proto/policy_signing_key.proto @@ -0,0 +1,20 @@ +// 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. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package enterprise_management; + +// Contains a signing key and its signature. +message PolicySigningKey { + // The key used to verify policy blobs sent down from the server. + optional bytes signing_key = 1; + + // The signature for this signing key (verified using a hard-coded key + // stored in the Chrome binary). This is essentially a certificate (key + // signed with another well-known key that establishes a trust root). + optional bytes signing_key_signature = 2; +} diff --git a/components/policy_strings.grdp b/components/policy_strings.grdp index 67be5ed..90ccd37 100644 --- a/components/policy_strings.grdp +++ b/components/policy_strings.grdp @@ -83,6 +83,9 @@ <message name="IDS_POLICY_VALIDATION_POLICY_PARSE_ERROR" desc="Message indicating a parse error in policy."> Error parsing policy settings </message> + <message name="IDS_POLICY_VALIDATION_BAD_KEY_VERIFICATION_SIGNATURE" desc="Message indicating a policy key had an invalid verification signature."> + Bad verification signature. + </message> <message name="IDS_POLICY_VALIDATION_UNKNOWN_ERROR" desc="Message indicating unknown error in policy validation."> Unknown error </message> |