[squid-users] HTTPS Filtering by Certificate Subject Name

James Harper james at ejbdigital.com.au
Sat Oct 18 07:22:14 UTC 2014


> 
> It looks like this question has come up before, but I'm hoping to get some
> further details on it.
> 
> I've used a couple of firewalls (Watchguard & Fortigate) that allow me to do a
> level of HTTPS site filtering without decryption. I believe that it works by
> requesting and examining the certificate sent from the remote server. If the
> subject name or subject alternate names on the certificate match a whitelist
> of domains that we have specified then access to the site is allowed. As far as
> I know, it does not require decrypting the SSL connection and I'm positive
> that it doesn't return self generated certificates.
> 
> It would not be very effective for someone trying to use Squid for blocking
> end users access to every site on the Internet. But, it works great for our use
> case where we want to allow our servers to only access a handful of sites.
> 
> From everything I've read, it looks like the only option is for Squid to decrypt
> the connection. Is there a particular reason why this feature could not be
> implemented in Squid if it's available in these other devices? Or if it is
> available, could I get some direction.
> 

I assume you are doing this for transparent proxying, otherwise you already have the hostname the user is trying to access, which is better than the cert name.

You could wait for this http://wiki.squid-cache.org/Features/SslPeekAndSplice which might eventually be able to do what you want.

In the meantime, you could try using an external acl. I didn't have a lot of luck... it works but has some limitations. Basically I threw together some php (see end of email), then used the following configuration:

# actual cert doesn't matter as it isn't used, but squid insists on it being there
https_port 0.0.0.0:33129 intercept name=transparentssl cert=/etc/squid/dummy.pem ssl-bump
...
ssl_bump none all
...
external_acl_type check_ssl %DST %PORT /usr/lib/squid3/check_ssl_cert_name
...
acl ssl_bad external check_ssl facebook.com
...
http_access deny transproxyssl ssl_bad
deny_info TCP_RESET transproxyssl

That's blacklist, not whitelist, but easy enough to invert. The problem I had is that I couldn't find a way to specify multiple 'acl ssl_bad external ...' lines, so you'd need a different acl + http_access for every certificate name you wanted to check against.

You'll find cases that will cause you trouble though, eg the certificate for "google.com" is also for "youtube.com", so you can't allow one without allowing the other.

About the code - I basically just threw it together using a few examples of ssl cert identification that google found for me. I don't sanitise any input. It almost certainly leaks memory and doesn't handle error conditions well, or at all. And the actual comparison to the value being tested is very basic. And I make the assumption that the certificate has a san, and that the cn is in the san list, which isn't always correct. Hopefully you will derive some inspiration from it though. One additional thing it could do is log the cn to the logfile - append "log=$cn" to the OK or ERR output, then use %ea (I think) in the squid log directive to log that value. Also PHP is probably a poor choice of language - I don't really know PHP it just happened to be the language my google search returned the best examples in.

I think squid caches the result of the external acl so performance shouldn't be too bad. If squid didn't cache the result you'd probably need to build some caching yourself otherwise it would do a callout every time.

James

#!/usr/bin/php
<?
stream_set_timeout(STDIN, -1);
while (!feof(STDIN)) {
  $line = trim(fgets(STDIN));
  if (substr_count($line, ' ') < 1) {
    continue;
  }
  $params = explode(' ', $line);
  $host = $params[0];
  $port = $params[1];
  $test = $params[2];

  $g = stream_context_create (array("ssl" => array("capture_peer_cert" => true)));
  $r = stream_socket_client("ssl://$host:$port", $errno, $errstr, 30, STREAM_CLIENT_CONNECT, $g);
  $cont = stream_context_get_params($r);
  $cert = openssl_x509_parse($cont["options"]["ssl"]["peer_certificate"]);
  $cn = $cert["subject"]["CN"];
  $san = $cert["extensions"]["subjectAltName"];
  $match = false;
  foreach (explode(', ', $san) as $name) {
    if (substr($name, -strlen($test)) == $test) {
      $match = true;
      break;
    }
  }
  if ($match) {
    fwrite(STDOUT, "OK\n");
  } else {
    fwrite(STDOUT, "ERR\n");
  }
}
?>



More information about the squid-users mailing list