| // Copyright 2026 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // https://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "crypto/default/eku_verifier.h" |
| |
| #include "absl/strings/string_view.h" |
| #include "openssl/asn1.h" |
| #include "openssl/base.h" |
| #include "openssl/bio.h" |
| #include "openssl/nid.h" |
| #include "openssl/obj.h" |
| #include "openssl/x509.h" |
| |
| namespace credentio { |
| namespace { |
| |
| constexpr absl::string_view kAnyEkuOid = "2.5.29.37.0"; |
| // See |
| // https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_trust_lists. |
| constexpr absl::string_view kC2paClaimSigningEkuOid = "1.3.6.1.4.1.62558.2.1"; |
| // The maximum number of EKU values that can be present in a certificate. |
| // We have this upper limit to prevent malicious certificates with arbitrarily |
| // large EKU lists to be processed by the check. |
| constexpr size_t kMaxEkuCount = 100; |
| // The bit index of the `digitalSignature` KU bit in the keyUsage bit string. |
| constexpr int kDigitalSignatureKeyUsageBit = 0; |
| |
| } // namespace |
| |
| bool EkuVerifier::CheckLeafCertEku(absl::string_view leaf_cert_der) { |
| const unsigned char* leaf_cert_der_bytes = |
| reinterpret_cast<const unsigned char*>(leaf_cert_der.data()); |
| bssl::UniquePtr<BIO> bio( |
| BIO_new_mem_buf(leaf_cert_der_bytes, leaf_cert_der.size())); |
| if (bio == nullptr) { |
| return false; |
| } |
| bssl::UniquePtr<X509> leaf_cert(d2i_X509_bio(bio.get(), /*x509=*/nullptr)); |
| if (leaf_cert == nullptr) { |
| return false; |
| } |
| |
| // Check that the certificate asserts the `digitalSignature` KU bit per |
| // https://spec.c2pa.org/specifications/specifications/2.4/specs/C2PA_Specification.html#_general_requirements. |
| bssl::UniquePtr<ASN1_BIT_STRING> key_usage(static_cast<ASN1_BIT_STRING*>( |
| X509_get_ext_d2i(leaf_cert.get(), NID_key_usage, /*out_critical=*/nullptr, |
| /*out_idx=*/nullptr))); |
| if (key_usage == nullptr) { |
| return false; |
| } |
| if (ASN1_BIT_STRING_get_bit(key_usage.get(), kDigitalSignatureKeyUsageBit) == |
| 0) { |
| return false; |
| } |
| |
| int loc_eku = |
| X509_get_ext_by_NID(leaf_cert.get(), NID_ext_key_usage, /*lastpos=*/-1); |
| if (loc_eku < 0) { |
| return false; |
| } |
| X509_EXTENSION* ext = X509_get_ext(leaf_cert.get(), loc_eku); |
| if (!ext) { |
| return false; |
| } |
| bssl::UniquePtr<EXTENDED_KEY_USAGE> eku( |
| (EXTENDED_KEY_USAGE*)X509V3_EXT_d2i(ext)); |
| if (!eku) { |
| return false; |
| } |
| bssl::UniquePtr<ASN1_OBJECT> target_oid( |
| OBJ_txt2obj(kC2paClaimSigningEkuOid.data(), 0)); |
| if (!target_oid) { |
| return false; |
| } |
| bssl::UniquePtr<ASN1_OBJECT> any_eku_oid(OBJ_txt2obj(kAnyEkuOid.data(), 0)); |
| if (!any_eku_oid) { |
| return false; |
| } |
| bool found = false; |
| bool any_eku_found = false; |
| int num_usages = sk_ASN1_OBJECT_num(eku.get()); |
| if (num_usages > kMaxEkuCount) { |
| return false; |
| } |
| for (int i = 0; i < num_usages; ++i) { |
| ASN1_OBJECT* obj = sk_ASN1_OBJECT_value(eku.get(), i); |
| if (!obj) { |
| continue; |
| } |
| if (OBJ_cmp(obj, target_oid.get()) == 0) { |
| // The C2PA claim signing EKU is present |
| found = true; |
| } |
| if (OBJ_cmp(obj, any_eku_oid.get()) == 0) { |
| // The `anyExtendedKeyUsage` EKU is present |
| any_eku_found = true; |
| } |
| } |
| return found && !any_eku_found; |
| } |
| |
| } // namespace credentio |