| // 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{}; | |
| // TODO(crbug.com/533048005): Remove this option. It now only controls serials | |
| // that aren't even valid integers. Historically, people needed to set it to | |
| // tolerate length over 20, but this is now always enabled. | |
| 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() {} | |
| 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; | |
| } | |
| 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(nullptr); | |
| } | |
| 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 |