summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api_unittest.cc
blob: 092a999f02e68fcd4ba1d63aa16ca6c1b8a08422 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h"

#include <algorithm>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/test/scoped_feature_list.h"
#include "chrome/browser/extensions/extension_api_unittest.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "chrome/browser/permissions/permission_request_manager.h"
#include "chrome/browser/sessions/session_tab_helper.h"
#include "chrome/browser/ui/permission_bubble/mock_permission_prompt_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "crypto/sha2.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/browser/extension_function_dispatcher.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace extensions {

namespace {

bool GetSingleBooleanResult(ExtensionFunction* function, bool* result) {
  const base::ListValue* result_list = function->GetResultList();
  if (!result_list) {
    ADD_FAILURE() << "Function has no result list.";
    return false;
  }

  if (result_list->GetSize() != 1u) {
    ADD_FAILURE() << "Invalid number of results.";
    return false;
  }

  if (!result_list->GetBoolean(0, result)) {
    ADD_FAILURE() << "Result is not boolean.";
    return false;
  }

  return true;
}

class CryptoTokenPrivateApiTest : public extensions::ExtensionApiUnittest {
 public:
  CryptoTokenPrivateApiTest() {}
  ~CryptoTokenPrivateApiTest() override {}

 protected:
  bool GetCanOriginAssertAppIdResult(const std::string& origin,
                                     const std::string& app_id,
                                     bool* out_result) {
    auto function = base::MakeRefCounted<
        api::CryptotokenPrivateCanOriginAssertAppIdFunction>();
    function->set_has_callback(true);

    auto args = std::make_unique<base::ListValue>();
    args->AppendString(origin);
    args->AppendString(app_id);

    if (!extension_function_test_utils::RunFunction(
            function.get(), std::move(args), browser(), api_test_utils::NONE)) {
      return false;
    }

    return GetSingleBooleanResult(function.get(), out_result);
  }

  bool GetAppIdHashInEnterpriseContext(const std::string& app_id,
                                       bool* out_result) {
    auto function = base::MakeRefCounted<
        api::CryptotokenPrivateIsAppIdHashInEnterpriseContextFunction>();
    function->set_has_callback(true);

    auto args = std::make_unique<base::Value>(base::Value::Type::LIST);
    args->Append(
        base::Value(base::Value::BlobStorage(app_id.begin(), app_id.end())));

    if (!extension_function_test_utils::RunFunction(
            function.get(), base::ListValue::From(std::move(args)), browser(),
            api_test_utils::NONE)) {
      return false;
    }

    return GetSingleBooleanResult(function.get(), out_result);
  }
};

TEST_F(CryptoTokenPrivateApiTest, CanOriginAssertAppId) {
  std::string origin1("https://www.example.com");

  bool result;
  ASSERT_TRUE(GetCanOriginAssertAppIdResult(origin1, origin1, &result));
  EXPECT_TRUE(result);

  std::string same_origin_appid("https://www.example.com/appId");
  ASSERT_TRUE(
      GetCanOriginAssertAppIdResult(origin1, same_origin_appid, &result));
  EXPECT_TRUE(result);
  std::string same_etld_plus_one_appid("https://appid.example.com/appId");
  ASSERT_TRUE(GetCanOriginAssertAppIdResult(origin1, same_etld_plus_one_appid,
                                            &result));
  EXPECT_TRUE(result);
  std::string different_etld_plus_one_appid("https://www.different.com/appId");
  ASSERT_TRUE(GetCanOriginAssertAppIdResult(
      origin1, different_etld_plus_one_appid, &result));
  EXPECT_FALSE(result);

  // For legacy purposes, google.com is allowed to use certain appIds hosted at
  // gstatic.com.
  // TODO(juanlang): remove once the legacy constraints are removed.
  std::string google_origin("https://accounts.google.com");
  std::string gstatic_appid("https://www.gstatic.com/securitykey/origins.json");
  ASSERT_TRUE(
      GetCanOriginAssertAppIdResult(google_origin, gstatic_appid, &result));
  EXPECT_TRUE(result);
  // Not all gstatic urls are allowed, just those specifically whitelisted.
  std::string gstatic_otherurl("https://www.gstatic.com/foobar");
  ASSERT_TRUE(
      GetCanOriginAssertAppIdResult(google_origin, gstatic_otherurl, &result));
  EXPECT_FALSE(result);
}

TEST_F(CryptoTokenPrivateApiTest, IsAppIdHashInEnterpriseContext) {
  const std::string example_com("https://example.com/");
  const std::string example_com_hash(crypto::SHA256HashString(example_com));
  const std::string rp_id_hash(crypto::SHA256HashString("example.com"));
  const std::string foo_com_hash(crypto::SHA256HashString("https://foo.com/"));

  bool result;
  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(example_com_hash, &result));
  EXPECT_FALSE(result);
  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(foo_com_hash, &result));
  EXPECT_FALSE(result);
  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(rp_id_hash, &result));
  EXPECT_FALSE(result);

  base::Value::ListStorage permitted_list;
  permitted_list.emplace_back(example_com);
  profile()->GetPrefs()->Set(prefs::kSecurityKeyPermitAttestation,
                             base::Value(permitted_list));

  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(example_com_hash, &result));
  EXPECT_TRUE(result);
  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(foo_com_hash, &result));
  EXPECT_FALSE(result);
  ASSERT_TRUE(GetAppIdHashInEnterpriseContext(rp_id_hash, &result));
  EXPECT_FALSE(result);
}

}  // namespace

class CryptoTokenPermissionTest : public ExtensionApiUnittest {
 public:
  CryptoTokenPermissionTest() = default;
  ~CryptoTokenPermissionTest() override = default;

  void SetUp() override {
    feature_list_.InitWithFeatures({features::kSecurityKeyAttestationPrompt},
                                   {});

    ExtensionApiUnittest::SetUp();
    const GURL url("http://example.com");
    AddTab(browser(), url);

    content::WebContents* web_contents =
        browser()->tab_strip_model()->GetWebContentsAt(0);
    tab_id_ = SessionTabHelper::IdForTab(web_contents).id();
    PermissionRequestManager::CreateForWebContents(web_contents);
    prompt_factory_ = std::make_unique<MockPermissionPromptFactory>(
        PermissionRequestManager::FromWebContents(web_contents));
  }

  void TearDown() override {
    prompt_factory_.reset();
    ExtensionApiUnittest::TearDown();
  }

 protected:
  // CanAppIdGetAttestation calls the cryptotoken private API of the same name
  // for |app_id| and sets |*out_result| to the result. If |bubble_action| is
  // not |NONE| then it waits for the permissions prompt to be shown and
  // performs the given action. Otherwise, the call is expected to be
  // synchronous.
  bool CanAppIdGetAttestation(
      const std::string& app_id,
      PermissionRequestManager::AutoResponseType bubble_action,
      bool* out_result) {
    if (bubble_action != PermissionRequestManager::NONE) {
      prompt_factory_->set_response_type(bubble_action);
      prompt_factory_->DocumentOnLoadCompletedInMainFrame();
    }

    auto function = base::MakeRefCounted<
        api::CryptotokenPrivateCanAppIdGetAttestationFunction>();
    function->set_has_callback(true);

    base::Value::DictStorage dict;
    dict.emplace("appId", std::make_unique<base::Value>(app_id));
    dict.emplace("tabId", std::make_unique<base::Value>(tab_id_));
    dict.emplace("origin", std::make_unique<base::Value>(app_id));
    auto args = std::make_unique<base::Value>(base::Value::Type::LIST);
    args->Append(base::Value(std::move(dict)));
    auto args_list = base::ListValue::From(std::move(args));

    extension_function_test_utils::RunFunction(
        function.get(), std::move(args_list), browser(), api_test_utils::NONE);

    return GetSingleBooleanResult(function.get(), out_result);
  }

 private:
  base::test::ScopedFeatureList feature_list_;
  int tab_id_ = -1;
  std::unique_ptr<MockPermissionPromptFactory> prompt_factory_;

  DISALLOW_COPY_AND_ASSIGN(CryptoTokenPermissionTest);
};

TEST_F(CryptoTokenPermissionTest, Prompt) {
  const std::vector<PermissionRequestManager::AutoResponseType> actions = {
      PermissionRequestManager::ACCEPT_ALL, PermissionRequestManager::DENY_ALL,
      PermissionRequestManager::DISMISS,
  };

  for (const auto& action : actions) {
    SCOPED_TRACE(action);

    bool result = false;
    ASSERT_TRUE(CanAppIdGetAttestation("https://test.com", action, &result));
    // The result should only be positive if the user accepted the permissions
    // prompt.
    EXPECT_EQ(action == PermissionRequestManager::ACCEPT_ALL, result);
  }
}

TEST_F(CryptoTokenPermissionTest, PolicyOverridesPrompt) {
  const std::string example_com("https://example.com");
  base::Value::ListStorage permitted_list;
  permitted_list.emplace_back(example_com);
  profile()->GetPrefs()->Set(prefs::kSecurityKeyPermitAttestation,
                             base::Value(permitted_list));

  // If an appId is configured by enterprise policy then attestation requests
  // should be permitted without showing a prompt.
  bool result = false;
  ASSERT_TRUE(CanAppIdGetAttestation(example_com,
                                     PermissionRequestManager::NONE, &result));
  EXPECT_TRUE(result);
}

}  // namespace extensions