File: two_qwac_unittest.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (565 lines) | stat: -rw-r--r-- 20,612 bytes parent folder | download | duplicates (3)
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
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert/two_qwac.h"

#include <stdint.h>

#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/values.h"
#include "crypto/hash.h"
#include "net/test/cert_builder.h"
#include "net/test/two_qwac_cert_binding_builder.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"

namespace net {
namespace {

TEST(ParseTlsCertificateBinding, MinimalValidBinding) {
  // Build a header that has the minimally required set of parameters
  TwoQwacCertBindingBuilder binding_builder;
  std::string jws = binding_builder.GetJWS();
  auto cert_binding = TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
}

TEST(ParseTlsCertificateBinding, MaximalValidBinding) {
  TwoQwacCertBindingBuilder binding_builder;
  // Set all of the optional fields in the header.
  binding_builder.SetHeaderOverrides(
      base::DictValue()
          .Set("kid", base::Value::Dict()
                          .Set("random key", "random value")
                          .Set("kids can have", "whatever they want"))
          .Set("x5t#S256", "base64urlhashA")
          .Set("iat", 12345)
          .Set("exp", 67.89)
          .Set("crit", base::ListValue().Append("sigD"))
          .Set("sigD",
               base::DictValue().Set("ctys", base::Value::List()
                                                 .Append("content-type1")
                                                 .Append("content-type2"))));
  std::string jws = binding_builder.GetJWS();

  auto cert_binding = TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
}

TEST(ParseTlsCertificateBinding, RS256ValidSigAlg) {
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetHeaderOverrides(base::DictValue().Set("alg", "RS256"));
  std::string jws = binding_builder.GetJWS();
  auto cert_binding = TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
  ASSERT_EQ(cert_binding->header().sig_alg, JwsSigAlg::kRsaPkcs1Sha256);
}

TEST(ParseTlsCertificateBinding, PS256ValidSigAlg) {
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetHeaderOverrides(base::DictValue().Set("alg", "PS256"));
  std::string jws = binding_builder.GetJWS();
  auto cert_binding = TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
  ASSERT_EQ(cert_binding->header().sig_alg, JwsSigAlg::kRsaPssSha256);
}

TEST(ParseTlsCertificateBinding, InvalidSigAlg) {
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetHeaderOverrides(base::DictValue().Set("alg", "RSA1_5"));
  std::string jws = binding_builder.GetJWS();
  EXPECT_FALSE(jws.empty());
  auto cert_binding = TwoQwacCertBinding::Parse(jws);
  ASSERT_FALSE(cert_binding.has_value());
}

// Test failure when the JWS header isn't a JSON object.
TEST(ParseTlsCertificateBinding, JwsHeaderNotObject) {
  std::string header = "[]";
  std::string jws;
  base::Base64UrlEncode(header, base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &jws);
  jws += "..";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

// Test failure when the JWS header isn't JSON.
TEST(ParseTlsCertificateBinding, JwsHeaderNotJson) {
  std::string header = "AAA";
  std::string jws;
  base::Base64UrlEncode(header, base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &jws);
  jws += "..";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

// Test failure when the JWS header isn't valid base64url.
TEST(ParseTlsCertificateBinding, JwsHeaderNotBase64) {
  // the header is encoded as "A", which is too short to be base64url.
  std::string jws = "A..";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

// Test failure when the JWS payload is non-empty.
TEST(ParseTlsCertificateBinding, JwsPayloadNonEmpty) {
  std::string header_b64 = TwoQwacCertBindingBuilder().GetHeader();
  // Make a JWS consisting of a valid header, a payload (base64url-encoded as
  // "AAAA") and an empty signature.
  std::string jws = header_b64 + ".AAAA.";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

// Test failure when the JWS signature is not valid base64url.
TEST(ParseTlsCertificateBinding, JwsSignatureNotBase64) {
  std::string header_b64 = TwoQwacCertBindingBuilder().GetHeader();
  std::string jws = header_b64 + "..A";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

// Test failure when the JWS consists of 2 components instead of 3.
TEST(ParseTlsCertificateBinding, JwsHas2Components) {
  std::string header_b64 = TwoQwacCertBindingBuilder().GetHeader();
  std::string jws = header_b64 + ".AAAA";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

// Test failure when the JWS consists of 4 components instead of 3.
TEST(ParseTlsCertificateBinding, JwsHas4Components) {
  std::string header_b64 = TwoQwacCertBindingBuilder().GetHeader();
  std::string jws = header_b64 + "..AAAA.AAAA";
  EXPECT_FALSE(TwoQwacCertBinding::Parse(jws).has_value());
}

TEST(ParseTlsCertificateBinding, InvalidFields) {
  const struct {
    std::string header_key;
    base::Value value;
  } kTests[] = {
      {
          // "alg" expects a string
          "alg",
          base::Value(1),
      },
      {
          // "alg" expects a supported signature algorithm from the IANA
          // registry. "none" is in the registry but we will never support it.
          "alg",
          base::Value("none"),
      },
      {
          // "cty" expects a string
          "cty",
          base::Value(1),
      },
      {
          // "cty" expects a specific value for its string
          "cty",
          base::Value("TLS-Certificate-Binding-v2"),
      },
      {
          // "x5t#S256" expects a string
          "x5t#S256",
          base::Value(1),
      },
      {
          // "x5c" expects a list
          "x5c",
          base::Value("wrong type"),
      },
      {
          // "x5c" expects strings in its list
          "x5c",
          base::Value(base::ListValue().Append(1)),
      },
      {
          // "x5c" expects base64 strings in its list. Test with a base64url
          // (but not regular base64) string.
          "x5c",
          base::Value(base::ListValue().Append("M-_A")),
      },
      {
          // "x5c" expects the base64 strings in its list to be valid X.509
          // certificates. This string is valid base64, but is a (very)
          // truncated X.509 certificate.
          "x5c",
          base::Value(base::ListValue().Append("MIID")),
      },
      {
          // "iat" expects an int (when used for 2-QWACs). "iat" more generally
          // (according to RFC 7519) can be a double, but we don't allow that,
          // so explicitly check that doubles are rejected.
          "iat",
          base::Value(1.0),
      },
      {
          // "exp" expects a numeric value
          "exp",
          base::Value("wrong type"),
      },
      {
          // "crit", if present, can only contain "sigD"
          "crit",
          base::Value(base::ListValue().Append("sigD").Append("x5c")),
      },
      {
          // "crit" expects a list
          "crit",
          base::Value("wrong type"),
      },
      {
          // "sigD" expects an object
          "sigD",
          base::Value(base::ListValue()),
      },
      {
          // The 2-QWAC TLS Certificate Binding JAdES profile only allows
          // specific fields in the JWS header, and "x5u" is not one of them.
          "x5u",
          base::Value("X.509 URL"),
      },
  };

  for (const auto& test : kTests) {
    SCOPED_TRACE(test.header_key);
    TwoQwacCertBindingBuilder binding_builder;
    binding_builder.SetHeaderOverrides(
        base::DictValue().Set(test.header_key, test.value.Clone()));
    std::string jws = binding_builder.GetJWS();
    auto cert_binding = TwoQwacCertBinding::Parse(jws);
    ASSERT_FALSE(cert_binding.has_value());
  }
}

TEST(ParseTlsCertificateBinding, SigDHeaderParam) {
  const struct {
    std::string name;
    base::RepeatingCallback<void(base::DictValue*)> header_func;
    bool valid;
  } kTests[] = {
      {
          "wrong mId",
          base::BindRepeating([](base::DictValue* sig_d) {
            sig_d->Set("mId", "http://uri.etsi.org/19182/ObjectIdByURI");
          }),
          false,
      },
      {
          "wrong mId type",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("mId", 1); }),
          false,
      },
      {
          "wrong pars type",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("pars", 1); }),
          false,
      },
      {
          "SHA-256 supported",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("hashM", "S256"); }),
          true,
      },
      {
          "SHA-384 supported",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("hashM", "S384"); }),
          true,
      },
      {
          "SHA-512 supported",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("hashM", "S512"); }),
          true,
      },
      {
          "Other hashM values not supported",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("hashM", "SHA1"); }),
          false,
      },
      {
          "wrong hashM type",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("hashM", 1); }),
          false,
      },
      {
          "wrong type in pars list",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "pars" and "hashV" must have the same length.
            sig_d->Set("pars", base::ListValue().Append(1));
            sig_d->Set("hashV", base::ListValue().Append("fakehash"));
          }),
          false,
      },
      {
          "disallowed base64 padding in hashV",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            // hashV list elements are base64url encoded with no padding
            sig_d->Set("hashV", base::ListValue().Append("fakehashAA=="));
          }),
          false,
      },
      {
          "bad base64url encoding in hashV",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            // a base64url input (with no padding) is malformed if its length
            // mod 4 is 1.
            sig_d->Set("hashV", base::ListValue().Append("fakehash1"));
          }),
          false,
      },
      {
          "wrong type in hashV list",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            sig_d->Set("hashV", base::ListValue().Append(1));
          }),
          false,
      },
      {
          "wrong hashV type",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("hashV", 1); }),
          false,
      },
      {
          "correct ctys type",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            sig_d->Set("hashV", base::ListValue().Append("fakehash"));
            sig_d->Set("ctys", base::ListValue().Append("content type"));
          }),
          true,
      },
      {
          "wrong ctys type",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("ctys", "wrong type"); }),
          false,
      },
      {
          "wrong type inside ctys list",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            sig_d->Set("hashV", base::ListValue().Append("fakehash"));
            sig_d->Set("ctys", base::ListValue().Append(1));
          }),
          false,
      },
      {
          "pars length mismatch",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL").Append("URL 2"));
            sig_d->Set("hashV", base::ListValue().Append("fakehash"));
            sig_d->Set("ctys", base::ListValue().Append("content type"));
          }),
          false,
      },
      {
          "hashV length mismatch",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            sig_d->Set("hashV",
                       base::ListValue().Append("fakehash").Append("hashfake"));
            sig_d->Set("ctys", base::ListValue().Append("content type"));
          }),
          false,
      },
      {
          "ctys length mismatch",
          base::BindRepeating([](base::DictValue* sig_d) {
            // "ctys" must have the same length as "pars" and "hashV".
            sig_d->Set("pars", base::ListValue().Append("URL"));
            sig_d->Set("hashV", base::ListValue().Append("fakehash"));
            sig_d->Set("ctys", base::ListValue()
                                   .Append("content type")
                                   .Append("content type"));
          }),
          false,
      },
      {
          "unknown member in sigD",
          base::BindRepeating(
              [](base::DictValue* sig_d) { sig_d->Set("spURI", "URL"); }),
          false,
      },
  };

  for (const auto& test : kTests) {
    SCOPED_TRACE(test.name);
    base::DictValue sig_d;
    test.header_func.Run(&sig_d);
    TwoQwacCertBindingBuilder binding_builder;
    binding_builder.SetHeaderOverrides(
        base::DictValue().Set("sigD", std::move(sig_d)));
    std::string jws = binding_builder.GetJWS();
    auto cert_binding = TwoQwacCertBinding::Parse(jws);
    EXPECT_EQ(cert_binding.has_value(), test.valid);
  }
}

TEST(VerifyTwoQwacCertBinding, ValidSignatureRS256) {
  TwoQwacCertBindingBuilder binding_builder;
  // Use RSA-PKCS1v1.5 for the TLS Certificate Binding.
  binding_builder.SetJwsSigAlg(JwsSigAlg::kRsaPkcs1Sha256);
  std::string jws = binding_builder.GetJWS();

  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
  // Check that the JWS header has "alg": "RS256"
  EXPECT_EQ(cert_binding->header().sig_alg, JwsSigAlg::kRsaPkcs1Sha256);
  EXPECT_TRUE(cert_binding->VerifySignature());
}

TEST(VerifyTwoQwacCertBinding, ValidSignaturePS256) {
  TwoQwacCertBindingBuilder binding_builder;
  // Use RSA-PSS for the TLS Certificate Binding.
  binding_builder.SetJwsSigAlg(JwsSigAlg::kRsaPssSha256);
  std::string jws = binding_builder.GetJWS();

  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
  // Check that the JWS header has "alg": "PS256"
  EXPECT_EQ(cert_binding->header().sig_alg, JwsSigAlg::kRsaPssSha256);
  EXPECT_TRUE(cert_binding->VerifySignature());
}

TEST(VerifyTwoQwacCertBinding, ValidSignatureES256) {
  TwoQwacCertBindingBuilder binding_builder;
  // Use ECDSA for the TLS Certificate Binding.
  binding_builder.SetJwsSigAlg(JwsSigAlg::kEcdsaP256Sha256);
  std::string jws = binding_builder.GetJWS();

  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
  // Check that the JWS header has "alg": "ES256"
  EXPECT_EQ(cert_binding->header().sig_alg, JwsSigAlg::kEcdsaP256Sha256);
  EXPECT_TRUE(cert_binding->VerifySignature());
}

TEST(VerifyTwoQwacCertBinding, InvalidEcdsaCurve) {
  TwoQwacCertBindingBuilder binding_builder;
  // Set "ES256" as the JWS signature algorithm.
  binding_builder.SetJwsSigAlg(JwsSigAlg::kEcdsaP256Sha256);
  // Set the leaf cert to use a P-384 key.
  bssl::UniquePtr<EC_KEY> ec_key(EC_KEY_new_by_curve_name(NID_secp384r1));
  ASSERT_TRUE(EC_KEY_generate_key(ec_key.get()));
  bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
  ASSERT_TRUE(EVP_PKEY_assign_EC_KEY(pkey.get(), ec_key.release()));
  binding_builder.GetLeafBuilder()->SetKey(std::move(pkey));

  std::string jws = binding_builder.GetJWS();

  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());
  // Check that the JWS header has "alg": "ES256"
  EXPECT_EQ(cert_binding->header().sig_alg, JwsSigAlg::kEcdsaP256Sha256);
  // Since the key uses the wrong curve, the signature verification should fail.
  EXPECT_FALSE(cert_binding->VerifySignature());
}

TEST(VerifyTwoQwacCertBinding, InvalidSignature) {
  TwoQwacCertBindingBuilder binding_builder;

  // Build a JWS with an invalid signature, and check that the signature
  // is invalid.
  std::optional<TwoQwacCertBinding> cert_binding_bad_sig =
      TwoQwacCertBinding::Parse(binding_builder.GetJWSWithInvalidSignature());
  ASSERT_TRUE(cert_binding_bad_sig.has_value());
  EXPECT_FALSE(cert_binding_bad_sig->VerifySignature());
}

TEST(TwoQwacCertBinding, BoundCertPresent) {
  auto [leaf, root] = net::CertBuilder::CreateSimpleChain2();
  std::string bound_cert = leaf->GetDER();
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({bound_cert});
  std::string jws = binding_builder.GetJWS();
  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());

  EXPECT_TRUE(cert_binding->BindsTlsCert(base::as_byte_span(bound_cert)));
}

TEST(TwoQwacCertBinding, MultipleBoundCerts) {
  auto [leaf1, root1] = net::CertBuilder::CreateSimpleChain2();
  std::string bound_cert1 = leaf1->GetDER();
  auto [leaf2, root2] = net::CertBuilder::CreateSimpleChain2();
  std::string bound_cert2 = leaf2->GetDER();
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({bound_cert1, bound_cert2});
  std::string jws = binding_builder.GetJWS();
  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());

  EXPECT_TRUE(cert_binding->BindsTlsCert(base::as_byte_span(bound_cert1)));
  EXPECT_TRUE(cert_binding->BindsTlsCert(base::as_byte_span(bound_cert2)));
}

TEST(TwoQwacCertBinding, UnboundCertNotFound) {
  auto [leaf, root] = net::CertBuilder::CreateSimpleChain2();
  std::string bound_cert = leaf->GetDER();
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({bound_cert});
  std::string jws = binding_builder.GetJWS();
  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());

  auto [leaf2, root2] = net::CertBuilder::CreateSimpleChain2();
  std::string unbound_cert = leaf2->GetDER();
  EXPECT_FALSE(cert_binding->BindsTlsCert(base::as_byte_span(unbound_cert)));
}

TEST(TwoQwacCertBinding, BoundCertPresentSha384) {
  auto [leaf, root] = net::CertBuilder::CreateSimpleChain2();
  std::string bound_cert = leaf->GetDER();
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({bound_cert});
  binding_builder.SetHashAlg(crypto::hash::kSha384);
  std::string jws = binding_builder.GetJWS();
  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());

  EXPECT_TRUE(cert_binding->BindsTlsCert(base::as_byte_span(bound_cert)));
}

TEST(TwoQwacCertBinding, BoundCertPresentSha512) {
  auto [leaf, root] = net::CertBuilder::CreateSimpleChain2();
  std::string bound_cert = leaf->GetDER();
  TwoQwacCertBindingBuilder binding_builder;
  binding_builder.SetBoundCerts({bound_cert});
  binding_builder.SetHashAlg(crypto::hash::kSha512);
  std::string jws = binding_builder.GetJWS();
  std::optional<TwoQwacCertBinding> cert_binding =
      TwoQwacCertBinding::Parse(jws);
  ASSERT_TRUE(cert_binding.has_value());

  EXPECT_TRUE(cert_binding->BindsTlsCert(base::as_byte_span(bound_cert)));
}

}  // namespace
}  // namespace net