fa045273fcbfb3e4357dbdb9bbca785585b047c1
[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 <credentials/certificates/x509.h>
21
22 typedef struct private_constraints_validator_t private_constraints_validator_t;
23
24 /**
25 * Private data of an constraints_validator_t object.
26 */
27 struct private_constraints_validator_t {
28
29 /**
30 * Public constraints_validator_t interface.
31 */
32 constraints_validator_t public;
33 };
34
35 /**
36 * Check pathlen constraint of issuer certificate
37 */
38 static bool check_pathlen(x509_t *issuer, int pathlen)
39 {
40 int pathlen_constraint;
41
42 pathlen_constraint = issuer->get_pathLenConstraint(issuer);
43 if (pathlen_constraint != X509_NO_CONSTRAINT &&
44 pathlen > pathlen_constraint)
45 {
46 DBG1(DBG_CFG, "path length of %d violates constraint of %d",
47 pathlen, pathlen_constraint);
48 return FALSE;
49 }
50 return TRUE;
51 }
52
53 /**
54 * Check if a FQDN/RFC822 constraint matches (suffix match)
55 */
56 static bool suffix_matches(identification_t *constraint, identification_t *id)
57 {
58 chunk_t c, i;
59
60 c = constraint->get_encoding(constraint);
61 i = id->get_encoding(id);
62
63 return i.len >= c.len && chunk_equals(c, chunk_skip(i, i.len - c.len));
64 }
65
66 /**
67 * Check if a DN constraint matches (RDN prefix match)
68 */
69 static bool dn_matches(identification_t *constraint, identification_t *id)
70 {
71 enumerator_t *ec, *ei;
72 id_part_t pc, pi;
73 chunk_t cc, ci;
74 bool match = TRUE;
75
76 ec = constraint->create_part_enumerator(constraint);
77 ei = id->create_part_enumerator(id);
78 while (ec->enumerate(ec, &pc, &cc))
79 {
80 if (!ei->enumerate(ei, &pi, &ci) ||
81 pi != pc || !chunk_equals(cc, ci))
82 {
83 match = FALSE;
84 break;
85 }
86 }
87 ec->destroy(ec);
88 ei->destroy(ei);
89
90 return match;
91 }
92
93 /**
94 * Check if a certificate matches to a NameConstraint
95 */
96 static bool name_constraint_matches(identification_t *constraint,
97 certificate_t *cert, bool permitted)
98 {
99 x509_t *x509 = (x509_t*)cert;
100 enumerator_t *enumerator;
101 identification_t *id;
102 id_type_t type;
103 bool matches = permitted;
104
105 type = constraint->get_type(constraint);
106 if (type == ID_DER_ASN1_DN)
107 {
108 matches = dn_matches(constraint, cert->get_subject(cert));
109 if (matches != permitted)
110 {
111 return matches;
112 }
113 }
114
115 enumerator = x509->create_subjectAltName_enumerator(x509);
116 while (enumerator->enumerate(enumerator, &id))
117 {
118 if (id->get_type(id) == type)
119 {
120 switch (type)
121 {
122 case ID_FQDN:
123 case ID_RFC822_ADDR:
124 matches = suffix_matches(constraint, id);
125 break;
126 case ID_DER_ASN1_DN:
127 matches = dn_matches(constraint, id);
128 break;
129 default:
130 DBG1(DBG_CFG, "%N NameConstraint matching not implemented",
131 id_type_names, type);
132 matches = FALSE;
133 break;
134 }
135 }
136 if (matches != permitted)
137 {
138 break;
139 }
140 }
141 enumerator->destroy(enumerator);
142
143 return matches;
144 }
145
146 /**
147 * Check if a permitted or excluded NameConstraint has been inherited to sub-CA
148 */
149 static bool name_constraint_inherited(identification_t *constraint,
150 x509_t *x509, bool permitted)
151 {
152 enumerator_t *enumerator;
153 identification_t *id;
154 bool inherited = FALSE;
155 id_type_t type;
156
157 if (!(x509->get_flags(x509) & X509_CA))
158 { /* not a sub-CA, not required */
159 return TRUE;
160 }
161
162 type = constraint->get_type(constraint);
163 enumerator = x509->create_name_constraint_enumerator(x509, permitted);
164 while (enumerator->enumerate(enumerator, &id))
165 {
166 if (id->get_type(id) == type)
167 {
168 switch (type)
169 {
170 case ID_FQDN:
171 case ID_RFC822_ADDR:
172 if (permitted)
173 { /* permitted constraint can be narrowed */
174 inherited = suffix_matches(constraint, id);
175 }
176 else
177 { /* excluded constraint can be widened */
178 inherited = suffix_matches(id, constraint);
179 }
180 break;
181 case ID_DER_ASN1_DN:
182 if (permitted)
183 {
184 inherited = dn_matches(constraint, id);
185 }
186 else
187 {
188 inherited = dn_matches(id, constraint);
189 }
190 break;
191 default:
192 DBG1(DBG_CFG, "%N NameConstraint matching not implemented",
193 id_type_names, type);
194 inherited = FALSE;
195 break;
196 }
197 }
198 if (inherited)
199 {
200 break;
201 }
202 }
203 enumerator->destroy(enumerator);
204 return inherited;
205 }
206
207 /**
208 * Check name constraints
209 */
210 static bool check_name_constraints(certificate_t *subject, x509_t *issuer)
211 {
212 enumerator_t *enumerator;
213 identification_t *constraint;
214
215 enumerator = issuer->create_name_constraint_enumerator(issuer, TRUE);
216 while (enumerator->enumerate(enumerator, &constraint))
217 {
218 if (!name_constraint_matches(constraint, subject, TRUE))
219 {
220 DBG1(DBG_CFG, "certificate '%Y' does not match permitted name "
221 "constraint '%Y'", subject->get_subject(subject), constraint);
222 enumerator->destroy(enumerator);
223 return FALSE;
224 }
225 if (!name_constraint_inherited(constraint, (x509_t*)subject, TRUE))
226 {
227 DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit permitted name "
228 "constraint '%Y'", subject->get_subject(subject), constraint);
229 enumerator->destroy(enumerator);
230 return FALSE;
231 }
232 }
233 enumerator->destroy(enumerator);
234
235 enumerator = issuer->create_name_constraint_enumerator(issuer, FALSE);
236 while (enumerator->enumerate(enumerator, &constraint))
237 {
238 if (name_constraint_matches(constraint, subject, FALSE))
239 {
240 DBG1(DBG_CFG, "certificate '%Y' matches excluded name "
241 "constraint '%Y'", subject->get_subject(subject), constraint);
242 enumerator->destroy(enumerator);
243 return FALSE;
244 }
245 if (!name_constraint_inherited(constraint, (x509_t*)subject, FALSE))
246 {
247 DBG1(DBG_CFG, "intermediate CA '%Y' does not inherit excluded name "
248 "constraint '%Y'", subject->get_subject(subject), constraint);
249 enumerator->destroy(enumerator);
250 return FALSE;
251 }
252 }
253 enumerator->destroy(enumerator);
254 return TRUE;
255 }
256
257 /**
258 * Check if an issuer certificate has a given policy OID
259 */
260 static bool has_policy(x509_t *issuer, chunk_t oid)
261 {
262 chunk_t any_policy = chunk_from_chars(0x55,0x1d,0x20,0x00);
263 x509_policy_mapping_t *mapping;
264 x509_cert_policy_t *policy;
265 enumerator_t *enumerator;
266
267 enumerator = issuer->create_cert_policy_enumerator(issuer);
268 while (enumerator->enumerate(enumerator, &policy))
269 {
270 if (chunk_equals(oid, policy->oid) ||
271 chunk_equals(any_policy, policy->oid))
272 {
273 enumerator->destroy(enumerator);
274 return TRUE;
275 }
276 }
277 enumerator->destroy(enumerator);
278
279 /* fall back to a mapped policy */
280 enumerator = issuer->create_policy_mapping_enumerator(issuer);
281 while (enumerator->enumerate(enumerator, &mapping))
282 {
283 if (chunk_equals(mapping->subject, oid))
284 {
285 enumerator->destroy(enumerator);
286 return TRUE;
287 }
288 }
289 enumerator->destroy(enumerator);
290 return FALSE;
291 }
292
293 /**
294 * Check certificatePolicies
295 */
296 static bool check_policy(x509_t *subject, x509_t *issuer, int pathlen,
297 auth_cfg_t *auth)
298 {
299 certificate_t *cert = (certificate_t*)subject;
300 x509_policy_mapping_t *mapping;
301 x509_cert_policy_t *policy;
302 enumerator_t *enumerator;
303 char *oid;
304
305 /* verify if policyMappings in subject are valid */
306 enumerator = subject->create_policy_mapping_enumerator(subject);
307 while (enumerator->enumerate(enumerator, &mapping))
308 {
309 if (!has_policy(issuer, mapping->issuer))
310 {
311 oid = asn1_oid_to_string(mapping->issuer);
312 DBG1(DBG_CFG, "certificate '%Y' maps policy from %s, but issuer "
313 "misses it", cert->get_subject(cert), oid);
314 free(oid);
315 enumerator->destroy(enumerator);
316 return FALSE;
317 }
318 }
319 enumerator->destroy(enumerator);
320
321 enumerator = subject->create_cert_policy_enumerator(subject);
322 while (enumerator->enumerate(enumerator, &policy))
323 {
324 if (!has_policy(issuer, policy->oid))
325 {
326 oid = asn1_oid_to_string(policy->oid);
327 DBG1(DBG_CFG, "policy %s missing in issuing certificate '%Y'",
328 oid, cert->get_issuer(cert));
329 free(oid);
330 enumerator->destroy(enumerator);
331 return FALSE;
332 }
333 if (pathlen == 0)
334 {
335 oid = asn1_oid_to_string(policy->oid);
336 if (oid)
337 {
338 auth->add(auth, AUTH_RULE_CERT_POLICY, oid);
339 }
340 }
341 }
342 enumerator->destroy(enumerator);
343
344 return TRUE;
345 }
346
347 METHOD(cert_validator_t, validate, bool,
348 private_constraints_validator_t *this, certificate_t *subject,
349 certificate_t *issuer, bool online, int pathlen, bool anchor,
350 auth_cfg_t *auth)
351 {
352 if (issuer->get_type(issuer) == CERT_X509 &&
353 subject->get_type(subject) == CERT_X509)
354 {
355 if (!check_pathlen((x509_t*)issuer, pathlen))
356 {
357 return FALSE;
358 }
359 if (!check_name_constraints(subject, (x509_t*)issuer))
360 {
361 return FALSE;
362 }
363 if (!check_policy((x509_t*)subject, (x509_t*)issuer, pathlen, auth))
364 {
365 return FALSE;
366 }
367 }
368 return TRUE;
369 }
370
371 METHOD(constraints_validator_t, destroy, void,
372 private_constraints_validator_t *this)
373 {
374 free(this);
375 }
376
377 /**
378 * See header
379 */
380 constraints_validator_t *constraints_validator_create()
381 {
382 private_constraints_validator_t *this;
383
384 INIT(this,
385 .public = {
386 .validator.validate = _validate,
387 .destroy = _destroy,
388 },
389 );
390
391 return &this->public;
392 }