-
Notifications
You must be signed in to change notification settings - Fork 0
/
x509.py
219 lines (169 loc) · 9.3 KB
/
x509.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
from asn1crypto import x509
import asn1crypto.core as asn1
from .oids import id_icao_cscaMasterListSigningKey
from .cert_utils import verify_cert_sig
from datetime import datetime
class CertificateVerificationError(Exception):
pass
class Certificate(x509.Certificate):
@property
def fingerprint(self) -> str:
"""SHA256 hash string of this object"""
return self.sha256.hex()
@property
def issuerCountry(self) -> str:
"""Function returns country of certificate issuer"""
country = self.issuer.native['country_name']
return country
@property
def subjectKey(self) -> bytes:
"""Function returns subject key of certificate"""
return self.key_identifier
@property
def authorityKey(self) -> bytes:
"""Function returns authority key of certificate"""
return self.authority_key_identifier
@property
def notValidBefore(self) -> datetime:
return self['tbs_certificate']['validity']['not_before'].native
@property
def notValidAfter(self) -> datetime:
return self['tbs_certificate']['validity']['not_after'].native
def isValidOn(self, dateTime: datetime):
''' Verifies if certificate is valid on specific date-time '''
nvb = self.notValidBefore
nva = self.notValidAfter
dateTime = dateTime.replace(tzinfo=nvb.tzinfo)
return nvb < dateTime < nva
def verify(self, issuing_cert: x509.Certificate, nc_verification = False):
"""
Verifies certificate has all required fields and that issuing certificate did issue this certificate.
On failure CertificateVerificationError exception is risen.
:param issuing_cert: The certificate that issued this certificate
:param nc_verification: Non-conformance verification, If False only signature will be verified.
"""
if not verify_cert_sig(self, issuing_cert):
raise CertificateVerificationError("Signature verification failed")
# Verify certificate is conform to the ICAO specifications
if nc_verification:
self._verifiy_cert_fields()
self._verifiy_tbs_cert_fields()
def _require(cond, message: str):
if not cond:
raise CertificateVerificationError(message)
def _require_cert_field(self, field: str):
Certificate._require(field in self,
"Missing required certificate field '{}'".format(field)
)
def _verifiy_cert_fields(self):
self._require_cert_field('tbs_certificate')
self._require_cert_field('signature_algorithm')
self._require_cert_field('signature_value')
def _require_tbs_cert_field(self, field: str):
Certificate._require(field in self['tbs_certificate'],
"Missing required tbs certificate field '{}'".format(field)
)
def _verifiy_tbs_cert_fields(self):
self._require_tbs_cert_field('extensions')
self._require_tbs_cert_field('issuer')
self._require_tbs_cert_field('serial_number')
self._require_tbs_cert_field('signature')
self._require_tbs_cert_field('subject')
self._require_tbs_cert_field('subject_public_key_info')
self._require_tbs_cert_field('validity')
self._require_tbs_cert_field('version')
def _require_extension_field(self, field: str):
exts = self['tbs_certificate']['extensions']
for e in exts:
if field in e['extn_id'].native:
return
Certificate._require(False,
"Missing required extension field '{}'".format(field)
)
def _require_extension_value(self, field: str, value):
exts = self['tbs_certificate']['extensions']
for e in exts:
if field in e['extn_id'].native:
if e['extn_value'].native == value:
return
Certificate._require(False,
"Extension value invalid! ext='{}' v='{}', req_v=''".format(field, e['extn_value'].native, value)
)
Certificate._require(False,
"Missing required extension field '{}'".format(field)
)
class CscaCertificate(Certificate):
def verify(self, nc_verification = False):
self.verify(self, nc_verification)
def verify(self, issuing_cert: x509.Certificate, nc_verification = False):
"""
Verifies certificate has all required fields and that issuing certificate did issue this certificate.
On failure CertificateVerificationError exception is risen.
:param issuing_cert: The certificate that issued this certificate.
:param nc_verification: Non-conformance verification, If False only signature will be verified.
"""
super().verify(issuing_cert, nc_verification)
# Verify certificate is conform to the ICAO specifications
if nc_verification:
super()._require_extension_field('subject_key_identifier')
Certificate._require(self.subjectKey is not None, "Missing required field 'subjectKeyIdentifier' in SubjectKeyIdentifier extension")
super()._require_extension_field('basic_constraints')
Certificate._require('ca' in self.basic_constraints_values, "Missing 'ca' field in basic constraints")
Certificate._require('max_path_length' in self.basic_constraints_values, "Missing 'ca' field in basic constraints")
Certificate._require( self.max_path_length is None or 0 <= self.max_path_length <= 1, #Note: Portuguese cross-link CSCA has value 2
"Invalid CSCA path length constraint: {}".format(self.max_path_length)
)
super()._require_extension_field('key_identifier')
super()._require_extension_field('key_usage')
key_usage = self.key_usage_value.native
Certificate._require( 'key_cert_sign' in key_usage, "Missing field 'keyCertSign' in KeyUsage extension")
Certificate._require('crl_sign' in key_usage, "Missing field 'cRLSign' in KeyUsage extension")
class MasterListSignerCertificate(Certificate):
def verify(self, issuing_cert: x509.Certificate, nc_verification = False):
"""
Verifies certificate has all required fields and that issuing certificate did issue this certificate.
On failure CertificateVerificationError exception is risen.
:param issuing_cert: The certificate that issued this certificate.
:param nc_verification: Non-conformance verification, If False only signature will be verified.
"""
if self.ca: # Signer certificate is probably CSCA
# We do this check because not all master list issuers follow the specification rules and
# they use CSCA to sign master list instead of separate signer certificate issued by CSCA.
# See for example German master list no. 20190925)
CscaCertificate.load(self.dump()).verify(issuing_cert, nc_verification)
else:
super().verify(issuing_cert, nc_verification)
super()._require_extension_value('extended_key_usage', [id_icao_cscaMasterListSigningKey]) #icao 9303-p12 p20, p27
# Verify certificate conforms to the ICAO specifications
if nc_verification:
super()._require_extension_field('authority_key_identifier')
Certificate._require(self.authorityKey is not None, "Missing required field 'keyIdentifier' in AuthorityKeyIdentifier extension")
super()._require_extension_field('subject_key_identifier')
Certificate._require(self.subjectKey is not None, "Missing required field 'subjectKeyIdentifier' in SubjectKeyIdentifier extension")
super()._require_extension_field('key_usage')
key_usage = self.key_usage_value.native
Certificate._require(
'digital_signature' in key_usage,
"Missing field 'digitalSignature' in KeyUsage"
)
class DocumentSignerCertificate(Certificate):
""" Document Signer Certificate (DSC) which should be used to verify SOD data file in eMRTD """
def verify(self, issuing_cert: x509.Certificate, nc_verification = False):
"""
Verifies certificate has all required fields and that issuing certificate did issue this certificate.
On failure CertificateVerificationError exception is risen.
:param issuing_cert: The certificate that issued this certificate.
:param nc_verification: Non-conformance verification, If False only signature will be verified.
"""
super().verify(issuing_cert, nc_verification)
if nc_verification:
super()._require_extension_field('authority_key_identifier')
Certificate._require(self.authorityKey is not None, "Missing required field 'keyIdentifier' in AuthorityKeyIdentifier extension")
super()._require_extension_field('subject_key_identifier')
Certificate._require(self.subjectKey is not None, "Missing required field 'subjectKeyIdentifier' in SubjectKeyIdentifier extension")
super()._require_extension_field('key_usage')
key_usage = self.key_usage_value.native
Certificate._require(
'digital_signature' in key_usage,
"Missing field 'digitalSignature' in KeyUsage"
)