]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/ssl/gadgets.cc
SourceFormat Enforcement
[thirdparty/squid.git] / src / ssl / gadgets.cc
index 15bedf67f9403afcb0d8c377ec9821fb696a1a53..5729ad323fbc09a7122e5b2a60b2622531e10b91 100644 (file)
@@ -1,9 +1,14 @@
 /*
- * $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
@@ -92,7 +97,7 @@ bool Ssl::appendCertToMemory(Ssl::X509_Pointer const & cert, std::string & buffe
     if (!ptr)
         return false;
 
-    if (!bufferToWrite.empty()) 
+    if (!bufferToWrite.empty())
         bufferToWrite.append(" "); // add a space...
 
     bufferToWrite.append(ptr, len);
@@ -155,26 +160,33 @@ bool Ssl::readCertFromMemory(X509_Pointer & cert, char const * bufferToRead)
 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;
@@ -188,12 +200,12 @@ static bool replaceCommonName(Ssl::X509_Pointer & cert, std::string const &cn)
 
     // 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
 };
@@ -206,10 +218,11 @@ const char *Ssl::CertAdaptAlgorithmStr[] = {
 };
 
 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
@@ -243,17 +256,97 @@ 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);
         }
     }
@@ -266,10 +359,10 @@ static bool buildCertificate(Ssl::X509_Pointer & cert, Ssl::CertificatePropertie
         (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())
@@ -280,8 +373,7 @@ static bool buildCertificate(Ssl::X509_Pointer & cert, Ssl::CertificatePropertie
     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;
@@ -304,20 +396,24 @@ static bool buildCertificate(Ssl::X509_Pointer & cert, Ssl::CertificatePropertie
             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;
@@ -358,11 +454,13 @@ static bool generateFakeSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP
     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;
@@ -374,14 +472,14 @@ static bool generateFakeSslCertificate(Ssl::X509_Pointer & certToStore, Ssl::EVP
 
 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
@@ -423,8 +521,8 @@ static BIGNUM *x509Pubkeydigest(Ssl::X509_Pointer const & cert)
     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;
@@ -433,7 +531,7 @@ static bool createSerial(Ssl::BIGNUM_Pointer &serial, Ssl::CertificateProperties
     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))
@@ -515,7 +613,7 @@ bool Ssl::sslDateIsInTheFuture(char const * date)
 /// 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]
 
@@ -534,8 +632,7 @@ static bool asn1timeToGeneralizedTimeStr(ASN1_TIME *aTime, char *buf, int bufLen
             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);
@@ -550,7 +647,7 @@ static int asn1time_cmp(ASN1_TIME *asnTime1, ASN1_TIME *asnTime2)
         return -1;
     if (!asn1timeToGeneralizedTimeStr(asnTime2, strTime2, sizeof(strTime2)))
         return -1;
-    
+
     return strcmp(strTime1, strTime2);
 }
 
@@ -564,7 +661,7 @@ bool Ssl::certificateMatchesProperties(X509 *cert, CertificateProperties const &
         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)
@@ -575,23 +672,17 @@ bool Ssl::certificateMatchesProperties(X509 *cert, CertificateProperties const &
         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) {
@@ -599,16 +690,19 @@ bool Ssl::certificateMatchesProperties(X509 *cert, CertificateProperties const &
         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);
@@ -617,16 +711,44 @@ bool Ssl::certificateMatchesProperties(X509 *cert, CertificateProperties const &
     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);
+}
+