From d7ae369636f9937e24e0c38f36f6552cce666542 Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Sun, 10 Nov 2024 22:49:35 +0200 Subject: [PATCH] Validate signing certificate on issue date and on TSA time IB-8296 Signed-off-by: Raul Metsma --- src/SignatureTST.cpp | 1 + src/SignatureXAdES_T.cpp | 5 ++- src/XMLDocument.h | 23 ++----------- src/crypto/Connect.cpp | 17 +++++----- src/crypto/Digest.cpp | 5 +-- src/crypto/Digest.h | 7 ++-- src/crypto/OCSP.cpp | 45 ++++++++++++++----------- src/crypto/OCSP.h | 7 ++-- src/crypto/OpenSSLHelpers.h | 15 ++++----- src/crypto/TS.cpp | 64 +++++++++++++++--------------------- src/crypto/TS.h | 10 ++++-- src/crypto/X509CertStore.cpp | 64 ++++++++++++++---------------------- src/crypto/X509CertStore.h | 9 ++--- src/util/DateTime.cpp | 20 ++++++++--- src/util/DateTime.h | 3 +- src/util/memory.h | 46 ++++++++++++++++++++++++++ 16 files changed, 185 insertions(+), 156 deletions(-) create mode 100644 src/util/memory.h diff --git a/src/SignatureTST.cpp b/src/SignatureTST.cpp index 3061efdbe..75299a426 100644 --- a/src/SignatureTST.cpp +++ b/src/SignatureTST.cpp @@ -21,6 +21,7 @@ #include "ASiC_S.h" #include "DataFile_p.h" +#include "crypto/Digest.h" #include "crypto/TS.h" #include "crypto/X509Cert.h" #include "util/DateTime.h" diff --git a/src/SignatureXAdES_T.cpp b/src/SignatureXAdES_T.cpp index 1b4a833ae..5689a1073 100644 --- a/src/SignatureXAdES_T.cpp +++ b/src/SignatureXAdES_T.cpp @@ -25,6 +25,7 @@ #include "crypto/Signer.h" #include "crypto/TS.h" #include "crypto/X509Cert.h" +#include "crypto/X509CertStore.h" #include "util/DateTime.h" #include "util/log.h" @@ -113,9 +114,7 @@ void SignatureXAdES_T::validate(const std::string &policy) const signatures->c14n(digest, canonicalizationMethod, signatureValue()); }); - tm tm = tsa.time(); - time_t validateTime = util::date::mkgmtime(tm); - if(!signingCertificate().isValid(&validateTime)) + if(!X509CertStore::instance()->verify(signingCertificate(), policy == POLv1, tsa.time())) THROW("Signing certificate was not valid on signing time"); auto completeCertRefs = usp/"CompleteCertificateRefs"; diff --git a/src/XMLDocument.h b/src/XMLDocument.h index 5ea27733f..e4a84f83c 100644 --- a/src/XMLDocument.h +++ b/src/XMLDocument.h @@ -21,6 +21,7 @@ #include "crypto/Digest.h" #include "util/log.h" +#include "util/memory.h" #include #include @@ -32,30 +33,12 @@ #include -#include #include namespace digidoc { #define VERSION_CHECK(major, minor, patch) (((major)<<16)|((minor)<<8)|(patch)) -template struct unique_xml; -template -struct unique_xml -{ - using type = std::unique_ptr; -}; - -template -using unique_xml_t = typename unique_xml::type; - -template -[[nodiscard]] -constexpr std::unique_ptr make_unique_ptr(T *p, D d) noexcept -{ - return {p, std::forward(d)}; -} - static std::vector from_base64(std::string_view data) { static constexpr std::string_view whitespace {" \n\r\f\t\v"}; @@ -300,7 +283,7 @@ struct XMLNode: public XMLElem } }; -struct XMLDocument: public unique_xml_t, public XMLNode +struct XMLDocument: public unique_free_t, public XMLNode { static constexpr std::string_view C14D_ID_1_0 {"http://www.w3.org/TR/2001/REC-xml-c14n-20010315"}; static constexpr std::string_view C14D_ID_1_0_COM {"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"}; @@ -312,7 +295,7 @@ struct XMLDocument: public unique_xml_t, public XMLNode using XMLNode::operator bool; XMLDocument(element_type *ptr = {}, const XMLName &n = {}) noexcept - : std::unique_ptr(ptr, xmlFreeDoc) + : unique_free_t(ptr, xmlFreeDoc) , XMLNode{xmlDocGetRootElement(get())} { if(d && !n.name.empty() && n.name != name() && !n.ns.empty() && n.ns != ns()) diff --git a/src/crypto/Connect.cpp b/src/crypto/Connect.cpp index d03d16362..077cde27a 100644 --- a/src/crypto/Connect.cpp +++ b/src/crypto/Connect.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #ifdef _WIN32 @@ -147,13 +148,13 @@ Connect::Connect(const string &_url, string _method, int _timeout, const vector< if(!certs.empty()) { SSL_CTX_set_verify(ssl.get(), SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); - SSL_CTX_set_cert_verify_callback(ssl.get(), [](X509_STORE_CTX *store, void *data) -> int { - X509 *x509 = X509_STORE_CTX_get0_cert(store); - auto *certs = (vector*)data; - return any_of(certs->cbegin(), certs->cend(), [x509](const X509Cert &cert) { - return cert && cert == x509; - }) ? 1 : 0; - }, const_cast*>(&certs)); + X509_STORE *store = SSL_CTX_get_cert_store(ssl.get()); + X509_STORE_set_flags(store, X509_V_FLAG_TRUSTED_FIRST | X509_V_FLAG_PARTIAL_CHAIN); + for(const X509Cert &cert: certs) + { + if(cert.handle()) + X509_STORE_add_cert(store, cert.handle()); + } } BIO *sbio = BIO_new_ssl(ssl.get(), 1); if(!sbio) @@ -292,7 +293,7 @@ Connect::Result Connect::exec(initializer_list> he stringstream stream(r.content); string line; auto to_lower = [](string str) { - std::transform(str.begin(), str.end(), str.begin(), ::tolower); + transform(str.begin(), str.end(), str.begin(), ::tolower); return str; }; while(getline(stream, line)) diff --git a/src/crypto/Digest.cpp b/src/crypto/Digest.cpp index 263d86cc5..cb7277618 100644 --- a/src/crypto/Digest.cpp +++ b/src/crypto/Digest.cpp @@ -26,6 +26,7 @@ #include #include +#include using namespace std; using namespace digidoc; @@ -37,7 +38,7 @@ using namespace digidoc; * @throws Exception throws exception if the digest calculator initialization failed. */ Digest::Digest(string_view uri) - : d(SCOPE_PTR(EVP_MD_CTX, EVP_MD_CTX_new())) + : d(EVP_MD_CTX_new(), EVP_MD_CTX_free) { if(uri.empty() && Conf::instance()->digestUri() == URI_SHA1) THROW("Unsupported digest method %.*s", int(uri.size()), uri.data()); @@ -269,7 +270,7 @@ vector Digest::result() const return result; } -vector Digest::result(const vector &data) +vector Digest::result(const vector &data) const { update(data.data(), data.size()); return result(); diff --git a/src/crypto/Digest.h b/src/crypto/Digest.h index d9472fc82..c00e3f669 100644 --- a/src/crypto/Digest.h +++ b/src/crypto/Digest.h @@ -21,7 +21,8 @@ #include "../Exports.h" -#include +#include "util/memory.h" + #include #include @@ -73,7 +74,7 @@ namespace digidoc Digest(std::string_view uri = {}); void update(const unsigned char *data, size_t length) const; void update(std::istream &is) const; - std::vector result(const std::vector &data); + std::vector result(const std::vector &data) const; std::vector result() const; std::string uri() const; @@ -89,7 +90,7 @@ namespace digidoc static std::string digestInfoUri(const std::vector &digest); private: - std::unique_ptr d; + unique_free_t d; }; } diff --git a/src/crypto/OCSP.cpp b/src/crypto/OCSP.cpp index e05125d58..c6b9b3e21 100644 --- a/src/crypto/OCSP.cpp +++ b/src/crypto/OCSP.cpp @@ -46,6 +46,8 @@ using namespace std; * Initialize OCSP certificate validator. */ OCSP::OCSP(const X509Cert &cert, const X509Cert &issuer, const std::string &userAgent) + : resp(nullptr, OCSP_RESPONSE_free) + , basic(nullptr, OCSP_BASICRESP_free) { if(!cert) THROW("Can not check X.509 certificate, certificate is NULL pointer."); @@ -56,7 +58,7 @@ OCSP::OCSP(const X509Cert &cert, const X509Cert &issuer, const std::string &user if(url.empty()) { STACK_OF(OPENSSL_STRING) *urls = X509_get1_ocsp(cert.handle()); - if(sk_OPENSSL_STRING_num(urls) > 0) + if(urls && sk_OPENSSL_STRING_num(urls) > 0) url = sk_OPENSSL_STRING_value(urls, 0); X509_email_free(urls); } @@ -90,8 +92,8 @@ OCSP::OCSP(const X509Cert &cert, const X509Cert &issuer, const std::string &user THROW("OCSP service responded - Forbidden"); if(!result) THROW("Failed to send OCSP request"); - const unsigned char *p2 = (const unsigned char*)result.content.c_str(); - resp.reset(d2i_OCSP_RESPONSE(nullptr, &p2, long(result.content.size())), OCSP_RESPONSE_free); + const auto *p2 = (const unsigned char*)result.content.c_str(); + resp.reset(d2i_OCSP_RESPONSE(nullptr, &p2, long(result.content.size()))); switch(int respStatus = OCSP_response_status(resp.get())) { @@ -106,7 +108,7 @@ OCSP::OCSP(const X509Cert &cert, const X509Cert &issuer, const std::string &user THROW("OCSP request failed, response status: %s", OCSP_response_status_str(respStatus)); } - basic.reset(OCSP_response_get1_basic(resp.get()), OCSP_BASICRESP_free); + basic.reset(OCSP_response_get1_basic(resp.get())); if(!basic) THROW("Incorrect OCSP response."); @@ -127,12 +129,14 @@ OCSP::OCSP(const X509Cert &cert, const X509Cert &issuer, const std::string &user } OCSP::OCSP(const unsigned char *data, size_t size) + : resp(nullptr, OCSP_RESPONSE_free) + , basic(nullptr, OCSP_BASICRESP_free) { if(size == 0) return; - resp.reset(d2i_OCSP_RESPONSE(nullptr, &data, long(size)), OCSP_RESPONSE_free); + resp.reset(d2i_OCSP_RESPONSE(nullptr, &data, long(size))); if(resp) - basic.reset(OCSP_response_get1_basic(resp.get()), OCSP_BASICRESP_free); + basic.reset(OCSP_response_get1_basic(resp.get())); } bool OCSP::compareResponderCert(const X509Cert &cert) const @@ -187,17 +191,19 @@ void OCSP::verifyResponse(const X509Cert &cert) const THROW("Failed to verify OCSP response."); tm tm = producedAt(); - time_t t = util::date::mkgmtime(tm); - SCOPE(X509_STORE, store, X509CertStore::createStore(X509CertStore::OCSP, &t)); - STACK_OF(X509) *stack = sk_X509_new_null(); + // Some OCSP-s do not have certificates in response and stack is used for finding certificate for this + auto stack = make_unique_ptr(sk_X509_new_null(), [](auto *sk) { sk_X509_free(sk); }); for(const X509Cert &i: X509CertStore::instance()->certs(X509CertStore::OCSP)) { if(compareResponderCert(i)) - sk_X509_push(stack, i.handle()); + sk_X509_push(stack.get(), i.handle()); } - int result = OCSP_basic_verify(basic.get(), stack, store.get(), OCSP_NOCHECKS); - sk_X509_free(stack); - if(result != 1) + auto store = X509CertStore::createStore(X509CertStore::OCSP, tm); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if(OCSP_basic_verify(basic.get(), stack.get(), store.get(), OCSP_NOCHECKS) != 1) +#else + if(OCSP_basic_verify(basic.get(), stack.get(), store.get(), OCSP_NOCHECKS | OCSP_PARTIAL_CHAIN) != 1) +#endif { unsigned long err = ERR_get_error(); if(ERR_GET_LIB(err) == ERR_LIB_OCSP && @@ -259,17 +265,18 @@ void OCSP::verifyResponse(const X509Cert &cert) const */ vector OCSP::nonce() const { + vector nonce; if(!basic) - return {}; + return nonce; int resp_idx = OCSP_BASICRESP_get_ext_by_NID(basic.get(), NID_id_pkix_OCSP_Nonce, -1); if(resp_idx < 0) - return {}; + return nonce; X509_EXTENSION *ext = OCSP_BASICRESP_get_ext(basic.get(), resp_idx); if(!ext) - return {}; + return nonce; ASN1_OCTET_STRING *value = X509_EXTENSION_get_data(ext); - vector nonce(value->data, value->data + value->length); + nonce.assign(value->data, std::next(value->data, value->length)); //OpenSSL OCSP created messages NID_id_pkix_OCSP_Nonce field is DER encoded twice, not a problem with java impl //XXX: UglyHackTM check if nonceAsn1 contains ASN1_OCTET_STRING //XXX: if first 2 bytes seem to be beginning of DER ASN1_OCTET_STRING then remove them @@ -281,9 +288,9 @@ vector OCSP::nonce() const tm OCSP::producedAt() const { - if(!basic) - return {}; tm tm {}; + if(!basic) + return tm; ASN1_TIME_to_tm(OCSP_resp_get0_produced_at(basic.get()), &tm); return tm; } diff --git a/src/crypto/OCSP.h b/src/crypto/OCSP.h index 63c716c10..bbfd7f166 100644 --- a/src/crypto/OCSP.h +++ b/src/crypto/OCSP.h @@ -19,7 +19,8 @@ #pragma once -#include +#include "util/memory.h" + #include #include @@ -51,7 +52,7 @@ namespace digidoc private: bool compareResponderCert(const X509Cert &cert) const; - std::shared_ptr resp; - std::shared_ptr basic; + unique_free_t resp; + unique_free_t basic; }; } diff --git a/src/crypto/OpenSSLHelpers.h b/src/crypto/OpenSSLHelpers.h index 1a5253e23..3c87757c0 100644 --- a/src/crypto/OpenSSLHelpers.h +++ b/src/crypto/OpenSSLHelpers.h @@ -21,9 +21,7 @@ #include "Exception.h" #include "util/log.h" - -#include -#include +#include "util/memory.h" #include @@ -34,7 +32,7 @@ namespace digidoc { -#define SCOPE_PTR_FREE(TYPE, DATA, FREE) std::unique_ptr(static_cast(DATA), FREE) +#define SCOPE_PTR_FREE(TYPE, DATA, FREE) make_unique_ptr(DATA, FREE) #define SCOPE_PTR(TYPE, DATA) SCOPE_PTR_FREE(TYPE, DATA, TYPE##_free) #define SCOPE(TYPE, VAR, DATA) auto VAR = SCOPE_PTR_FREE(TYPE, DATA, TYPE##_free) @@ -42,14 +40,15 @@ template [[nodiscard]] inline std::vector i2d(T *obj, Func func) { + std::vector result; if(!obj) - return {}; + return result; int size = func(obj, nullptr); if(size <= 0) - return {}; - std::vector result(size_t(size), 0); + return result; + result.resize(size_t(size), 0); if(unsigned char *p = result.data(); func(obj, &p) != size) - return {}; + result.clear(); return result; } diff --git a/src/crypto/TS.cpp b/src/crypto/TS.cpp index 89cebccd8..5dfb3a1b1 100644 --- a/src/crypto/TS.cpp +++ b/src/crypto/TS.cpp @@ -23,6 +23,7 @@ #include "Container.h" #include "Exception.h" #include "crypto/Connect.h" +#include "crypto/Digest.h" #include "crypto/OpenSSLHelpers.h" #include "crypto/X509CertStore.h" #include "util/DateTime.h" @@ -56,15 +57,15 @@ void *OPENSSL_memdup(const void *data, size_t size) #endif TS::TS(const Digest &digest, const std::string &userAgent) + : d(nullptr, PKCS7_free) + , cms(nullptr, CMS_ContentInfo_free) { auto req = SCOPE_PTR(TS_REQ, TS_REQ_new()); TS_REQ_set_version(req.get(), 1); TS_REQ_set_cert_req(req.get(), 1); auto algo = SCOPE_PTR(X509_ALGOR, X509_ALGOR_new()); - algo->algorithm = OBJ_nid2obj(Digest::toMethod(digest.uri())); - algo->parameter = ASN1_TYPE_new(); - algo->parameter->type = V_ASN1_NULL; + X509_ALGOR_set0(algo.get(), OBJ_nid2obj(Digest::toMethod(digest.uri())), V_ASN1_NULL, nullptr); auto msg_imprint = SCOPE_PTR(TS_MSG_IMPRINT, TS_MSG_IMPRINT_new()); TS_MSG_IMPRINT_set_algo(msg_imprint.get(), algo.get()); @@ -82,8 +83,7 @@ TS::TS(const Digest &digest, const std::string &userAgent) auto nonce = SCOPE_PTR(ASN1_INTEGER, ASN1_INTEGER_new()); ASN1_STRING_set(nonce.get(), nullptr, 20); - nonce->data[0] = 0; - while(nonce->data[0] == 0) // Make sure that first byte is not 0x00 + for(nonce->data[0] = 0; nonce->data[0] == 0;) // Make sure that first byte is not 0x00 RAND_bytes(nonce->data, nonce->length); TS_REQ_set_nonce(req.get(), nonce.get()); @@ -119,15 +119,20 @@ TS::TS(const Digest &digest, const std::string &userAgent) if(TS_RESP_verify_response(ctx.get(), resp.get()) != 1) THROW_OPENSSLEXCEPTION("Failed to verify TS response."); - d.reset(PKCS7_dup(TS_RESP_get_token(resp.get())), PKCS7_free); + d.reset(PKCS7_dup(TS_RESP_get_token(resp.get()))); DEBUG("TSA time %s", util::date::to_string(time()).c_str()); } TS::TS(const unsigned char *data, size_t size) + : d(nullptr, PKCS7_free) + , cms(nullptr, [](CMS_ContentInfo *contentInfo) { + CMS_ContentInfo_free(contentInfo); + ERR_clear_error(); + }) { if(size == 0) return; - d.reset(d2i_PKCS7(nullptr, &data, long(size)), PKCS7_free); + d.reset(d2i_PKCS7(nullptr, &data, long(size))); #ifndef OPENSSL_NO_CMS if(d) return; @@ -139,10 +144,7 @@ TS::TS(const unsigned char *data, size_t size) * * If PKCS7 wrapped TimeStamp parsing fails, try with CMS wrapping */ - cms.reset(d2i_CMS_ContentInfo(nullptr, &data, long(size)), [](CMS_ContentInfo *contentInfo) { - CMS_ContentInfo_free(contentInfo); - ERR_clear_error(); - }); + cms.reset(d2i_CMS_ContentInfo(nullptr, &data, long(size))); if(!cms || OBJ_obj2nid(CMS_get0_eContentType(cms.get())) != NID_id_smime_ct_TSTInfo) cms.reset(); @@ -152,22 +154,18 @@ TS::TS(const unsigned char *data, size_t size) X509Cert TS::cert() const { - using sk_X509_free_t = void (*)(STACK_OF(X509) *); - unique_ptr signers = [&] { - if(d && PKCS7_type_is_signed(d.get())) - return unique_ptr(PKCS7_get0_signers(d.get(), nullptr, 0), - [](STACK_OF(X509) *stack) { sk_X509_free(stack); }); + unique_free_t signers(nullptr, nullptr); + if(d && PKCS7_type_is_signed(d.get())) + signers = {PKCS7_get0_signers(d.get(), nullptr, 0), + [](STACK_OF(X509) *stack) { sk_X509_free(stack); }}; #ifndef OPENSSL_NO_CMS - if(cms) - return unique_ptr(CMS_get1_certs(cms.get()), - [](STACK_OF(X509) *stack) { sk_X509_pop_free(stack, X509_free); }); + if(!signers && cms) + signers = {CMS_get1_certs(cms.get()), + [](STACK_OF(X509) *stack) { sk_X509_pop_free(stack, X509_free); }}; #endif - return unique_ptr(nullptr, nullptr); - }(); - - if(!signers || sk_X509_num(signers.get()) != 1) - return X509Cert(); - return X509Cert(sk_X509_value(signers.get(), 0)); + if(signers && sk_X509_num(signers.get()) == 1) + return X509Cert(sk_X509_value(signers.get(), 0)); + return X509Cert(); } auto TS::tstInfo() const @@ -177,8 +175,8 @@ auto TS::tstInfo() const #ifndef OPENSSL_NO_CMS if(cms) { - auto out = SCOPE_PTR(BIO, CMS_dataInit(cms.get(), nullptr)); - return SCOPE_PTR(TS_TST_INFO, d2i_TS_TST_INFO_bio(out.get(), nullptr)); + if(auto out = SCOPE_PTR(BIO, CMS_dataInit(cms.get(), nullptr))) + return SCOPE_PTR(TS_TST_INFO, d2i_TS_TST_INFO_bio(out.get(), nullptr)); } #endif return SCOPE_PTR(TS_TST_INFO, nullptr); @@ -216,8 +214,7 @@ string TS::serial() const if(auto bn = SCOPE_PTR_FREE(BIGNUM, ASN1_INTEGER_to_BN(TS_TST_INFO_get_serial(info.get()), nullptr), BN_free)) { - auto openssl_free = [](char *data) { OPENSSL_free(data); }; - if(auto str = unique_ptr(BN_bn2dec(bn.get()), openssl_free)) + if(auto str = make_unique_ptr(BN_bn2dec(bn.get()), [](char *data) { OPENSSL_free(data); })) return str.get(); } return {}; @@ -234,15 +231,8 @@ tm TS::time() const void TS::verify(const vector &digest) { tm tm = time(); - time_t t = util::date::mkgmtime(tm); - auto store = SCOPE_PTR(X509_STORE, X509CertStore::createStore(X509CertStore::TSA, &t)); + auto store = X509CertStore::createStore(X509CertStore::TSA, tm); X509CertStore::instance()->activate(cert()); - auto csc = SCOPE_PTR(X509_STORE_CTX, X509_STORE_CTX_new()); - if (!csc) - THROW_OPENSSLEXCEPTION("Failed to create X509_STORE_CTX"); - if(!X509_STORE_CTX_init(csc.get(), store.get(), nullptr, nullptr)) - THROW_OPENSSLEXCEPTION("Failed to init X509_STORE_CTX"); - if(d) { auto ctx = SCOPE_PTR(TS_VERIFY_CTX, TS_VERIFY_CTX_new()); diff --git a/src/crypto/TS.h b/src/crypto/TS.h index 16186a411..f09c9f657 100644 --- a/src/crypto/TS.h +++ b/src/crypto/TS.h @@ -19,12 +19,16 @@ #pragma once -#include "Digest.h" +#include "util/memory.h" + +#include +#include using PKCS7 = struct pkcs7_st; using CMS_ContentInfo = struct CMS_ContentInfo_st; namespace digidoc { +class Digest; class X509Cert; class TS @@ -46,8 +50,8 @@ class TS private: auto tstInfo() const; - std::shared_ptr d; - std::shared_ptr cms; + unique_free_t d; + unique_free_t cms; }; } diff --git a/src/crypto/X509CertStore.cpp b/src/crypto/X509CertStore.cpp index b0dbf0dcc..9485ee61e 100644 --- a/src/crypto/X509CertStore.cpp +++ b/src/crypto/X509CertStore.cpp @@ -132,28 +132,19 @@ X509Cert X509CertStore::issuerFromAIA(const X509Cert &cert) return X509Cert((const unsigned char*)result.content.c_str(), result.content.size()); } -X509_STORE* X509CertStore::createStore(const Type &type, const time_t *t) +unique_free_t X509CertStore::createStore(const Type &type, tm &tm) { SCOPE(X509_STORE, store, X509_STORE_new()); if (!store) THROW_OPENSSLEXCEPTION("Failed to create X509_STORE_CTX"); - - if(type == CA) - X509_STORE_set_verify_cb(store.get(), [](int ok, X509_STORE_CTX *ctx) -> int { return validate(ok, ctx, CA); }); - else if(type == OCSP) - X509_STORE_set_verify_cb(store.get(), [](int ok, X509_STORE_CTX *ctx) -> int { return validate(ok, ctx, OCSP); }); - else if(type == TSA) - X509_STORE_set_verify_cb(store.get(), [](int ok, X509_STORE_CTX *ctx) -> int { return validate(ok, ctx, TSA); }); - - if(t) - { - X509_VERIFY_PARAM_set_time(X509_STORE_get0_param(store.get()), *t); - X509_STORE_set_flags(store.get(), X509_V_FLAG_USE_CHECK_TIME); - } - return store.release(); + X509_STORE_set_verify_cb(store.get(), X509CertStore::validate); + X509_STORE_set_ex_data(store.get(), 0, const_cast(&type)); + X509_STORE_set_flags(store.get(), X509_V_FLAG_USE_CHECK_TIME | X509_V_FLAG_PARTIAL_CHAIN); + X509_VERIFY_PARAM_set_time(X509_STORE_get0_param(store.get()), util::date::mkgmtime(tm)); + return store; } -int X509CertStore::validate(int ok, X509_STORE_CTX *ctx, const Type &type) +int X509CertStore::validate(int ok, X509_STORE_CTX *ctx) { switch(X509_STORE_CTX_get_error(ctx)) { @@ -167,39 +158,33 @@ int X509CertStore::validate(int ok, X509_STORE_CTX *ctx, const Type &type) default: return ok; } + 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()) + if(type->find(s.type) == type->cend()) // correct service type continue; if(none_of(s.certs, [&](const X509Cert &issuer) { - if(issuer == x509) + if(issuer == x509) // certificate is listed by service return true; - if(X509_check_issued(issuer.handle(), x509) != X509_V_OK) + if(X509_check_issued(issuer.handle(), x509) != X509_V_OK) // certificate is issued by service return false; SCOPE(EVP_PKEY, pub, X509_get_pubkey(issuer.handle())); - if(X509_verify(x509, pub.get()) == 1) + 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; - if(s.validity.empty()) - continue; - X509_STORE_CTX_set_ex_data(ctx, 0, const_cast(&s.validity.begin()->second)); - X509_VERIFY_PARAM *param = X509_STORE_CTX_get0_param(ctx); - if(!(X509_VERIFY_PARAM_get_flags(param) & X509_V_FLAG_USE_CHECK_TIME) || s.validity.empty()) - return 1; - auto current = util::date::to_string(X509_VERIFY_PARAM_get_time(param)); for(auto i = s.validity.crbegin(), end = s.validity.crend(); i != end; ++i) { - if(current >= i->first) - { - if(!i->second.has_value()) - break; - X509_STORE_CTX_set_ex_data(ctx, 0, const_cast(&i->second)); - return 1; - } + 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 1; } } return ok; @@ -209,13 +194,12 @@ int X509CertStore::validate(int ok, X509_STORE_CTX *ctx, const Type &type) * Check if X509Cert is signed by trusted issuer * @throw Exception if error */ -bool X509CertStore::verify(const X509Cert &cert, bool noqscd) const +bool X509CertStore::verify(const X509Cert &cert, bool noqscd, tm validation_time) const { activate(cert); - tm tm{}; - ASN1_TIME_to_tm(X509_get0_notBefore(cert.handle()), &tm); - time_t time = util::date::mkgmtime(tm); - SCOPE(X509_STORE, store, createStore(X509CertStore::CA, &time)); + 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); SCOPE(X509_STORE_CTX, csc, X509_STORE_CTX_new()); if(!X509_STORE_CTX_init(csc.get(), store.get(), cert.handle(), nullptr)) THROW_OPENSSLEXCEPTION("Failed to init X509_STORE_CTX"); diff --git a/src/crypto/X509CertStore.h b/src/crypto/X509CertStore.h index 8221f5093..61c6e724c 100644 --- a/src/crypto/X509CertStore.h +++ b/src/crypto/X509CertStore.h @@ -21,7 +21,8 @@ #include "../Exports.h" -#include +#include "util/memory.h" + #include #include #include @@ -47,15 +48,15 @@ namespace digidoc std::vector certs(const Type &type) const; X509Cert findIssuer(const X509Cert &cert, const Type &type) const; static X509Cert issuerFromAIA(const X509Cert &cert); - static X509_STORE* createStore(const Type &type, const time_t *t = nullptr); - bool verify(const X509Cert &cert, bool qscd) const; + static unique_free_t createStore(const Type &type, tm &tm); + bool verify(const X509Cert &cert, bool noqscd, tm validation_time = {}) const; private: X509CertStore(); ~X509CertStore(); DISABLE_COPY(X509CertStore); - static int validate(int ok, X509_STORE_CTX *ctx, const Type &type); + static int validate(int ok, X509_STORE_CTX *ctx); class Private; std::unique_ptr d; }; diff --git a/src/util/DateTime.cpp b/src/util/DateTime.cpp index 508c2cbab..bf949e89c 100644 --- a/src/util/DateTime.cpp +++ b/src/util/DateTime.cpp @@ -38,7 +38,20 @@ struct tm date::gmtime(time_t t) return tm; } -time_t date::mkgmtime(struct tm &t) +bool date::is_empty(const tm &t) +{ + return t.tm_sec == 0 && + t.tm_min == 0 && + t.tm_hour == 0 && + t.tm_mday == 0 && + t.tm_mon == 0 && + t.tm_year == 0 && + t.tm_wday == 0 && + t.tm_yday == 0 && + t.tm_isdst == 0; +} + +time_t date::mkgmtime(tm &t) { #ifdef _WIN32 return _mkgmtime(&t); @@ -54,11 +67,8 @@ string date::to_string(time_t t) string date::to_string(const tm &date) { - static const tm zero{}; - if(memcmp(&zero, &date, sizeof(zero)) == 0) - return {}; string result(20, 0); if(strftime(result.data(), result.size() + 1, "%Y-%m-%dT%H:%M:%SZ", &date) == 0) - return {}; + result.clear(); return result; } diff --git a/src/util/DateTime.h b/src/util/DateTime.h index 018eaefa8..a5260a796 100644 --- a/src/util/DateTime.h +++ b/src/util/DateTime.h @@ -30,7 +30,8 @@ namespace digidoc { public: static struct tm gmtime(time_t t); - static time_t mkgmtime(struct tm &t); + static bool is_empty(const tm &t); + static time_t mkgmtime(tm &t); static std::string to_string(time_t t); static std::string to_string(const tm &date); }; diff --git a/src/util/memory.h b/src/util/memory.h new file mode 100644 index 000000000..39f1a0516 --- /dev/null +++ b/src/util/memory.h @@ -0,0 +1,46 @@ +/* + * libdigidocpp + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#pragma once + +#include + +template +using unique_free_t = std::unique_ptr; + +template +[[nodiscard]] +constexpr unique_free_t make_unique_ptr(U *p, void (*d)(T*)) noexcept +{ + return {static_cast(p), d}; +} + +template +[[nodiscard]] +constexpr auto make_unique_ptr(nullptr_t, void (*d)(T*)) noexcept +{ + return make_unique_ptr(nullptr, d); +} + +template +[[nodiscard]] +constexpr std::unique_ptr make_unique_ptr(T *p, D d) noexcept +{ + return {p, std::forward(d)}; +}