Home | History | Annotate | Download | only in enterprise_platform_keys
      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