[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [bridgedb/master] Implement cert-chain and hostname checking OpenSSL.SSL.Context factory.
commit c171a250c1dfea890f5e6f965361e0829838264e
Author: Isis Lovecruft <isis@xxxxxxxxxxxxxx>
Date: Tue Mar 4 05:21:48 2014 +0000
Implement cert-chain and hostname checking OpenSSL.SSL.Context factory.
* ADD bridgedb.crypto.SSLVerifyingContextFactory class, which verifies
certificate chains and checks certificate hostnames for a requested
resource.
---
lib/bridgedb/crypto.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 97 insertions(+), 1 deletion(-)
diff --git a/lib/bridgedb/crypto.py b/lib/bridgedb/crypto.py
index 6c0101b..7f6d597 100644
--- a/lib/bridgedb/crypto.py
+++ b/lib/bridgedb/crypto.py
@@ -33,12 +33,16 @@ import hashlib
import hmac
import logging
import os
+import re
+import urllib
-import OpenSSL.rand
+import OpenSSL
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
+from twisted.internet import ssl
+
#: The hash digest to use for HMACs.
DIGESTMOD = hashlib.sha1
@@ -189,3 +193,95 @@ def getHMACFunc(key, hex=True):
else:
return h_tmp.digest()
return hmac_fn
+
+
+class SSLVerifyingContextFactory(ssl.CertificateOptions):
+ """``OpenSSL.SSL.Context`` factory which does full certificate-chain and
+ hostname verfication.
+ """
+ isClient = True
+
+ def __init__(self, url, **kwargs):
+ """Create a client-side verifying SSL Context factory.
+
+ To pass acceptable certificates for a server which does
+ client-authentication checks: initialise with a ``caCerts=[]`` keyword
+ argument, which should be a list of ``OpenSSL.crypto.X509`` instances
+ (one for each peer certificate to add to the store), and set
+ ``SSLVerifyingContextFactory.isClient=False``.
+
+ :param str url: The URL being requested by an
+ :api:`twisted.web.client.Agent`.
+ :param bool isClient: True if we're being used in a client
+ implementation; False if we're a server.
+ """
+ self.hostname = self.getHostnameFromURL(url)
+
+ # ``verify`` here refers to server-side verification of certificates
+ # presented by a client:
+ self.verify = False if self.isClient else True
+ super(SSLVerifyingContextFactory, self).__init__(verify=self.verify,
+ fixBrokenPeers=True,
+ **kwargs)
+
+ def getContext(self, hostname=None, port=None):
+ """Retrieve a configured ``OpenSSL.SSL.Context``.
+
+ Any certificates in the ``caCerts`` list given during initialisation
+ are added to the ``Context``'s certificate store.
+
+ The **hostname** and **port** arguments seem unused, but they are
+ required due to some Twisted and pyOpenSSL internals. See
+ :api:`twisted.web.client.Agent._wrapContextFactory`.
+
+ :rtype: ``OpenSSL.SSL.Context``
+ :returns: An SSL Context which verifies certificates.
+ """
+ ctx = super(SSLVerifyingContextFactory, self).getContext()
+ store = ctx.get_cert_store()
+ verifyOptions = OpenSSL.SSL.VERIFY_PEER
+ ctx.set_verify(verifyOptions, self.verifyHostname)
+ return ctx
+
+ def getHostnameFromURL(self, url):
+ """Parse the hostname from the originally requested URL.
+
+ :param str url: The URL being requested by an
+ :api:`twisted.web.client.Agent`.
+ :rtype: str
+ :returns: The full hostname (including any subdomains).
+ """
+ hostname = urllib.splithost(urllib.splittype(url)[1])[0]
+ logging.debug("Parsed hostname %r for cert CN matching." % hostname)
+ return hostname
+
+ def verifyHostname(self, connection, x509, errnum, depth, okay):
+ """Callback method for additional SSL certificate validation.
+
+ If the certificate is signed by a valid CA, and the chain is valid,
+ verify that the level 0 certificate has a subject common name which is
+ valid for the hostname of the originally requested URL.
+
+ :param connection: An ``OpenSSL.SSL.Connection``.
+ :param x509: An ``OpenSSL.crypto.X509`` object.
+ :param errnum: A pyOpenSSL error number. See that project's docs.
+ :param depth: The depth which the current certificate is at in the
+ certificate chain.
+ :param bool okay: True if all the pyOpenSSL default checks on the
+ certificate passed. False otherwise.
+ """
+ commonName = x509.get_subject().commonName
+ logging.debug("Received cert at level %d: '%s'" % (depth, commonName))
+
+ # We only want to verify that the hostname matches for the level 0
+ # certificate:
+ if okay and (depth == 0):
+ cn = commonName.replace('*', '.*')
+ hostnamesMatch = re.search(cn, self.hostname)
+ if not hostnamesMatch:
+ logging.warn("Invalid certificate subject CN for '%s': '%s'"
+ % (self.hostname, commonName))
+ return False
+ logging.debug("Valid certificate subject CN for '%s': '%s'"
+ % (self.hostname, commonName))
+ return True
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits