Skip to content

Commit

Permalink
first pass at verify_fingerprint
Browse files Browse the repository at this point in the history
  • Loading branch information
requiredfield committed May 13, 2015
1 parent 3e60305 commit 4e66d78
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 4 deletions.
38 changes: 34 additions & 4 deletions aiohttp/connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
import traceback
import warnings

from binascii import unhexlify
from collections import defaultdict
from hashlib import md5, sha1, sha256
from itertools import chain
from math import ceil

from . import hdrs
from .client import ClientRequest
from .errors import ServerDisconnectedError
from .errors import HttpProxyError, ProxyConnectionError
from .errors import ClientOSError, ClientTimeoutError
from .errors import ClientOSError, ClientTimeoutError, FingerprintMismatch
from .helpers import BasicAuth


Expand All @@ -25,6 +27,12 @@
PY_34 = sys.version_info >= (3, 4)
PY_343 = sys.version_info >= (3, 4, 3)

HASHFUNC_BY_DIGESTLEN = {
16: md5,
20: sha1,
32: sha256,
}


class Connection(object):

Expand Down Expand Up @@ -347,13 +355,15 @@ class TCPConnector(BaseConnector):
"""TCP connector.
:param bool verify_ssl: Set to True to check ssl certifications.
:param str verify_fingerprint: Set to a string of hex digits to
verify the ssl cert fingerprint matches.
:param bool resolve: Set to True to do DNS lookup for host name.
:param family: socket address family
:param args: see :class:`BaseConnector`
:param kwargs: see :class:`BaseConnector`
"""

def __init__(self, *, verify_ssl=True,
def __init__(self, *, verify_ssl=True, verify_fingerprint=None,
resolve=False, family=socket.AF_INET, ssl_context=None,
**kwargs):
super().__init__(**kwargs)
Expand All @@ -364,6 +374,14 @@ def __init__(self, *, verify_ssl=True,
"verify_ssl=False or specify ssl_context, not both.")

self._verify_ssl = verify_ssl
if verify_fingerprint:
verify_fingerprint = verify_fingerprint.replace(':', '').lower()
digestlen, odd = divmod(len(verify_fingerprint), 2)
if odd or digestlen not in HASHFUNC_BY_DIGESTLEN:
raise ValueError('Fingerprint is of invalid length.')
self._hashfunc = HASHFUNC_BY_DIGESTLEN[digestlen]
self._fingerprint_bytes = unhexlify(verify_fingerprint)
self._verify_fingerprint = verify_fingerprint
self._ssl_context = ssl_context
self._family = family
self._resolve = resolve
Expand All @@ -374,6 +392,11 @@ def verify_ssl(self):
"""Do check for ssl certifications?"""
return self._verify_ssl

@property
def verify_fingerprint(self):
"""Verify ssl cert fingerprint matches?"""
return self._verify_fingerprint

@property
def ssl_context(self):
"""SSLContext instance for https requests.
Expand Down Expand Up @@ -464,11 +487,18 @@ def _create_connection(self, req):

for hinfo in hosts:
try:
return (yield from self._loop.create_connection(
conn = yield from self._loop.create_connection(
self._factory, hinfo['host'], hinfo['port'],
ssl=sslcontext, family=hinfo['family'],
proto=hinfo['proto'], flags=hinfo['flags'],
server_hostname=hinfo['hostname'] if sslcontext else None))
server_hostname=hinfo['hostname'] if sslcontext else None)
if self._verify_fingerprint:
sock = conn[0]._sock
cert = sock.getpeercert(True)
digest = self._hashfunc(cert).digest()
if digest != self._fingerprint_bytes:
raise FingerprintMismatch
return conn
except OSError as e:
exc = e
else:
Expand Down
4 changes: 4 additions & 0 deletions aiohttp/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,7 @@ class LineLimitExceededParserError(ParserError):
def __init__(self, msg, limit):
super().__init__(msg)
self.limit = limit


class FingerprintMismatch(Exception):
"""SSL certificate does not match expected fingerprint."""

0 comments on commit 4e66d78

Please sign in to comment.