From 5107d2c4402dd49988bede872830008d3b0f77f9 Mon Sep 17 00:00:00 2001 From: Christos Tsantilas Date: Thu, 24 Aug 2017 00:13:52 +0300 Subject: [PATCH] Fix SSL certificate cache refresh and collision handling (#40) SslBump was ignoring some origin server certificate changes or differences, incorrectly using the previously cached fake certificate (mimicking now-stale properties or properties of a slightly different certificate). Also, Squid was not detecting key collisions inside certificate caches. On-disk certificate cache fixes: Use the original certificate signature instead of the certificate subject as part of the key. Using signatures reduces certificate key collisions to deliberate attacks and woefully misconfigured origins, and makes any mishandled attacks a lot less dangerous because the attacking origin server certificate cannot by trusted by a properly configured Squid and cannot be used for encryption by an attacker. We have considered using certificate digests instead of signatures. Digests would further reduce the attack surface to copies of public certificates (as if the origin server was woefully misconfigured). However, unlike the origin-supplied signatures, digests require (expensive) computation in Squid, and implemented collision handling should make any signature-based attacks unappealing. Signatures won on performance grounds. Other key components remain the same: NotValidAfter, NotValidBefore, forced common name, non-default signing algorithm, and signing hash. Store the original server certificate in the cache (together with the generated certificate) for reliable key collision detection. Upon detecting key collisions, ignore and replace the existing cache entry with a freshly computed one. This change is required to prevent an attacker from tricking Squid into hitting a cached impersonating certificate when talking to a legitimate origin. In-memory SSL context cache fixes: Use the original server certificate (in ASN.1 form) as a part of the cache key, to completely eliminate cache key collisions. Other related improvements: Make the LruMap keys template parameters. Polish Ssl::CertificateDb class member names to match Squid coding style. Rename some functions parameters to better match their meaning. Replace Ssl::CertificateProperties::dbKey() with: Ssl::OnDiskCertificateDbKey() in ssl/gadgets.cc for on-disk key generation by the ssl_crtd helper; Ssl::InRamCertificateDbKey() in ssl/support.cc for in-memory binary keys generation by the SSL context memory cache. Optimization: Added Ssl::BIO_new_SBuf(SBuf*) for OpenSSL to write directly into SBuf objects. This is a Measurement Factory project. --- acinclude/lib-checks.m4 | 2 + src/base/LruMap.h | 64 +++--- src/client_side.cc | 95 ++++----- src/client_side.h | 21 +- .../cert_generators/file/certificate_db.cc | 109 ++++++---- .../cert_generators/file/certificate_db.h | 27 ++- .../file/security_file_certgen.cc | 21 +- src/ssl/ServerBump.h | 4 + src/ssl/context_storage.h | 2 +- src/ssl/gadgets.cc | 187 ++++++++++++------ src/ssl/gadgets.h | 73 ++++--- src/ssl/helper.cc | 10 +- src/ssl/helper.h | 2 +- src/ssl/support.cc | 144 +++++++++++++- src/ssl/support.h | 16 +- src/tests/stub_client_side.cc | 2 +- src/tests/stub_libsslsquid.cc | 4 +- 17 files changed, 523 insertions(+), 260 deletions(-) diff --git a/acinclude/lib-checks.m4 b/acinclude/lib-checks.m4 index 0863b88caa..68bf33a183 100644 --- a/acinclude/lib-checks.m4 +++ b/acinclude/lib-checks.m4 @@ -72,6 +72,7 @@ AC_DEFUN([SQUID_CHECK_LIBCRYPTO_API],[ AH_TEMPLATE(HAVE_LIBCRYPTO_X509_UP_REF, "Define to 1 if the X509_up_ref() OpenSSL API function exists") AH_TEMPLATE(HAVE_LIBCRYPTO_X509_CRL_UP_REF, "Define to 1 if the X509_CRL_up_ref() OpenSSL API function exists") AH_TEMPLATE(HAVE_LIBCRYPTO_DH_UP_REF, "Define to 1 if the DH_up_ref() OpenSSL API function exists") + AH_TEMPLATE(HAVE_LIBCRYPTO_X509_GET0_SIGNATURE, "Define to 1 if the X509_get0_signature() OpenSSL API function exists") SQUID_STATE_SAVE(check_openssl_libcrypto_api) LIBS="$LIBS $SSLLIB" AC_CHECK_LIB(crypto, EVP_PKEY_get0_RSA, AC_DEFINE(HAVE_LIBCRYPTO_EVP_PKEY_GET0_RSA, 1)) @@ -85,6 +86,7 @@ AC_DEFUN([SQUID_CHECK_LIBCRYPTO_API],[ AC_CHECK_LIB(crypto, X509_up_ref, AC_DEFINE(HAVE_LIBCRYPTO_X509_UP_REF, 1)) AC_CHECK_LIB(crypto, X509_CRL_up_ref, AC_DEFINE(HAVE_LIBCRYPTO_X509_CRL_UP_REF, 1)) AC_CHECK_LIB(crypto, DH_up_ref, AC_DEFINE(HAVE_LIBCRYPTO_DH_UP_REF, 1)) + AC_CHECK_LIB(crypto, X509_get0_signature, AC_DEFINE(HAVE_LIBCRYPTO_X509_GET0_SIGNATURE, 1)) SQUID_STATE_ROLLBACK(check_openssl_libcrypto_api) ]) diff --git a/src/base/LruMap.h b/src/base/LruMap.h index 4936498251..74cd356e76 100644 --- a/src/base/LruMap.h +++ b/src/base/LruMap.h @@ -14,19 +14,19 @@ #include #include -template class LruMap +template class LruMap { public: class Entry { public: - Entry(const char *aKey, EntryValue *t): key(aKey), value(t), date(squid_curtime) {} + Entry(const Key &aKey, EntryValue *t): key(aKey), value(t), date(squid_curtime) {} ~Entry() {delete value;} private: Entry(Entry &); Entry & operator = (Entry &); public: - std::string key; ///< the key of entry + Key key; ///< the key of entry EntryValue *value; ///< A pointer to the stored value time_t date; ///< The date the entry created }; @@ -34,18 +34,18 @@ public: typedef typename std::list::iterator QueueIterator; /// key:queue_item mapping for fast lookups by key - typedef std::map Map; + typedef std::map Map; typedef typename Map::iterator MapIterator; - typedef std::pair MapPair; + typedef std::pair MapPair; LruMap(int ttl, size_t size); ~LruMap(); /// Search for an entry, and return a pointer - EntryValue *get(const char *key); + EntryValue *get(const Key &key); /// Add an entry to the map - bool add(const char *key, EntryValue *t); + bool add(const Key &key, EntryValue *t); /// Delete an entry from the map - bool del(const char *key); + bool del(const Key &key); /// (Re-)set the maximum size for this map void setMemLimit(size_t aSize); /// The available size for the map @@ -64,7 +64,7 @@ private: void trim(); void touch(const MapIterator &i); bool del(const MapIterator &i); - void findEntry(const char *key, LruMap::MapIterator &i); + void findEntry(const Key &key, LruMap::MapIterator &i); Map storage; ///< The Key/value * pairs Queue index; ///< LRU cache index @@ -73,25 +73,25 @@ private: int entries_; ///< The stored entries }; -template -LruMap::LruMap(int aTtl, size_t aSize): entries_(0) +template + LruMap::LruMap(int aTtl, size_t aSize): entries_(0) { ttl = aTtl; setMemLimit(aSize); } -template -LruMap::~LruMap() +template +LruMap::~LruMap() { for (QueueIterator i = index.begin(); i != index.end(); ++i) { delete *i; } } -template +template void -LruMap::setMemLimit(size_t aSize) +LruMap::setMemLimit(size_t aSize) { if (aSize > 0) memLimit_ = aSize; @@ -99,9 +99,9 @@ LruMap::setMemLimit(size_t aSize) memLimit_ = 0; } -template +template void -LruMap::findEntry(const char *key, LruMap::MapIterator &i) +LruMap::findEntry(const Key &key, LruMap::MapIterator &i) { i = storage.find(key); if (i == storage.end()) { @@ -121,9 +121,9 @@ LruMap::findEntry(const char *key, LruMap::MapIterator &i i = storage.end(); } -template +template EntryValue * -LruMap::get(const char *key) +LruMap::get(const Key &key) { MapIterator i; findEntry(key, i); @@ -135,9 +135,9 @@ LruMap::get(const char *key) return NULL; } -template +template bool -LruMap::add(const char *key, EntryValue *t) +LruMap::add(const Key &key, EntryValue *t) { if (ttl == 0) return false; @@ -155,9 +155,9 @@ LruMap::add(const char *key, EntryValue *t) return true; } -template +template bool -LruMap::expired(const LruMap::Entry &entry) const +LruMap::expired(const LruMap::Entry &entry) const { if (ttl < 0) return false; @@ -165,9 +165,9 @@ LruMap::expired(const LruMap::Entry &entry) const return (entry.date + ttl < squid_curtime); } -template +template bool -LruMap::del(LruMap::MapIterator const &i) +LruMap::del(LruMap::MapIterator const &i) { if (i != storage.end()) { Entry *e = *i->second; @@ -180,31 +180,31 @@ LruMap::del(LruMap::MapIterator const &i) return false; } -template +template bool -LruMap::del(const char *key) +LruMap::del(const Key &key) { MapIterator i; findEntry(key, i); return del(i); } -template +template void -LruMap::trim() +LruMap::trim() { while (size() >= memLimit()) { QueueIterator i = index.end(); --i; if (i != index.end()) { - del((*i)->key.c_str()); + del((*i)->key); } } } -template +template void -LruMap::touch(LruMap::MapIterator const &i) +LruMap::touch(LruMap::MapIterator const &i) { // this must not be done when nothing is being cached. if (ttl == 0) diff --git a/src/client_side.cc b/src/client_side.cc index d75119260a..74dd408db3 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -2838,8 +2838,10 @@ ConnStateData::sslCrtdHandleReply(const Helper::Reply &reply) Security::ContextPointer ctx(Security::GetFrom(fd_table[clientConnection->fd].ssl)); Ssl::configureUnconfiguredSslContext(ctx, signAlgorithm, *port); } else { - Security::ContextPointer ctx(Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port)); - getSslContextDone(ctx, true); + Security::ContextPointer ctx(Ssl::GenerateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port, (signAlgorithm == Ssl::algSignTrusted))); + if (ctx && !sslBumpCertKey.isEmpty()) + storeTlsContextToCache(sslBumpCertKey, ctx); + getSslContextDone(ctx); } return; } @@ -2853,9 +2855,8 @@ void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &cer { certProperties.commonName = sslCommonName_.isEmpty() ? sslConnectHostOrIp.termedBuf() : sslCommonName_.c_str(); - const bool triedToConnect = sslServerBump && sslServerBump->entry; - const bool connectedOK = triedToConnect && sslServerBump->entry->isEmpty(); - if (connectedOK) { + const bool connectedOk = sslServerBump && sslServerBump->connectedOk(); + if (connectedOk) { if (X509 *mimicCert = sslServerBump->serverCert.get()) certProperties.mimicCert.resetAndLock(mimicCert); @@ -2926,6 +2927,34 @@ void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &cer certProperties.signHash = Ssl::DefaultSignHash; } +Security::ContextPointer +ConnStateData::getTlsContextFromCache(const SBuf &cacheKey, const Ssl::CertificateProperties &certProperties) +{ + debugs(33, 5, "Finding SSL certificate for " << cacheKey << " in cache"); + Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); + if (Security::ContextPointer *ctx = ssl_ctx_cache ? ssl_ctx_cache->get(cacheKey) : nullptr) { + if (Ssl::verifySslCertificate(*ctx, certProperties)) { + debugs(33, 5, "Cached SSL certificate for " << certProperties.commonName << " is valid"); + return *ctx; + } else { + debugs(33, 5, "Cached SSL certificate for " << certProperties.commonName << " is out of date. Delete this certificate from cache"); + if (ssl_ctx_cache) + ssl_ctx_cache->del(cacheKey); + } + } + return Security::ContextPointer(nullptr); +} + +void +ConnStateData::storeTlsContextToCache(const SBuf &cacheKey, Security::ContextPointer &ctx) +{ + Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); + if (!ssl_ctx_cache || !ssl_ctx_cache->add(cacheKey, new Security::ContextPointer(ctx))) { + // If it is not in storage delete after using. Else storage deleted it. + fd_table[clientConnection->fd].dynamicTlsContext = ctx; + } +} + void ConnStateData::getSslContextStart() { @@ -2941,27 +2970,17 @@ ConnStateData::getSslContextStart() if (port->generateHostCertificates) { Ssl::CertificateProperties certProperties; buildSslCertGenerationParams(certProperties); - sslBumpCertKey = certProperties.dbKey().c_str(); - assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); // Disable caching for bumpPeekAndSplice mode if (!(sslServerBump && (sslServerBump->act.step1 == Ssl::bumpPeek || sslServerBump->act.step1 == Ssl::bumpStare))) { - debugs(33, 5, "Finding SSL certificate for " << sslBumpCertKey << " in cache"); - Ssl::LocalContextStorage * ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); - Security::ContextPointer *cachedCtx = ssl_ctx_cache ? ssl_ctx_cache->get(sslBumpCertKey.termedBuf()) : nullptr; - if (cachedCtx) { - debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " found in cache"); - if (Ssl::verifySslCertificate(*cachedCtx, certProperties)) { - debugs(33, 5, "Cached SSL certificate for " << sslBumpCertKey << " is valid"); - getSslContextDone(*cachedCtx); - return; - } else { - debugs(33, 5, "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); - if (ssl_ctx_cache) - ssl_ctx_cache->del(sslBumpCertKey.termedBuf()); - } - } else { - debugs(33, 5, "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); + sslBumpCertKey.clear(); + Ssl::InRamCertificateDbKey(certProperties, sslBumpCertKey); + assert(!sslBumpCertKey.isEmpty()); + + Security::ContextPointer ctx(getTlsContextFromCache(sslBumpCertKey, certProperties)); + if (ctx) { + getSslContextDone(ctx); + return; } } @@ -2993,8 +3012,10 @@ ConnStateData::getSslContextStart() Security::ContextPointer ctx(Security::GetFrom(fd_table[clientConnection->fd].ssl)); Ssl::configureUnconfiguredSslContext(ctx, certProperties.signAlgorithm, *port); } else { - Security::ContextPointer dynCtx(Ssl::generateSslContext(certProperties, *port)); - getSslContextDone(dynCtx, true); + Security::ContextPointer dynCtx(Ssl::GenerateSslContext(certProperties, *port, (signAlgorithm == Ssl::algSignTrusted))); + if (dynCtx && !sslBumpCertKey.isEmpty()) + storeTlsContextToCache(sslBumpCertKey, dynCtx); + getSslContextDone(dynCtx); } return; } @@ -3004,28 +3025,10 @@ ConnStateData::getSslContextStart() } void -ConnStateData::getSslContextDone(Security::ContextPointer &ctx, bool isNew) +ConnStateData::getSslContextDone(Security::ContextPointer &ctx) { - // Try to add generated ssl context to storage. - if (port->generateHostCertificates && isNew) { - - if (ctx && (signAlgorithm == Ssl::algSignTrusted)) { - Ssl::chainCertificatesToSSLContext(ctx, *port); - } else if (signAlgorithm == Ssl::algSignTrusted) { - debugs(33, DBG_IMPORTANT, "WARNING: can not add signing certificate to SSL context chain because SSL context chain is invalid!"); - } - //else it is self-signed or untrusted do not attrach any certificate - - Ssl::LocalContextStorage *ssl_ctx_cache = Ssl::TheGlobalContextStorage.getLocalStorage(port->s); - assert(sslBumpCertKey.size() > 0 && sslBumpCertKey[0] != '\0'); - if (ctx) { - if (!ssl_ctx_cache || !ssl_ctx_cache->add(sslBumpCertKey.termedBuf(), new Security::ContextPointer(ctx))) { - // If it is not in storage delete after using. Else storage deleted it. - fd_table[clientConnection->fd].dynamicTlsContext = ctx; - } - } else { - debugs(33, 2, HERE << "Failed to generate SSL cert for " << sslConnectHostOrIp); - } + if (port->generateHostCertificates && !ctx) { + debugs(33, 2, "Failed to generate TLS cotnext for " << sslConnectHostOrIp); } // If generated ssl context = NULL, try to use static ssl context. diff --git a/src/client_side.h b/src/client_side.h index ea5d0c0c9a..bdf135b632 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -238,12 +238,10 @@ public: /// Start to create dynamic Security::ContextPointer for host or uses static port SSL context. void getSslContextStart(); - /** - * Done create dynamic ssl certificate. - * - * \param[in] isNew if generated certificate is new, so we need to add this certificate to storage. - */ - void getSslContextDone(Security::ContextPointer &, bool isNew = false); + + /// finish configuring the newly created SSL context" + void getSslContextDone(Security::ContextPointer &); + /// Callback function. It is called when squid receive message from ssl_crtd. static void sslCrtdHandleReplyWrapper(void *data, const Helper::Reply &reply); /// Proccess response from ssl_crtd. @@ -373,6 +371,15 @@ private: bool parseProxy2p0(); bool proxyProtocolError(const char *reason); +#if USE_OPENSSL + /// \returns a pointer to the matching cached TLS context or nil + Security::ContextPointer getTlsContextFromCache(const SBuf &cacheKey, const Ssl::CertificateProperties &certProperties); + + /// Attempts to add a given TLS context to the cache, replacing the old + /// same-key context, if any + void storeTlsContextToCache(const SBuf &cacheKey, Security::ContextPointer &ctx); +#endif + /// whether PROXY protocol header is still expected bool needProxyProtocolHeader_; @@ -394,7 +401,7 @@ private: /// TLS client delivered SNI value. Empty string if none has been received. SBuf tlsClientSni_; - String sslBumpCertKey; ///< Key to use to store/retrieve generated certificate + SBuf sslBumpCertKey; ///< Key to use to store/retrieve generated certificate /// HTTPS server cert. fetching state for bump-ssl-server-first Ssl::ServerBump *sslServerBump; diff --git a/src/security/cert_generators/file/certificate_db.cc b/src/security/cert_generators/file/certificate_db.cc index 370020d967..09304d04df 100644 --- a/src/security/cert_generators/file/certificate_db.cc +++ b/src/security/cert_generators/file/certificate_db.cc @@ -208,7 +208,7 @@ void Ssl::CertificateDb::sq_TXT_DB_delete_row(TXT_DB *db, int idx) { Row row(rrow, cnlNumber); // row wrapper used to free the rrow - const Columns db_indexes[]= {cnlSerial, cnlName}; + const Columns db_indexes[]= {cnlSerial, cnlKey}; for (unsigned int i = 0; i < countof(db_indexes); ++i) { void *data = NULL; #if SQUID_SSLTXTDB_PSTRINGDATA @@ -238,11 +238,11 @@ int Ssl::CertificateDb::index_serial_cmp(const char **a, const char **b) { } unsigned long Ssl::CertificateDb::index_name_hash(const char **a) { - return(lh_strhash(a[Ssl::CertificateDb::cnlName])); + return(lh_strhash(a[Ssl::CertificateDb::cnlKey])); } int Ssl::CertificateDb::index_name_cmp(const char **a, const char **b) { - return(strcmp(a[Ssl::CertificateDb::cnlName], b[CertificateDb::cnlName])); + return(strcmp(a[Ssl::CertificateDb::cnlKey], b[CertificateDb::cnlKey])); } const std::string Ssl::CertificateDb::db_file("index.txt"); @@ -264,10 +264,12 @@ Ssl::CertificateDb::CertificateDb(std::string const & aDb_path, size_t aMax_db_s throw std::runtime_error("security_file_certgen is missing the required parameter. There should be -s and -M parameters together."); } -bool Ssl::CertificateDb::find(std::string const & host_name, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey) { +bool +Ssl::CertificateDb::find(std::string const &key, const Security::CertPointer &expectedOrig, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey) +{ const Locker locker(dbLock, Here); load(); - return pure_find(host_name, cert, pkey); + return pure_find(key, expectedOrig, cert, pkey); } bool Ssl::CertificateDb::purgeCert(std::string const & key) { @@ -276,19 +278,23 @@ bool Ssl::CertificateDb::purgeCert(std::string const & key) { if (!db) return false; - if (!deleteByHostname(key)) + if (!deleteByKey(key)) return false; save(); return true; } -bool Ssl::CertificateDb::addCertAndPrivateKey(Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName) { +bool +Ssl::CertificateDb::addCertAndPrivateKey(std::string const & useKey, const Security::CertPointer & cert, const Ssl::EVP_PKEY_Pointer & pkey, const Security::CertPointer &orig) { const Locker locker(dbLock, Here); load(); if (!db || !cert || !pkey) return false; + if(useKey.empty()) + return false; + // Functor to wrap xfree() for std::unique_ptr typedef HardFun CharDeleter; @@ -309,20 +315,8 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Security::CertPointer & cert, Ssl: return true; } - { - std::unique_ptr subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0)); - Security::CertPointer findCert; - Ssl::EVP_PKEY_Pointer findPkey; - if (pure_find(useName.empty() ? subject.get() : useName, findCert, findPkey)) { - // Replace with database certificate - cert = std::move(findCert); - pkey = std::move(findPkey); - return true; - } - // pure_find may fail because the entry is expired, or because the - // certs file is corrupted. Remove any entry with given hostname - deleteByHostname(useName.empty() ? subject.get() : useName); - } + // Remove any entry with given key + deleteByKey(useKey); // check db size while trying to minimize calls to size() size_t dbSize = size(); @@ -346,16 +340,11 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Security::CertPointer & cert, Ssl: dbSize = size(); // get the current database size } - row.setValue(cnlType, "V"); ASN1_UTCTIME * tm = X509_get_notAfter(cert.get()); row.setValue(cnlExp_date, std::string(reinterpret_cast(tm->data), tm->length).c_str()); - row.setValue(cnlFile, "unknown"); - if (!useName.empty()) - row.setValue(cnlName, useName.c_str()); - else { - std::unique_ptr subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0)); - row.setValue(cnlName, subject.get()); - } + std::unique_ptr subject(X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0)); + row.setValue(cnlName, subject.get()); + row.setValue(cnlKey, useKey.c_str()); if (!TXT_DB_insert(db.get(), row.getRow())) { // failed to add index (???) but we may have already modified @@ -367,7 +356,7 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Security::CertPointer & cert, Ssl: row.reset(); std::string filename(cert_full + "/" + serial_string + ".pem"); - if (!writeCertAndPrivateKeyToFile(cert, pkey, filename.c_str())) { + if (!WriteEntry(filename.c_str(), cert, pkey, orig)) { //remove row from txt_db and save sq_TXT_DB_delete(db.get(), (const char **)rrow); save(); @@ -379,7 +368,8 @@ bool Ssl::CertificateDb::addCertAndPrivateKey(Security::CertPointer & cert, Ssl: return true; } -void Ssl::CertificateDb::create(std::string const & db_path) { +void +Ssl::CertificateDb::Create(std::string const & db_path) { if (db_path == "") throw std::runtime_error("Path to db is empty"); std::string db_full(db_path + "/" + db_file); @@ -402,7 +392,8 @@ void Ssl::CertificateDb::create(std::string const & db_path) { throw std::runtime_error("Cannot open " + db_full + " to open"); } -void Ssl::CertificateDb::check(std::string const & db_path, size_t max_db_size, size_t fs_block_size) { +void +Ssl::CertificateDb::Check(std::string const & db_path, size_t max_db_size, size_t fs_block_size) { CertificateDb db(db_path, max_db_size, fs_block_size); db.load(); @@ -432,26 +423,32 @@ size_t Ssl::CertificateDb::rebuildSize() return dbSize; } -bool Ssl::CertificateDb::pure_find(std::string const & host_name, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey) { +bool +Ssl::CertificateDb::pure_find(std::string const &key, const Security::CertPointer &expectedOrig, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey) +{ if (!db) return false; Row row; - row.setValue(cnlName, host_name.c_str()); + row.setValue(cnlKey, key.c_str()); - char **rrow = TXT_DB_get_by_index(db.get(), cnlName, row.getRow()); + char **rrow = TXT_DB_get_by_index(db.get(), cnlKey, row.getRow()); if (rrow == NULL) return false; if (!sslDateIsInTheFuture(rrow[cnlExp_date])) return false; + Security::CertPointer storedOrig; // read cert and pkey from file. std::string filename(cert_full + "/" + rrow[cnlSerial] + ".pem"); - readCertAndPrivateKeyFromFiles(cert, pkey, filename.c_str(), NULL); - if (!cert || !pkey) + if (!ReadEntry(filename.c_str(), cert, pkey, storedOrig)) return false; - return true; + + if (!storedOrig && !expectedOrig) + return true; + else + return Ssl::CertificatesCmp(expectedOrig, storedOrig); } size_t Ssl::CertificateDb::size() { @@ -514,7 +511,7 @@ void Ssl::CertificateDb::load() { if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlSerial, NULL, LHASH_HASH_FN(index_serial_hash), LHASH_COMP_FN(index_serial_cmp))) corrupt = true; - if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlName, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp))) + if (!corrupt && !TXT_DB_create_index(temp_db.get(), cnlKey, NULL, LHASH_HASH_FN(index_name_hash), LHASH_COMP_FN(index_name_cmp))) corrupt = true; if (corrupt) @@ -596,7 +593,8 @@ bool Ssl::CertificateDb::deleteOldestCertificate() return true; } -bool Ssl::CertificateDb::deleteByHostname(std::string const & host) { +bool +Ssl::CertificateDb::deleteByKey(std::string const & key) { if (!db) return false; @@ -611,7 +609,7 @@ bool Ssl::CertificateDb::deleteByHostname(std::string const & host) { for (int i = 0; i < sk_num(db.get()->data); ++i) { const char ** current_row = ((const char **)sk_value(db.get()->data, i)); #endif - if (host == current_row[cnlName]) { + if (key == current_row[cnlKey]) { deleteRow(current_row, i); return true; } @@ -637,3 +635,32 @@ bool Ssl::CertificateDb::IsEnabledDiskStore() const { return enabled_disk_store; } +bool +Ssl::CertificateDb::WriteEntry(const std::string &filename, const Security::CertPointer & cert, const Ssl::EVP_PKEY_Pointer & pkey, const Security::CertPointer &orig) +{ + Ssl::BIO_Pointer bio; + if (!Ssl::OpenCertsFileForWriting(bio, filename.c_str())) + return false; + if (!Ssl::WriteX509Certificate(bio, cert)) + return false; + if (!Ssl::WritePrivateKey(bio, pkey)) + return false; + if (orig && !Ssl::WriteX509Certificate(bio, orig)) + return false; + return true; +} + +bool +Ssl::CertificateDb::ReadEntry(std::string filename, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, Security::CertPointer &orig) +{ + Ssl::BIO_Pointer bio; + if (!Ssl::OpenCertsFileForReading(bio, filename.c_str())) + return false; + if (!Ssl::ReadX509Certificate(bio, cert)) + return false; + if (!Ssl::ReadPrivateKey(bio, pkey, NULL)) + return false; + // The orig certificate is not mandatory + (void)Ssl::ReadX509Certificate(bio, orig); + return true; +} diff --git a/src/security/cert_generators/file/certificate_db.h b/src/security/cert_generators/file/certificate_db.h index 0325b00436..8c843690c6 100644 --- a/src/security/cert_generators/file/certificate_db.h +++ b/src/security/cert_generators/file/certificate_db.h @@ -69,11 +69,10 @@ class CertificateDb public: /// Names of db columns. enum Columns { - cnlType = 0, + cnlKey = 0, //< The key to use for storing/retrieving entries from DB. cnlExp_date, cnlRev_date, cnlSerial, - cnlFile, cnlName, cnlNumber }; @@ -97,17 +96,19 @@ public: }; CertificateDb(std::string const & db_path, size_t aMax_db_size, size_t aFs_block_size); - /// Find certificate and private key for host name - bool find(std::string const & host_name, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey); + /// finds matching generated certificate and its private key + bool find(std::string const & key, const Security::CertPointer &expectedOrig, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey); /// Delete a certificate from database bool purgeCert(std::string const & key); /// Save certificate to disk. - bool addCertAndPrivateKey(Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, std::string const & useName); + bool addCertAndPrivateKey(std::string const & useKey, const Security::CertPointer & cert, const Ssl::EVP_PKEY_Pointer & pkey, const Security::CertPointer &orig); + + bool IsEnabledDiskStore() const; ///< Check enabled of dist store. + /// Create and initialize a database under the db_path - static void create(std::string const & db_path); + static void Create(std::string const & db_path); /// Check the database stored under the db_path. - static void check(std::string const & db_path, size_t max_db_size, size_t fs_block_size); - bool IsEnabledDiskStore() const; ///< Check enabled of dist store. + static void Check(std::string const & db_path, size_t max_db_size, size_t fs_block_size); private: void load(); ///< Load db from disk. void save(); ///< Save db to disk. @@ -121,14 +122,20 @@ private: size_t getFileSize(std::string const & filename); ///< get file size on disk. size_t rebuildSize(); ///< Rebuild size_file /// Only find certificate in current db and return it. - bool pure_find(std::string const & host_name, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey); + bool pure_find(std::string const & key, const Security::CertPointer & expectedOrig, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey); void deleteRow(const char **row, int rowIndex); ///< Delete a row from TXT_DB bool deleteInvalidCertificate(); ///< Delete invalid certificate. bool deleteOldestCertificate(); ///< Delete oldest certificate. - bool deleteByHostname(std::string const & host); ///< Delete using host name. + bool deleteByKey(std::string const & key); ///< Delete using key. bool hasRows() const; ///< Whether the TXT_DB has stored items. + /// stores the db entry into a file + static bool WriteEntry(const std::string &filename, const Security::CertPointer & cert, const Ssl::EVP_PKEY_Pointer & pkey, const Security::CertPointer &orig); + + /// loads a db entry from the file + static bool ReadEntry(std::string filename, Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, Security::CertPointer &orig); + /// Removes the first matching row from TXT_DB. Ignores failures. static void sq_TXT_DB_delete(TXT_DB *db, const char **row); /// Remove the row on position idx from TXT_DB. Ignores failures. diff --git a/src/security/cert_generators/file/security_file_certgen.cc b/src/security/cert_generators/file/security_file_certgen.cc index 47358fb244..bbbb73a168 100644 --- a/src/security/cert_generators/file/security_file_certgen.cc +++ b/src/security/cert_generators/file/security_file_certgen.cc @@ -190,33 +190,24 @@ static bool processNewRequest(Ssl::CrtdMessage & request_message, std::string co Security::CertPointer cert; Ssl::EVP_PKEY_Pointer pkey; - std::string &cert_subject = certProperties.dbKey(); + Security::CertPointer orig; + std::string &certKey = Ssl::OnDiskCertificateDbKey(certProperties); bool dbFailed = false; try { - db.find(cert_subject, cert, pkey); + db.find(certKey, certProperties.mimicCert, cert, pkey); } catch (std::runtime_error &err) { dbFailed = true; error = err.what(); } - if (cert) { - if (!Ssl::certificateMatchesProperties(cert.get(), certProperties)) { - // The certificate changed (renewed or other reason). - // Generete a new one with the updated fields. - cert.reset(); - pkey.reset(); - db.purgeCert(cert_subject); - } - } - if (!cert || !pkey) { if (!Ssl::generateSslCertificate(cert, pkey, certProperties)) throw std::runtime_error("Cannot create ssl certificate or private key."); if (!dbFailed && db.IsEnabledDiskStore()) { try { - if (!db.addCertAndPrivateKey(cert, pkey, cert_subject)) { + if (!db.addCertAndPrivateKey(certKey, cert, pkey, certProperties.mimicCert)) { dbFailed = true; error = "Cannot add certificate to db."; } @@ -289,7 +280,7 @@ int main(int argc, char *argv[]) if (create_new_db) { std::cout << "Initialization SSL db..." << std::endl; - Ssl::CertificateDb::create(db_path); + Ssl::CertificateDb::Create(db_path); std::cout << "Done" << std::endl; exit(EXIT_SUCCESS); } @@ -308,7 +299,7 @@ int main(int argc, char *argv[]) } { - Ssl::CertificateDb::check(db_path, max_db_size, fs_block_size); + Ssl::CertificateDb::Check(db_path, max_db_size, fs_block_size); } // Initialize SSL subsystem SSL_load_error_strings(); diff --git a/src/ssl/ServerBump.h b/src/ssl/ServerBump.h index 2bb866f302..deb8456b71 100644 --- a/src/ssl/ServerBump.h +++ b/src/ssl/ServerBump.h @@ -15,6 +15,7 @@ #include "HttpRequest.h" #include "ip/Address.h" #include "security/forward.h" +#include "Store.h" class ConnStateData; class store_client; @@ -35,6 +36,9 @@ public: void attachServerSession(const Security::SessionPointer &); ///< Sets the server TLS session object const Security::CertErrors *sslErrors() const; ///< SSL [certificate validation] errors + /// whether there was a successful connection to (and peeking at) the origin server + bool connectedOk() const {return entry && entry->isEmpty();} + /// faked, minimal request; required by Client API HttpRequest::Pointer request; StoreEntry *entry; ///< for receiving Squid-generated error messages diff --git a/src/ssl/context_storage.h b/src/ssl/context_storage.h index d71589339f..40bf1ddd8c 100644 --- a/src/ssl/context_storage.h +++ b/src/ssl/context_storage.h @@ -48,7 +48,7 @@ public: virtual bool aggregatable() const { return false; } }; -typedef LruMap LocalContextStorage; +typedef LruMap LocalContextStorage; /// Class for storing/manipulating LocalContextStorage per local listening address/port. class GlobalContextStorage diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc index b516e4d424..bc2e60a3ef 100644 --- a/src/ssl/gadgets.cc +++ b/src/ssl/gadgets.cc @@ -9,6 +9,7 @@ #include "squid.h" #include "ssl/gadgets.h" +#include #if HAVE_OPENSSL_X509V3_H #include #endif @@ -117,26 +118,6 @@ bool Ssl::appendCertToMemory(Security::CertPointer const & cert, std::string & b return true; } -bool Ssl::writeCertAndPrivateKeyToFile(Security::CertPointer const & cert, Ssl::EVP_PKEY_Pointer const & pkey, char const * filename) -{ - if (!pkey || !cert) - return false; - - Ssl::BIO_Pointer bio(BIO_new(BIO_s_file())); - if (!bio) - return false; - if (!BIO_write_filename(bio.get(), const_cast(filename))) - return false; - - if (!PEM_write_bio_X509(bio.get(), cert.get())) - return false; - - if (!PEM_write_bio_PrivateKey(bio.get(), pkey.get(), NULL, NULL, 0, NULL, NULL)) - return false; - - return true; -} - bool Ssl::readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, char const * bufferToRead) { Ssl::BIO_Pointer bio(BIO_new(BIO_s_mem())); @@ -238,40 +219,60 @@ Ssl::CertificateProperties::CertificateProperties(): signHash(NULL) {} -std::string & Ssl::CertificateProperties::dbKey() const +static void +printX509Signature(const Security::CertPointer &cert, std::string &out) +{ + ASN1_BIT_STRING *sig = nullptr; +#if HAVE_LIBCRYPTO_X509_GET0_SIGNATURE + X509_ALGOR *sig_alg; + X509_get0_signature(&sig, &sig_alg, cert.get()); +#else + sig = cert->signature; +#endif + + if (sig && sig->data) { + const unsigned char *s = sig->data; + for (int i = 0; i < sig->length; ++i) { + char hex[3]; + snprintf(hex, sizeof(hex), "%02x", s[i]); + out.append(hex); + } + } +} + +std::string & +Ssl::OnDiskCertificateDbKey(const Ssl::CertificateProperties &properties) { static std::string certKey; certKey.clear(); certKey.reserve(4096); - if (mimicCert.get()) { - char buf[1024]; - certKey.append(X509_NAME_oneline(X509_get_subject_name(mimicCert.get()), buf, sizeof(buf))); - } + if (properties.mimicCert.get()) + printX509Signature(properties.mimicCert, certKey); if (certKey.empty()) { certKey.append("/CN=", 4); - certKey.append(commonName); + certKey.append(properties.commonName); } - if (setValidAfter) + if (properties.setValidAfter) certKey.append("+SetValidAfter=on", 17); - if (setValidBefore) + if (properties.setValidBefore) certKey.append("+SetValidBefore=on", 18); - if (setCommonName) { + if (properties.setCommonName) { certKey.append("+SetCommonName=", 15); - certKey.append(commonName); + certKey.append(properties.commonName); } - if (signAlgorithm != Ssl::algSignEnd) { + if (properties.signAlgorithm != Ssl::algSignEnd) { certKey.append("+Sign=", 6); - certKey.append(certSignAlgorithm(signAlgorithm)); + certKey.append(certSignAlgorithm(properties.signAlgorithm)); } - if (signHash != NULL) { + if (properties.signHash != NULL) { certKey.append("+SignHash=", 10); - certKey.append(EVP_MD_name(signHash)); + certKey.append(EVP_MD_name(properties.signHash)); } return certKey; @@ -698,46 +699,79 @@ bool Ssl::generateSslCertificate(Security::CertPointer & certToStore, Ssl::EVP_P return generateFakeSslCertificate(certToStore, pkeyToStore, properties, serial); } -/** - \ingroup ServerProtocolSSLInternal - * Read certificate from file. - */ -static X509 * readSslX509Certificate(char const * certFilename) +bool +Ssl::OpenCertsFileForReading(Ssl::BIO_Pointer &bio, const char *filename) { - if (!certFilename) - return NULL; - Ssl::BIO_Pointer bio(BIO_new(BIO_s_file())); + bio.reset(BIO_new(BIO_s_file())); 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; + return false; + if (!BIO_read_filename(bio.get(), filename)) + return false; + return true; } -EVP_PKEY * Ssl::readSslPrivateKey(char const * keyFilename, pem_password_cb *passwd_callback) +bool +Ssl::ReadX509Certificate(Ssl::BIO_Pointer &bio, Security::CertPointer & cert) +{ + assert(bio); + if (X509 *certificate = PEM_read_bio_X509(bio.get(), NULL, NULL, NULL)) { + cert.resetWithoutLocking(certificate); + return true; + } + return false; +} + +bool +Ssl::ReadPrivateKey(Ssl::BIO_Pointer &bio, Ssl::EVP_PKEY_Pointer &pkey, pem_password_cb *passwd_callback) +{ + assert(bio); + if (EVP_PKEY *akey = PEM_read_bio_PrivateKey(bio.get(), NULL, passwd_callback, NULL)) { + pkey.resetWithoutLocking(akey); + return true; + } + return false; +} + +void +Ssl::ReadPrivateKeyFromFile(char const * keyFilename, Ssl::EVP_PKEY_Pointer &pkey, pem_password_cb *passwd_callback) { if (!keyFilename) - return NULL; - Ssl::BIO_Pointer bio(BIO_new(BIO_s_file())); + return; + Ssl::BIO_Pointer bio; + if (!OpenCertsFileForReading(bio, keyFilename)) + return; + ReadPrivateKey(bio, pkey, passwd_callback); +} + +bool +Ssl::OpenCertsFileForWriting(Ssl::BIO_Pointer &bio, const char *filename) +{ + bio.reset(BIO_new(BIO_s_file())); if (!bio) - return NULL; - if (!BIO_read_filename(bio.get(), keyFilename)) - return NULL; - EVP_PKEY *pkey = PEM_read_bio_PrivateKey(bio.get(), NULL, passwd_callback, NULL); - return pkey; + return false; + if (!BIO_write_filename(bio.get(), const_cast(filename))) + return false; + return true; } -void Ssl::readCertAndPrivateKeyFromFiles(Security::CertPointer & cert, Ssl::EVP_PKEY_Pointer & pkey, char const * certFilename, char const * keyFilename) +bool +Ssl::WriteX509Certificate(Ssl::BIO_Pointer &bio, const Security::CertPointer & cert) { - if (keyFilename == NULL) - keyFilename = certFilename; - pkey.resetWithoutLocking(readSslPrivateKey(keyFilename)); - cert.resetWithoutLocking(readSslX509Certificate(certFilename)); - if (!pkey || !cert || !X509_check_private_key(cert.get(), pkey.get())) { - pkey.reset(); - cert.reset(); - } + if (!cert || !bio) + return false; + if (!PEM_write_bio_X509(bio.get(), cert.get())) + return false; + return true; +} + +bool +Ssl::WritePrivateKey(Ssl::BIO_Pointer &bio, const Ssl::EVP_PKEY_Pointer &pkey) +{ + if (!pkey || !bio) + return false; + if (!PEM_write_bio_PrivateKey(bio.get(), pkey.get(), NULL, NULL, 0, NULL, NULL)) + return false; + return true; } bool Ssl::sslDateIsInTheFuture(char const * date) @@ -893,3 +927,28 @@ const char *Ssl::getOrganization(X509 *x509) return getSubjectEntry(x509, NID_organizationName); } + +bool +Ssl::CertificatesCmp(const Security::CertPointer &cert1, const Security::CertPointer &cert2) +{ + if (!cert1 || ! cert2) + return false; + + int cert1Len; + unsigned char *cert1Asn = NULL; + cert1Len = ASN1_item_i2d((ASN1_VALUE *)cert1.get(), &cert1Asn, ASN1_ITEM_rptr(X509)); + + int cert2Len; + unsigned char *cert2Asn = NULL; + cert2Len = ASN1_item_i2d((ASN1_VALUE *)cert2.get(), &cert2Asn, ASN1_ITEM_rptr(X509)); + + if (cert1Len != cert2Len) + return false; + + bool ret = (memcmp(cert1Asn, cert2Asn, cert1Len) == 0); + + OPENSSL_free(cert1Asn); + OPENSSL_free(cert2Asn); + + return ret; +} diff --git a/src/ssl/gadgets.h b/src/ssl/gadgets.h index 93fc8123dd..b0e7f9004e 100644 --- a/src/ssl/gadgets.h +++ b/src/ssl/gadgets.h @@ -94,12 +94,6 @@ bool writeCertAndPrivateKeyToMemory(Security::CertPointer const & cert, EVP_PKEY */ bool appendCertToMemory(Security::CertPointer const & cert, std::string & bufferToWrite); -/** - \ingroup SslCrtdSslAPI - * Write private key and SSL certificate to file. - */ -bool writeCertAndPrivateKeyToFile(Security::CertPointer const & cert, EVP_PKEY_Pointer const & pkey, char const * filename); - /** \ingroup SslCrtdSslAPI * Write private key and SSL certificate to memory. @@ -112,6 +106,49 @@ bool readCertAndPrivateKeyFromMemory(Security::CertPointer & cert, EVP_PKEY_Poin */ bool readCertFromMemory(Security::CertPointer & cert, char const * bufferToRead); +/** + \ingroup SslCrtdSslAPI + * Read private key from file. + */ +void ReadPrivateKeyFromFile(char const * keyFilename, EVP_PKEY_Pointer &pkey, pem_password_cb *passwd_callback); + +/** + \ingroup SslCrtdSslAPI + * Initialize the bio with the file 'filename' openned for reading + */ +bool OpenCertsFileForReading(BIO_Pointer &bio, const char *filename); + +/** + \ingroup SslCrtdSslAPI + * Read a certificate from bio + */ +bool ReadX509Certificate(BIO_Pointer &bio, Security::CertPointer & cert); + +/** + \ingroup SslCrtdSslAPI + * Read a private key from bio + */ +bool ReadPrivateKey(BIO_Pointer &bio, EVP_PKEY_Pointer &pkey, pem_password_cb *passwd_callback); + +/** + \ingroup SslCrtdSslAPI + * Initialize the bio with the file 'filename' openned for writting + */ + +bool OpenCertsFileForWriting(BIO_Pointer &bio, const char *filename); + +/** + \ingroup SslCrtdSslAPI + * Write certificate to BIO. + */ +bool WriteX509Certificate(BIO_Pointer &bio, const Security::CertPointer & cert); + +/** + \ingroup SslCrtdSslAPI + * Write private key to BIO. + */ +bool WritePrivateKey(BIO_Pointer &bio, const EVP_PKEY_Pointer &pkey); + /** \ingroup SslCrtdSslAPI * Supported certificate signing algorithms @@ -191,14 +228,15 @@ public: std::string commonName; ///< A CN to use for the generated certificate CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use const EVP_MD *signHash; ///< The signing hash to use - /// Returns certificate database primary key. New fake certificates - /// purge old fake certificates with the same key. - std::string & dbKey() const; private: CertificateProperties(CertificateProperties &); CertificateProperties &operator =(CertificateProperties const &); }; +/// \ingroup SslCrtdSslAPI +/// \returns certificate database key +std::string & OnDiskCertificateDbKey(const CertificateProperties &); + /** \ingroup SslCrtdSslAPI * Decide on the kind of certificate and generate a CA- or self-signed one. @@ -208,20 +246,6 @@ private: */ bool generateSslCertificate(Security::CertPointer & cert, EVP_PKEY_Pointer & pkey, CertificateProperties const &properties); -/** - \ingroup SslCrtdSslAPI - * Read private key from file. Make sure that this is not encrypted file. - */ -EVP_PKEY * readSslPrivateKey(char const * keyFilename, pem_password_cb *passwd_callback = NULL); - -/** - \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(Security::CertPointer & 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, @@ -251,6 +275,9 @@ const char *CommonHostName(X509 *x509); */ const char *getOrganization(X509 *x509); +/// \ingroup ServerProtocolSSLAPI +/// \return whether both certificates exist and are the same (e.g., have identical ASN.1 images) +bool CertificatesCmp(const Security::CertPointer &cert1, const Security::CertPointer &cert2); } // namespace Ssl #endif // SQUID_SSL_GADGETS_H diff --git a/src/ssl/helper.cc b/src/ssl/helper.cc index 2a36266a12..9d9723b4ab 100644 --- a/src/ssl/helper.cc +++ b/src/ssl/helper.cc @@ -254,7 +254,7 @@ class submitData CBDATA_CLASS(submitData); public: - std::string query; + SBuf query; AsyncCall::Pointer callback; Security::SessionPointer ssl; }; @@ -291,7 +291,7 @@ sslCrtvdHandleReplyWrapper(void *data, const ::Helper::Reply &reply) if (Ssl::CertValidationHelper::HelperCache && (validationResponse->resultCode == ::Helper::Okay || validationResponse->resultCode == ::Helper::Error)) { Ssl::CertValidationResponse::Pointer *item = new Ssl::CertValidationResponse::Pointer(validationResponse); - if (!Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query.c_str(), item)) + if (!Ssl::CertValidationHelper::HelperCache->add(crtdvdData->query, item)) delete item; } @@ -308,14 +308,14 @@ void Ssl::CertValidationHelper::sslSubmit(Ssl::CertValidationRequest const &requ debugs(83, 5, "SSL crtvd request: " << message.compose().c_str()); submitData *crtdvdData = new submitData; - crtdvdData->query = message.compose(); - crtdvdData->query += '\n'; + crtdvdData->query.assign(message.compose().c_str()); + crtdvdData->query.append('\n'); crtdvdData->callback = callback; crtdvdData->ssl = request.ssl; Ssl::CertValidationResponse::Pointer const*validationResponse; if (CertValidationHelper::HelperCache && - (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query.c_str()))) { + (validationResponse = CertValidationHelper::HelperCache->get(crtdvdData->query))) { CertValidationHelper::CbDialer *dialer = dynamic_cast(callback->getDialer()); Must(dialer); diff --git a/src/ssl/helper.h b/src/ssl/helper.h index 88668d4f46..fd06aba753 100644 --- a/src/ssl/helper.h +++ b/src/ssl/helper.h @@ -61,7 +61,7 @@ private: helper * ssl_crt_validator; ///< helper for management of ssl_crtd. public: - typedef LruMap LruCache; + typedef LruMap LruCache; static LruCache *HelperCache; ///< cache for cert validation helper }; diff --git a/src/ssl/support.cc b/src/ssl/support.cc index d6c616c61c..e80ea89362 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -876,25 +876,31 @@ Ssl::createSSLContext(Security::CertPointer & x509, Ssl::EVP_PKEY_Pointer & pkey } Security::ContextPointer -Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port) +Ssl::GenerateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port, bool trusted) { Security::CertPointer cert; Ssl::EVP_PKEY_Pointer pkey; if (!readCertAndPrivateKeyFromMemory(cert, pkey, data) || !cert || !pkey) return Security::ContextPointer(); - return createSSLContext(cert, pkey, port); + Security::ContextPointer ctx(createSSLContext(cert, pkey, port)); + if (ctx && trusted) + Ssl::chainCertificatesToSSLContext(ctx, port); + return ctx; } Security::ContextPointer -Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port) +Ssl::GenerateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port, bool trusted) { Security::CertPointer cert; Ssl::EVP_PKEY_Pointer pkey; if (!generateSslCertificate(cert, pkey, properties) || !cert || !pkey) return Security::ContextPointer(); - return createSSLContext(cert, pkey, port); + Security::ContextPointer ctx(createSSLContext(cert, pkey, port)); + if (ctx && trusted) + Ssl::chainCertificatesToSSLContext(ctx, port); + return ctx; } void @@ -985,11 +991,7 @@ Ssl::verifySslCertificate(Security::ContextPointer &ctx, CertificateProperties c return false; 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); + return (X509_cmp_current_time(time_notBefore) < 0 && X509_cmp_current_time(time_notAfter) > 0); } bool @@ -1323,7 +1325,7 @@ void Ssl::readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, EVP_ // XXX: ssl_ask_password_cb needs SSL_CTX_set_default_passwd_cb_userdata() // so this may not fully work iff Config.Program.ssl_password is set. pem_password_cb *cb = ::Config.Program.ssl_password ? &ssl_ask_password_cb : NULL; - pkey.resetWithoutLocking(readSslPrivateKey(keyFilename, cb)); + Ssl::ReadPrivateKeyFromFile(keyFilename, pkey, cb); cert.resetWithoutLocking(readSslX509CertificatesChain(certFilename, chain.get())); if (!cert) { debugs(83, DBG_IMPORTANT, "WARNING: missing cert in '" << certFilename << "'"); @@ -1361,5 +1363,127 @@ bool Ssl::generateUntrustedCert(Security::CertPointer &untrustedCert, EVP_PKEY_P return Ssl::generateSslCertificate(untrustedCert, untrustedPkey, certProperties); } +void Ssl::InRamCertificateDbKey(const Ssl::CertificateProperties &certProperties, SBuf &key) +{ + bool origSignatureAsKey = false; + if (certProperties.mimicCert.get()) { + ASN1_BIT_STRING *sig = nullptr; +#if HAVE_LIBCRYPTO_X509_GET0_SIGNATURE + X509_ALGOR *sig_alg; + X509_get0_signature(&sig, &sig_alg, certProperties.mimicCert.get()); +#else + sig = certProperties.mimicCert->signature; +#endif + if (sig) { + origSignatureAsKey = true; + key.append((const char *)sig->data, sig->length); + } + } + + if (!origSignatureAsKey || certProperties.setCommonName) { + // Use common name instead + key.append(certProperties.commonName.c_str()); + } + key.append(certProperties.setCommonName ? '1' : '0'); + key.append(certProperties.setValidAfter ? '1' : '0'); + key.append(certProperties.setValidBefore ? '1' : '0'); + key.append(certProperties.signAlgorithm != Ssl:: algSignEnd ? certSignAlgorithm(certProperties.signAlgorithm) : "-"); + key.append(certProperties.signHash ? EVP_MD_name(certProperties.signHash) : "-"); + + if (certProperties.mimicCert) { + BIO *bio = BIO_new_SBuf(&key); + ASN1_item_i2d_bio(ASN1_ITEM_rptr(X509), bio, (ASN1_VALUE *)certProperties.mimicCert.get()); + } +} + +static int +bio_sbuf_create(BIO* bio) +{ + BIO_set_init(bio, 0); + BIO_set_data(bio, NULL); + return 1; +} + +static int +bio_sbuf_destroy(BIO* bio) +{ + if (!bio) + return 0; + return 1; +} + +int +bio_sbuf_write(BIO* bio, const char* data, int len) +{ + SBuf *buf = static_cast(BIO_get_data(bio)); + buf->append(data, len); + return len; +} + +int +bio_sbuf_puts(BIO* bio, const char* data) +{ + SBuf *buf = static_cast(BIO_get_data(bio)); + size_t oldLen = buf->length(); + buf->append(data); + return buf->length() - oldLen; +} + +long +bio_sbuf_ctrl(BIO* bio, int cmd, long num, void* ptr) { + SBuf *buf = static_cast(BIO_get_data(bio)); + switch (cmd) { + case BIO_CTRL_RESET: + buf->clear(); + return 1; + case BIO_CTRL_FLUSH: + return 1; + default: + return 0; + } +} + + +#if HAVE_LIBCRYPTO_BIO_METH_NEW +static BIO_METHOD *BioSBufMethods = nullptr; +#else +static BIO_METHOD BioSBufMethods = { + BIO_TYPE_MEM, + "Squid SBuf", + bio_sbuf_write, + nullptr, + bio_sbuf_puts, + nullptr, + bio_sbuf_ctrl, + bio_sbuf_create, + bio_sbuf_destroy, + NULL, + +}; +#endif + +BIO *Ssl::BIO_new_SBuf(SBuf *buf) +{ +#if HAVE_LIBCRYPTO_BIO_METH_NEW + if (!BioSBufMethods) { + BioSBufMethods = BIO_meth_new(BIO_TYPE_MEM, "Squid-SBuf"); + BIO_meth_set_write(BioSBufMethods, bio_sbuf_write); + BIO_meth_set_read(BioSBufMethods, nullptr); + BIO_meth_set_puts(BioSBufMethods, bio_sbuf_puts); + BIO_meth_set_gets(BioSBufMethods, nullptr); + BIO_meth_set_ctrl(BioSBufMethods, bio_sbuf_ctrl); + BIO_meth_set_create(BioSBufMethods, bio_sbuf_create); + BIO_meth_set_destroy(BioSBufMethods, bio_sbuf_destroy); + } +#else + BIO *bio = BIO_new(&BioSBufMethods); +#endif + if (!bio) + return nullptr; + BIO_set_data(bio, buf); + BIO_set_init(bio, 1); + return bio; +} + #endif /* USE_OPENSSL */ diff --git a/src/ssl/support.h b/src/ssl/support.h index 67d8c2b64b..b28322daf1 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -211,7 +211,7 @@ void unloadSquidUntrusted(); \ingroup ServerProtocolSSLAPI * Decide on the kind of certificate and generate a CA- or self-signed one */ -Security::ContextPointer generateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port); +Security::ContextPointer GenerateSslContext(CertificateProperties const &properties, AnyP::PortCfg &port, bool trusted); /** \ingroup ServerProtocolSSLAPI @@ -227,7 +227,7 @@ bool verifySslCertificate(Security::ContextPointer &, CertificateProperties cons * Read private key and certificate from memory and generate SSL context * using their. */ -Security::ContextPointer generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port); +Security::ContextPointer GenerateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port, bool trusted); /** \ingroup ServerProtocolSSLAPI @@ -321,6 +321,18 @@ int asn1timeToString(ASN1_TIME *tm, char *buf, int len); */ bool setClientSNI(SSL *ssl, const char *fqdn); + +/** + \ingroup ServerProtocolSSLAPI + * Generates a unique key based on CertificateProperties object and store it to key + */ +void InRamCertificateDbKey(const Ssl::CertificateProperties &certProperties, SBuf &key); + +/** + \ingroup ServerProtocolSSLAPI + Generates an OpenSSL BIO for writting to an SBuf object + */ +BIO *BIO_new_SBuf(SBuf *buf); } //namespace Ssl #if _SQUID_WINDOWS_ diff --git a/src/tests/stub_client_side.cc b/src/tests/stub_client_side.cc index 8a989b30dd..d321452f8e 100644 --- a/src/tests/stub_client_side.cc +++ b/src/tests/stub_client_side.cc @@ -44,7 +44,7 @@ NotePairs::Pointer ConnStateData::notes() STUB_RETVAL(NotePairs::Pointer()) #if USE_OPENSSL void ConnStateData::httpsPeeked(PinnedIdleContext) STUB void ConnStateData::getSslContextStart() STUB -void ConnStateData::getSslContextDone(Security::ContextPointer &, bool) STUB +void ConnStateData::getSslContextDone(Security::ContextPointer &) STUB void ConnStateData::sslCrtdHandleReplyWrapper(void *, const Helper::Reply &) STUB void ConnStateData::sslCrtdHandleReply(const Helper::Reply &) STUB void ConnStateData::switchToHttps(HttpRequest *, Ssl::BumpMode) STUB diff --git a/src/tests/stub_libsslsquid.cc b/src/tests/stub_libsslsquid.cc index 4481cef3bf..e0182ce12e 100644 --- a/src/tests/stub_libsslsquid.cc +++ b/src/tests/stub_libsslsquid.cc @@ -65,9 +65,9 @@ namespace Ssl //GETX509ATTRIBUTE GetX509Fingerprint; std::vector BumpModeStr = {""}; bool generateUntrustedCert(Security::CertPointer & untrustedCert, EVP_PKEY_Pointer & untrustedPkey, Security::CertPointer const & cert, EVP_PKEY_Pointer const & pkey) STUB_RETVAL(false) -Security::ContextPointer generateSslContext(CertificateProperties const &, AnyP::PortCfg &) STUB_RETVAL(Security::ContextPointer()) +Security::ContextPointer GenerateSslContext(CertificateProperties const &, AnyP::PortCfg &, bool) STUB_RETVAL(Security::ContextPointer()) bool verifySslCertificate(Security::ContextPointer &, CertificateProperties const &) STUB_RETVAL(false) -Security::ContextPointer generateSslContextUsingPkeyAndCertFromMemory(const char *, AnyP::PortCfg &) STUB_RETVAL(Security::ContextPointer()) +Security::ContextPointer GenerateSslContextUsingPkeyAndCertFromMemory(const char *, AnyP::PortCfg &, bool) STUB_RETVAL(Security::ContextPointer()) void addChainToSslContext(Security::ContextPointer &, STACK_OF(X509) *) STUB void readCertChainAndPrivateKeyFromFiles(Security::CertPointer & cert, EVP_PKEY_Pointer & pkey, X509_STACK_Pointer & chain, char const * certFilename, char const * keyFilename) STUB int matchX509CommonNames(X509 *peer_cert, void *check_data, int (*check_func)(void *check_data, ASN1_STRING *cn_data)) STUB_RETVAL(0) -- 2.39.5