From d620ae0e088b9dd97704a66e67f88d8f02d8e509 Mon Sep 17 00:00:00 2001 From: Christos Tsantilas Date: Mon, 1 Apr 2013 18:37:42 +0300 Subject: [PATCH] Hello forwarding patch Patch general description: - Adds the peek-and-splice SSL bumping mode. - The squid client side read the ssl client hello message. At this time just buffer the hello message and does not pass it to openSSL subsytem. The SSL-client-to-squid connection pauses here. - Squid extracts the ssl client hello message features and configure the squid-to-SSL-server SSL context to have the same features - Squid sents the SSL hello message to SSL server and gets the response. At this time just buffer the SSL server hello response and does not pass it to openSSL subsystem. The squid-to-SSL-server connection pauses here. - Squids decides (at this time always) to bump the connection, so it starts the squid-to-SSL-server connection. The connection to SSL server established. - Squids gets the server certificates, builds new based on these certificates and configure the SSL-client-to-squid SSL context with the generated certificates, and allow the client hello message to enter openSSL subsystem and establish the SSL connection with the client. Technical details: - client_side.cc: The clientNegotiateSSL split in to two functions the clientNegotiateSSL and Squid_SSL_accept, to support the new "pause SSL connection" feature. Add three new ConnStateData methods (startPeekAndSplice, startPeekAndSpliceDone and doPeekAndSpliceStep) to control SSL-client-to-squid connection pause/start. - forward.cc: Add code to the FwdState::initiateSSL method to retrieve SSL features from client SSL hello message and configure the SSL connectio with the SSL server. A new method added, the FwdState::checkForPeekAndSplice which called for peek-and-splice SSL bumping mode to decide if we need to splice or bump the SSL connection. - ssl/bio.cc, features added: Ssl::ClientBio: Read and buffer the hello message Ssl::ServerBio: Hacks the openSSL hello message while the message sent to the server. Buffer the SSL server hello message response Ssl::Bio:sslFeatures: A new class which is able to extract and store SSL features from SSL openSSL objects, or from raw SSL hello messages. --- src/cache_cf.cc | 3 + src/cf.data.pre | 4 + src/client_side.cc | 175 ++++++++++--- src/client_side.h | 8 + src/client_side_request.h | 2 +- src/forward.cc | 49 +++- src/forward.h | 3 + src/ssl/ServerBump.cc | 5 +- src/ssl/ServerBump.h | 3 +- src/ssl/bio.cc | 505 +++++++++++++++++++++++++++++++++++++- src/ssl/bio.h | 126 +++++++++- src/ssl/support.cc | 191 ++++++++++++-- src/ssl/support.h | 41 +++- 13 files changed, 1037 insertions(+), 78 deletions(-) diff --git a/src/cache_cf.cc b/src/cache_cf.cc index e9df3560bc..a7420ffb6c 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -4561,6 +4561,9 @@ static void parse_sslproxy_ssl_bump(acl_access **ssl_bump) } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpServerFirst]) == 0) { A->allow.kind = Ssl::bumpServerFirst; bumpCfgStyleNow = bcsNew; + } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpPeekAndSplice]) == 0) { + A->allow.kind = Ssl::bumpPeekAndSplice; + bumpCfgStyleNow = bcsNew; } else if (strcmp(bm, Ssl::BumpModeStr[Ssl::bumpNone]) == 0) { A->allow.kind = Ssl::bumpNone; bumpCfgStyleNow = bcsNew; diff --git a/src/cf.data.pre b/src/cf.data.pre index d04b07a618..3304d8daa2 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -2214,6 +2214,10 @@ DOC_START the client, using a mimicked server certificate. Works with both CONNECT requests and intercepted SSL connections. + peek-and-splice + Decides if the connection should bumped or not based on + client-to-squid and server-to-squid SSL hello messages. + none Become a TCP tunnel without decoding the connection. Works with both CONNECT requests and intercepted SSL diff --git a/src/client_side.cc b/src/client_side.cc index 609973ff9e..70379490ca 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -137,6 +137,7 @@ #include "ClientInfo.h" #endif #if USE_SSL +#include "ssl/bio.h" #include "ssl/ProxyCerts.h" #include "ssl/context_storage.h" #include "ssl/helper.h" @@ -3425,7 +3426,7 @@ httpAccept(const CommAcceptCbParams ¶ms) static SSL * httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext) { - if (SSL *ssl = Ssl::Create(sslContext, conn->fd, "client https start")) { + if (SSL *ssl = Ssl::CreateServer(sslContext, conn->fd, "client https start")) { debugs(33, 5, "httpsCreate: will negotate SSL on " << conn); return ssl; } @@ -3434,12 +3435,10 @@ httpsCreate(const Comm::ConnectionPointer &conn, SSL_CTX *sslContext) return NULL; } -/** negotiate an SSL connection */ -static void -clientNegotiateSSL(int fd, void *data) +static bool +Squid_SSL_accept(ConnStateData *conn, PF *callback) { - ConnStateData *conn = (ConnStateData *)data; - X509 *client_cert; + int fd = conn->clientConnection->fd; SSL *ssl = fd_table[fd].ssl; int ret; @@ -3449,48 +3448,61 @@ clientNegotiateSSL(int fd, void *data) switch (ssl_error) { case SSL_ERROR_WANT_READ: - Comm::SetSelect(fd, COMM_SELECT_READ, clientNegotiateSSL, conn, 0); - return; + Comm::SetSelect(fd, COMM_SELECT_READ, callback, conn, 0); + return false; case SSL_ERROR_WANT_WRITE: - Comm::SetSelect(fd, COMM_SELECT_WRITE, clientNegotiateSSL, conn, 0); - return; + Comm::SetSelect(fd, COMM_SELECT_WRITE, callback, conn, 0); + return false; case SSL_ERROR_SYSCALL: if (ret == 0) { - debugs(83, 2, "clientNegotiateSSL: Error negotiating SSL connection on FD " << fd << ": Aborted by client"); + debugs(83, 2, "Error negotiating SSL connection on FD " << fd << ": Aborted by client: " << ssl_error); comm_close(fd); - return; + return false; } else { int hard = 1; if (errno == ECONNRESET) hard = 0; - debugs(83, hard ? 1 : 2, "clientNegotiateSSL: Error negotiating SSL connection on FD " << + debugs(83, hard ? 1 : 2, "Error negotiating SSL connection on FD " << fd << ": " << strerror(errno) << " (" << errno << ")"); comm_close(fd); - return; + return false; } case SSL_ERROR_ZERO_RETURN: - debugs(83, DBG_IMPORTANT, "clientNegotiateSSL: Error negotiating SSL connection on FD " << fd << ": Closed by client"); + debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": Closed by client"); comm_close(fd); - return; + return false; default: - debugs(83, DBG_IMPORTANT, "clientNegotiateSSL: Error negotiating SSL connection on FD " << + debugs(83, DBG_IMPORTANT, "Error negotiating SSL connection on FD " << fd << ": " << ERR_error_string(ERR_get_error(), NULL) << " (" << ssl_error << "/" << ret << ")"); comm_close(fd); - return; + return false; } /* NOTREACHED */ } + return true; +} + +/** negotiate an SSL connection */ +static void +clientNegotiateSSL(int fd, void *data) +{ + ConnStateData *conn = (ConnStateData *)data; + X509 *client_cert; + SSL *ssl = fd_table[fd].ssl; + + if (!Squid_SSL_accept(conn, clientNegotiateSSL)) + return; if (SSL_session_reused(ssl)) { debugs(83, 2, "clientNegotiateSSL: Session " << SSL_get_session(ssl) << @@ -3707,8 +3719,16 @@ ConnStateData::sslCrtdHandleReply(const HelperReply &reply) 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(), *port); - getSslContextDone(ctx, true); + if (sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice) { + doPeekAndSpliceStep(); + SSL *ssl = fd_table[clientConnection->fd].ssl; + bool ret = Ssl::configureSSLUsingPkeyAndCertFromMemory(ssl, reply_message.getBody().c_str(), *port); + if (!ret) + debugs(33, 5, HERE << "Failed to set certificates to ssl object for PeekAndSplice mode"); + } else { + SSL_CTX *ctx = Ssl::generateSslContextUsingPkeyAndCertFromMemory(reply_message.getBody().c_str(), *port); + getSslContextDone(ctx, true); + } return; } } @@ -3815,22 +3835,25 @@ ConnStateData::getSslContextStart() sslBumpCertKey = certProperties.dbKey().c_str(); assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); - debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); - Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); - SSL_CTX * dynCtx = NULL; - Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf()); - if (cachedCtx && (dynCtx = cachedCtx->get())) { - debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache"); - if (Ssl::verifySslCertificate(dynCtx, certProperties)) { - debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid"); - getSslContextDone(dynCtx); - return; + // Disable caching for bumpPeekAndSplice mode + if (!(sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice)) { + debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); + Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); + SSL_CTX * dynCtx = NULL; + Ssl::SSL_CTX_Pointer *cachedCtx = ssl_ctx_cache.get(sslBumpCertKey.termedBuf()); + if (cachedCtx && (dynCtx = cachedCtx->get())) { + debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " have found in cache"); + if (Ssl::verifySslCertificate(dynCtx, certProperties)) { + debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is valid"); + getSslContextDone(dynCtx); + return; + } else { + debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); + ssl_ctx_cache.del(sslBumpCertKey.termedBuf()); + } } else { - debugs(33, 5, HERE << "Cached SSL certificate for " << sslBumpCertKey << " is out of date. Delete this certificate from cache"); - ssl_ctx_cache.del(sslBumpCertKey.termedBuf()); + debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } - } else { - debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } #if USE_SSL_CRTD @@ -3852,8 +3875,15 @@ ConnStateData::getSslContextStart() #endif // USE_SSL_CRTD debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); - dynCtx = Ssl::generateSslContext(certProperties, *port); - getSslContextDone(dynCtx, true); + if (sslServerBump && sslServerBump->mode == Ssl::bumpPeekAndSplice) { + doPeekAndSpliceStep(); + SSL *ssl = fd_table[clientConnection->fd].ssl; + if (!Ssl::configureSSL(ssl, certProperties, *port)) + debugs(33, 5, HERE << "Failed to set certificates to ssl object for PeekAndSplice mode"); + } else { + SSL_CTX *dynCtx = Ssl::generateSslContext(certProperties, *port); + getSslContextDone(dynCtx, true); + } return; } getSslContextDone(NULL); @@ -3927,11 +3957,84 @@ ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request); return; } + else if (bumpServerMode == Ssl::bumpPeekAndSplice) { + request->flags.sslPeek = true; + sslServerBump = new Ssl::ServerBump(request, NULL, Ssl::bumpPeekAndSplice); + startPeekAndSplice(); + return; + } // otherwise, use sslConnectHostOrIp getSslContextStart(); } +/** negotiate an SSL connection */ +static void +clientPeekAndSpliceSSL(int fd, void *data) +{ + ConnStateData *conn = (ConnStateData *)data; + SSL *ssl = fd_table[fd].ssl; + + debugs(83, 2, "Start peek and splice on" << fd); + + if (!Squid_SSL_accept(conn, clientPeekAndSpliceSSL)) + debugs(83, 2, "SSL_accept failed."); + + BIO *b = SSL_get_rbio(ssl); + assert(b); + Ssl::ClientBio *bio = static_cast(b->ptr); + if (bio->gotHello()) { + debugs(83, 2, "I got hello. Start forwarding the request!!! "); + Comm::SetSelect(fd, COMM_SELECT_READ, NULL, NULL, 0); + Comm::SetSelect(fd, COMM_SELECT_WRITE, NULL, NULL, 0); + conn->startPeekAndSpliceDone(); + return; + } +} + +void ConnStateData::startPeekAndSplice() +{ + // will call httpsPeeked() with certificate and connection, eventually + SSL_CTX *unConfiguredCTX = Ssl::createSSLContext(port->signingCert, port->signPkey, *port); + fd_table[clientConnection->fd].dynamicSslContext = unConfiguredCTX; + + if (!httpsCreate(clientConnection, unConfiguredCTX)) + return; + + // commSetConnTimeout() was called for this request before we switched. + + // Disable the client read handler until CachePeer selection is complete + Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0); + Comm::SetSelect(clientConnection->fd, COMM_SELECT_READ, clientPeekAndSpliceSSL, this, 0); + switchedToHttps_ = true; + + SSL *ssl = fd_table[clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(ssl); + Ssl::ClientBio *bio = static_cast(b->ptr); + bio->hold(true); +} + +void +ConnStateData::startPeekAndSpliceDone() +{ + FwdState::fwdStart(clientConnection, sslServerBump->entry, sslServerBump->request); +} + +void +ConnStateData::doPeekAndSpliceStep() +{ + SSL *ssl = fd_table[clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(ssl); + assert(b); + Ssl::ClientBio *bio = static_cast(b->ptr); + + debugs(33, 5, HERE << "PeekAndSplice mode, proceed with client negotiation. Currrent state:" << SSL_state_string_long(ssl)); + bio->hold(false); + + Comm::SetSelect(clientConnection->fd, COMM_SELECT_WRITE, clientNegotiateSSL, this, 0); + switchedToHttps_ = true; +} + void ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) { diff --git a/src/client_side.h b/src/client_side.h index 208b3bf361..c5a0099234 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -332,6 +332,14 @@ public: void quitAfterError(HttpRequest *request); // meant to be private #if USE_SSL + /// Initializes and starts a peek-and-splice negotiation with the SSL client + void startPeekAndSplice(); + /// Called when the initialization of peek-and-splice negotiation finidhed + void startPeekAndSpliceDone(); + /// Called when a peek-and-splice step finished. For example after + /// server-side SSL certificates received and client-side SSL certificates + /// generated + void doPeekAndSpliceStep(); /// called by FwdState when it is done bumping the server void httpsPeeked(Comm::ConnectionPointer serverConnection); diff --git a/src/client_side_request.h b/src/client_side_request.h index a5c0821116..4dfd63b6b8 100644 --- a/src/client_side_request.h +++ b/src/client_side_request.h @@ -155,7 +155,7 @@ public: /// returns raw sslBump mode value Ssl::BumpMode sslBumpNeed() const { return sslBumpNeed_; } /// returns true if and only if the request needs to be bumped - bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst; } + bool sslBumpNeeded() const { return sslBumpNeed_ == Ssl::bumpServerFirst || sslBumpNeed_ == Ssl::bumpClientFirst || sslBumpNeed_ == Ssl::bumpPeekAndSplice; } /// set the sslBumpNeeded state void sslBumpNeed(Ssl::BumpMode mode); void sslBumpStart(); diff --git a/src/forward.cc b/src/forward.cc index 6a15ed82a5..f774f0bf70 100644 --- a/src/forward.cc +++ b/src/forward.cc @@ -72,6 +72,7 @@ #include "urn.h" #include "whois.h" #if USE_SSL +#include "ssl/bio.h" #include "ssl/cert_validate_message.h" #include "ssl/Config.h" #include "ssl/helper.h" @@ -651,6 +652,8 @@ FwdState::negotiateSSL(int fd) unsigned long ssl_lib_error = SSL_ERROR_NONE; SSL *ssl = fd_table[fd].ssl; int ret; + BIO *b = SSL_get_rbio(ssl); + Ssl::ServerBio *srvBio = static_cast(b->ptr); if ((ret = SSL_connect(ssl)) <= 0) { int ssl_error = SSL_get_error(ssl, ret); @@ -667,6 +670,11 @@ FwdState::negotiateSSL(int fd) return; case SSL_ERROR_WANT_WRITE: + if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice && srvBio->holdWrite()) { + debugs(81, DBG_IMPORTANT, "hold write on SSL connection on FD " << fd); + checkForPeekAndSplice(); + return; + } Comm::SetSelect(fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0); return; @@ -796,6 +804,23 @@ FwdState::negotiateSSL(int fd) dispatch(); } +void +FwdState::checkForPeekAndSplice() +{ + SSL *ssl = fd_table[serverConn->fd].ssl; + BIO *b = SSL_get_rbio(ssl); + Ssl::ServerBio *srvBio = static_cast(b->ptr); + debugs(83,5, "Will check for peek and splice on fd " << serverConn->fd); + const bool splice = false; + if (!splice) { + //Allow write, proceed with the connection + srvBio->holdWrite(false); + srvBio->recordInput(false); + Comm::SetSelect(serverConn->fd, COMM_SELECT_WRITE, fwdNegotiateSSLWrapper, this, 0); + debugs(83,5, "Retry the fwdNegotiateSSL on fd " << serverConn->fd); + } +} + void FwdState::sslCrtvdHandleReplyWrapper(void *data, Ssl::CertValidationResponse const &validationResponse) { @@ -928,7 +953,7 @@ FwdState::initiateSSL() assert(sslContext); - SSL *ssl = Ssl::Create(sslContext, fd, "server https start"); + SSL *ssl = Ssl::CreateClient(sslContext, fd, "server https start"); if (!ssl) { ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, HTTP_INTERNAL_SERVER_ERROR, request); // TODO: create Ssl::ErrorDetail with OpenSSL-supplied error code @@ -954,6 +979,28 @@ FwdState::initiateSSL() if (peer->sslSession) SSL_set_session(ssl, peer->sslSession); + } else if (request->clientConnectionManager->sslBumpMode == Ssl::bumpPeekAndSplice) { + SSL *clientSsl = fd_table[request->clientConnectionManager->clientConnection->fd].ssl; + BIO *b = SSL_get_rbio(clientSsl); + Ssl::ClientBio *clnBio = static_cast(b->ptr); + const Ssl::Bio::sslFeatures &features = clnBio->getFeatures(); + if (features.sslVersion != -1) { + SSL_set_ssl_method(ssl, Ssl::method(features.toSquidSSLVersion())); + if (!features.serverName.empty()) + SSL_set_tlsext_host_name(ssl, features.serverName.c_str()); + if (!features.clientRequestedCiphers.empty()) + SSL_set_cipher_list(ssl, features.clientRequestedCiphers.c_str()); +#ifdef SSL_OP_NO_COMPRESSION /* XXX: OpenSSL 0.9.8k lacks SSL_OP_NO_COMPRESSION */ + if (features.compressMethod == 0) + SSL_set_options(ssl, SSL_OP_NO_COMPRESSION); +#endif + if (features.sslVersion >= 3) { + b = SSL_get_rbio(ssl); + Ssl::ServerBio *srvBio = static_cast(b->ptr); + srvBio->setClientRandom(features.client_random); + srvBio->recordInput(true); + } + } } else { // While we are peeking at the certificate, we may not know the server // name that the client will request (after interception or CONNECT) diff --git a/src/forward.h b/src/forward.h index aad16c21ac..aeca1aa2f6 100644 --- a/src/forward.h +++ b/src/forward.h @@ -70,8 +70,11 @@ public: void connectStart(); void connectDone(const Comm::ConnectionPointer & conn, comm_err_t status, int xerrno); void connectTimeout(int fd); +#if USE_SSL void initiateSSL(); void negotiateSSL(int fd); + void checkForPeekAndSplice(); +#endif bool checkRetry(); bool checkRetriable(); void dispatch(); diff --git a/src/ssl/ServerBump.cc b/src/ssl/ServerBump.cc index 10d7182858..59eb761359 100644 --- a/src/ssl/ServerBump.cc +++ b/src/ssl/ServerBump.cc @@ -14,9 +14,10 @@ CBDATA_NAMESPACED_CLASS_INIT(Ssl, ServerBump); -Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e): +Ssl::ServerBump::ServerBump(HttpRequest *fakeRequest, StoreEntry *e, Ssl::BumpMode md): request(fakeRequest), - sslErrors(NULL) + sslErrors(NULL), + mode(md) { debugs(33, 4, HERE << "will peek at " << request->GetHost() << ':' << request->port); const char *uri = urlCanonical(request); diff --git a/src/ssl/ServerBump.h b/src/ssl/ServerBump.h index f214e726aa..b681505507 100644 --- a/src/ssl/ServerBump.h +++ b/src/ssl/ServerBump.h @@ -20,7 +20,7 @@ namespace Ssl class ServerBump { public: - explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL); + explicit ServerBump(HttpRequest *fakeRequest, StoreEntry *e = NULL, Ssl::BumpMode mode = Ssl::bumpServerFirst); ~ServerBump(); /// faked, minimal request; required by server-side API @@ -28,6 +28,7 @@ public: StoreEntry *entry; ///< for receiving Squid-generated error messages Ssl::X509_Pointer serverCert; ///< HTTPS server certificate Ssl::Errors *sslErrors; ///< SSL [certificate validation] errors + Ssl::BumpMode mode; ///< The SSL server bump mode private: store_client *sc; ///< dummy client to prevent entry trimming diff --git a/src/ssl/bio.cc b/src/ssl/bio.cc index 1bee31cbb9..d697a67a0c 100644 --- a/src/ssl/bio.cc +++ b/src/ssl/bio.cc @@ -4,17 +4,23 @@ */ #include "squid.h" +#include "ssl/support.h" /* support.cc says this is needed */ #if USE_SSL #include "comm.h" +#include "ip/Address.h" +#include "fde.h" +#include "globals.h" #include "Mem.h" #include "ssl/bio.h" #if HAVE_OPENSSL_SSL_H #include #endif +#undef DO_SSLV23 + // TODO: fde.h should probably export these for wrappers like ours extern int default_read_method(int, char *, int); extern int default_write_method(int, const char *, int); @@ -50,10 +56,10 @@ static BIO_METHOD SquidMethods = { }; BIO * -Ssl::Bio::Create(const int fd) +Ssl::Bio::Create(const int fd, Ssl::Bio::Type type) { if (BIO *bio = BIO_new(&SquidMethods)) { - BIO_int_ctrl(bio, BIO_C_SET_FD, BIO_NOCLOSE, fd); + BIO_int_ctrl(bio, BIO_C_SET_FD, type, fd); return bio; } return NULL; @@ -136,10 +142,231 @@ Ssl::Bio::stateChanged(const SSL *ssl, int where, int ret) // else if (where & SSL_CB_HANDSHAKE_DONE) // debugs(83, 9, "SSL connection established"); - debugs(83, 7, "FD " << fd_ << " now: " << where << ' ' << + debugs(83, 7, "FD " << fd_ << " now: 0x" << std::hex << where << std::dec << ' ' << SSL_state_string(ssl) << " (" << SSL_state_string_long(ssl) << ")"); } +bool +Ssl::ClientBio::isClientHello(int state) +{ + return (state == SSL2_ST_GET_CLIENT_HELLO_A || + state == SSL3_ST_SR_CLNT_HELLO_A || + state == SSL23_ST_SR_CLNT_HELLO_A || + state == SSL23_ST_SR_CLNT_HELLO_B || + state == SSL3_ST_SR_CLNT_HELLO_B || + state == SSL3_ST_SR_CLNT_HELLO_C + ); +} + +void +Ssl::ClientBio::stateChanged(const SSL *ssl, int where, int ret) +{ + Ssl::Bio::stateChanged(ssl, where, ret); +} + +int +Ssl::ClientBio::write(const char *buf, int size, BIO *table) +{ + if (holdWrite_) { + BIO_set_retry_write(table); + return 0; + } + + return Ssl::Bio::write(buf, size, table); +} + +const char *objToString(unsigned char const *bytes, int len) +{ + static std::string buf; + buf.clear(); + for(int i = 0; i < len; i++ ) { + char tmp[3]; + snprintf(tmp, sizeof(tmp), "%.2x", bytes[i]); + buf.append(tmp); + } + return buf.c_str(); +} + +int +Ssl::ClientBio::read(char *buf, int size, BIO *table) +{ + if (headerState < 2) { + + if (rbuf.isNull()) + rbuf.init(1024, 4096); + + size = rbuf.spaceSize() > size ? size : rbuf.spaceSize(); + + if (!size) + return 0; + + int bytes = Ssl::Bio::read(buf, size, table); + if (!bytes) + return 0; + rbuf.append(buf, bytes); + debugs(83, 7, "rbuf size: " << rbuf.contentSize()); + } + + if (headerState == 0) { + + const unsigned char *head = (const unsigned char *)rbuf.content(); + const char *s = objToString(head, rbuf.contentSize()); + debugs(83, 7, "SSL Header: " << s); + if (rbuf.contentSize() < 5) { + BIO_set_retry_read(table); + return 0; + } + + if (head[0] == 0x16) { + debugs(83, 7, "SSL version 3 handshake message"); + headerBytes = (head[3] << 8) + head[4]; + debugs(83, 7, "SSL Header Size: " << headerBytes); +#ifdef DO_SSLV23 + } else if ((head[0] & 0x80) && head[2] == 0x01 && head[3] == 0x03) { + debugs(83, 7, "SSL version 2 handshake message with v3 support"); + headerBytes = head[1]; +#endif + }else { + debugs(83, 7, "Not an SSL acceptable handshake message (SSLv2 message?)"); + return -1; + } + + headerState = 1; //Next state + } + + if (headerState == 1) { + const unsigned char *head = (const unsigned char *)rbuf.content(); + const char *s = objToString(head, rbuf.contentSize()); + debugs(83, 7, "SSL Header: " << s); + + if (headerBytes > rbuf.contentSize()) { + BIO_set_retry_read(table); + return -1; + } + features.get((const unsigned char *)rbuf.content()); + headerState = 2; + } + + if (holdRead_) { + debugs(83, 7, "Hold flag is set, retry latter. (Hold " << size << "bytes)"); + BIO_set_retry_read(table); + return -1; + } + + if (headerState >=2) { + if (rbuf.hasContent()) { + int bytes = (size <= rbuf.contentSize() ? size : rbuf.contentSize()); + memcpy(buf, rbuf.content(), bytes); + rbuf.consume(bytes); + return bytes; + } else + return Ssl::Bio::read(buf, size, table); + } + + return -1; +} + +void +Ssl::ServerBio::stateChanged(const SSL *ssl, int where, int ret) +{ + Ssl::Bio::stateChanged(ssl, where, ret); +} + +void +Ssl::ServerBio::setClientRandom(const unsigned char *r) +{ + memcpy(clientRandom, r, SSL3_RANDOM_SIZE); + randomSet = true; +}; + +int +Ssl::ServerBio::read(char *buf, int size, BIO *table) +{ + int bytes = Ssl::Bio::read(buf, size, table); + + if (bytes > 0 && record_) { + if (rbuf.isNull()) + rbuf.init(1024, 8196); + rbuf.append(buf, bytes); + } + return bytes; +} + +int +Ssl::ServerBio::write(const char *buf, int size, BIO *table) +{ + + if (holdWrite_) { + debugs(83, 7, "Hold write, for SSL connection on " << fd_); + BIO_set_retry_write(table); + return -1; + } + + if (!helloBuild) { + if ( + buf[1] >= 3 //it is an SSL Version3 message + && buf[0] == 0x16 // and it is a Handshake/Hello message + ) { + if (helloMsg.isNull()) + helloMsg.init(1024, 4096); + + //Hello message is the first message we write to server + assert(!helloMsg.hasContent()); + + SSL *ssl = fd_table[fd_].ssl; + if (randomSet && ssl && ssl->s3) { + assert(size > 11 + SSL3_RANDOM_SIZE); + helloMsg.append(buf, 11); + //The random number is stored in the 11 position of the + // message we are going to sent + helloMsg.append((char *)clientRandom, SSL3_RANDOM_SIZE); + size_t len = size - 11 - SSL3_RANDOM_SIZE; + helloMsg.append(buf + 11 + SSL3_RANDOM_SIZE, len); + + // We need to fix the random in SSL struct: + memcpy(ssl->s3->client_random, clientRandom, SSL3_RANDOM_SIZE); + // We also need to fix the raw message in SSL struct + // stored in SSL->init_buf. Looks that it is used to get + // digest of the previous sent SSL message, to compute keys + // for encryption/decryption: + memcpy(ssl->init_buf->data + 6, clientRandom, SSL3_RANDOM_SIZE); + + debugs(83, 7, "SSL HELLO message for FD " << fd_ << ": Random number is adjusted"); + } + } + helloBuild = true; + helloMsgSize = helloMsg.contentSize(); + } + + if (helloMsg.hasContent()) { + debugs(83, 7, "buffered write for FD " << fd_); + int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table); + helloMsg.consume(ret); + if (helloMsg.hasContent()) { + // We need to retry sendind data. + // Say to openSSL to retry sending hello message + BIO_set_retry_write(table); + return -1; + } + + // Sending hello message complete. Do not send more data for now... + holdWrite_ = true; + // The size should be less than the size of the hello message + assert(size >= helloMsgSize); + return helloMsgSize; + } else + return Ssl::Bio::write(buf, size, table); +} + +void +Ssl::ServerBio::flush(BIO *table) +{ + if (helloMsg.hasContent()) { + int ret = Ssl::Bio::write(helloMsg.content(), helloMsg.contentSize(), table); + helloMsg.consume(ret); + } +} + /// initializes BIO table after allocation static int squid_bio_create(BIO *bi) @@ -196,7 +423,11 @@ squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2) case BIO_C_SET_FD: { assert(arg2); const int fd = *static_cast(arg2); - Ssl::Bio *bio = new Ssl::Bio(fd); + Ssl::Bio *bio; + if (arg1 == Ssl::Bio::BIO_TO_SERVER) + bio = new Ssl::ServerBio(fd); + else + bio = new Ssl::ClientBio(fd); assert(!table->ptr); table->ptr = bio; table->init = 1; @@ -222,7 +453,7 @@ squid_bio_ctrl(BIO *table, int cmd, long arg1, void *arg2) if (table->init) { Ssl::Bio *bio = static_cast(table->ptr); assert(bio); - bio->flush(); + bio->flush(table); return 1; } return 0; @@ -255,4 +486,268 @@ squid_ssl_info(const SSL *ssl, int where, int ret) } } +Ssl::Bio::sslFeatures::sslFeatures(): sslVersion(-1), compressMethod(-1) +{ + memset(client_random, 0, SSL3_RANDOM_SIZE); +} + +int Ssl::Bio::sslFeatures::toSquidSSLVersion() const +{ + if(sslVersion == SSL2_VERSION) + return 2; + else if(sslVersion == SSL3_VERSION) + return 3; + else if(sslVersion == TLS1_VERSION) + return 4; +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + else if(sslVersion == TLS1_1_VERSION) + return 5; + else if(sslVersion == TLS1_2_VERSION) + return 6; +#endif + else + return 1; +} + +bool +Ssl::Bio::sslFeatures::get(const SSL *ssl) +{ + sslVersion = SSL_version(ssl); + debugs(83, 7, "SSL version: " << SSL_get_version(ssl) << " (" << sslVersion << ")"); + + if(const char *server = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name)) + serverName = server; + debugs(83, 7, "SNI server name: " << serverName); + + if (ssl->session->compress_meth) + compressMethod = ssl->session->compress_meth; + else if(sslVersion >= 3) //if it is 3 or newer version then compression is disabled + compressMethod = 0; + debugs(83, 7, "SSL compression: " << compressMethod); + + STACK_OF(SSL_CIPHER) * ciphers = NULL; + if (ssl->server) + ciphers = ssl->session->ciphers; + else + ciphers = ssl->cipher_list; + if (ciphers) { + for (int i = 0; i < sk_SSL_CIPHER_num(ciphers); ++i) { + SSL_CIPHER *c = sk_SSL_CIPHER_value(ciphers, i); + if (c != NULL) { + if(!clientRequestedCiphers.empty()) + clientRequestedCiphers.append(":"); + clientRequestedCiphers.append(c->name); + } + } + } + debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers); + + if (sslVersion >=3 && ssl->s3 && ssl->s3->client_random[0]) { + memcpy(client_random, ssl->s3->client_random, SSL3_RANDOM_SIZE); + } + +#if 0 /* XXX: OpenSSL 0.9.8k lacks at least some of these tlsext_* fields */ + //The following extracted for logging purpuses: + // TLSEXT_TYPE_ec_point_formats + unsigned char *p; + int len; + if (ssl->server) { + p = ssl->session->tlsext_ecpointformatlist; + len = ssl->session->tlsext_ecpointformatlist_length; + } else { + p = ssl->tlsext_ecpointformatlist; + len = ssl->tlsext_ecpointformatlist_length; + } + if (p) { + ecPointFormatList = objToString(p, len); + debugs(83, 7, "tlsExtension ecPointFormatList of length " << len << " :" << ecPointFormatList); + } + + // TLSEXT_TYPE_elliptic_curves + if (ssl->server) { + p = ssl->session->tlsext_ellipticcurvelist; + len = ssl->session->tlsext_ellipticcurvelist_length; + } else { + p = ssl->tlsext_ellipticcurvelist; + len = ssl->tlsext_ellipticcurvelist_length; + } + if (p) { + ellipticCurves = objToString(p, len); + debugs(83, 7, "tlsExtension ellipticCurveList of length " << len <<" :" << ellipticCurves); + } + // TLSEXT_TYPE_opaque_prf_input + p = NULL; + if (ssl->server) { + if (ssl->s3 && ssl->s3->client_opaque_prf_input) { + p = (unsigned char *)ssl->s3->client_opaque_prf_input; + len = ssl->s3->client_opaque_prf_input_len; + } + } else { + p = (unsigned char *)ssl->tlsext_opaque_prf_input; + len = ssl->tlsext_opaque_prf_input_len; + } + if (p) { + debugs(83, 7, "tlsExtension client-opaque-prf-input of length " << len); + opaquePrf = objToString(p, len); + } +#endif + return true; +} + +bool +Ssl::Bio::sslFeatures::get(const unsigned char *hello) +{ + // The SSL handshake message should starts with a 0x16 byte + if (hello[0] == 0x16) { + return parseV3Hello(hello); +#ifdef DO_SSLV23 + } else if ((hello[0] & 0x80) && hello[2] == 0x01 && hello[3] == 0x03) { + return parseV23Hello(hello); +#endif + } + + debugs(83, 7, "Not a known SSL handshake message"); + return false; +} + +bool +Ssl::Bio::sslFeatures::parseV3Hello(const unsigned char *hello) +{ + debugs(83, 7, "Get fake features from v3 hello message."); + // The SSL version exist in the 2nd and 3rd bytes + sslVersion = (hello[1] << 8) | hello[2]; + debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion); + + // The following hello message size exist in 4th and 5th bytes + int helloSize = (hello[3] << 8) | hello[4]; + helloSize += 5; //Include the 5 header bytes. + + //For SSLv3 or TLSv1.* protocols we can get some more informations + if (hello[1] == 0x3 && hello[5] == 0x1 /*HELLO A message*/) { + // Get the correct version of the sub-hello message + sslVersion = (hello[9] << 8) | hello[10]; + //Get Client Random number. It starts on the position 11 of hello message + memcpy(client_random, hello + 11, SSL3_RANDOM_SIZE); + debugs(83, 7, "Client random: " << objToString(client_random, SSL3_RANDOM_SIZE)); + + // At the position 43 (11+SSL3_RANDOM_SIZE) + int sessIDLen = (int)hello[43]; + debugs(83, 7, "Session ID Length: " << sessIDLen); + + //Ciphers list. It is stored after the Session ID. + const unsigned char *ciphers = hello + 44 + sessIDLen; + int ciphersLen = (ciphers[0] << 8) | ciphers[1]; + ciphers += 2; + if (ciphersLen) { + const SSL_METHOD *method = SSLv3_method(); + int cs = method->put_cipher_by_char(NULL, NULL); + assert(cs > 0); + for (int i = 0; i < ciphersLen; i += cs) { + const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i)); + if (c != NULL) { + if(!clientRequestedCiphers.empty()) + clientRequestedCiphers.append(":"); + clientRequestedCiphers.append(c->name); + } + } + } + debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers); + + // Compression field: 1 bytes the number of compression methods and + // 1 byte for each compression method + const unsigned char *compression = ciphers + ciphersLen; + if (compression[0] > 1) + compressMethod = 1; + else + compressMethod = 0; + debugs(83, 7, "SSL compression methods number: " << (int)compression[0]); + + const unsigned char *extensions = compression + 1 + (int)compression[0]; + if (extensions < hello + helloSize) { + int extensionsLen = (extensions[0] << 8) | extensions[1]; + const unsigned char *ext = extensions + 2; + while (ext < extensions+extensionsLen){ + short extType = (ext[0] << 8) | ext[1]; + ext += 2; + short extLen = (ext[0] << 8) | ext[1]; + ext += 2; + debugs(83, 7, "SSL Exntension: " << std::hex << extType << " of size:" << extLen); + //The SNI extension has the type 0 (extType == 0) + // The two first bytes indicates the length of the SNI data (should be extLen-2) + // The next byte is the hostname type, it should be '0' for normal hostname (ext[2] == 0) + // The 3rd and 4th bytes are the length of the hostname + if (extType == 0 && ext[2] == 0) { + int hostLen = (ext[3] << 8) | ext[4]; + serverName.assign((const char *)(ext+5), hostLen); + debugs(83, 7, "Found server name: " << serverName); + } + ext += extLen; + } + } + } + return true; +} + +bool +Ssl::Bio::sslFeatures::parseV23Hello(const unsigned char *hello) +{ +#ifdef DO_SSLV23 + debugs(83, 7, "Get fake features from v23 hello message."); + sslVersion = (hello[3] << 8) | hello[4]; + debugs(83, 7, "Get fake features. Version :" << std::hex << std::setw(8) << std::setfill('0')<< sslVersion); + + // The following hello message size exist in 2nd byte + int helloSize = hello[1]; + helloSize += 2; //Include the 2 header bytes. + + //Ciphers list. It is stored after the Session ID. + + int ciphersLen = (hello[5] << 8) | hello[6]; + const unsigned char *ciphers = hello + 11; + if (ciphersLen) { + const SSL_METHOD *method = SSLv23_method(); + int cs = method->put_cipher_by_char(NULL, NULL); + assert(cs > 0); + for (int i = 0; i < ciphersLen; i += cs) { + // The v2 hello messages cipher has 3 bytes. + // The v2 cipher has the first byte not null + // Because we are going to sent only v3 message we + // are ignoring these ciphers + if (ciphers[i] != 0) + continue; + const SSL_CIPHER *c = method->get_cipher_by_char((ciphers + i + 1)); + if (c != NULL) { + if(!clientRequestedCiphers.empty()) + clientRequestedCiphers.append(":"); + clientRequestedCiphers.append(c->name); + } + } + } + debugs(83, 7, "Ciphers requested by client: " << clientRequestedCiphers); + + //Get Client Random number. It starts on the position 11 of hello message + memcpy(client_random, ciphers + ciphersLen, SSL3_RANDOM_SIZE); + debugs(83, 7, "Client random: " << objToString(client_random, SSL3_RANDOM_SIZE)); + + compressMethod = 0; + return true; +#else + return false; +#endif +} + +std::ostream & +Ssl::Bio::sslFeatures::print(std::ostream &os) const +{ + static std::string buf; + return os << "v" << sslVersion << + " SNI:" << (serverName.empty() ? "-" : serverName) << + " comp:" << compressMethod << + " Ciphers:" << clientRequestedCiphers << + " Random:" << objToString(client_random, SSL3_RANDOM_SIZE) << + " ecPointFormats:" << ecPointFormatList << + " ec:" << ellipticCurves << + " opaquePrf:" << opaquePrf; +} + #endif /* USE_SSL */ diff --git a/src/ssl/bio.h b/src/ssl/bio.h index 2d0fa04688..690a6ab099 100644 --- a/src/ssl/bio.h +++ b/src/ssl/bio.h @@ -1,6 +1,8 @@ #ifndef SQUID_SSL_BIO_H #define SQUID_SSL_BIO_H +#include "MemBuf.h" +#include #if HAVE_OPENSSL_BIO_H #include #endif @@ -13,29 +15,139 @@ namespace Ssl { /// BIO source and sink node, handling socket I/O and monitoring SSL state class Bio { public: + enum Type { + BIO_TO_CLIENT = 6000, + BIO_TO_SERVER + }; + + /// Class to store SSL connection features + class sslFeatures { + public: + sslFeatures(); + bool get(const SSL *ssl); ///< Retrieves the features from SSL object + bool get(const unsigned char *hello); ///< Retrieves the features from raw SSL hello message + bool parseV3Hello(const unsigned char *hello); + bool parseV23Hello(const unsigned char *hello); + /// Prints to os stream a human readable form of sslFeatures object + std::ostream & print(std::ostream &os) const; + /// Converts to the internal squid SSL version form the sslVersion + int toSquidSSLVersion() const; + public: + int sslVersion; ///< The requested/used SSL version + int compressMethod; ///< The requested/used compressed method + std::string serverName; ///< The SNI hostname, if any + std::string clientRequestedCiphers; ///< The client requested ciphers + std::string ecPointFormatList;///< tlsExtension ecPointFormatList + std::string ellipticCurves; ///< tlsExtension ellipticCurveList + std::string opaquePrf; ///< tlsExtension opaquePrf + /// The client random number + unsigned char client_random[SSL3_RANDOM_SIZE]; + }; explicit Bio(const int anFd); ~Bio(); - int write(const char *buf, int size, BIO *table); - int read(char *buf, int size, BIO *table); - void flush() {} // we do not buffer (yet?) + /// Writes the given data to socket + virtual int write(const char *buf, int size, BIO *table); + + /// Reads data from socket + virtual int read(char *buf, int size, BIO *table); - int fd() const { return fd_; } + /// Flushes any buffered data to socket. + /// The Ssl::Bio does not buffer any data, so this method has nothing to do + virtual void flush(BIO *table) {} + + int fd() const { return fd_; } ///< The SSL socket descriptor /// Called by linked SSL connection whenever state changes, an alert /// appears, or an error occurs. See SSL_set_info_callback(). - void stateChanged(const SSL *ssl, int where, int ret); + virtual void stateChanged(const SSL *ssl, int where, int ret); /// Creates a low-level BIO table, creates a high-level Ssl::Bio object /// for a given socket, and then links the two together via BIO_C_SET_FD. - static BIO *Create(const int fd); + static BIO *Create(const int fd, Type type); /// Tells ssl connection to use BIO and monitor state via stateChanged() static void Link(SSL *ssl, BIO *bio); -private: +protected: const int fd_; ///< the SSL socket we are reading and writing }; +/// BIO node to handle socket IO for squid client side +class ClientBio: public Bio { +public: + explicit ClientBio(const int anFd): Bio(anFd), holdRead_(false), holdWrite_(false), headerState(0), headerBytes(0) {} + + /// The ClientBio version of the Ssl::Bio::stateChanged method + /// When the client hello message retrieved, fill the + /// "features" member with the client provided informations. + virtual void stateChanged(const SSL *ssl, int where, int ret); + /// The ClientBio version of the Ssl::Bio::write method + virtual int write(const char *buf, int size, BIO *table); + /// The ClientBio version of the Ssl::Bio::read method + /// If the holdRead flag is true then it does not write any data + /// to socket and sets the "read retry" flag of the BIO to true + virtual int read(char *buf, int size, BIO *table); + /// Return true if the client hello message received and analized + bool gotHello() {return features.sslVersion != -1;} + /// Return the SSL features requested by SSL client + const Bio::sslFeatures &getFeatures() const {return features;} + /// Prevents or allow writting on socket. + void hold(bool h) {holdRead_ = holdWrite_ = h;} + +private: + /// True if the SSL state corresponds to a hello message + bool isClientHello(int state); + /// The futures retrieved from client SSL hello message + Bio::sslFeatures features; + bool holdRead_; ///< The read hold state of the bio. + bool holdWrite_; ///< The write hold state of the bio. + MemBuf rbuf; ///< Used to buffer input data. + int headerState; + int headerBytes; +}; + +/// BIO node to handle socket IO for squid server side +class ServerBio: public Bio { +public: + explicit ServerBio(const int anFd): Bio(anFd), randomSet(false), helloMsgSize(0), helloBuild(false), holdWrite_(false), record_(false) {} + /// The ServerBio version of the Ssl::Bio::stateChanged method + virtual void stateChanged(const SSL *ssl, int where, int ret); + /// The ServerBio version of the Ssl::Bio::write method + /// If a clientRandom number is set then rewrites the raw hello message + /// "client random" field with the provided random number. + /// It may buffer the output packets. + virtual int write(const char *buf, int size, BIO *table); + /// The ServerBio version of the Ssl::Bio::read method + /// If the record flag is set then append the data to the rbuf member + virtual int read(char *buf, int size, BIO *table); + /// The ServerBio version of the Ssl::Bio::flush method. + /// Flushes any buffered data + virtual void flush(BIO *table); + /// Sets the random number to use in client SSL HELLO message + void setClientRandom(const unsigned char *r); + + bool holdWrite() const {return holdWrite_;} + void holdWrite(bool h) {holdWrite_ = h;} + void recordInput(bool r) {record_ = r;} + const MemBuf &rBufData() {return rbuf;} +private: + /// A random number to use as "client random" in client hello message + unsigned char clientRandom[SSL3_RANDOM_SIZE]; + bool randomSet; ///< True if the clientRandom member is set and can be used + MemBuf helloMsg; ///< Used to buffer output data. + int helloMsgSize; + bool helloBuild; ///< True if the client hello message sent to the server + bool holdWrite_; ///< The write hold state of the bio. + bool record_; + MemBuf rbuf; ///< Used to buffer input data. +}; + +inline +std::ostream &operator <<(std::ostream &os, Ssl::Bio::sslFeatures const &f) +{ + return f.print(os); +} + } // namespace Ssl #endif /* SQUID_SSL_BIO_H */ diff --git a/src/ssl/support.cc b/src/ssl/support.cc index 8918bd8f27..a6be51a195 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -59,6 +59,7 @@ const char *Ssl::BumpModeStr[] = { "none", "client-first", "server-first", + "peek-and-splice", NULL }; @@ -943,32 +944,95 @@ sslCreateServerContext(AnyP::PortCfg &port) 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::OpenSSLtoSquidSSLVersion(int sslVersion) { - int ssl_error; + if(sslVersion == SSL2_VERSION) + return 2; + else if(sslVersion == SSL3_VERSION) + return 3; + else if(sslVersion == TLS1_VERSION) + return 4; +#if OPENSSL_VERSION_NUMBER >= 0x10001000L + else if(sslVersion == TLS1_1_VERSION) + return 5; + else if(sslVersion == TLS1_2_VERSION) + return 6; +#endif + else + return 1; +} + + #if OPENSSL_VERSION_NUMBER < 0x00909000L - SSL_METHOD *method; +SSL_METHOD * #else - const SSL_METHOD *method; +const SSL_METHOD * #endif - SSL_CTX *sslContext; - long fl = Ssl::parse_flags(flags); +Ssl::method(int version) +{ + switch (version) { - ssl_initialize(); + case 2: +#ifndef OPENSSL_NO_SSL2 + debugs(83, 5, "Using SSLv2."); + return SSLv2_client_method(); +#else + debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy."); + return NULL; +#endif + break; - if (!keyfile) - keyfile = certfile; + case 3: + debugs(83, 5, "Using SSLv3."); + return SSLv3_client_method(); + break; - if (!certfile) - certfile = keyfile; + case 4: + debugs(83, 5, "Using TLSv1."); + return TLSv1_client_method(); + break; + + case 5: +#if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. + debugs(83, 5, "Using TLSv1.1."); + return TLSv1_1_client_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"); + return TLSv1_2_client_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."); + return SSLv23_client_method(); + break; + } + + //Not reached + return NULL; +} + +const SSL_METHOD * +Ssl::serverMethod(int version) +{ switch (version) { case 2: #ifndef OPENSSL_NO_SSL2 debugs(83, 5, "Using SSLv2."); - method = SSLv2_client_method(); + return SSLv2_server_method(); #else debugs(83, DBG_IMPORTANT, "SSLv2 is not available in this Proxy."); return NULL; @@ -977,18 +1041,18 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c case 3: debugs(83, 5, "Using SSLv3."); - method = SSLv3_client_method(); + return SSLv3_server_method(); break; case 4: debugs(83, 5, "Using TLSv1."); - method = TLSv1_client_method(); + return 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_client_method(); + return TLSv1_1_server_method(); #else debugs(83, DBG_IMPORTANT, "TLSv1.1 is not available in this Proxy."); return NULL; @@ -998,7 +1062,7 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c case 6: #if OPENSSL_VERSION_NUMBER >= 0x10001000L // NP: not sure exactly which sub-version yet. debugs(83, 5, "Using TLSv1.2"); - method = TLSv1_2_client_method(); + return TLSv1_2_server_method(); #else debugs(83, DBG_IMPORTANT, "TLSv1.2 is not available in this Proxy."); return NULL; @@ -1009,10 +1073,37 @@ sslCreateClientContext(const char *certfile, const char *keyfile, int version, c default: debugs(83, 5, "Using SSLv2/SSLv3."); - method = SSLv23_client_method(); + return SSLv23_server_method(); break; } + //Not reached + return NULL; +} + +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); + + ssl_initialize(); + + if (!keyfile) + keyfile = certfile; + + if (!certfile) + certfile = keyfile; + + if (!(method = Ssl::method(version))) + return NULL; + sslContext = SSL_CTX_new(method); if (sslContext == NULL) { @@ -1402,8 +1493,8 @@ Ssl::contextMethod(int version) /// \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, AnyP::PortCfg &port) +SSL_CTX * +Ssl::createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port) { Ssl::SSL_CTX_Pointer sslContext(SSL_CTX_new(port.contextMethod)); @@ -1450,6 +1541,49 @@ Ssl::generateSslContext(CertificateProperties const &properties, AnyP::PortCfg & return createSSLContext(cert, pkey, port); } +bool +Ssl::configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!generateSslCertificate(cert, pkey, properties)) + return false; + + if (!cert) + return false; + + if (!pkey) + return false; + + if (!SSL_use_certificate(ssl, cert.get())) + return false; + + if (!SSL_use_PrivateKey(ssl, pkey.get())) + return false; + + return true; +} + +bool +Ssl::configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port) +{ + Ssl::X509_Pointer cert; + Ssl::EVP_PKEY_Pointer pkey; + if (!readCertAndPrivateKeyFromMemory(cert, pkey, data)) + return false; + + if (!cert || !pkey) + return false; + + if (!SSL_use_certificate(ssl, cert.get())) + return false; + + if (!SSL_use_PrivateKey(ssl, pkey.get())) + return false; + + return true; +} + bool Ssl::verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &properties) { // Temporary ssl for getting X509 certificate from SSL_CTX. @@ -1582,13 +1716,13 @@ bool Ssl::generateUntrustedCert(X509_Pointer &untrustedCert, EVP_PKEY_Pointer &u } SSL * -Ssl::Create(SSL_CTX *sslContext, const int fd, const char *squidCtx) +SslCreate(SSL_CTX *sslContext, const int fd, Ssl::Bio::Type type, const char *squidCtx) { const char *errAction = NULL; int errCode = 0; if (SSL *ssl = SSL_new(sslContext)) { // without BIO, we would call SSL_set_fd(ssl, fd) instead - if (BIO *bio = Ssl::Bio::Create(fd)) { + if (BIO *bio = Ssl::Bio::Create(fd, type)) { Ssl::Bio::Link(ssl, bio); // cannot fail fd_table[fd].ssl = ssl; @@ -1611,4 +1745,17 @@ Ssl::Create(SSL_CTX *sslContext, const int fd, const char *squidCtx) return NULL; } +SSL * +Ssl::CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_SERVER, squidCtx); +} + +SSL * +Ssl::CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx) +{ + return SslCreate(sslContext, fd, Ssl::Bio::BIO_TO_CLIENT, squidCtx); +} + + #endif /* USE_SSL */ diff --git a/src/ssl/support.h b/src/ssl/support.h index c66a65b369..61dc1fceec 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -74,9 +74,13 @@ typedef int ssl_error_t; typedef CbDataList Errors; -/// Creates SSL connection structure and initializes SSL I/O (Comm and BIO). +/// Creates SSL Client connection structure and initializes SSL I/O (Comm and BIO). /// On errors, emits DBG_IMPORTANT with details and returns NULL. -SSL *Create(SSL_CTX *sslContext, const int fd, const char *squidCtx); +SSL *CreateClient(SSL_CTX *sslContext, const int fd, const char *squidCtx); + +/// Creates SSL Server connection structure and initializes SSL I/O (Comm and BIO). +/// On errors, emits DBG_IMPORTANT with details and returns NULL. +SSL *CreateServer(SSL_CTX *sslContext, const int fd, const char *squidCtx); } //namespace Ssl @@ -128,7 +132,7 @@ GETX509ATTRIBUTE GetX509Fingerprint; \ingroup ServerProtocolSSLAPI * Supported ssl-bump modes */ -enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpEnd}; +enum BumpMode {bumpNone = 0, bumpClientFirst, bumpServerFirst, bumpPeekAndSplice, bumpEnd}; /** \ingroup ServerProtocolSSLAPI @@ -203,6 +207,27 @@ bool verifySslCertificate(SSL_CTX * sslContext, CertificateProperties const &pr */ SSL_CTX * generateSslContextUsingPkeyAndCertFromMemory(const char * data, AnyP::PortCfg &port); +/** + \ingroup ServerProtocolSSLAPI + * Create an SSL context using the provided certificate and key + */ +SSL_CTX * createSSLContext(Ssl::X509_Pointer & x509, Ssl::EVP_PKEY_Pointer & pkey, AnyP::PortCfg &port); + +/** + \ingroup ServerProtocolSSLAPI + * Generates a certificate and a private key using provided properies and set it + * to SSL object. + */ +bool configureSSL(SSL *ssl, CertificateProperties const &properties, AnyP::PortCfg &port); + +/** + \ingroup ServerProtocolSSLAPI + * Read private key and certificate from memory and set it to SSL object + * using their. + */ +bool configureSSLUsingPkeyAndCertFromMemory(SSL *ssl, const char *data, AnyP::PortCfg &port); + + /** \ingroup ServerProtocolSSLAPI * Adds the certificates in certList to the certificate chain of the SSL context @@ -255,6 +280,16 @@ int asn1timeToString(ASN1_TIME *tm, char *buf, int len); \return true if SNI set false otherwise */ bool setClientSNI(SSL *ssl, const char *fqdn); + +int OpenSSLtoSquidSSLVersion(int sslVersion); + +#if OPENSSL_VERSION_NUMBER < 0x00909000L +SSL_METHOD *method(int version); +#else +const SSL_METHOD *method(int version); +#endif + +const SSL_METHOD *serverMethod(int version); } //namespace Ssl #if _SQUID_WINDOWS_ -- 2.47.3