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