Error.cc \
Error.h \
ExceptionErrorDetail.h \
+ SysErrorDetail.cc \
SysErrorDetail.h \
forward.h
--- /dev/null
+/*
+ * 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;
+}
+
#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
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
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 */
#include "base/IoManip.h"
#include "fde.h"
#include "security/Io.h"
+#include "ssl/gadgets.h"
namespace Security {
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
}
#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()
{
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();
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()
{
// 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);
}
// 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()) {
$(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 \
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;
}
#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"
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;
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;
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;
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) {
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;
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 *
static const std::string param_proto_version;
/// Parameter name for SSL cipher
static const std::string param_cipher;
+
+private:
+ void tryParsingResponse(CertValidationResponse &);
};
}//namespace Ssl
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));
}
}
*/
#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());
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));
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
}
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
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.
*/
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
*/
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
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()));
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)
+