b5762b54ccbbbfcb613b2e6576cb19d3565970cc
[strongswan.git] / src / libstrongswan / plugins / constraints / constraints_validator.c
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 #include "constraints_validator.h"
17
18 #include <utils/debug.h>
19 #include <asn1/asn1.h>
20 #include <collections/linked_list.h>
21 #include <credentials/certificates/x509.h>
22
23 typedef struct private_constraints_validator_t private_constraints_validator_t;
24
25 /**
26 * Private data of an constraints_validator_t object.
27 */
28 struct private_constraints_validator_t {
29
30 /**
31 * Public constraints_validator_t interface.
32 */
33 constraints_validator_t public;
34 };
35
36 /**
37 * Check pathlen constraint of issuer certificate
38 */
39 static bool check_pathlen(x509_t *issuer, int pathlen)
40 {
41 u_int pathlen_constraint;
42
43 pathlen_constraint = issuer->get_constraint(issuer, X509_PATH_LEN);
44 if (pathlen_constraint != X509_NO_CONSTRAINT &&
45 pathlen > pathlen_constraint)
46 {
47 DBG1(DBG_CFG, "path length of %d violates constraint of %d",
48 pathlen, pathlen_constraint);
49 return FALSE;
50 }
51 return TRUE;
52 }
53
54 /**
55 * Check if a FQDN/RFC822 constraint matches (suffix match)
56 */
57 static bool suffix_matches(identification_t *constraint, identification_t *id)
58 {
59 chunk_t c, i;
60
61 c = constraint->get_encoding(constraint);
62 i = id->get_encoding(id);
63
64 return i.len >= c.len && chunk_equals(c, chunk_skip(i, i.len - c.len));
65 }
66
67 /**
68 * Check if a DN constraint matches (RDN prefix match)
69 */
70 static bool dn_matches(identification_t *constraint, identification_t *id)
71 {
72 enumerator_t *ec, *ei;
73 id_part_t pc, pi;
74 chunk_t cc, ci;
75 bool match = TRUE;
76
77 ec = constraint->create_part_enumerator(constraint);
78 ei = id->create_part_enumerator(id);
79 while (ec->enumerate(ec, &pc, &cc))
80 {
81 if (!ei->enumerate(ei, &pi, &ci) ||
82 pi != pc || !chunk_equals(cc, ci))
83 {
84 match = FALSE;
85 break;
86 }
87 }
88 ec->destroy(ec);
89 ei->destroy(ei);
90
91 return match;
92 }
93
94 /**
95 * Check if a certificate matches to a NameConstraint
96 */
97 static bool name_constraint_matches(identification_t *constraint,
98 certificate_t *cert, bool permitted)
99 {
100 x509_t *x509 = (x509_t*)cert;
101 enumerator_t *enumerator;
102 identification_t *id;
103 id_type_t type;
104 bool matches = permitted;
105
106 type = constraint->get_type(constraint);
107 if (type == ID_DER_ASN1_DN)
108 {
109 matches = dn_matches(constraint, cert->get_subject(cert));
110 if (matches != permitted)
111 {
112 return matches;
113 }
114 }
115
116 enumerator = x509->create_subjectAltName_enumerator(x509);
117 while (enumerator->enumerate(enumerator, &id))
118 {
119 if (id->get_type(id) == type)
120 {
121 switch (type)
122 {
123 case ID_FQDN:
124 case ID_RFC822_ADDR:
125 matches = suffix_matches(constraint, id);
126 break;
127 case ID_DER_ASN1_DN:
128 matches = dn_matches(constraint, id);
129 break;
130 default:
131 DBG1(DBG_CFG, "%N NameConstraint matching not implemented",
132 id_type_names, type);
133 matches = FALSE;
134 break;
135 }
136 }
137 if (matches != permitted)
138 {
139 break;
140 }
141 }
142 enumerator->destroy(enumerator);
143
144 return matches;
145 }
146
147 /**
148 * Check if a permitted or excluded NameConstraint has been inherited to sub-CA
149 */
150 static bool name_constraint_inherited(identification_t *constraint,
151 x509_t *x509, bool permitted)
152 {
153 enumerator_t *enumerator;
154 identification_t *id;
155 bool inherited = FALSE;
156 id_type_t type;
157
158 if (!(x509->get_flags(x509) & X509_CA))
159 { /* not a sub-CA, not required */
160 return TRUE;
161 }
162
163 type = constraint->get_type(constraint);
164 enumerator = x509->create_name_constraint_enumerator(x509, permitted);
165 while (enumerator->enumerate(enumerator, &id))
166 {
167 if (id->get_type(id) == type)
168 {
169 switch (type)
170 {
171 case ID_FQDN:
172 case ID_RFC822_ADDR:
173 if (permitted)
174 { /* permitted constraint can be narrowed */
175 inherited = suffix_matches(constraint, id);
176 }
177 else
178 { /* excluded constraint can be widened */
179 inherited = suffix_matches(id, constraint);
180 }
181 break;
182 case ID_DER_ASN1_DN:
183 if (permitted)
184 {
185 inherited = dn_matches(constraint, id);
186 }
187 else
188 {
189 inherited = dn_matches(id, constraint);
190 }
191 break;
192 default:
193 DBG1(DBG_CFG, "%N NameConstraint matching not implemented",
194 id_type_names, type);
195 inherited = FALSE;
196 break;
197 }
198 }
199 if (inherited)
200 {
201 break;
202 }
203 }
204 enumerator->destroy(enumerator);
205 return inherited;
206 }
207
208 /**
209 * Check name constraints
210 */
211 static bool check_name_constraints(certificate_t *subject, x509_t *issuer)
212 {
213 enumerator_t *enumerator;
214 identification_t *constraint;
215
216 enumerator = issuer->create_name_constraint_enumerator(issuer, TRUE);
217 while (enumerator->enumerate(enumerator, &constraint))
218 {
219 if (!name_constraint_matches(constraint, subject, TRUE))
220 {
221 DBG1(DBG_CFG, "certificate '%Y' does not match permitted name "
222 "constraint '%Y'", subject->get_subject(subject), constraint);
223 enumerator->destroy(enumerator);
224 return FALSE;
225 }
226 if (!name_constraint_inherited(constraint, (x509_t*)subject, TRUE))
227 {
228 DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit permitted name "
229 "constraint '%Y'", subject->get_subject(subject), constraint);
230 enumerator->destroy(enumerator);
231 return FALSE;
232 }
233 }
234 enumerator->destroy(enumerator);
235
236 enumerator = issuer->create_name_constraint_enumerator(issuer, FALSE);
237 while (enumerator->enumerate(enumerator, &constraint))
238 {
239 if (name_constraint_matches(constraint, subject, FALSE))
240 {
241 DBG1(DBG_CFG, "certificate '%Y' matches excluded name "
242 "constraint '%Y'", subject->get_subject(subject), constraint);
243 enumerator->destroy(enumerator);
244 return FALSE;
245 }
246 if (!name_constraint_inherited(constraint, (x509_t*)subject, FALSE))
247 {
248 DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit excluded name "
249 "constraint '%Y'", subject->get_subject(subject), constraint);
250 enumerator->destroy(enumerator);
251 return FALSE;
252 }
253 }
254 enumerator->destroy(enumerator);
255 return TRUE;
256 }
257
258 /**
259 * Special OID for anyPolicy
260 */
261 static chunk_t any_policy = chunk_from_chars(0x55,0x1d,0x20,0x00);
262
263 /**
264 * Check if an issuer certificate has a given policy OID
265 */
266 static bool has_policy(x509_t *issuer, chunk_t oid)
267 {
268 x509_policy_mapping_t *mapping;
269 x509_cert_policy_t *policy;
270 enumerator_t *enumerator;
271
272 enumerator = issuer->create_cert_policy_enumerator(issuer);
273 while (enumerator->enumerate(enumerator, &policy))
274 {
275 if (chunk_equals(oid, policy->oid) ||
276 chunk_equals(any_policy, policy->oid))
277 {
278 enumerator->destroy(enumerator);
279 return TRUE;
280 }
281 }
282 enumerator->destroy(enumerator);
283
284 /* fall back to a mapped policy */
285 enumerator = issuer->create_policy_mapping_enumerator(issuer);
286 while (enumerator->enumerate(enumerator, &mapping))
287 {
288 if (chunk_equals(mapping->subject, oid))
289 {
290 enumerator->destroy(enumerator);
291 return TRUE;
292 }
293 }
294 enumerator->destroy(enumerator);
295 return FALSE;
296 }
297
298 /**
299 * Check certificatePolicies.
300 */
301 static bool check_policy(x509_t *subject, x509_t *issuer)
302 {
303 certificate_t *cert = (certificate_t*)subject;
304 x509_policy_mapping_t *mapping;
305 x509_cert_policy_t *policy;
306 enumerator_t *enumerator;
307 char *oid;
308
309 /* verify if policyMappings in subject are valid */
310 enumerator = subject->create_policy_mapping_enumerator(subject);
311 while (enumerator->enumerate(enumerator, &mapping))
312 {
313 if (!has_policy(issuer, mapping->issuer))
314 {
315 oid = asn1_oid_to_string(mapping->issuer);
316 DBG1(DBG_CFG, "certificate '%Y' maps policy from %s, but issuer "
317 "misses it", cert->get_subject(cert), oid);
318 free(oid);
319 enumerator->destroy(enumerator);
320 return FALSE;
321 }
322 }
323 enumerator->destroy(enumerator);
324
325 enumerator = subject->create_cert_policy_enumerator(subject);
326 while (enumerator->enumerate(enumerator, &policy))
327 {
328 if (!has_policy(issuer, policy->oid))
329 {
330 oid = asn1_oid_to_string(policy->oid);
331 DBG1(DBG_CFG, "policy %s missing in issuing certificate '%Y'",
332 oid, cert->get_issuer(cert));
333 free(oid);
334 enumerator->destroy(enumerator);
335 return FALSE;
336 }
337 }
338 enumerator->destroy(enumerator);
339
340 return TRUE;
341 }
342
343 /**
344 * Check if a given policy is valid under a trustchain
345 */
346 static bool is_policy_valid(linked_list_t *chain, chunk_t oid)
347 {
348 x509_policy_mapping_t *mapping;
349 x509_cert_policy_t *policy;
350 x509_t *issuer;
351 enumerator_t *issuers, *policies, *mappings;
352 bool found = TRUE;
353
354 issuers = chain->create_enumerator(chain);
355 while (issuers->enumerate(issuers, &issuer))
356 {
357 int maxmap = 8;
358
359 while (found)
360 {
361 found = FALSE;
362
363 policies = issuer->create_cert_policy_enumerator(issuer);
364 while (policies->enumerate(policies, &policy))
365 {
366 if (chunk_equals(oid, policy->oid) ||
367 chunk_equals(any_policy, policy->oid))
368 {
369 found = TRUE;
370 break;
371 }
372 }
373 policies->destroy(policies);
374 if (found)
375 {
376 break;
377 }
378 /* fall back to a mapped policy */
379 mappings = issuer->create_policy_mapping_enumerator(issuer);
380 while (mappings->enumerate(mappings, &mapping))
381 {
382 if (chunk_equals(mapping->subject, oid))
383 {
384 oid = mapping->issuer;
385 found = TRUE;
386 break;
387 }
388 }
389 mappings->destroy(mappings);
390 if (--maxmap == 0)
391 {
392 found = FALSE;
393 break;
394 }
395 }
396 if (!found)
397 {
398 break;
399 }
400 }
401 issuers->destroy(issuers);
402
403 return found;
404 }
405
406 /**
407 * Check len certificates in trustchain for inherited policies
408 */
409 static bool has_policy_chain(linked_list_t *chain, x509_t *subject, int len)
410 {
411 enumerator_t *enumerator;
412 x509_t *issuer;
413 bool valid = TRUE;
414
415 enumerator = chain->create_enumerator(chain);
416 while (len-- > 0 && enumerator->enumerate(enumerator, &issuer))
417 {
418 if (!check_policy(subject, issuer))
419 {
420 valid = FALSE;
421 break;
422 }
423 subject = issuer;
424 }
425 enumerator->destroy(enumerator);
426 return valid;
427 }
428
429 /**
430 * Check len certificates in trustchain to have no policyMappings
431 */
432 static bool has_no_policy_mapping(linked_list_t *chain, int len)
433 {
434 enumerator_t *enumerator, *mappings;
435 x509_policy_mapping_t *mapping;
436 certificate_t *cert;
437 x509_t *x509;
438 bool valid = TRUE;
439
440 enumerator = chain->create_enumerator(chain);
441 while (len-- > 0 && enumerator->enumerate(enumerator, &x509))
442 {
443 mappings = x509->create_policy_mapping_enumerator(x509);
444 valid = !mappings->enumerate(mappings, &mapping);
445 mappings->destroy(mappings);
446 if (!valid)
447 {
448 cert = (certificate_t*)x509;
449 DBG1(DBG_CFG, "found policyMapping in certificate '%Y', but "
450 "inhibitPolicyMapping in effect", cert->get_subject(cert));
451 break;
452 }
453 }
454 enumerator->destroy(enumerator);
455 return valid;
456 }
457
458 /**
459 * Check len certificates in trustchain to have no anyPolicies
460 */
461 static bool has_no_any_policy(linked_list_t *chain, int len)
462 {
463 enumerator_t *enumerator, *policies;
464 x509_cert_policy_t *policy;
465 certificate_t *cert;
466 x509_t *x509;
467 bool valid = TRUE;
468
469 enumerator = chain->create_enumerator(chain);
470 while (len-- > 0 && enumerator->enumerate(enumerator, &x509))
471 {
472 policies = x509->create_cert_policy_enumerator(x509);
473 while (policies->enumerate(policies, &policy))
474 {
475 if (chunk_equals(policy->oid, any_policy))
476 {
477 cert = (certificate_t*)x509;
478 DBG1(DBG_CFG, "found anyPolicy in certificate '%Y', but "
479 "inhibitAnyPolicy in effect", cert->get_subject(cert));
480 valid = FALSE;
481 break;
482 }
483 }
484 policies->destroy(policies);
485 }
486 enumerator->destroy(enumerator);
487 return valid;
488 }
489
490 /**
491 * Check requireExplicitPolicy and inhibitPolicyMapping constraints
492 */
493 static bool check_policy_constraints(x509_t *issuer, u_int pathlen,
494 auth_cfg_t *auth)
495 {
496 certificate_t *subject;
497 bool valid = TRUE;
498
499 subject = auth->get(auth, AUTH_RULE_SUBJECT_CERT);
500 if (subject)
501 {
502 if (subject->get_type(subject) == CERT_X509)
503 {
504 x509_cert_policy_t *policy;
505 enumerator_t *enumerator;
506 linked_list_t *chain;
507 certificate_t *cert;
508 auth_rule_t rule;
509 x509_t *x509;
510 int len = 0;
511 u_int expl, inh;
512 char *oid;
513
514 /* prepare trustchain to validate */
515 chain = linked_list_create();
516 enumerator = auth->create_enumerator(auth);
517 while (enumerator->enumerate(enumerator, &rule, &cert))
518 {
519 if (rule == AUTH_RULE_IM_CERT &&
520 cert->get_type(cert) == CERT_X509)
521 {
522 chain->insert_last(chain, cert);
523 }
524 }
525 enumerator->destroy(enumerator);
526 chain->insert_last(chain, issuer);
527
528 /* search for requireExplicitPolicy constraints */
529 enumerator = chain->create_enumerator(chain);
530 while (enumerator->enumerate(enumerator, &x509))
531 {
532 expl = x509->get_constraint(x509, X509_REQUIRE_EXPLICIT_POLICY);
533 if (expl != X509_NO_CONSTRAINT)
534 {
535 if (!has_policy_chain(chain, (x509_t*)subject, len - expl))
536 {
537 valid = FALSE;
538 break;
539 }
540 }
541 len++;
542 }
543 enumerator->destroy(enumerator);
544
545 /* search for inhibitPolicyMapping/inhibitAnyPolicy constraints */
546 len = 0;
547 chain->insert_first(chain, subject);
548 enumerator = chain->create_enumerator(chain);
549 while (enumerator->enumerate(enumerator, &x509))
550 {
551 inh = x509->get_constraint(x509, X509_INHIBIT_POLICY_MAPPING);
552 if (inh != X509_NO_CONSTRAINT)
553 {
554 if (!has_no_policy_mapping(chain, len - inh))
555 {
556 valid = FALSE;
557 break;
558 }
559 }
560 inh = x509->get_constraint(x509, X509_INHIBIT_ANY_POLICY);
561 if (inh != X509_NO_CONSTRAINT)
562 {
563 if (!has_no_any_policy(chain, len - inh))
564 {
565 valid = FALSE;
566 break;
567 }
568 }
569 len++;
570 }
571 enumerator->destroy(enumerator);
572
573 if (valid)
574 {
575 x509 = (x509_t*)subject;
576
577 enumerator = x509->create_cert_policy_enumerator(x509);
578 while (enumerator->enumerate(enumerator, &policy))
579 {
580 oid = asn1_oid_to_string(policy->oid);
581 if (oid)
582 {
583 if (is_policy_valid(chain, policy->oid))
584 {
585 auth->add(auth, AUTH_RULE_CERT_POLICY, oid);
586 }
587 else
588 {
589 DBG1(DBG_CFG, "certificate policy %s for '%Y' "
590 "not allowed by trustchain, ignored",
591 oid, subject->get_subject(subject));
592 free(oid);
593 }
594 }
595 }
596 enumerator->destroy(enumerator);
597 }
598 chain->destroy(chain);
599 }
600 }
601 return valid;
602 }
603
604 METHOD(cert_validator_t, validate, bool,
605 private_constraints_validator_t *this, certificate_t *subject,
606 certificate_t *issuer, bool online, u_int pathlen, bool anchor,
607 auth_cfg_t *auth)
608 {
609 if (issuer->get_type(issuer) == CERT_X509 &&
610 subject->get_type(subject) == CERT_X509)
611 {
612 if (!check_pathlen((x509_t*)issuer, pathlen))
613 {
614 lib->credmgr->call_hook(lib->credmgr, CRED_HOOK_EXCEEDED_PATH_LEN,
615 subject);
616 return FALSE;
617 }
618 if (!check_name_constraints(subject, (x509_t*)issuer))
619 {
620 lib->credmgr->call_hook(lib->credmgr, CRED_HOOK_POLICY_VIOLATION,
621 subject);
622 return FALSE;
623 }
624 if (anchor)
625 {
626 if (!check_policy_constraints((x509_t*)issuer, pathlen, auth))
627 {
628 lib->credmgr->call_hook(lib->credmgr,
629 CRED_HOOK_POLICY_VIOLATION, issuer);
630 return FALSE;
631 }
632 }
633 }
634 return TRUE;
635 }
636
637 METHOD(constraints_validator_t, destroy, void,
638 private_constraints_validator_t *this)
639 {
640 free(this);
641 }
642
643 /**
644 * See header
645 */
646 constraints_validator_t *constraints_validator_create()
647 {
648 private_constraints_validator_t *this;
649
650 INIT(this,
651 .public = {
652 .validator.validate = _validate,
653 .destroy = _destroy,
654 },
655 );
656
657 return &this->public;
658 }