651f41d7dd519e9ab10f37a6e1e912efcdd5c440
[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 <debug.h>
19 #include <asn1/asn1.h>
20 #include <utils/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 int pathlen_constraint;
42
43 pathlen_constraint = issuer->get_pathLenConstraint(issuer);
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 * Check if an issuer certificate has a given policy OID
260 */
261 static bool has_policy(x509_t *issuer, chunk_t oid)
262 {
263 chunk_t any_policy = chunk_from_chars(0x55,0x1d,0x20,0x00);
264 x509_policy_mapping_t *mapping;
265 x509_cert_policy_t *policy;
266 enumerator_t *enumerator;
267
268 enumerator = issuer->create_cert_policy_enumerator(issuer);
269 while (enumerator->enumerate(enumerator, &policy))
270 {
271 if (chunk_equals(oid, policy->oid) ||
272 chunk_equals(any_policy, policy->oid))
273 {
274 enumerator->destroy(enumerator);
275 return TRUE;
276 }
277 }
278 enumerator->destroy(enumerator);
279
280 /* fall back to a mapped policy */
281 enumerator = issuer->create_policy_mapping_enumerator(issuer);
282 while (enumerator->enumerate(enumerator, &mapping))
283 {
284 if (chunk_equals(mapping->subject, oid))
285 {
286 enumerator->destroy(enumerator);
287 return TRUE;
288 }
289 }
290 enumerator->destroy(enumerator);
291 return FALSE;
292 }
293
294 /**
295 * Check certificatePolicies.
296 */
297 static bool check_policy(x509_t *subject, x509_t *issuer, bool check,
298 auth_cfg_t *auth)
299 {
300 certificate_t *cert = (certificate_t*)subject;
301 x509_policy_mapping_t *mapping;
302 x509_cert_policy_t *policy;
303 enumerator_t *enumerator;
304 char *oid;
305
306 /* verify if policyMappings in subject are valid */
307 enumerator = subject->create_policy_mapping_enumerator(subject);
308 while (enumerator->enumerate(enumerator, &mapping))
309 {
310 if (!has_policy(issuer, mapping->issuer))
311 {
312 oid = asn1_oid_to_string(mapping->issuer);
313 DBG1(DBG_CFG, "certificate '%Y' maps policy from %s, but issuer "
314 "misses it", cert->get_subject(cert), oid);
315 free(oid);
316 enumerator->destroy(enumerator);
317 return FALSE;
318 }
319 }
320 enumerator->destroy(enumerator);
321
322 if (check)
323 {
324 enumerator = subject->create_cert_policy_enumerator(subject);
325 while (enumerator->enumerate(enumerator, &policy))
326 {
327 if (!has_policy(issuer, policy->oid))
328 {
329 oid = asn1_oid_to_string(policy->oid);
330 DBG1(DBG_CFG, "policy %s missing in issuing certificate '%Y'",
331 oid, cert->get_issuer(cert));
332 free(oid);
333 enumerator->destroy(enumerator);
334 return FALSE;
335 }
336 if (auth)
337 {
338 oid = asn1_oid_to_string(policy->oid);
339 if (oid)
340 {
341 auth->add(auth, AUTH_RULE_CERT_POLICY, oid);
342 }
343 }
344 }
345 enumerator->destroy(enumerator);
346 }
347
348 return TRUE;
349 }
350
351 /**
352 * Check len certificates in trustchain for inherited policies
353 */
354 static bool has_policy_chain(linked_list_t *chain, x509_t *subject, int len)
355 {
356 enumerator_t *enumerator;
357 x509_t *issuer;
358 bool valid = TRUE;
359
360 enumerator = chain->create_enumerator(chain);
361 while (len-- > 0 && enumerator->enumerate(enumerator, &issuer))
362 {
363 if (!check_policy(subject, issuer, TRUE, NULL))
364 {
365 valid = FALSE;
366 break;
367 }
368 subject = issuer;
369 }
370 enumerator->destroy(enumerator);
371 return valid;
372 }
373
374 /**
375 * Check if required explicit policies are given in a path
376 */
377 static bool check_explicit_policy(x509_t *issuer, int pathlen, auth_cfg_t *auth)
378 {
379 certificate_t *subject;
380 bool valid = TRUE;
381
382 subject = auth->get(auth, AUTH_RULE_SUBJECT_CERT);
383 if (subject)
384 {
385 if (subject->get_type(subject) == CERT_X509)
386 {
387 enumerator_t *enumerator;
388 linked_list_t *chain;
389 certificate_t *cert;
390 auth_rule_t rule;
391 x509_t *x509;
392 int len = 0, expl;
393
394 /* prepare trustchain to validate */
395 chain = linked_list_create();
396 enumerator = auth->create_enumerator(auth);
397 while (enumerator->enumerate(enumerator, &rule, &cert))
398 {
399 if (rule == AUTH_RULE_IM_CERT &&
400 cert->get_type(cert) == CERT_X509)
401 {
402 chain->insert_last(chain, cert);
403 }
404 }
405 enumerator->destroy(enumerator);
406 chain->insert_last(chain, issuer);
407
408 /* search for requireExplicitPolicy constraints */
409 enumerator = chain->create_enumerator(chain);
410 while (enumerator->enumerate(enumerator, &x509))
411 {
412 expl = x509->get_policyConstraint(x509, FALSE);
413 if (expl != X509_NO_CONSTRAINT)
414 {
415 if (!has_policy_chain(chain, (x509_t*)subject, len - expl))
416 {
417 valid = FALSE;
418 break;
419 }
420 }
421 len++;
422 }
423 enumerator->destroy(enumerator);
424 chain->destroy(chain);
425 }
426 }
427 return valid;
428 }
429
430 METHOD(cert_validator_t, validate, bool,
431 private_constraints_validator_t *this, certificate_t *subject,
432 certificate_t *issuer, bool online, int pathlen, bool anchor,
433 auth_cfg_t *auth)
434 {
435 if (issuer->get_type(issuer) == CERT_X509 &&
436 subject->get_type(subject) == CERT_X509)
437 {
438 if (!check_pathlen((x509_t*)issuer, pathlen))
439 {
440 return FALSE;
441 }
442 if (!check_name_constraints(subject, (x509_t*)issuer))
443 {
444 return FALSE;
445 }
446 if (!check_policy((x509_t*)subject, (x509_t*)issuer, !pathlen, auth))
447 {
448 return FALSE;
449 }
450 if (anchor)
451 {
452 if (!check_explicit_policy((x509_t*)issuer, pathlen, auth))
453 {
454 return FALSE;
455 }
456 }
457 }
458 return TRUE;
459 }
460
461 METHOD(constraints_validator_t, destroy, void,
462 private_constraints_validator_t *this)
463 {
464 free(this);
465 }
466
467 /**
468 * See header
469 */
470 constraints_validator_t *constraints_validator_create()
471 {
472 private_constraints_validator_t *this;
473
474 INIT(this,
475 .public = {
476 .validator.validate = _validate,
477 .destroy = _destroy,
478 },
479 );
480
481 return &this->public;
482 }