diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp index 5cd2e26cb01..f2f9f5dc385 100644 --- a/src/lib/ffi/ffi_cert.cpp +++ b/src/lib/ffi/ffi_cert.cpp @@ -372,20 +372,12 @@ int botan_x509_cert_view_string_values(botan_x509_cert_t cert, return BOTAN_FFI_ERROR_OUT_OF_RANGE; }; - auto wrap_or_empty = [](std::string str) -> std::vector { - if(str.empty()) { - return {}; - } else { - return {std::move(str)}; - } - }; - return BOTAN_FFI_VISIT(cert, [=](const Botan::X509_Certificate& c) -> int { switch(value_type) { case BOTAN_X509_CRL_DISTRIBUTION_URLS: return enumerate_crl_distribution_points(c, index); case BOTAN_X509_OCSP_RESPONDER_URLS: - return enumerate(wrap_or_empty(c.ocsp_responder()), index); + return enumerate(c.ocsp_responders(), index); case BOTAN_X509_CA_ISSUERS_URLS: return enumerate(c.ca_issuers(), index); case BOTAN_X509_PEM_ENCODING: diff --git a/src/lib/x509/x509_ext.cpp b/src/lib/x509/x509_ext.cpp index cea8d51270b..89aa22bf1d6 100644 --- a/src/lib/x509/x509_ext.cpp +++ b/src/lib/x509/x509_ext.cpp @@ -632,9 +632,9 @@ std::vector Authority_Information_Access::encode_inner() const { DER_Encoder der(output); der.start_sequence(); - // OCSP - if(!m_ocsp_responder.empty()) { - const ASN1_String url(m_ocsp_responder, ASN1_Type::Ia5String); + // OCSP Responders + for(const auto& ocsp_responder : m_ocsp_responders) { + const ASN1_String url(ocsp_responder, ASN1_Type::Ia5String); der.start_sequence() .encode(OID::from_string("PKIX.OCSP")) .add_object(ASN1_Type(6), ASN1_Class::ContextSpecific, url.value()) @@ -642,8 +642,8 @@ std::vector Authority_Information_Access::encode_inner() const { } // CA Issuers - for(const auto& ca_isser : m_ca_issuers) { - const ASN1_String asn1_ca_issuer(ca_isser, ASN1_Type::Ia5String); + for(const auto& ca_issuer : m_ca_issuers) { + const ASN1_String asn1_ca_issuer(ca_issuer, ASN1_Type::Ia5String); der.start_sequence() .encode(OID::from_string("PKIX.CertificateAuthorityIssuers")) .add_object(ASN1_Type(6), ASN1_Class::ContextSpecific, asn1_ca_issuer.value()) @@ -668,7 +668,7 @@ void Authority_Information_Access::decode_inner(const std::vector& in) const BER_Object name = info.get_next_object(); if(name.is_a(6, ASN1_Class::ContextSpecific)) { - m_ocsp_responder = ASN1::to_string(name); + m_ocsp_responders.push_back(ASN1::to_string(name)); } } if(oid == OID::from_string("PKIX.CertificateAuthorityIssuers")) { diff --git a/src/lib/x509/x509_ext.h b/src/lib/x509/x509_ext.h index 8a930944887..29930e5eaeb 100644 --- a/src/lib/x509/x509_ext.h +++ b/src/lib/x509/x509_ext.h @@ -322,16 +322,28 @@ class BOTAN_PUBLIC_API(2, 0) Certificate_Policies final : public Certificate_Ext class BOTAN_PUBLIC_API(2, 0) Authority_Information_Access final : public Certificate_Extension { public: std::unique_ptr copy() const override { - return std::make_unique(m_ocsp_responder, m_ca_issuers); + return std::make_unique(m_ocsp_responders, m_ca_issuers); } Authority_Information_Access() = default; + BOTAN_DEPRECATED("Use constructor with list of OCSP responders") explicit Authority_Information_Access(std::string_view ocsp, const std::vector& ca_issuers = std::vector()) : - m_ocsp_responder(ocsp), m_ca_issuers(ca_issuers) {} + m_ocsp_responders{std::string(ocsp)}, m_ca_issuers(ca_issuers) {} - std::string ocsp_responder() const { return m_ocsp_responder; } + explicit Authority_Information_Access(std::vector ocsp_responders, + std::vector ca_issuers = std::vector()) : + m_ocsp_responders(std::move(ocsp_responders)), m_ca_issuers(std::move(ca_issuers)) {} + + BOTAN_DEPRECATED("Use ocsp_responders") std::string ocsp_responder() const { + if(m_ocsp_responders.empty()) { + return {}; + } + return m_ocsp_responders[0]; + } + + const std::vector& ocsp_responders() const { return m_ocsp_responders; } static OID static_oid() { return OID({1, 3, 6, 1, 5, 5, 7, 1, 1}); } @@ -342,12 +354,12 @@ class BOTAN_PUBLIC_API(2, 0) Authority_Information_Access final : public Certifi private: std::string oid_name() const override { return "PKIX.AuthorityInformationAccess"; } - bool should_encode() const override { return (!m_ocsp_responder.empty() || !m_ca_issuers.empty()); } + bool should_encode() const override { return (!m_ocsp_responders.empty() || !m_ca_issuers.empty()); } std::vector encode_inner() const override; void decode_inner(const std::vector& in) override; - std::string m_ocsp_responder; + std::vector m_ocsp_responders; std::vector m_ca_issuers; }; diff --git a/src/lib/x509/x509cert.cpp b/src/lib/x509/x509cert.cpp index e1c35d1eda4..135a75d5290 100644 --- a/src/lib/x509/x509cert.cpp +++ b/src/lib/x509/x509cert.cpp @@ -48,7 +48,7 @@ struct X509_Certificate_Data { std::vector m_cert_policies; std::vector m_crl_distribution_points; - std::string m_ocsp_responder; + std::vector m_ocsp_responders; std::vector m_ca_issuers; std::vector m_issuer_dn_bits_sha256; @@ -265,7 +265,7 @@ std::unique_ptr parse_x509_cert_body(const X509_Object& o } if(const auto* ext = data->m_v3_extensions.get_extension_object_as()) { - data->m_ocsp_responder = ext->ocsp_responder(); + data->m_ocsp_responders = ext->ocsp_responders(); data->m_ca_issuers = ext->ca_issuers(); } @@ -538,7 +538,14 @@ bool X509_Certificate::is_critical(std::string_view ex_name) const { } std::string X509_Certificate::ocsp_responder() const { - return data().m_ocsp_responder; + if(data().m_ocsp_responders.empty()) { + return {}; + } + return data().m_ocsp_responders[0]; +} + +const std::vector& X509_Certificate::ocsp_responders() const { + return data().m_ocsp_responders; } std::vector X509_Certificate::ca_issuers() const { @@ -798,8 +805,12 @@ std::string X509_Certificate::to_string() const { } } - if(!ocsp_responder().empty()) { - out << "OCSP responder " << ocsp_responder() << "\n"; + const auto& ocsp_responders = this->ocsp_responders(); + if(!ocsp_responders.empty()) { + out << "OCSP Responders:\n"; + for(const auto& ocsp_responder : ocsp_responders) { + out << " URI: " << ocsp_responder << "\n"; + } } const std::vector ca_issuers = this->ca_issuers(); diff --git a/src/lib/x509/x509cert.h b/src/lib/x509/x509cert.h index 9513a1c90e5..25c7dbf877d 100644 --- a/src/lib/x509/x509cert.h +++ b/src/lib/x509/x509cert.h @@ -330,7 +330,12 @@ class BOTAN_PUBLIC_API(2, 0) X509_Certificate : public X509_Object { /** * Return the listed address of an OCSP responder, or empty if not set */ - std::string ocsp_responder() const; + BOTAN_DEPRECATED("Use ocsp_responders") std::string ocsp_responder() const; + + /** + * Return the listed addresses of OCSP responders, or empty if not set + */ + const std::vector& ocsp_responders() const; /** * Return the listed addresses of ca issuers, or empty if not set diff --git a/src/tests/data/x509/misc/contains_multiple_ocsp_responders.pem b/src/tests/data/x509/misc/contains_multiple_ocsp_responders.pem new file mode 100644 index 00000000000..ce769d3e43e --- /dev/null +++ b/src/tests/data/x509/misc/contains_multiple_ocsp_responders.pem @@ -0,0 +1,55 @@ +-----BEGIN CERTIFICATE----- +MIIFvzCCA6egAwIBAgIUbNlFYpSi5yeivRmg+5P61FgbKucwDQYJKoZIhvcNAQEL +BQAwLjEsMCoGA1UEAwwjVGVzdCBDZXJ0aWZpY2F0ZSB3aXRoIE11bHRpcGxlIE9D +U1AwHhcNMjYwMTEyMTMxNTE0WhcNMzYwMTEwMTMxNTE0WjAuMSwwKgYDVQQDDCNU +ZXN0IENlcnRpZmljYXRlIHdpdGggTXVsdGlwbGUgT0NTUDCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBAJ1P8yscKZ5lKggYIxMjRrID52eXuo1K1i5MyllF +qljU5cKrsCHXQRNSS1qIc2k3mYwTUerGT7a4uLG/Z6WHA5xgt6Q4ivSmKvcm56Ta +fiSEgHBxG5BrgemUciCbjNSJTX2N+8urrCbLYkuS6+DLfUdTPy7RGEijBJZ4+/rc +7FNiU6WA0O4X5vm6lUBhCKnqE4Yq15bi40fTWW65e7O1mrtyjT7tcfGm4Dla5UCi +oTQ2xedubjP2PPLgjw1yHYNX5y+TRN/A7pZDRv0/irK7AaTjIc69JY3u3kDmVeB1 +fjqSveaCrhjc6ZdEDYSnE3YxSoqrbwU513ArwH9ujtuaiDtj1eIcYOGLoh076j7S +/1iugUhTsDeveHU1fG9idiiktNyRUWRCzMvL97u0og+mQRus4BrsZhukj6VzjOmG +xXLjq/S6mHMG7N2Rk0NyOVomzV+DqfhxT2u53beww8ISJxAFZZOEx1ys9FRJNoc5 +BewbpAi3nRtxE2BPIzL89kDmjDsXuJTipVatN0LlI1KOwXLfjt1fAKIl88cDx0xW +Y5sqiI5vX/vh5doveRzwwyLrCyiPnVzcFL0v9sIFZnFsc0F8FSfnf5yXOX0j2omC +Ymll9FJUeU7tzuiiDNjdz5iOOcLXAYlchcDPGiuOTwJTXLJ5QecGOfXfSJRE5vnk +jgCbAgMBAAGjgdQwgdEwHQYDVR0OBBYEFOI4tY7OLK/QNpmDBjE7lnO6oNG5MB8G +A1UdIwQYMBaAFOI4tY7OLK/QNpmDBjE7lnO6oNG5MAwGA1UdEwQFMAMBAf8wgYAG +CCsGAQUFBwEBBHQwcjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AxLmV4YW1wbGUu +Y29tMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcDIuZXhhbXBsZS5jb20wJAYIKwYB +BQUHMAGGGGh0dHA6Ly9vY3NwMy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsFAAOC +AgEARhrXE1DcGt7UJtH1j0zxMEgfKzgbnolLVgaWMzZ//1sOH6Qk8P2mfOyjm+l3 +4pH6bz/h8+D8TglOKDlcdniwOHXsuP2w4CG9LfQS3YprKH4ltGaQCQ5cL1OMWWq4 +MlQ80tHe43kSbJ6eQF+EZr5WGxEwA8hcAaQ08F6QEiPN1h0zOpTfIa4zU0ExUYGm +dCs1ST0RNQuCkEogKnyl6iFzd4AzshHkmzeqdSjvewsbO+UDwSsy/R5pYfzX2IJE +zg8J77q5fy43WJrcubT4uu2fsUKjHMxjpgdYrkIlLtNByviBj6px/4JK2wrczPJ0 ++hPOa5boE7P+qpimklmBHnOXDo5BTpRCZXwvHQHKB1JHPmLAGLibF4ljzJ32H44h +DUIV/z1P2Jx2duxh6CIgfOJAZBSln2blpHSpuSemt2j1orM+tT+DWuIz+H3uzKm6 +O2TmYGi5R0+wvV8a1ux4HsUMYU9yW+g88LVYRzWWw89g8aRbkVQ6OAAvYgMtJj7d +VDnoCzt9VOU6269w4N2jmQ1IYa3sP60JbdpW+pvfeN35WARmvwcPEBrQYiLl92Fu +e/P6h6L4nMkVS952DJCCDdBceDIh2CPD8t0/XHZqdLRYCaeiOVe+zNYTe6TY1gcx +Soy/P3vJc7UfvNtl0PZx3Fs5UCb5O0RhJXdOn58l8lScCWo= +-----END CERTIFICATE----- + + +openssl req -new -x509 -days 3650 -nodes \ + -newkey rsa:4096 \ + -keyout multi_ocsp_key.pem \ + -out multi_ocsp_cert.pem \ + -config multi_ocsp.cnf + +[ req ] +default_bits = 4096 +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +prompt = no + +[ req_distinguished_name ] +CN = Test Certificate with Multiple OCSP + +[ v3_ca ] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer +basicConstraints = CA:TRUE +authorityInfoAccess = OCSP;URI:http://ocsp1.example.com,OCSP;URI:http://ocsp2.example.com,OCSP;URI:http://ocsp3.example.com diff --git a/src/tests/unit_x509.cpp b/src/tests/unit_x509.cpp index ff4e3ef2a9e..73bb70e4e53 100644 --- a/src/tests/unit_x509.cpp +++ b/src/tests/unit_x509.cpp @@ -490,6 +490,31 @@ Test::Result test_x509_encode_authority_info_access_extension() { result.test_is_true("OCSP URI available", !cert.ocsp_responder().empty()); result.test_is_true("CA Issuer URI available", !cert.ca_issuers().empty()); + + // create a certificate with multiple OCSP URIs + Botan::X509_Cert_Options opts_multi_ocsp = req_opts1(sig_algo); + const std::vector ocsp_uris = {"http://ocsp.example.com", "http://backup-ocsp.example.com"}; + opts_multi_ocsp.extensions.add(std::make_unique(ocsp_uris)); + + req = Botan::X509::create_cert_req(opts_multi_ocsp, *key, hash_fn, *rng); + + cert = ca.sign_request(req, *rng, from_date(-1, 01, 01), from_date(2, 01, 01)); + + const auto* aia_ext = + cert.v3_extensions().get_extension_object_as(); + result.test_is_true("AIA extension present", aia_ext != nullptr); + + const auto ocsp_responders = aia_ext->ocsp_responders(); + result.test_sz_eq("number of OCSP responder URIs", ocsp_responders.size(), 2); + result.test_str_eq("First OCSP responder URI matches", ocsp_responders[0], "http://ocsp.example.com"); + result.test_str_eq("Second OCSP responder URI matches", ocsp_responders[1], "http://backup-ocsp.example.com"); + + const auto cert_ocsp_responders = cert.ocsp_responders(); + result.test_sz_eq("Certificate: number of OCSP responder URIs", cert_ocsp_responders.size(), 2); + result.test_str_eq( + "Certificate: First OCSP responder URI matches", cert_ocsp_responders[0], "http://ocsp.example.com"); + result.test_str_eq( + "Certificate: Second OCSP responder URI matches", cert_ocsp_responders[1], "http://backup-ocsp.example.com"); #endif return result; @@ -686,6 +711,17 @@ Test::Result test_x509_authority_info_access_extension() { "ldap://directory.d-trust.net/CN=Bdrive%20Test%20CA%201-2%202017,O=Bundesdruckerei%20GmbH,C=DE?cACertificate?base?"); result.test_str_eq("OCSP responder URL matches", aia_cert_2ca.ocsp_responder(), "http://staging.ocsp.d-trust.net"); + // contains AIA extension with multiple OCSP responders + const Botan::X509_Certificate aia_cert_multi_ocsp( + Test::data_file("x509/misc/contains_multiple_ocsp_responders.pem")); + + const auto& ocsp_responders_multi = aia_cert_multi_ocsp.ocsp_responders(); + result.test_sz_eq("number of OCSP responders", ocsp_responders_multi.size(), 3); + result.test_str_eq("First OCSP responder URL matches", ocsp_responders_multi[0], "http://ocsp1.example.com"); + result.test_str_eq("Second OCSP responder URL matches", ocsp_responders_multi[1], "http://ocsp2.example.com"); + result.test_str_eq("Third OCSP responder URL matches", ocsp_responders_multi[2], "http://ocsp3.example.com"); + result.test_is_true("no CA Issuer URI available", aia_cert_multi_ocsp.ca_issuers().empty()); + return result; }