From: Christos Tsantilas Date: Fri, 27 Jan 2012 15:53:38 +0000 (+0200) Subject: sslproxy_cert_sign squid.conf option X-Git-Tag: BumpSslServerFirst.take04~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=aebe6888b2d7870965f2e7877e562004e4d91bf9;p=thirdparty%2Fsquid.git sslproxy_cert_sign squid.conf option This option control how generated fake SSL certificates are signed. Syntax: sslproxy_cert_sign acl ... where can be one of the signTrusted, signUntrusted or signSelf Default signing algorithm if the sslproxy_cert_sign is not configured is signSelf, if the server certificate is self signed, signUntrusted if the server certificate is untrusted (ERR_INVALID_CA, ERR_SELF_SIGNED_CERT_IN_CHAIN, ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, ERR_UNABLE_TO_GET_ISSUER_CERT, ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY and ERR_CERT_UNTRUSTED errors) and signTrusted if the server certificate is trusted. Notes: - The signing algorithm passed as parameter to the ssl_crtd daemon - A self signed certificate generated on squid startup to be used as signing certificate for untrusted certificates, with CN = "Squid CA for Untrusted Certificates" - The configured certificates with http(s)_port squid.conf option chained to the client SSL connection only when signTrusted signing algorithm selected. When the signing algorithm is signSelf or signUntrusted no other certificate send to the client browser. - A small bug fixed which did not allow the sslproxy_cert_adapt option to be enabled in squid if the icap client is not enabled --- diff --git a/src/cache_cf.cc b/src/cache_cf.cc index 5f325270cc..c523792e77 100644 --- a/src/cache_cf.cc +++ b/src/cache_cf.cc @@ -197,6 +197,9 @@ static void free_http_port_list(http_port_list **); static void parse_https_port_list(https_port_list **); static void dump_https_port_list(StoreEntry *, const char *, const https_port_list *); static void free_https_port_list(https_port_list **); +static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign); +static void dump_sslproxy_cert_sign(StoreEntry *entry, const char *name, sslproxy_cert_sign *cert_sign); +static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign); static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt); static void dump_sslproxy_cert_adapt(StoreEntry *entry, const char *name, sslproxy_cert_adapt *cert_adapt); static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt); @@ -4523,6 +4526,7 @@ static void free_icap_service_failure_limit(Adaptation::Icap::Config *cfg) cfg->oldest_service_failure = 0; cfg->service_failure_limit = 0; } +#endif #if USE_SSL static void parse_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt) @@ -4600,6 +4604,59 @@ static void free_sslproxy_cert_adapt(sslproxy_cert_adapt **cert_adapt) safe_free(ca); } } -#endif + +static void parse_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign) +{ + char *al; + sslproxy_cert_sign *cs = (sslproxy_cert_sign *) xcalloc(1, sizeof(sslproxy_cert_sign)); + if ((al = strtok(NULL, w_space)) == NULL) { + self_destruct(); + return; + } + + if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignTrusted]) == 0) + cs->alg = Ssl::algSignTrusted; + else if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignUntrusted]) == 0) + cs->alg = Ssl::algSignUntrusted; + else if (strcmp(al, Ssl::CertSignAlgorithmStr[Ssl::algSignSelf]) == 0) + cs->alg = Ssl::algSignSelf; + else { + debugs(3, DBG_CRITICAL, "FATAL: sslproxy_cert_sign: unknown cert signing algorithm: " << al); + self_destruct(); + return; + } + + aclParseAclList(LegacyParser, &cs->aclList); + + while(*cert_sign) + cert_sign = &(*cert_sign)->next; + + *cert_sign = cs; +} + +static void dump_sslproxy_cert_sign(StoreEntry *entry, const char *name, sslproxy_cert_sign *cert_sign) +{ + sslproxy_cert_sign *cs; + for (cs = cert_sign; cs != NULL; cs = cs->next) { + storeAppendPrintf(entry, "%s ", name); + storeAppendPrintf(entry, "%s ", Ssl::certSignAlgorithm(cs->alg)); + if (cs->aclList) + dump_acl_list(entry, cs->aclList); + storeAppendPrintf(entry, "\n"); + } +} + +static void free_sslproxy_cert_sign(sslproxy_cert_sign **cert_sign) +{ + while(*cert_sign) { + sslproxy_cert_sign *cs = *cert_sign; + *cert_sign = cs->next; + + if (cs->aclList) + aclDestroyAclList(&cs->aclList); + + safe_free(cs); + } +} #endif diff --git a/src/cf.data.depend b/src/cf.data.depend index 478390aef9..6250d0bf6e 100644 --- a/src/cf.data.depend +++ b/src/cf.data.depend @@ -69,4 +69,5 @@ wccp2_amethod wccp2_service wccp2_service_info wordlist +sslproxy_cert_sign acl sslproxy_cert_adapt acl diff --git a/src/cf.data.pre b/src/cf.data.pre index 0b45881252..0b1a2c4a4a 100644 --- a/src/cf.data.pre +++ b/src/cf.data.pre @@ -2052,6 +2052,30 @@ DOC_START Default setting: sslproxy_cert_error deny all DOC_END +NAME: sslproxy_cert_sign +IFDEF: USE_SSL +DEFAULT: none +TYPE: sslproxy_cert_sign +LOC: Config.ssl_client.cert_sign +DOC_START + + sslproxy_cert_sign acl ... + + The following certificate signing algorithms supported: + signTrusted + the current signing algorithm using a configured CA certificate + that is usually placed in and trusted by end-user browsers + signUntrusted + sign to guarantee an X509_V_ERR_CERT_UNTRUSTED browser error + signSelf + sign using a self-signed certificate with the right CN to + generate a X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT error in the + browser. + + When the acl(s) match, the corresponding signing algorithm is used to + generate the certificate. Otherwise, the default signing algorithm used +DOC_END + NAME: sslproxy_cert_adapt IFDEF: USE_SSL DEFAULT: none diff --git a/src/client_side.cc b/src/client_side.cc index 99355d28c7..afaf08c1d6 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -3632,15 +3632,19 @@ ConnStateData::sslCrtdHandleReply(const char * reply) getSslContextDone(NULL); } -void ConnStateData::buildSslCertAdaptParams(Ssl::CrtdMessage::BodyParams &certAdaptParams) +void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties, Ssl::CrtdMessage::BodyParams &certAdaptParams) { // Build a key to use for storing/retrieving certificates to cache sslBumpCertKey = sslCommonName.defined() ? sslCommonName : sslConnectHostOrIp; + certProperties.commonName = sslBumpCertKey.termedBuf(); // fake certificate adaptation requires bump-server-first mode if (!bumpServerFirstErrorEntry()) return; + if (X509 *mimicCert = bumpServerCert.get()) + certProperties.mimicCert.resetAndLock(mimicCert); + HttpRequest *fakeRequest = new HttpRequest(); fakeRequest->SetHost(sslConnectHostOrIp.termedBuf()); fakeRequest->port = clientConnection->local.GetPort(); @@ -3664,8 +3668,16 @@ void ConnStateData::buildSslCertAdaptParams(Ssl::CrtdMessage::BodyParams &certAd // if not param defined for Common Name adaptation use hostname from // the CONNECT request - if (!param && ca->alg == Ssl::algSetCommonName) - param = sslConnectHostOrIp.termedBuf(); + if (ca->alg == Ssl::algSetCommonName) { + if (!param) + param = sslConnectHostOrIp.termedBuf(); + certProperties.commonName = param; + certProperties.setCommonName = true; + } + else if(ca->alg == Ssl::algSetValidAfter) + certProperties.setValidAfter = true; + else if(ca->alg == Ssl::algSetValidBefore) + certProperties.setValidBefore = true; assert(alg && param); debugs(33, 5, HERE << "Matches certificate adaptation aglorithm: " << @@ -3680,7 +3692,71 @@ void ConnStateData::buildSslCertAdaptParams(Ssl::CrtdMessage::BodyParams &certAd sslBumpCertKey.append("="); sslBumpCertKey.append(param); } - } + } + + certProperties.signAlgorithm = Ssl::algSignEnd; + for (sslproxy_cert_sign *sg = Config.ssl_client.cert_sign; sg != NULL; sg = sg->next) { + if (sg->aclList && checklist.fastCheck(sg->aclList) == ACCESS_ALLOWED) { + const char *sgAlg = Ssl::CertSignAlgorithmStr[sg->alg]; + certProperties.signAlgorithm = (Ssl::CertSignAlgorithm)sg->alg; + certAdaptParams.insert( std::make_pair(Ssl::CrtdMessage::param_Sign, sgAlg)); + sslBumpCertKey.append("+Sign="); + sslBumpCertKey.append(sgAlg); + break; + } + } + + if (certProperties.signAlgorithm == Ssl::algSignEnd) { + // Use the default algorithm + //Temporary code.... + // TODO: implement the following using acls: + Ssl::ssl_error_t selfSignErrors[] = {X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT, 0}; + Ssl::ssl_error_t unTrustedErrors[] = {X509_V_ERR_INVALID_CA, + X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN, + X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT, + X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY, + X509_V_ERR_CERT_UNTRUSTED,0}; + for (int i = 0; selfSignErrors[i] != 0; i++) { + if (bumpSslErrorNoList->find(selfSignErrors[i])) { + certProperties.signAlgorithm = Ssl::algSignSelf; + const char *sgAlg = Ssl::CertSignAlgorithmStr[Ssl::algSignSelf]; + sslBumpCertKey.append("+Sign="); + sslBumpCertKey.append(sgAlg); + certAdaptParams.insert( std::make_pair(Ssl::CrtdMessage::param_Sign, sgAlg)); + break; + } + } + if (certProperties.signAlgorithm == Ssl::algSignEnd) { + for (int i = 0; unTrustedErrors[i] != 0; i++) { + if (bumpSslErrorNoList->find(selfSignErrors[i])) { + certProperties.signAlgorithm = Ssl::algSignUntrusted; + const char *sgAlg = Ssl::CertSignAlgorithmStr[Ssl::algSignUntrusted]; + sslBumpCertKey.append("+Sign="); + sslBumpCertKey.append(sgAlg); + certAdaptParams.insert( std::make_pair(Ssl::CrtdMessage::param_Sign, sgAlg)); + break; + } + } + } + if (certProperties.signAlgorithm == Ssl::algSignEnd) + certProperties.signAlgorithm = Ssl::algSignTrusted; + //End of Temporary code.... + } + + if (certProperties.signAlgorithm == Ssl::algSignUntrusted) { + assert(Ssl::SquidCaCert.get() && Ssl::SquidCaCertKey.get()); + certProperties.signWithX509.resetAndLock(Ssl::SquidCaCert.get()); + certProperties.signWithPkey.resetAndLock(Ssl::SquidCaCertKey.get()); + } + else { + if (port->signingCert.get()) + certProperties.signWithX509.resetAndLock(port->signingCert.get()); + + if (port->signPkey.get()) + certProperties.signWithPkey.resetAndLock(port->signPkey.get()); + } + signAlgorithm = certProperties.signAlgorithm; } void @@ -3688,10 +3764,11 @@ ConnStateData::getSslContextStart() { if (port->generateHostCertificates) { Ssl::CrtdMessage::BodyParams certAdaptParams; - buildSslCertAdaptParams(certAdaptParams); + Ssl::CertificateProperties certProperties; + buildSslCertGenerationParams(certProperties, certAdaptParams); assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); - debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); + debugs(33, 5, HERE << "Finding SSL certificate for " << sslBumpCertKey << " in cache"); Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); SSL_CTX * dynCtx = ssl_ctx_cache.find(sslBumpCertKey.termedBuf()); if (dynCtx) { @@ -3708,19 +3785,18 @@ ConnStateData::getSslContextStart() debugs(33, 5, HERE << "SSL certificate for " << sslBumpCertKey << " haven't found in cache"); } - char const * host = sslCommonName.defined() ? sslCommonName.termedBuf() : sslConnectHostOrIp.termedBuf(); #if USE_SSL_CRTD - debugs(33, 5, HERE << "Generating SSL certificate for " << host << " using ssl_crtd."); + debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName << " using ssl_crtd."); Ssl::CrtdMessage request_message; request_message.setCode(Ssl::CrtdMessage::code_new_certificate); Ssl::CrtdMessage::BodyParams map; - map.insert(std::make_pair(Ssl::CrtdMessage::param_host, host)); + map.insert(std::make_pair(Ssl::CrtdMessage::param_host, certProperties.commonName)); /*Append parameters for cert adaptation*/ map.insert(certAdaptParams.begin(), certAdaptParams.end()); std::string bufferToWrite; - Ssl::writeCertAndPrivateKeyToMemory(port->signingCert, port->signPkey, bufferToWrite); - if (bumpServerCert.get()) { - Ssl::appendCertToMemory(bumpServerCert, bufferToWrite); + Ssl::writeCertAndPrivateKeyToMemory(certProperties.signWithX509, certProperties.signWithPkey, bufferToWrite); + if (certProperties.mimicCert.get()) { + Ssl::appendCertToMemory(certProperties.mimicCert, bufferToWrite); debugs(33, 5, HERE << "Append Mimic Certificate to body request: " << bufferToWrite); } request_message.composeBody(map, bufferToWrite); @@ -3728,8 +3804,8 @@ ConnStateData::getSslContextStart() Ssl::Helper::GetInstance()->sslSubmit(request_message, sslCrtdHandleReplyWrapper, this); return; #else - debugs(33, 5, HERE << "Generating SSL certificate for " << host); - dynCtx = Ssl::generateSslContext(host, bumpServerCert, port->signingCert, port->signPkey, certAdaptParams); + debugs(33, 5, HERE << "Generating SSL certificate for " << certProperties.commonName); + dynCtx = Ssl::generateSslContext(certProperties); getSslContextDone(dynCtx, true); return; #endif //USE_SSL_CRTD @@ -3743,7 +3819,9 @@ ConnStateData::getSslContextDone(SSL_CTX * sslContext, bool isNew) // Try to add generated ssl context to storage. if (port->generateHostCertificates && isNew) { - Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); + if (signAlgorithm == Ssl::algSignTrusted) + Ssl::addChainToSslContext(sslContext, port->certsToChain.get()); + //else it is self-signed or untrusted do not attrach any certificate Ssl::LocalContextStorage & ssl_ctx_cache(Ssl::TheGlobalContextStorage.getLocalStorage(port->s)); assert(sslBumpCertKey.defined() && sslBumpCertKey[0] != '\0'); diff --git a/src/client_side.h b/src/client_side.h index 589d818a0b..65c4b1b2a6 100644 --- a/src/client_side.h +++ b/src/client_side.h @@ -338,7 +338,7 @@ public: void setBumpSslErrorList(Ssl::Errors *errNoList) {bumpSslErrorNoList = cbdataReference(errNoList);} /// Fill the certAdaptParams with the required data for certificate adaptation /// and create the key for storing/retrieve the certificate to/from the cache - void buildSslCertAdaptParams(Ssl::CrtdMessage::BodyParams &certAdaptParams); + void buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties, Ssl::CrtdMessage::BodyParams &certAdaptParams); bool serveDelayedError(ClientSocketContext *context); #else bool switchedToHttps() const { return false; } @@ -374,6 +374,7 @@ private: StoreEntry *bumpErrorEntry; Ssl::X509_Pointer bumpServerCert; Ssl::Errors *bumpSslErrorNoList; ///< The list of SSL certificate errors which ignored + Ssl::CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use #endif AsyncCall::Pointer reader; ///< set when we are reading diff --git a/src/ssl/crtd_message.cc b/src/ssl/crtd_message.cc index f23c1aa2bb..6d870336c8 100644 --- a/src/ssl/crtd_message.cc +++ b/src/ssl/crtd_message.cc @@ -179,3 +179,4 @@ const std::string Ssl::CrtdMessage::param_host("host"); const std::string Ssl::CrtdMessage::param_SetValidAfter(Ssl::CertAdaptAlgorithmStr[algSetValidAfter]); const std::string Ssl::CrtdMessage::param_SetValidBefore(Ssl::CertAdaptAlgorithmStr[algSetValidBefore]); const std::string Ssl::CrtdMessage::param_SetCommonName(Ssl::CertAdaptAlgorithmStr[algSetCommonName]); +const std::string Ssl::CrtdMessage::param_Sign("Sign"); diff --git a/src/ssl/crtd_message.h b/src/ssl/crtd_message.h index 5891c3f207..86489b53d0 100644 --- a/src/ssl/crtd_message.h +++ b/src/ssl/crtd_message.h @@ -71,6 +71,8 @@ public: static const std::string param_SetValidBefore; /// Parameter name for passing SetCommonName cert adaptation variable static const std::string param_SetCommonName; + /// Parameter name for passing signing algorithm + static const std::string param_Sign; private: enum ParseState { BEFORE_CODE, diff --git a/src/ssl/gadgets.cc b/src/ssl/gadgets.cc index bed17c327b..bbfd252311 100644 --- a/src/ssl/gadgets.cc +++ b/src/ssl/gadgets.cc @@ -8,44 +8,6 @@ #include #endif -/** - \ingroup ServerProtocolSSLInternal - * Add CN to subject in request. - */ -static bool addCnToRequest(Ssl::X509_REQ_Pointer & request, char const * cn) -{ - // not an Ssl::X509_NAME_Pointer because X509_REQ_get_subject_name() - // returns a pointer to the existing subject name. Nothing to clean here. - X509_NAME *name = X509_REQ_get_subject_name(request.get()); - if (!name) - return false; - - // The second argument of the X509_NAME_add_entry_by_txt declared as - // "char *" on some OS. Use cn_name to avoid compile warnings. - static char cn_name[3] = "CN"; - if (!X509_NAME_add_entry_by_txt(name, cn_name, MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0)) - return false; - - return true; -} - -/** - \ingroup ServerProtocolSSLInternal - * Make request on sign using private key and hostname. - */ -static bool makeRequest(Ssl::X509_REQ_Pointer & request, Ssl::EVP_PKEY_Pointer const & pkey, char const * host) -{ - if (!X509_REQ_set_version(request.get(), 0L)) - return false; - - if (!addCnToRequest(request, host)) - return false; - - if (!X509_REQ_set_pubkey(request.get(), pkey.get())) - return false; - return true; -} - EVP_PKEY * Ssl::createSslPrivateKey() { Ssl::EVP_PKEY_Pointer pkey(EVP_PKEY_new()); @@ -65,18 +27,6 @@ EVP_PKEY * Ssl::createSslPrivateKey() return pkey.release(); } -X509_REQ * Ssl::createNewX509Request(Ssl::EVP_PKEY_Pointer const & pkey, const char * hostname) -{ - Ssl::X509_REQ_Pointer request(X509_REQ_new()); - - if (!request) - return NULL; - - if (!makeRequest(request, pkey, hostname)) - return NULL; - return request.release(); -} - /** \ingroup ServerProtocolSSLInternal * Set serial random serial number or set random serial number. @@ -101,41 +51,6 @@ static bool setSerialNumber(ASN1_INTEGER *ai, BIGNUM const* serial) return true; } -X509 * Ssl::signRequest(Ssl::X509_REQ_Pointer const & request, Ssl::X509_Pointer const & x509, Ssl::EVP_PKEY_Pointer const & pkey, ASN1_TIME * timeNotAfter, BIGNUM const * serial) -{ - Ssl::X509_Pointer cert(X509_new()); - if (!cert) - return NULL; - - if (!setSerialNumber(X509_get_serialNumber(cert.get()), serial)) - return NULL; - - if (!X509_set_issuer_name(cert.get(), x509.get() ? X509_get_subject_name(x509.get()) : X509_REQ_get_subject_name(request.get()))) - return NULL; - - if (!X509_gmtime_adj(X509_get_notBefore(cert.get()), (-2)*24*60*60)) - return NULL; - - if (timeNotAfter) { - if (!X509_set_notAfter(cert.get(), timeNotAfter)) - return NULL; - } else if (!X509_gmtime_adj(X509_get_notAfter(cert.get()), 60*60*24*356*3)) - return NULL; - - if (!X509_set_subject_name(cert.get(), X509_REQ_get_subject_name(request.get()))) - return NULL; - - Ssl::EVP_PKEY_Pointer tmppkey(X509_REQ_get_pubkey(request.get())); - - if (!tmppkey || !X509_set_pubkey(cert.get(), tmppkey.get())) - return NULL; - - if (!X509_sign(cert.get(), pkey.get(), EVP_sha1())) - return NULL; - - return cert.release(); -} - bool Ssl::writeCertAndPrivateKeyToMemory(Ssl::X509_Pointer const & cert, Ssl::EVP_PKEY_Pointer const & pkey, std::string & bufferToWrite) { bufferToWrite.clear(); @@ -235,27 +150,6 @@ bool Ssl::readCertFromMemory(X509_Pointer & cert, char const * bufferToRead) return true; } -bool Ssl::generateSslCertificateAndPrivateKey(char const *host, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::X509_Pointer & cert, Ssl::EVP_PKEY_Pointer & pkey, BIGNUM const * serial) -{ - pkey.reset(createSslPrivateKey()); - if (!pkey) - return false; - - Ssl::X509_REQ_Pointer request(createNewX509Request(pkey, host)); - if (!request) - return false; - - if (signedX509.get() && signedPkey.get()) - cert.reset(signRequest(request, signedX509, signedPkey, X509_get_notAfter(signedX509.get()), serial)); - else - cert.reset(signRequest(request, signedX509, pkey, NULL, serial)); - - if (!cert) - return false; - - return true; -} - // Replace certs common name with the given static bool replaceCommonName(Ssl::X509_Pointer & cert, const char *cn) { @@ -264,15 +158,24 @@ static bool replaceCommonName(Ssl::X509_Pointer & cert, const char *cn) return false; // Remove the CN part: int loc = X509_NAME_get_index_by_NID(name, NID_commonName, -1); - X509_NAME_ENTRY *tmp = X509_NAME_get_entry(name, loc); - X509_NAME_delete_entry(name, loc); - X509_NAME_ENTRY_free(tmp); + if (loc >=0) { + X509_NAME_ENTRY *tmp = X509_NAME_get_entry(name, loc); + X509_NAME_delete_entry(name, loc); + X509_NAME_ENTRY_free(tmp); + } // Add a new CN return X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_ASC, (unsigned char *)cn, -1, -1, 0); } +const char *Ssl::CertSignAlgorithmStr[] = { + "signTrusted", + "signUntrusted", + "signSelf", + NULL +}; + const char *Ssl::CertAdaptAlgorithmStr[] = { "setValidAfter", "setValidBefore", @@ -280,23 +183,28 @@ const char *Ssl::CertAdaptAlgorithmStr[] = { NULL }; -static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & caCert, Ssl::X509_Pointer const &certToMimic, Ssl::CrtdMessage::BodyParams const &exceptions) +Ssl::CertificateProperties::CertificateProperties(): serial(NULL), + setValidAfter(false), + setValidBefore(false), + setCommonName(false), + signAlgorithm(Ssl::algSignEnd) +{} + +static bool buildCertificate(Ssl::X509_Pointer & cert, Ssl::CertificateProperties const &properties) { // not an Ssl::X509_NAME_Pointer because X509_REQ_get_subject_name() // returns a pointer to the existing subject name. Nothing to clean here. - X509_NAME *name = X509_get_subject_name(certToMimic.get()); - if (!name) - return false; - // X509_set_subject_name will call X509_dup for name - X509_set_subject_name(cert.get(), name); + if (properties.mimicCert.get()) { + X509_NAME *name = X509_get_subject_name(properties.mimicCert.get()); + if (!name) + return false; + // X509_set_subject_name will call X509_dup for name + X509_set_subject_name(cert.get(), name); + } - Ssl::CrtdMessage::BodyParams::const_iterator it; - it = exceptions.find(sslCertAdaptAlgoritm(Ssl::algSetCommonName)); - if (it != exceptions.end()) { - // In this case the CN of the certificate given by the exceptions - // and should be replaced - const char *cn = it->second.c_str(); - if (cn && !replaceCommonName(cert, cn)) + if (properties.setCommonName || !properties.mimicCert.get()) { + // In this case the CN of the certificate given by the user + if (!replaceCommonName(cert, properties.commonName.c_str())) return false; } @@ -306,11 +214,10 @@ static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & // Currently there is not any way in openssl tollkit to compare two ASN1_TIME // objects. ASN1_TIME *aTime = NULL; - it = exceptions.find(sslCertAdaptAlgoritm(Ssl::algSetValidBefore)); - if (it == exceptions.end() || strcasecmp(it->second.c_str(), "on") != 0) - aTime = X509_get_notBefore(certToMimic.get()); - if (!aTime) - aTime = X509_get_notBefore(caCert.get()); + if (!properties.setValidBefore && properties.mimicCert.get()) + aTime = X509_get_notBefore(properties.mimicCert.get()); + if (!aTime && properties.signWithX509.get()) + aTime = X509_get_notBefore(properties.signWithX509.get()); if (aTime) { if (!X509_set_notBefore(cert.get(), aTime)) @@ -320,37 +227,37 @@ static bool mimicCertificate(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & return false; aTime = NULL; - it = exceptions.find(sslCertAdaptAlgoritm(Ssl::algSetValidAfter)); - if (it == exceptions.end() || strcasecmp(it->second.c_str(), "on") != 0) - aTime = X509_get_notAfter(certToMimic.get()); - if (!aTime) - aTime = X509_get_notAfter(caCert.get()); + if (!properties.setValidAfter && properties.mimicCert.get()) + aTime = X509_get_notAfter(properties.mimicCert.get()); + if (!aTime && properties.signWithX509.get()) + aTime = X509_get_notAfter(properties.signWithX509.get()); if (aTime) { if (!X509_set_notAfter(cert.get(), aTime)) return NULL; } else if (!X509_gmtime_adj(X509_get_notAfter(cert.get()), 60*60*24*356*3)) return NULL; - unsigned char *alStr; - int alLen; - alStr = X509_alias_get0(certToMimic.get(), &alLen); - if (alStr) { - X509_alias_set1(cert.get(), alStr, alLen); - } - // Add subjectAltName extension used to support multiple hostnames with one certificate - int pos=X509_get_ext_by_NID (certToMimic.get(), OBJ_sn2nid("subjectAltName"), -1); - X509_EXTENSION *ext=X509_get_ext(certToMimic.get(), pos); - if (ext) - X509_add_ext(cert.get(), ext, -1); + if (properties.mimicCert.get()) { + unsigned char *alStr; + int alLen; + alStr = X509_alias_get0(properties.mimicCert.get(), &alLen); + if (alStr) { + X509_alias_set1(cert.get(), alStr, alLen); + } + + // Add subjectAltName extension used to support multiple hostnames with one certificate + int pos=X509_get_ext_by_NID (properties.mimicCert.get(), OBJ_sn2nid("subjectAltName"), -1); + X509_EXTENSION *ext=X509_get_ext(properties.mimicCert.get(), pos); + if (ext) + X509_add_ext(cert.get(), ext, -1); + } return true; } -bool Ssl::generateSslCertificate(Ssl::X509_Pointer const &certToMimic, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkey, BIGNUM const * serial, Ssl::CrtdMessage::BodyParams const &mimicExceptions) +bool Ssl::generateSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP_PKEY_Pointer & pkeyToStore, Ssl::CertificateProperties const &properties) { - if (!certToMimic.get()) - return false; - + Ssl::EVP_PKEY_Pointer pkey; pkey.reset(createSslPrivateKey()); if (!pkey) return false; @@ -362,21 +269,25 @@ bool Ssl::generateSslCertificate(Ssl::X509_Pointer const &certToMimic, Ssl::X509 // Set pub key and serial given by the caller if (!X509_set_pubkey(cert.get(), pkey.get())) return false; - if (!setSerialNumber(X509_get_serialNumber(cert.get()), serial)) + if (!setSerialNumber(X509_get_serialNumber(cert.get()), properties.serial.get())) return false; - // inherit properties from certToMimic - if (!mimicCertificate(cert, signedX509, certToMimic, mimicExceptions)) + // Fill the certificate with the required properties + if (!buildCertificate(cert, properties)) return false; + int ret = 0; // Set issuer name, from CA or our subject name for self signed cert - if (!X509_set_issuer_name(cert.get(), signedX509.get() ? X509_get_subject_name(signedX509.get()) : X509_get_subject_name(cert.get()))) + if (properties.signAlgorithm != Ssl::algSignSelf && properties.signWithX509.get()) + ret = X509_set_issuer_name(cert.get(), X509_get_subject_name(properties.signWithX509.get())); + else // Self signed certificate, set issuer to self + ret = X509_set_issuer_name(cert.get(), X509_get_subject_name(cert.get())); + if (!ret) return false; /*Now sign the request */ - int ret = 0; - if (signedPkey.get()) - ret = X509_sign(cert.get(), signedPkey.get(), EVP_sha1()); + if (properties.signAlgorithm != Ssl::algSignSelf && properties.signWithPkey.get()) + ret = X509_sign(cert.get(), properties.signWithPkey.get(), EVP_sha1()); else //else sign with self key (self signed request) ret = X509_sign(cert.get(), pkey.get(), EVP_sha1()); @@ -384,6 +295,7 @@ bool Ssl::generateSslCertificate(Ssl::X509_Pointer const &certToMimic, Ssl::X509 return false; certToStore.reset(cert.release()); + pkeyToStore.reset(pkey.release()); return true; } diff --git a/src/ssl/gadgets.h b/src/ssl/gadgets.h index e8cf163fe7..10d46188e0 100644 --- a/src/ssl/gadgets.h +++ b/src/ssl/gadgets.h @@ -26,6 +26,28 @@ namespace Ssl because they are used by ssl_crtd. */ +/** + \ingroup SslCrtdSslAPI + * Add SSL locking (a.k.a. reference counting) to TidyPointer + */ +template +class LockingPointer: public TidyPointer +{ +public: + typedef TidyPointer Parent; + + LockingPointer(T *t = NULL): Parent(t) { + } + + void resetAndLock(T *t) { + if (t != this->get()) { + reset(t); + if (t) + CRYPTO_add(&t->references, 1, lock); + } + } +}; + // Macro to be used to define the C++ equivalent function of an extern "C" // function. The C++ function suffixed with the _cpp extension #define CtoCpp1(function, argument) \ @@ -38,13 +60,13 @@ namespace Ssl * TidyPointer typedefs for common SSL objects */ CtoCpp1(X509_free, X509 *) -typedef TidyPointer X509_Pointer; +typedef LockingPointer X509_Pointer; CtoCpp1(sk_X509_free, STACK_OF(X509) *) typedef TidyPointer X509_STACK_Pointer; CtoCpp1(EVP_PKEY_free, EVP_PKEY *) -typedef TidyPointer EVP_PKEY_Pointer; +typedef LockingPointer EVP_PKEY_Pointer; CtoCpp1(BN_free, BIGNUM *) typedef TidyPointer BIGNUM_Pointer; @@ -80,12 +102,6 @@ typedef TidyPointer SSL_Pointer; */ EVP_PKEY * createSslPrivateKey(); -/** - \ingroup SslCrtdSslAPI - * Create request on certificate for a host. - */ -X509_REQ * createNewX509Request(EVP_PKEY_Pointer const & pkey, const char * hostname); - /** \ingroup SslCrtdSslAPI * Write private key and SSL certificate to memory. @@ -116,27 +132,49 @@ bool readCertAndPrivateKeyFromMemory(X509_Pointer & cert, EVP_PKEY_Pointer & pke */ bool readCertFromMemory(X509_Pointer & cert, char const * bufferToRead); +/** + \ingroup SslCrtdSslAPI + * Supported certificate signing algorithms + */ +enum CertSignAlgorithm {algSignTrusted = 0, algSignUntrusted, algSignSelf, algSignEnd}; + /** \ingroup SslCrtdSslAPI - * Sign SSL request. - * \param x509 if this param equals NULL, returning certificate will be selfsigned. - * \return X509 Signed certificate. + * Short names for certificate signing algorithms */ -X509 * signRequest(X509_REQ_Pointer const & request, X509_Pointer const & x509, EVP_PKEY_Pointer const & pkey, ASN1_TIME * timeNotAfter, BIGNUM const * serial); + +extern const char *CertSignAlgorithmStr[]; /** \ingroup SslCrtdSslAPI - * Decide on the kind of certificate and generate a CA- or self-signed one. - * Return generated certificate and private key in resultX509 and resultPkey - * variables. + * Return the short name of the signing algorithm "sg" + */ +inline const char *certSignAlgorithm(int sg) +{ + if (sg >=0 && sg < Ssl::algSignEnd) + return Ssl::CertSignAlgorithmStr[sg]; + + return NULL; +} + +/** + \ingroup SslCrtdSslAPI + * Return the id of the signing algorithm "sg" */ -bool generateSslCertificateAndPrivateKey(char const *host, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const* serial); +inline CertSignAlgorithm certSignAlgorithmId(const char *sg) +{ + for (int i = 0; i < algSignEnd && Ssl::CertSignAlgorithmStr[i] != NULL; i++) + if (strcmp(Ssl::CertSignAlgorithmStr[i], sg) == 0) + return (CertSignAlgorithm)i; + + return algSignEnd; +} /** \ingroup SslCrtdSslAPI * Supported certificate adaptation algorithms */ -enum CertAdaptAlgorithm {algSetValidAfter = 0, algSetValidBefore, algSetCommonName, algEnd}; +enum CertAdaptAlgorithm {algSetValidAfter = 0, algSetValidBefore, algSetCommonName, algSetEnd}; /** \ingroup SslCrtdSslAPI @@ -150,12 +188,33 @@ extern const char *CertAdaptAlgorithmStr[]; */ inline const char *sslCertAdaptAlgoritm(int alg) { - if (alg >=0 && alg < Ssl::algEnd) + if (alg >=0 && alg < Ssl::algSetEnd) return Ssl::CertAdaptAlgorithmStr[alg]; return NULL; } +/** + \ingroup SslCrtdSslAPI + * Simple struct to pass certificate generation parameters to generateSslCertificate function. + */ +class CertificateProperties { +public: + CertificateProperties(); + X509_Pointer mimicCert; ///< Certificate to mimic + X509_Pointer signWithX509; ///< Certificate to sign the generated request + EVP_PKEY_Pointer signWithPkey; ///< The key of the signing certificate + BIGNUM_Pointer serial; ///< Use this serial for generated certificate + bool setValidAfter; ///< Do not mimic "Not Valid After" field + bool setValidBefore; ///< Do not mimic "Not Valid Before" field + bool setCommonName; ///< Replace the CN field of the mimicing subject with the given + std::string commonName; ///< A CN to use for the generated certificate + CertSignAlgorithm signAlgorithm; ///< The signing algorithm to use +private: + CertificateProperties(CertificateProperties &); + CertificateProperties &operator =(CertificateProperties const &); +}; + /** \ingroup SslCrtdSslAPI * Decide on the kind of certificate and generate a CA- or self-signed one. @@ -163,7 +222,7 @@ inline const char *sslCertAdaptAlgoritm(int alg) * Return generated certificate and private key in resultX509 and resultPkey * variables. */ -bool generateSslCertificate(X509_Pointer const &certToMimic, X509_Pointer const & signedX509, EVP_PKEY_Pointer const & signedPkey, X509_Pointer & cert, EVP_PKEY_Pointer & pkey, BIGNUM const * serial, CrtdMessage::BodyParams const & mimicExceptions); +bool generateSslCertificate(X509_Pointer & cert, EVP_PKEY_Pointer & pkey, CertificateProperties const &properties); /** \ingroup SslCrtdSslAPI diff --git a/src/ssl/ssl_crtd.cc b/src/ssl/ssl_crtd.cc index 6ba0194448..2c3ae0c2a5 100644 --- a/src/ssl/ssl_crtd.cc +++ b/src/ssl/ssl_crtd.cc @@ -213,54 +213,72 @@ static void usage() static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::string const & db_path, size_t max_db_size, size_t fs_block_size) { Ssl::CrtdMessage::BodyParams map; + Ssl::CertificateProperties certProperties; std::string body_part; request_message.parseBody(map, body_part); Ssl::CrtdMessage::BodyParams::iterator i = map.find(Ssl::CrtdMessage::param_host); if (i == map.end()) throw std::runtime_error("Cannot find \"" + Ssl::CrtdMessage::param_host + "\" parameter in request message."); - std::string host = i->second; + certProperties.commonName = i->second; Ssl::CertificateDb db(db_path, max_db_size, fs_block_size); Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; - Ssl::X509_Pointer certToMimic; const char *s; std::string cert_subject; if ((s = strstr(body_part.c_str(), CERT_BEGIN_STR))) { s += strlen(CERT_BEGIN_STR); if ((s = strstr(s, CERT_BEGIN_STR))) { - Ssl::readCertFromMemory(certToMimic, s); - if (certToMimic.get()) { + Ssl::readCertFromMemory(certProperties.mimicCert, s); + if (certProperties.mimicCert.get()) { char buf[1024]; - cert_subject = X509_NAME_oneline(X509_get_subject_name(certToMimic.get()), buf, sizeof(buf)); + cert_subject = X509_NAME_oneline(X509_get_subject_name(certProperties.mimicCert.get()), buf, sizeof(buf)); } } } if (cert_subject.empty()) - cert_subject = "/CN=" + host; + cert_subject = "/CN=" + certProperties.commonName; i = map.find(Ssl::CrtdMessage::param_SetValidAfter); - if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) + if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) { cert_subject.append("+SetValidAfter=on"); + certProperties.setValidAfter = true; + } i = map.find(Ssl::CrtdMessage::param_SetValidBefore); - if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) + if (i != map.end() && strcasecmp(i->second.c_str(), "on") == 0) { cert_subject.append("+SetValidBefore=on"); + certProperties.setValidBefore = true; + } i = map.find(Ssl::CrtdMessage::param_SetCommonName); if (i != map.end()) { cert_subject.append("+SetCommonName="); cert_subject.append(i->second); + // use this as Common Name instead of the hostname + // defined with host or Common Name from mimic cert + certProperties.commonName = i->second; + certProperties.setCommonName = true; } + i = map.find(Ssl::CrtdMessage::param_Sign); + if (i != map.end()) { + cert_subject.append("+Sign="); + cert_subject.append(i->second); + if ((certProperties.signAlgorithm = Ssl::certSignAlgorithmId(i->second.c_str())) == Ssl::algSignEnd) + throw std::runtime_error("Wrong signing algoritm:" + i->second); + } + else + certProperties.signAlgorithm = Ssl::algSignTrusted; + db.find(cert_subject, cert, pkey); - if (cert.get() && certToMimic.get()) { - if (!Ssl::ssl_match_certificates(cert.get(), certToMimic.get())) { + if (cert.get() && certProperties.mimicCert.get()) { + if (!Ssl::ssl_match_certificates(cert.get(), certProperties.mimicCert.get())) { // The certificate changed (renewed or other reason). // Generete a new one with the updated fields. cert.reset(NULL); @@ -269,18 +287,15 @@ static bool proccessNewRequest(Ssl::CrtdMessage const & request_message, std::st } if (!cert || !pkey) { - Ssl::X509_Pointer certToSign; - Ssl::EVP_PKEY_Pointer pkeyToSign; - Ssl::readCertAndPrivateKeyFromMemory(certToSign, pkeyToSign, body_part.c_str()); - - Ssl::BIGNUM_Pointer serial(db.getCurrentSerialNumber()); - - if (certToMimic.get()) { - Ssl::generateSslCertificate(certToMimic, certToSign, pkeyToSign, cert, pkey, serial.get(), map); - } - else - if (!Ssl::generateSslCertificateAndPrivateKey(host.c_str(), certToSign, pkeyToSign, cert, pkey, serial.get())) - throw std::runtime_error("Cannot create ssl certificate or private key."); + if (certProperties.signAlgorithm != Ssl::algSignSelf) { + if (!Ssl::readCertAndPrivateKeyFromMemory(certProperties.signWithX509, certProperties.signWithPkey, body_part.c_str())) + throw std::runtime_error("Broken signing certificate!"); + } /*else Squid did not send certificate to sign the generated certificate*/ + + certProperties.serial.reset(db.getCurrentSerialNumber()); + + if (!Ssl::generateSslCertificate(cert, pkey, certProperties)) + throw std::runtime_error("Cannot create ssl certificate or private key."); if (!db.addCertAndPrivateKey(cert, pkey, cert_subject) && db.IsEnabledDiskStore()) throw std::runtime_error("Cannot add certificate to db."); diff --git a/src/ssl/support.cc b/src/ssl/support.cc index c6e7f8d589..a41615bff4 100644 --- a/src/ssl/support.cc +++ b/src/ssl/support.cc @@ -46,6 +46,9 @@ #include "ssl/support.h" #include "ssl/gadgets.h" +Ssl::X509_Pointer Ssl::SquidCaCert; +Ssl::EVP_PKEY_Pointer Ssl::SquidCaCertKey; + /** \defgroup ServerProtocolSSLInternal Server-Side SSL Internals \ingroup ServerProtocolSSLAPI @@ -649,6 +652,13 @@ ssl_initialize(void) } + // Generate the self-signed Ssl::SquidCaCert, using the "SquidLocalCa" as CN + Ssl::CertificateProperties certProperties; + certProperties.commonName = "Squid CA for Untrusted Certificates"; + certProperties.signAlgorithm = Ssl::algSignSelf; + bool ret = Ssl::generateSslCertificate(Ssl::SquidCaCert, Ssl::SquidCaCertKey, certProperties); + assert(ret); + ssl_ex_index_server = SSL_get_ex_new_index(0, (void *) "server", NULL, NULL, NULL); ssl_ctx_ex_index_dont_verify_domain = SSL_CTX_get_ex_new_index(0, (void *) "dont_verify_domain", NULL, NULL, NULL); ssl_ex_index_cert_error_check = SSL_get_ex_new_index(0, (void *) "cert_error_check", NULL, &ssl_dupAclChecklist, &ssl_freeAclChecklist); @@ -1260,17 +1270,12 @@ SSL_CTX * Ssl::generateSslContextUsingPkeyAndCertFromMemory(const char * data) return createSSLContext(cert, pkey); } -SSL_CTX * Ssl::generateSslContext(char const *host, Ssl::X509_Pointer const & mimicCert, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, Ssl::CrtdMessage::BodyParams const &mimicExceptions) +SSL_CTX * Ssl::generateSslContext(CertificateProperties const &properties) { Ssl::X509_Pointer cert; Ssl::EVP_PKEY_Pointer pkey; - if (mimicCert .get()) { - if (!generateSslCertificate(mimicCert, signedX509, signedPkey, cert, pkey, NULL, mimicExceptions)) - return NULL; - } - else if (!generateSslCertificateAndPrivateKey(host, signedX509, signedPkey, cert, pkey, NULL)) { + if (!generateSslCertificate(cert, pkey, properties)) return NULL; - } if (!cert) return NULL; diff --git a/src/ssl/support.h b/src/ssl/support.h index 0ba49c1376..a4f571e140 100644 --- a/src/ssl/support.h +++ b/src/ssl/support.h @@ -109,11 +109,25 @@ const char *sslGetUserCertificateChainPEM(SSL *ssl); namespace Ssl { + +/** + \ingroup ServerProtocolSSLAPI + * A temporary self-signed certificate generated on squid start up, to be + * used to sign the generated untrusted certificates. +*/ +extern X509_Pointer SquidCaCert; + +/** + \ingroup ServerProtocolSSLAPI + * The key of the SquidCaCert certificate. +*/ +extern EVP_PKEY_Pointer SquidCaCertKey; + /** \ingroup ServerProtocolSSLAPI * Decide on the kind of certificate and generate a CA- or self-signed one */ - SSL_CTX *generateSslContext(char const *host, Ssl::X509_Pointer const & mimicCert, Ssl::X509_Pointer const & signedX509, Ssl::EVP_PKEY_Pointer const & signedPkey, CrtdMessage::BodyParams const & mimicExceptions); +SSL_CTX * generateSslContext(CertificateProperties const &properties); /** \ingroup ServerProtocolSSLAPI diff --git a/src/structs.h b/src/structs.h index 5e7525fc28..6aaadf29b2 100644 --- a/src/structs.h +++ b/src/structs.h @@ -629,6 +629,7 @@ struct SquidConfig { char *flags; acl_access *cert_error; SSL_CTX *sslContext; + sslproxy_cert_sign *cert_sign; sslproxy_cert_adapt *cert_adapt; } ssl_client; #endif @@ -1303,6 +1304,12 @@ struct _store_rebuild_data { }; #if USE_SSL +struct _sslproxy_cert_sign { + int alg; + ACLList *aclList; + sslproxy_cert_sign *next; +}; + struct _sslproxy_cert_adapt { int alg; char *param; diff --git a/src/typedefs.h b/src/typedefs.h index ac61115715..0129207d77 100644 --- a/src/typedefs.h +++ b/src/typedefs.h @@ -115,6 +115,8 @@ typedef struct _link_list link_list; typedef struct _customlog customlog; #if USE_SSL +typedef struct _sslproxy_cert_sign sslproxy_cert_sign; + typedef struct _sslproxy_cert_adapt sslproxy_cert_adapt; #endif