mirror of
https://github.com/openembedded/meta-openembedded.git
synced 2026-06-13 17:39:57 +00:00
botan: patch CVE-2026-32883
Details: https://nvd.nist.gov/vuln/detail/CVE-2026-32883 Backport the patch that was identified by Debian[1]. The included test passed successfully (along with the other tests). [1]: https://security-tracker.debian.org/tracker/CVE-2026-32883 Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com> Signed-off-by: Anuj Mittal <anuj.mittal@oss.qualcomm.com>
This commit is contained in:
committed by
Anuj Mittal
parent
c4b5bca1e8
commit
70a903c888
@@ -0,0 +1,171 @@
|
|||||||
|
From 38a49aafc8115ddb6275ca6bbb8748b3d7b3064d Mon Sep 17 00:00:00 2001
|
||||||
|
From: Jack Lloyd <jack@randombit.net>
|
||||||
|
Date: Sun, 15 Mar 2026 10:28:52 -0400
|
||||||
|
Subject: [PATCH] Fix OCSP response verification
|
||||||
|
|
||||||
|
During a refactoring of OCSP (#3067) the check that the OCSP response signature
|
||||||
|
is valid was omitted. This allows OCSP responses to be forged.
|
||||||
|
|
||||||
|
CVE: CVE-2026-32883
|
||||||
|
Upstream-Status: Backport [https://github.com/randombit/botan/commit/acbffadcede18b36eea42beae57e6cae4b4da4a0]
|
||||||
|
Signed-off-by: Gyorgy Sarvari <skandigraun@gmail.com>
|
||||||
|
---
|
||||||
|
src/lib/x509/x509path.cpp | 67 +++++++++++++++++++++++----------------
|
||||||
|
src/tests/test_ocsp.cpp | 47 +++++++++++++++++++++++++++
|
||||||
|
2 files changed, 87 insertions(+), 27 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/src/lib/x509/x509path.cpp b/src/lib/x509/x509path.cpp
|
||||||
|
index 4059d32..8212bb2 100644
|
||||||
|
--- a/src/lib/x509/x509path.cpp
|
||||||
|
+++ b/src/lib/x509/x509path.cpp
|
||||||
|
@@ -298,6 +298,41 @@ Certificate_Status_Code verify_ocsp_signing_cert(const X509_Certificate& signing
|
||||||
|
return validation_result.result();
|
||||||
|
}
|
||||||
|
|
||||||
|
+std::set<Certificate_Status_Code> evaluate_ocsp_response(const OCSP::Response& ocsp_response,
|
||||||
|
+ const X509_Certificate& subject,
|
||||||
|
+ const X509_Certificate& ca,
|
||||||
|
+ const std::vector<X509_Certificate>& cert_path,
|
||||||
|
+ const std::vector<Certificate_Store*>& certstores,
|
||||||
|
+ std::chrono::system_clock::time_point ref_time,
|
||||||
|
+ const Path_Validation_Restrictions& restrictions) {
|
||||||
|
+ // Handle softfail conditions (eg. OCSP unavailable)
|
||||||
|
+ if(auto dummy_status = ocsp_response.dummy_status()) {
|
||||||
|
+ return {dummy_status.value()};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Find the certificate that signed this OCSP response
|
||||||
|
+ auto signing_cert = ocsp_response.find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
|
||||||
|
+ if(!signing_cert) {
|
||||||
|
+ return {Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Verify the signing certificate is trusted
|
||||||
|
+ auto cert_status = verify_ocsp_signing_cert(
|
||||||
|
+ signing_cert.value(), ca, concat(ocsp_response.certificates(), cert_path), certstores, ref_time, restrictions);
|
||||||
|
+ if(cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) {
|
||||||
|
+ return {cert_status, Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Verify the cryptographic signature on the OCSP response
|
||||||
|
+ auto sig_status = ocsp_response.verify_signature(signing_cert.value());
|
||||||
|
+ if(sig_status != Certificate_Status_Code::OCSP_SIGNATURE_OK) {
|
||||||
|
+ return {sig_status};
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // All checks passed, return the certificate's revocation status
|
||||||
|
+ return {ocsp_response.status_for(ca, subject, ref_time, restrictions.max_ocsp_age())};
|
||||||
|
+}
|
||||||
|
+
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>& cert_path,
|
||||||
|
@@ -312,38 +347,16 @@ CertificatePathStatusCodes PKIX::check_ocsp(const std::vector<X509_Certificate>&
|
||||||
|
CertificatePathStatusCodes cert_status(cert_path.size() - 1);
|
||||||
|
|
||||||
|
for(size_t i = 0; i != cert_path.size() - 1; ++i) {
|
||||||
|
- std::set<Certificate_Status_Code>& status = cert_status.at(i);
|
||||||
|
-
|
||||||
|
const X509_Certificate& subject = cert_path.at(i);
|
||||||
|
const X509_Certificate& ca = cert_path.at(i + 1);
|
||||||
|
|
||||||
|
- if(i < ocsp_responses.size() && (ocsp_responses.at(i) != std::nullopt) &&
|
||||||
|
- (ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful)) {
|
||||||
|
+ if(i < ocsp_responses.size() && ocsp_responses.at(i).has_value() &&
|
||||||
|
+ ocsp_responses.at(i)->status() == OCSP::Response_Status_Code::Successful) {
|
||||||
|
try {
|
||||||
|
- const auto& ocsp_response = ocsp_responses.at(i);
|
||||||
|
-
|
||||||
|
- if(auto dummy_status = ocsp_response->dummy_status()) {
|
||||||
|
- // handle softfail conditions
|
||||||
|
- status.insert(dummy_status.value());
|
||||||
|
- } else if(auto signing_cert =
|
||||||
|
- ocsp_response->find_signing_certificate(ca, restrictions.trusted_ocsp_responders());
|
||||||
|
- !signing_cert) {
|
||||||
|
- status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
|
||||||
|
- } else if(auto ocsp_signing_cert_status =
|
||||||
|
- verify_ocsp_signing_cert(signing_cert.value(),
|
||||||
|
- ca,
|
||||||
|
- concat(ocsp_response->certificates(), cert_path),
|
||||||
|
- certstores,
|
||||||
|
- ref_time,
|
||||||
|
- restrictions);
|
||||||
|
- ocsp_signing_cert_status > Certificate_Status_Code::FIRST_ERROR_STATUS) {
|
||||||
|
- status.insert(ocsp_signing_cert_status);
|
||||||
|
- status.insert(Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
|
||||||
|
- } else {
|
||||||
|
- status.insert(ocsp_response->status_for(ca, subject, ref_time, restrictions.max_ocsp_age()));
|
||||||
|
- }
|
||||||
|
+ cert_status.at(i) = evaluate_ocsp_response(
|
||||||
|
+ ocsp_responses.at(i).value(), subject, ca, cert_path, certstores, ref_time, restrictions);
|
||||||
|
} catch(Exception&) {
|
||||||
|
- status.insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
|
||||||
|
+ cert_status.at(i).insert(Certificate_Status_Code::OCSP_RESPONSE_INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
diff --git a/src/tests/test_ocsp.cpp b/src/tests/test_ocsp.cpp
|
||||||
|
index 06383cd..54b2c03 100644
|
||||||
|
--- a/src/tests/test_ocsp.cpp
|
||||||
|
+++ b/src/tests/test_ocsp.cpp
|
||||||
|
@@ -408,6 +408,52 @@ class OCSP_Tests final : public Test {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ static Test::Result test_forged_ocsp_signature_is_rejected() {
|
||||||
|
+ Test::Result result("OCSP response with forged signature is rejected by path validation");
|
||||||
|
+
|
||||||
|
+ auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
|
||||||
|
+ auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
|
||||||
|
+ auto trust_root = load_test_X509_cert("x509/ocsp/geotrust.pem");
|
||||||
|
+
|
||||||
|
+ const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};
|
||||||
|
+
|
||||||
|
+ Botan::Certificate_Store_In_Memory certstore;
|
||||||
|
+ certstore.add_certificate(trust_root);
|
||||||
|
+
|
||||||
|
+ const auto valid_time = Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint();
|
||||||
|
+
|
||||||
|
+ // Verify the unmodified response is accepted
|
||||||
|
+ {
|
||||||
|
+ auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");
|
||||||
|
+ const auto ocsp_status = Botan::PKIX::check_ocsp(
|
||||||
|
+ cert_path, {ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
|
||||||
|
+
|
||||||
|
+ if(result.test_eq("Legitimate: expected result count", ocsp_status.size(), 1) &&
|
||||||
|
+ result.test_eq("Legitimate: expected status count", ocsp_status[0].size(), 1)) {
|
||||||
|
+ result.test_eq("Legitimate response is accepted",
|
||||||
|
+ ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_RESPONSE_GOOD), true);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ // Tamper with the signature and verify check_ocsp rejects it
|
||||||
|
+ {
|
||||||
|
+ auto ocsp_bytes = Test::read_binary_data_file("x509/ocsp/randombit_ocsp.der");
|
||||||
|
+ ocsp_bytes.back() ^= 0x01;
|
||||||
|
+ Botan::OCSP::Response forged_ocsp(ocsp_bytes.data(), ocsp_bytes.size());
|
||||||
|
+
|
||||||
|
+ const auto ocsp_status = Botan::PKIX::check_ocsp(
|
||||||
|
+ cert_path, {forged_ocsp}, {&certstore}, valid_time, Botan::Path_Validation_Restrictions());
|
||||||
|
+
|
||||||
|
+ if(result.test_eq("Forged: expected result count", ocsp_status.size(), 1) &&
|
||||||
|
+ result.test_eq("Forged: expected status count", ocsp_status[0].size(), 1)) {
|
||||||
|
+ result.test_eq("Forged signature is rejected",
|
||||||
|
+ ocsp_status[0].contains(Botan::Certificate_Status_Code::OCSP_SIGNATURE_ERROR), true);
|
||||||
|
+ }
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ return result;
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
static Test::Result test_responder_cert_with_nocheck_extension() {
|
||||||
|
Test::Result result("BDr's OCSP response contains certificate featuring NoCheck extension");
|
||||||
|
|
||||||
|
@@ -436,6 +482,7 @@ class OCSP_Tests final : public Test {
|
||||||
|
results.push_back(test_response_verification_without_next_update_without_max_age());
|
||||||
|
results.push_back(test_response_verification_softfail());
|
||||||
|
results.push_back(test_response_verification_with_additionally_trusted_responder());
|
||||||
|
+ results.push_back(test_forged_ocsp_signature_is_rejected());
|
||||||
|
results.push_back(test_responder_cert_with_nocheck_extension());
|
||||||
|
|
||||||
|
#if defined(BOTAN_HAS_ONLINE_REVOCATION_CHECKS)
|
||||||
@@ -6,6 +6,7 @@ SECTION = "libs"
|
|||||||
|
|
||||||
SRC_URI = "https://botan.randombit.net/releases/Botan-${PV}.tar.xz \
|
SRC_URI = "https://botan.randombit.net/releases/Botan-${PV}.tar.xz \
|
||||||
file://CVE-2026-32877.patch \
|
file://CVE-2026-32877.patch \
|
||||||
|
file://CVE-2026-32883.patch \
|
||||||
"
|
"
|
||||||
SRC_URI[sha256sum] = "fde194236f6d5434f136ea0a0627f6cc9d26af8b96e9f1e1c7d8c82cd90f4f24"
|
SRC_URI[sha256sum] = "fde194236f6d5434f136ea0a0627f6cc9d26af8b96e9f1e1c7d8c82cd90f4f24"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user