8
8
import traceback
9
9
import warnings
10
10
11
+ from binascii import unhexlify
11
12
from collections import defaultdict
13
+ from hashlib import md5 , sha1 , sha256
12
14
from itertools import chain
13
15
from math import ceil
14
16
15
17
from . import hdrs
16
18
from .client import ClientRequest
17
19
from .errors import ServerDisconnectedError
18
20
from .errors import HttpProxyError , ProxyConnectionError
19
- from .errors import ClientOSError , ClientTimeoutError
21
+ from .errors import ClientOSError , ClientTimeoutError , FingerprintMismatch
20
22
from .helpers import BasicAuth
21
23
22
24
25
27
PY_34 = sys .version_info >= (3 , 4 )
26
28
PY_343 = sys .version_info >= (3 , 4 , 3 )
27
29
30
+ HASHFUNC_BY_DIGESTLEN = {
31
+ 16 : md5 ,
32
+ 20 : sha1 ,
33
+ 32 : sha256 ,
34
+ }
35
+
28
36
29
37
class Connection (object ):
30
38
@@ -347,13 +355,15 @@ class TCPConnector(BaseConnector):
347
355
"""TCP connector.
348
356
349
357
:param bool verify_ssl: Set to True to check ssl certifications.
358
+ :param str verify_fingerprint: Set to a string of hex digits to
359
+ verify the ssl cert fingerprint matches.
350
360
:param bool resolve: Set to True to do DNS lookup for host name.
351
361
:param family: socket address family
352
362
:param args: see :class:`BaseConnector`
353
363
:param kwargs: see :class:`BaseConnector`
354
364
"""
355
365
356
- def __init__ (self , * , verify_ssl = True ,
366
+ def __init__ (self , * , verify_ssl = True , verify_fingerprint = None ,
357
367
resolve = False , family = socket .AF_INET , ssl_context = None ,
358
368
** kwargs ):
359
369
super ().__init__ (** kwargs )
@@ -364,6 +374,14 @@ def __init__(self, *, verify_ssl=True,
364
374
"verify_ssl=False or specify ssl_context, not both." )
365
375
366
376
self ._verify_ssl = verify_ssl
377
+ if verify_fingerprint :
378
+ verify_fingerprint = verify_fingerprint .replace (':' , '' ).lower ()
379
+ digestlen , odd = divmod (len (verify_fingerprint ), 2 )
380
+ if odd or digestlen not in HASHFUNC_BY_DIGESTLEN :
381
+ raise ValueError ('Fingerprint is of invalid length.' )
382
+ self ._hashfunc = HASHFUNC_BY_DIGESTLEN [digestlen ]
383
+ self ._fingerprint_bytes = unhexlify (verify_fingerprint )
384
+ self ._verify_fingerprint = verify_fingerprint
367
385
self ._ssl_context = ssl_context
368
386
self ._family = family
369
387
self ._resolve = resolve
@@ -374,6 +392,11 @@ def verify_ssl(self):
374
392
"""Do check for ssl certifications?"""
375
393
return self ._verify_ssl
376
394
395
+ @property
396
+ def verify_fingerprint (self ):
397
+ """Verify ssl cert fingerprint matches?"""
398
+ return self ._verify_fingerprint
399
+
377
400
@property
378
401
def ssl_context (self ):
379
402
"""SSLContext instance for https requests.
@@ -464,11 +487,18 @@ def _create_connection(self, req):
464
487
465
488
for hinfo in hosts :
466
489
try :
467
- return ( yield from self ._loop .create_connection (
490
+ conn = yield from self ._loop .create_connection (
468
491
self ._factory , hinfo ['host' ], hinfo ['port' ],
469
492
ssl = sslcontext , family = hinfo ['family' ],
470
493
proto = hinfo ['proto' ], flags = hinfo ['flags' ],
471
- server_hostname = hinfo ['hostname' ] if sslcontext else None ))
494
+ server_hostname = hinfo ['hostname' ] if sslcontext else None )
495
+ if self ._verify_fingerprint :
496
+ sock = conn [0 ]._sock
497
+ cert = sock .getpeercert (True )
498
+ digest = self ._hashfunc (cert ).digest ()
499
+ if digest != self ._fingerprint_bytes :
500
+ raise FingerprintMismatch
501
+ return conn
472
502
except OSError as e :
473
503
exc = e
474
504
else :
0 commit comments