1
+ use std:: collections:: HashSet ;
2
+ use std:: sync:: Arc ;
3
+
4
+ use cryptography_x509:: oid:: {
5
+ AUTHORITY_INFORMATION_ACCESS_OID , AUTHORITY_KEY_IDENTIFIER_OID , BASIC_CONSTRAINTS_OID ,
6
+ EXTENDED_KEY_USAGE_OID , KEY_USAGE_OID , NAME_CONSTRAINTS_OID , SUBJECT_ALTERNATIVE_NAME_OID ,
7
+ SUBJECT_KEY_IDENTIFIER_OID ,
8
+ } ;
9
+
10
+ use cryptography_x509:: extensions:: Extension ;
11
+
12
+ use cryptography_x509_verification:: ops:: VerificationCertificate ;
13
+ use cryptography_x509_verification:: policy:: {
14
+ Criticality , ExtensionPolicy , ExtensionValidator , MaybeExtensionValidatorCallback , Policy ,
15
+ PresentExtensionValidatorCallback ,
16
+ } ;
17
+ use cryptography_x509_verification:: { ValidationError , ValidationErrorKind , ValidationResult } ;
18
+ use pyo3:: types:: PyAnyMethods ;
19
+ use pyo3:: types:: PyTypeMethods ;
20
+ use pyo3:: { intern, PyResult } ;
21
+
22
+ use crate :: asn1:: py_oid_to_oid;
23
+
24
+ use crate :: types;
25
+ use crate :: x509:: certificate:: parse_cert_ext;
26
+
1
27
use super :: PyCryptoOps ;
2
- use cryptography_x509_verification:: policy:: ExtensionPolicy ;
28
+
29
+ #[ pyo3:: pyclass(
30
+ frozen,
31
+ eq,
32
+ module = "cryptography.x509.verification" ,
33
+ name = "Criticality"
34
+ ) ]
35
+ #[ derive( PartialEq , Eq , Clone ) ]
36
+ pub ( crate ) enum PyCriticality {
37
+ #[ pyo3( name = "CRITICAL" ) ]
38
+ Critical ,
39
+ #[ pyo3( name = "AGNOSTIC" ) ]
40
+ Agnostic ,
41
+ #[ pyo3( name = "NON_CRITICAL" ) ]
42
+ NonCritical ,
43
+ }
44
+
45
+ impl From < PyCriticality > for Criticality {
46
+ fn from ( criticality : PyCriticality ) -> Criticality {
47
+ match criticality {
48
+ PyCriticality :: Critical => Criticality :: Critical ,
49
+ PyCriticality :: Agnostic => Criticality :: Agnostic ,
50
+ PyCriticality :: NonCritical => Criticality :: NonCritical ,
51
+ }
52
+ }
53
+ }
3
54
4
55
#[ pyo3:: pyclass(
5
56
frozen,
@@ -8,6 +59,7 @@ use cryptography_x509_verification::policy::ExtensionPolicy;
8
59
) ]
9
60
pub ( crate ) struct PyExtensionPolicy {
10
61
inner_policy : ExtensionPolicy < ' static , PyCryptoOps > ,
62
+ already_set_oids : HashSet < asn1:: ObjectIdentifier > ,
11
63
}
12
64
13
65
impl PyExtensionPolicy {
@@ -16,10 +68,63 @@ impl PyExtensionPolicy {
16
68
}
17
69
18
70
fn new ( inner_policy : ExtensionPolicy < ' static , PyCryptoOps > ) -> Self {
19
- PyExtensionPolicy { inner_policy }
71
+ PyExtensionPolicy {
72
+ inner_policy,
73
+ already_set_oids : HashSet :: new ( ) ,
74
+ }
75
+ }
76
+
77
+ fn with_assigned_validator (
78
+ & self ,
79
+ oid : asn1:: ObjectIdentifier ,
80
+ validator : ExtensionValidator < ' static , PyCryptoOps > ,
81
+ ) -> PyResult < PyExtensionPolicy > {
82
+ if self . already_set_oids . contains ( & oid) {
83
+ return Err ( pyo3:: exceptions:: PyValueError :: new_err ( format ! (
84
+ "ExtensionPolicy already configured for extension with OID {oid}"
85
+ ) ) ) ;
86
+ }
87
+
88
+ let mut policy = self . inner_policy . clone ( ) ;
89
+ match oid {
90
+ AUTHORITY_INFORMATION_ACCESS_OID => policy. authority_information_access = validator,
91
+ AUTHORITY_KEY_IDENTIFIER_OID => policy. authority_key_identifier = validator,
92
+ SUBJECT_KEY_IDENTIFIER_OID => policy. subject_key_identifier = validator,
93
+ KEY_USAGE_OID => policy. key_usage = validator,
94
+ SUBJECT_ALTERNATIVE_NAME_OID => policy. subject_alternative_name = validator,
95
+ BASIC_CONSTRAINTS_OID => policy. basic_constraints = validator,
96
+ NAME_CONSTRAINTS_OID => policy. name_constraints = validator,
97
+ EXTENDED_KEY_USAGE_OID => policy. extended_key_usage = validator,
98
+ _ => {
99
+ return Err ( pyo3:: exceptions:: PyValueError :: new_err ( format ! (
100
+ "Unsupported extension OID: {}" ,
101
+ oid
102
+ ) ) )
103
+ }
104
+ }
105
+
106
+ let mut already_set_oids = self . already_set_oids . clone ( ) ;
107
+ already_set_oids. insert ( oid) ;
108
+ Ok ( PyExtensionPolicy {
109
+ inner_policy : policy,
110
+ already_set_oids,
111
+ } )
20
112
}
21
113
}
22
114
115
+ fn oid_from_py_extension_type (
116
+ py : pyo3:: Python < ' _ > ,
117
+ extension_type : pyo3:: Bound < ' _ , pyo3:: types:: PyType > ,
118
+ ) -> pyo3:: PyResult < asn1:: ObjectIdentifier > {
119
+ if !extension_type. is_subclass ( & types:: EXTENSION_TYPE . get ( py) ?) ? {
120
+ return Err ( pyo3:: exceptions:: PyTypeError :: new_err (
121
+ "extension_type must be a subclass of ExtensionType" ,
122
+ ) ) ;
123
+ }
124
+
125
+ py_oid_to_oid ( extension_type. getattr ( intern ! ( py, "oid" ) ) ?)
126
+ }
127
+
23
128
#[ pyo3:: pymethods]
24
129
impl PyExtensionPolicy {
25
130
#[ staticmethod]
@@ -36,4 +141,149 @@ impl PyExtensionPolicy {
36
141
pub ( crate ) fn webpki_defaults_ee ( ) -> Self {
37
142
PyExtensionPolicy :: new ( ExtensionPolicy :: new_default_webpki_ee ( ) )
38
143
}
144
+
145
+ pub ( crate ) fn require_not_present (
146
+ & self ,
147
+ py : pyo3:: Python < ' _ > ,
148
+ extension_type : pyo3:: Bound < ' _ , pyo3:: types:: PyType > ,
149
+ ) -> pyo3:: PyResult < PyExtensionPolicy > {
150
+ let oid = oid_from_py_extension_type ( py, extension_type) ?;
151
+ self . with_assigned_validator ( oid, ExtensionValidator :: NotPresent )
152
+ }
153
+
154
+ #[ pyo3( signature = ( extension_type, criticality, validator_cb) ) ]
155
+ pub ( crate ) fn may_be_present (
156
+ & self ,
157
+ py : pyo3:: Python < ' _ > ,
158
+ extension_type : pyo3:: Bound < ' _ , pyo3:: types:: PyType > ,
159
+ criticality : PyCriticality ,
160
+ validator_cb : Option < pyo3:: PyObject > ,
161
+ ) -> pyo3:: PyResult < PyExtensionPolicy > {
162
+ let oid = oid_from_py_extension_type ( py, extension_type) ?;
163
+ self . with_assigned_validator (
164
+ oid,
165
+ ExtensionValidator :: MaybePresent {
166
+ criticality : criticality. into ( ) ,
167
+ validator : validator_cb. map ( wrap_maybe_validator_callback) ,
168
+ } ,
169
+ )
170
+ }
171
+
172
+ #[ pyo3( signature = ( extension_type, criticality, validator_cb) ) ]
173
+ pub ( crate ) fn require_present (
174
+ & self ,
175
+ py : pyo3:: Python < ' _ > ,
176
+ extension_type : pyo3:: Bound < ' _ , pyo3:: types:: PyType > ,
177
+ criticality : PyCriticality ,
178
+ validator_cb : Option < pyo3:: PyObject > ,
179
+ ) -> pyo3:: PyResult < PyExtensionPolicy > {
180
+ let oid = oid_from_py_extension_type ( py, extension_type) ?;
181
+ self . with_assigned_validator (
182
+ oid,
183
+ ExtensionValidator :: Present {
184
+ criticality : criticality. into ( ) ,
185
+ validator : validator_cb. map ( wrap_present_validator_callback) ,
186
+ } ,
187
+ )
188
+ }
189
+ }
190
+
191
+ fn wrap_maybe_validator_callback (
192
+ py_cb : pyo3:: PyObject ,
193
+ ) -> MaybeExtensionValidatorCallback < ' static , PyCryptoOps > {
194
+ Arc :: new (
195
+ move |policy : & Policy < ' _ , PyCryptoOps > ,
196
+ cert : & VerificationCertificate < ' _ , PyCryptoOps > ,
197
+ ext : Option < & Extension < ' _ > > | {
198
+ pyo3:: Python :: with_gil ( |py| {
199
+ invoke_py_validator_callback (
200
+ py,
201
+ & py_cb,
202
+ (
203
+ policy. extra . clone_ref ( py) ,
204
+ cert. extra ( ) . clone_ref ( py) ,
205
+ make_py_extension ( py, ext) ?,
206
+ ) ,
207
+ )
208
+ } )
209
+ } ,
210
+ )
211
+ }
212
+
213
+ fn wrap_present_validator_callback (
214
+ py_cb : pyo3:: PyObject ,
215
+ ) -> PresentExtensionValidatorCallback < ' static , PyCryptoOps > {
216
+ Arc :: new (
217
+ move |policy : & Policy < ' _ , PyCryptoOps > ,
218
+ cert : & VerificationCertificate < ' _ , PyCryptoOps > ,
219
+ ext : & Extension < ' _ > | {
220
+ pyo3:: Python :: with_gil ( |py| {
221
+ invoke_py_validator_callback (
222
+ py,
223
+ & py_cb,
224
+ (
225
+ policy. extra . clone_ref ( py) ,
226
+ cert. extra ( ) . clone_ref ( py) ,
227
+ make_py_extension ( py, Some ( ext) ) ?. unwrap ( ) ,
228
+ ) ,
229
+ )
230
+ } )
231
+ } ,
232
+ )
233
+ }
234
+
235
+ fn make_py_extension < ' chain , ' p > (
236
+ py : pyo3:: Python < ' p > ,
237
+ ext : Option < & Extension < ' p > > ,
238
+ ) -> ValidationResult < ' chain , Option < pyo3:: Bound < ' p , pyo3:: types:: PyAny > > , PyCryptoOps > {
239
+ Ok ( match ext {
240
+ None => None ,
241
+ Some ( ext) => parse_cert_ext ( py, ext) . map_err ( |e| {
242
+ ValidationError :: new ( ValidationErrorKind :: Other ( format ! (
243
+ "{e} (while converting Extension to Python object)"
244
+ ) ) )
245
+ } ) ?,
246
+ } )
247
+ }
248
+
249
+ fn invoke_py_validator_callback < ' py > (
250
+ py : pyo3:: Python < ' py > ,
251
+ py_cb : & pyo3:: PyObject ,
252
+ args : impl pyo3:: IntoPyObject < ' py , Target = pyo3:: types:: PyTuple > ,
253
+ ) -> ValidationResult < ' static , ( ) , PyCryptoOps > {
254
+ let result = py_cb. bind ( py) . call1 ( args) . map_err ( |e| {
255
+ ValidationError :: new ( ValidationErrorKind :: Other ( format ! (
256
+ "Python extension validator failed: {}" ,
257
+ e
258
+ ) ) )
259
+ } ) ?;
260
+
261
+ if !result. is_none ( ) {
262
+ let error_kind =
263
+ ValidationErrorKind :: Other ( "Python validator must return None." . to_string ( ) ) ;
264
+ Err ( ValidationError :: new ( error_kind) )
265
+ } else {
266
+ Ok ( ( ) )
267
+ }
268
+ }
269
+
270
+ #[ cfg( test) ]
271
+ mod tests {
272
+ use cryptography_x509:: extensions:: Extension ;
273
+
274
+ #[ test]
275
+ fn test_make_py_extension_fail ( ) {
276
+ pyo3:: Python :: with_gil ( |py| {
277
+ let invalid_extension = Extension {
278
+ // SubjectAlternativeName
279
+ extn_id : asn1:: ObjectIdentifier :: from_string ( "2.5.29.17" ) . unwrap ( ) ,
280
+ critical : false ,
281
+ extn_value : & [ ] ,
282
+ } ;
283
+ let result = super :: make_py_extension ( py, Some ( & invalid_extension) ) ;
284
+ assert ! ( result. is_err( ) ) ;
285
+ let error = result. unwrap_err ( ) ;
286
+ assert ! ( format!( "{error}" ) . contains( "(while converting Extension to Python object)" ) ) ;
287
+ } )
288
+ }
39
289
}
0 commit comments