]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Fixed (and renamed) Ssl::ReadX509Certificate() API (#1048)
authorAlex Rousskov <rousskov@measurement-factory.com>
Mon, 16 May 2022 12:53:26 +0000 (12:53 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Tue, 17 May 2022 13:31:52 +0000 (13:31 +0000)
This pointer-returning non-throwing function was used to load an unknown
number of certificates from a file. A nil result normally indicated the
end of the certificate sequence but also a certificate loading failure.
The caller could not distinguish the two nil-result outcomes without
OpenSSL error stack analysis, which is difficult to do correctly, and
which none of the callers bothered to do, resulting in some certificate
configuration errors silently ignored by Squid.

The adjusted API uses exceptions to report errors. A nil result is still
used to report the end of the certificate sequence. To help callers that
must have at least one certificate, one function is now dedicated to
loading required certificate(s) while a companion function is added to
load optional ones.

Avoid exponential growth in the number of changed functions by wrapping
callers inside the not-ready-to-throw code chains (that cannot be easily
converted) with try/catch-return-false blocks.

Also removed the need for Ssl::readCertFromMemory() by adding
Ssl::ReadOnlyBioTiedTo(). Functions reading things (from various input
sources) should be kept separately from functions creating input sources
(for various reading functions). Otherwise, we will end up creating a
lot more functions (at least readers*creators) with virtually no gain in
performance or convenience. OpenSSL BIO API exists specifically to
separate I/O mechanisms from I/O users. Use it to our advantage.

We now also clear old/stale OpenSSL error stack before performing the
certificate loading operation that may trigger one or more errors.

Also forget stale errno when forgetting OpenSSL errors. This reset
avoids misleading error reports based on stale errno values.

Also detail certificate loading errors, now that they are not ignored.

ReportSysError was added to avoid SysErrorDetail::NewIfAny() memory
allocations when reporting basic system call errors. Perhaps there is a
way to reuse SysErrorDetail here via (enhanced) Optional, but the
resulting complexity and the current SBuf-based reporting overheads do
not justify the reuse of "if errno then strerror(errno)" logic. We may
revisit this when Optional supports non-trivial classes.

Also moved most SysErrorDetail implementation details to the newly added
.cc file (to reduce user exposure), replacing strerror() with xstrerr().

ForgetErrors guts were moved from Security to Ssl because we cannot link
helpers with src/security (yet). We may have to refactor Security to
make such reuse possible in the future.

14 files changed:
src/error/Makefile.am
src/error/SysErrorDetail.cc [new file with mode: 0644]
src/error/SysErrorDetail.h
src/security/Io.cc
src/security/KeyData.cc
src/security/cert_generators/file/Makefile.am
src/security/cert_generators/file/certificate_db.cc
src/ssl/cert_validate_message.cc
src/ssl/cert_validate_message.h
src/ssl/crtd_message.cc
src/ssl/gadgets.cc
src/ssl/gadgets.h
src/ssl/support.cc
src/tests/stub_liberror.cc

index bdc6d40edd867a572add44e180888ba186e7c5b3..e42d308e92822e0f0a2072c8bdceda44132172f2 100644 (file)
@@ -22,6 +22,7 @@ liberror_la_SOURCES = \
        Error.cc \
        Error.h \
        ExceptionErrorDetail.h \
+       SysErrorDetail.cc \
        SysErrorDetail.h \
        forward.h
 
diff --git a/src/error/SysErrorDetail.cc b/src/error/SysErrorDetail.cc
new file mode 100644 (file)
index 0000000..27fce4e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 1996-2022 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 sys_error_details.
+ */
+
+#include "squid.h"
+#include "error/SysErrorDetail.h"
+#include "sbuf/SBuf.h"
+#include "sbuf/Stream.h"
+
+SBuf
+SysErrorDetail::Brief(int errorNo)
+{
+    return SysErrorDetail(errorNo).brief();
+}
+
+SBuf
+SysErrorDetail::brief() const
+{
+    return ToSBuf("errno=", errorNo);
+}
+
+SBuf
+SysErrorDetail::verbose(const HttpRequestPointer &) const
+{
+    return SBuf(xstrerr(errorNo));
+}
+
+std::ostream &
+operator <<(std::ostream &os, const ReportSysError rse)
+{
+    if (const auto errorNo = rse.errorNo)
+        os << Debug::Extra << "system call error: " << xstrerr(errorNo);
+    return os;
+}
+
index 2eafc12587aa85eb53bbd9d03b916d8f3f7ab0d9..f52a629aa1c16f3251c00920afc257c0cc27155d 100644 (file)
@@ -10,8 +10,7 @@
 #define _SQUID_SRC_ERROR_SYSERRORDETAIL_H
 
 #include "error/Detail.h"
-#include "sbuf/SBuf.h"
-#include "sbuf/Stream.h"
+#include "sbuf/forward.h"
 
 /// system call failure detail based on standard errno(3)/strerror(3) APIs
 class SysErrorDetail: public ErrorDetail
@@ -26,18 +25,12 @@ public:
         return errorNo ? new SysErrorDetail(errorNo) : nullptr;
     }
 
-    static SBuf Brief(int errorNo) {
-        return SysErrorDetail(errorNo).brief();
-    }
+    /// \copydoc ErrorDetail::brief()
+    static SBuf Brief(int errorNo);
 
     /* ErrorDetail API */
-    virtual SBuf brief() const override {
-        return ToSBuf("errno=", errorNo);
-    }
-
-    virtual SBuf verbose(const HttpRequestPointer &) const override {
-        return SBuf(strerror(errorNo));
-    }
+    virtual SBuf brief() const override;
+    virtual SBuf verbose(const HttpRequestPointer &) const override;
 
 private:
     // hidden by NewIfAny() to avoid creating SysErrorDetail from zero errno
@@ -46,5 +39,16 @@ private:
     int errorNo; ///< errno(3) set by the last failed system call or equivalent
 };
 
+/// a stream manipulator for printing a system call error (if any)
+class ReportSysError
+{
+public:
+    explicit ReportSysError(const int anErrorNo): errorNo(anErrorNo) {}
+    int errorNo;
+};
+
+/// reports a system call error (if any) on a dedicated Debug::Extra line
+std::ostream &operator <<(std::ostream &, ReportSysError);
+
 #endif /* _SQUID_SRC_ERROR_SYSERRORDETAIL_H */
 
index 7ea81dffec94522e22b0406f933a21d50b228b61..1c2cc09892c4bb9219cac14516a48e52ea7ae932 100644 (file)
@@ -12,6 +12,7 @@
 #include "base/IoManip.h"
 #include "fde.h"
 #include "security/Io.h"
+#include "ssl/gadgets.h"
 
 namespace Security {
 
@@ -50,15 +51,13 @@ Security::IoResult::print(std::ostream &os) const
         os << ", important";
 }
 
-// TODO: Replace high-level ERR_get_error() calls with a new std::ostream
-// ReportErrors manipulator inside debugs(), followed by a ForgetErrors() call.
+// TODO: Replace high-level ERR_get_error() calls with ForgetErrors() calls or
+// exceptions carrying ReportAndForgetErrors() reports.
 void
 Security::ForgetErrors()
 {
 #if USE_OPENSSL
-    unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary
-    while (const auto errorToForget = ERR_get_error())
-        debugs(83, 7, '#' << (++reported) << ": " << asHex(errorToForget));
+    Ssl::ForgetErrors();
 #endif
 }
 
index b5ded80a1b5aa1a7cd5f9370ea4ba07cc09d2662..8e3b577acc0724b988ce81003e961221c5737fee 100644 (file)
 #include "ssl/bio.h"
 #include "ssl/gadgets.h"
 
-/**
- * Read certificate from file.
- * See also: Ssl::ReadX509Certificate function, gadgets.cc file
- */
+/// load a signing certificate from certFile
 bool
 Security::KeyData::loadX509CertFromFile()
 {
@@ -33,7 +30,16 @@ Security::KeyData::loadX509CertFromFile()
         return false;
     }
 
-    cert = Ssl::ReadX509Certificate(bio); // error detected/reported below
+    try {
+        cert = Ssl::ReadCertificate(bio);
+        return true;
+    }
+    catch (...) {
+        // TODO: Convert the rest of this method to throw on errors instead.
+        debugs(83, DBG_IMPORTANT, "ERROR: unable to load certificate file '" << certFile << "':" <<
+               Debug::Extra << "problem: " << CurrentException);
+        return false;
+    }
 
 #elif USE_GNUTLS
     const char *certFilename = certFile.c_str();
@@ -77,10 +83,7 @@ Security::KeyData::loadX509CertFromFile()
     return bool(cert);
 }
 
-/**
- * Read certificate from file.
- * See also: Ssl::ReadX509Certificate function, gadgets.cc file
- */
+/// load any intermediate certs that form the chain with the loaded signing cert
 void
 Security::KeyData::loadX509ChainFromFile()
 {
@@ -105,7 +108,7 @@ Security::KeyData::loadX509ChainFromFile()
         // and add to the chain any other certificate exist in the file
         CertPointer latestCert = cert;
 
-        while (const auto ca = Ssl::ReadX509Certificate(bio)) {
+        while (const auto ca = Ssl::ReadOptionalCertificate(bio)) {
             // get Issuer name of the cert for debug display
             char *nameStr = X509_NAME_oneline(X509_get_subject_name(ca.get()), nullptr, 0);
 
@@ -197,7 +200,14 @@ Security::KeyData::loadFromFiles(const AnyP::PortCfg &port, const char *portType
     }
 
     // certificate chain in the PEM file is optional
-    loadX509ChainFromFile();
+    try {
+        loadX509ChainFromFile();
+    }
+    catch (...) {
+        // XXX: Reject malformed configurations by letting exceptions propagate.
+        debugs(83, DBG_CRITICAL, "ERROR: '" << portType << "_port " << port.s.toUrl(buf, sizeof(buf)) << "' cannot load intermediate certificates from '" << certFile << "':" <<
+               Debug::Extra << "problem: " << CurrentException);
+    }
 
     // pkey is mandatory, not having it makes cert and chain pointless.
     if (!loadX509PrivateKeyFromFile()) {
index 168cf2030c3bbf8b14612cc193e0d8554ef02858..15951083a4ad10c057a86801a08a0a45160fbc3d 100644 (file)
@@ -27,6 +27,7 @@ security_file_certgen_LDADD = \
        $(top_builddir)/src/ssl/libsslutil.la \
        $(top_builddir)/src/sbuf/libsbuf.la \
        $(top_builddir)/src/debug/libdebug.la \
+       $(top_builddir)/src/error/liberror.la \
        $(top_builddir)/src/comm/libminimal.la \
        $(top_builddir)/src/mem/libminimal.la \
        $(top_builddir)/src/base/libbase.la \
index 8af1b6bc152ca3fdc2059a59cebb96ffbeded999..01e0cde27a74a913148016661fbaacb0361a5844 100644 (file)
@@ -649,11 +649,14 @@ Ssl::CertificateDb::ReadEntry(std::string filename, Security::CertPointer &cert,
     Ssl::BIO_Pointer bio;
     if (!Ssl::OpenCertsFileForReading(bio, filename.c_str()))
         return false;
-    if (!(cert = Ssl::ReadX509Certificate(bio)))
-        return false;
+
+    cert = Ssl::ReadCertificate(bio);
+
     if (!Ssl::ReadPrivateKey(bio, pkey, NULL))
         return false;
-    orig = Ssl::ReadX509Certificate(bio); // optional; may be nil
+
+    orig = Ssl::ReadOptionalCertificate(bio);
+
     return true;
 }
 
index b7b691eaa3804e68a432352f96739f9337dbdada..8edf37e543bbca1c05c65424ca979c7f8d5af2bb 100644 (file)
@@ -10,6 +10,7 @@
 #include "acl/FilledChecklist.h"
 #include "globals.h"
 #include "helper.h"
+#include "sbuf/Stream.h"
 #include "security/CertError.h"
 #include "ssl/cert_validate_message.h"
 #include "ssl/ErrorDetail.h"
@@ -90,6 +91,22 @@ get_error_id(const char *label, size_t len)
 
 bool
 Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp)
+{
+    try {
+        tryParsingResponse(resp);
+        return true;
+    }
+    catch (...) {
+        debugs(83, DBG_IMPORTANT, "ERROR: Cannot parse sslcrtvalidator_program response:" <<
+               Debug::Extra << "problem: " << CurrentException);
+        return false;
+    }
+}
+
+/// implements primary parseResponse() functionality until that method callers
+/// are ready to handle exceptions
+void
+Ssl::CertValidationMsg::tryParsingResponse(CertValidationResponse &resp)
 {
     std::vector<CertItem> certs;
 
@@ -103,8 +120,7 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp)
 
         size_t param_len = strcspn(param, "=\r\n");
         if (param[param_len] !=  '=') {
-            debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: " << param);
-            return false;
+            throw TextException(ToSBuf("Missing parameter value: ", param), Here());
         }
         const char *value=param+param_len+1;
 
@@ -112,15 +128,13 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp)
                 strncmp(param, param_cert.c_str(), param_cert.length()) == 0) {
             CertItem ci;
             ci.name.assign(param, param_len);
-            Security::CertPointer x509;
-            readCertFromMemory(x509, value);
+            const auto x509 = ReadCertificate(ReadOnlyBioTiedTo(value));
             ci.setCert(x509.get());
             certs.push_back(ci);
 
             const char *b = strstr(value, "-----END CERTIFICATE-----");
             if (b == NULL) {
-                debugs(83, DBG_IMPORTANT, "WARNING: cert Validator response parse error: Failed  to find certificate boundary " << value);
-                return false;
+                throw TextException(ToSBuf("Missing END CERTIFICATE boundary: ", value), Here());
             }
             b += strlen("-----END CERTIFICATE-----");
             param = b + 1;
@@ -140,8 +154,7 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp)
                 strncmp(param, param_error_name.c_str(), param_error_name.length()) == 0) {
             currentItem.error_no = Ssl::GetErrorCode(v.c_str());
             if (currentItem.error_no == SSL_ERROR_NONE) {
-                debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: Unknown SSL Error: " << v);
-                return false;
+                throw TextException(ToSBuf("Unknown TLS error name: ", v), Here());
             }
         } else if (param_len > param_error_reason.length() &&
                    strncmp(param, param_error_reason.c_str(), param_error_reason.length()) == 0) {
@@ -167,8 +180,7 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp)
                    std::all_of(v.begin(), v.end(), isdigit)) {
             currentItem.error_depth = atoi(v.c_str());
         } else {
-            debugs(83, DBG_IMPORTANT, "WARNING: cert validator response parse error: Unknown parameter name " << std::string(param, param_len).c_str());
-            return false;
+            throw TextException(ToSBuf("Unknown parameter name: ", std::string(param, param_len)), Here());
         }
 
         param = value + value_len;
@@ -178,12 +190,9 @@ Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp)
     typedef Ssl::CertValidationResponse::RecvdErrors::const_iterator SVCRECI;
     for (SVCRECI i = resp.errors.begin(); i != resp.errors.end(); ++i) {
         if (i->error_no == SSL_ERROR_NONE) {
-            debugs(83, DBG_IMPORTANT, "WARNING: cert validator incomplete response: Missing error name from error_id: " << i->id);
-            return false;
+            throw TextException(ToSBuf("Incomplete response; missing error name from error_id: ", i->id), Here());
         }
     }
-
-    return true;
 }
 
 X509 *
index 87a5b4862408a45f9f7adaa3d7a14b99a99cac73..a448f48f4ab8c1ca59bfca3275d84b17e63afc41 100644 (file)
@@ -123,6 +123,9 @@ public:
     static const std::string param_proto_version;
     /// Parameter name for SSL cipher
     static const std::string param_cipher;
+
+private:
+    void tryParsingResponse(CertValidationResponse &);
 };
 
 }//namespace Ssl
index df7068e9bb2504f8ea7b0d262d7ca00b1169c634..832ce94763cf60b9d119e7e11ca572b7cb5fd139 100644 (file)
@@ -228,7 +228,7 @@ Ssl::CrtdMessage::parseRequest(CertificateProperties &certProperties)
     if ((pos = certs_part.find(CERT_BEGIN_STR)) != std::string::npos) {
         pos += CERT_BEGIN_STR.length();
         if ((pos= certs_part.find(CERT_BEGIN_STR, pos)) != std::string::npos)
-            Ssl::readCertFromMemory(certProperties.mimicCert, certs_part.c_str() + pos);
+            certProperties.mimicCert = ReadCertificate(ReadOnlyBioTiedTo(certs_part.c_str() + pos));
     }
 }
 
index b03489bbe5267247c0a8d6f05bd4722f2d5d344e..eb3ca3b54658ca129d40b6a9535f28148f6cdf82 100644 (file)
@@ -7,8 +7,47 @@
  */
 
 #include "squid.h"
+#include "base/IoManip.h"
+#include "error/SysErrorDetail.h"
+#include "security/Io.h"
+#include "sbuf/Stream.h"
 #include "ssl/gadgets.h"
 
+void
+Ssl::ForgetErrors()
+{
+    if (ERR_peek_last_error()) {
+        debugs(83, 5, "forgetting stale OpenSSL errors:" << ReportAndForgetErrors);
+        // forget errors if section/level-specific debugging above was disabled
+        while (ERR_get_error()) {}
+    }
+
+    // Technically, the caller should just ignore (potentially stale) errno when
+    // no system calls have failed. However, due to OpenSSL error-reporting API
+    // deficiencies, many callers cannot detect when a TLS error was caused by a
+    // system call failure. We forget the stale errno (just like we forget stale
+    // OpenSSL errors above) so that the caller only uses fresh errno values.
+    errno = 0;
+}
+
+std::ostream &
+Ssl::ReportAndForgetErrors(std::ostream &os)
+{
+    unsigned int reported = 0; // efficiently marks ForgetErrors() call boundary
+    while (const auto errorToForget = ERR_get_error())
+        os << Debug::Extra << "OpenSSL-saved error #" << (++reported) << ": " << asHex(errorToForget);
+    return os;
+}
+
+[[ noreturn ]] static void
+ThrowErrors(const char * const problem, const int savedErrno, const SourceLocation &where)
+{
+    throw TextException(ToSBuf(problem, ": ",
+                               Ssl::ReportAndForgetErrors,
+                               ReportSysError(savedErrno)),
+                        where);
+}
+
 EVP_PKEY * Ssl::createSslPrivateKey()
 {
     Security::PrivateKeyPointer pkey(EVP_PKEY_new());
@@ -115,8 +154,15 @@ bool Ssl::readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Security
     Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
     BIO_puts(bio.get(), bufferToRead);
 
-    if (!(cert = Ssl::ReadX509Certificate(bio)))
+    try {
+        cert = ReadCertificate(bio);
+    } catch (...) {
+        debugs(83, DBG_IMPORTANT, "ERROR: Cannot deserialize a signing certificate:" <<
+               Debug::Extra << "problem: " << CurrentException);
+        cert.reset();
+        pkey.reset();
         return false;
+    }
 
     EVP_PKEY * pkeyPtr = NULL;
     pkey.resetWithoutLocking(PEM_read_bio_PrivateKey(bio.get(), &pkeyPtr, 0, 0));
@@ -126,15 +172,18 @@ bool Ssl::readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Security
     return true;
 }
 
-bool Ssl::readCertFromMemory(Security::CertPointer & cert, char const * bufferToRead)
+// TODO: Convert matching BIO_s_mem() callers.
+Ssl::BIO_Pointer
+Ssl::ReadOnlyBioTiedTo(const char * const bufferToRead)
 {
-    Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
-    BIO_puts(bio.get(), bufferToRead);
-
-    if (!(cert = Ssl::ReadX509Certificate(bio)))
-        return false;
-
-    return true;
+    ForgetErrors();
+    // OpenSSL BIO API is not const-correct, but OpenSSL does not free or modify
+    // BIO_new_mem_buf() data because it is marked with BIO_FLAGS_MEM_RDONLY.
+    const auto castedBuffer = const_cast<char*>(bufferToRead);
+    if (const auto bio = BIO_new_mem_buf(castedBuffer, -1)) // no memcpy()
+        return BIO_Pointer(bio);
+    const auto savedErrno = errno;
+    ThrowErrors("cannot allocate OpenSSL BIO structure", savedErrno, Here());
 }
 
 // According to RFC 5280 (Section A.1), the common name length in a certificate
@@ -689,10 +738,38 @@ Ssl::OpenCertsFileForReading(Ssl::BIO_Pointer &bio, const char *filename)
 }
 
 Security::CertPointer
-Ssl::ReadX509Certificate(const BIO_Pointer &bio)
+Ssl::ReadOptionalCertificate(const BIO_Pointer &bio)
+{
+    Assure(bio);
+    ForgetErrors();
+    if (const auto cert = PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr))
+        return Security::CertPointer(cert);
+    const auto savedErrno = errno;
+
+    // PEM_R_NO_START_LINE means OpenSSL could not find a BEGIN CERTIFICATE
+    // marker after successfully reading input. That includes such use cases as
+    // empty input, valid input exhausted by previous extractions, malformed
+    // input, and valid key-only input without the certificate. We cannot
+    // distinguish all these outcomes and treat this error as an EOF condition.
+    if (ERR_GET_REASON(ERR_peek_last_error()) == PEM_R_NO_START_LINE) {
+        // consume PEM_R_NO_START_LINE to clean global error queue (if that was
+        // the only error) and/or to let us check for other errors (otherwise)
+        (void)ERR_get_error();
+        if (!ERR_peek_last_error())
+            return nullptr; // EOF without any other errors
+    }
+
+    ThrowErrors("cannot read a PEM-encoded certificate", savedErrno, Here());
+}
+
+Security::CertPointer
+Ssl::ReadCertificate(const BIO_Pointer &bio)
 {
-    assert(bio);
-    return Security::CertPointer(PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+    if (const auto cert = ReadOptionalCertificate(bio))
+        return cert;
+
+    // PEM_R_NO_START_LINE
+    throw TextException("missing a required PEM-encoded certificate", Here());
 }
 
 bool
index 3c4de81b07b0adb7ca30d7e8c30a5774bb9f1deb..1785e72bbca7df0ae7ad9f1f57048847e3648e57 100644 (file)
@@ -72,6 +72,16 @@ typedef std::unique_ptr<GENERAL_NAME, HardFun<void, GENERAL_NAME*, &GENERAL_NAME
 typedef std::unique_ptr<X509_EXTENSION, HardFun<void, X509_EXTENSION*, &X509_EXTENSION_free>> X509_EXTENSION_Pointer;
 
 typedef std::unique_ptr<X509_STORE_CTX, HardFun<void, X509_STORE_CTX *, &X509_STORE_CTX_free>> X509_STORE_CTX_Pointer;
+
+/// Clear any errors accumulated by OpenSSL in its global storage.
+void ForgetErrors();
+
+/// Manipulator to report errors accumulated by OpenSSL in its global storage.
+/// Each error is reported on a dedicated Debug::Extra line.
+/// Nothing is reported if there are no errors.
+/// Also clears all reported errors.
+std::ostream &ReportAndForgetErrors(std::ostream &);
+
 /**
  \ingroup SslCrtdSslAPI
  * Create 1024 bits rsa key.
@@ -96,11 +106,9 @@ bool appendCertToMemory(Security::CertPointer const & cert, std::string & buffer
  */
 bool readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Security::PrivateKeyPointer & pkey, char const * bufferToRead);
 
-/**
- \ingroup SslCrtdSslAPI
- * Read SSL certificate from memory.
- */
-bool readCertFromMemory(Security::CertPointer & cert, char const * bufferToRead);
+/// Creates and returns a BIO for reading from the given c-string.
+/// The returned BIO lifetime must not exceed that of the given c-string!
+BIO_Pointer ReadOnlyBioTiedTo(const char *);
 
 /**
  \ingroup SslCrtdSslAPI
@@ -114,9 +122,13 @@ void ReadPrivateKeyFromFile(char const * keyFilename, Security::PrivateKeyPointe
  */
 bool OpenCertsFileForReading(BIO_Pointer &bio, const char *filename);
 
-/// reads and returns a certificate using the given OpenSSL BIO
-/// \returns a nil pointer on errors (TODO: throw instead)
-Security::CertPointer ReadX509Certificate(const BIO_Pointer &);
+/// Reads and returns a certificate using the given OpenSSL BIO.
+/// Never returns a nil pointer.
+Security::CertPointer ReadCertificate(const BIO_Pointer &);
+
+/// Reads and returns a certificate using the given OpenSSL BIO.
+/// \returns a nil pointer if the given BIO is empty or exhausted
+Security::CertPointer ReadOptionalCertificate(const BIO_Pointer &);
 
 /**
  \ingroup SslCrtdSslAPI
index 112407723dbcbe02e1aadeb97e8c8e979e49985f..873b269d112186423ac8318cd1af8c7582d8ec02 100644 (file)
@@ -1116,7 +1116,7 @@ Ssl::loadCerts(const char *certsFile, Ssl::CertsIndexedList &list)
         return false;
     }
 
-    while (auto aCert = ReadX509Certificate(in)) {
+    while (auto aCert = ReadOptionalCertificate(in)) {
         static char buffer[2048];
         X509_NAME_oneline(X509_get_subject_name(aCert.get()), buffer, sizeof(buffer));
         list.insert(std::pair<SBuf, X509 *>(SBuf(buffer), aCert.release()));
index f5f977d2bd568d782eabadda786e0d8ca50ab349..51bbcb2bbaf70c7509e6c1b59f887d7b1547ba3c 100644 (file)
@@ -13,3 +13,9 @@ std::ostream &operator <<(std::ostream &os, const Error &) STUB_RETVAL(os)
 
 ErrorDetail::Pointer MakeNamedErrorDetail(const char *) STUB_RETVAL(ErrorDetail::Pointer())
 
+#include "error/SysErrorDetail.h"
+SBuf SysErrorDetail::Brief(int) STUB_RETVAL(SBuf())
+SBuf SysErrorDetail::brief() const STUB_RETVAL(SBuf())
+SBuf SysErrorDetail::verbose(const HttpRequestPointer &) const STUB_RETVAL(SBuf())
+std::ostream &operator <<(std::ostream &os, ReportSysError) STUB_RETVAL(os)
+