SSL_CTX * dynCtx = ssl_ctx_cache.find(host);
if (dynCtx) {
debugs(33, 5, HERE << "SSL certificate for " << host << " have found in cache");
- if (Ssl::verifySslCertificateDate(dynCtx)) {
+ if (Ssl::verifySslCertificate(dynCtx, bumpServerCert.get())) {
debugs(33, 5, HERE << "Cached SSL certificate for " << host << " is valid");
getSslContextDone(dynCtx);
return;
SSL_set_ex_data(ssl, ssl_ex_index_cert_error_check, check);
}
+ // store peeked cert to check SQUID_X509_V_ERR_CERT_CHANGE
+ X509 *peeked_cert;
+ if (request->clientConnectionManager.valid() && (peeked_cert = request->clientConnectionManager->getBumpServerCert())) {
+ CRYPTO_add(&(peeked_cert->references),1,CRYPTO_LOCK_X509);
+ SSL_set_ex_data(ssl, ssl_ex_index_ssl_peeked_cert, peeked_cert);
+ }
+
fd_table[fd].ssl = ssl;
fd_table[fd].read_method = &ssl_read_method;
fd_table[fd].write_method = &ssl_write_method;
extern int ssl_ctx_ex_index_dont_verify_domain; /* -1 */
extern int ssl_ex_index_cert_error_check; /* -1 */
extern int ssl_ex_index_ssl_error_detail; /* -1 */
+ extern int ssl_ex_index_ssl_peeked_cert; /* -1 */
extern const char *external_acl_message; /* NULL */
extern int opt_send_signal; /* -1 */
SslErrors TheSslErrors;
static SslErrorEntry TheSslErrorArray[] = {
+ {SQUID_X509_V_ERR_CERT_CHANGE,
+ "SQUID_X509_V_ERR_CERT_CHANGE"},
{SQUID_ERR_SSL_HANDSHAKE,
"SQUID_ERR_SSL_HANDSHAKE"},
{SQUID_X509_V_ERR_DOMAIN_MISMATCH,
return (X509_cmp_current_time(&tm) > 0);
}
+
+/// Print the time represented by a ASN1_TIME struct to a string using GeneralizedTime format
+static bool asn1timeToGeneralizedTimeStr(ASN1_TIME *aTime, char *buf, int bufLen)
+{
+ // ASN1_Time holds time to UTCTime or GeneralizedTime form.
+ // UTCTime has the form YYMMDDHHMMSS[Z | [+|-]offset]
+ // GeneralizedTime has the form YYYYMMDDHHMMSS[Z | [+|-] offset]
+
+ // length should have space for data plus 2 extra bytes for the two extra year fields
+ // plus the '\0' char.
+ if ((aTime->length + 3) > bufLen)
+ return false;
+
+ char *str;
+ if (aTime->type == V_ASN1_UTCTIME) {
+ if (aTime->data[0] > '5') { // RFC 2459, section 4.1.2.5.1
+ buf[0] = '1';
+ buf[1] = '9';
+ } else {
+ buf[0] = '2';
+ buf[1] = '0';
+ }
+ str = buf +2;
+ }
+ else // if (aTime->type == V_ASN1_GENERALIZEDTIME)
+ str = buf;
+
+ memcpy(str, aTime->data, aTime->length);
+ str[aTime->length] = '\0';
+ return true;
+}
+
+static int asn1time_cmp(ASN1_TIME *asnTime1, ASN1_TIME *asnTime2)
+{
+ char strTime1[64], strTime2[64];
+ if (!asn1timeToGeneralizedTimeStr(asnTime1, strTime1, sizeof(strTime1)))
+ return -1;
+ if (!asn1timeToGeneralizedTimeStr(asnTime2, strTime2, sizeof(strTime2)))
+ return -1;
+
+ return strcmp(strTime1, strTime2);
+}
+
+bool Ssl::ssl_match_certificates(X509 *cert1, X509 *cert2)
+{
+ assert(cert1 && cert2);
+ X509_NAME *cert1_name = X509_get_subject_name(cert1);
+ X509_NAME *cert2_name = X509_get_subject_name(cert2);
+ if (X509_NAME_cmp(cert1_name, cert2_name) != 0)
+ return false;
+
+ ASN1_TIME *aTime = X509_get_notBefore(cert1);
+ ASN1_TIME *bTime = X509_get_notBefore(cert2);
+ if (asn1time_cmp(aTime, bTime) != 0)
+ return false;
+
+ aTime = X509_get_notAfter(cert1);
+ bTime = X509_get_notAfter(cert2);
+ if (asn1time_cmp(aTime, bTime) != 0)
+ return false;
+
+ char *alStr1;
+ int alLen;
+ alStr1 = (char *)X509_alias_get0(cert1, &alLen);
+ char *alStr2 = (char *)X509_alias_get0(cert2, &alLen);
+ if ((!alStr1 && alStr2) || (alStr1 && !alStr2) ||
+ (alStr1 && alStr2 && strcmp(alStr1, alStr2)) != 0)
+ return false;
+
+ // Compare subjectAltName extension
+ STACK_OF(GENERAL_NAME) * cert1_altnames;
+ cert1_altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(cert1, NID_subject_alt_name, NULL, NULL);
+ STACK_OF(GENERAL_NAME) * cert2_altnames;
+ cert2_altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(cert2, NID_subject_alt_name, NULL, NULL);
+ bool match = true;
+ if (cert1_altnames) {
+ int numalts = sk_GENERAL_NAME_num(cert1_altnames);
+ for (int i = 0; match && i < numalts; i++) {
+ const GENERAL_NAME *aName = sk_GENERAL_NAME_value(cert1_altnames, i);
+ match = sk_GENERAL_NAME_find(cert2_altnames, aName);
+ }
+ }
+ else if (cert2_altnames)
+ match = false;
+
+ sk_GENERAL_NAME_pop_free(cert1_altnames, GENERAL_NAME_free);
+ sk_GENERAL_NAME_pop_free(cert2_altnames, GENERAL_NAME_free);
+
+ return match;
+}
*/
bool sslDateIsInTheFuture(char const * date);
+/**
+ \ingroup SslCrtdSslAPI
+ * Check if the major (mimicked) fields of the two certificates matches
+ \return true if the certificates matches false otherwise.
+*/
+bool ssl_match_certificates(X509 *peer_cert, X509 *peeked_cert);
} // namespace Ssl
#endif // SQUID_SSL_GADGETS_H
db.find(cert_subject, cert, pkey);
+ if (cert.get() && certToMimic.get()) {
+ if (!Ssl::ssl_match_certificates(cert.get(), certToMimic.get())) {
+ // The certificate changed (renewed or other reason).
+ // Generete a new one with the updated fields.
+ cert.reset(NULL);
+ pkey.reset(NULL);
+ }
+ }
+
if (!cert || !pkey) {
Ssl::X509_Pointer certToSign;
Ssl::EVP_PKEY_Pointer pkeyToSign;
const char *server = (const char *)SSL_get_ex_data(ssl, ssl_ex_index_server);
void *dont_verify_domain = SSL_CTX_get_ex_data(sslctx, ssl_ctx_ex_index_dont_verify_domain);
ACLChecklist *check = (ACLChecklist*)SSL_get_ex_data(ssl, ssl_ex_index_cert_error_check);
+ X509 *peeked_cert = (X509 *)SSL_get_ex_data(ssl, ssl_ex_index_ssl_peeked_cert);
X509 *peer_cert = ctx->cert;
X509_NAME_oneline(X509_get_subject_name(peer_cert), buffer,
}
}
+ if (ok && peeked_cert) {
+ /*Check if the already peeked certificate match the new one*/
+ if (X509_cmp(peer_cert, peeked_cert) != 0) {
+ debugs(83, 2, "SQUID_X509_V_ERR_CERT_CHANGE: Certificate " << buffer << " does not match peeked certificate");
+ ok = 0;
+ error_no = SQUID_X509_V_ERR_CERT_CHANGE;
+ }
+ }
+
if (!ok) {
if (const char *err_descr = Ssl::GetErrorDescr(error_no))
debugs(83, 5, err_descr << ": " << buffer);
delete errDetail;
}
+// "free" function for X509 certificates
+static void
+ssl_free_X509(void *, void *ptr, CRYPTO_EX_DATA *,
+ int, long, void *)
+{
+ X509 *cert = static_cast <X509 *>(ptr);
+ X509_free(cert);
+}
+
/// \ingroup ServerProtocolSSLInternal
static void
ssl_initialize(void)
ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL);
ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist);
ssl_ex_index_ssl_error_detail = SSL_get_ex_new_index(0, (void *) "ssl_error_detail", NULL, NULL, &ssl_free_ErrorDetail);
+ ssl_ex_index_ssl_peeked_cert = SSL_get_ex_new_index(0, (void *) "ssl_peeked_cert", NULL, NULL, &ssl_free_X509);
}
/// \ingroup ServerProtocolSSLInternal
return createSSLContext(cert, pkey);
}
-bool Ssl::verifySslCertificateDate(SSL_CTX * sslContext)
+bool Ssl::verifySslCertificate(SSL_CTX * sslContext, X509 *checkCert)
{
// Temporary ssl for getting X509 certificate from SSL_CTX.
Ssl::SSL_Pointer ssl(SSL_new(sslContext));
ASN1_TIME * time_notBefore = X509_get_notBefore(cert);
ASN1_TIME * time_notAfter = X509_get_notAfter(cert);
bool ret = (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0);
- return ret;
+ if (!ret)
+ return false;
+
+ if (checkCert)
+ return ssl_match_certificates(cert, checkCert);
+
+ return true;
}
bool
*/
// Custom SSL errors; assumes all official errors are positive
+#define SQUID_X509_V_ERR_CERT_CHANGE -3
#define SQUID_ERR_SSL_HANDSHAKE -2
#define SQUID_X509_V_ERR_DOMAIN_MISMATCH -1
// All SSL errors range: from smallest (negative) custom to largest SSL error
-#define SQUID_SSL_ERROR_MIN SQUID_ERR_SSL_HANDSHAKE
+#define SQUID_SSL_ERROR_MIN SQUID_X509_V_ERR_CERT_CHANGE
#define SQUID_SSL_ERROR_MAX INT_MAX
namespace Ssl
/**
\ingroup ServerProtocolSSLAPI
- * Check date of certificate signature. If there is out of date error fucntion
- * returns false, true otherwise.
+ * Check if the certificate of the given context is still valid
+ \param sslContext The context to check
+ \param checkCert Also check if the context certificate matches this certificate
+ \return true if the contexts certificate is valid, false otherwise
*/
-bool verifySslCertificateDate(SSL_CTX * sslContext);
+bool verifySslCertificate(SSL_CTX * sslContext, X509 *checkCert = NULL);
/**
\ingroup ServerProtocolSSLAPI