SslBump: Send intermediate CA SslBump code assumed that it is signing generated certificates with a root CA certificate. Root certificates are usually not sent along with the server certificates because clients must have them independently installed or built-in. Squid was not sending the signing certificate. In many environments, Squid signing certificate is intermediate (i.e., it belongs to a non-root CA). If Squid does not send that intermediate signing certificate with the generated one, the client will not be able to establish a complete chain of trust from the generated fake to the root CA certificate, leading to errors. With this change, Squid may send the signing certificate (along with the generated one) using the following rules: * If the configured signing certificate is self-signed, then just send the generated certificate alone. Note that root CA certificates are self-signed (by root CA). * Otherwise (i.e., if the configured signing certificate is an intermediate CA certificate), send both the intermediate CA and the generated fake certificate. This is a Measurement Factory project. === modified file 'src/ProtoPort.h' --- src/ProtoPort.h 2011-10-19 09:35:47 +0000 +++ src/ProtoPort.h 2011-10-19 14:55:29 +0000 @@ -45,35 +45,36 @@ http_port_list &http; char *cert; char *key; int version; char *cipher; char *options; char *clientca; char *cafile; char *capath; char *crlfile; char *dhfile; char *sslflags; char *sslContextSessionId; ///< "session id context" for staticSslContext bool generateHostCertificates; ///< dynamically make host cert for sslBump size_t dynamicCertMemCacheSize; ///< max size of generated certificates memory cache Ssl::SSL_CTX_Pointer staticSslContext; ///< for HTTPS accelerator or static sslBump Ssl::X509_Pointer signingCert; ///< x509 certificate for signing generated certificates Ssl::EVP_PKEY_Pointer signPkey; ///< private key for sighing generated certificates + Ssl::X509_STACK_Pointer certsToChain; ///< x509 certificates to send with the generated cert #endif CBDATA_CLASS2(http_port_list); }; #if USE_SSL struct https_port_list: public http_port_list { https_port_list(); }; #endif #endif /* SQUID_PROTO_PORT_H */ === modified file 'src/cache_cf.cc' --- src/cache_cf.cc 2011-10-19 09:35:47 +0000 +++ src/cache_cf.cc 2011-10-19 14:55:29 +0000 @@ -722,41 +722,41 @@ } } } { http_port_list *s; for (s = Config.Sockaddr.http; s != NULL; s = (http_port_list *) s->next) { if (!s->cert && !s->key) continue; debugs(3, 1, "Initializing http_port " << s->http.s << " SSL context"); s->staticSslContext.reset( sslCreateServerContext(s->cert, s->key, s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslContextSessionId)); - Ssl::readCertAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->cert, s->key); + Ssl::readCertChainAndPrivateKeyFromFiles(s->signingCert, s->signPkey, s->certsToChain, s->cert, s->key); } } { https_port_list *s; for (s = Config.Sockaddr.https; s != NULL; s = (https_port_list *) s->http.next) { debugs(3, 1, "Initializing https_port " << s->http.s << " SSL context"); s->staticSslContext.reset( sslCreateServerContext(s->cert, s->key, s->version, s->cipher, s->options, s->sslflags, s->clientca, s->cafile, s->capath, s->crlfile, s->dhfile, s->sslContextSessionId)); } } #endif === modified file 'src/client_side.cc' --- src/client_side.cc 2011-10-19 09:35:47 +0000 +++ src/client_side.cc 2011-10-19 14:55:29 +0000 @@ -3328,41 +3328,42 @@ ConnStateData::sslCrtdHandleReplyWrapper(void *data, char *reply) { ConnStateData * state_data = (ConnStateData *)(data); state_data->sslCrtdHandleReply(reply); } void ConnStateData::sslCrtdHandleReply(const char * reply) { if (!reply) { debugs(1, 1, HERE << "\"ssl_crtd\" helper return reply"); } else { Ssl::CrtdMessage reply_message; if (reply_message.parse(reply, strlen(reply)) != Ssl::CrtdMessage::OK) { debugs(33, 5, HERE << "Reply from ssl_crtd for " << sslHostName << " is incorrect"); } else { if (reply_message.getCode() != "ok") { debugs(33, 5, HERE << "Certificate for " << sslHostName << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); } else { debugs(33, 5, HERE << "Certificate for " << sslHostName << " was successfully recieved from ssl_crtd"); - getSslContextDone(Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str()), true); + SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str()); + getSslContextDone(ctx, true); return; } } } getSslContextDone(NULL); } bool ConnStateData::getSslContextStart() { char const * host = sslHostName.termedBuf(); if (port->generateHostCertificates && host && strcmp(host, "") != 0) { debugs(33, 5, HERE << "Finding SSL certificate for " << host << " in cache"); Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); 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)) { debugs(33, 5, HERE << "Cached SSL certificate for " << host << " is valid"); return getSslContextDone(dynCtx); @@ -3382,40 +3383,43 @@ map.insert(std::make_pair(Ssl::CrtdMessage::param_host, host)); std::string bufferToWrite; Ssl::writeCertAndPrivateKeyToMemory(port->signingCert, port->signPkey, bufferToWrite); request_message.composeBody(map, bufferToWrite); Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); return true; #else debugs(33, 5, HERE << "Generating SSL certificate for " << host); dynCtx = Ssl::generateSslContext(host, port->signingCert, port->signPkey); return getSslContextDone(dynCtx, true); #endif //USE_SSL_CRTD } return getSslContextDone(NULL); } bool ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) { // Try to add generated ssl context to storage. if (port->generateHostCertificates && isNew) { + + Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); + Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); if (sslContext && sslHostName != "") { if (!ssl_ctx_cache.add(sslHostName.termedBuf(), sslContext)) { // If it is not in storage delete after using. Else storage deleted it. fd_table[fd].dynamicSslContext = sslContext; } } else { debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslHostName); } } // If generated ssl context = NULL, try to use static ssl context. if (!sslContext) { if (!port->staticSslContext) { debugs(83, 1, "Closing SSL FD " << fd << " as lacking SSL context"); comm_close(fd); return false; } else { debugs(33, 5, HERE << "Using static ssl context."); sslContext = port->staticSslContext.get(); === modified file 'src/ssl/gadgets.cc' --- src/ssl/gadgets.cc 2011-10-19 09:35:47 +0000 +++ src/ssl/gadgets.cc 2011-10-19 14:55:29 +0000 @@ -1,26 +1,29 @@ /* * $Id$ */ #include "config.h" #include "ssl/gadgets.h" +#if HAVE_OPENSSL_X509V3_H +#include +#endif /** \ingroup ServerProtocolSSLInternal * Add CN to subject in request. */ static bool addCnToRequest(Ssl::X509_REQ_Pointer & request, char const * cn) { Ssl::X509_NAME_Pointer name(X509_REQ_get_subject_name(request.get())); if (!name) return false; // The second argument of the X509_NAME_add_entry_by_txt declared as // "char *" on some OS. Use cn_name to avoid compile warnings. static char cn_name[3] = "CN"; if (!X509_NAME_add_entry_by_txt(name.get(), cn_name, MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0)) return false; name.release(); return true; } @@ -214,45 +217,41 @@ return true; } /** \ingroup ServerProtocolSSLInternal * Read certificate from file. */ static X509 * readSslX509Certificate(char const * certFilename) { if (!certFilename) return NULL; Ssl::BIO_Pointer bio(BIO_new(BIO_s_file_internal())); if (!bio) return NULL; if (!BIO_read_filename(bio.get(), certFilename)) return NULL; X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL); return certificate; } -/** - \ingroup ServerProtocolSSLInternal - * Read private key from file. Make sure that this is not encrypted file. - */ -static EVP_PKEY * readSslPrivateKey(char const * keyFilename) +EVP_PKEY * Ssl::readSslPrivateKey(char const * keyFilename) { if (!keyFilename) return NULL; Ssl::BIO_Pointer bio(BIO_new(BIO_s_file_internal())); if (!bio) return NULL; if (!BIO_read_filename(bio.get(), keyFilename)) return NULL; EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL); return pkey; } void Ssl::readCertAndPrivateKeyFromFiles(Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, char const * certFilename, char const * keyFilename) { if (keyFilename == NULL) keyFilename = certFilename; pkey.reset(readSslPrivateKey(keyFilename)); cert.reset(readSslX509Certificate(certFilename)); if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) { pkey.reset(NULL); === modified file 'src/ssl/gadgets.h' --- src/ssl/gadgets.h 2011-10-19 09:35:47 +0000 +++ src/ssl/gadgets.h 2011-10-19 14:55:29 +0000 @@ -22,40 +22,43 @@ /** \defgroup SslCrtdSslAPI ssl_crtd SSL api. These functions must not depend on Squid runtime code such as debug() because they are used by ssl_crtd. */ // Macro to be used to define the C++ equivalent function of an extern "C" // function. The C++ function suffixed with the _cpp extension #define CtoCpp1(function, argument) \ extern "C++" inline void function ## _cpp(argument a) { \ function(a); \ } /** \ingroup SslCrtdSslAPI * TidyPointer typedefs for common SSL objects */ CtoCpp1(X509_free, X509 *) typedef TidyPointer X509_Pointer; +CtoCpp1(sk_X509_free, STACK_OF(X509) *) +typedef TidyPointer X509_STACK_Pointer; + CtoCpp1(EVP_PKEY_free, EVP_PKEY *) typedef TidyPointer EVP_PKEY_Pointer; CtoCpp1(BN_free, BIGNUM *) typedef TidyPointer BIGNUM_Pointer; CtoCpp1(BIO_free, BIO *) typedef TidyPointer BIO_Pointer; CtoCpp1(ASN1_INTEGER_free, ASN1_INTEGER *) typedef TidyPointer ASN1_INT_Pointer; CtoCpp1(TXT_DB_free, TXT_DB *) typedef TidyPointer TXT_DB_Pointer; CtoCpp1(X509_NAME_free, X509_NAME *) typedef TidyPointer X509_NAME_Pointer; CtoCpp1(RSA_free, RSA *) typedef TidyPointer RSA_Pointer; @@ -101,35 +104,41 @@ bool readCertAndPrivateKeyFromMemory(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, char const * bufferToRead); /** \ingroup SslCrtdSslAPI * Sign SSL request. * \param x509 if this param equals NULL, returning certificate will be selfsigned. * \return X509 Signed certificate. */ X509 * signRequest(X509_REQ_Pointer const & request, X509_Pointer const & x509, EVP_PKEY_Pointer const & pkey, ASN1_TIME * timeNotAfter, BIGNUM const * serial); /** \ingroup SslCrtdSslAPI * Decide on the kind of certificate and generate a CA- or self-signed one. * Return generated certificate and private key in resultX509 and resultPkey * variables. */ bool generateSslCertificateAndPrivateKey(char const *host, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const* serial); /** \ingroup SslCrtdSslAPI + * Read private key from file. Make sure that this is not encrypted file. + */ +EVP_PKEY * readSslPrivateKey(char const * keyFilename); + +/** + \ingroup SslCrtdSslAPI * Read certificate and private key from files. * \param certFilename name of file with certificate. * \param keyFilename name of file with private key. */ void readCertAndPrivateKeyFromFiles(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, char const * certFilename, char const * keyFilename); /** \ingroup SslCrtdSslAPI * Verify date. Date format it ASN1_UTCTIME. if there is out of date error, * return false. */ bool sslDateIsInTheFuture(char const * date); } // namespace Ssl #endif // SQUID_SSL_GADGETS_H === modified file 'src/ssl_support.cc' --- src/ssl_support.cc 2011-10-19 09:35:47 +0000 +++ src/ssl_support.cc 2011-10-19 15:00:00 +0000 @@ -1239,21 +1239,90 @@ if (!cert) return NULL; if (!pkey) return NULL; return createSSLContext(cert, pkey); } bool Ssl::verifySslCertificateDate(SSL_CTX * sslContext) { // Temporary ssl for getting X509 certificate from SSL_CTX. Ssl::SSL_Pointer ssl(SSL_new(sslContext)); X509 * cert = SSL_get_certificate(ssl.get()); 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; } +void Ssl::addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *chain) +{ + if (!chain) + return; + + for (int i = 0; i < sk_X509_num(chain); i++) { + X509 *cert = sk_X509_value(chain, i); + if (SSL_CTX_add_extra_chain_cert(sslContext, cert)) { + // increase the certificate lock + CRYPTO_add(&(cert->references),1,CRYPTO_LOCK_X509); + } else { + const int ssl_error = ERR_get_error(); + debugs(83, DBG_IMPORTANT, "WARNING: can not add certificate to SSL context chain: " << ERR_error_string(ssl_error, NULL)); + } + } +} + +/** + \ingroup ServerProtocolSSLInternal + * Read certificate from file. + * See also: static readSslX509Certificate function, gadgets.cc file + */ +static X509 * readSslX509CertificatesChain(char const * certFilename, STACK_OF(X509)* chain) +{ + if (!certFilename) + return NULL; + Ssl::BIO_Pointer bio(BIO_new(BIO_s_file_internal())); + if (!bio) + return NULL; + if (!BIO_read_filename(bio.get(), certFilename)) + return NULL; + X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL); + + if (certificate && chain) { + + if (X509_check_issued(certificate, certificate) == X509_V_OK) + debugs(83, 5, "Certificate is self-signed, will not be chained"); + else { + if(sk_X509_push(chain, certificate)) + CRYPTO_add(&(certificate->references), 1, CRYPTO_LOCK_X509); + else + debugs(83, DBG_IMPORTANT, "WARNING: unable to add signing certificate to cert chain"); + // and add to the chain any certificate loaded from the file + while (X509 *ca = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL)) { + if (!sk_X509_push(chain, ca)) + debugs(83, DBG_IMPORTANT, "WARNING: unable to add CA certificate to cert chain"); + } + } + } + + return certificate; +} + +void Ssl::readCertChainAndPrivateKeyFromFiles(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, X509_STACK_Pointer & chain, char const * certFilename, char const * keyFilename) +{ + if (keyFilename == NULL) + keyFilename = certFilename; + if (!chain) + chain.reset(sk_X509_new_null()); + if (!chain) + debugs(83, DBG_IMPORTANT, "WARNING: unable to allocate memory for cert chain"); + pkey.reset(readSslPrivateKey(keyFilename)); + cert.reset(readSslX509CertificatesChain(certFilename, chain.get())); + if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) { + pkey.reset(NULL); + cert.reset(NULL); + } +} + #endif /* USE_SSL */ === modified file 'src/ssl_support.h' --- src/ssl_support.h 2011-10-19 09:35:47 +0000 +++ src/ssl_support.h 2011-10-19 14:57:36 +0000 @@ -95,40 +95,55 @@ \ingroup ServerProtocolSSLAPI * Decide on the kind of certificate and generate a CA- or self-signed one */ SSL_CTX *generateSslContext(char const *host, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey); /** \ingroup ServerProtocolSSLAPI * Check date of certificate signature. If there is out of date error fucntion * returns false, true otherwise. */ bool verifySslCertificateDate(SSL_CTX * sslContext); /** \ingroup ServerProtocolSSLAPI * Read private key and certificate from memory and generate SSL context * using their. */ SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data); /** + \ingroup ServerProtocolSSLAPI + * Adds the certificates in certList to the certificate chain of the SSL context + */ +void addChainToSslContext(SSL_CTX *sslContext, STACK_OF(X509) *certList); + +/** + \ingroup ServerProtocolSSLAPI + * Read certificate, private key and any certificates which must be chained from files. + * See also: Ssl::readCertAndPrivateKeyFromFiles function, defined in gadgets.h + * \param certFilename name of file with certificate and certificates which must be chainned. + * \param keyFilename name of file with private key. + */ +void readCertChainAndPrivateKeyFromFiles(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, X509_STACK_Pointer & chain, char const * certFilename, char const * keyFilename); + +/** \ingroup ServerProtocolSSLAPI * Iterates over the X509 common and alternate names and to see if matches with given data * using the check_func. \param peer_cert The X509 cert to check \param check_data The data with which the X509 CNs compared \param check_func The function used to match X509 CNs. The CN data passed as ASN1_STRING data \return 1 if any of the certificate CN matches, 0 if none matches. */ int matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)); /** \ingroup ServerProtocolSSLAPI * Convert a given ASN1_TIME to a string form. \param tm the time in ASN1_TIME form \param buf the buffer to write the output \param len write at most len bytes \return The number of bytes written */ int asn1timeToString(ASN1_TIME *tm, char *buf, int len);