4 Copyright (c) 2004-2005 Open Source Applications Foundation.
8 from M2Crypto import util, EVP
11 class SSLVerificationError(Exception):
14 class NoCertificate(SSLVerificationError):
17 class WrongCertificate(SSLVerificationError):
20 class WrongHost(SSLVerificationError):
21 def __init__(self, expectedHost, actualHost, fieldName='commonName'):
23 This exception will be raised if the certificate returned by the
24 peer was issued for a different host than we tried to connect to.
25 This could be due to a server misconfiguration or an active attack.
27 @param expectedHost: The name of the host we expected to find in the
29 @param actualHost: The name of the host we actually found in the
31 @param fieldName: The field name where we noticed the error. This
32 should be either 'commonName' or 'subjectAltName'.
34 if fieldName not in ('commonName', 'subjectAltName'):
35 raise ValueError('Unknown fieldName, should be either commonName or subjectAltName')
37 SSLVerificationError.__init__(self)
38 self.expectedHost = expectedHost
39 self.actualHost = actualHost
40 self.fieldName = fieldName
43 s = 'Peer certificate %s does not match host, expected %s, got %s' \
44 % (self.fieldName, self.expectedHost, self.actualHost)
45 if isinstance(s, unicode):
51 def __init__(self, host=None, peerCertHash=None, peerCertDigest='sha1'):
53 self.fingerprint = peerCertHash
54 self.digest = peerCertDigest
55 self.numericIpMatch = re.compile('^[0-9]+(\.[0-9]+)*$')
57 def __call__(self, peerCert, host=None):
59 raise NoCertificate('peer did not return certificate')
65 if self.digest not in ('sha1', 'md5'):
66 raise ValueError('unsupported digest "%s"' %(self.digest))
68 if (self.digest == 'sha1' and len(self.fingerprint) != 40) or \
69 (self.digest == 'md5' and len(self.fingerprint) != 32):
70 raise WrongCertificate('peer certificate fingerprint length does not match')
72 der = peerCert.as_der()
73 md = EVP.MessageDigest(self.digest)
76 if util.octx_to_num(digest) != int(self.fingerprint, 16):
77 raise WrongCertificate('peer certificate fingerprint does not match')
80 hostValidationPassed = False
82 # XXX subjectAltName might contain multiple fields
83 # subjectAltName=DNS:somehost
85 subjectAltName = peerCert.get_ext('subjectAltName').get_value()
86 if not self._match(self.host, subjectAltName, True):
87 raise WrongHost(expectedHost=self.host,
88 actualHost=subjectAltName,
89 fieldName='subjectAltName')
90 hostValidationPassed = True
97 ##-----by Soner, comment outed
99 ## if not hostValidationPassed:
101 ## commonName = peerCert.get_subject().CN
102 ## if not self._match(self.host, commonName):
103 ## raise WrongHost(expectedHost=self.host,
104 ## actualHost=commonName,
105 ## fieldName='commonName')
106 ## except AttributeError:
107 ## raise WrongCertificate('no commonName in peer certificate')
113 def _match(self, host, certHost, subjectAltName=False):
115 >>> check = Checker()
116 >>> check._match(host='my.example.com', certHost='DNS:my.example.com', subjectAltName=True)
118 >>> check._match(host='my.example.com', certHost='DNS:*.example.com', subjectAltName=True)
120 >>> check._match(host='my.example.com', certHost='DNS:m*.example.com', subjectAltName=True)
122 >>> check._match(host='my.example.com', certHost='DNS:m*ample.com', subjectAltName=True)
124 >>> check._match(host='my.example.com', certHost='my.example.com')
126 >>> check._match(host='my.example.com', certHost='*.example.com')
128 >>> check._match(host='my.example.com', certHost='m*.example.com')
130 >>> check._match(host='my.example.com', certHost='m*.EXAMPLE.com')
132 >>> check._match(host='my.example.com', certHost='m*ample.com')
134 >>> check._match(host='my.example.com', certHost='*.*.com')
136 >>> check._match(host='1.2.3.4', certHost='1.2.3.4')
138 >>> check._match(host='1.2.3.4', certHost='*.2.3.4')
140 >>> check._match(host='1234', certHost='1234')
143 # XXX See RFC 2818 and 3280 for matching rules, this is not
147 certHost = certHost.lower()
150 if certHost[:4] != 'dns:':
152 certHost = certHost[4:]
157 if certHost.count('*') > 1:
158 # Not sure about this, but being conservative
161 if self.numericIpMatch.match(host) or \
162 self.numericIpMatch.match(certHost.replace('*', '')):
163 # Not sure if * allowed in numeric IP, but think not.
166 if certHost.find('\\') > -1:
167 # Not sure about this, maybe some encoding might have these.
168 # But being conservative for now, because regex below relies
172 # Massage certHost so that it can be used in regex
173 certHost = certHost.replace('.', '\.')
174 certHost = certHost.replace('*', '[^\.]*')
175 if re.compile('^%s$' %(certHost)).match(host):
181 if __name__ == '__main__':