1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <cryptohi.h> 6 7 #include "base/macros.h" 8 #include "base/strings/stringprintf.h" 9 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h" 10 #include "chrome/browser/extensions/extension_apitest.h" 11 #include "chrome/browser/extensions/extension_service.h" 12 #include "chrome/browser/net/nss_context.h" 13 #include "chrome/browser/net/url_request_mock_util.h" 14 #include "chromeos/chromeos_switches.h" 15 #include "chromeos/login/user_names.h" 16 #include "components/policy/core/browser/browser_policy_connector.h" 17 #include "components/policy/core/common/mock_configuration_policy_provider.h" 18 #include "components/policy/core/common/policy_map.h" 19 #include "content/public/browser/notification_service.h" 20 #include "content/public/common/content_switches.h" 21 #include "content/public/test/test_utils.h" 22 #include "crypto/nss_util_internal.h" 23 #include "crypto/scoped_test_system_nss_key_slot.h" 24 #include "extensions/browser/notification_types.h" 25 #include "net/base/net_errors.h" 26 #include "net/cert/nss_cert_database.h" 27 #include "net/test/url_request/url_request_mock_http_job.h" 28 #include "policy/policy_constants.h" 29 #include "testing/gmock/include/gmock/gmock.h" 30 31 namespace { 32 33 // The test extension has a certificate referencing this private key which will 34 // be stored in the user's token in the test setup. 35 // 36 // openssl genrsa > privkey.pem 37 // openssl pkcs8 -inform pem -in privkey.pem -topk8 38 // -outform der -out privkey8.der -nocrypt 39 // xxd -i privkey8.der 40 const unsigned char privateKeyPkcs8User[] = { 41 0x30, 0x82, 0x01, 0x55, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 42 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 43 0x01, 0x3f, 0x30, 0x82, 0x01, 0x3b, 0x02, 0x01, 0x00, 0x02, 0x41, 0x00, 44 0xc7, 0xc1, 0x4d, 0xd5, 0xdc, 0x3a, 0x2e, 0x1f, 0x42, 0x30, 0x3d, 0x21, 45 0x1e, 0xa2, 0x1f, 0x60, 0xcb, 0x71, 0x11, 0x53, 0xb0, 0x75, 0xa0, 0x62, 46 0xfe, 0x5e, 0x0a, 0xde, 0xb0, 0x0f, 0x48, 0x97, 0x5e, 0x42, 0xa7, 0x3a, 47 0xd1, 0xca, 0x4c, 0xe3, 0xdb, 0x5f, 0x31, 0xc2, 0x99, 0x08, 0x89, 0xcd, 48 0x6d, 0x20, 0xaa, 0x75, 0xe6, 0x2b, 0x98, 0xd2, 0xf3, 0x7b, 0x4b, 0xe5, 49 0x9b, 0xfe, 0xe2, 0x6d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x40, 0x4a, 50 0xf5, 0x76, 0x10, 0xe7, 0xb8, 0x89, 0x70, 0x3f, 0x75, 0x3c, 0xab, 0x3e, 51 0x04, 0x96, 0x83, 0xcb, 0x34, 0x1d, 0xcd, 0x6a, 0xed, 0x69, 0x07, 0x5c, 52 0xee, 0xcb, 0x63, 0x6f, 0x6b, 0xfc, 0xcf, 0xee, 0xa2, 0xc4, 0x67, 0x05, 53 0x68, 0x4d, 0x21, 0x7e, 0x3e, 0xde, 0x74, 0x72, 0xf8, 0x04, 0x35, 0x66, 54 0x1e, 0x6b, 0x1d, 0xef, 0x77, 0xf7, 0x33, 0xf0, 0x35, 0xcf, 0x35, 0x6e, 55 0x53, 0x3f, 0x9d, 0x02, 0x21, 0x00, 0xee, 0x48, 0x67, 0x1b, 0x24, 0x6e, 56 0x3d, 0x7b, 0xa0, 0xc3, 0xee, 0x8a, 0x2e, 0xc7, 0xd0, 0xa1, 0xdb, 0x25, 57 0x31, 0x12, 0x99, 0x43, 0x06, 0x3c, 0xb0, 0x80, 0x35, 0x2b, 0xf4, 0xc5, 58 0xa2, 0xd3, 0x02, 0x21, 0x00, 0xd6, 0x9b, 0x8b, 0x75, 0x91, 0x52, 0xd4, 59 0xf0, 0x76, 0xcf, 0xa2, 0xbe, 0xa6, 0xaf, 0x72, 0x6c, 0x52, 0xf9, 0xc9, 60 0x0e, 0xea, 0x4a, 0x4c, 0xd2, 0xdf, 0x25, 0x70, 0xc6, 0x66, 0x35, 0x9d, 61 0xbf, 0x02, 0x21, 0x00, 0xe8, 0x9e, 0x40, 0x21, 0xcc, 0x37, 0xde, 0xc7, 62 0xd1, 0x13, 0x55, 0xcd, 0x0a, 0x8c, 0x40, 0xcd, 0xb1, 0xed, 0xa5, 0xf1, 63 0x7d, 0x33, 0x64, 0x64, 0x5c, 0xfe, 0x5c, 0x6a, 0x34, 0x03, 0xb8, 0xc7, 64 0x02, 0x20, 0x17, 0xe1, 0xb5, 0x52, 0x3e, 0xfa, 0xc5, 0xc1, 0x80, 0xa7, 65 0x38, 0x88, 0x18, 0xca, 0x7b, 0x64, 0x3c, 0x93, 0x99, 0x61, 0x34, 0x87, 66 0x52, 0x27, 0x41, 0x37, 0xcc, 0x65, 0xf7, 0xa7, 0xcd, 0xc7, 0x02, 0x21, 67 0x00, 0x8a, 0x17, 0x7f, 0xf9, 0x45, 0xf3, 0xfd, 0xf7, 0x96, 0x62, 0xf3, 68 0x7a, 0x09, 0xfb, 0xe9, 0x9e, 0xc7, 0x7a, 0x1f, 0x53, 0x1a, 0xb8, 0xd5, 69 0x88, 0x9d, 0xd4, 0x79, 0x57, 0x88, 0x68, 0x72, 0x6f}; 70 71 // The test extension has a certificate referencing this private key which will 72 // be stored in the system token in the test setup. 73 const unsigned char privateKeyPkcs8System[] = { 74 0x30, 0x82, 0x01, 0x54, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 75 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 76 0x01, 0x3e, 0x30, 0x82, 0x01, 0x3a, 0x02, 0x01, 0x00, 0x02, 0x41, 0x00, 77 0xe8, 0xb3, 0x04, 0xb1, 0xad, 0xef, 0x6b, 0xe5, 0xbe, 0xc9, 0x05, 0x75, 78 0x07, 0x41, 0xf5, 0x70, 0x50, 0xc2, 0xe8, 0xee, 0xeb, 0x09, 0x9d, 0x49, 79 0x64, 0x4c, 0x60, 0x61, 0x80, 0xbe, 0xc5, 0x41, 0xf3, 0x8c, 0x57, 0x90, 80 0x3a, 0x44, 0x62, 0x6d, 0x51, 0xb8, 0xbb, 0xc6, 0x9a, 0x16, 0xdf, 0xf9, 81 0xce, 0xe3, 0xb8, 0x8c, 0x2e, 0xa2, 0x16, 0xc8, 0xed, 0xc7, 0xf8, 0x4f, 82 0xbd, 0xd3, 0x6e, 0x63, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x40, 0x76, 83 0xc9, 0x83, 0xf8, 0xeb, 0xd0, 0x8f, 0xa4, 0xdd, 0x4a, 0xa2, 0xe5, 0x85, 84 0xc9, 0xee, 0xef, 0xe1, 0xda, 0x4d, 0xac, 0x41, 0x01, 0x4c, 0x70, 0x7d, 85 0xa9, 0xdb, 0x7d, 0x8a, 0x8a, 0x58, 0x09, 0x04, 0x45, 0x43, 0xa4, 0xf3, 86 0xb4, 0x98, 0xf6, 0x34, 0x68, 0x5f, 0xc1, 0xc2, 0xa7, 0x86, 0x3e, 0xec, 87 0x84, 0x0b, 0x18, 0xbc, 0xb1, 0xee, 0x6f, 0x3f, 0xb1, 0x6d, 0xbc, 0x3e, 88 0xbf, 0x6d, 0x31, 0x02, 0x21, 0x00, 0xff, 0x9d, 0x90, 0x4f, 0x0e, 0xe8, 89 0x7e, 0xf3, 0x38, 0xa7, 0xec, 0x73, 0x80, 0xf9, 0x39, 0x2c, 0xaa, 0x33, 90 0x91, 0x72, 0x10, 0x7c, 0x8b, 0xc3, 0x61, 0x6d, 0x40, 0x96, 0xac, 0xb3, 91 0x5e, 0xc9, 0x02, 0x21, 0x00, 0xe9, 0x0c, 0xa1, 0x34, 0xf2, 0x43, 0x3c, 92 0x74, 0xec, 0x1a, 0xf6, 0x80, 0x8e, 0x50, 0x10, 0x6d, 0x55, 0x64, 0xce, 93 0x47, 0x4a, 0x1e, 0x34, 0x27, 0x6c, 0x49, 0x79, 0x6a, 0x23, 0xc6, 0x9d, 94 0xcb, 0x02, 0x20, 0x48, 0xda, 0xa8, 0xc1, 0xcf, 0xb6, 0xf6, 0x4f, 0xee, 95 0x4a, 0xf6, 0x3a, 0xa9, 0x7c, 0xdf, 0x0d, 0xda, 0xe8, 0xdd, 0xc0, 0x8b, 96 0xf0, 0x63, 0x89, 0x69, 0x60, 0x51, 0x33, 0x60, 0xbf, 0xb2, 0xf9, 0x02, 97 0x21, 0x00, 0xb4, 0x77, 0x81, 0x46, 0x7c, 0xec, 0x30, 0x1e, 0xe2, 0xcf, 98 0x26, 0x5f, 0xfa, 0xd4, 0x69, 0x44, 0x21, 0x42, 0x84, 0xb2, 0x93, 0xe4, 99 0xbb, 0xc2, 0x63, 0x8a, 0xaa, 0x28, 0xd5, 0x37, 0x72, 0xed, 0x02, 0x20, 100 0x16, 0xde, 0x3d, 0x57, 0xc5, 0xd5, 0x3d, 0x90, 0x8b, 0xfd, 0x90, 0x3b, 101 0xd8, 0x71, 0x69, 0x5e, 0x8d, 0xb4, 0x48, 0x1c, 0xa4, 0x01, 0xce, 0xc1, 102 0xb5, 0x6f, 0xe9, 0x1b, 0x32, 0x91, 0x34, 0x38 103 }; 104 105 const base::FilePath::CharType kTestExtensionDir[] = 106 FILE_PATH_LITERAL("extensions/api_test/enterprise_platform_keys"); 107 const base::FilePath::CharType kUpdateManifestFileName[] = 108 FILE_PATH_LITERAL("update_manifest.xml"); 109 110 void ImportPrivateKeyPKCS8ToSlot(const unsigned char* pkcs8_der, 111 size_t pkcs8_der_size, 112 PK11SlotInfo* slot) { 113 SECItem pki_der_user = { 114 siBuffer, 115 // NSS requires non-const data even though it is just for input. 116 const_cast<unsigned char*>(pkcs8_der), 117 pkcs8_der_size}; 118 119 SECKEYPrivateKey* seckey = NULL; 120 ASSERT_EQ(SECSuccess, 121 PK11_ImportDERPrivateKeyInfoAndReturnKey(slot, 122 &pki_der_user, 123 NULL, // nickname 124 NULL, // publicValue 125 true, // isPerm 126 true, // isPrivate 127 KU_ALL, // usage 128 &seckey, 129 NULL)); 130 } 131 132 // The managed_storage extension has a key defined in its manifest, so that 133 // its extension ID is well-known and the policy system can push policies for 134 // the extension. 135 const char kTestExtensionID[] = "aecpbnckhoppanpmefllkdkohionpmig"; 136 137 enum SystemToken { 138 SYSTEM_TOKEN_EXISTS, 139 SYSTEM_TOKEN_NOT_EXISTS 140 }; 141 142 enum DeviceStatus { 143 DEVICE_STATUS_ENROLLED, 144 DEVICE_STATUS_NOT_ENROLLED 145 }; 146 147 enum UserAffiliation { 148 USER_AFFILIATION_ENROLLED_DOMAIN, 149 USER_AFFILIATION_UNRELATED 150 }; 151 152 struct Params { 153 Params(SystemToken system_token, 154 DeviceStatus device_status, 155 UserAffiliation user_affiliation) 156 : system_token_(system_token), 157 device_status_(device_status), 158 user_affiliation_(user_affiliation) {} 159 160 SystemToken system_token_; 161 DeviceStatus device_status_; 162 UserAffiliation user_affiliation_; 163 }; 164 165 class EnterprisePlatformKeysTest 166 : public ExtensionApiTest, 167 public ::testing::WithParamInterface<Params> { 168 public: 169 EnterprisePlatformKeysTest() {} 170 171 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 172 ExtensionApiTest::SetUpCommandLine(command_line); 173 174 // Enable the WebCrypto API. 175 command_line->AppendSwitch( 176 switches::kEnableExperimentalWebPlatformFeatures); 177 178 std::string user_email = "someuser (at) anydomain.com"; 179 180 // The command line flag kLoginUser determines the user's email and thus 181 // his affiliation to the domain that the device is enrolled to. 182 if (GetParam().user_affiliation_ == USER_AFFILIATION_ENROLLED_DOMAIN) 183 user_email = chromeos::login::kStubUser; 184 185 command_line->AppendSwitchASCII(chromeos::switches::kLoginUser, user_email); 186 } 187 188 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 189 ExtensionApiTest::SetUpInProcessBrowserTestFixture(); 190 191 if (GetParam().device_status_ == DEVICE_STATUS_ENROLLED) { 192 device_policy_test_helper_.device_policy()->policy_data().set_username( 193 chromeos::login::kStubUser); 194 195 device_policy_test_helper_.device_policy()->Build(); 196 device_policy_test_helper_.MarkAsEnterpriseOwned(); 197 } 198 199 EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_)) 200 .WillRepeatedly(testing::Return(true)); 201 policy_provider_.SetAutoRefresh(); 202 policy::BrowserPolicyConnector::SetPolicyProviderForTesting( 203 &policy_provider_); 204 } 205 206 virtual void SetUpOnMainThread() OVERRIDE { 207 if (GetParam().system_token_ == SYSTEM_TOKEN_EXISTS) { 208 base::RunLoop loop; 209 content::BrowserThread::PostTask( 210 content::BrowserThread::IO, 211 FROM_HERE, 212 base::Bind(&EnterprisePlatformKeysTest::SetUpTestSystemSlotOnIO, 213 base::Unretained(this), 214 browser()->profile()->GetResourceContext(), 215 loop.QuitClosure())); 216 loop.Run(); 217 } 218 219 ExtensionApiTest::SetUpOnMainThread(); 220 221 // Enable the URLRequestMock, which is required for force-installing the 222 // test extension through policy. 223 content::BrowserThread::PostTask( 224 content::BrowserThread::IO, 225 FROM_HERE, 226 base::Bind(chrome_browser_net::SetUrlRequestMocksEnabled, true)); 227 228 { 229 base::RunLoop loop; 230 GetNSSCertDatabaseForProfile( 231 browser()->profile(), 232 base::Bind(&EnterprisePlatformKeysTest::DidGetCertDatabase, 233 base::Unretained(this), 234 loop.QuitClosure())); 235 loop.Run(); 236 } 237 238 SetPolicy(); 239 } 240 241 virtual void TearDownOnMainThread() OVERRIDE { 242 ExtensionApiTest::TearDownOnMainThread(); 243 244 if (GetParam().system_token_ == SYSTEM_TOKEN_EXISTS) { 245 base::RunLoop loop; 246 content::BrowserThread::PostTask( 247 content::BrowserThread::IO, 248 FROM_HERE, 249 base::Bind(&EnterprisePlatformKeysTest::TearDownTestSystemSlotOnIO, 250 base::Unretained(this), 251 loop.QuitClosure())); 252 loop.Run(); 253 } 254 } 255 256 private: 257 void DidGetCertDatabase(const base::Closure& done_callback, 258 net::NSSCertDatabase* cert_db) { 259 // In order to use a prepared certificate, import a private key to the 260 // user's token for which the Javscript test will import the certificate. 261 ImportPrivateKeyPKCS8ToSlot(privateKeyPkcs8User, 262 arraysize(privateKeyPkcs8User), 263 cert_db->GetPrivateSlot().get()); 264 done_callback.Run(); 265 } 266 267 void SetUpTestSystemSlotOnIO(content::ResourceContext* context, 268 const base::Closure& done_callback) { 269 test_system_slot_.reset(new crypto::ScopedTestSystemNSSKeySlot()); 270 ASSERT_TRUE(test_system_slot_->ConstructedSuccessfully()); 271 272 // Import a private key to the system slot. The Javascript part of this 273 // test has a prepared certificate for this key. 274 ImportPrivateKeyPKCS8ToSlot(privateKeyPkcs8System, 275 arraysize(privateKeyPkcs8System), 276 test_system_slot_->slot()); 277 278 content::BrowserThread::PostTask( 279 content::BrowserThread::UI, FROM_HERE, done_callback); 280 } 281 282 void TearDownTestSystemSlotOnIO(const base::Closure& done_callback) { 283 test_system_slot_.reset(); 284 285 content::BrowserThread::PostTask( 286 content::BrowserThread::UI, FROM_HERE, done_callback); 287 } 288 289 void SetPolicy() { 290 // Extensions that are force-installed come from an update URL, which 291 // defaults to the webstore. Use a mock URL for this test with an update 292 // manifest that includes the crx file of the test extension. 293 base::FilePath update_manifest_path = 294 base::FilePath(kTestExtensionDir).Append(kUpdateManifestFileName); 295 GURL update_manifest_url( 296 net::URLRequestMockHTTPJob::GetMockUrl(update_manifest_path)); 297 298 scoped_ptr<base::ListValue> forcelist(new base::ListValue); 299 forcelist->AppendString(base::StringPrintf( 300 "%s;%s", kTestExtensionID, update_manifest_url.spec().c_str())); 301 302 policy::PolicyMap policy; 303 policy.Set(policy::key::kExtensionInstallForcelist, 304 policy::POLICY_LEVEL_MANDATORY, 305 policy::POLICY_SCOPE_MACHINE, 306 forcelist.release(), 307 NULL); 308 309 // Set the policy and wait until the extension is installed. 310 content::WindowedNotificationObserver observer( 311 extensions::NOTIFICATION_EXTENSION_WILL_BE_INSTALLED_DEPRECATED, 312 content::NotificationService::AllSources()); 313 policy_provider_.UpdateChromePolicy(policy); 314 observer.Wait(); 315 } 316 317 policy::DevicePolicyCrosTestHelper device_policy_test_helper_; 318 scoped_ptr<crypto::ScopedTestSystemNSSKeySlot> test_system_slot_; 319 policy::MockConfigurationPolicyProvider policy_provider_; 320 }; 321 322 } // namespace 323 324 IN_PROC_BROWSER_TEST_P(EnterprisePlatformKeysTest, Basic) { 325 // By default, the system token is disabled. 326 std::string system_token_availability = ""; 327 328 // Only if the system token exists, and the current user is of the same domain 329 // as the device is enrolled to, the system token is available to the 330 // extension. 331 if (GetParam().system_token_ == SYSTEM_TOKEN_EXISTS && 332 GetParam().device_status_ == DEVICE_STATUS_ENROLLED && 333 GetParam().user_affiliation_ == USER_AFFILIATION_ENROLLED_DOMAIN) { 334 system_token_availability = "systemTokenEnabled"; 335 } 336 337 ASSERT_TRUE(RunExtensionSubtest( 338 "", 339 base::StringPrintf("chrome-extension://%s/basic.html?%s", 340 kTestExtensionID, 341 system_token_availability.c_str()))) 342 << message_; 343 } 344 345 INSTANTIATE_TEST_CASE_P( 346 CheckSystemTokenAvailability, 347 EnterprisePlatformKeysTest, 348 ::testing::Values(Params(SYSTEM_TOKEN_EXISTS, 349 DEVICE_STATUS_ENROLLED, 350 USER_AFFILIATION_ENROLLED_DOMAIN), 351 Params(SYSTEM_TOKEN_EXISTS, 352 DEVICE_STATUS_ENROLLED, 353 USER_AFFILIATION_UNRELATED), 354 Params(SYSTEM_TOKEN_EXISTS, 355 DEVICE_STATUS_NOT_ENROLLED, 356 USER_AFFILIATION_UNRELATED), 357 Params(SYSTEM_TOKEN_NOT_EXISTS, 358 DEVICE_STATUS_ENROLLED, 359 USER_AFFILIATION_ENROLLED_DOMAIN))); 360 361 class EnterprisePlatformKeysTestNonPolicyInstalledExtension 362 : public EnterprisePlatformKeysTest {}; 363 364 // Ensure that extensions that are not pre-installed by policy throw an install 365 // warning if they request the enterprise.platformKeys permission in the 366 // manifest and that such extensions don't see the 367 // chrome.enterprise.platformKeys namespace. 368 IN_PROC_BROWSER_TEST_F(ExtensionApiTest, 369 EnterprisePlatformKeysIsRestrictedToPolicyExtension) { 370 ASSERT_TRUE(RunExtensionSubtest("enterprise_platform_keys", 371 "api_not_available.html", 372 kFlagIgnoreManifestWarnings)); 373 374 base::FilePath extension_path = 375 test_data_dir_.AppendASCII("enterprise_platform_keys"); 376 ExtensionService* service = 377 extensions::ExtensionSystem::Get(profile())->extension_service(); 378 const extensions::Extension* extension = 379 GetExtensionByPath(service->extensions(), extension_path); 380 ASSERT_FALSE(extension->install_warnings().empty()); 381 EXPECT_EQ( 382 "'enterprise.platformKeys' is not allowed for specified install " 383 "location.", 384 extension->install_warnings()[0].message); 385 } 386