Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/migration_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ should either call a specific function on ``X509_Certificate`` which returns the
same information, or lacking that, iterate over the result of
``X509_Certificate::v3_extensions``.

OCSP Response Validation
------------------------

After mitigating CVE-2022-43705 the OCSP response signature validation was refactored.
This led to the removal of the `OCSP::Response::check_signature()` method. If you
must validate OCSP responses directly in your application please use the new method
`OCSP::Response::find_signing_certificate()` and `OCSP::Response::verify_signature()`.

Use of ``enum class``
--------------------------------

Expand Down
1 change: 1 addition & 0 deletions src/build-data/oids.txt
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@

1.3.6.1.5.5.7.48.1 = PKIX.OCSP
1.3.6.1.5.5.7.48.1.1 = PKIX.OCSP.BasicResponse
1.3.6.1.5.5.7.48.1.5 = PKIX.OCSP.NoCheck
1.3.6.1.5.5.7.48.2 = PKIX.CertificateAuthorityIssuers

1.3.6.1.4.1.311.20.2.2 = Microsoft SmartcardLogon
Expand Down
2 changes: 1 addition & 1 deletion src/cli/x509.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ class OCSP_Check final : public Command

Botan::Certificate_Store_In_Memory cas;
cas.add_certificate(issuer);
Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, &cas, timeout);
Botan::OCSP::Response resp = Botan::OCSP::online_check(issuer, subject, timeout);

auto status = resp.status_for(issuer, subject, std::chrono::system_clock::now());

Expand Down
4 changes: 3 additions & 1 deletion src/lib/asn1/oid_maps.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* OID maps
*
* This file was automatically generated by src/scripts/oids.py on 2022-01-10
* This file was automatically generated by ./src/scripts/oids.py on 2022-09-19
*
* All manual edits to this file will be lost. Edit the script
* then regenerate this source file.
Expand Down Expand Up @@ -187,6 +187,7 @@ std::unordered_map<std::string, std::string> OIDS::load_oid2str_map()
{ "1.3.6.1.5.5.7.3.9", "PKIX.OCSPSigning" },
{ "1.3.6.1.5.5.7.48.1", "PKIX.OCSP" },
{ "1.3.6.1.5.5.7.48.1.1", "PKIX.OCSP.BasicResponse" },
{ "1.3.6.1.5.5.7.48.1.5", "PKIX.OCSP.NoCheck" },
{ "1.3.6.1.5.5.7.48.2", "PKIX.CertificateAuthorityIssuers" },
{ "1.3.6.1.5.5.7.8.5", "PKIX.XMPPAddr" },
{ "2.16.840.1.101.3.4.1.2", "AES-128/CBC" },
Expand Down Expand Up @@ -391,6 +392,7 @@ std::unordered_map<std::string, OID> OIDS::load_str2oid_map()
{ "PKIX.IPsecUser", OID({1,3,6,1,5,5,7,3,7}) },
{ "PKIX.OCSP", OID({1,3,6,1,5,5,7,48,1}) },
{ "PKIX.OCSP.BasicResponse", OID({1,3,6,1,5,5,7,48,1,1}) },
{ "PKIX.OCSP.NoCheck", OID({1,3,6,1,5,5,7,48,1,5}) },
{ "PKIX.OCSPSigning", OID({1,3,6,1,5,5,7,3,9}) },
{ "PKIX.ServerAuth", OID({1,3,6,1,5,5,7,3,1}) },
{ "PKIX.TimeStamping", OID({1,3,6,1,5,5,7,3,8}) },
Expand Down
2 changes: 2 additions & 0 deletions src/lib/x509/cert_status.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const char* to_string(Certificate_Status_Code code)
return "OCSP URL not available";
case Certificate_Status_Code::OCSP_SERVER_NOT_AVAILABLE:
return "OCSP server not available";
case Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED:
return "OCSP issuer is not trustworthy";

case Certificate_Status_Code::NO_REVOCATION_DATA:
return "No revocation data";
Expand Down
125 changes: 38 additions & 87 deletions src/lib/x509/ocsp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
#include <botan/pubkey.h>
#include <botan/internal/parsing.h>

#include <functional>

#if defined(BOTAN_HAS_HTTP_UTIL)
#include <botan/internal/http_util.h>
#endif
Expand Down Expand Up @@ -89,16 +91,14 @@ std::string Request::base64_encode() const
}

Response::Response(Certificate_Status_Code status)
: m_status(Response_Status_Code::Successful)
, m_dummy_response_status(status)
{
m_status = Response_Status_Code::Successful;
m_dummy_response_status = status;
}

Response::Response(const uint8_t response_bits[], size_t response_bits_len) :
m_response_bits(response_bits, response_bits + response_bits_len)
{
m_dummy_response_status = Certificate_Status_Code::OCSP_RESPONSE_INVALID;

BER_Decoder response_outer = BER_Decoder(m_response_bits).start_sequence();

size_t resp_status = 0;
Expand Down Expand Up @@ -154,8 +154,14 @@ Response::Response(const uint8_t response_bits[], size_t response_bits_len) :

Certificate_Status_Code Response::verify_signature(const X509_Certificate& issuer) const
{
if (m_responses.empty())
return m_dummy_response_status;
if(m_dummy_response_status)
return m_dummy_response_status.value();

if(m_signer_name.empty() && m_key_hash.empty())
return Certificate_Status_Code::OCSP_RESPONSE_INVALID;

if(!is_issued_by(issuer))
return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND;

try
{
Expand Down Expand Up @@ -183,96 +189,52 @@ Certificate_Status_Code Response::verify_signature(const X509_Certificate& issue
}
}

Certificate_Status_Code Response::check_signature(const std::vector<Certificate_Store*>& trusted_roots,
const std::vector<X509_Certificate>& ee_cert_path) const
{
if (m_responses.empty())
return m_dummy_response_status;

std::optional<X509_Certificate> signing_cert;
std::optional<X509_Certificate>
Response::find_signing_certificate(const X509_Certificate& issuer_certificate,
const Certificate_Store* trusted_ocsp_responders) const
{
using namespace std::placeholders;

for(const auto& trusted_root : trusted_roots)
// Check whether the CA issuing the certificate in question also signed this
if(is_issued_by(issuer_certificate))
{
if(m_signer_name.empty() && m_key_hash.empty())
return Certificate_Status_Code::OCSP_RESPONSE_INVALID;

if(!m_signer_name.empty())
{
signing_cert = trusted_root->find_cert(m_signer_name, std::vector<uint8_t>());
if(signing_cert)
{
break;
}
}

if(!m_key_hash.empty())
{
signing_cert = trusted_root->find_cert_by_pubkey_sha1(m_key_hash);
if(signing_cert)
{
break;
}
}
return issuer_certificate;
}

if(!signing_cert && ee_cert_path.size() > 1)
// Then try to find a delegated responder certificate in the stapled certs
auto match = std::find_if(m_certs.begin(), m_certs.end(), std::bind(&Response::is_issued_by, this, _1));
if(match != m_certs.end())
{
// End entity cert is not allowed to sign their own OCSP request :)
for(size_t i = 1; i < ee_cert_path.size(); ++i)
{
// Check all CA certificates in the (assumed validated) EE cert path
if(!m_signer_name.empty() && ee_cert_path[i].subject_dn() == m_signer_name)
{
signing_cert = ee_cert_path[i];
break;
}

if(!m_key_hash.empty() && ee_cert_path[i].subject_public_key_bitstring_sha1() == m_key_hash)
{
signing_cert = ee_cert_path[i];
break;
}
}
return *match;
}

if(!signing_cert && !m_certs.empty())
// Last resort: check the additionally provides trusted OCSP responders
if(trusted_ocsp_responders)
{
for(const auto& cert : m_certs)
std::optional<X509_Certificate> signing_cert;
if(!m_key_hash.empty() && (signing_cert = trusted_ocsp_responders->find_cert_by_pubkey_sha1(m_key_hash)))
{
// Check all CA certificates in the (assumed validated) EE cert path
if(!m_signer_name.empty() && cert.subject_dn() == m_signer_name)
{
signing_cert = cert;
break;
}

if(!m_key_hash.empty() && cert.subject_public_key_bitstring_sha1() == m_key_hash)
{
signing_cert = cert;
break;
}
return signing_cert;
}
}

if(!signing_cert)
return Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND;

if(!signing_cert->allowed_usage(CRL_SIGN) &&
!signing_cert->allowed_extended_usage("PKIX.OCSPSigning"))
{
return Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE;
if(!m_signer_name.empty() && (signing_cert = trusted_ocsp_responders->find_cert(m_signer_name, {})))
{
return signing_cert;
}
}

return this->verify_signature(*signing_cert);
return std::nullopt;
}


Certificate_Status_Code Response::status_for(const X509_Certificate& issuer,
const X509_Certificate& subject,
std::chrono::system_clock::time_point ref_time,
std::chrono::seconds max_age) const
{
if(m_responses.empty())
{ return m_dummy_response_status; }
if(m_dummy_response_status)
{ return m_dummy_response_status.value(); }

for(const auto& response : m_responses)
{
Expand Down Expand Up @@ -309,7 +271,6 @@ Certificate_Status_Code Response::status_for(const X509_Certificate& issuer,
Response online_check(const X509_Certificate& issuer,
const BigInt& subject_serial,
const std::string& ocsp_responder,
Certificate_Store* trusted_roots,
std::chrono::milliseconds timeout)
{
if(ocsp_responder.empty())
Expand All @@ -327,21 +288,12 @@ Response online_check(const X509_Certificate& issuer,

// Check the MIME type?

OCSP::Response response(http.body());

std::vector<Certificate_Store*> trusted_roots_vec;
trusted_roots_vec.push_back(trusted_roots);

if(trusted_roots)
response.check_signature(trusted_roots_vec);

return response;
return OCSP::Response(http.body());
}


Response online_check(const X509_Certificate& issuer,
const X509_Certificate& subject,
Certificate_Store* trusted_roots,
std::chrono::milliseconds timeout)
{
if(subject.issuer_dn() != issuer.subject_dn())
Expand All @@ -350,7 +302,6 @@ Response online_check(const X509_Certificate& issuer,
return online_check(issuer,
BigInt::decode(subject.serial_number()),
subject.ocsp_responder(),
trusted_roots,
timeout);
}

Expand Down
62 changes: 40 additions & 22 deletions src/lib/x509/ocsp.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@
#include <botan/pkix_types.h>
#include <botan/x509cert.h>
#include <botan/bigint.h>

#include <chrono>
#include <optional>

namespace Botan {

Expand Down Expand Up @@ -159,23 +161,31 @@ class BOTAN_PUBLIC_API(2,0) Response final
size_t response_bits_len);

/**
* Check signature and return status
* The optional cert_path is the (already validated!) certificate path of
* the end entity which is being inquired about
* @param trust_roots list of certstores containing trusted roots
* @param cert_path optionally, the (already verified!) certificate path for the certificate
* this is an OCSP response for. This is necessary to find the correct intermediate CA in
* some cases.
* Find the certificate that signed this OCSP response from all possible
* candidates and taking the attached certificates into account.
*
* @param issuer_certificate is the issuer of the certificate in question
* @param trusted_ocsp_responders optionally, a certificate store containing
* additionally trusted responder certificates
*
* @return the certificate that signed this response or std::nullopt if not found
*/
Certificate_Status_Code check_signature(const std::vector<Certificate_Store*>& trust_roots,
const std::vector<X509_Certificate>& cert_path = {}) const;
std::optional<X509_Certificate>
find_signing_certificate(const X509_Certificate& issuer_certificate,
const Certificate_Store* trusted_ocsp_responders = nullptr) const;

/**
* Verify that issuer's key signed this response
* @param issuer certificate of issuer
* @return if signature valid OCSP_SIGNATURE_OK else an error code
* Check signature of the OCSP response.
*
* Note: It is the responsibility of the caller to verify that signing
* certificate is trustworthy and authorized to do so.
*
* @param signing_certificate the certificate that signed this response
* (@sa Response::find_signing_certificate).
*
* @return status code indicating the validity of the signature
*/
Certificate_Status_Code verify_signature(const X509_Certificate& issuer) const;
Certificate_Status_Code verify_signature(const X509_Certificate& signing_certificate) const;

/**
* @return the status of the response
Expand Down Expand Up @@ -225,6 +235,18 @@ class BOTAN_PUBLIC_API(2,0) Response final
*/
const std::vector<X509_Certificate> &certificates() const { return m_certs; }

/**
* @return the dummy response if this is a 'fake' OCSP response otherwise std::nullopt
*/
std::optional<Certificate_Status_Code> dummy_status() const { return m_dummy_response_status; }

private:
bool is_issued_by(const X509_Certificate& candidate) const
{
return (!m_signer_name.empty() && candidate.subject_dn() == m_signer_name) ||
(!m_key_hash.empty() && candidate.subject_public_key_bitstring_sha1() == m_key_hash);
}

private:
Response_Status_Code m_status;
std::vector<uint8_t> m_response_bits;
Expand All @@ -238,39 +260,35 @@ class BOTAN_PUBLIC_API(2,0) Response final

std::vector<SingleResponse> m_responses;

Certificate_Status_Code m_dummy_response_status;
std::optional<Certificate_Status_Code> m_dummy_response_status;
};

#if defined(BOTAN_HAS_HTTP_UTIL)

/**
* Makes an online OCSP request via HTTP and returns the OCSP response.
* Makes an online OCSP request via HTTP and returns the (unverified) OCSP response.
* @param issuer issuer certificate
* @param subject_serial the subject's serial number
* @param ocsp_responder the OCSP responder to query
* @param trusted_roots trusted roots for the OCSP response
* @param timeout a timeout on the HTTP request
* @return OCSP response
*/
BOTAN_PUBLIC_API(2,1)
BOTAN_PUBLIC_API(3,0)
Response online_check(const X509_Certificate& issuer,
const BigInt& subject_serial,
const std::string& ocsp_responder,
Certificate_Store* trusted_roots,
std::chrono::milliseconds timeout = std::chrono::milliseconds(3000));

/**
* Makes an online OCSP request via HTTP and returns the OCSP response.
* Makes an online OCSP request via HTTP and returns the (unverified) OCSP response.
* @param issuer issuer certificate
* @param subject subject certificate
* @param trusted_roots trusted roots for the OCSP response
* @param timeout a timeout on the HTTP request
* @return OCSP response
*/
BOTAN_PUBLIC_API(2,0)
BOTAN_PUBLIC_API(3,0)
Response online_check(const X509_Certificate& issuer,
const X509_Certificate& subject,
Certificate_Store* trusted_roots,
std::chrono::milliseconds timeout = std::chrono::milliseconds(3000));

#endif
Expand Down
1 change: 1 addition & 0 deletions src/lib/x509/pkix_enums.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ enum class Certificate_Status_Code {
UNTRUSTED_HASH = 1001,
NO_REVOCATION_DATA = 1002,
NO_MATCHING_CRLDP = 1003,
OCSP_ISSUER_NOT_TRUSTED = 1004,

// Time problems
CERT_NOT_YET_VALID = 2000,
Expand Down
Loading