| // 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 "base/base64.h" |
| #include "base/base64url.h" |
| #include "base/containers/contains.h" |
| #include "base/json/json_reader.h" |
| #include "base/strings/string_split.h" |
| #include "crypto/evp.h" |
| #include "crypto/signature_verifier.h" |
| #include "net/cert/asn1_util.h" |
| #include "net/cert/x509_util.h" |
| #include "third_party/boringssl/src/include/openssl/ec.h" |
| #include "third_party/boringssl/src/include/openssl/ec_key.h" |
| |
| namespace net { |
| |
| Jades2QwacHeader::Jades2QwacHeader() = default; |
| Jades2QwacHeader::Jades2QwacHeader(const Jades2QwacHeader& other) = default; |
| Jades2QwacHeader::Jades2QwacHeader(Jades2QwacHeader&& other) = default; |
| Jades2QwacHeader::~Jades2QwacHeader() = default; |
| |
| namespace { |
| |
| std::optional<Jades2QwacHeader> ParseJades2QwacHeader( |
| std::string_view header_string) { |
| Jades2QwacHeader parsed_header; |
| // The header of a JWS is a JSON-encoded object (RFC 7515, section 4). |
| // |
| // RFC 7515 section 5.2 (signature verification) step 3: verify the resulting |
| // octet sequence (the header_string variable passed into this function) is a |
| // UTF-8-encoded representation of a completely valid JSON object. By using |
| // the JSONReader with base::JSON_PARSE_RFC and checking that the returned |
| // value is a base::DictValue, we check that the input is UTF-8-encoded and |
| // a valid JSON object. |
| std::optional<base::Value> header_value = |
| base::JSONReader::Read(header_string, base::JSON_PARSE_RFC); |
| if (!header_value.has_value() || !header_value->is_dict()) { |
| return std::nullopt; |
| } |
| // RFC 7515 section 5.2 (signature verification) step 4: If using the JWS |
| // compact serialization (which we are), let the JOSE Header (the header |
| // variable here) be the JWS Protected Header (the JSON object decoded in step |
| // 3). During this step, verify that the resulting JOSE Header does not |
| // contain duplicate Header Parameter names. |
| // |
| // base::JSONReader will not return an object with duplicate keys. It returns |
| // the last key-value pair. This is consistent with section 4 of RFC 7515 |
| // which states that a JWS parser must either reject JWSs with duplicate |
| // Header Parameter names or use a JSON parser that returns only the lexically |
| // last duplicate member name, as specified in "The JSON Object" section of |
| // the ECMAScript standard. base::JSONReader chooses this second option for |
| // compliance with standards. |
| base::Value::Dict& header = header_value->GetDict(); |
| |
| // "alg" (Algorithm) parameter - RFC 7515, section 4.1.1 |
| // |
| // Possible values for this field are found in the JSON Web Signature and |
| // Encryption Algorithms IANA registry: |
| // https://www.iana.org/assignments/jose/jose.xhtml#web-signature-encryption-algorithms |
| // |
| // The only requirement that the 2-QWAC spec (ETSI TS 119 411-5 Annex B) |
| // imposes on this field is that it not conflict with the type of the public |
| // key in the signing certificate. Annex B also states that the binding is |
| // according to ETSI TS 119 182-1. Clause 5.1.2 of ETSI TS 119 182-1 merely |
| // states that the syntax and semantics of this header parameter are as |
| // specified in RFC 7515 section 4.1.1. In terms of allowed values, the only |
| // requirement is that it shall be one specified in the aforementioned IANA |
| // registry; neither ETSI TS 119 411-5 nor ETSI TS 119 182-1 specify a set of |
| // required or mandatory-to-implement algorithms. The IANA registry has a |
| // "JOSE Implementation Requirements" column; no (asymmetric) signature |
| // algorithms are listed as "Required". |
| // |
| // Given that there are no required signature algorithms, this only supports |
| // algorithms that at the time of writing are both listed in the IANA registry |
| // and supported by crypto::SignatureVerifier. |
| std::string* alg = header.FindString("alg"); |
| if (!alg) { |
| return std::nullopt; |
| } else if (*alg == "RS256") { |
| parsed_header.sig_alg = JwsSigAlg::kRsaPkcs1Sha256; |
| } else if (*alg == "PS256") { |
| parsed_header.sig_alg = JwsSigAlg::kRsaPssSha256; |
| } else if (*alg == "ES256") { |
| parsed_header.sig_alg = JwsSigAlg::kEcdsaP256Sha256; |
| } else { |
| return std::nullopt; |
| } |
| header.Remove("alg"); |
| |
| // "kid" (Key ID) parameter - RFC 7515, section 4.1.4 |
| // |
| // The Key ID can be of any type and is used to identify the key used for |
| // signing. In this profile, the key used to verify the signature will be |
| // found in the "x5c" parameter, so the "kid" is useless to us and is ignored. |
| header.Remove("kid"); |
| |
| // "cty" (Content Type) parameter - RFC 7515, section 4.1.10 |
| // |
| // ETSI TS 119 411-5 V2.1.1 requires the "cty" parameter to be |
| // "TLS-Certificate-Binding-v1". |
| std::string* cty = header.FindString("cty"); |
| if (!cty || *cty != "TLS-Certificate-Binding-v1") { |
| return std::nullopt; |
| } |
| header.Remove("cty"); |
| |
| // "x5t#S256" (X.509 Certificate SHA-256 Thumbprint) parameter (RFC 7515, |
| // section 4.1.8) is the base64url-encoded SHA-256 thumbprint of the |
| // DER encoding of the X.509 certificate used to sign the JWS. This value is |
| // not needed to verify the signature (the leaf cert of the "x5c" parameter is |
| // the signing cert), and it is optional according to RFC 7515, so we ignore |
| // it. |
| if (header.FindString("x5t#S256")) { |
| header.Remove("x5t#S256"); |
| } |
| |
| // "x5c" (X.509 Certificate Chain) header - RFC 7515 section 4.1.6 |
| base::ListValue* x5c_list = header.FindList("x5c"); |
| if (!x5c_list) { |
| return std::nullopt; |
| } |
| |
| size_t i = 0; |
| bssl::UniquePtr<CRYPTO_BUFFER> leaf; |
| std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> intermediates; |
| for (const base::Value& cert_value : *x5c_list) { |
| // RFC 7515 section 4.1.6: |
| // "Each string in the array is a base64-encoded (not base64url-encoded) DER |
| // PKIX certificate value." |
| if (!cert_value.is_string()) { |
| return std::nullopt; |
| } |
| auto cert_bytes = base::Base64Decode(cert_value.GetString()); |
| if (!cert_bytes.has_value()) { |
| return std::nullopt; |
| } |
| auto buf = x509_util::CreateCryptoBuffer(*cert_bytes); |
| if (i == 0) { |
| leaf = std::move(buf); |
| } else { |
| intermediates.emplace_back(std::move(buf)); |
| } |
| i++; |
| } |
| parsed_header.two_qwac_cert = X509Certificate::CreateFromBuffer( |
| std::move(leaf), std::move(intermediates)); |
| if (!parsed_header.two_qwac_cert) { |
| return std::nullopt; |
| } |
| header.Remove("x5c"); |
| |
| // "iat" header. TS 119 182-1 section 5.1.11 defines this header parameter to |
| // be almost the same as RFC 7519's JWT "iat" claim. Despite TS 119 182-1 |
| // citing RFC 7519 as the definition for this header parameter, JWS header |
| // parameters and JWT claims are not the same thing. In any case, ETSI defines |
| // this header to be an integer representing the claimed signing time. |
| // |
| // I see no indication in TS 119 411-5 that "iat" is required to be present, |
| // and RFC 7519 specifies it as optional. Further, I haven't yet found an |
| // indication as to how one would interpret and apply this field in signature |
| // validation, so I'm ignoring it. |
| if (header.FindInt("iat")) { |
| header.Remove("iat"); |
| } |
| |
| // "exp" header. TS 119 411-5 Annex B defines this as the expiry date of the |
| // binding, and like TS 119 182-1 for the "iat" header, incorrectly cites RFC |
| // 7519's claim definition of the field (section 4.1.4). Unlike the ETSI |
| // specification for "iat" that restricts its NumericDate type to an integer, |
| // we only have the RFC 7519 definition of "exp" to use, which defines |
| // NumericDate as a JSON numeric value. RFC 7159 allows JSON numeric values to |
| // contain a fraction part. |
| // |
| // Like the "iat" header, TS 119 411-5 does not require the presence of "exp", |
| // RFC 7519 specifies it as optional, and there is no indication in any ETSI |
| // spec on how this field would affect signature validation, so it is ignored. |
| if (header.FindDouble("exp")) { |
| header.Remove("exp"); |
| } |
| |
| // "sigD" header - ETSI TS 119 182-1 section 5.2.8, with additional |
| // requirements specified in ETSI TS 119 411-5 Annex B. This parameter is a |
| // JSON object and is required to be present. |
| base::DictValue* sig_d = header.FindDict("sigD"); |
| if (!sig_d) { |
| return std::nullopt; |
| } |
| |
| // The sigD header must have a "mId" (mechanism ID) of |
| // "http://uri.etsi.org/19182/ObjectIdByURIHash". (ETSI TS 119 411-5 Annex B.) |
| std::string* m_id = sig_d->FindString("mId"); |
| if (!m_id || *m_id != "http://uri.etsi.org/19182/ObjectIdByURIHash") { |
| return std::nullopt; |
| } |
| sig_d->Remove("mId"); |
| |
| // The sigD header must have a "pars" member, which is a list of strings. We |
| // don't care about the contents of this list, but its size must match that of |
| // "hashV". (ETSI 119 182-1 clause 5.2.8.) |
| const base::ListValue* pars = sig_d->FindList("pars"); |
| if (!pars) { |
| return std::nullopt; |
| } |
| size_t bound_cert_count = pars->size(); |
| for (const base::Value& par : *pars) { |
| if (!par.is_string()) { |
| return std::nullopt; |
| } |
| } |
| sig_d->Remove("pars"); |
| |
| // The sigD header must have a "hashM" member (TS 119 182-1 |
| // section 5.2.8.3.3), which is a string identifying the hashing algorithm |
| // used for the "hashV" member. ETSI TS 119 411-5 only requires that S256, |
| // S384, and S512 be supported. |
| std::string* hash_m = sig_d->FindString("hashM"); |
| if (!hash_m) { |
| return std::nullopt; |
| } |
| if (*hash_m == "S256") { |
| parsed_header.hash_alg = crypto::hash::kSha256; |
| } else if (*hash_m == "S384") { |
| parsed_header.hash_alg = crypto::hash::kSha384; |
| } else if (*hash_m == "S512") { |
| parsed_header.hash_alg = crypto::hash::kSha512; |
| } else { |
| // Unsupported hashing algorithm. |
| return std::nullopt; |
| } |
| sig_d->Remove("hashM"); |
| |
| // The sigD header must have a "hashV" member, which is a list of |
| // base64url-encoded digest values of the base64url-encoded data objects. |
| // (ETSI TS 119 182-1 clause 5.2.8. The "b64" header parameter is absent, so |
| // the digest is computed over the base64url-encoded data object instead of |
| // computed directly over the data object.) |
| const base::ListValue* hash_v = sig_d->FindList("hashV"); |
| if (!hash_v) { |
| return std::nullopt; |
| } |
| if (hash_v->size() != bound_cert_count) { |
| return std::nullopt; |
| } |
| parsed_header.bound_cert_hashes.reserve(bound_cert_count); |
| for (const base::Value& hash_value : *hash_v) { |
| const std::string* hash_b64url = hash_value.GetIfString(); |
| if (!hash_b64url) { |
| return std::nullopt; |
| } |
| // ETSI TS 119 182-1 fails to specify the definition of "base64url-encoded". |
| // Given that other uses of base64url encoding come from the JWS spec, and |
| // JWS disallows padding in its base64url encoding, we disallow it here as |
| // well. |
| auto hash = base::Base64UrlDecode( |
| *hash_b64url, base::Base64UrlDecodePolicy::DISALLOW_PADDING); |
| if (!hash.has_value()) { |
| return std::nullopt; |
| } |
| parsed_header.bound_cert_hashes.emplace_back(std::move(*hash)); |
| } |
| sig_d->Remove("hashV"); |
| |
| // Given the mId used, the sigD header may have a "ctys" member (TS 119 182-1 |
| // clause 5.2.8.3.3), with semantics and syntax as specified in clause |
| // 5.2.8.1. Clause 5.2.8.1 defines the "ctys" member's syntax to be an array |
| // of strings. This array has the same length as the "pars" (and "hashV") |
| // array, and each element is the content type (RFC 7515 section 4.1.10) of |
| // the data object referred to by the value in "pars" at the same index. |
| // RFC 7515 specifies that the content type parameter is ignored by JWS |
| // implementations and processing of it is performed by the JWS application. |
| // Since neither ETSI TS 119 182-1 nor TS 119 411-5 provide guidance on the |
| // content type used for the individual data objects, this implementation has |
| // no opinion on the stated content types. |
| const base::ListValue* ctys = sig_d->FindList("ctys"); |
| if (ctys) { |
| if (ctys->size() != bound_cert_count) { |
| return std::nullopt; |
| } |
| for (const base::Value& cty_value : *ctys) { |
| if (!cty_value.is_string()) { |
| return std::nullopt; |
| } |
| } |
| } else if (sig_d->contains("ctys")) { |
| // check that there isn't a "ctys" of the wrong type |
| return std::nullopt; |
| } |
| sig_d->Remove("ctys"); |
| |
| // sigD has no other members than the aforementioned "mId", "pars", "hashM", |
| // "hashV", and "ctys". (ETSI TS 119 182-1 clause 5.2.8.) |
| if (!sig_d->empty()) { |
| return std::nullopt; |
| } |
| header.Remove("sigD"); |
| |
| // The header must not contain fields other than "alg", "kid", "cty", |
| // "x5t#S256", "x5c", "iat", "exp", or "sigD", as required by ETSI TS 119 |
| // 411-5 V2.1.1, Annex B. |
| // |
| // ETSI TS 119 182-1 V1.2.1 section 5.1.9 specifies that if the "sigD" header |
| // parameter is present, then the "crit" header parameter shall also be |
| // present with "sigD" as one of its array elements. This is in conflict with |
| // the requirement in 119 411-5 V2.1.1 Annex B. To resolve this conflict, this |
| // implementation will allow the presence of "crit", but if it is present, it |
| // must be an array containing exactly "sigD". |
| const auto* crit_value = header.Find("crit"); |
| if (crit_value) { |
| if (!crit_value->is_list()) { |
| return std::nullopt; |
| } |
| const auto& crit_list = crit_value->GetList(); |
| if (crit_list.size() != 1 || !crit_list.contains("sigD")) { |
| return std::nullopt; |
| } |
| } |
| header.Remove("crit"); |
| |
| // RFC 7515 section 5.2 (signature verification) step 5: Verify that the |
| // implementation understands and can process all fields that it is required |
| // to support. This implementation rejects a JWS header that contains unknown |
| // fields. |
| if (!header.empty()) { |
| return std::nullopt; |
| } |
| |
| return parsed_header; |
| } |
| |
| } // namespace |
| |
| TwoQwacCertBinding::TwoQwacCertBinding(Jades2QwacHeader header, |
| std::string header_string, |
| std::vector<uint8_t> signature) |
| : header_(header), header_string_(header_string), signature_(signature) {} |
| |
| TwoQwacCertBinding::TwoQwacCertBinding(const TwoQwacCertBinding& other) = |
| default; |
| TwoQwacCertBinding::TwoQwacCertBinding(TwoQwacCertBinding&& other) = default; |
| TwoQwacCertBinding::~TwoQwacCertBinding() = default; |
| |
| std::optional<TwoQwacCertBinding> TwoQwacCertBinding::Parse( |
| std::string_view jws) { |
| // ETSI TS 119 411-5 V2.1.1 Annex B: The JAdES signatures shall be serialized |
| // using JWS Compact Serialization as specified in IETF RFC 7515. |
| // |
| // The JWS Compact Serialization format consists of 3 components separated by |
| // a dot (".") (RFC 7515, section 7.1). |
| // |
| // RFC 7515 section 5.2 (signature verification) step 1: parse the JWS |
| // representation to extract the serialized values for the components of the |
| // JWS. |
| std::vector<std::string_view> jws_components = base::SplitStringPiece( |
| jws, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); |
| if (jws_components.size() != 3) { |
| // Reject a JWS that does not consist of 3 components. |
| return std::nullopt; |
| } |
| std::string_view header_b64 = jws_components[0]; |
| std::string_view payload_b64 = jws_components[1]; |
| std::string_view signature_b64 = jws_components[2]; |
| |
| // The 3 components of a JWS are the header, the payload, and the signature. |
| // The components are base64url encoded (RFC 7515, section 7.1) and the base64 |
| // encoding is without any padding "=" characters (Ibid., section 2). |
| |
| // RFC 7515 section 5.2 (signature verification) step 2: base64url-decode the |
| // encoded representation of the JWS Protected Header. |
| std::string header_string; |
| if (!base::Base64UrlDecode(header_b64, |
| base::Base64UrlDecodePolicy::DISALLOW_PADDING, |
| &header_string)) { |
| return std::nullopt; |
| } |
| // RFC 7515 section 5.2 (signature verification) step 7: base64url-decode the |
| // encoded representation of the JWS Signature. |
| std::optional<std::vector<uint8_t>> signature = base::Base64UrlDecode( |
| signature_b64, base::Base64UrlDecodePolicy::DISALLOW_PADDING); |
| if (!signature.has_value()) { |
| return std::nullopt; |
| } |
| |
| // Parse the JWS/JAdES header. This function will perform steps 3-5 of RFC |
| // 7515 section 5.2 (signature verification). |
| auto header = ParseJades2QwacHeader(header_string); |
| if (!header.has_value()) { |
| return std::nullopt; |
| } |
| |
| // ETSI TS 119 411-5 V2.1.1 Annex B specifies a "sigD" header parameter. This |
| // header parameter is defined in ETSI TS 119 182-1 V1.2.1, section 5.2.8, |
| // which states "The sigD header parameter shall not appear in JAdES |
| // signatures whose JWS Payload is attached". Thus, it can be inferred that |
| // the JWS Payload is detached. A detached payload for a JWS means that the |
| // encoded payload is empty (RFC 7515, Appendix F). |
| // |
| // RFC 7515 section 5.2 (signature verification) step 6: base64url-decode the |
| // encoded representation of the JWS Payload. Since the only valid payload is |
| // the empty payload, checking that the encoded representation is empty is |
| // sufficient to decode and check that the JWS Payload is empty. |
| if (!payload_b64.empty()) { |
| return std::nullopt; |
| } |
| |
| return TwoQwacCertBinding(*header, std::string(header_b64), *signature); |
| } |
| |
| namespace { |
| |
| // Given a SPKI, returns whether the public key is an ECDSA key on the curve |
| // P-256. |
| bool IsKeyP256(base::span<const uint8_t> spki) { |
| bssl::UniquePtr<EVP_PKEY> public_key = crypto::evp::PublicKeyFromBytes(spki); |
| if (!public_key) { |
| return false; |
| } |
| EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(public_key.get()); |
| if (!ec_key) { |
| return false; |
| } |
| const EC_GROUP* group = EC_KEY_get0_group(ec_key); |
| if (!group) { |
| return false; |
| } |
| return EC_GROUP_get_curve_name(group) == NID_X9_62_prime256v1; |
| } |
| |
| } // namespace |
| |
| bool TwoQwacCertBinding::VerifySignature() { |
| // ETSI TS 119 411-5 clause 6.2.2 step 5 states: |
| // |
| // Validate the JAdES signature on the TLS Certificate binding according to |
| // ETSI EN 319 102-1. |
| // |
| // - If this step fails or the TLS Certificate binding is not considered |
| // valid, the procedure finishes negatively. |
| // |
| // ETSI EN 319 102-1 does not say how to validate a JAdES signature. If we |
| // attempt to apply the processes that it describes generically for AdES |
| // signatures, we encounter a problem in the cryptographic validation building |
| // block in clause 5.2.7.4. That clause states that the technical details on |
| // how to perform the cryptographic validation are out of scope, and to see |
| // other documents for details. None of the listed documents provide any |
| // details about JAdES signatures or JWSs. |
| // |
| // Since ETSI EN 319 102-1 lacks a pointer to the proper specification |
| // containing the technical details needed to cryptographically validate a |
| // JAdES signature, I look at the 2-QWAC spec (ETSI TS 119 411-5) which cited |
| // ETSI EN 319 102-1 for assistance. ETSI TS 119 411-5 includes ETSI TS 119 |
| // 182-1 ("JAdES digital signatures") as a normative reference. ETSI TS 119 |
| // 182-1 clause 1 defines the scope of that document, and the validation of |
| // JAdES digital signatures is out of scope for that document. Although the |
| // validation of JAdES digital signatures is out of scope for that document, |
| // it does define a JAdES signature as being an extension of JSON Web |
| // Signatures as specified in IETF RFC 7515. |
| // |
| // For lack of a better reference, this 2-QWAC implementation will use the |
| // process defined in section 5.2 of RFC 7515 (Message Signature or MAC |
| // Validation) to validate the signature on the TLS Certificate Binding JWS/ |
| // JAdES signature. This function only implements the process defined in RFC |
| // 7515; it does not implement any of the other building blocks used by the |
| // validation process for Basic Signatures defined in clause 5.3 of ETSI EN |
| // 319 102-1. |
| |
| // Extract public key from certificate and initialize verifier. ETSI TS 119 |
| // 411-5 Annex B requires checking that the "alg" parameter does not conflict |
| // with the type of public key in the signing certificate. The call to |
| // VerifyInit checks that the signature algorithm is compatible with the |
| // signing key (from the signing certificate). |
| std::string_view spki; |
| if (!asn1::ExtractSPKIFromDERCert(x509_util::CryptoBufferAsStringPiece( |
| header_.two_qwac_cert->cert_buffer()), |
| &spki)) { |
| return false; |
| } |
| crypto::SignatureVerifier::SignatureAlgorithm sig_alg; |
| switch (header_.sig_alg) { |
| case JwsSigAlg::kEcdsaP256Sha256: |
| // SignatureAlgorithm::ECDSA_SHA256 doesn't require that the EC curve be |
| // P-256, but the JWS signature algorithm does require that it be P-256. |
| // Before converting JwsSigAlg::kEcdsaP256Sha256 to ECDSA_SHA256, check |
| // that the key is P-256. |
| if (!IsKeyP256(base::as_byte_span(spki))) { |
| return false; |
| } |
| sig_alg = crypto::SignatureVerifier::SignatureAlgorithm::ECDSA_SHA256; |
| break; |
| case JwsSigAlg::kRsaPkcs1Sha256: |
| sig_alg = crypto::SignatureVerifier::SignatureAlgorithm::RSA_PKCS1_SHA256; |
| break; |
| case JwsSigAlg::kRsaPssSha256: |
| sig_alg = crypto::SignatureVerifier::SignatureAlgorithm::RSA_PSS_SHA256; |
| break; |
| } |
| // The crypto::SignatureVerifier checks that the public key in |spki| is |
| // compatible with the signature algorithm in |sig_alg| that came from the JWS |
| // header. This handles the requirement in the 2-QWAC spec (ETSI TS 119 411-5 |
| // Annex B) that the "alg" JWS header field not conflict with the type of the |
| // public key in the "x5c" JWS header field. |
| crypto::SignatureVerifier verifier; |
| if (!verifier.VerifyInit(sig_alg, signature_, base::as_byte_span(spki))) { |
| return false; |
| } |
| |
| // RFC 7515 section 5.2 steps 1-7 are performed by TwoQwacCertBinding::Parse. |
| |
| // Step 8: Validate the JWS Signature against the JWS Signing Input. |
| // |
| // The JWS Signing Input is ASCII(BASE64URL(UTF8(JWS Protected Header)) || '.' |
| // || BASE64URL(JWS Payload)) (RFC 7515 section 5.2 step 8). |
| // |
| // The first component of the input - BASE64URL(UTF8(JWS Protected Header)) - |
| // is the unparsed JWS header: |
| verifier.VerifyUpdate(base::as_byte_span(header_string_)); |
| static constexpr uint8_t separator[] = {'.'}; |
| verifier.VerifyUpdate(separator); |
| // The JWS Payload is empty, so there are 0 bytes to contribute to the |
| // BASE64URL(JWS Payload) component of the JWS Signing Input. |
| |
| // Step 9 only applies if the JWS JSON Serialization is being used; we use the |
| // JWS Compact Serialization. |
| |
| // Step 10: In the JWS Compact Serialization case, the result can simply |
| // indicate whether or not the JWS was successfully validated. |
| return verifier.VerifyFinal(); |
| } |
| |
| bool TwoQwacCertBinding::BindsTlsCert(base::span<const uint8_t> tls_cert_der) { |
| // header.bound_cert_hashes contains a list of Digest(base64url(der)), where |
| // the digest algorithm is specified by header.hash_alg. Compute the digest of |
| // the base64url-encoded cert and search for that in the list of bound cert |
| // hashes. |
| std::string tls_cert_b64; |
| base::Base64UrlEncode(tls_cert_der, base::Base64UrlEncodePolicy::OMIT_PADDING, |
| &tls_cert_b64); |
| std::vector<uint8_t> tls_cert_hash( |
| crypto::hash::DigestSizeForHashKind(header_.hash_alg)); |
| crypto::hash::Hash(header_.hash_alg, tls_cert_b64, tls_cert_hash); |
| |
| return base::Contains(header_.bound_cert_hashes, tls_cert_hash); |
| } |
| |
| } // namespace net |