Add public API for a certificate. This is effectively a derivation of certificate.cc from chromium_certificate_verifier, without absl. Bug: 660 Change-Id: I628827a00ab80a733582eee5a25e048dd65bf6b9 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/64171 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: Bob Beck <bbe@google.com>
diff --git a/include/openssl/pki/certificate.h b/include/openssl/pki/certificate.h new file mode 100644 index 0000000..7d41d64 --- /dev/null +++ b/include/openssl/pki/certificate.h
@@ -0,0 +1,84 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#ifndef OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_ +#define OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_ + +#include <memory> +#include <string> +#include <string_view> +#include <vector> + +#include <openssl/base.h> +#include <openssl/span.h> + +namespace bssl { + +struct CertificateInternals; + +// Certificate represents a parsed X.509 certificate. It includes accessors for +// the various things that one might want to extract from a certificate, +class OPENSSL_EXPORT Certificate { + public: + Certificate(Certificate&& other); + Certificate(const Certificate& other) = delete; + ~Certificate(); + Certificate& operator=(const Certificate& other) = delete; + + // FromDER returns a certificate from an DER-encoded X.509 object in |der|. + // In the event of a failure, it will return no value, and |out_diagnostic| + // may be set to a string of human readable debugging information if + // information abou the failure is available. + static std::unique_ptr<Certificate> FromDER( + bssl::Span<const uint8_t> der, std::string *out_diagnostic); + + // FromPEM returns a certificate from the first CERTIFICATE PEM block in + // |pem|. In the event of a failure, it will return no value, and + // |out_diagnostic| may be set to a string of human readable debugging + // informtion if informaiton about the failuew is available. + static std::unique_ptr<Certificate> FromPEM( + std::string_view pem, std::string *out_diagnostic); + + // IsSelfIssued returns true if the certificate is "self-issued" per RFC 5280 + // section 6.1. I.e. that the subject and issuer names are equal after + // canonicalization (and no other checks). + // + // Other contexts may have a different notion such as "self signed" which + // may or may not be this, and may check other properties of the certificate. + bool IsSelfIssued() const; + + // Validity specifies the temporal validity of a cerificate, expressed in + // POSIX time values of seconds since the POSIX epoch. The certificate is + // valid at POSIX time t in second granularity, where not_before <= t <= + // not_after. + struct Validity { + int64_t not_before; + int64_t not_after; + }; + + Validity GetValidity() const; + + // The binary, big-endian, DER representation of the certificate serial + // number. It may include a leading 00 byte. + bssl::Span<const uint8_t> GetSerialNumber() const; + + private: + explicit Certificate(std::unique_ptr<CertificateInternals> internals); + + std::unique_ptr<CertificateInternals> internals_; +}; + +} // namespace bssl + +#endif // OPENSSL_HEADER_BSSL_PKI_CERTIFICATE_H_
diff --git a/pki/certificate.cc b/pki/certificate.cc new file mode 100644 index 0000000..db1c627 --- /dev/null +++ b/pki/certificate.cc
@@ -0,0 +1,117 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include <optional> +#include <string_view> + +#include <openssl/pki/certificate.h> +#include <openssl/pool.h> + +#include "cert_errors.h" +#include "encode_values.h" +#include "parsed_certificate.h" +#include "pem.h" +#include "parse_values.h" + +namespace bssl { + +namespace { + +std::shared_ptr<const bssl::ParsedCertificate> ParseCertificateFromDer( + bssl::Span<const uint8_t>cert, std::string *out_diagnostic) { + bssl::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. davidben explains + // why: + // + // 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; + + bssl::UniquePtr<CRYPTO_BUFFER> buffer( + CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr)); + bssl::CertErrors errors; + std::shared_ptr<const bssl::ParsedCertificate> parsed_cert( + bssl::ParsedCertificate::Create(std::move(buffer), default_options, &errors)); + if (!parsed_cert) { + *out_diagnostic = errors.ToDebugString(); + return nullptr; + } + return parsed_cert; +} + +} // namespace + +struct CertificateInternals { + std::shared_ptr<const bssl::ParsedCertificate> cert; +}; + +Certificate::Certificate(std::unique_ptr<CertificateInternals> internals) + : internals_(std::move(internals)) {} +Certificate::~Certificate() = default; +Certificate::Certificate(Certificate&& other) = default; + +std::unique_ptr<Certificate> Certificate::FromDER(bssl::Span<const uint8_t> der, + std::string *out_diagnostic) { + std::shared_ptr<const bssl::ParsedCertificate> result = + ParseCertificateFromDer(der, out_diagnostic); + if (result == nullptr) { + return nullptr; + } + + auto internals = std::make_unique<CertificateInternals>(); + internals->cert = std::move(result); + std::unique_ptr<Certificate> ret(new Certificate(std::move(internals))); + return ret; +} + +std::unique_ptr<Certificate> Certificate::FromPEM(std::string_view pem, + std::string *out_diagnostic) { + bssl::PEMTokenizer tokenizer(pem, {"CERTIFICATE"}); + if (!tokenizer.GetNext()) { + return nullptr; + } + return FromDER(StringAsBytes(tokenizer.data()), out_diagnostic); +} + +bool Certificate::IsSelfIssued() const { + return internals_->cert->normalized_subject() == + internals_->cert->normalized_issuer(); +} + +Certificate::Validity Certificate::GetValidity() const { + Certificate::Validity validity; + + // As this is a previously parsed certificate, we know the not_before + // and not after are valid, so these conversions can not fail. + (void) GeneralizedTimeToPosixTime( + internals_->cert->tbs().validity_not_before, &validity.not_before); + (void) GeneralizedTimeToPosixTime( + internals_->cert->tbs().validity_not_after, &validity.not_after); + return validity; +} + +bssl::Span<const uint8_t> Certificate::GetSerialNumber() const { + return internals_->cert->tbs().serial_number; +} + +} // namespace boringssl
diff --git a/pki/certificate_unittest.cc b/pki/certificate_unittest.cc new file mode 100644 index 0000000..57a51fa --- /dev/null +++ b/pki/certificate_unittest.cc
@@ -0,0 +1,76 @@ +/* Copyright (c) 2023, Google Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ + +#include <optional> +#include <string> +#include <string_view> + +#include <openssl/pki/certificate.h> +#include <gmock/gmock.h> + +#include "string_util.h" +#include "test_helpers.h" + +TEST(CertificateTest, FromPEM) { + std::string diagnostic; + std::unique_ptr<bssl::Certificate> cert( + bssl::Certificate::FromPEM("nonsense", &diagnostic)); + EXPECT_FALSE(cert); + + cert = bssl::Certificate::FromPEM(bssl::ReadTestFileToString( + "testdata/verify_unittest/self-issued.pem"), &diagnostic); + EXPECT_TRUE(cert); +} + +TEST(CertificateTest, IsSelfIssued) { + std::string diagnostic; + const std::string leaf = + bssl::ReadTestFileToString("testdata/verify_unittest/google-leaf.der"); + std::unique_ptr<bssl::Certificate> leaf_cert( + bssl::Certificate::FromDER(bssl::StringAsBytes(leaf), &diagnostic)); + EXPECT_TRUE(leaf_cert); + EXPECT_FALSE(leaf_cert->IsSelfIssued()); + + const std::string self_issued = + bssl::ReadTestFileToString("testdata/verify_unittest/self-issued.pem"); + std::unique_ptr<bssl::Certificate> self_issued_cert( + bssl::Certificate::FromPEM(self_issued, &diagnostic)); + EXPECT_TRUE(self_issued_cert); + EXPECT_TRUE(self_issued_cert->IsSelfIssued()); +} + +TEST(CertificateTest, Validity) { + std::string diagnostic; + const std::string leaf = + bssl::ReadTestFileToString("testdata/verify_unittest/google-leaf.der"); + std::unique_ptr<bssl::Certificate> cert( + bssl::Certificate::FromDER(bssl::StringAsBytes(leaf), &diagnostic)); + EXPECT_TRUE(cert); + + bssl::Certificate::Validity validity = cert->GetValidity(); + EXPECT_EQ(validity.not_before, 1498644466); + EXPECT_EQ(validity.not_after, 1505899620); +} + +TEST(CertificateTest, SerialNumber) { + std::string diagnostic; + const std::string leaf = + bssl::ReadTestFileToString("testdata/verify_unittest/google-leaf.der"); + std::unique_ptr<bssl::Certificate> cert( + bssl::Certificate::FromDER(bssl::StringAsBytes(leaf), &diagnostic)); + EXPECT_TRUE(cert); + + EXPECT_EQ(bssl::string_util::HexEncode(cert->GetSerialNumber()), + "0118F044A8F31892"); +}
diff --git a/pki/testdata/verify_unittest/google-leaf.der b/pki/testdata/verify_unittest/google-leaf.der new file mode 100644 index 0000000..01eea35 --- /dev/null +++ b/pki/testdata/verify_unittest/google-leaf.der Binary files differ
diff --git a/pki/testdata/verify_unittest/self-issued.pem b/pki/testdata/verify_unittest/self-issued.pem new file mode 100644 index 0000000..e219b6d --- /dev/null +++ b/pki/testdata/verify_unittest/self-issued.pem
@@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDrTCCApWgAwIBAgIUcu/NYcYTwG79IqzpuKoOf0Jyu1UwDQYJKoZIhvcNAQEL +BQAwZjELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEfMB0GA1UEAwwWSXNTZWxmSXNzdWVk +IHRlc3QgY2VydDAeFw0yMjA0MDcyMzIyMzRaFw0yMzA0MDcyMzIyMzRaMGYxCzAJ +BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQxHzAdBgNVBAMMFklzU2VsZklzc3VlZCB0ZXN0IGNl +cnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv6ueRHng0bJsKdDSg +YP1wfpao+dGZ6iY0wfDaLGY2SUgHn7HCvGeS1btZ7lCdCBdRTd6OjrMJkaoPXOCG +rqUVv9/WBPyYgpA+Zlm3F8Uf8vuYUZ96ZOdvHBeAO8Ac9wFoZhC3VNwVF0mynlVb +AhMu1T33Pi387jaE2OMMAle2w4zApcEMbMvlgEk1UdlpL0PBT4nT6XIoCbO04tEG +KLMnGutdHUmVW3L+77hjy/H4LhfsZb7PD3xqFkWpspP3OgfWJU0NUiAX/RIbgKTe +pYNVUElAGgchziGQyDJ8Si7rXslCIx6m/qMD9+JX4hZa+ANPf9psEAVjt8TKhozi +TCBHAgMBAAGjUzBRMB0GA1UdDgQWBBQludvSp1hD9JPiK6JyzuROz9S95zAfBgNV +HSMEGDAWgBQludvSp1hD9JPiK6JyzuROz9S95zAPBgNVHRMBAf8EBTADAQH/MA0G +CSqGSIb3DQEBCwUAA4IBAQA3a4h15aX4xbinxNUNDNRoqrHL5J/nwFgZxTKvzvC+ +I+evxJr3b2vWip70nepiDYW9XyzDD3rr18P/XlacxaBWy2L6iwAc0SDHHUwPJtid +7av3VjC3Qz8kk3gIuLb9SMJ9eQweKzox7UF6ZJhoEhSMDkyvbHmkMYmQGjIwuVvG +GZm0Df1XYiBQyqj6jqsTkZeqlWyFkpuFMOOSvFwWV5rOt0WrIYB/iIbHqXtePGU1 +aE2CZqTTTCldn4gvMjtAAJ5D/JXY788a0apaEG5FjU1XzVHzWbKm0bz7y1cEPBhQ +iEgWK2iloE3NzPABUZIcqdQF5NUiP45i396ccRGuNxY5 +-----END CERTIFICATE-----
diff --git a/sources.cmake b/sources.cmake index 6afc739..febdb31 100644 --- a/sources.cmake +++ b/sources.cmake
@@ -359,6 +359,7 @@ pki/cert_error_params.cc pki/cert_errors.cc pki/cert_issuer_source_static.cc + pki/certificate.cc pki/certificate_policies.cc pki/common_cert_errors.cc pki/crl.cc @@ -395,6 +396,7 @@ crypto/test/gtest_main.cc pki/cert_issuer_source_static_unittest.cc + pki/certificate_unittest.cc pki/certificate_policies_unittest.cc pki/crl_unittest.cc pki/encode_values_unittest.cc