/*
- * $Id$
+ * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
*/
#include "squid.h"
#include "ssl/gadgets.h"
+
#if HAVE_OPENSSL_X509V3_H
#include <openssl/x509v3.h>
#endif
if (!ptr)
return false;
- if (!bufferToWrite.empty())
+ if (!bufferToWrite.empty())
bufferToWrite.append(" "); // add a space...
bufferToWrite.append(ptr, len);
static const size_t MaxCnLen = 64;
// Replace certs common name with the given
-static bool replaceCommonName(Ssl::X509_Pointer & cert, std::string const &cn)
+static bool replaceCommonName(Ssl::X509_Pointer & cert, std::string const &rawCn)
{
- std::string fixedCn;
+ std::string cn = rawCn;
+
if (cn.length() > MaxCnLen) {
// In the case the length od CN is more than the maximum supported size
// try to use the first upper level domain.
size_t pos = 0;
do {
pos = cn.find('.', pos + 1);
- } while(pos != std::string::npos && (cn.length() - pos + 2) > MaxCnLen);
+ } while (pos != std::string::npos && (cn.length() - pos + 2) > MaxCnLen);
// If no short domain found or this domain is a toplevel domain
// we failed to find a good cn name.
if (pos == std::string::npos || cn.find('.', pos + 1) == std::string::npos)
return false;
- fixedCn.append(1,'*');
+ std::string fixedCn(1, '*');
fixedCn.append(cn.c_str() + pos);
+ cn = fixedCn;
}
+ // Assume [] surround an IPv6 address and strip them because browsers such
+ // as Firefox, Chromium, and Safari prefer bare IPv6 addresses in CNs.
+ if (cn.length() > 2 && *cn.begin() == '[' && *cn.rbegin() == ']')
+ cn = cn.substr(1, cn.size()-2);
+
X509_NAME *name = X509_get_subject_name(cert.get());
if (!name)
return false;
// Add a new CN
return X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_ASC,
- (unsigned char *)(fixedCn.empty() ? cn.c_str() : fixedCn.c_str()), -1, -1, 0);
+ (unsigned char *)(cn.c_str()), -1, -1, 0);
}
const char *Ssl::CertSignAlgorithmStr[] = {
"signTrusted",
- "signUntrusted",
+ "signUntrusted",
"signSelf",
NULL
};
};
Ssl::CertificateProperties::CertificateProperties():
- setValidAfter(false),
- setValidBefore(false),
- setCommonName(false),
- signAlgorithm(Ssl::algSignEnd)
+ setValidAfter(false),
+ setValidBefore(false),
+ setCommonName(false),
+ signAlgorithm(Ssl::algSignEnd),
+ signHash(NULL)
{}
std::string & Ssl::CertificateProperties::dbKey() const
certKey.append(certSignAlgorithm(signAlgorithm));
}
+ if (signHash != NULL) {
+ certKey.append("+SignHash=", 10);
+ certKey.append(EVP_MD_name(signHash));
+ }
+
return certKey;
}
+/// Copy certificate extensions from cert to mimicCert.
+/// Returns the number of extensions copied.
+// Currently only extensions which are reported by the users that required are
+// mimicked. More safe to mimic extensions would be added here if users request
+// them.
+static int
+mimicExtensions(Ssl::X509_Pointer & cert, Ssl::X509_Pointer const & mimicCert)
+{
+ static int extensions[]= {
+ NID_key_usage,
+ NID_ext_key_usage,
+ NID_basic_constraints,
+ 0
+ };
+
+ // key usage bit names
+ enum {
+ DigitalSignature,
+ NonRepudiation,
+ KeyEncipherment, // NSS requires for RSA but not EC
+ DataEncipherment,
+ KeyAgreement,
+ KeyCertificateSign,
+ CRLSign,
+ EncipherOnly,
+ DecipherOnly
+ };
+
+ int mimicAlgo = OBJ_obj2nid(mimicCert.get()->cert_info->key->algor->algorithm);
+
+ int added = 0;
+ int nid;
+ for (int i = 0; (nid = extensions[i]) != 0; ++i) {
+ const int pos = X509_get_ext_by_NID(mimicCert.get(), nid, -1);
+ if (X509_EXTENSION *ext = X509_get_ext(mimicCert.get(), pos)) {
+ // Mimic extension exactly.
+ if (X509_add_ext(cert.get(), ext, -1))
+ ++added;
+ if ( nid == NID_key_usage && mimicAlgo != NID_rsaEncryption ) {
+ // NSS does not requre the KeyEncipherment flag on EC keys
+ // but it does require it for RSA keys. Since ssl-bump
+ // substitutes RSA keys for EC ones, we need to ensure that
+ // that the more stringent requirements are met.
+
+ const int p = X509_get_ext_by_NID(cert.get(), NID_key_usage, -1);
+ if ((ext = X509_get_ext(cert.get(), p)) != NULL) {
+ ASN1_BIT_STRING *keyusage = (ASN1_BIT_STRING *)X509V3_EXT_d2i(ext);
+ ASN1_BIT_STRING_set_bit(keyusage, KeyEncipherment, 1);
+
+ //Build the ASN1_OCTET_STRING
+ const X509V3_EXT_METHOD *method = X509V3_EXT_get(ext);
+ assert(method && method->it);
+ unsigned char *ext_der = NULL;
+ int ext_len = ASN1_item_i2d((ASN1_VALUE *)keyusage,
+ &ext_der,
+ (const ASN1_ITEM *)ASN1_ITEM_ptr(method->it));
+
+ ASN1_OCTET_STRING *ext_oct = M_ASN1_OCTET_STRING_new();
+ ext_oct->data = ext_der;
+ ext_oct->length = ext_len;
+ X509_EXTENSION_set_data(ext, ext_oct);
+
+ M_ASN1_OCTET_STRING_free(ext_oct);
+ ASN1_BIT_STRING_free(keyusage);
+ }
+ }
+ }
+ }
+
+ // We could also restrict mimicking of the CA extension to CA:FALSE
+ // because Squid does not generate valid fake CA certificates.
+
+ return added;
+}
+
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.
if (properties.mimicCert.get()) {
// Leave subject empty if we cannot extract it from true cert.
if (X509_NAME *name = X509_get_subject_name(properties.mimicCert.get())) {
- // X509_set_subject_name will call X509_dup for name
+ // X509_set_subject_name will call X509_dup for name
X509_set_subject_name(cert.get(), name);
}
}
(void)replaceCommonName(cert, properties.commonName);
}
- // We should get caCert notBefore and notAfter fields and do not allow
+ // We should get caCert notBefore and notAfter fields and do not allow
// notBefore/notAfter values from certToMimic before/after notBefore/notAfter
// fields from caCert.
- // Currently there is not any way in openssl tollkit to compare two ASN1_TIME
+ // Currently there is not any way in openssl tollkit to compare two ASN1_TIME
// objects.
ASN1_TIME *aTime = NULL;
if (!properties.setValidBefore && properties.mimicCert.get())
if (aTime) {
if (!X509_set_notBefore(cert.get(), aTime))
return false;
- }
- else if (!X509_gmtime_adj(X509_get_notBefore(cert.get()), (-2)*24*60*60))
+ } else if (!X509_gmtime_adj(X509_get_notBefore(cert.get()), (-2)*24*60*60))
return false;
aTime = NULL;
X509_alias_set1(cert.get(), alStr, alLen);
}
+ int addedExtensions = 0;
+
// Mimic subjectAltName unless we used a configured CN: browsers reject
// certificates with CN unrelated to subjectAltNames.
if (!properties.setCommonName) {
int pos=X509_get_ext_by_NID (properties.mimicCert.get(), OBJ_sn2nid("subjectAltName"), -1);
- X509_EXTENSION *ext=X509_get_ext(properties.mimicCert.get(), pos);
+ X509_EXTENSION *ext=X509_get_ext(properties.mimicCert.get(), pos);
if (ext) {
- X509_add_ext(cert.get(), ext, -1);
- /* According the RFC 5280 using extensions requires version 3
- certificate.
- Set version value to 2 for version 3 certificates.
- */
- X509_set_version(cert.get(), 2);
+ if (X509_add_ext(cert.get(), ext, -1))
+ ++addedExtensions;
}
}
+
+ addedExtensions += mimicExtensions(cert, properties.mimicCert);
+
+ // According to RFC 5280, using extensions requires v3 certificate.
+ if (addedExtensions)
+ X509_set_version(cert.get(), 2); // value 2 means v3
}
return true;
if (!ret)
return false;
+ const EVP_MD *hash = properties.signHash ? properties.signHash : EVP_get_digestbyname(SQUID_SSL_SIGN_HASH_IF_NONE);
+ assert(hash);
/*Now sign the request */
if (properties.signAlgorithm != Ssl::algSignSelf && properties.signWithPkey.get())
- ret = X509_sign(cert.get(), properties.signWithPkey.get(), EVP_sha1());
+ ret = X509_sign(cert.get(), properties.signWithPkey.get(), hash);
else //else sign with self key (self signed request)
- ret = X509_sign(cert.get(), pkey.get(), EVP_sha1());
+ ret = X509_sign(cert.get(), pkey.get(), hash);
if (!ret)
return false;
static BIGNUM *createCertSerial(unsigned char *md, unsigned int n)
{
-
+
assert(n == 20); //for sha1 n is 20 (for md5 n is 16)
BIGNUM *serial = NULL;
serial = BN_bin2bn(md, n, NULL);
// if the serial is "0" set it to '1'
- if (BN_is_zero(serial))
+ if (BN_is_zero(serial) == true)
BN_one(serial);
// serial size does not exceed 20 bytes
return createCertSerial(md, n);
}
-/// Generate a unique serial number based on a Ssl::CertificateProperties object
-/// for a new generated certificate
+/// Generate a unique serial number based on a Ssl::CertificateProperties object
+/// for a new generated certificate
static bool createSerial(Ssl::BIGNUM_Pointer &serial, Ssl::CertificateProperties const &properties)
{
Ssl::EVP_PKEY_Pointer fakePkey;
serial.reset(x509Pubkeydigest(properties.signWithX509));
if (!serial.get()) {
serial.reset(BN_new());
- BN_is_zero(serial.get());
+ BN_zero(serial.get());
}
if (!generateFakeSslCertificate(fakeCert, fakePkey, properties, serial))
/// Print the time represented by a ASN1_TIME struct to a string using GeneralizedTime format
static bool asn1timeToGeneralizedTimeStr(ASN1_TIME *aTime, char *buf, int bufLen)
{
- // ASN1_Time holds time to UTCTime or GeneralizedTime form.
+ // ASN1_Time holds time to UTCTime or GeneralizedTime form.
// UTCTime has the form YYMMDDHHMMSS[Z | [+|-]offset]
// GeneralizedTime has the form YYYYMMDDHHMMSS[Z | [+|-] offset]
buf[1] = '0';
}
str = buf +2;
- }
- else // if (aTime->type == V_ASN1_GENERALIZEDTIME)
+ } else // if (aTime->type == V_ASN1_GENERALIZEDTIME)
str = buf;
memcpy(str, aTime->data, aTime->length);
return -1;
if (!asn1timeToGeneralizedTimeStr(asnTime2, strTime2, sizeof(strTime2)))
return -1;
-
+
return strcmp(strTime1, strTime2);
}
if (X509_check_issued(properties.signWithX509.get(), cert) != X509_V_OK)
return false;
}
-
+
X509 *cert2 = properties.mimicCert.get();
// If there is not certificate to mimic stop here
if (!cert2)
X509_NAME *cert2_name = X509_get_subject_name(cert2);
if (X509_NAME_cmp(cert1_name, cert2_name) != 0)
return false;
- }
- /* else {
- if (properties.commonName != Ssl::CommonHostName(cert))
- return false;
- This function normaly called to verify a cached certificate matches the
- specifications given by properties parameter.
- The cached certificate retrieved from the cache using a key which has
- as part the properties.commonName. This is enough to assume that the
- cached cert has in its subject the properties.commonName as cn field.
- }
- */
-
+ } else if (properties.commonName != CommonHostName(cert))
+ return false;
+
if (!properties.setValidBefore) {
ASN1_TIME *aTime = X509_get_notBefore(cert);
ASN1_TIME *bTime = X509_get_notBefore(cert2);
if (asn1time_cmp(aTime, bTime) != 0)
return false;
+ } else if (X509_cmp_current_time(X509_get_notBefore(cert)) >= 0) {
+ // notBefore does not exist (=0) or it is in the future (>0)
+ return false;
}
if (!properties.setValidAfter) {
ASN1_TIME *bTime = X509_get_notAfter(cert2);
if (asn1time_cmp(aTime, bTime) != 0)
return false;
+ } else if (X509_cmp_current_time(X509_get_notAfter(cert)) <= 0) {
+ // notAfter does not exist (0) or it is in the past (<0)
+ return false;
}
-
+
char *alStr1;
int alLen;
alStr1 = (char *)X509_alias_get0(cert, &alLen);
char *alStr2 = (char *)X509_alias_get0(cert2, &alLen);
if ((!alStr1 && alStr2) || (alStr1 && !alStr2) ||
- (alStr1 && alStr2 && strcmp(alStr1, alStr2)) != 0)
+ (alStr1 && alStr2 && strcmp(alStr1, alStr2)) != 0)
return false;
-
+
// Compare subjectAltName extension
STACK_OF(GENERAL_NAME) * cert1_altnames;
cert1_altnames = (STACK_OF(GENERAL_NAME)*)X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
bool match = true;
if (cert1_altnames) {
int numalts = sk_GENERAL_NAME_num(cert1_altnames);
- for (int i = 0; match && i < numalts; i++) {
+ for (int i = 0; match && i < numalts; ++i) {
const GENERAL_NAME *aName = sk_GENERAL_NAME_value(cert1_altnames, i);
match = sk_GENERAL_NAME_find(cert2_altnames, aName);
}
- }
- else if (cert2_altnames)
+ } else if (cert2_altnames)
match = false;
-
+
sk_GENERAL_NAME_pop_free(cert1_altnames, GENERAL_NAME_free);
sk_GENERAL_NAME_pop_free(cert2_altnames, GENERAL_NAME_free);
return match;
}
+
+static const char *getSubjectEntry(X509 *x509, int nid)
+{
+ static char name[1024] = ""; // stores common name (CN)
+
+ if (!x509)
+ return NULL;
+
+ // TODO: What if the entry is a UTF8String? See X509_NAME_get_index_by_NID(3ssl).
+ const int nameLen = X509_NAME_get_text_by_NID(
+ X509_get_subject_name(x509),
+ nid, name, sizeof(name));
+
+ if (nameLen > 0)
+ return name;
+
+ return NULL;
+}
+
+const char *Ssl::CommonHostName(X509 *x509)
+{
+ return getSubjectEntry(x509, NID_commonName);
+}
+
+const char *Ssl::getOrganization(X509 *x509)
+{
+ return getSubjectEntry(x509, NID_organizationName);
+}
+