From 5f07ea344b7814b4d478fbd4048296d13885ad3f Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 23 Feb 2026 15:23:50 +0200 Subject: [PATCH] Fetch additional intermediate certificates for validation IB-7992 Signed-off-by: Raul Metsma --- examples/pkcs11sign.cpp | 1 + src/SignatureXAdES_B.cpp | 43 ++++++++++++++----------- src/SignatureXAdES_B.h | 4 +-- src/SignatureXAdES_LT.cpp | 46 +++++++++++++++++++++++--- src/SignatureXAdES_LT.h | 1 + src/SignatureXAdES_T.cpp | 2 +- src/crypto/OCSP.cpp | 18 +++++++++-- src/crypto/OCSP.h | 2 +- src/crypto/Signer.cpp | 1 - src/crypto/Signer.h | 4 ++- src/crypto/X509Cert.cpp | 13 ++++++-- src/crypto/X509Cert.h | 3 +- src/crypto/X509CertStore.cpp | 62 +++++++++++++++++++++--------------- src/crypto/X509CertStore.h | 2 +- 14 files changed, 141 insertions(+), 61 deletions(-) diff --git a/examples/pkcs11sign.cpp b/examples/pkcs11sign.cpp index 2d824bf8a..5ad7562ad 100644 --- a/examples/pkcs11sign.cpp +++ b/examples/pkcs11sign.cpp @@ -1,4 +1,5 @@ #include +#include #include #include diff --git a/src/SignatureXAdES_B.cpp b/src/SignatureXAdES_B.cpp index 6f1723853..fa9cb9253 100644 --- a/src/SignatureXAdES_B.cpp +++ b/src/SignatureXAdES_B.cpp @@ -544,11 +544,8 @@ void SignatureXAdES_B::validate(const string &policy) const if(!signatureref.empty()) EXCEPTION_ADD(exception, "Manifest references and signature references do not match"); - try { checkKeyInfo(); } - catch(const Exception& e) { exception.addCause(e); } - - try { checkSigningCertificate(policy == POLv1); } - catch(const Exception& e) { exception.addCause(e); } + auto signingCertificate = checkSigningCertificate(policy == POLv1); + checkKeyInfo(signingCertificate); } catch(const Exception &e) { exception.addCause(e); } catch(...) { @@ -595,9 +592,8 @@ void SignatureXAdES_B::checkDigest(XMLNode digest, const vector & * Verify if SigningCertificate matches with * XAdES::SigningCertificate/SigningCertificateV2 Digest and IssuerSerial info */ -void SignatureXAdES_B::checkKeyInfo() const +void SignatureXAdES_B::checkKeyInfo(const X509Cert &x509) const { - X509Cert x509 = signingCertificate(); if(auto sigCert = signedSignatureProperties()/"SigningCertificate") { if(auto certs = sigCert/"Cert"; certs || !(certs + 1)) @@ -626,21 +622,30 @@ void SignatureXAdES_B::checkKeyInfo() const * Check if signing certificate was issued by trusted party. * @throws Exception on a problem with signing certificate */ -void SignatureXAdES_B::checkSigningCertificate(bool noqscd) const +X509Cert SignatureXAdES_B::checkSigningCertificate(bool noqscd, tm validation_time) const { - try + X509Cert signingCertificate; + vector untrusted; + for(auto x509Data = signature/"KeyInfo"/"X509Data"; x509Data; x509Data++) { - X509Cert signingCert = signingCertificate(); - vector usage = signingCert.keyUsage(); - if(!contains(usage, X509Cert::NonRepudiation)) - THROW("Signing certificate does not contain NonRepudiation key usage flag"); - if(!signingCertificate().verify(noqscd)) - THROW("Unable to verify signing certificate"); - } - catch(const Exception &e) - { - THROW_CAUSE( e, "Unable to verify signing certificate" ); + for(auto x509Cert = x509Data/"X509Certificate"; x509Cert; x509Cert++) + { + vector cert = x509Cert; + if(cert.empty()) + continue; + if(!signingCertificate) + signingCertificate = X509Cert(cert); + else + untrusted.emplace_back(cert); + } } + if(!signingCertificate) + THROW("Signature does not contain signer certificate"); + if(!contains(signingCertificate.keyUsage(), X509Cert::NonRepudiation)) + THROW("Signing certificate does not contain NonRepudiation key usage flag"); + if(!signingCertificate.verify(noqscd, validation_time, untrusted)) + THROW("Unable to verify signing certificate"); + return signingCertificate; } void SignatureXAdES_B::addDataObjectFormat(const string &uri, const string &mime) diff --git a/src/SignatureXAdES_B.h b/src/SignatureXAdES_B.h index 56f12e485..ba9127952 100644 --- a/src/SignatureXAdES_B.h +++ b/src/SignatureXAdES_B.h @@ -92,6 +92,7 @@ namespace digidoc constexpr XMLNode signedSignatureProperties() const noexcept; static void checkCertID(XMLNode certID, const X509Cert &cert); static void checkDigest(XMLNode digest, const std::vector &data); + X509Cert checkSigningCertificate(bool noqscd, tm validation_time = {}) const; XMLNode signature; ASiContainer *bdoc {}; @@ -116,7 +117,6 @@ namespace digidoc constexpr XMLNode V1orV2(std::string_view v1, std::string_view v2) const noexcept; // offline checks - void checkSigningCertificate(bool noqscd) const; - void checkKeyInfo() const; + void checkKeyInfo(const X509Cert &x509) const; }; } diff --git a/src/SignatureXAdES_LT.cpp b/src/SignatureXAdES_LT.cpp index 83792bfc7..bb90399e0 100644 --- a/src/SignatureXAdES_LT.cpp +++ b/src/SignatureXAdES_LT.cpp @@ -123,13 +123,14 @@ void SignatureXAdES_LT::validate(const string &policy) const * Find OCSP response that matches with signingCertificate. * If none is found throw all OCSP validation exceptions. */ + vector untrusted = certificateValues(revocationValues); bool foundSignerOCSP = false; vector ocspExceptions; for(auto resp = ocspValues/"EncapsulatedOCSPValue"; resp; resp++) { OCSP ocsp(resp); try { - ocsp.verifyResponse(signingCertificate()); + ocsp.verifyResponse(signingCertificate(), untrusted); foundSignerOCSP = true; } catch(const Exception &e) { ocspExceptions.push_back(e); @@ -221,7 +222,7 @@ void SignatureXAdES_LT::extendSignatureProfile(Signer *signer) cert.issuerName().c_str()); OCSP ocsp(cert, issuer, signer->userAgent()); - ocsp.verifyResponse(cert); + ocsp.verifyResponse(cert, {}); addCertificateValue(id() + "-CA-CERT", issuer); addOCSPValue(id().replace(0, 1, "N"), ocsp); @@ -255,6 +256,41 @@ void SignatureXAdES_LT::addOCSPValue(const string &id, const OCSP &ocsp) enc = ocsp; } +vector SignatureXAdES_LT::certificateValues(XMLNode until) const +{ + vector certs; + auto addCert = [&certs](XMLNode cert) { + try { + certs.emplace_back(cert); + } catch(const Exception &e) { + WARN("Failed to parse certificate from EncapsulatedX509Certificate: %s", e.msg().c_str()); + } + }; + + // KeyInfo intermediate certificates (skip first = signing cert) + bool first = true; + for(auto x509Data = signature/"KeyInfo"/"X509Data"; x509Data; x509Data++) + { + for(auto x509Cert = x509Data/"X509Certificate"; x509Cert; x509Cert++) + { + if(first) { first = false; continue; } + addCert(x509Cert); + } + } + + // CertificateValues certificates that are available before the guarded node. + for(auto elem: unsignedSignatureProperties()) + { + if(until && elem == until) + break; + if(elem.name() != "CertificateValues" || elem.ns() != XADES_NS) + continue; + for(auto cert = elem/"EncapsulatedX509Certificate"; cert; cert++) + addCert(cert); + }; + return certs; +} + /** * Get value of UnsignedProperties\UnsignedSignatureProperties\RevocationValues\OCSPValues\EncapsulatedOCSPValue * which contains whole OCSP response @@ -264,12 +300,14 @@ OCSP SignatureXAdES_LT::getOCSPResponseValue() const { try { - auto ocspValues = unsignedSignatureProperties()/"RevocationValues"/"OCSPValues"; + auto revocationValues = unsignedSignatureProperties()/"RevocationValues"; + vector untrusted = certificateValues(revocationValues); + auto ocspValues = revocationValues/"OCSPValues"; for(auto resp = ocspValues/"EncapsulatedOCSPValue"; resp; resp++) { try { OCSP ocsp(resp); - ocsp.verifyResponse(signingCertificate()); + ocsp.verifyResponse(signingCertificate(), untrusted); return ocsp; } catch(const Exception &) { } diff --git a/src/SignatureXAdES_LT.h b/src/SignatureXAdES_LT.h index 27135b6fc..ec7d1d92a 100644 --- a/src/SignatureXAdES_LT.h +++ b/src/SignatureXAdES_LT.h @@ -45,6 +45,7 @@ class SignatureXAdES_LT: public SignatureXAdES_T void addOCSPValue(const std::string &id, const OCSP &ocsp); void addCertificateValue(const std::string& certId, const X509Cert& x509); + std::vector certificateValues(XMLNode until = XMLNode{}) const; OCSP getOCSPResponseValue() const; }; diff --git a/src/SignatureXAdES_T.cpp b/src/SignatureXAdES_T.cpp index e2312a8bb..b62a514c5 100644 --- a/src/SignatureXAdES_T.cpp +++ b/src/SignatureXAdES_T.cpp @@ -114,7 +114,7 @@ void SignatureXAdES_T::validate(const std::string &policy) const signatures->c14n(digest, canonicalizationMethod, signatureValue()); }); - if(!signingCertificate().verify(policy == POLv1, tsa.time())) + if(!checkSigningCertificate(policy == POLv1, tsa.time())) THROW("Signing certificate was not valid on signing time"); auto completeCertRefs = usp/"CompleteCertificateRefs"; diff --git a/src/crypto/OCSP.cpp b/src/crypto/OCSP.cpp index 86262053d..cf7afe79d 100644 --- a/src/crypto/OCSP.cpp +++ b/src/crypto/OCSP.cpp @@ -186,7 +186,7 @@ OCSP::operator vector() const /** * Check that response was signed with trusted OCSP certificate */ -void OCSP::verifyResponse(const X509Cert &cert) const +void OCSP::verifyResponse(const X509Cert &cert, const vector &untrusted) const { if(!basic) THROW("Failed to verify OCSP response."); @@ -202,7 +202,10 @@ void OCSP::verifyResponse(const X509Cert &cert) const sk_X509_push(stack.get(), i.handle()); } } + for(const X509Cert &i: untrusted) + sk_X509_push(stack.get(), i.handle()); auto store = X509CertStore::createStore(X509CertStore::OCSP, tm); + ERR_clear_error(); if(OCSP_basic_verify(basic.get(), stack.get(), store.get(), OCSP_NOCHECKS | OCSP_PARTIAL_CHAIN) != 1) { unsigned long err = ERR_get_error(); @@ -217,9 +220,20 @@ void OCSP::verifyResponse(const X509Cert &cert) const throw OpenSSLException(EXCEPTION_PARAMS("Failed to verify OCSP response."), err); } - // Find issuer before OCSP validation to activate region TSL + // Find issuer from TSL or from untrusted CertificateValues X509Cert issuer = X509CertStore::instance()->findIssuer(cert, X509CertStore::CA); if(!issuer) + { + for(const X509Cert &i: untrusted) + { + if(X509_check_issued(i.handle(), cert.handle()) == X509_V_OK) + { + issuer = i; + break; + } + } + } + if(!issuer) { Exception e(EXCEPTION_PARAMS("Certificate status: unknown")); e.setCode(Exception::CertificateUnknown); diff --git a/src/crypto/OCSP.h b/src/crypto/OCSP.h index bbfd7f166..606ba6c1c 100644 --- a/src/crypto/OCSP.h +++ b/src/crypto/OCSP.h @@ -45,7 +45,7 @@ namespace digidoc std::vector nonce() const; tm producedAt() const; X509Cert responderCert() const; - void verifyResponse(const X509Cert &cert) const; + void verifyResponse(const X509Cert &cert, const std::vector &untrusted) const; operator std::vector() const; diff --git a/src/crypto/Signer.cpp b/src/crypto/Signer.cpp index ea40f8f7c..829518960 100644 --- a/src/crypto/Signer.cpp +++ b/src/crypto/Signer.cpp @@ -28,7 +28,6 @@ #include -#include #include #include diff --git a/src/crypto/Signer.h b/src/crypto/Signer.h index d23c18a00..2442c7a7a 100644 --- a/src/crypto/Signer.h +++ b/src/crypto/Signer.h @@ -19,9 +19,11 @@ #pragma once -#include "../Exception.h" +#include "../Exports.h" #include +#include +#include namespace digidoc { diff --git a/src/crypto/X509Cert.cpp b/src/crypto/X509Cert.cpp index f8c47b45d..61b99834e 100644 --- a/src/crypto/X509Cert.cpp +++ b/src/crypto/X509Cert.cpp @@ -315,7 +315,7 @@ X509Cert::X509Cert(X509Cert &&other) noexcept = default; /** * Clean up underlying OpenSSL X509 data. */ -X509Cert::~X509Cert() = default; +X509Cert::~X509Cert() noexcept = default; /** * Encodes the X509 certificate using DER encoding. @@ -578,7 +578,16 @@ bool X509Cert::isValid(time_t *t) const */ bool X509Cert::verify(bool noqscd, tm validation_time) const { - return X509CertStore::instance()->verify(*this, noqscd, validation_time); + return X509CertStore::instance()->verify(*this, noqscd, validation_time, {}); +} + +/** + * Returns true if certificate is signed by trusted issuer + * @throws Exception if error + */ +bool X509Cert::verify(bool noqscd, tm validation_time, const vector &untrusted) const +{ + return X509CertStore::instance()->verify(*this, noqscd, validation_time, untrusted); } /** diff --git a/src/crypto/X509Cert.h b/src/crypto/X509Cert.h index d0776490e..4c056058f 100644 --- a/src/crypto/X509Cert.h +++ b/src/crypto/X509Cert.h @@ -86,7 +86,7 @@ namespace digidoc explicit X509Cert(const std::string &path, Format format = Pem); X509Cert(X509Cert &&other) noexcept; X509Cert(const X509Cert &other); - ~X509Cert(); + ~X509Cert() noexcept; std::string serial() const; std::string issuerName(const std::string &obj = std::string()) const; @@ -97,6 +97,7 @@ namespace digidoc bool isCA() const; bool isValid(time_t *t = nullptr) const; bool verify(bool noqscd, tm validation_time = {}) const; + bool verify(bool noqscd, tm validation_time, const std::vector &untrusted) const; X509* handle() const; operator std::vector() const; diff --git a/src/crypto/X509CertStore.cpp b/src/crypto/X509CertStore.cpp index fba1ffef8..1fb6f7094 100644 --- a/src/crypto/X509CertStore.cpp +++ b/src/crypto/X509CertStore.cpp @@ -154,34 +154,41 @@ int X509CertStore::validate(int ok, X509_STORE_CTX *ctx) } auto *type = static_cast(X509_STORE_get_ex_data(X509_STORE_CTX_get0_store(ctx), 0)); - X509 *x509 = X509_STORE_CTX_get0_cert(ctx); auto current = util::date::to_string(X509_VERIFY_PARAM_get_time(X509_STORE_CTX_get0_param(ctx))); - for(const TSL::Service &s: *instance()->d) - { - if(type->find(s.type) == type->cend()) // correct service type - continue; - if(none_of(s.certs, [&](const X509Cert &issuer) { - if(issuer == x509) // certificate is listed by service - return true; - if(X509_check_issued(issuer.handle(), x509) != X509_V_OK) // certificate is issued by service (function checks only issuer name) - return false; - auto pub = make_unique_ptr(X509_get_pubkey(issuer.handle())); - if(X509_verify(x509, pub.get()) == 1) // certificate is signed by service - return true; - ERR_clear_error(); - return false; - })) // certificate is trusted by service - continue; - for(auto i = s.validity.crbegin(), end = s.validity.crend(); i != end; ++i) + auto trusted = [&](X509 *x509) { + for(const TSL::Service &s: *instance()->d) { - if(current < i->first) // Search older status + if(type->find(s.type) == type->cend()) // correct service type continue; - if(!i->second.has_value()) // Has revoked - break; - X509_STORE_CTX_set_ex_data(ctx, 0, const_cast(&i->second)); - return 1; + if(none_of(s.certs, [&](const X509Cert &issuer) { + if(issuer == x509) // certificate is listed by service + return true; + if(X509_check_issued(issuer.handle(), x509) != X509_V_OK) // certificate is issued by service (function checks only issuer name) + return false; + auto pub = make_unique_ptr(X509_get_pubkey(issuer.handle())); + if(X509_verify(x509, pub.get()) == 1) // certificate is signed by service + return true; + ERR_clear_error(); + return false; + })) // certificate is trusted by service + continue; + for(auto i = s.validity.crbegin(), end = s.validity.crend(); i != end; ++i) + { + if(current < i->first) // Search older status + continue; + if(!i->second.has_value()) // Has revoked + break; + X509_STORE_CTX_set_ex_data(ctx, 0, const_cast(&i->second)); + return true; + } } - } + return false; + }; + X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); + X509 *target_cert = X509_STORE_CTX_get0_cert(ctx); + if((current_cert && trusted(current_cert)) || + (target_cert && target_cert != current_cert && trusted(target_cert))) + return 1; return ok; } @@ -200,14 +207,17 @@ void X509CertStore::update() const * Check if X509Cert is signed by trusted issuer * @throw Exception if error */ -bool X509CertStore::verify(const X509Cert &cert, bool noqscd, tm validation_time) const +bool X509CertStore::verify(const X509Cert &cert, bool noqscd, tm validation_time, const vector &untrusted) const { activate(cert); if(util::date::is_empty(validation_time)) ASN1_TIME_to_tm(X509_get0_notBefore(cert.handle()), &validation_time); auto store = createStore(X509CertStore::CA, validation_time); auto csc = make_unique_ptr(X509_STORE_CTX_new()); - if(!X509_STORE_CTX_init(csc.get(), store.get(), cert.handle(), nullptr)) + auto stack = make_unique_ptr(sk_X509_new_null(), [](auto *sk) { sk_X509_free(sk); }); + for(const X509Cert &i: untrusted) + sk_X509_push(stack.get(), i.handle()); + if(!X509_STORE_CTX_init(csc.get(), store.get(), cert.handle(), stack.get())) THROW_OPENSSLEXCEPTION("Failed to init X509_STORE_CTX"); if(X509_verify_cert(csc.get()) <= 0) { diff --git a/src/crypto/X509CertStore.h b/src/crypto/X509CertStore.h index f0310fb96..5cd471569 100644 --- a/src/crypto/X509CertStore.h +++ b/src/crypto/X509CertStore.h @@ -50,7 +50,7 @@ namespace digidoc static X509Cert issuerFromAIA(const X509Cert &cert); static unique_free_t createStore(const Type &type, tm &tm); void update() const; - bool verify(const X509Cert &cert, bool noqscd, tm validation_time = {}) const; + bool verify(const X509Cert &cert, bool noqscd, tm validation_time, const std::vector &untrusted) const; private: X509CertStore();