3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2014 Thomas Voegtlin
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from datetime import datetime
26 sys.exit("Error: pyasn1 does not seem to be installed. Try 'sudo pip install pyasn1'")
31 sys.exit("Error: tlslite does not seem to be installed. Try 'sudo pip install tlslite'")
35 from pyasn1.codec.der import decoder, encoder
36 from pyasn1.type.univ import Any, ObjectIdentifier, OctetString
37 from pyasn1.type.char import BMPString, IA5String, UTF8String
38 from pyasn1.type.useful import GeneralizedTime
39 from pyasn1_modules.rfc2459 import (Certificate, DirectoryString,
40 SubjectAltName, GeneralNames,
42 from pyasn1_modules.rfc2459 import id_ce_subjectAltName as SUBJECT_ALT_NAME
43 from pyasn1_modules.rfc2459 import id_at_commonName as COMMON_NAME
44 from pyasn1_modules.rfc2459 import id_at_organizationalUnitName as OU_NAME
45 from pyasn1_modules.rfc2459 import id_ce_basicConstraints, BasicConstraints
46 XMPP_ADDR = ObjectIdentifier('1.3.6.1.5.5.7.8.5')
47 SRV_NAME = ObjectIdentifier('1.3.6.1.5.5.7.8.7')
48 ALGO_RSA_SHA1 = ObjectIdentifier('1.2.840.113549.1.1.5')
51 class CertificateError(Exception):
55 encoding = 'utf-16-be' if isinstance(data, BMPString) else 'utf-8'
56 return bytes(data).decode(encoding)
59 class X509(tlslite.X509):
60 """Child class of tlslite.X509 that uses pyasn1 to parse cert
61 information. Note: pyasn1 is a lot slower than tlslite, so we
62 should try to do everything in tlslite.
66 self.cert = decoder.decode(str(self.bytes), asn1Spec=Certificate())[0]
67 self.tbs = self.cert.getComponentByName('tbsCertificate')
68 self.subject = self.tbs.getComponentByName('subject')
69 self.extensions = self.tbs.getComponentByName('extensions') or []
71 def extract_names(self):
72 results = {'CN': None,
79 # Extract the CommonName(s) from the cert.
80 for rdnss in self.subject:
83 oid = name.getComponentByName('type')
84 value = name.getComponentByName('value')
86 if oid == COMMON_NAME:
87 value = decoder.decode(value, asn1Spec=DirectoryString())[0]
88 value = decode_str(value.getComponent())
92 value = decoder.decode(value, asn1Spec=DirectoryString())[0]
93 value = decode_str(value.getComponent())
96 # Extract the Subject Alternate Names (DNS, SRV, URI, XMPPAddr)
97 for extension in self.extensions:
98 oid = extension.getComponentByName('extnID')
99 if oid != SUBJECT_ALT_NAME:
102 value = decoder.decode(extension.getComponentByName('extnValue'),
103 asn1Spec=OctetString())[0]
104 sa_names = decoder.decode(value, asn1Spec=SubjectAltName())[0]
105 for name in sa_names:
106 name_type = name.getName()
107 if name_type == 'dNSName':
108 results['DNS'].add(decode_str(name.getComponent()))
109 if name_type == 'uniformResourceIdentifier':
110 value = decode_str(name.getComponent())
111 if value.startswith('xmpp:'):
112 results['URI'].add(value[5:])
113 elif name_type == 'otherName':
114 name = name.getComponent()
116 oid = name.getComponentByName('type-id')
117 value = name.getComponentByName('value')
120 value = decoder.decode(value, asn1Spec=UTF8String())[0]
121 results['XMPPAddr'].add(decode_str(value))
122 elif oid == SRV_NAME:
123 value = decoder.decode(value, asn1Spec=IA5String())[0]
124 results['SRV'].add(decode_str(value))
129 for extension in self.extensions:
130 oid = extension.getComponentByName('extnID')
131 if oid != id_ce_basicConstraints:
133 value = decoder.decode(extension.getComponentByName('extnValue'),
134 asn1Spec=OctetString())[0]
135 constraints = decoder.decode(value, asn1Spec=BasicConstraints())[0]
136 return bool(constraints[0])
138 def extract_sig(self):
139 signature = self.cert.getComponentByName('signatureValue')
140 algorithm = self.cert.getComponentByName('signatureAlgorithm')
141 data = encoder.encode(self.tbs)
142 s = encoder.encode(signature)
143 return algorithm, s, data
146 def extract_pubkey(self):
147 pki = self.tbs.getComponentByName('subjectPublicKeyInfo')
148 algo = pki.getComponentByName('algorithm')
149 algorithm = algo.getComponentByName('algorithm')
150 parameters = algo.getComponentByName('parameters')
151 subjectPublicKey = pki.getComponentByName('subjectPublicKey')
152 return algorithm, parameters, encoder.encode(subjectPublicKey)
155 def extract_dates(self):
156 validity = self.tbs.getComponentByName('validity')
157 not_before = validity.getComponentByName('notBefore')
158 not_before = str(not_before.getComponent())
159 not_after = validity.getComponentByName('notAfter')
160 not_after = str(not_after.getComponent())
161 if isinstance(not_before, GeneralizedTime):
162 not_before = datetime.strptime(not_before, '%Y%m%d%H%M%SZ')
164 not_before = datetime.strptime(not_before, '%y%m%d%H%M%SZ')
165 if isinstance(not_after, GeneralizedTime):
166 not_after = datetime.strptime(not_after, '%Y%m%d%H%M%SZ')
168 not_after = datetime.strptime(not_after, '%y%m%d%H%M%SZ')
169 return not_before, not_after
172 not_before, not_after = self.extract_dates()
173 if not_after is None:
175 return not_after - datetime.utcnow()
177 def check_date(self):
178 not_before, not_after = self.extract_dates()
179 now = datetime.utcnow()
181 raise CertificateError(
182 'Certificate has not entered its valid date range.')
184 raise CertificateError(
185 'Certificate has expired.')
187 def check_name(self, expected):
188 cert_names = self.extract_names()
190 expected_wild = expected[expected.index('.'):]
192 expected_wild = expected
193 expected_srv = '_xmpp-client.%s' % expected
194 for name in cert_names['XMPPAddr']:
197 for name in cert_names['SRV']:
198 if name == expected_srv or name == expected:
200 for name in cert_names['DNS']:
203 if name.startswith('*'):
205 name_wild = name[name.index('.'):]
208 if expected_wild == name_wild:
210 for name in cert_names['URI']:
213 if cert_names['CN'] == expected:
215 raise CertificateError(
216 'Could not match certficate against hostname: %s' % expected)
219 class X509CertChain(tlslite.X509CertChain):