+/*
+ * 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 "acl/FilledChecklist.h"
+#include "globals.h"
#include "helper.h"
-#include "ssl/support.h"
+#include "sbuf/Stream.h"
+#include "security/CertError.h"
#include "ssl/cert_validate_message.h"
#include "ssl/ErrorDetail.h"
+#include "ssl/support.h"
+#include "util.h"
+
+/// Retrieves the certificates chain used to verify the peer.
+/// This is the full chain built by OpenSSL while verifying the server
+/// certificate or, if this is not available, the chain sent by server.
+/// \return the certificates chain or nil
+static STACK_OF(X509) *
+PeerValidationCertificatesChain(const Security::SessionPointer &ssl)
+{
+ assert(ssl);
+ // The full chain built by openSSL while verifying the server cert,
+ // retrieved from verify callback:
+ if (const auto certs = static_cast<STACK_OF(X509) *>(SSL_get_ex_data(ssl.get(), ssl_ex_index_ssl_cert_chain)))
+ return certs;
+
+ /// Last resort: certificates chain sent by server
+ return SSL_get_peer_cert_chain(ssl.get()); // may be nil
+}
void
Ssl::CertValidationMsg::composeRequest(CertValidationRequest const &vcert)
{
body.clear();
body += Ssl::CertValidationMsg::param_host + "=" + vcert.domainName;
- if (vcert.errors) {
- body += "\n" + Ssl::CertValidationMsg::param_error + "=";
- bool comma = false;
- for (const Ssl::Errors *err = vcert.errors; err; err = err->next ) {
- if (comma)
- body += ",";
- body += GetErrorName(err->element);
- comma = true;
- }
- }
- STACK_OF(X509) *peerCerts = SSL_get_peer_cert_chain(vcert.ssl);
+ if (const char *sslVersion = SSL_get_version(vcert.ssl.get()))
+ body += "\n" + Ssl::CertValidationMsg::param_proto_version + "=" + sslVersion;
+
+ if (const char *cipherName = SSL_CIPHER_get_name(SSL_get_current_cipher(vcert.ssl.get())))
+ body += "\n" + Ssl::CertValidationMsg::param_cipher + "=" + cipherName;
+
+ STACK_OF(X509) *peerCerts = PeerValidationCertificatesChain(vcert.ssl);
if (peerCerts) {
- body +="\n";
Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem()));
for (int i = 0; i < sk_X509_num(peerCerts); ++i) {
X509 *cert = sk_X509_value(peerCerts, i);
PEM_write_bio_X509(bio.get(), cert);
- body = body + "cert_" + xitoa(i) + "=";
+ body = body + "\n" + param_cert + xitoa(i) + "=";
char *ptr;
long len = BIO_get_mem_data(bio.get(), &ptr);
- body.append(ptr, len);
- // Normally openssl toolkit terminates Certificate with a '\n'.
- if (ptr[len-1] != '\n')
- body +="\n";
+ body.append(ptr, (ptr[len-1] == '\n' ? len - 1 : len));
if (!BIO_reset(bio.get())) {
// print an error?
}
}
}
+
+ if (vcert.errors) {
+ int i = 0;
+ for (const Security::CertErrors *err = vcert.errors; err; err = err->next, ++i) {
+ body +="\n";
+ body = body + param_error_name + xitoa(i) + "=" + GetErrorName(err->element.code) + "\n";
+ int errorCertPos = -1;
+ if (err->element.cert.get())
+ errorCertPos = sk_X509_find(peerCerts, err->element.cert.get());
+ if (errorCertPos < 0) {
+ // assert this error ?
+ debugs(83, 4, "WARNING: wrong cert in cert validator request");
+ }
+ body += param_error_cert + xitoa(i) + "=";
+ body += param_cert + xitoa((errorCertPos >= 0 ? errorCertPos : 0));
+ }
+ }
}
static int
const char *e = label + len -1;
while (e != label && xisdigit(*e)) --e;
if (e != label) ++e;
- return strtol(e, 0 , 10);
+ return strtol(e, nullptr, 10);
}
bool
-Ssl::CertValidationMsg::parseResponse(CertValidationResponse &resp, STACK_OF(X509) *peerCerts, std::string &error)
+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;
+ const STACK_OF(X509) *peerCerts = PeerValidationCertificatesChain(resp.ssl);
+
const char *param = body.c_str();
while (*param) {
while (xisspace(*param)) param++;
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);
- X509_Pointer 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;
+ if (b == nullptr) {
+ 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) {
//if certId is not correct sk_X509_value returns NULL
currentItem.setCert(sk_X509_value(peerCerts, certId));
}
+ } else if (param_len > param_error_depth.length() &&
+ strncmp(param, param_error_depth.c_str(), param_error_depth.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 +1;
+ param = value + value_len;
}
/*Run through parsed errors to check for errors*/
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 *
if (ci->name.compare(name) == 0)
return ci->cert.get();
}
- return NULL;
+ return nullptr;
+}
+
+uint64_t
+Ssl::CertValidationResponse::MemoryUsedByResponse(const CertValidationResponse::Pointer &)
+{
+ // XXX: This math does not account for most of the response size!
+ return sizeof(CertValidationResponse);
}
Ssl::CertValidationResponse::RecvdError &
return errors.back();
}
-Ssl::CertValidationResponse::RecvdError::RecvdError(const RecvdError &old)
-{
- id = old.id;
- error_no = old.error_no;
- error_reason = old.error_reason;
- setCert(old.cert.get());
-}
-
-Ssl::CertValidationResponse::RecvdError & Ssl::CertValidationResponse::RecvdError::operator = (const RecvdError &old)
-{
- id = old.id;
- error_no = old.error_no;
- error_reason = old.error_reason;
- setCert(old.cert.get());
- return *this;
-}
-
void
Ssl::CertValidationResponse::RecvdError::setCert(X509 *aCert)
{
cert.resetAndLock(aCert);
}
-Ssl::CertValidationMsg::CertItem::CertItem(const CertItem &old)
-{
- name = old.name;
- setCert(old.cert.get());
-}
-
-Ssl::CertValidationMsg::CertItem & Ssl::CertValidationMsg::CertItem::operator = (const CertItem &old)
-{
- name = old.name;
- setCert(old.cert.get());
- return *this;
-}
-
void
Ssl::CertValidationMsg::CertItem::setCert(X509 *aCert)
{
const std::string Ssl::CertValidationMsg::code_cert_validate("cert_validate");
const std::string Ssl::CertValidationMsg::param_domain("domain");
-const std::string Ssl::CertValidationMsg::param_error("errors");
const std::string Ssl::CertValidationMsg::param_cert("cert_");
const std::string Ssl::CertValidationMsg::param_error_name("error_name_");
const std::string Ssl::CertValidationMsg::param_error_reason("error_reason_");
const std::string Ssl::CertValidationMsg::param_error_cert("error_cert_");
+const std::string Ssl::CertValidationMsg::param_error_depth("error_depth_");
+const std::string Ssl::CertValidationMsg::param_proto_version("proto_version");
+const std::string Ssl::CertValidationMsg::param_cipher("cipher");