TLS/SSL Options does not apply to the dynamically generated certificates The TLS/SSL options configured with http_port configuration parameter does not used to generate SSL_CTX context objects used to establish SSL connections. This is means that certificate based authentication, or SSL version selection and other SSL/TLS http_port options does not work for ssl-bumped connection. This patch fixes this problem. This is a Measurement Factory project === modified file 'src/anyp/PortCfg.cc' --- src/anyp/PortCfg.cc 2012-05-14 10:37:40 +0000 +++ src/anyp/PortCfg.cc 2012-07-25 15:15:30 +0000 @@ -30,41 +30,41 @@ } safe_free(name); safe_free(defaultsite); safe_free(protocol); #if USE_SSL safe_free(cert); safe_free(key); safe_free(options); safe_free(cipher); safe_free(cafile); safe_free(capath); safe_free(dhfile); safe_free(sslflags); safe_free(sslContextSessionId); #endif } AnyP::PortCfg * -AnyP::PortCfg::clone() const + AnyP::PortCfg::clone() const { AnyP::PortCfg *b = new AnyP::PortCfg(protocol); b->s = s; if (name) b->name = xstrdup(name); if (defaultsite) b->defaultsite = xstrdup(defaultsite); b->intercepted = intercepted; b->spoof_client_ip = spoof_client_ip; b->accel = accel; b->allow_direct = allow_direct; b->vhost = vhost; b->sslBump = sslBump; b->vport = vport; b->connection_auth_disabled = connection_auth_disabled; b->disable_pmtu_discovery = disable_pmtu_discovery; memcpy( &(b->tcp_keepalive), &(tcp_keepalive), sizeof(tcp_keepalive)); @@ -79,55 +79,70 @@ char *cipher; char *options; char *clientca; char *cafile; char *capath; char *crlfile; char *dhfile; char *sslflags; char *sslContextSessionId; SSL_CTX *sslContext; #endif #endif /*0*/ return b; } #if USE_SSL void AnyP::PortCfg::configureSslServerContext() { - staticSslContext.reset( - sslCreateServerContext(cert, key, - version, cipher, options, sslflags, clientca, - cafile, capath, crlfile, dhfile, - sslContextSessionId)); - - if (!staticSslContext) { - char buf[128]; - fatalf("%s_port %s initialization error", protocol, s.ToURL(buf, sizeof(buf))); - } - - if (!sslBump) - return; - if (cert) Ssl::readCertChainAndPrivateKeyFromFiles(signingCert, signPkey, certsToChain, cert, key); if (!signingCert) { char buf[128]; fatalf("No valid signing SSL certificate configured for %s_port %s", protocol, s.ToURL(buf, sizeof(buf))); } if (!signPkey) debugs(3, DBG_IMPORTANT, "No SSL private key configured for " << protocol << "_port " << s); Ssl::generateUntrustedCert(untrustedSigningCert, untrustedSignPkey, signingCert, signPkey); if (!untrustedSigningCert) { char buf[128]; fatalf("Unable to generate signing SSL certificate for untrusted sites for %s_port %s", protocol, s.ToURL(buf, sizeof(buf))); } + + if (crlfile) + clientVerifyCrls.reset(Ssl::loadCrl(crlfile, sslContextFlags)); + + if (clientca) { + clientCA.reset(SSL_load_client_CA_file(clientca)); + if(clientCA.get() == NULL) { + fatalf("Unable to read client CAs! from %s", clientca); + } + } + + contextMethod = Ssl::contextMethod(version); + if (!contextMethod) + fatalf("Unable to compute context method to use"); + + if (dhfile) + dhParams.reset(Ssl::readDHParams(dhfile)); + + if (sslflags) + sslContextFlags = Ssl::parse_flags(sslflags); + + sslOptions = Ssl::parse_options(options); + + staticSslContext.reset(sslCreateServerContext(*this)); + + if (!staticSslContext) { + char buf[128]; + fatalf("%s_port %s initialization error", protocol, s.ToURL(buf, sizeof(buf))); + } } #endif === modified file 'src/anyp/PortCfg.h' --- src/anyp/PortCfg.h 2012-06-19 21:51:49 +0000 +++ src/anyp/PortCfg.h 2012-07-25 09:52:36 +0000 @@ -59,35 +59,42 @@ 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 Ssl::X509_Pointer untrustedSigningCert; ///< x509 certificate for signing untrusted generated certificates Ssl::EVP_PKEY_Pointer untrustedSignPkey; ///< private key for signing untrusted generated certificates + + Ssl::X509_CRL_STACK_Pointer clientVerifyCrls; ///< additional CRL lists to use when verifying the client certificate + Ssl::X509_NAME_STACK_Pointer clientCA; ///< + Ssl::DH_Pointer dhParams; ///< + Ssl::ContextMethod contextMethod; + long sslContextFlags; + long sslOptions; #endif CBDATA_CLASS2(PortCfg); // namespaced }; } // namespace AnyP // Max number of TCP listening ports #define MAXTCPLISTENPORTS 128 // TODO: kill this global array. Need to check performance of array vs list though. extern int NHttpSockets; extern int HttpSockets[MAXTCPLISTENPORTS]; #endif /* SQUID_ANYP_PORTCFG_H */ === modified file 'src/client_side.cc' --- src/client_side.cc 2012-08-06 17:21:57 +0000 +++ src/client_side.cc 2012-08-09 12:48:18 +0000 @@ -3671,41 +3671,41 @@ 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, DBG_IMPORTANT, 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 " << sslConnectHostOrIp << " is incorrect"); } else { if (reply_message.getCode() != "OK") { debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " cannot be generated. ssl_crtd response: " << reply_message.getBody()); } else { debugs(33, 5, HERE << "Certificate for " << sslConnectHostOrIp << " was successfully recieved from ssl_crtd"); - SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str()); + SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port); getSslContextDone(ctx, true); return; } } } getSslContextDone(NULL); } void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties) { certProperties.commonName = sslCommonName.defined() ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf(); // fake certificate adaptation requires bump-server-first mode if (!sslServerBump) { assert(port->signingCert.get()); certProperties.signWithX509.resetAndLock(port->signingCert.get()); if (port->signPkey.get()) certProperties.signWithPkey.resetAndLock(port->signPkey.get()); certProperties.signAlgorithm = Ssl::algSignTrusted; return; @@ -3816,41 +3816,41 @@ #if USE_SSL_CRTD try { debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName << " using ssl_crtd."); Ssl::CrtdMessage request_message; request_message.setCode(Ssl::CrtdMessage::code_new_certificate); request_message.composeRequest(certProperties); debugs(33, 5, HERE << "SSL crtd request: " << request_message.compose().c_str()); Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); return; } catch (const std::exception &e) { debugs(33, DBG_IMPORTANT, "ERROR: Failed to compose ssl_crtd " << "request for " << certProperties.commonName << " certificate: " << e.what() << "; will now block to " << "generate that certificate."); // fall through to do blocking in-process generation. } #endif // USE_SSL_CRTD debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); - dynCtx = Ssl::generateSslContext(certProperties); + dynCtx = Ssl::generateSslContext(certProperties, *port); getSslContextDone(dynCtx, true); return; } getSslContextDone(NULL); } void ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) { // Try to add generated ssl context to storage. if (port->generateHostCertificates && isNew) { if (signAlgorithm == Ssl::algSignTrusted) Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); //else it is self-signed or untrusted do not attrach any certificate Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); if (sslContext) { if (!ssl_ctx_cache.add(sslBumpCertKey.termedBuf(), sslContext)) { === modified file 'src/ssl/gadgets.h' --- src/ssl/gadgets.h 2012-07-27 23:02:09 +0000 +++ src/ssl/gadgets.h 2012-08-09 12:48:18 +0000 @@ -9,109 +9,131 @@ #include "ssl/crtd_message.h" #if HAVE_OPENSSL_SSL_H #include #endif #if HAVE_OPENSSL_TXT_DB_H #include #endif #if HAVE_STRING #include #endif namespace Ssl { /** \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. */ +#if OPENSSL_VERSION_NUMBER < 0x00909000L +typedef SSL_METHOD * ContextMethod; +#else +typedef const SSL_METHOD * ContextMethod; +#endif + /** \ingroup SslCrtdSslAPI * Add SSL locking (a.k.a. reference counting) to TidyPointer */ template class LockingPointer: public TidyPointer { public: typedef TidyPointer Parent; LockingPointer(T *t = NULL): Parent(t) { } void resetAndLock(T *t) { if (t != this->get()) { this->reset(t); if (t) CRYPTO_add(&t->references, 1, lock); } } }; // 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); \ } +// Macro to be used to define the C++ wrapper function of a sk_*_pop_free +// openssl family functions. The C++ function suffixed with the _free_wrapper +// extension +#define sk_free_wrapper(sk_object, argument, freefunction) \ + extern "C++" inline void sk_object ## _free_wrapper(argument a) { \ + sk_object ## _pop_free(a, freefunction); \ + } + /** \ingroup SslCrtdSslAPI * TidyPointer typedefs for common SSL objects */ CtoCpp1(X509_free, X509 *) typedef LockingPointer X509_Pointer; -CtoCpp1(sk_X509_free, STACK_OF(X509) *) -typedef TidyPointer X509_STACK_Pointer; +sk_free_wrapper(sk_X509, STACK_OF(X509) *, X509_free) +typedef TidyPointer X509_STACK_Pointer; CtoCpp1(EVP_PKEY_free, EVP_PKEY *) typedef LockingPointer 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; CtoCpp1(X509_REQ_free, X509_REQ *) typedef TidyPointer X509_REQ_Pointer; CtoCpp1(SSL_CTX_free, SSL_CTX *) typedef TidyPointer SSL_CTX_Pointer; CtoCpp1(SSL_free, SSL *) typedef TidyPointer SSL_Pointer; +CtoCpp1(DH_free, DH *); +typedef TidyPointer DH_Pointer; + +sk_free_wrapper(sk_X509_CRL, STACK_OF(X509_CRL) *, X509_CRL_free) +typedef TidyPointer X509_CRL_STACK_Pointer; + +sk_free_wrapper(sk_X509_NAME, STACK_OF(X509_NAME) *, X509_NAME_free) +typedef TidyPointer X509_NAME_STACK_Pointer; /** \ingroup SslCrtdSslAPI * Create 1024 bits rsa key. */ EVP_PKEY * createSslPrivateKey(); /** \ingroup SslCrtdSslAPI * Write private key and SSL certificate to memory. */ bool writeCertAndPrivateKeyToMemory(X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey, std::string & bufferToWrite); /** \ingroup SslCrtdSslAPI * Append SSL certificate to bufferToWrite. */ bool appendCertToMemory(X509_Pointer const & cert, std::string & bufferToWrite); /** === modified file 'src/ssl/support.cc' --- src/ssl/support.cc 2012-08-06 17:21:57 +0000 +++ src/ssl/support.cc 2012-08-09 12:48:18 +0000 @@ -25,40 +25,41 @@ * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA. * */ #include "squid-old.h" /* MS Visual Studio Projects are monolithic, so we need the following * #if to exclude the SSL code from compile process when not needed. */ #if USE_SSL #include "fde.h" #include "acl/FilledChecklist.h" +#include "anyp/PortCfg.h" #include "ssl/ErrorDetail.h" #include "ssl/support.h" #include "ssl/gadgets.h" const char *Ssl::BumpModeStr[] = { "none", "client-first", "server-first", NULL }; /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals \ingroup ServerProtocolSSLAPI */ /// \ingroup ServerProtocolSSLInternal static int ssl_ask_password_cb(char *buf, int size, int rwflag, void *userdata) { @@ -427,42 +428,42 @@ #endif #if SSL_OP_NO_TLSv1_1 { "NO_TLSv1_1", SSL_OP_NO_TLSv1_1 }, #endif #if SSL_OP_NO_TLSv1_2 { "NO_TLSv1_2", SSL_OP_NO_TLSv1_2 }, #endif { "", 0 }, { NULL, 0 } }; /// \ingroup ServerProtocolSSLInternal -static long -ssl_parse_options(const char *options) +long +Ssl::parse_options(const char *options) { long op = 0; char *tmp; char *option; if (!options) goto no_options; tmp = xstrdup(options); option = strtok(tmp, ":,"); while (option) { struct ssl_option *opt = NULL, *opttmp; long value = 0; enum { MODE_ADD, MODE_REMOVE } mode; @@ -521,42 +522,42 @@ no_options: return op; } /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_NO_DEFAULT_CA (1<<0) /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_DELAYED_AUTH (1<<1) /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_DONT_VERIFY_PEER (1<<2) /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_DONT_VERIFY_DOMAIN (1<<3) /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_NO_SESSION_REUSE (1<<4) /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_VERIFY_CRL (1<<5) /// \ingroup ServerProtocolSSLInternal #define SSL_FLAG_VERIFY_CRL_ALL (1<<6) /// \ingroup ServerProtocolSSLInternal -static long -ssl_parse_flags(const char *flags) +long +Ssl::parse_flags(const char *flags) { long fl = 0; char *tmp; char *flag; if (!flags) return 0; tmp = xstrdup(flags); flag = strtok(tmp, ":,"); while (flag) { if (strcmp(flag, "NO_DEFAULT_CA") == 0) fl |= SSL_FLAG_NO_DEFAULT_CA; else if (strcmp(flag, "DELAYED_AUTH") == 0) fl |= SSL_FLAG_DELAYED_AUTH; else if (strcmp(flag, "DONT_VERIFY_PEER") == 0) fl |= SSL_FLAG_DONT_VERIFY_PEER; else if (strcmp(flag, "DONT_VERIFY_DOMAIN") == 0) @@ -681,51 +682,276 @@ int count = 0; if (!in) { debugs(83, 2, "WARNING: Failed to open CRL file '" << CRLfile << "'"); return 0; } while ((crl = PEM_read_bio_X509_CRL(in,NULL,NULL,NULL))) { if (!X509_STORE_add_crl(st, crl)) debugs(83, 2, "WARNING: Failed to add CRL from file '" << CRLfile << "'"); else ++count; X509_CRL_free(crl); } BIO_free(in); return count; } +STACK_OF(X509_CRL) * +Ssl::loadCrl(const char *CRLFile, long &flags) +{ + X509_CRL *crl; + BIO *in = BIO_new_file(CRLFile, "r"); + if (!in) { + debugs(83, 2, "WARNING: Failed to open CRL file '" << CRLFile << "'"); + return NULL; + } + + STACK_OF(X509_CRL) *CRLs = sk_X509_CRL_new_null(); + if (!CRLs) { + debugs(83, 2, "WARNING: Failed to allocate X509_CRL stack to load file '" << CRLFile << "'"); + return NULL; + } + + int count = 0; + while ((crl = PEM_read_bio_X509_CRL(in,NULL,NULL,NULL))) { + if (!sk_X509_CRL_push(CRLs, crl)) + debugs(83, 2, "WARNING: Failed to add CRL from file '" << CRLFile << "'"); + else + ++count; + } + BIO_free(in); + + if (count) + flags |= SSL_FLAG_VERIFY_CRL; + + return CRLs; +} + +DH *Ssl::readDHParams(const char *dhfile) +{ + FILE *in = fopen(dhfile, "r"); + DH *dh = NULL; + int codes; + + if (in) { + dh = PEM_read_DHparams(in, NULL, NULL, NULL); + fclose(in); + } + + if (!dh) + debugs(83, DBG_IMPORTANT, "WARNING: Failed to read DH parameters '" << dhfile << "'"); + else if (dh && DH_check(dh, &codes) == 0) { + if (codes) { + debugs(83, DBG_IMPORTANT, "WARNING: Failed to verify DH parameters '" << dhfile << "' (" << std::hex << codes << ")"); + DH_free(dh); + dh = NULL; + } + } + return dh; +} + +static bool configureSslContext(SSL_CTX *sslContext, AnyP::PortCfg &port) +{ + int ssl_error; + SSL_CTX_set_options(sslContext, port.sslOptions); + + if (port.sslContextSessionId) + SSL_CTX_set_session_id_context(sslContext, (const unsigned char *)port.sslContextSessionId, strlen(port.sslContextSessionId)); + + if (port.sslContextFlags & SSL_FLAG_NO_SESSION_REUSE) { + SSL_CTX_set_session_cache_mode(sslContext, SSL_SESS_CACHE_OFF); + } + + if (Config.SSL.unclean_shutdown) { + debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation)."); + + SSL_CTX_set_quiet_shutdown(sslContext, 1); + } + + if (port.cipher) { + debugs(83, 5, "Using chiper suite " << port.cipher << "."); + + if (!SSL_CTX_set_cipher_list(sslContext, port.cipher)) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: Failed to set SSL cipher suite '" << port.cipher << "': " << ERR_error_string(ssl_error, NULL)); + return false; + } + } + + debugs(83, 9, "Setting RSA key generation callback."); + SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb); + + debugs(83, 9, "Setting CA certificate locations."); + + const char *cafile = port.cafile ? port.cafile : port.clientca; + if ((cafile || port.capath) && !SSL_CTX_load_verify_locations(sslContext, cafile, port.capath)) { + ssl_error = ERR_get_error(); + debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting CA certificate locations: " << ERR_error_string(ssl_error, NULL)); + } + + if (!(port.sslContextFlags & SSL_FLAG_NO_DEFAULT_CA) && + !SSL_CTX_set_default_verify_paths(sslContext)) { + ssl_error = ERR_get_error(); + debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting default CA certificate location: " << ERR_error_string(ssl_error, NULL)); + } + + if (port.clientCA.get()) { + ERR_clear_error(); + SSL_CTX_set_client_CA_list(sslContext, port.clientCA.get()); + + if (port.sslContextFlags & SSL_FLAG_DELAYED_AUTH) { + debugs(83, 9, "Not requesting client certificates until acl processing requires one"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL); + } else { + debugs(83, 9, "Requiring client certificates."); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, ssl_verify_cb); + } + + if (port.clientVerifyCrls.get()) { + X509_STORE *st = SSL_CTX_get_cert_store(sslContext); + for (int i = 0; i < sk_X509_CRL_num(port.clientVerifyCrls.get()); ++i) { + X509_CRL *crl = sk_X509_CRL_value(port.clientVerifyCrls.get(), i); + if (!X509_STORE_add_crl(st, crl)) + debugs(83, 2, "WARNING: Failed to add CRL"); + } + } + +#if X509_V_FLAG_CRL_CHECK + if (port.sslContextFlags & SSL_FLAG_VERIFY_CRL_ALL) + X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); + else if (port.sslContextFlags & SSL_FLAG_VERIFY_CRL) + X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK); +#endif + + } else { + debugs(83, 9, "Not requiring any client certificates"); + SSL_CTX_set_verify(sslContext, SSL_VERIFY_NONE, NULL); + } + + + if (port.dhParams.get()) { + SSL_CTX_set_tmp_dh(sslContext, port.dhParams.get()); + } + + if (port.sslContextFlags & SSL_FLAG_DONT_VERIFY_DOMAIN) + SSL_CTX_set_ex_data(sslContext, ssl_ctx_ex_index_dont_verify_domain, (void *) -1); + + return true; +} + +SSL_CTX * +sslCreateServerContext(AnyP::PortCfg &port) +{ + int ssl_error; + SSL_CTX *sslContext; + const char *keyfile, *certfile; + certfile = port.cert; + keyfile = port.key; + + ssl_initialize(); + + if (!keyfile) + keyfile = certfile; + + if (!certfile) + certfile = keyfile; + + sslContext = SSL_CTX_new(port.contextMethod); + + if (sslContext == NULL) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate SSL context: " << ERR_error_string(ssl_error, NULL)); + return NULL; + } + + if (!SSL_CTX_use_certificate(sslContext, port.signingCert.get())) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL certificate '" << certfile << "': " << ERR_error_string(ssl_error, NULL)); + SSL_CTX_free(sslContext); + return NULL; + } + + if (!SSL_CTX_use_PrivateKey(sslContext, port.signPkey.get())) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL private key '" << keyfile << "': " << ERR_error_string(ssl_error, NULL)); + SSL_CTX_free(sslContext); + return NULL; + } + + Ssl::addChainToSslContext(sslContext, port.certsToChain.get()); + + +/* Alternate code; + debugs(83, DBG_IMPORTANT, "Using certificate in " << certfile); + + if (!SSL_CTX_use_certificate_chain_file(sslContext, certfile)) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL certificate '" << certfile << "': " << ERR_error_string(ssl_error, NULL)); + SSL_CTX_free(sslContext); + return NULL; + } + + debugs(83, DBG_IMPORTANT, "Using private key in " << keyfile); + ssl_ask_password(sslContext, keyfile); + + if (!SSL_CTX_use_PrivateKey_file(sslContext, keyfile, SSL_FILETYPE_PEM)) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: Failed to acquire SSL private key '" << keyfile << "': " << ERR_error_string(ssl_error, NULL)); + SSL_CTX_free(sslContext); + return NULL; + } + + debugs(83, 5, "Comparing private and public SSL keys."); + + if (!SSL_CTX_check_private_key(sslContext)) { + ssl_error = ERR_get_error(); + debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" << + keyfile << "': " << ERR_error_string(ssl_error, NULL)); + SSL_CTX_free(sslContext); + return NULL; + } +*/ + + if (!configureSslContext(sslContext, port)) { + debugs(83, DBG_CRITICAL, "ERROR: Configuring static SSL context"); + SSL_CTX_free(sslContext); + return NULL; + } + + return sslContext; +} + SSL_CTX * sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhfile, const char *context) { int ssl_error; #if OPENSSL_VERSION_NUMBER < 0x00909000L SSL_METHOD *method; #else const SSL_METHOD *method; #endif SSL_CTX *sslContext; - long fl = ssl_parse_flags(flags); + long fl = Ssl::parse_flags(flags); ssl_initialize(); if (!keyfile) keyfile = certfile; if (!certfile) certfile = keyfile; if (!CAfile) CAfile = clientCA; if (!certfile) { debugs(83, DBG_CRITICAL, "ERROR: No certificate file"); return NULL; } switch (version) { case 2: @@ -767,41 +993,41 @@ return NULL; #endif break; case 1: default: debugs(83, 5, "Using SSLv2/SSLv3."); method = SSLv23_server_method(); break; } sslContext = SSL_CTX_new(method); if (sslContext == NULL) { ssl_error = ERR_get_error(); debugs(83, DBG_CRITICAL, "ERROR: Failed to allocate SSL context: " << ERR_error_string(ssl_error, NULL)); return NULL; } - SSL_CTX_set_options(sslContext, ssl_parse_options(options)); + SSL_CTX_set_options(sslContext, Ssl::parse_options(options)); if (context && *context) { SSL_CTX_set_session_id_context(sslContext, (const unsigned char *)context, strlen(context)); } if (fl & SSL_FLAG_NO_SESSION_REUSE) { SSL_CTX_set_session_cache_mode(sslContext, SSL_SESS_CACHE_OFF); } if (Config.SSL.unclean_shutdown) { debugs(83, 5, "Enabling quiet SSL shutdowns (RFC violation)."); SSL_CTX_set_quiet_shutdown(sslContext, 1); } if (cipher) { debugs(83, 5, "Using chiper suite " << cipher << "."); if (!SSL_CTX_set_cipher_list(sslContext, cipher)) { ssl_error = ERR_get_error(); @@ -834,41 +1060,41 @@ if (!SSL_CTX_check_private_key(sslContext)) { ssl_error = ERR_get_error(); debugs(83, DBG_CRITICAL, "ERROR: SSL private key '" << certfile << "' does not match public key '" << keyfile << "': " << ERR_error_string(ssl_error, NULL)); SSL_CTX_free(sslContext); return NULL; } debugs(83, 9, "Setting RSA key generation callback."); SSL_CTX_set_tmp_rsa_callback(sslContext, ssl_temp_rsa_cb); debugs(83, 9, "Setting CA certificate locations."); if ((CAfile || CApath) && !SSL_CTX_load_verify_locations(sslContext, CAfile, CApath)) { ssl_error = ERR_get_error(); debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting CA certificate locations: " << ERR_error_string(ssl_error, NULL)); } if (!(fl & SSL_FLAG_NO_DEFAULT_CA) && - !SSL_CTX_set_default_verify_paths(sslContext)) { + !SSL_CTX_set_default_verify_paths(sslContext)) { ssl_error = ERR_get_error(); debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting default CA certificate location: " << ERR_error_string(ssl_error, NULL)); } if (clientCA) { STACK_OF(X509_NAME) *cert_names; debugs(83, 9, "Set client certifying authority list."); cert_names = SSL_load_client_CA_file(clientCA); if (cert_names == NULL) { debugs(83, DBG_IMPORTANT, "ERROR: loading the client CA certificates from '" << clientCA << "\': " << ERR_error_string(ERR_get_error(),NULL)); SSL_CTX_free(sslContext); return NULL; } ERR_clear_error(); SSL_CTX_set_client_CA_list(sslContext, cert_names); if (fl & SSL_FLAG_DELAYED_AUTH) { debugs(83, 9, "Not requesting client certificates until acl processing requires one"); @@ -919,41 +1145,41 @@ if (dh) SSL_CTX_set_tmp_dh(sslContext, dh); } if (fl & SSL_FLAG_DONT_VERIFY_DOMAIN) SSL_CTX_set_ex_data(sslContext, ssl_ctx_ex_index_dont_verify_domain, (void *) -1); return sslContext; } SSL_CTX * sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile) { int ssl_error; #if OPENSSL_VERSION_NUMBER < 0x00909000L SSL_METHOD *method; #else const SSL_METHOD *method; #endif SSL_CTX *sslContext; - long fl = ssl_parse_flags(flags); + long fl = Ssl::parse_flags(flags); ssl_initialize(); if (!keyfile) keyfile = certfile; if (!certfile) certfile = keyfile; switch (version) { case 2: #ifndef OPENSSL_NO_SSL2 debugs(83, 5, "Using SSLv2."); method = SSLv2_client_method(); #else debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy."); return NULL; #endif break; @@ -987,41 +1213,41 @@ return NULL; #endif break; case 1: default: debugs(83, 5, "Using SSLv2/SSLv3."); method = SSLv23_client_method(); break; } sslContext = SSL_CTX_new(method); if (sslContext == NULL) { ssl_error = ERR_get_error(); fatalf("Failed to allocate SSL context: %s\n", ERR_error_string(ssl_error, NULL)); } - SSL_CTX_set_options(sslContext, ssl_parse_options(options)); + SSL_CTX_set_options(sslContext, Ssl::parse_options(options)); if (cipher) { debugs(83, 5, "Using chiper suite " << cipher << "."); if (!SSL_CTX_set_cipher_list(sslContext, cipher)) { ssl_error = ERR_get_error(); fatalf("Failed to set SSL cipher suite '%s': %s\n", cipher, ERR_error_string(ssl_error, NULL)); } } if (certfile) { debugs(83, DBG_IMPORTANT, "Using certificate in " << certfile); if (!SSL_CTX_use_certificate_chain_file(sslContext, certfile)) { ssl_error = ERR_get_error(); fatalf("Failed to acquire SSL certificate '%s': %s\n", certfile, ERR_error_string(ssl_error, NULL)); } @@ -1058,41 +1284,41 @@ if ((CAfile || CApath) && !SSL_CTX_load_verify_locations(sslContext, CAfile, CApath)) { ssl_error = ERR_get_error(); debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting CA certificate locations: " << ERR_error_string(ssl_error, NULL)); } if (CRLfile) { ssl_load_crl(sslContext, CRLfile); fl |= SSL_FLAG_VERIFY_CRL; } #if X509_V_FLAG_CRL_CHECK if (fl & SSL_FLAG_VERIFY_CRL_ALL) X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL); else if (fl & SSL_FLAG_VERIFY_CRL) X509_STORE_set_flags(SSL_CTX_get_cert_store(sslContext), X509_V_FLAG_CRL_CHECK); #endif if (!(fl & SSL_FLAG_NO_DEFAULT_CA) && - !SSL_CTX_set_default_verify_paths(sslContext)) { + !SSL_CTX_set_default_verify_paths(sslContext)) { ssl_error = ERR_get_error(); debugs(83, DBG_IMPORTANT, "WARNING: Ignoring error setting default CA certificate location: " << ERR_error_string(ssl_error, NULL)); } return sslContext; } /// \ingroup ServerProtocolSSLInternal int ssl_read_method(int fd, char *buf, int len) { SSL *ssl = fd_table[fd].ssl; int i; #if DONT_DO_THIS if (!SSL_is_init_finished(ssl)) { errno = ENOTCONN; return -1; } @@ -1276,81 +1502,142 @@ return sslGetUserCertificatePEM(ssl); mem = BIO_new(BIO_s_mem()); for (i = 0; i < sk_X509_num(chain); ++i) { X509 *cert = sk_X509_value(chain, i); PEM_write_bio_X509(mem, cert); } len = BIO_get_mem_data(mem, &ptr); str = (char *)xmalloc(len + 1); memcpy(str, ptr, len); str[len] = '\0'; BIO_free(mem); return str; } +Ssl::ContextMethod +Ssl::contextMethod(int version) +{ + Ssl::ContextMethod method; + + switch (version) { + + case 2: +#ifndef OPENSSL_NO_SSL2 + debugs(83, 5, "Using SSLv2."); + method = SSLv2_server_method(); +#else + debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy."); + return NULL; +#endif + break; + + case 3: + debugs(83, 5, "Using SSLv3."); + method = SSLv3_server_method(); + break; + + case 4: + debugs(83, 5, "Using TLSv1."); + method = TLSv1_server_method(); + break; + + case 5: +#if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. + debugs(83, 5, "Using TLSv1.1."); + method = TLSv1_1_server_method(); +#else + debugs(83, DBG_IMPORTANT, "TLSv1.1 is not available in this Proxy."); + return NULL; +#endif + break; + + case 6: +#if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. + debugs(83, 5, "Using TLSv1.2"); + method = TLSv1_2_server_method(); +#else + debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy."); + return NULL; +#endif + break; + + case 1: + + default: + debugs(83, 5, "Using SSLv2/SSLv3."); + method = SSLv23_server_method(); + break; + } + return method; +} + /// \ingroup ServerProtocolSSLInternal /// Create SSL context and apply ssl certificate and private key to it. -static SSL_CTX * createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey) +static SSL_CTX * createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port) { - Ssl::SSL_CTX_Pointer sslContext(SSL_CTX_new(SSLv23_server_method())); + Ssl::SSL_CTX_Pointer sslContext(SSL_CTX_new(port.contextMethod)); if (!SSL_CTX_use_certificate(sslContext.get(), x509.get())) return NULL; if (!SSL_CTX_use_PrivateKey(sslContext.get(), pkey.get())) return NULL; + + if (!configureSslContext(sslContext.get(), port)) + return NULL; + return sslContext.release(); } -SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data) +SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; if (!readCertAndPrivateKeyFromMemory(cert, pkey, data)) return NULL; if (!cert || !pkey) return NULL; - return createSSLContext(cert, pkey); + return createSSLContext(cert, pkey, port); } -SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties) +SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; if (!generateSslCertificate(cert, pkey, properties)) return NULL; if (!cert) return NULL; if (!pkey) return NULL; - return createSSLContext(cert, pkey); + return createSSLContext(cert, pkey, port); } bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties) { // 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); if (!ret) return false; return certificateMatchesProperties(cert, properties); } bool Ssl::setClientSNI(SSL *ssl, const char *fqdn) { //The SSL_CTRL_SET_TLSEXT_HOSTNAME is a openssl macro which indicates @@ -1370,79 +1657,85 @@ } 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 - */ + \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 (certFilename == NULL) + certFilename = keyFilename; + + debugs(83, DBG_IMPORTANT, "Using certificate in " << 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, ssl_ask_password_cb)); cert.reset(readSslX509CertificatesChain(certFilename, chain.get())); if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) { pkey.reset(NULL); cert.reset(NULL); } } bool Ssl::generateUntrustedCert(X509_Pointer &untrustedCert, EVP_PKEY_Pointer &untrustedPkey, X509_Pointer const &cert, EVP_PKEY_Pointer const & pkey) { // Generate the self-signed certificate, using a hard-coded subject prefix Ssl::CertificateProperties certProperties; if (const char *cn = CommonHostName(cert.get())) { certProperties.commonName = "Not trusted by \""; certProperties.commonName += cn; certProperties.commonName += "\""; === modified file 'src/ssl/support.h' --- src/ssl/support.h 2012-06-19 21:51:49 +0000 +++ src/ssl/support.h 2012-07-25 15:15:54 +0000 @@ -47,52 +47,58 @@ #if HAVE_OPENSSL_ERR_H #include #endif #if HAVE_OPENSSL_ENGINE_H #include #endif /** \defgroup ServerProtocolSSLAPI Server-Side SSL API \ingroup ServerProtocol */ // 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_X509_V_ERR_CERT_CHANGE #define SQUID_SSL_ERROR_MAX INT_MAX +namespace AnyP{ + class PortCfg; +}; + namespace Ssl { /// Squid defined error code (<0), an error code returned by SSL X509 api, or SSL_ERROR_NONE typedef int ssl_error_t; typedef CbDataList Errors; } //namespace Ssl /// \ingroup ServerProtocolSSLAPI SSL_CTX *sslCreateServerContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *clientCA, const char *CAfile, const char *CApath, const char *CRLfile, const char *dhpath, const char *context); +SSL_CTX *sslCreateServerContext(AnyP::PortCfg &port); + /// \ingroup ServerProtocolSSLAPI SSL_CTX *sslCreateClientContext(const char *certfile, const char *keyfile, int version, const char *cipher, const char *options, const char *flags, const char *CAfile, const char *CApath, const char *CRLfile); /// \ingroup ServerProtocolSSLAPI int ssl_read_method(int, char *, int); /// \ingroup ServerProtocolSSLAPI int ssl_write_method(int, const char *, int); /// \ingroup ServerProtocolSSLAPI void ssl_shutdown_method(SSL *ssl); /// \ingroup ServerProtocolSSLAPI const char *sslGetUserEmail(SSL *ssl); /// \ingroup ServerProtocolSSLAPI typedef char const *SSLGETATTRIBUTE(SSL *, const char *); /// \ingroup ServerProtocolSSLAPI @@ -114,66 +120,96 @@ * Supported ssl-bump modes */ enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpEnd}; /** \ingroup ServerProtocolSSLAPI * Short names for ssl-bump modes */ extern const char *BumpModeStr[]; /** \ingroup ServerProtocolSSLAPI * Return the short name of the ssl-bump mode "bm" */ inline const char *bumpMode(int bm) { return (0 <= bm && bm < Ssl::bumpEnd) ? Ssl::BumpModeStr[bm] : NULL; } /** + \ingroup ServerProtocolSSLAPI + * Parses the SSL flags. + */ +long parse_flags(const char *flags); + +/** + \ingroup ServerProtocolSSLAPI + * Parses the SSL options. + */ +long parse_options(const char *options); + +/** + \ingroup ServerProtocolSSLAPI + * Load a CRLs list stored in a file + */ +STACK_OF(X509_CRL) *loadCrl(const char *CRLFile, long &flags); + +/** + \ingroup ServerProtocolSSLAPI + * Load DH params from file + */ +DH *readDHParams(const char *dhfile); + +/** + \ingroup ServerProtocolSSLAPI + * Compute the Ssl::ContextMethod (SSL_METHOD) from SSL version + */ +ContextMethod contextMethod(int version); + +/** \ingroup ServerProtocolSSLAPI * Generate a certificate to be used as untrusted signing certificate, based on a trusted CA */ bool generateUntrustedCert(X509_Pointer & untrustedCert, EVP_PKEY_Pointer & untrustedPkey, X509_Pointer const & cert, EVP_PKEY_Pointer const & pkey); /** \ingroup ServerProtocolSSLAPI * Decide on the kind of certificate and generate a CA- or self-signed one */ -SSL_CTX * generateSslContext(CertificateProperties const &properties); +SSL_CTX * generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port); /** \ingroup ServerProtocolSSLAPI * Check if the certificate of the given context is still valid \param sslContext The context to check \param properties Check if the context certificate matches the given properties \return true if the contexts certificate is valid, false otherwise */ bool verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties); /** \ingroup ServerProtocolSSLAPI * Read private key and certificate from memory and generate SSL context * using their. */ -SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data); +SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port); /** \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.