]>
Commit | Line | Data |
---|---|---|
0f113f3e | 1 | /* |
33388b44 | 2 | * Copyright 2003-2020 The OpenSSL Project Authors. All Rights Reserved. |
520b76ff | 3 | * |
4286ca47 | 4 | * Licensed under the Apache License 2.0 (the "License"). You may not use |
d2e9e320 RS |
5 | * this file except in compliance with the License. You can obtain a copy |
6 | * in the file LICENSE in the source distribution or at | |
7 | * https://www.openssl.org/source/license.html | |
520b76ff DSH |
8 | */ |
9 | ||
b39fc560 | 10 | #include "internal/cryptlib.h" |
9cde5f81 | 11 | #include "internal/numbers.h" |
07016a8a | 12 | #include <stdio.h> |
25f2138b | 13 | #include "crypto/asn1.h" |
520b76ff DSH |
14 | #include <openssl/asn1t.h> |
15 | #include <openssl/conf.h> | |
16 | #include <openssl/x509v3.h> | |
c23e497d | 17 | #include <openssl/bn.h> |
520b76ff | 18 | |
25f2138b | 19 | #include "crypto/x509.h" |
69d92459 | 20 | #include "crypto/punycode.h" |
df2ee0e2 | 21 | #include "ext_dat.h" |
2743e38c | 22 | |
babb3798 | 23 | static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method, |
0f113f3e MC |
24 | X509V3_CTX *ctx, |
25 | STACK_OF(CONF_VALUE) *nval); | |
26 | static int i2r_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method, void *a, | |
27 | BIO *bp, int ind); | |
babb3798 | 28 | static int do_i2r_name_constraints(const X509V3_EXT_METHOD *method, |
0f113f3e | 29 | STACK_OF(GENERAL_SUBTREE) *trees, BIO *bp, |
c8f717fe | 30 | int ind, const char *name); |
520b76ff DSH |
31 | static int print_nc_ipadd(BIO *bp, ASN1_OCTET_STRING *ip); |
32 | ||
e9746e03 DSH |
33 | static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc); |
34 | static int nc_match_single(GENERAL_NAME *sub, GENERAL_NAME *gen); | |
8cc86b81 | 35 | static int nc_dn(const X509_NAME *sub, const X509_NAME *nm); |
e9746e03 DSH |
36 | static int nc_dns(ASN1_IA5STRING *sub, ASN1_IA5STRING *dns); |
37 | static int nc_email(ASN1_IA5STRING *sub, ASN1_IA5STRING *eml); | |
69d92459 | 38 | static int nc_email_eai(ASN1_UTF8STRING *sub, ASN1_IA5STRING *eml); |
e9746e03 | 39 | static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base); |
dd36fce0 | 40 | static int nc_ip(ASN1_OCTET_STRING *ip, ASN1_OCTET_STRING *base); |
e9746e03 | 41 | |
47864aea | 42 | const X509V3_EXT_METHOD ossl_v3_name_constraints = { |
0f113f3e MC |
43 | NID_name_constraints, 0, |
44 | ASN1_ITEM_ref(NAME_CONSTRAINTS), | |
45 | 0, 0, 0, 0, | |
46 | 0, 0, | |
47 | 0, v2i_NAME_CONSTRAINTS, | |
48 | i2r_NAME_CONSTRAINTS, 0, | |
49 | NULL | |
520b76ff DSH |
50 | }; |
51 | ||
52 | ASN1_SEQUENCE(GENERAL_SUBTREE) = { | |
0f113f3e MC |
53 | ASN1_SIMPLE(GENERAL_SUBTREE, base, GENERAL_NAME), |
54 | ASN1_IMP_OPT(GENERAL_SUBTREE, minimum, ASN1_INTEGER, 0), | |
55 | ASN1_IMP_OPT(GENERAL_SUBTREE, maximum, ASN1_INTEGER, 1) | |
520b76ff DSH |
56 | } ASN1_SEQUENCE_END(GENERAL_SUBTREE) |
57 | ||
58 | ASN1_SEQUENCE(NAME_CONSTRAINTS) = { | |
0f113f3e MC |
59 | ASN1_IMP_SEQUENCE_OF_OPT(NAME_CONSTRAINTS, permittedSubtrees, |
60 | GENERAL_SUBTREE, 0), | |
61 | ASN1_IMP_SEQUENCE_OF_OPT(NAME_CONSTRAINTS, excludedSubtrees, | |
62 | GENERAL_SUBTREE, 1), | |
520b76ff | 63 | } ASN1_SEQUENCE_END(NAME_CONSTRAINTS) |
0f113f3e | 64 | |
520b76ff DSH |
65 | |
66 | IMPLEMENT_ASN1_ALLOC_FUNCTIONS(GENERAL_SUBTREE) | |
67 | IMPLEMENT_ASN1_ALLOC_FUNCTIONS(NAME_CONSTRAINTS) | |
68 | ||
9cde5f81 MC |
69 | /* |
70 | * We cannot use strncasecmp here because that applies locale specific rules. | |
71 | * For example in Turkish 'I' is not the uppercase character for 'i'. We need to | |
72 | * do a simple ASCII case comparison ignoring the locale (that is why we use | |
73 | * numeric constants below). | |
74 | */ | |
75 | static int ia5ncasecmp(const char *s1, const char *s2, size_t n) | |
76 | { | |
77 | for (; n > 0; n--, s1++, s2++) { | |
78 | if (*s1 != *s2) { | |
79 | unsigned char c1 = (unsigned char)*s1, c2 = (unsigned char)*s2; | |
80 | ||
81 | /* Convert to lower case */ | |
82 | if (c1 >= 0x41 /* A */ && c1 <= 0x5A /* Z */) | |
83 | c1 += 0x20; | |
84 | if (c2 >= 0x41 /* A */ && c2 <= 0x5A /* Z */) | |
85 | c2 += 0x20; | |
86 | ||
87 | if (c1 == c2) | |
88 | continue; | |
89 | ||
90 | if (c1 < c2) | |
91 | return -1; | |
92 | ||
93 | /* c1 > c2 */ | |
94 | return 1; | |
95 | } else if (*s1 == 0) { | |
96 | /* If we get here we know that *s2 == 0 too */ | |
97 | return 0; | |
98 | } | |
99 | } | |
100 | ||
101 | return 0; | |
102 | } | |
103 | ||
104 | static int ia5casecmp(const char *s1, const char *s2) | |
105 | { | |
106 | return ia5ncasecmp(s1, s2, SIZE_MAX); | |
107 | } | |
108 | ||
babb3798 | 109 | static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method, |
0f113f3e MC |
110 | X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval) |
111 | { | |
112 | int i; | |
113 | CONF_VALUE tval, *val; | |
114 | STACK_OF(GENERAL_SUBTREE) **ptree = NULL; | |
115 | NAME_CONSTRAINTS *ncons = NULL; | |
116 | GENERAL_SUBTREE *sub = NULL; | |
86885c28 | 117 | |
0f113f3e | 118 | ncons = NAME_CONSTRAINTS_new(); |
90945fa3 | 119 | if (ncons == NULL) |
0f113f3e MC |
120 | goto memerr; |
121 | for (i = 0; i < sk_CONF_VALUE_num(nval); i++) { | |
122 | val = sk_CONF_VALUE_value(nval, i); | |
86885c28 | 123 | if (strncmp(val->name, "permitted", 9) == 0 && val->name[9]) { |
0f113f3e MC |
124 | ptree = &ncons->permittedSubtrees; |
125 | tval.name = val->name + 10; | |
86885c28 | 126 | } else if (strncmp(val->name, "excluded", 8) == 0 && val->name[8]) { |
0f113f3e MC |
127 | ptree = &ncons->excludedSubtrees; |
128 | tval.name = val->name + 9; | |
129 | } else { | |
9311d0c4 | 130 | ERR_raise(ERR_LIB_X509V3, X509V3_R_INVALID_SYNTAX); |
0f113f3e MC |
131 | goto err; |
132 | } | |
133 | tval.value = val->value; | |
134 | sub = GENERAL_SUBTREE_new(); | |
90945fa3 MC |
135 | if (sub == NULL) |
136 | goto memerr; | |
0f113f3e MC |
137 | if (!v2i_GENERAL_NAME_ex(sub->base, method, ctx, &tval, 1)) |
138 | goto err; | |
90945fa3 | 139 | if (*ptree == NULL) |
0f113f3e | 140 | *ptree = sk_GENERAL_SUBTREE_new_null(); |
90945fa3 | 141 | if (*ptree == NULL || !sk_GENERAL_SUBTREE_push(*ptree, sub)) |
0f113f3e MC |
142 | goto memerr; |
143 | sub = NULL; | |
144 | } | |
145 | ||
146 | return ncons; | |
147 | ||
148 | memerr: | |
9311d0c4 | 149 | ERR_raise(ERR_LIB_X509V3, ERR_R_MALLOC_FAILURE); |
0f113f3e | 150 | err: |
895cba19 RS |
151 | NAME_CONSTRAINTS_free(ncons); |
152 | GENERAL_SUBTREE_free(sub); | |
0f113f3e MC |
153 | |
154 | return NULL; | |
155 | } | |
520b76ff | 156 | |
babb3798 | 157 | static int i2r_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method, void *a, |
0f113f3e MC |
158 | BIO *bp, int ind) |
159 | { | |
160 | NAME_CONSTRAINTS *ncons = a; | |
161 | do_i2r_name_constraints(method, ncons->permittedSubtrees, | |
162 | bp, ind, "Permitted"); | |
a4c467c9 DO |
163 | if (ncons->permittedSubtrees && ncons->excludedSubtrees) |
164 | BIO_puts(bp, "\n"); | |
0f113f3e MC |
165 | do_i2r_name_constraints(method, ncons->excludedSubtrees, |
166 | bp, ind, "Excluded"); | |
167 | return 1; | |
168 | } | |
520b76ff | 169 | |
babb3798 | 170 | static int do_i2r_name_constraints(const X509V3_EXT_METHOD *method, |
0f113f3e | 171 | STACK_OF(GENERAL_SUBTREE) *trees, |
c8f717fe | 172 | BIO *bp, int ind, const char *name) |
0f113f3e MC |
173 | { |
174 | GENERAL_SUBTREE *tree; | |
175 | int i; | |
176 | if (sk_GENERAL_SUBTREE_num(trees) > 0) | |
177 | BIO_printf(bp, "%*s%s:\n", ind, "", name); | |
178 | for (i = 0; i < sk_GENERAL_SUBTREE_num(trees); i++) { | |
a4c467c9 DO |
179 | if (i > 0) |
180 | BIO_puts(bp, "\n"); | |
0f113f3e MC |
181 | tree = sk_GENERAL_SUBTREE_value(trees, i); |
182 | BIO_printf(bp, "%*s", ind + 2, ""); | |
183 | if (tree->base->type == GEN_IPADD) | |
184 | print_nc_ipadd(bp, tree->base->d.ip); | |
185 | else | |
186 | GENERAL_NAME_print(bp, tree->base); | |
0f113f3e MC |
187 | } |
188 | return 1; | |
189 | } | |
520b76ff DSH |
190 | |
191 | static int print_nc_ipadd(BIO *bp, ASN1_OCTET_STRING *ip) | |
0f113f3e | 192 | { |
278260bf DDO |
193 | /* ip->length should be 8 or 32 and len1 == len2 == 4 or len1 == len2 == 16 */ |
194 | int len1 = ip->length >= 16 ? 16 : ip->length >= 4 ? 4 : ip->length; | |
195 | int len2 = ip->length - len1; | |
9500c823 SL |
196 | char *ip1 = ossl_ipaddr_to_asc(ip->data, len1); |
197 | char *ip2 = ossl_ipaddr_to_asc(ip->data + len1, len2); | |
084b7bec | 198 | int ret = ip1 != NULL && ip2 != NULL |
278260bf DDO |
199 | && BIO_printf(bp, "IP:%s/%s", ip1, ip2) > 0; |
200 | ||
201 | OPENSSL_free(ip1); | |
202 | OPENSSL_free(ip2); | |
203 | return ret; | |
0f113f3e | 204 | } |
520b76ff | 205 | |
8545051c DB |
206 | #define NAME_CHECK_MAX (1 << 20) |
207 | ||
208 | static int add_lengths(int *out, int a, int b) | |
209 | { | |
210 | /* sk_FOO_num(NULL) returns -1 but is effectively 0 when iterating. */ | |
211 | if (a < 0) | |
212 | a = 0; | |
213 | if (b < 0) | |
214 | b = 0; | |
215 | ||
216 | if (a > INT_MAX - b) | |
217 | return 0; | |
218 | *out = a + b; | |
219 | return 1; | |
220 | } | |
221 | ||
1d97c843 TH |
222 | /*- |
223 | * Check a certificate conforms to a specified set of constraints. | |
e9746e03 DSH |
224 | * Return values: |
225 | * X509_V_OK: All constraints obeyed. | |
226 | * X509_V_ERR_PERMITTED_VIOLATION: Permitted subtree violation. | |
227 | * X509_V_ERR_EXCLUDED_VIOLATION: Excluded subtree violation. | |
228 | * X509_V_ERR_SUBTREE_MINMAX: Min or max values present and matching type. | |
229 | * X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE: Unsupported constraint type. | |
230 | * X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX: bad unsupported constraint syntax. | |
231 | * X509_V_ERR_UNSUPPORTED_NAME_SYNTAX: bad or unsupported syntax of name | |
e9746e03 DSH |
232 | */ |
233 | ||
234 | int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc) | |
0f113f3e | 235 | { |
8545051c | 236 | int r, i, name_count, constraint_count; |
0f113f3e | 237 | X509_NAME *nm; |
e9746e03 | 238 | |
0f113f3e | 239 | nm = X509_get_subject_name(x); |
e9746e03 | 240 | |
8545051c DB |
241 | /* |
242 | * Guard against certificates with an excessive number of names or | |
243 | * constraints causing a computationally expensive name constraints check. | |
244 | */ | |
245 | if (!add_lengths(&name_count, X509_NAME_entry_count(nm), | |
246 | sk_GENERAL_NAME_num(x->altname)) | |
247 | || !add_lengths(&constraint_count, | |
248 | sk_GENERAL_SUBTREE_num(nc->permittedSubtrees), | |
249 | sk_GENERAL_SUBTREE_num(nc->excludedSubtrees)) | |
250 | || (name_count > 0 && constraint_count > NAME_CHECK_MAX / name_count)) | |
251 | return X509_V_ERR_UNSPECIFIED; | |
252 | ||
0f113f3e MC |
253 | if (X509_NAME_entry_count(nm) > 0) { |
254 | GENERAL_NAME gntmp; | |
255 | gntmp.type = GEN_DIRNAME; | |
256 | gntmp.d.directoryName = nm; | |
e9746e03 | 257 | |
0f113f3e | 258 | r = nc_match(&gntmp, nc); |
e9746e03 | 259 | |
0f113f3e MC |
260 | if (r != X509_V_OK) |
261 | return r; | |
e9746e03 | 262 | |
0f113f3e | 263 | gntmp.type = GEN_EMAIL; |
e9746e03 | 264 | |
0f113f3e | 265 | /* Process any email address attributes in subject name */ |
e9746e03 | 266 | |
0f113f3e | 267 | for (i = -1;;) { |
9f5466b9 F |
268 | const X509_NAME_ENTRY *ne; |
269 | ||
0f113f3e MC |
270 | i = X509_NAME_get_index_by_NID(nm, NID_pkcs9_emailAddress, i); |
271 | if (i == -1) | |
272 | break; | |
273 | ne = X509_NAME_get_entry(nm, i); | |
274 | gntmp.d.rfc822Name = X509_NAME_ENTRY_get_data(ne); | |
275 | if (gntmp.d.rfc822Name->type != V_ASN1_IA5STRING) | |
276 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
e9746e03 | 277 | |
0f113f3e | 278 | r = nc_match(&gntmp, nc); |
e9746e03 | 279 | |
0f113f3e MC |
280 | if (r != X509_V_OK) |
281 | return r; | |
282 | } | |
e9746e03 | 283 | |
0f113f3e | 284 | } |
e9746e03 | 285 | |
0f113f3e MC |
286 | for (i = 0; i < sk_GENERAL_NAME_num(x->altname); i++) { |
287 | GENERAL_NAME *gen = sk_GENERAL_NAME_value(x->altname, i); | |
288 | r = nc_match(gen, nc); | |
289 | if (r != X509_V_OK) | |
290 | return r; | |
291 | } | |
e9746e03 | 292 | |
0f113f3e | 293 | return X509_V_OK; |
e9746e03 | 294 | |
0f113f3e | 295 | } |
e9746e03 | 296 | |
d02d80b2 VD |
297 | static int cn2dnsid(ASN1_STRING *cn, unsigned char **dnsid, size_t *idlen) |
298 | { | |
55a6250f | 299 | int utf8_length; |
d02d80b2 | 300 | unsigned char *utf8_value; |
55a6250f | 301 | int i; |
d02d80b2 VD |
302 | int isdnsname = 0; |
303 | ||
304 | /* Don't leave outputs uninitialized */ | |
305 | *dnsid = NULL; | |
306 | *idlen = 0; | |
307 | ||
308 | /*- | |
309 | * Per RFC 6125, DNS-IDs representing internationalized domain names appear | |
310 | * in certificates in A-label encoded form: | |
311 | * | |
312 | * https://tools.ietf.org/html/rfc6125#section-6.4.2 | |
313 | * | |
314 | * The same applies to CNs which are intended to represent DNS names. | |
315 | * However, while in the SAN DNS-IDs are IA5Strings, as CNs they may be | |
316 | * needlessly encoded in 16-bit Unicode. We perform a conversion to UTF-8 | |
317 | * to ensure that we get an ASCII representation of any CNs that are | |
318 | * representable as ASCII, but just not encoded as ASCII. The UTF-8 form | |
319 | * may contain some non-ASCII octets, and that's fine, such CNs are not | |
320 | * valid legacy DNS names. | |
321 | * | |
322 | * Note, 'int' is the return type of ASN1_STRING_to_UTF8() so that's what | |
323 | * we must use for 'utf8_length'. | |
324 | */ | |
325 | if ((utf8_length = ASN1_STRING_to_UTF8(&utf8_value, cn)) < 0) | |
326 | return X509_V_ERR_OUT_OF_MEM; | |
327 | ||
328 | /* | |
329 | * Some certificates have had names that include a *trailing* NUL byte. | |
330 | * Remove these harmless NUL characters. They would otherwise yield false | |
331 | * alarms with the following embedded NUL check. | |
332 | */ | |
333 | while (utf8_length > 0 && utf8_value[utf8_length - 1] == '\0') | |
334 | --utf8_length; | |
335 | ||
336 | /* Reject *embedded* NULs */ | |
55a6250f VD |
337 | if ((size_t)utf8_length != strlen((char *)utf8_value)) { |
338 | OPENSSL_free(utf8_value); | |
339 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
340 | } | |
d02d80b2 VD |
341 | |
342 | /* | |
343 | * XXX: Deviation from strict DNS name syntax, also check names with '_' | |
344 | * Check DNS name syntax, any '-' or '.' must be internal, | |
345 | * and on either side of each '.' we can't have a '-' or '.'. | |
346 | * | |
347 | * If the name has just one label, we don't consider it a DNS name. This | |
348 | * means that "CN=sometld" cannot be precluded by DNS name constraints, but | |
349 | * that is not a problem. | |
350 | */ | |
351 | for (i = 0; i < utf8_length; ++i) { | |
352 | unsigned char c = utf8_value[i]; | |
353 | ||
354 | if ((c >= 'a' && c <= 'z') | |
355 | || (c >= 'A' && c <= 'Z') | |
356 | || (c >= '0' && c <= '9') | |
357 | || c == '_') | |
358 | continue; | |
359 | ||
360 | /* Dot and hyphen cannot be first or last. */ | |
361 | if (i > 0 && i < utf8_length - 1) { | |
362 | if (c == '-') | |
363 | continue; | |
364 | /* | |
365 | * Next to a dot the preceding and following characters must not be | |
366 | * another dot or a hyphen. Otherwise, record that the name is | |
367 | * plausible, since it has two or more labels. | |
368 | */ | |
369 | if (c == '.' | |
370 | && utf8_value[i + 1] != '.' | |
371 | && utf8_value[i - 1] != '-' | |
372 | && utf8_value[i + 1] != '-') { | |
373 | isdnsname = 1; | |
374 | continue; | |
375 | } | |
376 | } | |
377 | isdnsname = 0; | |
378 | break; | |
379 | } | |
380 | ||
381 | if (isdnsname) { | |
382 | *dnsid = utf8_value; | |
383 | *idlen = (size_t)utf8_length; | |
384 | return X509_V_OK; | |
385 | } | |
386 | OPENSSL_free(utf8_value); | |
387 | return X509_V_OK; | |
388 | } | |
389 | ||
390 | /* | |
55a6250f | 391 | * Check CN against DNS-ID name constraints. |
d02d80b2 | 392 | */ |
5bd5dcd4 DSH |
393 | int NAME_CONSTRAINTS_check_CN(X509 *x, NAME_CONSTRAINTS *nc) |
394 | { | |
395 | int r, i; | |
8cc86b81 | 396 | const X509_NAME *nm = X509_get_subject_name(x); |
5bd5dcd4 DSH |
397 | ASN1_STRING stmp; |
398 | GENERAL_NAME gntmp; | |
d02d80b2 | 399 | |
5bd5dcd4 DSH |
400 | stmp.flags = 0; |
401 | stmp.type = V_ASN1_IA5STRING; | |
402 | gntmp.type = GEN_DNS; | |
403 | gntmp.d.dNSName = &stmp; | |
404 | ||
5bd5dcd4 DSH |
405 | /* Process any commonName attributes in subject name */ |
406 | ||
407 | for (i = -1;;) { | |
408 | X509_NAME_ENTRY *ne; | |
d02d80b2 VD |
409 | ASN1_STRING *cn; |
410 | unsigned char *idval; | |
411 | size_t idlen; | |
d2a56999 | 412 | |
5bd5dcd4 DSH |
413 | i = X509_NAME_get_index_by_NID(nm, NID_commonName, i); |
414 | if (i == -1) | |
415 | break; | |
416 | ne = X509_NAME_get_entry(nm, i); | |
d02d80b2 | 417 | cn = X509_NAME_ENTRY_get_data(ne); |
5bd5dcd4 | 418 | |
d02d80b2 VD |
419 | /* Only process attributes that look like host names */ |
420 | if ((r = cn2dnsid(cn, &idval, &idlen)) != X509_V_OK) | |
421 | return r; | |
422 | if (idlen == 0) | |
423 | continue; | |
5bd5dcd4 | 424 | |
d02d80b2 VD |
425 | stmp.length = idlen; |
426 | stmp.data = idval; | |
427 | r = nc_match(&gntmp, nc); | |
428 | OPENSSL_free(idval); | |
429 | if (r != X509_V_OK) | |
430 | return r; | |
5bd5dcd4 DSH |
431 | } |
432 | return X509_V_OK; | |
433 | } | |
434 | ||
c23e497d FT |
435 | /* |
436 | * Return nonzero if the GeneralSubtree has valid 'minimum' field | |
437 | * (must be absent or 0) and valid 'maximum' field (must be absent). | |
438 | */ | |
439 | static int nc_minmax_valid(GENERAL_SUBTREE *sub) { | |
440 | BIGNUM *bn = NULL; | |
441 | int ok = 1; | |
442 | ||
443 | if (sub->maximum) | |
444 | ok = 0; | |
445 | ||
446 | if (sub->minimum) { | |
447 | bn = ASN1_INTEGER_to_BN(sub->minimum, NULL); | |
448 | if (bn == NULL || !BN_is_zero(bn)) | |
449 | ok = 0; | |
450 | BN_free(bn); | |
451 | } | |
452 | ||
453 | return ok; | |
454 | } | |
455 | ||
e9746e03 | 456 | static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc) |
0f113f3e MC |
457 | { |
458 | GENERAL_SUBTREE *sub; | |
459 | int i, r, match = 0; | |
69d92459 DB |
460 | /* |
461 | * We need to compare not gen->type field but an "effective" type because | |
462 | * the otherName field may contain EAI email address treated specially | |
463 | * according to RFC 8398, section 6 | |
464 | */ | |
465 | int effective_type = ((gen->type == GEN_OTHERNAME) && | |
466 | (OBJ_obj2nid(gen->d.otherName->type_id) == | |
467 | NID_id_on_SmtpUTF8Mailbox)) ? GEN_EMAIL : gen->type; | |
0f113f3e MC |
468 | |
469 | /* | |
470 | * Permitted subtrees: if any subtrees exist of matching the type at | |
471 | * least one subtree must match. | |
472 | */ | |
473 | ||
474 | for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) { | |
475 | sub = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i); | |
69d92459 | 476 | if (effective_type != sub->base->type) |
0f113f3e | 477 | continue; |
c23e497d | 478 | if (!nc_minmax_valid(sub)) |
0f113f3e MC |
479 | return X509_V_ERR_SUBTREE_MINMAX; |
480 | /* If we already have a match don't bother trying any more */ | |
481 | if (match == 2) | |
482 | continue; | |
483 | if (match == 0) | |
484 | match = 1; | |
485 | r = nc_match_single(gen, sub->base); | |
486 | if (r == X509_V_OK) | |
487 | match = 2; | |
488 | else if (r != X509_V_ERR_PERMITTED_VIOLATION) | |
489 | return r; | |
490 | } | |
491 | ||
492 | if (match == 1) | |
493 | return X509_V_ERR_PERMITTED_VIOLATION; | |
494 | ||
495 | /* Excluded subtrees: must not match any of these */ | |
496 | ||
497 | for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); i++) { | |
498 | sub = sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i); | |
69d92459 | 499 | if (effective_type != sub->base->type) |
0f113f3e | 500 | continue; |
c23e497d | 501 | if (!nc_minmax_valid(sub)) |
0f113f3e MC |
502 | return X509_V_ERR_SUBTREE_MINMAX; |
503 | ||
504 | r = nc_match_single(gen, sub->base); | |
505 | if (r == X509_V_OK) | |
506 | return X509_V_ERR_EXCLUDED_VIOLATION; | |
507 | else if (r != X509_V_ERR_PERMITTED_VIOLATION) | |
508 | return r; | |
509 | ||
510 | } | |
511 | ||
512 | return X509_V_OK; | |
513 | ||
514 | } | |
e9746e03 DSH |
515 | |
516 | static int nc_match_single(GENERAL_NAME *gen, GENERAL_NAME *base) | |
0f113f3e | 517 | { |
69d92459 DB |
518 | switch (gen->type) { |
519 | case GEN_OTHERNAME: | |
520 | /* | |
521 | * We are here only when we have SmtpUTF8 name, | |
522 | * so we match the value of othername with base->d.rfc822Name | |
523 | */ | |
524 | return nc_email_eai(gen->d.otherName->value->value.utf8string, | |
525 | base->d.rfc822Name); | |
0f113f3e MC |
526 | case GEN_DIRNAME: |
527 | return nc_dn(gen->d.directoryName, base->d.directoryName); | |
e9746e03 | 528 | |
0f113f3e MC |
529 | case GEN_DNS: |
530 | return nc_dns(gen->d.dNSName, base->d.dNSName); | |
e9746e03 | 531 | |
0f113f3e MC |
532 | case GEN_EMAIL: |
533 | return nc_email(gen->d.rfc822Name, base->d.rfc822Name); | |
e9746e03 | 534 | |
0f113f3e MC |
535 | case GEN_URI: |
536 | return nc_uri(gen->d.uniformResourceIdentifier, | |
537 | base->d.uniformResourceIdentifier); | |
e9746e03 | 538 | |
0f113f3e MC |
539 | case GEN_IPADD: |
540 | return nc_ip(gen->d.iPAddress, base->d.iPAddress); | |
dd36fce0 | 541 | |
0f113f3e MC |
542 | default: |
543 | return X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE; | |
544 | } | |
e9746e03 | 545 | |
0f113f3e | 546 | } |
e9746e03 | 547 | |
0f113f3e MC |
548 | /* |
549 | * directoryName name constraint matching. The canonical encoding of | |
550 | * X509_NAME makes this comparison easy. It is matched if the subtree is a | |
551 | * subset of the name. | |
e9746e03 DSH |
552 | */ |
553 | ||
8cc86b81 | 554 | static int nc_dn(const X509_NAME *nm, const X509_NAME *base) |
0f113f3e MC |
555 | { |
556 | /* Ensure canonical encodings are up to date. */ | |
557 | if (nm->modified && i2d_X509_NAME(nm, NULL) < 0) | |
558 | return X509_V_ERR_OUT_OF_MEM; | |
559 | if (base->modified && i2d_X509_NAME(base, NULL) < 0) | |
560 | return X509_V_ERR_OUT_OF_MEM; | |
561 | if (base->canon_enclen > nm->canon_enclen) | |
562 | return X509_V_ERR_PERMITTED_VIOLATION; | |
563 | if (memcmp(base->canon_enc, nm->canon_enc, base->canon_enclen)) | |
564 | return X509_V_ERR_PERMITTED_VIOLATION; | |
565 | return X509_V_OK; | |
566 | } | |
e9746e03 DSH |
567 | |
568 | static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base) | |
0f113f3e MC |
569 | { |
570 | char *baseptr = (char *)base->data; | |
571 | char *dnsptr = (char *)dns->data; | |
12a765a5 | 572 | |
0f113f3e | 573 | /* Empty matches everything */ |
12a765a5 | 574 | if (*baseptr == '\0') |
0f113f3e MC |
575 | return X509_V_OK; |
576 | /* | |
577 | * Otherwise can add zero or more components on the left so compare RHS | |
578 | * and if dns is longer and expect '.' as preceding character. | |
579 | */ | |
580 | if (dns->length > base->length) { | |
581 | dnsptr += dns->length - base->length; | |
582 | if (*baseptr != '.' && dnsptr[-1] != '.') | |
583 | return X509_V_ERR_PERMITTED_VIOLATION; | |
584 | } | |
585 | ||
9cde5f81 | 586 | if (ia5casecmp(baseptr, dnsptr)) |
0f113f3e MC |
587 | return X509_V_ERR_PERMITTED_VIOLATION; |
588 | ||
589 | return X509_V_OK; | |
590 | ||
591 | } | |
e9746e03 | 592 | |
69d92459 DB |
593 | /* |
594 | * This function implements comparison between ASCII/U-label in eml | |
595 | * and A-label in base according to RFC 8398, section 6. | |
596 | * Convert base to U-label and ASCII-parts of domain names, for base | |
597 | * Octet-to-octet comparison of `eml` and `base` hostname parts | |
598 | * (ASCII-parts should be compared in case-insensitive manner) | |
599 | */ | |
600 | static int nc_email_eai(ASN1_UTF8STRING *eml, ASN1_IA5STRING *base) | |
601 | { | |
602 | const char *baseptr = (char *)base->data; | |
603 | const char *emlptr = (char *)eml->data; | |
604 | const char *emlat = strrchr(emlptr, '@'); | |
605 | ||
606 | char ulabel[256]; | |
607 | size_t size = sizeof(ulabel) - 1; | |
608 | ||
609 | if (emlat == NULL) | |
610 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
611 | ||
612 | memset(ulabel, 0, sizeof(ulabel)); | |
613 | /* Special case: initial '.' is RHS match */ | |
614 | if (*baseptr == '.') { | |
615 | ulabel[0] = '.'; | |
616 | size -= 1; | |
617 | if (ossl_a2ulabel(baseptr, ulabel + 1, &size) <= 0) | |
618 | return X509_V_ERR_UNSPECIFIED; | |
619 | ||
620 | if ((size_t)eml->length > size + 1) { | |
621 | emlptr += eml->length - (size + 1); | |
622 | if (ia5casecmp(ulabel, emlptr) == 0) | |
623 | return X509_V_OK; | |
624 | } | |
625 | return X509_V_ERR_PERMITTED_VIOLATION; | |
626 | } | |
627 | ||
628 | emlptr = emlat + 1; | |
629 | if (ossl_a2ulabel(baseptr, ulabel, &size) <= 0) | |
630 | return X509_V_ERR_UNSPECIFIED; | |
631 | /* Just have hostname left to match: case insensitive */ | |
632 | if (ia5casecmp(ulabel, emlptr)) | |
633 | return X509_V_ERR_PERMITTED_VIOLATION; | |
634 | ||
635 | return X509_V_OK; | |
636 | ||
637 | } | |
638 | ||
e9746e03 | 639 | static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base) |
0f113f3e MC |
640 | { |
641 | const char *baseptr = (char *)base->data; | |
642 | const char *emlptr = (char *)eml->data; | |
643 | ||
69d92459 DB |
644 | const char *baseat = strrchr(baseptr, '@'); |
645 | const char *emlat = strrchr(emlptr, '@'); | |
0f113f3e MC |
646 | if (!emlat) |
647 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
0d4fb843 | 648 | /* Special case: initial '.' is RHS match */ |
0f113f3e MC |
649 | if (!baseat && (*baseptr == '.')) { |
650 | if (eml->length > base->length) { | |
651 | emlptr += eml->length - base->length; | |
9cde5f81 | 652 | if (ia5casecmp(baseptr, emlptr) == 0) |
0f113f3e MC |
653 | return X509_V_OK; |
654 | } | |
655 | return X509_V_ERR_PERMITTED_VIOLATION; | |
656 | } | |
657 | ||
658 | /* If we have anything before '@' match local part */ | |
659 | ||
660 | if (baseat) { | |
661 | if (baseat != baseptr) { | |
662 | if ((baseat - baseptr) != (emlat - emlptr)) | |
663 | return X509_V_ERR_PERMITTED_VIOLATION; | |
664 | /* Case sensitive match of local part */ | |
665 | if (strncmp(baseptr, emlptr, emlat - emlptr)) | |
666 | return X509_V_ERR_PERMITTED_VIOLATION; | |
667 | } | |
668 | /* Position base after '@' */ | |
669 | baseptr = baseat + 1; | |
670 | } | |
671 | emlptr = emlat + 1; | |
672 | /* Just have hostname left to match: case insensitive */ | |
9cde5f81 | 673 | if (ia5casecmp(baseptr, emlptr)) |
0f113f3e MC |
674 | return X509_V_ERR_PERMITTED_VIOLATION; |
675 | ||
676 | return X509_V_OK; | |
677 | ||
678 | } | |
e9746e03 DSH |
679 | |
680 | static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base) | |
0f113f3e MC |
681 | { |
682 | const char *baseptr = (char *)base->data; | |
683 | const char *hostptr = (char *)uri->data; | |
684 | const char *p = strchr(hostptr, ':'); | |
685 | int hostlen; | |
12a765a5 | 686 | |
0f113f3e | 687 | /* Check for foo:// and skip past it */ |
12a765a5 | 688 | if (p == NULL || p[1] != '/' || p[2] != '/') |
0f113f3e MC |
689 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; |
690 | hostptr = p + 3; | |
691 | ||
692 | /* Determine length of hostname part of URI */ | |
693 | ||
694 | /* Look for a port indicator as end of hostname first */ | |
695 | ||
696 | p = strchr(hostptr, ':'); | |
697 | /* Otherwise look for trailing slash */ | |
12a765a5 | 698 | if (p == NULL) |
0f113f3e MC |
699 | p = strchr(hostptr, '/'); |
700 | ||
12a765a5 | 701 | if (p == NULL) |
0f113f3e MC |
702 | hostlen = strlen(hostptr); |
703 | else | |
704 | hostlen = p - hostptr; | |
705 | ||
706 | if (hostlen == 0) | |
707 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
708 | ||
0d4fb843 | 709 | /* Special case: initial '.' is RHS match */ |
0f113f3e MC |
710 | if (*baseptr == '.') { |
711 | if (hostlen > base->length) { | |
712 | p = hostptr + hostlen - base->length; | |
9cde5f81 | 713 | if (ia5ncasecmp(p, baseptr, base->length) == 0) |
0f113f3e MC |
714 | return X509_V_OK; |
715 | } | |
716 | return X509_V_ERR_PERMITTED_VIOLATION; | |
717 | } | |
718 | ||
719 | if ((base->length != (int)hostlen) | |
9cde5f81 | 720 | || ia5ncasecmp(hostptr, baseptr, hostlen)) |
0f113f3e MC |
721 | return X509_V_ERR_PERMITTED_VIOLATION; |
722 | ||
723 | return X509_V_OK; | |
724 | ||
725 | } | |
dd36fce0 LADL |
726 | |
727 | static int nc_ip(ASN1_OCTET_STRING *ip, ASN1_OCTET_STRING *base) | |
0f113f3e MC |
728 | { |
729 | int hostlen, baselen, i; | |
730 | unsigned char *hostptr, *baseptr, *maskptr; | |
731 | hostptr = ip->data; | |
732 | hostlen = ip->length; | |
733 | baseptr = base->data; | |
734 | baselen = base->length; | |
735 | ||
736 | /* Invalid if not IPv4 or IPv6 */ | |
737 | if (!((hostlen == 4) || (hostlen == 16))) | |
738 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
739 | if (!((baselen == 8) || (baselen == 32))) | |
740 | return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; | |
741 | ||
742 | /* Do not match IPv4 with IPv6 */ | |
743 | if (hostlen * 2 != baselen) | |
744 | return X509_V_ERR_PERMITTED_VIOLATION; | |
745 | ||
746 | maskptr = base->data + hostlen; | |
747 | ||
748 | /* Considering possible not aligned base ipAddress */ | |
749 | /* Not checking for wrong mask definition: i.e.: 255.0.255.0 */ | |
750 | for (i = 0; i < hostlen; i++) | |
751 | if ((hostptr[i] & maskptr[i]) != (baseptr[i] & maskptr[i])) | |
752 | return X509_V_ERR_PERMITTED_VIOLATION; | |
753 | ||
754 | return X509_V_OK; | |
755 | ||
756 | } |