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