blob: 0bb18089c2cef9bee422e1a251be64251e04a3b1 [file] [edit]
// 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