This permits chains to be validated when the intended use is to validate a C2PA time stamp signer or C2PA manifest signer. As in the RCS MLS case, further C2PA specific checks for the purpose intended may be needed on the certificate after verify is run. Bug: 400963456 Change-Id: I22bfe6b2d3ad3cfd939a2c5a13debb5f0d3ccbb3 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/77487 Commit-Queue: Bob Beck <bbe@google.com> Reviewed-by: David Benjamin <davidben@google.com>
364 lines
12 KiB
C++
364 lines
12 KiB
C++
// Copyright 2023 The BoringSSL Authors
|
|
//
|
|
// 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 <openssl/pki/verify.h>
|
|
|
|
#include <assert.h>
|
|
|
|
#include <chrono>
|
|
#include <optional>
|
|
#include <string_view>
|
|
|
|
#include <openssl/base.h>
|
|
#include <openssl/bytestring.h>
|
|
#include <openssl/pool.h>
|
|
|
|
#include <openssl/pki/signature_verify_cache.h>
|
|
|
|
#include "cert_errors.h"
|
|
#include "cert_issuer_source_static.h"
|
|
#include "certificate_policies.h"
|
|
#include "common_cert_errors.h"
|
|
#include "encode_values.h"
|
|
#include "input.h"
|
|
#include "parse_certificate.h"
|
|
#include "parse_values.h"
|
|
#include "parsed_certificate.h"
|
|
#include "path_builder.h"
|
|
#include "simple_path_builder_delegate.h"
|
|
#include "trust_store.h"
|
|
#include "trust_store_in_memory.h"
|
|
#include "verify_certificate_chain.h"
|
|
|
|
BSSL_NAMESPACE_BEGIN
|
|
|
|
namespace {
|
|
|
|
std::optional<std::shared_ptr<const ParsedCertificate>>
|
|
InternalParseCertificate(Span<const uint8_t> cert, std::string *out_diagnostic) {
|
|
ParseCertificateOptions default_options{};
|
|
// We follow Chromium in setting |allow_invalid_serial_numbers| in order to
|
|
// not choke on 21-byte serial numbers, which are common.
|
|
//
|
|
// The reason for the discrepancy is that unsigned numbers with the high bit
|
|
// otherwise set get an extra 0 byte in front to keep them positive. So if you
|
|
// do:
|
|
// var num [20]byte
|
|
// fillWithRandom(num[:])
|
|
// serialNumber := new(big.Int).SetBytes(num[:])
|
|
// encodeASN1Integer(serialNumber)
|
|
//
|
|
// Then half of your serial numbers will be encoded with 21 bytes. (And
|
|
// 1/512th will have 19 bytes instead of 20.)
|
|
default_options.allow_invalid_serial_numbers = true;
|
|
|
|
UniquePtr<CRYPTO_BUFFER> buffer(
|
|
CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr));
|
|
CertErrors errors;
|
|
std::shared_ptr<const ParsedCertificate> parsed_cert(
|
|
ParsedCertificate::Create(std::move(buffer), default_options, &errors));
|
|
if (!parsed_cert) {
|
|
*out_diagnostic = errors.ToDebugString();
|
|
return {};
|
|
}
|
|
return parsed_cert;
|
|
}
|
|
} // namespace
|
|
|
|
|
|
CertPool::CertPool() {}
|
|
|
|
CertificateVerifyOptions::CertificateVerifyOptions() {}
|
|
|
|
static std::unique_ptr<VerifyTrustStore> WrapTrustStore(
|
|
std::unique_ptr<TrustStoreInMemory> trust_store) {
|
|
std::unique_ptr<VerifyTrustStore> ret(new VerifyTrustStore);
|
|
ret->trust_store = std::move(trust_store);
|
|
return ret;
|
|
}
|
|
|
|
VerifyTrustStore::~VerifyTrustStore() {}
|
|
|
|
std::unique_ptr<VerifyTrustStore> VerifyTrustStore::FromDER(
|
|
std::string_view der_certs, std::string *out_diagnostic) {
|
|
auto store = std::make_unique<TrustStoreInMemory>();
|
|
CBS cbs = StringAsBytes(der_certs);
|
|
|
|
for (size_t cert_num = 1; CBS_len(&cbs) != 0; cert_num++) {
|
|
CBS cert;
|
|
if (!CBS_get_asn1_element(&cbs, &cert, CBS_ASN1_SEQUENCE)) {
|
|
*out_diagnostic = "failed to get ASN.1 SEQUENCE from input at cert " +
|
|
std::to_string(cert_num);
|
|
return {};
|
|
}
|
|
|
|
auto parsed_cert = InternalParseCertificate(cert, out_diagnostic);
|
|
if (!parsed_cert.has_value()) {
|
|
return {};
|
|
}
|
|
store->AddTrustAnchor(parsed_cert.value());
|
|
}
|
|
|
|
return WrapTrustStore(std::move(store));
|
|
}
|
|
|
|
std::unique_ptr<VerifyTrustStore> VerifyTrustStore::FromDER(
|
|
const std::vector<std::string_view> &der_roots,
|
|
std::string *out_diagnostic) {
|
|
auto store = std::make_unique<TrustStoreInMemory>();
|
|
|
|
for (const std::string_view &cert : der_roots) {
|
|
auto parsed_cert = InternalParseCertificate(StringAsBytes(cert), out_diagnostic);
|
|
if (!parsed_cert.has_value()) {
|
|
return {};
|
|
}
|
|
store->AddTrustAnchor(parsed_cert.value());
|
|
}
|
|
|
|
return WrapTrustStore(std::move(store));
|
|
}
|
|
|
|
CertPool::~CertPool() {}
|
|
|
|
|
|
std::unique_ptr<CertPool> CertPool::FromCerts(
|
|
const std::vector<std::string_view> &der_certs,
|
|
std::string *out_diagnostic) {
|
|
auto pool = std::make_unique<CertPool>();
|
|
pool->impl_ = std::make_unique<CertIssuerSourceStatic>();
|
|
|
|
for (const std::string_view &cert : der_certs) {
|
|
auto parsed_cert =
|
|
InternalParseCertificate(StringAsBytes(cert), out_diagnostic);
|
|
if (!parsed_cert.has_value()) {
|
|
return {};
|
|
}
|
|
pool->impl_->AddCert(std::move(parsed_cert.value()));
|
|
}
|
|
|
|
return pool;
|
|
}
|
|
|
|
CertificateVerifyStatus::CertificateVerifyStatus() {}
|
|
|
|
size_t CertificateVerifyStatus::IterationCount() const {
|
|
return iteration_count_;
|
|
}
|
|
|
|
size_t CertificateVerifyStatus::MaxDepthSeen() const { return max_depth_seen_; }
|
|
|
|
// PathBuilderDelegateImpl implements a deadline and allows for the
|
|
// use of a SignatureVerifyCache if an implementation is provided.
|
|
class PathBuilderDelegateImpl : public SimplePathBuilderDelegate {
|
|
public:
|
|
PathBuilderDelegateImpl(
|
|
size_t min_rsa_modulus_length_bits, DigestPolicy digest_policy,
|
|
std::chrono::time_point<std::chrono::steady_clock> deadline,
|
|
SignatureVerifyCache *cache)
|
|
: SimplePathBuilderDelegate(min_rsa_modulus_length_bits, digest_policy),
|
|
deadline_(deadline),
|
|
cache_(cache) {}
|
|
|
|
bool IsDeadlineExpired() override {
|
|
return (std::chrono::steady_clock::now() > deadline_);
|
|
}
|
|
|
|
SignatureVerifyCache *GetVerifyCache() override { return cache_; }
|
|
|
|
private:
|
|
const std::chrono::time_point<std::chrono::steady_clock> deadline_;
|
|
SignatureVerifyCache *cache_;
|
|
};
|
|
|
|
std::optional<std::vector<std::vector<std::string>>> CertificateVerifyInternal(
|
|
const CertificateVerifyOptions &opts, VerifyError *out_error,
|
|
CertificateVerifyStatus *out_status, bool all_paths) {
|
|
VerifyError dummy;
|
|
if (!out_error) {
|
|
out_error = &dummy;
|
|
}
|
|
if (out_status != nullptr) {
|
|
out_status->iteration_count_ = 0;
|
|
out_status->max_depth_seen_ = 0;
|
|
}
|
|
|
|
std::string diagnostic;
|
|
std::optional<std::shared_ptr<const ParsedCertificate>> maybe_leaf =
|
|
InternalParseCertificate(StringAsBytes(opts.leaf_cert), &diagnostic);
|
|
|
|
if (!maybe_leaf.has_value()) {
|
|
*out_error = {VerifyError::StatusCode::CERTIFICATE_INVALID, 0, diagnostic};
|
|
return {};
|
|
}
|
|
std::shared_ptr<const ParsedCertificate> leaf_cert = maybe_leaf.value();
|
|
|
|
int64_t now;
|
|
if (opts.time.has_value()) {
|
|
now = opts.time.value();
|
|
} else {
|
|
now = time(NULL);
|
|
}
|
|
|
|
der::GeneralizedTime verification_time;
|
|
if (!der::EncodePosixTimeAsGeneralizedTime(now, &verification_time)) {
|
|
*out_error = {VerifyError::StatusCode::VERIFICATION_FAILURE, -1,
|
|
"\nCould not encode verification time\n"};
|
|
return {};
|
|
}
|
|
|
|
TrustStore *trust_store = nullptr;
|
|
if (opts.trust_store) {
|
|
trust_store = opts.trust_store->trust_store.get();
|
|
}
|
|
|
|
auto digest_policy = SimplePathBuilderDelegate::DigestPolicy::kStrong;
|
|
// TODO(b/111551631): remove this
|
|
if (opts.insecurely_allow_sha1) {
|
|
digest_policy = SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1;
|
|
}
|
|
|
|
std::chrono::time_point<std::chrono::steady_clock> deadline =
|
|
std::chrono::time_point<std::chrono::steady_clock>::max();
|
|
if (opts.deadline.has_value()) {
|
|
deadline = opts.deadline.value();
|
|
}
|
|
|
|
PathBuilderDelegateImpl path_builder_delegate(
|
|
opts.min_rsa_modulus_length, digest_policy, deadline,
|
|
opts.signature_verify_cache);
|
|
|
|
KeyPurpose key_purpose = KeyPurpose::SERVER_AUTH;
|
|
switch (opts.key_purpose) {
|
|
case CertificateVerifyOptions::KeyPurpose::ANY_EKU:
|
|
key_purpose = KeyPurpose::ANY_EKU;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH:
|
|
key_purpose = KeyPurpose::SERVER_AUTH;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH:
|
|
key_purpose = KeyPurpose::CLIENT_AUTH;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH_STRICT:
|
|
key_purpose = KeyPurpose::SERVER_AUTH_STRICT;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT:
|
|
key_purpose = KeyPurpose::CLIENT_AUTH_STRICT;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::SERVER_AUTH_STRICT_LEAF:
|
|
key_purpose = KeyPurpose::SERVER_AUTH_STRICT_LEAF;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::CLIENT_AUTH_STRICT_LEAF:
|
|
key_purpose = KeyPurpose::CLIENT_AUTH_STRICT_LEAF;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::RCS_MLS_CLIENT_AUTH:
|
|
key_purpose = KeyPurpose::RCS_MLS_CLIENT_AUTH;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::C2PA_TIMESTAMPING:
|
|
key_purpose = KeyPurpose::C2PA_TIMESTAMPING;
|
|
break;
|
|
case CertificateVerifyOptions::KeyPurpose::C2PA_MANIFEST:
|
|
key_purpose = KeyPurpose::C2PA_MANIFEST;
|
|
break;
|
|
}
|
|
CertPathBuilder path_builder(leaf_cert, trust_store, &path_builder_delegate,
|
|
verification_time, key_purpose,
|
|
InitialExplicitPolicy::kFalse,
|
|
/* user_initial_policy_set= */
|
|
{der::Input(kAnyPolicyOid)},
|
|
InitialPolicyMappingInhibit::kFalse,
|
|
InitialAnyPolicyInhibit::kFalse);
|
|
|
|
CertIssuerSourceStatic intermediates;
|
|
for (const std::string_view &cert : opts.intermediates) {
|
|
std::string diag_string;
|
|
std::optional<std::shared_ptr<const ParsedCertificate>> parsed =
|
|
InternalParseCertificate(StringAsBytes(cert), &diag_string);
|
|
if (!parsed.has_value()) {
|
|
if (path_builder_delegate.IsDebugLogEnabled()) {
|
|
path_builder_delegate.DebugLog("skipping bad intermediate: " +
|
|
diag_string);
|
|
}
|
|
continue;
|
|
}
|
|
intermediates.AddCert(std::move(parsed.value()));
|
|
}
|
|
path_builder.AddCertIssuerSource(&intermediates);
|
|
|
|
if (opts.extra_intermediates != nullptr) {
|
|
path_builder.AddCertIssuerSource(opts.extra_intermediates->impl_.get());
|
|
}
|
|
|
|
if (opts.max_iteration_count > 0) {
|
|
path_builder.SetIterationLimit(opts.max_iteration_count);
|
|
}
|
|
|
|
if (opts.max_path_building_depth > 0) {
|
|
path_builder.SetDepthLimit(opts.max_path_building_depth);
|
|
}
|
|
|
|
path_builder.SetExploreAllPaths(all_paths);
|
|
|
|
CertPathBuilder::Result result = path_builder.Run();
|
|
|
|
if (out_status != nullptr) {
|
|
out_status->iteration_count_ = result.iteration_count;
|
|
out_status->max_depth_seen_ = result.max_depth_seen;
|
|
}
|
|
|
|
*out_error = result.GetBestPathVerifyError();
|
|
|
|
if (result.HasValidPath()) {
|
|
std::vector<std::vector<std::string>> ret;
|
|
if (!all_paths) {
|
|
auto best_path = result.GetBestValidPath();
|
|
ret.push_back(std::vector<std::string>());
|
|
for (size_t i = 0; i < best_path->certs.size(); i++) {
|
|
ret[0].emplace_back(BytesAsStringView(best_path->certs[i]->der_cert()));
|
|
}
|
|
return ret;
|
|
}
|
|
for (const auto &path : result.paths) {
|
|
if (!path->IsValid()) {
|
|
continue;
|
|
}
|
|
std::vector<std::string> ret_path;
|
|
for (const auto &cert : path->certs) {
|
|
ret_path.emplace_back(BytesAsStringView(cert->der_cert()));
|
|
}
|
|
ret.push_back(ret_path);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
std::optional<std::vector<std::string>> CertificateVerify(
|
|
const CertificateVerifyOptions &opts, VerifyError *out_error,
|
|
CertificateVerifyStatus *out_status) {
|
|
auto single_path = CertificateVerifyInternal(opts, out_error, out_status,
|
|
/*all_paths=*/false);
|
|
if (!single_path.has_value()) {
|
|
return {};
|
|
}
|
|
return single_path.value()[0];
|
|
}
|
|
|
|
std::optional<std::vector<std::vector<std::string>>> CertificateVerifyAllPaths(
|
|
const CertificateVerifyOptions &opts) {
|
|
return CertificateVerifyInternal(opts, nullptr, nullptr, /*all_paths=*/true);
|
|
}
|
|
|
|
BSSL_NAMESPACE_END
|