1 // Copyright 2024 The Chromium Authors
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 "net/device_bound_sessions/session_binding_utils.h"
6
7 #include <optional>
8 #include <string_view>
9
10 #include "base/base64url.h"
11 #include "base/json/json_reader.h"
12 #include "base/json/json_writer.h"
13 #include "base/strings/strcat.h"
14 #include "base/strings/string_split.h"
15 #include "base/time/time.h"
16 #include "base/value_iterators.h"
17 #include "base/values.h"
18 #include "crypto/signature_verifier.h"
19 #include "net/device_bound_sessions/test_support.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "url/gurl.h"
22
23 namespace net::device_bound_sessions {
24
25 namespace {
26
Base64UrlEncodedJsonToValue(std::string_view input)27 base::Value Base64UrlEncodedJsonToValue(std::string_view input) {
28 std::string json;
29 EXPECT_TRUE(base::Base64UrlDecode(
30 input, base::Base64UrlDecodePolicy::DISALLOW_PADDING, &json));
31 std::optional<base::Value> result = base::JSONReader::Read(json);
32 EXPECT_TRUE(result.has_value());
33 return std::move(*result);
34 }
35
36 } // namespace
37
TEST(SessionBindingUtilsTest,CreateKeyRegistrationHeaderAndPayload)38 TEST(SessionBindingUtilsTest, CreateKeyRegistrationHeaderAndPayload) {
39 auto [spki, jwk] = GetRS256SpkiAndJwkForTesting();
40
41 std::optional<std::string> result = CreateKeyRegistrationHeaderAndPayload(
42 "test_challenge", GURL("https://accounts.example.test/RegisterKey"),
43 crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256, spki,
44 base::Time::UnixEpoch() + base::Days(200) + base::Milliseconds(123), "");
45 ASSERT_TRUE(result.has_value());
46
47 std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
48 *result, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
49 ASSERT_EQ(header_and_payload.size(), 2U);
50 base::Value actual_header =
51 Base64UrlEncodedJsonToValue(header_and_payload[0]);
52 base::Value actual_payload =
53 Base64UrlEncodedJsonToValue(header_and_payload[1]);
54
55 base::Value::Dict expected_header =
56 base::Value::Dict().Set("alg", "RS256").Set("typ", "jwt");
57 base::Value::Dict expected_payload =
58 base::Value::Dict()
59 .Set("aud", "https://accounts.example.test/RegisterKey")
60 .Set("jti", "test_challenge")
61 .Set("iat", 17280000)
62 .Set("key", base::JSONReader::Read(jwk).value())
63 .Set("authorization", "");
64
65 EXPECT_EQ(actual_header, expected_header);
66 EXPECT_EQ(actual_payload, expected_payload);
67 }
68
TEST(SessionBindingUtilsTest,CreateKeyRegistrationHeaderAndPayloadWithNullAuth)69 TEST(SessionBindingUtilsTest,
70 CreateKeyRegistrationHeaderAndPayloadWithNullAuth) {
71 auto [spki, jwk] = GetRS256SpkiAndJwkForTesting();
72
73 std::optional<std::string> result = CreateKeyRegistrationHeaderAndPayload(
74 "test_challenge", GURL("https://accounts.example.test/RegisterKey"),
75 crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256, spki,
76 base::Time::UnixEpoch() + base::Days(200) + base::Milliseconds(123),
77 /*authorization=*/std::nullopt);
78 ASSERT_TRUE(result.has_value());
79
80 std::vector<std::string_view> header_and_payload = base::SplitStringPiece(
81 *result, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
82 ASSERT_EQ(header_and_payload.size(), 2U);
83 base::Value actual_header =
84 Base64UrlEncodedJsonToValue(header_and_payload[0]);
85 base::Value actual_payload =
86 Base64UrlEncodedJsonToValue(header_and_payload[1]);
87
88 base::Value::Dict expected_header =
89 base::Value::Dict().Set("alg", "RS256").Set("typ", "jwt");
90 base::Value::Dict expected_payload =
91 base::Value::Dict()
92 .Set("aud", "https://accounts.example.test/RegisterKey")
93 .Set("jti", "test_challenge")
94 .Set("iat", 17280000)
95 .Set("key", base::JSONReader::Read(jwk).value());
96
97 EXPECT_EQ(actual_header, expected_header);
98 EXPECT_EQ(actual_payload, expected_payload);
99 }
100
TEST(SessionBindingUtilsTest,AppendSignatureToHeaderAndPayload)101 TEST(SessionBindingUtilsTest, AppendSignatureToHeaderAndPayload) {
102 std::optional<std::string> result = AppendSignatureToHeaderAndPayload(
103 "abc.efg",
104 crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256,
105 std::vector<uint8_t>({1, 2, 3}));
106 EXPECT_EQ(result, "abc.efg.AQID");
107 }
108
TEST(SessionBindingUtilsTest,AppendSignatureToHeaderAndPayloadValidECDSASignature)109 TEST(SessionBindingUtilsTest,
110 AppendSignatureToHeaderAndPayloadValidECDSASignature) {
111 const std::vector<uint8_t> kDerSignature = {
112 0x30, 0x45, 0x02, 0x20, 0x74, 0xa0, 0x6f, 0x6b, 0x2b, 0x0e, 0x82, 0x0e,
113 0x03, 0x3b, 0x6e, 0x98, 0xfc, 0x89, 0x9c, 0xf3, 0x30, 0xb5, 0x56, 0xd3,
114 0x29, 0x89, 0xb5, 0x82, 0x33, 0x5f, 0x9d, 0x97, 0xfb, 0x65, 0x64, 0x90,
115 0x02, 0x21, 0x00, 0xbc, 0xb5, 0xee, 0x42, 0xe2, 0x5a, 0x87, 0xae, 0x21,
116 0x18, 0xda, 0x7e, 0x68, 0x65, 0x30, 0xbe, 0xe5, 0x69, 0x3d, 0xc5, 0x5f,
117 0xd5, 0x62, 0x45, 0x3e, 0x8d, 0x0b, 0x05, 0x1a, 0x33, 0x79, 0x8d};
118 constexpr std::string_view kRawSignatureBase64UrlEncoded =
119 "dKBvaysOgg4DO26Y_Imc8zC1VtMpibWCM1-dl_tlZJC8te5C4lqHriEY2n5oZTC-5Wk9xV_"
120 "VYkU-jQsFGjN5jQ";
121
122 std::optional<std::string> result = AppendSignatureToHeaderAndPayload(
123 "abc.efg", crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256,
124 kDerSignature);
125 EXPECT_EQ(result, base::StrCat({"abc.efg.", kRawSignatureBase64UrlEncoded}));
126 }
127
TEST(SessionBindingUtilsTest,AppendSignatureToHeaderAndPayloadInvalidECDSASignature)128 TEST(SessionBindingUtilsTest,
129 AppendSignatureToHeaderAndPayloadInvalidECDSASignature) {
130 std::optional<std::string> result = AppendSignatureToHeaderAndPayload(
131 "abc.efg", crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256,
132 std::vector<uint8_t>({1, 2, 3}));
133 EXPECT_EQ(result, std::nullopt);
134 }
135
136 } // namespace net::device_bound_sessions
137