// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/webid/delegation/sd_jwt.h"

#include "base/base64.h"
#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/values.h"
#include "content/browser/webid/delegation/jwt_signer.h"
#include "crypto/keypair.h"
#include "crypto/random.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"

namespace content::sdjwt {

using ::testing::NiceMock;

class SdJwtTest : public testing::Test {
 protected:
  SdJwtTest() = default;
  ~SdJwtTest() override = default;

  void SetUp() override {}

  void TearDown() override {}
};

TEST_F(SdJwtTest, JwkParsing) {
  // Example from
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#appendix-A.5
  std::string json =
      R"({
      "kty": "EC",
      "crv": "P-256",
      "x": "b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ",
      "y": "Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8"
    })";

  auto jwk = Jwk::From(
      *base::JSONReader::ReadDict(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(jwk);

  EXPECT_EQ(jwk->kty, "EC");
  EXPECT_EQ(jwk->crv, "P-256");
  EXPECT_EQ(jwk->x, "b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ");
  EXPECT_EQ(jwk->y, "Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8");
}

TEST_F(SdJwtTest, JWkSerializing) {
  // Example from
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#appendix-A.5

  Jwk jwk;

  jwk.kty = "EC";
  jwk.crv = "P-256";
  jwk.x = "b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ";
  jwk.y = "Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8";

  std::string json =
      "{"
      "\"crv\":\"P-256\","
      "\"kty\":\"EC\","
      "\"x\":\"b28d4MwZMjw8-00CG4xfnn9SLMVMM19SlqZpVb_uNtQ\","
      "\"y\":\"Xv5zWwuoaTgdS6hV43yI6gBwTnjukmFQQnJ_kCxzqk8\""
      "}";
  EXPECT_STREQ(jwk.Serialize()->c_str(), json.c_str());
}

TEST_F(SdJwtTest, JwkParsingAndSerializingRs256) {
  Jwk jwk;
  jwk.kty = "RSA";
  jwk.alg = "RS256";
  jwk.n = "n-value";
  jwk.e = "e-value";

  auto serialized = jwk.Serialize();
  ASSERT_TRUE(serialized);

  auto parsed = Jwk::From(*base::JSONReader::ReadDict(
      *serialized, base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  ASSERT_TRUE(parsed);
  EXPECT_EQ(parsed->kty, "RSA");
  EXPECT_EQ(parsed->alg, "RS256");
  EXPECT_EQ(parsed->n, "n-value");
  EXPECT_EQ(parsed->e, "e-value");
}

TEST_F(SdJwtTest, JwkParsingAndSerializingEdDsa) {
  Jwk jwk;
  jwk.kty = "OKP";
  jwk.alg = "EdDSA";
  jwk.crv = "Ed25519";
  jwk.x = "x-value";

  auto serialized = jwk.Serialize();
  ASSERT_TRUE(serialized);

  auto parsed = Jwk::From(*base::JSONReader::ReadDict(
      *serialized, base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  ASSERT_TRUE(parsed);
  EXPECT_EQ(parsed->kty, "OKP");
  EXPECT_EQ(parsed->alg, "EdDSA");
  EXPECT_EQ(parsed->crv, "Ed25519");
  EXPECT_EQ(parsed->x, "x-value");
}

TEST_F(SdJwtTest, DisclosureParsing) {
  // Example from
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-4.2.1
  std::string json = R"(["_26bc4LT-ac6q2KI6cBW5es", "family_name", "Möbius"])";

  auto disclosure = Disclosure::From(
      base::JSONReader::Read(json, base::JSON_PARSE_CHROMIUM_EXTENSIONS)
          ->GetList());
  EXPECT_TRUE(disclosure);

  EXPECT_EQ(disclosure->salt, Base64String("_26bc4LT-ac6q2KI6cBW5es"));
  EXPECT_EQ(disclosure->name, "family_name");
  EXPECT_EQ(disclosure->value, "Möbius");
}

TEST_F(SdJwtTest, DisclosureSerialization) {
  // Example from
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-4.2.1

  Disclosure disclosure;
  disclosure.salt = Base64String("_26bc4LT-ac6q2KI6cBW5es");
  disclosure.name = "family_name";
  disclosure.value = "Möbius";

  Base64String base64 = disclosure.Serialize();

  // This value is different from what's in the spec, but that's because
  // our JSON serialization strips whitespaces between array elements
  // which causes the base64 encoding to not match.
  // https://github.com/openid/OpenID4VP/issues/325
  //
  // This base64 value decodes to:
  //
  // (["_26bc4LT-ac6q2KI6cBW5es","family_name","Möbius"])
  std::string expected =
      "WyJfMjZiYzRMVC1hYzZxMktJNmNCVzVlcyIsImZhbWlseV9uYW1lIiwiTcO2Yml1cyJd";

  EXPECT_STREQ(base64->c_str(), expected.c_str());
}

TEST_F(SdJwtTest, JwtParsing) {
  // Jwt's top level structure is a header/payload/signature tuple
  // separate by ".".
  std::string jwt = "aGVhZGVy.cGF5bG9hZA.signature";

  auto token = Jwt::From(*Jwt::Parse(jwt));
  EXPECT_TRUE(token);

  EXPECT_EQ(token->header, JSONString("header"));
  EXPECT_EQ(token->payload, JSONString("payload"));
  EXPECT_EQ(token->signature, Base64String("signature"));
}

TEST_F(SdJwtTest, JwtParsingInvalid) {
  EXPECT_FALSE(Jwt::Parse(""));
  EXPECT_FALSE(Jwt::Parse("YQ"));
  EXPECT_FALSE(Jwt::Parse("."));
  EXPECT_FALSE(Jwt::Parse("YQ."));
  EXPECT_FALSE(Jwt::Parse(".YQ"));
  EXPECT_FALSE(Jwt::Parse("YWE."));
  EXPECT_FALSE(Jwt::Parse(".YWE"));

  EXPECT_FALSE(Jwt::Parse(".."));
  EXPECT_FALSE(Jwt::Parse("YQ.YWE."));
  EXPECT_FALSE(Jwt::Parse("YQ..YWE"));
  EXPECT_FALSE(Jwt::Parse(".YQ.YWE"));
}

TEST_F(SdJwtTest, JwtSerializing) {
  Jwt token;
  token.header = JSONString("header");
  token.payload = JSONString("payload");
  token.signature = Base64String("signature");

  // Jwt's top level structure:
  // Base64UrlEncode(header) . Base64UrlEncode(payload)  . signature
  EXPECT_EQ(token.Serialize(), JSONString("aGVhZGVy.cGF5bG9hZA.signature"));
}

TEST_F(SdJwtTest, JwtVerification) {
  auto key = crypto::keypair::PrivateKey::GenerateEd25519();
  auto pub_bytes =
      crypto::keypair::PublicKey::FromPrivateKey(key).ToEd25519PublicKey();

  sdjwt::Jwk jwk;
  jwk.kty = "OKP";
  jwk.crv = "Ed25519";
  base::Base64UrlEncode(pub_bytes, base::Base64UrlEncodePolicy::OMIT_PADDING,
                        &jwk.x);

  Jwt token;
  token.header = JSONString(R"({"alg":"EdDSA","kid":"test_kid","typ":"JWT"})");
  token.payload = JSONString(R"({"iss":"issuer","exp":1234567890})");

  ASSERT_TRUE(token.Sign(sdjwt::CreateJwtSigner(std::move(key))));

  sdjwt::Header header;
  header.alg = "EdDSA";
  header.typ = "JWT";
  EXPECT_TRUE(token.Verify(sdjwt::CreateJwtVerifier(jwk, header)));
}

TEST_F(SdJwtTest, HeaderParsingAndSerializing) {
  Header header;
  header.alg = "foo";
  header.typ = "bar";

  EXPECT_EQ(header.ToJson(), JSONString(R"({"alg":"foo","typ":"bar"})"));
  // The serializion of the header is a base64 encoding of the JSON.
  EXPECT_EQ(header.Serialize(),
            Base64String("eyJhbGciOiJmb28iLCJ0eXAiOiJiYXIifQ"));

  // Test that we can go back from base64 to value.
  auto parsed = Header::From(*base::JSONReader::ReadDict(
      R"({"alg":"foo","typ":"bar"})", base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(parsed);
  EXPECT_EQ(parsed->alg, "foo");
  EXPECT_EQ(parsed->typ, "bar");
}

TEST_F(SdJwtTest, PayloadParsingAndSerializing) {
  Payload payload;
  payload.sub = "foo";

  EXPECT_EQ(payload.ToJson(), JSONString(R"({"sub":"foo"})"));
  // The serializion of the header is a base64 encoding of the JSON.
  EXPECT_EQ(payload.Serialize(), Base64String("eyJzdWIiOiJmb28ifQ"));

  // Test that we can go back from base64 to value.
  auto parsed = Payload::From(*base::JSONReader::ReadDict(
      R"({"sub":"foo"})", base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(parsed);
  EXPECT_EQ(parsed->sub, "foo");
}

TEST_F(SdJwtTest, JwtParsingRFC) {
  // Example from:
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-5.1
  std::string jwt =
      "eyJhbGciOiAiRVMyNTYiLCAidHlwIjogImV4YW1wbGUrc2Qtand0In0.eyJfc2QiOiBb"
      "IkNyUWU3UzVrcUJBSHQtbk1ZWGdjNmJkdDJTSDVhVFkxc1VfTS1QZ2tqUEkiLCAiSnpZ"
      "akg0c3ZsaUgwUjNQeUVNZmVadTZKdDY5dTVxZWhabzdGN0VQWWxTRSIsICJQb3JGYnBL"
      "dVZ1Nnh5bUphZ3ZrRnNGWEFiUm9jMkpHbEFVQTJCQTRvN2NJIiwgIlRHZjRvTGJnd2Q1"
      "SlFhSHlLVlFaVTlVZEdFMHc1cnREc3JaemZVYW9tTG8iLCAiWFFfM2tQS3QxWHlYN0tB"
      "TmtxVlI2eVoyVmE1TnJQSXZQWWJ5TXZSS0JNTSIsICJYekZyendzY002R242Q0pEYzZ2"
      "Vks4QmtNbmZHOHZPU0tmcFBJWmRBZmRFIiwgImdiT3NJNEVkcTJ4Mkt3LXc1d1BFemFr"
      "b2I5aFYxY1JEMEFUTjNvUUw5Sk0iLCAianN1OXlWdWx3UVFsaEZsTV8zSmx6TWFTRnpn"
      "bGhRRzBEcGZheVF3TFVLNCJdLCAiaXNzIjogImh0dHBzOi8vaXNzdWVyLmV4YW1wbGUu"
      "Y29tIiwgImlhdCI6IDE2ODMwMDAwMDAsICJleHAiOiAxODgzMDAwMDAwLCAic3ViIjog"
      "InVzZXJfNDIiLCAibmF0aW9uYWxpdGllcyI6IFt7Ii4uLiI6ICJwRm5kamtaX1ZDem15"
      "VGE2VWpsWm8zZGgta284YUlLUWM5RGxHemhhVllvIn0sIHsiLi4uIjogIjdDZjZKa1B1"
      "ZHJ5M2xjYndIZ2VaOGtoQXYxVTFPU2xlclAwVmtCSnJXWjAifV0sICJfc2RfYWxnIjog"
      "InNoYS0yNTYiLCAiY25mIjogeyJqd2siOiB7Imt0eSI6ICJFQyIsICJjcnYiOiAiUC0y"
      "NTYiLCAieCI6ICJUQ0FFUjE5WnZ1M09IRjRqNFc0dmZTVm9ISVAxSUxpbERsczd2Q2VH"
      "ZW1jIiwgInkiOiAiWnhqaVdXYlpNUUdIVldLVlE0aGJTSWlyc1ZmdWVjQ0U2dDRqVDlG"
      "MkhaUSJ9fX0.oQ0UNJB1E1agYouB1yfGXfYLyWueHhfMFuicSV-n_GLXHtX0XK99sfDD"
      "ERiWKukCUzadGTT4QbCwXe6JvVmZWw";

  auto token = Jwt::From(*Jwt::Parse(jwt));
  EXPECT_TRUE(token);

  EXPECT_EQ(token->header,
            JSONString(R"({"alg": "ES256", "typ": "example+sd-jwt"})"));
  EXPECT_EQ(token->signature,
            Base64String("oQ0UNJB1E1agYouB1yfGXfYLyWueHhfMFuicSV-n_"
                         "GLXHtX0XK99sfDDERiWKukCUzadGTT4QbCwXe6JvVmZWw"));

  std::string expected =
      R"({)"
      R"("_sd": [)"
      R"("CrQe7S5kqBAHt-nMYXgc6bdt2SH5aTY1sU_M-PgkjPI", )"
      R"("JzYjH4svliH0R3PyEMfeZu6Jt69u5qehZo7F7EPYlSE", )"
      R"("PorFbpKuVu6xymJagvkFsFXAbRoc2JGlAUA2BA4o7cI", )"
      R"("TGf4oLbgwd5JQaHyKVQZU9UdGE0w5rtDsrZzfUaomLo", )"
      R"("XQ_3kPKt1XyX7KANkqVR6yZ2Va5NrPIvPYbyMvRKBMM", )"
      R"("XzFrzwscM6Gn6CJDc6vVK8BkMnfG8vOSKfpPIZdAfdE", )"
      R"("gbOsI4Edq2x2Kw-w5wPEzakob9hV1cRD0ATN3oQL9JM", )"
      R"("jsu9yVulwQQlhFlM_3JlzMaSFzglhQG0DpfayQwLUK4")"
      R"(], )"
      R"("iss": "https://issuer.example.com", )"
      R"("iat": 1683000000, )"
      R"("exp": 1883000000, )"
      R"("sub": "user_42", )"
      R"("nationalities": [)"
      R"({"...": "pFndjkZ_VCzmyTa6UjlZo3dh-ko8aIKQc9DlGzhaVYo"}, )"
      R"({"...": "7Cf6JkPudry3lcbwHgeZ8khAv1U1OSlerP0VkBJrWZ0"}], )"
      R"("_sd_alg": "sha-256", )"
      R"("cnf": {"jwk": {"kty": "EC", "crv": "P-256", )"
      R"("x": "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc", )"
      R"("y": "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ"}})"
      R"(})";

  EXPECT_STREQ(token->payload.value().c_str(), expected.c_str());

  auto header = Header::From(*base::JSONReader::ReadDict(
      token->header.value(), base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(header);

  EXPECT_EQ(header->typ, "example+sd-jwt");
  EXPECT_EQ(header->alg, "ES256");

  auto payload = Payload::From(*base::JSONReader::ReadDict(
      token->payload.value(), base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(payload);

  EXPECT_EQ(payload->iss, "https://issuer.example.com");
  EXPECT_EQ(payload->sub, "user_42");
  EXPECT_EQ(payload->iat, base::Time::FromTimeT(1683000000));
  EXPECT_EQ(payload->_sd_alg, "sha-256");

  EXPECT_TRUE(payload->cnf);
  EXPECT_EQ(payload->cnf->jwk.kty, "EC");
  EXPECT_EQ(payload->cnf->jwk.crv, "P-256");
  EXPECT_EQ(payload->cnf->jwk.x, "TCAER19Zvu3OHF4j4W4vfSVoHIP1ILilDls7vCeGemc");
  EXPECT_EQ(payload->cnf->jwk.y, "ZxjiWWbZMQGHVWKVQ4hbSIirsVfuecCE6t4jT9F2HZQ");
}

TEST_F(SdJwtTest, SdJwtParsingAndSerializing) {
  // SdJwt's top level structure is a jwt followed by ~ disclosures.
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-4
  std::string jwt =
      "aGVhZGVy.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~";

  auto token = SdJwt::From(*SdJwt::Parse(jwt));
  EXPECT_TRUE(token);

  EXPECT_EQ(token->jwt.header, JSONString("header"));
  EXPECT_EQ(token->jwt.payload, JSONString("payload"));
  EXPECT_EQ(token->jwt.signature, Base64String("signature"));
  EXPECT_EQ(token->disclosures.size(), 2ul);
  EXPECT_EQ(token->disclosures[0], JSONString("disclosure1"));
  EXPECT_EQ(token->disclosures[1], JSONString("disclosure2"));

  // Asserts that we can serialize the token again.
  token->jwt.header = JSONString("new-header");
  EXPECT_EQ(
      token->Serialize(),
      "bmV3LWhlYWRlcg.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~");
}

TEST_F(SdJwtTest, SdJwtParsingCornerCases) {
  // Valid: no disclosures
  EXPECT_TRUE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature~"));

  // Invalid JWT
  EXPECT_FALSE(SdJwt::Parse("~"));
  EXPECT_FALSE(SdJwt::Parse(".~"));
  EXPECT_FALSE(SdJwt::Parse("..~"));
  EXPECT_FALSE(SdJwt::Parse(".cGF5bG9hZA.signature~"));
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy..signature~"));
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.~"));
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy..signature~"));
  EXPECT_FALSE(SdJwt::Parse(".cGF5bG9hZA.signature~"));

  // Invalid: needs trailing ~
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature"));
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE"));
  EXPECT_FALSE(SdJwt::Parse(
      "aGVhZGVy.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI"));
  EXPECT_FALSE(
      SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE~"
                   "ZGlzY2xvc3VyZTI~ZGlzY2xvc3VyZTM"));

  // Invalid: disclosure can't be empty
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature~~"));
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE~~"));
  EXPECT_FALSE(SdJwt::Parse(
      "aGVhZGVy.cGF5bG9hZA.signature~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~~"));
  EXPECT_FALSE(SdJwt::Parse("aGVhZGVy.cGF5bG9hZA.signature~~~"));
}

TEST_F(SdJwtTest, SelectiveDisclosure) {
  Disclosure name;
  name.salt = Base64String("fake-salt1");
  name.name = "name";
  name.value = "Sam";

  Disclosure email;
  email.salt = Base64String("fake-salt2");
  email.name = "email";
  email.value = "goto@email.com";

  // Only present "name", keep "email" private.
  auto presentation = SdJwt::Disclose(
      {
          {email.name, *email.ToJson()},
          {name.name, *name.ToJson()},
      },
      {"name"});

  EXPECT_TRUE(presentation);

  // Asserts that we got only 1 disclosure ...
  EXPECT_EQ(presentation->size(), 1ul);

  // ... and that it was selected correctly:
  EXPECT_EQ((*presentation)[0],
            JSONString("[\"fake-salt1\",\"name\",\"Sam\"]"));
}

TEST_F(SdJwtTest, SdJwtKbParsingAndSerializing) {
  // SdJwtKb's top level structure is a jwt followed by ~ disclosures
  // followed by a key binding JWT.
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#name-sd-jwt-and-sd-jwtkb-data-fo
  std::string bin =
      "aGVhZGVy.cGF5bG9hZA.iss_signature"
      "~ZGlzY2xvc3VyZTE"
      "~ZGlzY2xvc3VyZTI"
      "~"
      "aGVhZGVy.cGF5bG9hZA.kb_signature";

  auto token = SdJwtKb::Parse(bin);
  EXPECT_TRUE(token);

  EXPECT_EQ(token->sd_jwt.jwt.header, JSONString("header"));
  EXPECT_EQ(token->sd_jwt.jwt.payload, JSONString("payload"));
  EXPECT_EQ(token->sd_jwt.jwt.signature, Base64String("iss_signature"));
  EXPECT_EQ(token->sd_jwt.disclosures.size(), 2ul);
  EXPECT_EQ(token->sd_jwt.disclosures[0], JSONString("disclosure1"));
  EXPECT_EQ(token->sd_jwt.disclosures[1], JSONString("disclosure2"));
  EXPECT_EQ(token->kb_jwt.header, JSONString("header"));
  EXPECT_EQ(token->kb_jwt.payload, JSONString("payload"));
  EXPECT_EQ(token->kb_jwt.signature, Base64String("kb_signature"));

  // Asserts that we can serialize the token again.
  token->sd_jwt.jwt.header = JSONString("new-header");
  EXPECT_EQ(
      token->Serialize(),
      "bmV3LWhlYWRlcg.cGF5bG9hZA.iss_signature~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~"
      "aGVhZGVy.cGF5bG9hZA.kb_signature");
}

TEST_F(SdJwtTest, SdJwtKbParsingCornerCases) {
  EXPECT_TRUE(
      SdJwtKb::Parse("aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~"
                     "aGVhZGVy.cGF5bG9hZA.kbs"));

  // No KB JWT.
  EXPECT_FALSE(SdJwtKb::Parse(
      "aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~"));

  // Ends in ~
  EXPECT_FALSE(
      SdJwtKb::Parse("aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~"
                     "aGVhZGVy.cGF5bG9hZA.kbs~"));

  // KB JWT missing payload and signature
  EXPECT_FALSE(
      SdJwtKb::Parse("aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~."
                     "cGF5bG9hZA.kbs"));
  // KB JWT with empty header and payload
  EXPECT_FALSE(SdJwtKb::Parse(
      "aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~aGVhZGVy..kbs"));
  // KB JWT with empty signature
  EXPECT_FALSE(
      SdJwtKb::Parse("aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~"
                     "aGVhZGVy.cGF5bG9hZA."));
  // KB JWT with empty header, payload and signature
  EXPECT_FALSE(SdJwtKb::Parse(
      "aGVhZGVy.cGF5bG9hZA.is~ZGlzY2xvc3VyZTE~ZGlzY2xvc3VyZTI~.."));
}

std::optional<std::vector<std::uint8_t>> TestSigner(
    const std::string_view& message) {
  std::string str = "Signed(" + std::string(message) + ")";
  std::vector<uint8_t> result(str.begin(), str.end());
  return result;
}

std::vector<std::uint8_t> TestSha256(std::string_view data) {
  std::string str = "Sha256(" + std::string(data) + ")";
  std::vector<uint8_t> result(str.begin(), str.end());
  return result;
}

TEST_F(SdJwtTest, SdJwtKb_Bind) {
  Disclosure name;
  name.salt = Base64String("fake-salt1");
  name.name = "name";
  name.value = "Sam";

  Disclosure email;
  email.salt = Base64String("fake-salt2");
  email.name = "email";
  email.value = "goto@email.com";

  Jwk key;

  auto disclosures = SdJwt::Disclose(
      {
          {name.name, *name.ToJson()},
          {email.name, *email.ToJson()},
      },
      {"name"});

  EXPECT_TRUE(disclosures);

  Jwt issued;
  issued.header = JSONString("header");
  issued.payload = JSONString("payload");
  issued.signature = Base64String("signature");

  SdJwt presentation;
  presentation.jwt = issued;
  presentation.disclosures = *disclosures;

  auto sdjwtkb = SdJwtKb::Create(presentation, "https://verifier.example",
                                 "__fake_nonce__", base::Time::FromTimeT(1234),
                                 base::BindRepeating(TestSha256),
                                 base::BindRepeating(TestSigner));

  EXPECT_TRUE(sdjwtkb);

  auto kb = sdjwtkb->kb_jwt;

  // Checks KB headers:
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-4.3
  auto header = Header::From(*base::JSONReader::ReadDict(
      kb.header.value(), base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(header);
  // typ MUST be "kb+jwt".
  EXPECT_EQ(header->typ, "kb+jwt");
  EXPECT_EQ(header->alg, "ES256");

  auto payload = Payload::From(*base::JSONReader::ReadDict(
      kb.payload.value(), base::JSON_PARSE_CHROMIUM_EXTENSIONS));
  EXPECT_TRUE(payload);
  // aud is required.
  EXPECT_EQ(payload->aud, "https://verifier.example");
  // nonce is required.
  EXPECT_EQ(payload->nonce, "__fake_nonce__");
  // iat is required.
  EXPECT_EQ(payload->iat, base::Time::FromTimeT(1234));

  // sd_hash is required.

  // Checks for how the hash was constructed:
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-4.3.1
  EXPECT_EQ(
      payload->sd_hash,
      Base64String(
          "U2hhMjU2KGFHVmhaR1Z5LmNHRjViRzloWkEuc2lnbmF0dXJlfld5Sm1ZV3RsTFh"
          "OaGJIUXhJaXdpYm1GdFpTSXNJbE5oYlNKZH4p"));

  // Checks that the signature was constructed correctly too:
  // https://www.ietf.org/archive/id/draft-ietf-oauth-selective-disclosure-jwt-13.html#section-4.3.1
  //
  // This value is a base64 encode string of the following header:
  //
  //  {
  //    "alg": "ES256",
  //    "typ": "kb+jwt"
  //  }
  //
  // and payload:
  //
  //  {
  //    "aud": "https://verifier.example",
  //    "iat": 1234,
  //    "nonce": "__fake_nonce__",
  //    "sd_hash":
  //      "Sha256(Base64(header).Base64(payload).signature~
  //           Base64(["fake-salt1","name","Sam"])~)"
  //  }
  //
  std::string base64;
  base::Base64UrlEncode(
      "Signed(eyJhbGciOiJFUzI1NiIsInR5cCI6ImtiK2p3dCJ9."
      "eyJhdWQiOiJodHRwczovL3ZlcmlmaWVyLmV4YW1wbGUiLCJpYXQiOjEyMzQsIm5"
      "vbmNlIjoiX19mYWtlX25vbmNlX18iLCJzZF9oYXNoIjoiVTJoaE1qVTJLR0ZIVm"
      "1oYVIxWjVMbU5IUmpWaVJ6bG9Xa0V1YzJsbmJtRjBkWEpsZmxkNVNtMVpWM1JzV"
      "EZoT2FHSklVWGhKYVhkcFltMUdkRnBUU1hOSmJFNW9ZbE5LWkg0cCJ9)",
      base::Base64UrlEncodePolicy::OMIT_PADDING, &base64);

  EXPECT_STREQ(kb.signature->c_str(), base64.c_str());
}

}  // namespace content::sdjwt
