]>
Commit | Line | Data |
---|---|---|
c2e5cee4 | 1 | /* |
13f76a24 | 2 | * Copyright (C) 2015-2018 Tobias Brunner |
c2e5cee4 MW |
3 | * Copyright (C) 2010 Martin Willi |
4 | * Copyright (C) 2010 revosec AG | |
5 | * Copyright (C) 2009 Andreas Steffen | |
13f76a24 | 6 | * HSR Hochschule fuer Technik Rapperswil |
c2e5cee4 MW |
7 | * |
8 | * This program is free software; you can redistribute it and/or modify it | |
9 | * under the terms of the GNU General Public License as published by the | |
10 | * Free Software Foundation; either version 2 of the License, or (at your | |
11 | * option) any later version. See <http://www.fsf.org/copyleft/gpl.txt>. | |
12 | * | |
13 | * This program is distributed in the hope that it will be useful, but | |
14 | * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY | |
15 | * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
16 | * for more details. | |
17 | */ | |
18 | ||
13f76a24 TB |
19 | #include <time.h> |
20 | ||
c2e5cee4 MW |
21 | #include "revocation_validator.h" |
22 | ||
f05b4272 | 23 | #include <utils/debug.h> |
c2e5cee4 MW |
24 | #include <credentials/certificates/x509.h> |
25 | #include <credentials/certificates/crl.h> | |
26 | #include <credentials/certificates/ocsp_request.h> | |
27 | #include <credentials/certificates/ocsp_response.h> | |
28 | #include <credentials/sets/ocsp_response_wrapper.h> | |
29 | #include <selectors/traffic_selector.h> | |
30 | ||
31 | typedef struct private_revocation_validator_t private_revocation_validator_t; | |
32 | ||
33 | /** | |
34 | * Private data of an revocation_validator_t object. | |
35 | */ | |
36 | struct private_revocation_validator_t { | |
37 | ||
38 | /** | |
39 | * Public revocation_validator_t interface. | |
40 | */ | |
41 | revocation_validator_t public; | |
e3f63c64 AS |
42 | |
43 | /** | |
2de9bb30 | 44 | * Enable OCSP validation |
e3f63c64 AS |
45 | */ |
46 | bool enable_ocsp; | |
47 | ||
48 | /** | |
2de9bb30 | 49 | * Enable CRL validation |
e3f63c64 AS |
50 | */ |
51 | bool enable_crl; | |
52 | ||
c2e5cee4 MW |
53 | }; |
54 | ||
55 | /** | |
56 | * Do an OCSP request | |
57 | */ | |
58 | static certificate_t *fetch_ocsp(char *url, certificate_t *subject, | |
59 | certificate_t *issuer) | |
60 | { | |
61 | certificate_t *request, *response; | |
62 | chunk_t send, receive; | |
63 | ||
64 | /* TODO: requestor name, signature */ | |
65 | request = lib->creds->create(lib->creds, | |
66 | CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST, | |
67 | BUILD_CA_CERT, issuer, | |
68 | BUILD_CERT, subject, BUILD_END); | |
69 | if (!request) | |
70 | { | |
71 | DBG1(DBG_CFG, "generating ocsp request failed"); | |
72 | return NULL; | |
73 | } | |
74 | ||
0406eeaa MW |
75 | if (!request->get_encoding(request, CERT_ASN1_DER, &send)) |
76 | { | |
77 | DBG1(DBG_CFG, "encoding ocsp request failed"); | |
78 | request->destroy(request); | |
79 | return NULL; | |
80 | } | |
c2e5cee4 MW |
81 | request->destroy(request); |
82 | ||
83 | DBG1(DBG_CFG, " requesting ocsp status from '%s' ...", url); | |
84 | if (lib->fetcher->fetch(lib->fetcher, url, &receive, | |
85 | FETCH_REQUEST_DATA, send, | |
86 | FETCH_REQUEST_TYPE, "application/ocsp-request", | |
87 | FETCH_END) != SUCCESS) | |
88 | { | |
89 | DBG1(DBG_CFG, "ocsp request to %s failed", url); | |
90 | chunk_free(&send); | |
91 | return NULL; | |
92 | } | |
93 | chunk_free(&send); | |
94 | ||
95 | response = lib->creds->create(lib->creds, | |
96 | CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE, | |
97 | BUILD_BLOB_ASN1_DER, receive, BUILD_END); | |
98 | chunk_free(&receive); | |
99 | if (!response) | |
100 | { | |
101 | DBG1(DBG_CFG, "parsing ocsp response failed"); | |
102 | return NULL; | |
103 | } | |
104 | return response; | |
105 | } | |
106 | ||
107 | /** | |
108 | * check the signature of an OCSP response | |
109 | */ | |
91d71abb | 110 | static bool verify_ocsp(ocsp_response_t *response, certificate_t *ca) |
c2e5cee4 MW |
111 | { |
112 | certificate_t *issuer, *subject; | |
113 | identification_t *responder; | |
114 | ocsp_response_wrapper_t *wrapper; | |
115 | enumerator_t *enumerator; | |
91d71abb MW |
116 | x509_t *x509; |
117 | bool verified = FALSE, found = FALSE; | |
c2e5cee4 MW |
118 | |
119 | wrapper = ocsp_response_wrapper_create((ocsp_response_t*)response); | |
747f837c | 120 | lib->credmgr->add_local_set(lib->credmgr, &wrapper->set, FALSE); |
c2e5cee4 MW |
121 | |
122 | subject = &response->certificate; | |
123 | responder = subject->get_issuer(subject); | |
91d71abb MW |
124 | |
125 | /* check OCSP response using CA or directly delegated OCSP signer */ | |
126 | enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, CERT_X509, | |
c2e5cee4 | 127 | KEY_ANY, responder, FALSE); |
91d71abb | 128 | while (enumerator->enumerate(enumerator, &issuer)) |
c2e5cee4 | 129 | { |
91d71abb MW |
130 | x509 = (x509_t*)issuer; |
131 | if (!issuer->get_validity(issuer, NULL, NULL, NULL)) | |
132 | { /* OCSP signer currently invalid */ | |
133 | continue; | |
134 | } | |
91d71abb MW |
135 | if (!ca->equals(ca, issuer)) |
136 | { /* delegated OCSP signer? */ | |
137 | if (!lib->credmgr->issued_by(lib->credmgr, issuer, ca, NULL)) | |
138 | { /* OCSP response not signed by CA, nor delegated OCSP signer */ | |
139 | continue; | |
140 | } | |
141 | if (!(x509->get_flags(x509) & X509_OCSP_SIGNER)) | |
142 | { /* delegated OCSP signer does not have OCSP signer flag */ | |
143 | continue; | |
144 | } | |
145 | } | |
94fb33bb | 146 | found = TRUE; |
fd4ff118 | 147 | if (lib->credmgr->issued_by(lib->credmgr, subject, issuer, NULL)) |
c2e5cee4 MW |
148 | { |
149 | DBG1(DBG_CFG, " ocsp response correctly signed by \"%Y\"", | |
91d71abb | 150 | issuer->get_subject(issuer)); |
c2e5cee4 MW |
151 | verified = TRUE; |
152 | break; | |
153 | } | |
91d71abb MW |
154 | DBG1(DBG_CFG, "ocsp response verification failed, " |
155 | "invalid signature"); | |
c2e5cee4 MW |
156 | } |
157 | enumerator->destroy(enumerator); | |
158 | ||
91d71abb MW |
159 | if (!verified) |
160 | { | |
161 | /* as fallback, use any locally installed OCSP signer certificate */ | |
162 | enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, | |
163 | CERT_X509, KEY_ANY, responder, TRUE); | |
164 | while (enumerator->enumerate(enumerator, &issuer)) | |
165 | { | |
166 | x509 = (x509_t*)issuer; | |
167 | /* while issued_by() accepts both OCSP signer or CA basic | |
168 | * constraint flags to verify OCSP responses, unrelated but trusted | |
169 | * OCSP signers must explicitly have the OCSP signer flag set. */ | |
170 | if ((x509->get_flags(x509) & X509_OCSP_SIGNER) && | |
171 | issuer->get_validity(issuer, NULL, NULL, NULL)) | |
172 | { | |
173 | found = TRUE; | |
174 | if (lib->credmgr->issued_by(lib->credmgr, subject, issuer, NULL)) | |
175 | { | |
176 | DBG1(DBG_CFG, " ocsp response correctly signed by \"%Y\"", | |
177 | issuer->get_subject(issuer)); | |
178 | verified = TRUE; | |
179 | break; | |
180 | } | |
181 | DBG1(DBG_CFG, "ocsp response verification failed, " | |
182 | "invalid signature"); | |
183 | } | |
184 | } | |
185 | enumerator->destroy(enumerator); | |
186 | } | |
187 | ||
c2e5cee4 MW |
188 | lib->credmgr->remove_local_set(lib->credmgr, &wrapper->set); |
189 | wrapper->destroy(wrapper); | |
91d71abb MW |
190 | |
191 | if (!found) | |
192 | { | |
193 | DBG1(DBG_CFG, "ocsp response verification failed, " | |
194 | "no signer certificate '%Y' found", responder); | |
195 | } | |
c2e5cee4 MW |
196 | return verified; |
197 | } | |
198 | ||
199 | /** | |
200 | * Get the better of two OCSP responses, and check for usable OCSP info | |
201 | */ | |
202 | static certificate_t *get_better_ocsp(certificate_t *cand, certificate_t *best, | |
a844b658 MW |
203 | x509_t *subject, x509_t *issuer, |
204 | cert_validation_t *valid, bool cache) | |
c2e5cee4 MW |
205 | { |
206 | ocsp_response_t *response; | |
207 | time_t revocation, this_update, next_update, valid_until; | |
208 | crl_reason_t reason; | |
209 | bool revoked = FALSE; | |
210 | ||
211 | response = (ocsp_response_t*)cand; | |
212 | ||
213 | /* check ocsp signature */ | |
91d71abb | 214 | if (!verify_ocsp(response, &issuer->interface)) |
c2e5cee4 | 215 | { |
c2e5cee4 MW |
216 | cand->destroy(cand); |
217 | return best; | |
218 | } | |
219 | /* check if response contains our certificate */ | |
220 | switch (response->get_status(response, subject, issuer, &revocation, &reason, | |
221 | &this_update, &next_update)) | |
222 | { | |
223 | case VALIDATION_REVOKED: | |
224 | /* subject has been revoked by a valid OCSP response */ | |
225 | DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N", | |
226 | &revocation, TRUE, crl_reason_names, reason); | |
227 | revoked = TRUE; | |
228 | break; | |
229 | case VALIDATION_GOOD: | |
230 | /* results in either good or stale */ | |
231 | break; | |
232 | default: | |
233 | case VALIDATION_FAILED: | |
234 | /* candidate unusable, does not contain our cert */ | |
235 | DBG1(DBG_CFG, " ocsp response contains no status on our certificate"); | |
236 | cand->destroy(cand); | |
237 | return best; | |
238 | } | |
239 | ||
240 | /* select the better of the two responses */ | |
241 | if (best == NULL || certificate_is_newer(cand, best)) | |
242 | { | |
243 | DESTROY_IF(best); | |
244 | best = cand; | |
245 | if (best->get_validity(best, NULL, NULL, &valid_until)) | |
246 | { | |
247 | DBG1(DBG_CFG, " ocsp response is valid: until %T", | |
248 | &valid_until, FALSE); | |
249 | *valid = VALIDATION_GOOD; | |
250 | if (cache) | |
251 | { /* cache non-stale only, stale certs get refetched */ | |
252 | lib->credmgr->cache_cert(lib->credmgr, best); | |
253 | } | |
254 | } | |
255 | else | |
256 | { | |
257 | DBG1(DBG_CFG, " ocsp response is stale: since %T", | |
258 | &valid_until, FALSE); | |
259 | *valid = VALIDATION_STALE; | |
260 | } | |
261 | } | |
262 | else | |
263 | { | |
264 | *valid = VALIDATION_STALE; | |
265 | cand->destroy(cand); | |
266 | } | |
267 | if (revoked) | |
268 | { /* revoked always counts, even if stale */ | |
269 | *valid = VALIDATION_REVOKED; | |
270 | } | |
271 | return best; | |
272 | } | |
273 | ||
274 | /** | |
275 | * validate a x509 certificate using OCSP | |
276 | */ | |
277 | static cert_validation_t check_ocsp(x509_t *subject, x509_t *issuer, | |
278 | auth_cfg_t *auth) | |
279 | { | |
280 | enumerator_t *enumerator; | |
281 | cert_validation_t valid = VALIDATION_SKIPPED; | |
282 | certificate_t *best = NULL, *current; | |
283 | identification_t *keyid = NULL; | |
284 | public_key_t *public; | |
285 | chunk_t chunk; | |
286 | char *uri = NULL; | |
287 | ||
288 | /** lookup cache for valid OCSP responses */ | |
289 | enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, | |
290 | CERT_X509_OCSP_RESPONSE, KEY_ANY, NULL, FALSE); | |
291 | while (enumerator->enumerate(enumerator, ¤t)) | |
292 | { | |
293 | current->get_ref(current); | |
a844b658 | 294 | best = get_better_ocsp(current, best, subject, issuer, &valid, FALSE); |
c2e5cee4 MW |
295 | if (best && valid != VALIDATION_STALE) |
296 | { | |
297 | DBG1(DBG_CFG, " using cached ocsp response"); | |
298 | break; | |
299 | } | |
300 | } | |
301 | enumerator->destroy(enumerator); | |
302 | ||
303 | /* derive the authorityKeyIdentifier from the issuer's public key */ | |
304 | current = &issuer->interface; | |
305 | public = current->get_public_key(current); | |
da9724e6 | 306 | if (public && public->get_fingerprint(public, KEYID_PUBKEY_SHA1, &chunk)) |
c2e5cee4 MW |
307 | { |
308 | keyid = identification_create_from_encoding(ID_KEY_ID, chunk); | |
309 | } | |
310 | /** fetch from configured OCSP responder URLs */ | |
311 | if (keyid && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED) | |
312 | { | |
313 | enumerator = lib->credmgr->create_cdp_enumerator(lib->credmgr, | |
314 | CERT_X509_OCSP_RESPONSE, keyid); | |
315 | while (enumerator->enumerate(enumerator, &uri)) | |
316 | { | |
317 | current = fetch_ocsp(uri, &subject->interface, &issuer->interface); | |
318 | if (current) | |
319 | { | |
320 | best = get_better_ocsp(current, best, subject, issuer, | |
a844b658 | 321 | &valid, TRUE); |
c2e5cee4 MW |
322 | if (best && valid != VALIDATION_STALE) |
323 | { | |
324 | break; | |
325 | } | |
326 | } | |
327 | } | |
328 | enumerator->destroy(enumerator); | |
329 | } | |
330 | DESTROY_IF(public); | |
331 | DESTROY_IF(keyid); | |
332 | ||
333 | /* fallback to URL fetching from subject certificate's URIs */ | |
334 | if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED) | |
335 | { | |
336 | enumerator = subject->create_ocsp_uri_enumerator(subject); | |
337 | while (enumerator->enumerate(enumerator, &uri)) | |
338 | { | |
339 | current = fetch_ocsp(uri, &subject->interface, &issuer->interface); | |
340 | if (current) | |
341 | { | |
342 | best = get_better_ocsp(current, best, subject, issuer, | |
a844b658 | 343 | &valid, TRUE); |
c2e5cee4 MW |
344 | if (best && valid != VALIDATION_STALE) |
345 | { | |
346 | break; | |
347 | } | |
348 | } | |
349 | } | |
350 | enumerator->destroy(enumerator); | |
351 | } | |
352 | /* an uri was found, but no result. switch validation state to failed */ | |
353 | if (valid == VALIDATION_SKIPPED && uri) | |
354 | { | |
355 | valid = VALIDATION_FAILED; | |
356 | } | |
357 | if (auth) | |
358 | { | |
359 | auth->add(auth, AUTH_RULE_OCSP_VALIDATION, valid); | |
360 | if (valid == VALIDATION_GOOD) | |
361 | { /* successful OCSP check fulfills also CRL constraint */ | |
362 | auth->add(auth, AUTH_RULE_CRL_VALIDATION, VALIDATION_GOOD); | |
363 | } | |
364 | } | |
365 | DESTROY_IF(best); | |
366 | return valid; | |
367 | } | |
368 | ||
369 | /** | |
370 | * fetch a CRL from an URL | |
371 | */ | |
372 | static certificate_t* fetch_crl(char *url) | |
373 | { | |
374 | certificate_t *crl; | |
375 | chunk_t chunk; | |
376 | ||
377 | DBG1(DBG_CFG, " fetching crl from '%s' ...", url); | |
378 | if (lib->fetcher->fetch(lib->fetcher, url, &chunk, FETCH_END) != SUCCESS) | |
379 | { | |
380 | DBG1(DBG_CFG, "crl fetching failed"); | |
381 | return NULL; | |
382 | } | |
383 | crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL, | |
e161238e | 384 | BUILD_BLOB_PEM, chunk, BUILD_END); |
c2e5cee4 MW |
385 | chunk_free(&chunk); |
386 | if (!crl) | |
387 | { | |
388 | DBG1(DBG_CFG, "crl fetched successfully but parsing failed"); | |
389 | return NULL; | |
390 | } | |
391 | return crl; | |
392 | } | |
393 | ||
394 | /** | |
395 | * check the signature of an CRL | |
396 | */ | |
a844b658 | 397 | static bool verify_crl(certificate_t *crl) |
c2e5cee4 MW |
398 | { |
399 | certificate_t *issuer; | |
400 | enumerator_t *enumerator; | |
401 | bool verified = FALSE; | |
402 | ||
403 | enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr, | |
404 | KEY_ANY, crl->get_issuer(crl), FALSE); | |
a844b658 | 405 | while (enumerator->enumerate(enumerator, &issuer, NULL)) |
c2e5cee4 | 406 | { |
fd4ff118 | 407 | if (lib->credmgr->issued_by(lib->credmgr, crl, issuer, NULL)) |
c2e5cee4 MW |
408 | { |
409 | DBG1(DBG_CFG, " crl correctly signed by \"%Y\"", | |
410 | issuer->get_subject(issuer)); | |
411 | verified = TRUE; | |
412 | break; | |
413 | } | |
414 | } | |
415 | enumerator->destroy(enumerator); | |
416 | ||
417 | return verified; | |
418 | } | |
419 | ||
cee01fc9 TB |
420 | /** |
421 | * Report the given CRL's validity and cache it if valid and requested | |
422 | */ | |
13f76a24 | 423 | static bool is_crl_valid(certificate_t *crl, time_t now, bool cache) |
cee01fc9 TB |
424 | { |
425 | time_t valid_until; | |
426 | ||
13f76a24 | 427 | if (crl->get_validity(crl, &now, NULL, &valid_until)) |
cee01fc9 TB |
428 | { |
429 | DBG1(DBG_CFG, " crl is valid: until %T", &valid_until, FALSE); | |
430 | if (cache) | |
431 | { | |
432 | lib->credmgr->cache_cert(lib->credmgr, crl); | |
433 | } | |
434 | return TRUE; | |
435 | } | |
436 | DBG1(DBG_CFG, " crl is stale: since %T", &valid_until, FALSE); | |
437 | return FALSE; | |
438 | } | |
439 | ||
13f76a24 TB |
440 | /** |
441 | * Check if the CRL should be used yet | |
442 | */ | |
443 | static bool is_crl_not_valid_yet(certificate_t *crl, time_t now) | |
444 | { | |
445 | time_t this_update; | |
446 | ||
447 | if (!crl->get_validity(crl, &now, &this_update, NULL)) | |
448 | { | |
449 | if (this_update > now) | |
450 | { | |
451 | DBG1(DBG_CFG, " crl is not valid: until %T", &this_update, FALSE); | |
452 | return TRUE; | |
453 | } | |
454 | /* we accept stale CRLs */ | |
455 | } | |
456 | return FALSE; | |
457 | } | |
458 | ||
c2e5cee4 MW |
459 | /** |
460 | * Get the better of two CRLs, and check for usable CRL info | |
461 | */ | |
462 | static certificate_t *get_better_crl(certificate_t *cand, certificate_t *best, | |
a844b658 | 463 | x509_t *subject, cert_validation_t *valid, |
7b3740d9 | 464 | bool cache, crl_t *base) |
c2e5cee4 MW |
465 | { |
466 | enumerator_t *enumerator; | |
13f76a24 | 467 | time_t now, revocation; |
c2e5cee4 | 468 | crl_reason_t reason; |
432358cf | 469 | chunk_t subject_serial, serial; |
7b3740d9 MW |
470 | crl_t *crl = (crl_t*)cand; |
471 | ||
472 | if (base) | |
473 | { | |
474 | if (!crl->is_delta_crl(crl, &serial) || | |
475 | !chunk_equals(serial, base->get_serial(base))) | |
476 | { | |
477 | cand->destroy(cand); | |
478 | return best; | |
479 | } | |
480 | } | |
481 | else | |
482 | { | |
483 | if (crl->is_delta_crl(crl, NULL)) | |
484 | { | |
485 | cand->destroy(cand); | |
486 | return best; | |
487 | } | |
488 | } | |
c2e5cee4 MW |
489 | |
490 | /* check CRL signature */ | |
a844b658 | 491 | if (!verify_crl(cand)) |
c2e5cee4 MW |
492 | { |
493 | DBG1(DBG_CFG, "crl response verification failed"); | |
494 | cand->destroy(cand); | |
495 | return best; | |
496 | } | |
13f76a24 TB |
497 | now = time(NULL); |
498 | if (is_crl_not_valid_yet(cand, now)) | |
499 | { | |
500 | cand->destroy(cand); | |
501 | return best; | |
502 | } | |
c2e5cee4 | 503 | |
432358cf | 504 | subject_serial = chunk_skip_zero(subject->get_serial(subject)); |
c2e5cee4 MW |
505 | enumerator = crl->create_enumerator(crl); |
506 | while (enumerator->enumerate(enumerator, &serial, &revocation, &reason)) | |
507 | { | |
432358cf | 508 | if (chunk_equals(subject_serial, chunk_skip_zero(serial))) |
c2e5cee4 | 509 | { |
6e5e2762 TE |
510 | if (reason != CRL_REASON_CERTIFICATE_HOLD) |
511 | { | |
512 | *valid = VALIDATION_REVOKED; | |
513 | } | |
514 | else | |
515 | { | |
516 | /* if the cert is on hold, a newer CRL might not contain it */ | |
517 | *valid = VALIDATION_ON_HOLD; | |
518 | } | |
13f76a24 | 519 | is_crl_valid(cand, now, cache); |
cee01fc9 TB |
520 | DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N", |
521 | &revocation, TRUE, crl_reason_names, reason); | |
c2e5cee4 MW |
522 | enumerator->destroy(enumerator); |
523 | DESTROY_IF(best); | |
524 | return cand; | |
525 | } | |
526 | } | |
527 | enumerator->destroy(enumerator); | |
528 | ||
529 | /* select the better of the two CRLs */ | |
530 | if (best == NULL || crl_is_newer(crl, (crl_t*)best)) | |
531 | { | |
532 | DESTROY_IF(best); | |
533 | best = cand; | |
13f76a24 | 534 | if (is_crl_valid(best, now, cache)) |
c2e5cee4 | 535 | { |
c2e5cee4 | 536 | *valid = VALIDATION_GOOD; |
c2e5cee4 MW |
537 | } |
538 | else | |
539 | { | |
c2e5cee4 MW |
540 | *valid = VALIDATION_STALE; |
541 | } | |
542 | } | |
543 | else | |
544 | { | |
545 | *valid = VALIDATION_STALE; | |
546 | cand->destroy(cand); | |
547 | } | |
548 | return best; | |
549 | } | |
550 | ||
551 | /** | |
7d7beaa1 | 552 | * Find or fetch a certificate for a given crlIssuer |
c2e5cee4 | 553 | */ |
7d7beaa1 | 554 | static cert_validation_t find_crl(x509_t *subject, identification_t *issuer, |
a844b658 MW |
555 | crl_t *base, certificate_t **best, |
556 | bool *uri_found) | |
c2e5cee4 MW |
557 | { |
558 | cert_validation_t valid = VALIDATION_SKIPPED; | |
c2e5cee4 | 559 | enumerator_t *enumerator; |
7d7beaa1 MW |
560 | certificate_t *current; |
561 | char *uri; | |
c2e5cee4 | 562 | |
7b3740d9 | 563 | /* find a cached (delta) crl */ |
7d7beaa1 MW |
564 | enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr, |
565 | CERT_X509_CRL, KEY_ANY, issuer, FALSE); | |
566 | while (enumerator->enumerate(enumerator, ¤t)) | |
c2e5cee4 | 567 | { |
7d7beaa1 | 568 | current->get_ref(current); |
a844b658 | 569 | *best = get_better_crl(current, *best, subject, &valid, FALSE, base); |
7d7beaa1 | 570 | if (*best && valid != VALIDATION_STALE) |
c2e5cee4 | 571 | { |
7d7beaa1 MW |
572 | DBG1(DBG_CFG, " using cached crl"); |
573 | break; | |
c2e5cee4 | 574 | } |
7d7beaa1 MW |
575 | } |
576 | enumerator->destroy(enumerator); | |
c2e5cee4 | 577 | |
7d7beaa1 | 578 | /* fallback to fetching crls from credential sets cdps */ |
7b3740d9 | 579 | if (!base && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED) |
7d7beaa1 MW |
580 | { |
581 | enumerator = lib->credmgr->create_cdp_enumerator(lib->credmgr, | |
582 | CERT_X509_CRL, issuer); | |
583 | while (enumerator->enumerate(enumerator, &uri)) | |
c2e5cee4 | 584 | { |
7d7beaa1 MW |
585 | *uri_found = TRUE; |
586 | current = fetch_crl(uri); | |
7d7beaa1 | 587 | if (current) |
c2e5cee4 | 588 | { |
d3a18dad MW |
589 | if (!current->has_issuer(current, issuer)) |
590 | { | |
591 | DBG1(DBG_CFG, "issuer of fetched CRL '%Y' does not match CRL " | |
592 | "issuer '%Y'", current->get_issuer(current), issuer); | |
593 | current->destroy(current); | |
594 | continue; | |
595 | } | |
6840a6fb | 596 | *best = get_better_crl(current, *best, subject, |
a844b658 | 597 | &valid, TRUE, base); |
7d7beaa1 | 598 | if (*best && valid != VALIDATION_STALE) |
c2e5cee4 | 599 | { |
7d7beaa1 | 600 | break; |
c2e5cee4 MW |
601 | } |
602 | } | |
c2e5cee4 | 603 | } |
7d7beaa1 | 604 | enumerator->destroy(enumerator); |
c2e5cee4 | 605 | } |
7d7beaa1 MW |
606 | return valid; |
607 | } | |
608 | ||
21553276 TB |
609 | /** |
610 | * Check if the issuer of the given CRL matches | |
611 | */ | |
612 | static bool check_issuer(certificate_t *crl, x509_t *issuer, x509_cdp_t *cdp) | |
613 | { | |
614 | certificate_t *cissuer = (certificate_t*)issuer; | |
615 | identification_t *id; | |
616 | chunk_t chunk; | |
617 | bool matches = FALSE; | |
618 | ||
619 | if (cdp->issuer) | |
620 | { | |
621 | return crl->has_issuer(crl, cdp->issuer); | |
622 | } | |
623 | /* check SKI/AKI first, but fall back to DN matching */ | |
624 | chunk = issuer->get_subjectKeyIdentifier(issuer); | |
625 | if (chunk.len) | |
626 | { | |
627 | id = identification_create_from_encoding(ID_KEY_ID, chunk); | |
628 | matches = crl->has_issuer(crl, id); | |
629 | id->destroy(id); | |
630 | } | |
631 | return matches || crl->has_issuer(crl, cissuer->get_subject(cissuer)); | |
632 | } | |
633 | ||
7b3740d9 MW |
634 | /** |
635 | * Look for a delta CRL for a given base CRL | |
636 | */ | |
637 | static cert_validation_t check_delta_crl(x509_t *subject, x509_t *issuer, | |
a844b658 | 638 | crl_t *base, cert_validation_t base_valid) |
7b3740d9 MW |
639 | { |
640 | cert_validation_t valid = VALIDATION_SKIPPED; | |
21553276 | 641 | certificate_t *best = NULL, *current, *cissuer = (certificate_t*)issuer; |
7b3740d9 MW |
642 | enumerator_t *enumerator; |
643 | identification_t *id; | |
644 | x509_cdp_t *cdp; | |
645 | chunk_t chunk; | |
646 | bool uri; | |
647 | ||
648 | /* find cached delta CRL via subjectKeyIdentifier */ | |
649 | chunk = issuer->get_subjectKeyIdentifier(issuer); | |
650 | if (chunk.len) | |
651 | { | |
652 | id = identification_create_from_encoding(ID_KEY_ID, chunk); | |
a844b658 | 653 | valid = find_crl(subject, id, base, &best, &uri); |
7b3740d9 MW |
654 | id->destroy(id); |
655 | } | |
656 | ||
657 | /* find delta CRL by CRLIssuer */ | |
658 | enumerator = subject->create_crl_uri_enumerator(subject); | |
659 | while (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED && | |
660 | enumerator->enumerate(enumerator, &cdp)) | |
661 | { | |
662 | if (cdp->issuer) | |
663 | { | |
a844b658 | 664 | valid = find_crl(subject, cdp->issuer, base, &best, &uri); |
7b3740d9 MW |
665 | } |
666 | } | |
667 | enumerator->destroy(enumerator); | |
668 | ||
669 | /* fetch from URIs found in Freshest CRL extension */ | |
670 | enumerator = base->create_delta_crl_uri_enumerator(base); | |
671 | while (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED && | |
672 | enumerator->enumerate(enumerator, &cdp)) | |
673 | { | |
674 | current = fetch_crl(cdp->uri); | |
675 | if (current) | |
676 | { | |
21553276 | 677 | if (!check_issuer(current, issuer, cdp)) |
7b3740d9 MW |
678 | { |
679 | DBG1(DBG_CFG, "issuer of fetched delta CRL '%Y' does not match " | |
21553276 TB |
680 | "certificate's %sissuer '%Y'", |
681 | current->get_issuer(current), cdp->issuer ? "CRL " : "", | |
682 | cdp->issuer ?: cissuer->get_subject(cissuer)); | |
7b3740d9 MW |
683 | current->destroy(current); |
684 | continue; | |
685 | } | |
a844b658 | 686 | best = get_better_crl(current, best, subject, &valid, TRUE, base); |
7b3740d9 MW |
687 | if (best && valid != VALIDATION_STALE) |
688 | { | |
689 | break; | |
690 | } | |
691 | } | |
692 | } | |
693 | enumerator->destroy(enumerator); | |
694 | ||
695 | if (best) | |
696 | { | |
697 | best->destroy(best); | |
698 | return valid; | |
699 | } | |
700 | return base_valid; | |
701 | } | |
702 | ||
7d7beaa1 MW |
703 | /** |
704 | * validate a x509 certificate using CRL | |
705 | */ | |
706 | static cert_validation_t check_crl(x509_t *subject, x509_t *issuer, | |
707 | auth_cfg_t *auth) | |
708 | { | |
709 | cert_validation_t valid = VALIDATION_SKIPPED; | |
21553276 | 710 | certificate_t *best = NULL, *cissuer = (certificate_t*)issuer; |
a6478a04 MW |
711 | identification_t *id; |
712 | x509_cdp_t *cdp; | |
7d7beaa1 MW |
713 | bool uri_found = FALSE; |
714 | certificate_t *current; | |
715 | enumerator_t *enumerator; | |
716 | chunk_t chunk; | |
7d7beaa1 MW |
717 | |
718 | /* use issuers subjectKeyIdentifier to find a cached CRL / fetch from CDP */ | |
719 | chunk = issuer->get_subjectKeyIdentifier(issuer); | |
720 | if (chunk.len) | |
721 | { | |
722 | id = identification_create_from_encoding(ID_KEY_ID, chunk); | |
a844b658 | 723 | valid = find_crl(subject, id, NULL, &best, &uri_found); |
7d7beaa1 MW |
724 | id->destroy(id); |
725 | } | |
726 | ||
727 | /* find a cached CRL or fetch via configured CDP via CRLIssuer */ | |
728 | enumerator = subject->create_crl_uri_enumerator(subject); | |
729 | while (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED && | |
a6478a04 | 730 | enumerator->enumerate(enumerator, &cdp)) |
7d7beaa1 | 731 | { |
7b3740d9 | 732 | if (cdp->issuer) |
7d7beaa1 | 733 | { |
a844b658 | 734 | valid = find_crl(subject, cdp->issuer, NULL, &best, &uri_found); |
7d7beaa1 MW |
735 | } |
736 | } | |
737 | enumerator->destroy(enumerator); | |
c2e5cee4 | 738 | |
7d7beaa1 | 739 | /* fallback to fetching CRLs from CDPs found in subjects certificate */ |
c2e5cee4 MW |
740 | if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED) |
741 | { | |
742 | enumerator = subject->create_crl_uri_enumerator(subject); | |
a6478a04 | 743 | while (enumerator->enumerate(enumerator, &cdp)) |
c2e5cee4 | 744 | { |
7d7beaa1 | 745 | uri_found = TRUE; |
a6478a04 | 746 | current = fetch_crl(cdp->uri); |
c2e5cee4 MW |
747 | if (current) |
748 | { | |
21553276 | 749 | if (!check_issuer(current, issuer, cdp)) |
7d7beaa1 MW |
750 | { |
751 | DBG1(DBG_CFG, "issuer of fetched CRL '%Y' does not match " | |
21553276 TB |
752 | "certificate's %sissuer '%Y'", |
753 | current->get_issuer(current), cdp->issuer ? "CRL " : "", | |
754 | cdp->issuer ?: cissuer->get_subject(cissuer)); | |
7d7beaa1 MW |
755 | current->destroy(current); |
756 | continue; | |
757 | } | |
6840a6fb | 758 | best = get_better_crl(current, best, subject, &valid, |
a844b658 | 759 | TRUE, NULL); |
c2e5cee4 MW |
760 | if (best && valid != VALIDATION_STALE) |
761 | { | |
762 | break; | |
763 | } | |
764 | } | |
765 | } | |
766 | enumerator->destroy(enumerator); | |
767 | } | |
768 | ||
7b3740d9 MW |
769 | /* look for delta CRLs */ |
770 | if (best && (valid == VALIDATION_GOOD || valid == VALIDATION_STALE)) | |
771 | { | |
a844b658 | 772 | valid = check_delta_crl(subject, issuer, (crl_t*)best, valid); |
7b3740d9 MW |
773 | } |
774 | ||
c2e5cee4 | 775 | /* an uri was found, but no result. switch validation state to failed */ |
7d7beaa1 | 776 | if (valid == VALIDATION_SKIPPED && uri_found) |
c2e5cee4 MW |
777 | { |
778 | valid = VALIDATION_FAILED; | |
779 | } | |
780 | if (auth) | |
781 | { | |
782 | if (valid == VALIDATION_SKIPPED) | |
783 | { /* if we skipped CRL validation, we use the result of OCSP for | |
784 | * constraint checking */ | |
785 | auth->add(auth, AUTH_RULE_CRL_VALIDATION, | |
786 | auth->get(auth, AUTH_RULE_OCSP_VALIDATION)); | |
787 | } | |
788 | else | |
789 | { | |
790 | auth->add(auth, AUTH_RULE_CRL_VALIDATION, valid); | |
791 | } | |
792 | } | |
793 | DESTROY_IF(best); | |
794 | return valid; | |
795 | } | |
796 | ||
797 | METHOD(cert_validator_t, validate, bool, | |
798 | private_revocation_validator_t *this, certificate_t *subject, | |
d390b3b9 | 799 | certificate_t *issuer, bool online, u_int pathlen, bool anchor, |
6aba6ff0 | 800 | auth_cfg_t *auth) |
c2e5cee4 | 801 | { |
2de9bb30 TB |
802 | if (online && (this->enable_ocsp || this->enable_crl) && |
803 | subject->get_type(subject) == CERT_X509 && | |
804 | issuer->get_type(issuer) == CERT_X509) | |
c2e5cee4 MW |
805 | { |
806 | DBG1(DBG_CFG, "checking certificate status of \"%Y\"", | |
807 | subject->get_subject(subject)); | |
e3f63c64 AS |
808 | |
809 | if (this->enable_ocsp) | |
c2e5cee4 | 810 | { |
e3f63c64 AS |
811 | switch (check_ocsp((x509_t*)subject, (x509_t*)issuer, |
812 | pathlen ? NULL : auth)) | |
813 | { | |
814 | case VALIDATION_GOOD: | |
815 | DBG1(DBG_CFG, "certificate status is good"); | |
816 | return TRUE; | |
817 | case VALIDATION_REVOKED: | |
818 | case VALIDATION_ON_HOLD: | |
819 | /* has already been logged */ | |
820 | lib->credmgr->call_hook(lib->credmgr, CRED_HOOK_REVOKED, | |
821 | subject); | |
822 | return FALSE; | |
823 | case VALIDATION_SKIPPED: | |
824 | DBG2(DBG_CFG, "ocsp check skipped, no ocsp found"); | |
825 | break; | |
826 | case VALIDATION_STALE: | |
827 | DBG1(DBG_CFG, "ocsp information stale, fallback to crl"); | |
828 | break; | |
829 | case VALIDATION_FAILED: | |
830 | DBG1(DBG_CFG, "ocsp check failed, fallback to crl"); | |
831 | break; | |
832 | } | |
c2e5cee4 | 833 | } |
e3f63c64 AS |
834 | |
835 | if (this->enable_crl) | |
c2e5cee4 | 836 | { |
e3f63c64 AS |
837 | switch (check_crl((x509_t*)subject, (x509_t*)issuer, |
838 | pathlen ? NULL : auth)) | |
839 | { | |
840 | case VALIDATION_GOOD: | |
841 | DBG1(DBG_CFG, "certificate status is good"); | |
842 | return TRUE; | |
843 | case VALIDATION_REVOKED: | |
844 | case VALIDATION_ON_HOLD: | |
845 | /* has already been logged */ | |
846 | lib->credmgr->call_hook(lib->credmgr, CRED_HOOK_REVOKED, | |
847 | subject); | |
848 | return FALSE; | |
849 | case VALIDATION_FAILED: | |
850 | case VALIDATION_SKIPPED: | |
851 | DBG1(DBG_CFG, "certificate status is not available"); | |
852 | break; | |
853 | case VALIDATION_STALE: | |
854 | DBG1(DBG_CFG, "certificate status is unknown, crl is stale"); | |
855 | break; | |
856 | } | |
c2e5cee4 | 857 | } |
e3f63c64 | 858 | |
4d7a7628 MW |
859 | lib->credmgr->call_hook(lib->credmgr, CRED_HOOK_VALIDATION_FAILED, |
860 | subject); | |
c2e5cee4 MW |
861 | } |
862 | return TRUE; | |
863 | } | |
864 | ||
865 | METHOD(revocation_validator_t, destroy, void, | |
866 | private_revocation_validator_t *this) | |
867 | { | |
868 | free(this); | |
869 | } | |
870 | ||
871 | /** | |
872 | * See header | |
873 | */ | |
874 | revocation_validator_t *revocation_validator_create() | |
875 | { | |
876 | private_revocation_validator_t *this; | |
877 | ||
878 | INIT(this, | |
879 | .public = { | |
880 | .validator.validate = _validate, | |
881 | .destroy = _destroy, | |
882 | }, | |
e3f63c64 AS |
883 | .enable_ocsp = lib->settings->get_bool(lib->settings, |
884 | "%s.plugins.revocation.enable_ocsp", TRUE, lib->ns), | |
885 | .enable_crl = lib->settings->get_bool(lib->settings, | |
886 | "%s.plugins.revocation.enable_crl", TRUE, lib->ns), | |
c2e5cee4 MW |
887 | ); |
888 | ||
e3f63c64 AS |
889 | if (!this->enable_ocsp) |
890 | { | |
2de9bb30 | 891 | DBG1(DBG_LIB, "all OCSP validation disabled"); |
e3f63c64 AS |
892 | } |
893 | if (!this->enable_crl) | |
894 | { | |
2de9bb30 | 895 | DBG1(DBG_LIB, "all CRL validation disabled"); |
e3f63c64 | 896 | } |
c2e5cee4 MW |
897 | return &this->public; |
898 | } |