/* * Copyright (C) 1996-2023 The Squid Software Foundation and contributors * * Squid software is distributed under GPLv2+ license and includes * contributions from numerous individuals and organizations. * Please see the COPYING and CONTRIBUTORS files for details. */ #include "squid.h" #include "base/IoManip.h" #include "error/SysErrorDetail.h" #include "html/Quoting.h" #include "sbuf/SBuf.h" #include "sbuf/Stream.h" #include "security/Certificate.h" #include "security/ErrorDetail.h" #include "security/forward.h" #include "security/Io.h" #include "util.h" #if USE_OPENSSL #include "ssl/ErrorDetailManager.h" #elif HAVE_LIBGNUTLS #if HAVE_GNUTLS_GNUTLS_H #include #endif #endif #include #include namespace Security { // we use std::map to optimize search; TODO: Use std::unordered_map instead? typedef std::map ErrorCodeNames; static const ErrorCodeNames TheErrorCodeNames = { { SQUID_TLS_ERR_ACCEPT, "SQUID_TLS_ERR_ACCEPT" }, { SQUID_TLS_ERR_CONNECT, "SQUID_TLS_ERR_CONNECT" }, { SQUID_X509_V_ERR_INFINITE_VALIDATION, "SQUID_X509_V_ERR_INFINITE_VALIDATION" }, { SQUID_X509_V_ERR_CERT_CHANGE, "SQUID_X509_V_ERR_CERT_CHANGE" }, { SQUID_X509_V_ERR_DOMAIN_MISMATCH, "SQUID_X509_V_ERR_DOMAIN_MISMATCH" }, #if USE_OPENSSL { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT" }, { X509_V_ERR_UNABLE_TO_GET_CRL, "X509_V_ERR_UNABLE_TO_GET_CRL" }, { X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE, "X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE" }, { X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE, "X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE" }, { X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY, "X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY" }, { X509_V_ERR_CERT_SIGNATURE_FAILURE, "X509_V_ERR_CERT_SIGNATURE_FAILURE" }, { X509_V_ERR_CRL_SIGNATURE_FAILURE, "X509_V_ERR_CRL_SIGNATURE_FAILURE" }, { X509_V_ERR_CERT_NOT_YET_VALID, "X509_V_ERR_CERT_NOT_YET_VALID" }, { X509_V_ERR_CERT_HAS_EXPIRED, "X509_V_ERR_CERT_HAS_EXPIRED" }, { X509_V_ERR_CRL_NOT_YET_VALID, "X509_V_ERR_CRL_NOT_YET_VALID" }, { X509_V_ERR_CRL_HAS_EXPIRED, "X509_V_ERR_CRL_HAS_EXPIRED" }, { X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD, "X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD" }, { X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD, "X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD" }, { X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD, "X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD" }, { X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD, "X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD" }, { X509_V_ERR_OUT_OF_MEM, "X509_V_ERR_OUT_OF_MEM" }, { X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, "X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT" }, { X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, "X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN" }, { X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, "X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY" }, { X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, "X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE" }, { X509_V_ERR_CERT_CHAIN_TOO_LONG, "X509_V_ERR_CERT_CHAIN_TOO_LONG" }, { X509_V_ERR_CERT_REVOKED, "X509_V_ERR_CERT_REVOKED" }, { X509_V_ERR_INVALID_CA, "X509_V_ERR_INVALID_CA" }, { X509_V_ERR_PATH_LENGTH_EXCEEDED, "X509_V_ERR_PATH_LENGTH_EXCEEDED" }, { X509_V_ERR_INVALID_PURPOSE, "X509_V_ERR_INVALID_PURPOSE" }, { X509_V_ERR_CERT_UNTRUSTED, "X509_V_ERR_CERT_UNTRUSTED" }, { X509_V_ERR_CERT_REJECTED, "X509_V_ERR_CERT_REJECTED" }, { X509_V_ERR_SUBJECT_ISSUER_MISMATCH, "X509_V_ERR_SUBJECT_ISSUER_MISMATCH" }, { X509_V_ERR_AKID_SKID_MISMATCH, "X509_V_ERR_AKID_SKID_MISMATCH" }, { X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH, "X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH" }, { X509_V_ERR_KEYUSAGE_NO_CERTSIGN, "X509_V_ERR_KEYUSAGE_NO_CERTSIGN" }, #if defined(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER) { X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER, // 33 "X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER" }, #endif #if defined(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION) { X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION, // 34 "X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION" }, #endif #if defined(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN) { X509_V_ERR_KEYUSAGE_NO_CRL_SIGN, // 35 "X509_V_ERR_KEYUSAGE_NO_CRL_SIGN" }, #endif #if defined(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION) { X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION, // 36 "X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION" }, #endif #if defined(X509_V_ERR_INVALID_NON_CA) { X509_V_ERR_INVALID_NON_CA, // 37 "X509_V_ERR_INVALID_NON_CA" }, #endif #if defined(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED) { X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED, // 38 "X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED" }, #endif #if defined(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE) { X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE, // 39 "X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE" }, #endif #if defined(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED) { X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED, // 40 "X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED" }, #endif #if defined(X509_V_ERR_INVALID_EXTENSION) { X509_V_ERR_INVALID_EXTENSION, // 41 "X509_V_ERR_INVALID_EXTENSION" }, #endif #if defined(X509_V_ERR_INVALID_POLICY_EXTENSION) { X509_V_ERR_INVALID_POLICY_EXTENSION, // 42 "X509_V_ERR_INVALID_POLICY_EXTENSION" }, #endif #if defined(X509_V_ERR_NO_EXPLICIT_POLICY) { X509_V_ERR_NO_EXPLICIT_POLICY, // 43 "X509_V_ERR_NO_EXPLICIT_POLICY" }, #endif #if defined(X509_V_ERR_DIFFERENT_CRL_SCOPE) { X509_V_ERR_DIFFERENT_CRL_SCOPE, // 44 "X509_V_ERR_DIFFERENT_CRL_SCOPE" }, #endif #if defined(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE) { X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE, // 45 "X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE" }, #endif #if defined(X509_V_ERR_UNNESTED_RESOURCE) { X509_V_ERR_UNNESTED_RESOURCE, // 46 "X509_V_ERR_UNNESTED_RESOURCE" }, #endif #if defined(X509_V_ERR_PERMITTED_VIOLATION) { X509_V_ERR_PERMITTED_VIOLATION, // 47 "X509_V_ERR_PERMITTED_VIOLATION" }, #endif #if defined(X509_V_ERR_EXCLUDED_VIOLATION) { X509_V_ERR_EXCLUDED_VIOLATION, // 48 "X509_V_ERR_EXCLUDED_VIOLATION" }, #endif #if defined(X509_V_ERR_SUBTREE_MINMAX) { X509_V_ERR_SUBTREE_MINMAX, // 49 "X509_V_ERR_SUBTREE_MINMAX" }, #endif { X509_V_ERR_APPLICATION_VERIFICATION, // 50 "X509_V_ERR_APPLICATION_VERIFICATION" }, #if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE) { X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE, // 51 "X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE" }, #endif #if defined(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX) { X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX, // 52 "X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX" }, #endif #if defined(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX) { X509_V_ERR_UNSUPPORTED_NAME_SYNTAX, // 53 "X509_V_ERR_UNSUPPORTED_NAME_SYNTAX" }, #endif #if defined(X509_V_ERR_CRL_PATH_VALIDATION_ERROR) { X509_V_ERR_CRL_PATH_VALIDATION_ERROR, // 54 "X509_V_ERR_CRL_PATH_VALIDATION_ERROR" }, #endif #if defined(X509_V_ERR_PATH_LOOP) { X509_V_ERR_PATH_LOOP, // 55 "X509_V_ERR_PATH_LOOP" }, #endif #if defined(X509_V_ERR_SUITE_B_INVALID_VERSION) { X509_V_ERR_SUITE_B_INVALID_VERSION, // 56 "X509_V_ERR_SUITE_B_INVALID_VERSION" }, #endif #if defined(X509_V_ERR_SUITE_B_INVALID_ALGORITHM) { X509_V_ERR_SUITE_B_INVALID_ALGORITHM, // 57 "X509_V_ERR_SUITE_B_INVALID_ALGORITHM" }, #endif #if defined(X509_V_ERR_SUITE_B_INVALID_CURVE) { X509_V_ERR_SUITE_B_INVALID_CURVE, // 58 "X509_V_ERR_SUITE_B_INVALID_CURVE" }, #endif #if defined(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM) { X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM, // 59 "X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM" }, #endif #if defined(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED) { X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED, // 60 "X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED" }, #endif #if defined(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256) { X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256, // 61 "X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256" }, #endif #if defined(X509_V_ERR_HOSTNAME_MISMATCH) { X509_V_ERR_HOSTNAME_MISMATCH, // 62 "X509_V_ERR_HOSTNAME_MISMATCH" }, #endif #if defined(X509_V_ERR_EMAIL_MISMATCH) { X509_V_ERR_EMAIL_MISMATCH, // 63 "X509_V_ERR_EMAIL_MISMATCH" }, #endif #if defined(X509_V_ERR_IP_ADDRESS_MISMATCH) { X509_V_ERR_IP_ADDRESS_MISMATCH, // 64 "X509_V_ERR_IP_ADDRESS_MISMATCH" }, #endif #if defined(X509_V_ERR_DANE_NO_MATCH) { X509_V_ERR_DANE_NO_MATCH, // 65 "X509_V_ERR_DANE_NO_MATCH" }, #endif #if defined(X509_V_ERR_EE_KEY_TOO_SMALL) { X509_V_ERR_EE_KEY_TOO_SMALL, // 66 "X509_V_ERR_EE_KEY_TOO_SMALL" }, #endif #if defined(X509_V_ERR_CA_KEY_TOO_SMALL) { X509_V_ERR_CA_KEY_TOO_SMALL, // 67 "X509_V_ERR_CA_KEY_TOO_SMALL" }, #endif #if defined(X509_V_ERR_CA_MD_TOO_WEAK) { X509_V_ERR_CA_MD_TOO_WEAK, // 68 "X509_V_ERR_CA_MD_TOO_WEAK" }, #endif #if defined(X509_V_ERR_INVALID_CALL) { X509_V_ERR_INVALID_CALL, // 69 "X509_V_ERR_INVALID_CALL" }, #endif #if defined(X509_V_ERR_STORE_LOOKUP) { X509_V_ERR_STORE_LOOKUP, // 70 "X509_V_ERR_STORE_LOOKUP" }, #endif #if defined(X509_V_ERR_NO_VALID_SCTS) { X509_V_ERR_NO_VALID_SCTS, // 71 "X509_V_ERR_NO_VALID_SCTS" }, #endif #if defined(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION) { X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION, // 72 "X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION" }, #endif #if defined(X509_V_ERR_OCSP_VERIFY_NEEDED) { X509_V_ERR_OCSP_VERIFY_NEEDED, // 73 "X509_V_ERR_OCSP_VERIFY_NEEDED" }, #endif #if defined(X509_V_ERR_OCSP_VERIFY_FAILED) { X509_V_ERR_OCSP_VERIFY_FAILED, // 74 "X509_V_ERR_OCSP_VERIFY_FAILED" }, #endif #if defined(X509_V_ERR_OCSP_CERT_UNKNOWN) { X509_V_ERR_OCSP_CERT_UNKNOWN, // 75 "X509_V_ERR_OCSP_CERT_UNKNOWN" }, #endif { SSL_ERROR_NONE, "SSL_ERROR_NONE" }, #endif // USE_OPENSSL }; } // namespace Security Security::ErrorCode Security::ErrorCodeFromName(const char *name) { static auto TheCmp = [](const char *a, const char *b) {return strcmp(a, b) < 0;}; static std::map TheErrorCodeByNameIndx(TheCmp); if (TheErrorCodeByNameIndx.empty()) { for (const auto &i: TheErrorCodeNames) TheErrorCodeByNameIndx.insert(std::make_pair(i.second, i.first)); // redirector to support legacy error translations TheErrorCodeByNameIndx.insert(std::make_pair("SQUID_ERR_SSL_HANDSHAKE", SQUID_TLS_ERR_CONNECT)); } const auto it = TheErrorCodeByNameIndx.find(name); if (it != TheErrorCodeByNameIndx.end()) return it->second; return 0; } const char * Security::ErrorNameFromCode(const ErrorCode err, const bool prefixRawCode) { const auto it = TheErrorCodeNames.find(err); if (it != TheErrorCodeNames.end()) return it->second; static char tmpBuffer[128]; snprintf(tmpBuffer, sizeof(tmpBuffer), "%s%d", (prefixRawCode ? "SSL_ERR=" : ""), static_cast(err)); return tmpBuffer; } /* Security::ErrorDetail */ /// helper constructor implementing the logic shared by the two public ones Security::ErrorDetail::ErrorDetail(const ErrorCode err, const int aSysErrorNo): error_no(err), // We could restrict errno(3) collection to cases where the TLS library // explicitly talks about the errno being set, but correctly detecting those // cases is difficult. We simplify in hope that all other cases will either // have a useful errno or a zero errno. sysErrorNo(aSysErrorNo) { #if USE_OPENSSL /// Extract and remember errors stored internally by the TLS library. if ((lib_error_no = ERR_get_error())) { debugs(83, 7, "got 0x" << asHex(lib_error_no)); // more errors may be stacked // TODO: Save/detail all stacked errors by always flushing stale ones. ForgetErrors(); } #else // other libraries return errors explicitly instead of auto-storing them #endif } Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const CertPointer &cert, const CertPointer &broken, const char *aReason): ErrorDetail(anErrorCode, 0) { errReason = aReason; peer_cert = cert; broken_cert = broken ? broken : cert; } #if USE_OPENSSL Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const int anIoErrorNo, const int aSysErrorNo): ErrorDetail(anErrorCode, aSysErrorNo) { ioErrorNo = anIoErrorNo; } #elif HAVE_LIBGNUTLS Security::ErrorDetail::ErrorDetail(const ErrorCode anErrorCode, const LibErrorCode aLibErrorNo, const int aSysErrorNo): ErrorDetail(anErrorCode, aSysErrorNo) { lib_error_no = aLibErrorNo; } #endif void Security::ErrorDetail::setPeerCertificate(const CertPointer &cert) { assert(cert); assert(!peer_cert); assert(!broken_cert); peer_cert = cert; // unlike the constructor, the supplied certificate is not a broken_cert } SBuf Security::ErrorDetail::brief() const { SBufStream os; printErrorCode(os); if (lib_error_no) { #if USE_OPENSSL // TODO: Log ERR_error_string_n() instead, despite length, whitespace? // Example: `error:1408F09C:SSL routines:ssl3_get_record:http request`. os << "+TLS_LIB_ERR=" << asHex(lib_error_no).upperCase(); #elif HAVE_LIBGNUTLS os << '+' << gnutls_strerror_name(lib_error_no); #endif } #if USE_OPENSSL // TODO: Consider logging long but human-friendly names (e.g., // SSL_ERROR_SYSCALL). if (ioErrorNo) os << "+TLS_IO_ERR=" << ioErrorNo; #endif if (sysErrorNo) { os << '+' << SysErrorDetail::Brief(sysErrorNo); } if (broken_cert) os << "+broken_cert"; return os.buf(); } SBuf Security::ErrorDetail::verbose(const HttpRequestPointer &request) const { std::optional customFormat; #if USE_OPENSSL if (const auto errorDetail = Ssl::ErrorDetailsManager::GetInstance().findDetail(error_no, request)) customFormat = errorDetail->detail; #else (void)request; #endif auto format = customFormat ? customFormat->c_str() : "SSL handshake error (%err_name)"; SBufStream os; assert(format); auto remainder = format; while (auto p = strchr(remainder, '%')) { os.write(remainder, p - remainder); const auto formattingCodeLen = convertErrorCodeToDescription(++p, os); if (!formattingCodeLen) os << '%'; remainder = p + formattingCodeLen; } os << remainder; return os.buf(); } /// textual representation of the subject of the broken certificate void Security::ErrorDetail::printSubject(std::ostream &os) const { if (broken_cert) { auto buf = SubjectName(*broken_cert); if (!buf.isEmpty()) { // TODO: Convert html_quote() into an std::ostream manipulator. // quote to avoid possible html code injection through // certificate subject os << html_quote(buf.c_str()); return; } } os << "[Not available]"; } #if USE_OPENSSL /// prints X.509 names extracted using Ssl::HasMatchingSubjectName() class CommonNamesPrinter: public Ssl::GeneralNameMatcher { public: explicit CommonNamesPrinter(std::ostream &os): os_(os) {} /// the number of names printed so far mutable size_t printed = 0; protected: /* GeneralNameMatcher API */ bool matchDomainName(const Dns::DomainName &) const override; bool matchIp(const Ip::Address &) const override; private: std::ostream &itemStream() const; std::ostream &os_; ///< destination for printed names }; bool CommonNamesPrinter::matchDomainName(const Dns::DomainName &domain) const { // TODO: Convert html_quote() into an std::ostream manipulator accepting (buf, n). itemStream() << html_quote(SBuf(domain).c_str()); return false; // keep going } bool CommonNamesPrinter::matchIp(const Ip::Address &ip) const { char hostStr[MAX_IPSTRLEN]; (void)ip.toStr(hostStr, sizeof(hostStr)); // no html_quote() is needed; no brackets itemStream().write(hostStr, strlen(hostStr)); return false; // keep going } /// prints a comma in front of each item except for the very first one /// \returns a stream for printing the name std::ostream & CommonNamesPrinter::itemStream() const { if (printed++) os_ << ", "; return os_; } #endif // USE_OPENSSL /// a list of the broken certificates CN and alternate names void Security::ErrorDetail::printCommonName(std::ostream &os) const { #if USE_OPENSSL if (broken_cert.get()) { CommonNamesPrinter printer(os); (void)Ssl::HasMatchingSubjectName(*broken_cert, printer); if (printer.printed) return; } #endif // USE_OPENSSL os << "[Not available]"; } /// the issuer of the broken certificate void Security::ErrorDetail::printCaName(std::ostream &os) const { if (broken_cert) { auto buf = IssuerName(*broken_cert); if (!buf.isEmpty()) { // quote to avoid possible html code injection through // certificate issuer subject os << html_quote(buf.c_str()); return; } } os << "[Not available]"; } /// textual representation of the "not before" field of the broken certificate void Security::ErrorDetail::printNotBefore(std::ostream &os) const { #if USE_OPENSSL if (broken_cert.get()) { if (const auto tm = X509_getm_notBefore(broken_cert.get())) { // TODO: Add and use an ASN1_TIME printing operator instead. static char tmpBuffer[256]; // A temporary buffer Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); os << tmpBuffer; return; } } #endif // USE_OPENSSL os << "[Not available]"; } /// textual representation of the "not after" field of the broken certificate void Security::ErrorDetail::printNotAfter(std::ostream &os) const { #if USE_OPENSSL if (broken_cert.get()) { if (const auto tm = X509_getm_notAfter(broken_cert.get())) { // XXX: Reduce code duplication. static char tmpBuffer[256]; // A temporary buffer Ssl::asn1timeToString(tm, tmpBuffer, sizeof(tmpBuffer)); os << tmpBuffer; return; } } #endif // USE_OPENSSL os << "[Not available]"; } /// textual representation of error_no void Security::ErrorDetail::printErrorCode(std::ostream &os) const { #if USE_OPENSSL // try detailEntry first because it is faster if (detailEntry) { os << detailEntry->name; return; } #endif os << ErrorNameFromCode(error_no); } /// short description of error_no void Security::ErrorDetail::printErrorDescription(std::ostream &os) const { if (!error_no) { os << "[No Error]"; return; } #if USE_OPENSSL if (detailEntry) { os << detailEntry->descr; return; } #endif os << "[Not available]"; } /// textual representation of lib_error_no void Security::ErrorDetail::printErrorLibError(std::ostream &os) const { if (errReason.size() > 0) os << errReason; else if (lib_error_no) os << ErrorString(lib_error_no); else os << "[No Error]"; } /** * Converts the code to a string value. Supported formatting codes are: * * Error meta information: * %err_name: The name of a high-level SSL error (e.g., X509_V_ERR_*) * %ssl_error_descr: A short description of the SSL error * %ssl_lib_error: human-readable low-level error string by ErrorString() * * Certificate information extracted from broken (not necessarily peer!) cert * %ssl_cn: The comma-separated list of common and alternate names * %ssl_subject: The certificate subject * %ssl_ca_name: The certificate issuer name * %ssl_notbefore: The certificate "not before" field * %ssl_notafter: The certificate "not after" field * \returns the length of the code (the number of characters to be replaced by value) \retval 0 for unsupported codes */ size_t Security::ErrorDetail::convertErrorCodeToDescription(const char * const code, std::ostream &os) const { using PartDescriber = void (ErrorDetail::*)(std::ostream &os) const; static const std::map PartDescriberByCode = { {"ssl_subject", &ErrorDetail::printSubject}, {"ssl_ca_name", &ErrorDetail::printCaName}, {"ssl_cn", &ErrorDetail::printCommonName}, {"ssl_notbefore", &ErrorDetail::printNotBefore}, {"ssl_notafter", &ErrorDetail::printNotAfter}, {"err_name", &ErrorDetail::printErrorCode}, {"ssl_error_descr", &ErrorDetail::printErrorDescription}, {"ssl_lib_error", &ErrorDetail::printErrorLibError} }; // We can refactor the map to find matches without looping, but that // requires a "starts with" comparison function -- `code` length is unknown. for (const auto &pair: PartDescriberByCode) { const auto len = strlen(pair.first); if (strncmp(code, pair.first, len) == 0) { const auto method = pair.second; (this->*method)(os); return len; } } // TODO: Support logformat %codes. return 0; }