]>
Commit | Line | Data |
---|---|---|
6d7eed9a MW |
1 | /* |
2 | * Copyright (C) 2010 Martin Willi | |
3 | * Copyright (C) 2010 revosec AG | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License as published by the | |
7 | * Free Software Foundation; either version 2 of the License, or (at your | |
8 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but | |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
12 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
13 | * for more details. | |
14 | */ | |
15 | ||
16 | /* | |
17 | * Copyright (C) 2010 secunet Security Networks AG | |
18 | * Copyright (C) 2010 Thomas Egerer | |
19 | * | |
20 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
21 | * of this software and associated documentation files (the "Software"), to deal | |
22 | * in the Software without restriction, including without limitation the rights | |
23 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
24 | * copies of the Software, and to permit persons to whom the Software is | |
25 | * furnished to do so, subject to the following conditions: | |
26 | * | |
27 | * The above copyright notice and this permission notice shall be included in | |
28 | * all copies or substantial portions of the Software. | |
29 | * | |
30 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
31 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
32 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
33 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
34 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
35 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
36 | * THE SOFTWARE. | |
37 | */ | |
38 | ||
39 | #include "openssl_crl.h" | |
40 | #include "openssl_util.h" | |
41 | ||
42 | #include <openssl/x509.h> | |
43 | #include <openssl/x509v3.h> | |
44 | ||
f05b4272 | 45 | #include <utils/debug.h> |
12642a68 | 46 | #include <collections/enumerator.h> |
6d7eed9a MW |
47 | #include <credentials/certificates/x509.h> |
48 | ||
49 | typedef struct private_openssl_crl_t private_openssl_crl_t; | |
50 | ||
51 | /** | |
52 | * Private data of an openssl_crl_t object. | |
53 | */ | |
54 | struct private_openssl_crl_t { | |
55 | ||
56 | /** | |
57 | * Public openssl_crl_t interface. | |
58 | */ | |
59 | openssl_crl_t public; | |
60 | ||
61 | /** | |
62 | * OpenSSL representation of a CRL | |
63 | */ | |
64 | X509_CRL *crl; | |
65 | ||
66 | /** | |
67 | * DER encoding of the CRL | |
68 | */ | |
69 | chunk_t encoding; | |
70 | ||
71 | /** | |
72 | * Serial Number (crlNumber) of the CRL) | |
73 | */ | |
74 | chunk_t serial; | |
75 | ||
76 | /** | |
77 | * AuthorityKeyIdentifier of the issuing CA | |
78 | */ | |
79 | chunk_t authKeyIdentifier; | |
80 | ||
81 | /** | |
82 | * Date of this CRL | |
83 | */ | |
84 | time_t thisUpdate; | |
85 | ||
86 | /** | |
87 | * Date of next CRL expected | |
88 | */ | |
89 | time_t nextUpdate; | |
90 | ||
91 | /** | |
92 | * Issuer of this CRL | |
93 | */ | |
94 | identification_t *issuer; | |
95 | ||
96 | /** | |
97 | * Signature scheme used in this CRL | |
98 | */ | |
99 | signature_scheme_t scheme; | |
100 | ||
101 | /** | |
102 | * References to this CRL | |
103 | */ | |
104 | refcount_t ref; | |
105 | }; | |
106 | ||
107 | /** | |
108 | * Enumerator over revoked certificates | |
109 | */ | |
110 | typedef struct { | |
111 | /** | |
112 | * Implements enumerator_t | |
113 | */ | |
114 | enumerator_t public; | |
115 | ||
116 | /** | |
117 | * stack of revoked certificates | |
118 | */ | |
119 | STACK_OF(X509_REVOKED) *stack; | |
120 | ||
121 | /** | |
122 | * Total number of revoked certificates | |
123 | */ | |
124 | int num; | |
125 | ||
126 | /** | |
127 | * Current position of enumerator | |
128 | */ | |
129 | int i; | |
130 | } crl_enumerator_t; | |
131 | ||
132 | ||
133 | METHOD(enumerator_t, crl_enumerate, bool, | |
134 | crl_enumerator_t *this, chunk_t *serial, time_t *date, crl_reason_t *reason) | |
135 | { | |
136 | if (this->i < this->num) | |
137 | { | |
138 | X509_REVOKED *revoked; | |
139 | ASN1_ENUMERATED *crlrsn; | |
140 | ||
141 | revoked = sk_X509_REVOKED_value(this->stack, this->i); | |
142 | if (serial) | |
143 | { | |
144 | *serial = openssl_asn1_str2chunk(revoked->serialNumber); | |
145 | } | |
146 | if (date) | |
147 | { | |
148 | *date = openssl_asn1_to_time(revoked->revocationDate); | |
149 | } | |
150 | if (reason) | |
151 | { | |
152 | *reason = CRL_REASON_UNSPECIFIED; | |
153 | crlrsn = X509_REVOKED_get_ext_d2i(revoked, NID_crl_reason, | |
154 | NULL, NULL); | |
155 | if (crlrsn) | |
156 | { | |
157 | if (ASN1_STRING_type(crlrsn) == V_ASN1_ENUMERATED && | |
158 | ASN1_STRING_length(crlrsn) == 1) | |
159 | { | |
160 | *reason = *ASN1_STRING_data(crlrsn); | |
161 | } | |
162 | ASN1_STRING_free(crlrsn); | |
163 | } | |
164 | } | |
165 | this->i++; | |
166 | return TRUE; | |
167 | } | |
168 | return FALSE; | |
169 | } | |
170 | ||
171 | METHOD(crl_t, create_enumerator, enumerator_t*, | |
172 | private_openssl_crl_t *this) | |
173 | { | |
174 | crl_enumerator_t *enumerator; | |
175 | ||
176 | INIT(enumerator, | |
177 | .public = { | |
178 | .enumerate = (void*)_crl_enumerate, | |
179 | .destroy = (void*)free, | |
180 | }, | |
181 | .stack = X509_CRL_get_REVOKED(this->crl), | |
182 | ); | |
183 | if (!enumerator->stack) | |
184 | { | |
185 | free(enumerator); | |
186 | return enumerator_create_empty(); | |
187 | } | |
24fa80ac | 188 | enumerator->num = sk_X509_REVOKED_num(enumerator->stack); |
6d7eed9a MW |
189 | return &enumerator->public; |
190 | } | |
191 | ||
192 | METHOD(crl_t, get_serial, chunk_t, | |
193 | private_openssl_crl_t *this) | |
194 | { | |
195 | return this->serial; | |
196 | } | |
197 | ||
198 | METHOD(crl_t, get_authKeyIdentifier, chunk_t, | |
199 | private_openssl_crl_t *this) | |
200 | { | |
201 | return this->authKeyIdentifier; | |
202 | } | |
203 | ||
204 | METHOD(certificate_t, get_type, certificate_type_t, | |
205 | private_openssl_crl_t *this) | |
206 | { | |
207 | return CERT_X509_CRL; | |
208 | } | |
209 | ||
210 | METHOD(certificate_t, get_subject_or_issuer, identification_t*, | |
211 | private_openssl_crl_t *this) | |
212 | { | |
213 | return this->issuer; | |
214 | } | |
215 | ||
216 | METHOD(certificate_t, has_subject_or_issuer, id_match_t, | |
217 | private_openssl_crl_t *this, identification_t *id) | |
218 | { | |
219 | if (id->get_type(id) == ID_KEY_ID && | |
220 | chunk_equals(this->authKeyIdentifier, id->get_encoding(id))) | |
221 | { | |
222 | return ID_MATCH_PERFECT; | |
223 | } | |
224 | return this->issuer->matches(this->issuer, id); | |
225 | } | |
226 | ||
227 | METHOD(certificate_t, issued_by, bool, | |
a37f2d20 MW |
228 | private_openssl_crl_t *this, certificate_t *issuer, |
229 | signature_scheme_t *scheme) | |
6d7eed9a MW |
230 | { |
231 | chunk_t fingerprint, tbs; | |
232 | public_key_t *key; | |
233 | x509_t *x509; | |
234 | bool valid; | |
235 | ||
236 | if (issuer->get_type(issuer) != CERT_X509) | |
237 | { | |
238 | return FALSE; | |
239 | } | |
240 | x509 = (x509_t*)issuer; | |
241 | if (!(x509->get_flags(x509) & X509_CA)) | |
242 | { | |
243 | return FALSE; | |
244 | } | |
245 | key = issuer->get_public_key(issuer); | |
246 | if (!key) | |
247 | { | |
248 | return FALSE; | |
249 | } | |
250 | if (this->authKeyIdentifier.ptr && key) | |
251 | { | |
da9724e6 | 252 | if (!key->get_fingerprint(key, KEYID_PUBKEY_SHA1, &fingerprint) || |
6d7eed9a MW |
253 | !chunk_equals(fingerprint, this->authKeyIdentifier)) |
254 | { | |
255 | return FALSE; | |
256 | } | |
257 | } | |
258 | else | |
259 | { | |
260 | if (!this->issuer->equals(this->issuer, issuer->get_subject(issuer))) | |
261 | { | |
262 | return FALSE; | |
263 | } | |
264 | } | |
265 | if (this->scheme == SIGN_UNKNOWN) | |
266 | { | |
267 | return FALSE; | |
268 | } | |
269 | tbs = openssl_i2chunk(X509_CRL_INFO, this->crl->crl); | |
270 | valid = key->verify(key, this->scheme, tbs, | |
271 | openssl_asn1_str2chunk(this->crl->signature)); | |
272 | free(tbs.ptr); | |
273 | key->destroy(key); | |
a37f2d20 MW |
274 | if (valid && scheme) |
275 | { | |
276 | *scheme = this->scheme; | |
277 | } | |
6d7eed9a MW |
278 | return valid; |
279 | } | |
280 | ||
281 | METHOD(certificate_t, get_public_key, public_key_t*, | |
282 | private_openssl_crl_t *this) | |
283 | { | |
284 | return NULL; | |
285 | } | |
286 | ||
287 | METHOD(certificate_t, get_validity, bool, | |
288 | private_openssl_crl_t *this, | |
289 | time_t *when, time_t *not_before, time_t *not_after) | |
290 | { | |
291 | time_t t = when ? *when : time(NULL); | |
292 | ||
293 | if (not_before) | |
294 | { | |
295 | *not_before = this->thisUpdate; | |
296 | } | |
297 | if (not_after) | |
298 | { | |
299 | *not_after = this->nextUpdate; | |
300 | } | |
301 | return t <= this->nextUpdate; | |
302 | } | |
303 | ||
0406eeaa MW |
304 | METHOD(certificate_t, get_encoding, bool, |
305 | private_openssl_crl_t *this, cred_encoding_type_t type, chunk_t *encoding) | |
6d7eed9a | 306 | { |
0406eeaa MW |
307 | if (type == CERT_ASN1_DER) |
308 | { | |
309 | *encoding = chunk_clone(this->encoding); | |
310 | return TRUE; | |
311 | } | |
312 | return lib->encoding->encode(lib->encoding, type, NULL, encoding, | |
313 | CRED_PART_X509_CRL_ASN1_DER, this->encoding, CRED_PART_END); | |
6d7eed9a MW |
314 | } |
315 | ||
316 | METHOD(certificate_t, equals, bool, | |
317 | private_openssl_crl_t *this, certificate_t *other) | |
318 | { | |
319 | chunk_t encoding; | |
320 | bool equal; | |
321 | ||
322 | if (&this->public.crl.certificate == other) | |
323 | { | |
324 | return TRUE; | |
325 | } | |
326 | if (other->equals == (void*)equals) | |
327 | { /* skip allocation if we have the same implementation */ | |
328 | return chunk_equals(this->encoding, | |
329 | ((private_openssl_crl_t*)other)->encoding); | |
330 | } | |
0406eeaa MW |
331 | if (!other->get_encoding(other, CERT_ASN1_DER, &encoding)) |
332 | { | |
333 | return FALSE; | |
334 | } | |
6d7eed9a MW |
335 | equal = chunk_equals(this->encoding, encoding); |
336 | free(encoding.ptr); | |
337 | return equal; | |
338 | } | |
339 | ||
340 | METHOD(certificate_t, get_ref, certificate_t*, | |
341 | private_openssl_crl_t *this) | |
342 | { | |
343 | ref_get(&this->ref); | |
344 | return &this->public.crl.certificate; | |
345 | } | |
346 | ||
347 | METHOD(certificate_t, destroy, void, | |
348 | private_openssl_crl_t *this) | |
349 | { | |
350 | if (ref_put(&this->ref)) | |
351 | { | |
352 | if (this->crl) | |
353 | { | |
354 | X509_CRL_free(this->crl); | |
355 | } | |
356 | DESTROY_IF(this->issuer); | |
357 | free(this->authKeyIdentifier.ptr); | |
358 | free(this->serial.ptr); | |
359 | free(this->encoding.ptr); | |
360 | free(this); | |
361 | } | |
362 | } | |
363 | ||
364 | /** | |
365 | * Create an empty CRL. | |
366 | */ | |
367 | static private_openssl_crl_t *create_empty() | |
368 | { | |
369 | private_openssl_crl_t *this; | |
370 | ||
371 | INIT(this, | |
372 | .public = { | |
373 | .crl = { | |
374 | .certificate = { | |
375 | .get_type = _get_type, | |
376 | .get_subject = _get_subject_or_issuer, | |
377 | .get_issuer = _get_subject_or_issuer, | |
378 | .has_subject = _has_subject_or_issuer, | |
379 | .has_issuer = _has_subject_or_issuer, | |
380 | .issued_by = _issued_by, | |
381 | .get_public_key = _get_public_key, | |
382 | .get_validity = _get_validity, | |
6d7eed9a MW |
383 | .get_encoding = _get_encoding, |
384 | .equals = _equals, | |
385 | .get_ref = _get_ref, | |
386 | .destroy = _destroy, | |
387 | }, | |
388 | .get_serial = _get_serial, | |
389 | .get_authKeyIdentifier = _get_authKeyIdentifier, | |
55e4d898 MW |
390 | .is_delta_crl = (void*)return_false, |
391 | .create_delta_crl_uri_enumerator = (void*)enumerator_create_empty, | |
6d7eed9a MW |
392 | .create_enumerator = _create_enumerator, |
393 | }, | |
394 | }, | |
395 | .ref = 1, | |
396 | ); | |
397 | return this; | |
398 | } | |
399 | ||
400 | /** | |
401 | * Parse the authKeyIdentifier extension | |
402 | */ | |
403 | static bool parse_authKeyIdentifier_ext(private_openssl_crl_t *this, | |
404 | X509_EXTENSION *ext) | |
405 | { | |
406 | AUTHORITY_KEYID *keyid; | |
407 | ||
408 | keyid = (AUTHORITY_KEYID *)X509V3_EXT_d2i(ext); | |
409 | if (keyid) | |
410 | { | |
411 | free(this->authKeyIdentifier.ptr); | |
412 | this->authKeyIdentifier = chunk_clone( | |
413 | openssl_asn1_str2chunk(keyid->keyid)); | |
414 | AUTHORITY_KEYID_free(keyid); | |
415 | return TRUE; | |
416 | } | |
417 | return FALSE; | |
418 | } | |
419 | ||
420 | /** | |
421 | * Parse the crlNumber extension | |
422 | */ | |
423 | static bool parse_crlNumber_ext(private_openssl_crl_t *this, | |
424 | X509_EXTENSION *ext) | |
425 | { | |
2291754d MW |
426 | chunk_t chunk; |
427 | ||
428 | chunk = openssl_asn1_str2chunk(X509_EXTENSION_get_data(ext)); | |
429 | /* quick and dirty INTEGER unwrap */ | |
430 | if (chunk.len > 1 && chunk.ptr[0] == V_ASN1_INTEGER && | |
431 | chunk.ptr[1] == chunk.len - 2) | |
432 | { | |
433 | chunk = chunk_skip(chunk, 2); | |
434 | free(this->serial.ptr); | |
435 | this->serial = chunk_clone(chunk); | |
436 | return TRUE; | |
437 | } | |
438 | return FALSE; | |
6d7eed9a MW |
439 | } |
440 | ||
441 | /** | |
442 | * Parse X509 CRL extensions | |
443 | */ | |
444 | static bool parse_extensions(private_openssl_crl_t *this) | |
445 | { | |
446 | bool ok; | |
447 | int i, num; | |
448 | X509_EXTENSION *ext; | |
449 | STACK_OF(X509_EXTENSION) *extensions; | |
450 | ||
451 | extensions = this->crl->crl->extensions; | |
452 | if (extensions) | |
453 | { | |
454 | num = sk_X509_EXTENSION_num(extensions); | |
455 | for (i = 0; i < num; ++i) | |
456 | { | |
457 | ext = sk_X509_EXTENSION_value(extensions, i); | |
458 | ||
459 | switch (OBJ_obj2nid(X509_EXTENSION_get_object(ext))) | |
460 | { | |
461 | case NID_authority_key_identifier: | |
462 | ok = parse_authKeyIdentifier_ext(this, ext); | |
463 | break; | |
464 | case NID_crl_number: | |
465 | ok = parse_crlNumber_ext(this, ext); | |
466 | break; | |
12677867 AS |
467 | case NID_issuing_distribution_point: |
468 | /* TODO support of IssuingDistributionPoints */ | |
469 | ok = TRUE; | |
470 | break; | |
6d7eed9a | 471 | default: |
c4fd3b2f AS |
472 | ok = X509_EXTENSION_get_critical(ext) == 0 || |
473 | !lib->settings->get_bool(lib->settings, | |
8dc6e716 | 474 | "%s.x509.enforce_critical", TRUE, lib->ns); |
e24a02a2 MW |
475 | if (!ok) |
476 | { | |
477 | DBG1(DBG_LIB, "found unsupported critical X.509 " | |
478 | "CRL extension"); | |
479 | } | |
6d7eed9a MW |
480 | break; |
481 | } | |
482 | if (!ok) | |
483 | { | |
484 | return FALSE; | |
485 | } | |
486 | } | |
487 | } | |
488 | return TRUE; | |
489 | } | |
490 | ||
491 | /** | |
492 | * Parse a X509 CRL | |
493 | */ | |
494 | static bool parse_crl(private_openssl_crl_t *this) | |
495 | { | |
496 | const unsigned char *ptr = this->encoding.ptr; | |
497 | ||
498 | this->crl = d2i_X509_CRL(NULL, &ptr, this->encoding.len); | |
499 | if (!this->crl) | |
500 | { | |
501 | return FALSE; | |
502 | } | |
503 | ||
504 | if (!chunk_equals( | |
505 | openssl_asn1_obj2chunk(this->crl->crl->sig_alg->algorithm), | |
506 | openssl_asn1_obj2chunk(this->crl->sig_alg->algorithm))) | |
507 | { | |
508 | return FALSE; | |
509 | } | |
510 | this->scheme = signature_scheme_from_oid(openssl_asn1_known_oid( | |
511 | this->crl->sig_alg->algorithm)); | |
512 | ||
513 | this->issuer = openssl_x509_name2id(X509_CRL_get_issuer(this->crl)); | |
514 | if (!this->issuer) | |
515 | { | |
516 | return FALSE; | |
517 | } | |
518 | this->thisUpdate = openssl_asn1_to_time(X509_CRL_get_lastUpdate(this->crl)); | |
519 | this->nextUpdate = openssl_asn1_to_time(X509_CRL_get_nextUpdate(this->crl)); | |
520 | ||
521 | return parse_extensions(this); | |
522 | } | |
523 | ||
524 | /** | |
525 | * Load the CRL. | |
526 | */ | |
527 | openssl_crl_t *openssl_crl_load(certificate_type_t type, va_list args) | |
528 | { | |
529 | chunk_t blob = chunk_empty; | |
530 | ||
531 | while (TRUE) | |
532 | { | |
533 | switch (va_arg(args, builder_part_t)) | |
534 | { | |
535 | case BUILD_BLOB_ASN1_DER: | |
536 | blob = va_arg(args, chunk_t); | |
537 | continue; | |
538 | case BUILD_END: | |
539 | break; | |
540 | default: | |
541 | return NULL; | |
542 | } | |
543 | break; | |
544 | } | |
545 | if (blob.ptr) | |
546 | { | |
547 | private_openssl_crl_t *this = create_empty(); | |
548 | ||
549 | this->encoding = chunk_clone(blob); | |
550 | if (parse_crl(this)) | |
551 | { | |
552 | return &this->public; | |
553 | } | |
554 | destroy(this); | |
555 | } | |
556 | return NULL; | |
557 | } |