v3_ncons.c revision 325337
1/* v3_ncons.c */
2/*
3 * Written by Dr Stephen N Henson (steve@openssl.org) for the OpenSSL
4 * project.
5 */
6/* ====================================================================
7 * Copyright (c) 2003 The OpenSSL Project.  All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 *    notice, this list of conditions and the following disclaimer in
18 *    the documentation and/or other materials provided with the
19 *    distribution.
20 *
21 * 3. All advertising materials mentioning features or use of this
22 *    software must display the following acknowledgment:
23 *    "This product includes software developed by the OpenSSL Project
24 *    for use in the OpenSSL Toolkit. (http://www.OpenSSL.org/)"
25 *
26 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
27 *    endorse or promote products derived from this software without
28 *    prior written permission. For written permission, please contact
29 *    licensing@OpenSSL.org.
30 *
31 * 5. Products derived from this software may not be called "OpenSSL"
32 *    nor may "OpenSSL" appear in their names without prior written
33 *    permission of the OpenSSL Project.
34 *
35 * 6. Redistributions of any form whatsoever must retain the following
36 *    acknowledgment:
37 *    "This product includes software developed by the OpenSSL Project
38 *    for use in the OpenSSL Toolkit (http://www.OpenSSL.org/)"
39 *
40 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
41 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
42 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
43 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
44 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
45 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
46 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
47 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
48 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
49 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
50 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
51 * OF THE POSSIBILITY OF SUCH DAMAGE.
52 * ====================================================================
53 *
54 * This product includes cryptographic software written by Eric Young
55 * (eay@cryptsoft.com).  This product includes software written by Tim
56 * Hudson (tjh@cryptsoft.com).
57 *
58 */
59
60#include <stdio.h>
61#include "cryptlib.h"
62#include <openssl/asn1t.h>
63#include <openssl/conf.h>
64#include <openssl/x509v3.h>
65
66static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method,
67                                  X509V3_CTX *ctx,
68                                  STACK_OF(CONF_VALUE) *nval);
69static int i2r_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method, void *a,
70                                BIO *bp, int ind);
71static int do_i2r_name_constraints(const X509V3_EXT_METHOD *method,
72                                   STACK_OF(GENERAL_SUBTREE) *trees, BIO *bp,
73                                   int ind, char *name);
74static int print_nc_ipadd(BIO *bp, ASN1_OCTET_STRING *ip);
75
76static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc);
77static int nc_match_single(GENERAL_NAME *sub, GENERAL_NAME *gen);
78static int nc_dn(X509_NAME *sub, X509_NAME *nm);
79static int nc_dns(ASN1_IA5STRING *sub, ASN1_IA5STRING *dns);
80static int nc_email(ASN1_IA5STRING *sub, ASN1_IA5STRING *eml);
81static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base);
82
83const X509V3_EXT_METHOD v3_name_constraints = {
84    NID_name_constraints, 0,
85    ASN1_ITEM_ref(NAME_CONSTRAINTS),
86    0, 0, 0, 0,
87    0, 0,
88    0, v2i_NAME_CONSTRAINTS,
89    i2r_NAME_CONSTRAINTS, 0,
90    NULL
91};
92
93ASN1_SEQUENCE(GENERAL_SUBTREE) = {
94        ASN1_SIMPLE(GENERAL_SUBTREE, base, GENERAL_NAME),
95        ASN1_IMP_OPT(GENERAL_SUBTREE, minimum, ASN1_INTEGER, 0),
96        ASN1_IMP_OPT(GENERAL_SUBTREE, maximum, ASN1_INTEGER, 1)
97} ASN1_SEQUENCE_END(GENERAL_SUBTREE)
98
99ASN1_SEQUENCE(NAME_CONSTRAINTS) = {
100        ASN1_IMP_SEQUENCE_OF_OPT(NAME_CONSTRAINTS, permittedSubtrees,
101                                                        GENERAL_SUBTREE, 0),
102        ASN1_IMP_SEQUENCE_OF_OPT(NAME_CONSTRAINTS, excludedSubtrees,
103                                                        GENERAL_SUBTREE, 1),
104} ASN1_SEQUENCE_END(NAME_CONSTRAINTS)
105
106
107IMPLEMENT_ASN1_ALLOC_FUNCTIONS(GENERAL_SUBTREE)
108IMPLEMENT_ASN1_ALLOC_FUNCTIONS(NAME_CONSTRAINTS)
109
110/*
111 * We cannot use strncasecmp here because that applies locale specific rules.
112 * For example in Turkish 'I' is not the uppercase character for 'i'. We need to
113 * do a simple ASCII case comparison ignoring the locale (that is why we use
114 * numeric constants below).
115 */
116static int ia5ncasecmp(const char *s1, const char *s2, size_t n)
117{
118    for (; n > 0; n--, s1++, s2++) {
119        if (*s1 != *s2) {
120            unsigned char c1 = (unsigned char)*s1, c2 = (unsigned char)*s2;
121
122            /* Convert to lower case */
123            if (c1 >= 0x41 /* A */ && c1 <= 0x5A /* Z */)
124                c1 += 0x20;
125            if (c2 >= 0x41 /* A */ && c2 <= 0x5A /* Z */)
126                c2 += 0x20;
127
128            if (c1 == c2)
129                continue;
130
131            if (c1 < c2)
132                return -1;
133
134            /* c1 > c2 */
135            return 1;
136        } else if (*s1 == 0) {
137            /* If we get here we know that *s2 == 0 too */
138            return 0;
139        }
140    }
141
142    return 0;
143}
144
145static int ia5casecmp(const char *s1, const char *s2)
146{
147    /* No portable definition of SIZE_MAX, so we use (size_t)(-1) instead */
148    return ia5ncasecmp(s1, s2, (size_t)(-1));
149}
150
151static void *v2i_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method,
152                                  X509V3_CTX *ctx, STACK_OF(CONF_VALUE) *nval)
153{
154    int i;
155    CONF_VALUE tval, *val;
156    STACK_OF(GENERAL_SUBTREE) **ptree = NULL;
157    NAME_CONSTRAINTS *ncons = NULL;
158    GENERAL_SUBTREE *sub = NULL;
159    ncons = NAME_CONSTRAINTS_new();
160    if (!ncons)
161        goto memerr;
162    for (i = 0; i < sk_CONF_VALUE_num(nval); i++) {
163        val = sk_CONF_VALUE_value(nval, i);
164        if (!strncmp(val->name, "permitted", 9) && val->name[9]) {
165            ptree = &ncons->permittedSubtrees;
166            tval.name = val->name + 10;
167        } else if (!strncmp(val->name, "excluded", 8) && val->name[8]) {
168            ptree = &ncons->excludedSubtrees;
169            tval.name = val->name + 9;
170        } else {
171            X509V3err(X509V3_F_V2I_NAME_CONSTRAINTS, X509V3_R_INVALID_SYNTAX);
172            goto err;
173        }
174        tval.value = val->value;
175        sub = GENERAL_SUBTREE_new();
176        if (sub == NULL)
177            goto memerr;
178        if (!v2i_GENERAL_NAME_ex(sub->base, method, ctx, &tval, 1))
179            goto err;
180        if (!*ptree)
181            *ptree = sk_GENERAL_SUBTREE_new_null();
182        if (!*ptree || !sk_GENERAL_SUBTREE_push(*ptree, sub))
183            goto memerr;
184        sub = NULL;
185    }
186
187    return ncons;
188
189 memerr:
190    X509V3err(X509V3_F_V2I_NAME_CONSTRAINTS, ERR_R_MALLOC_FAILURE);
191 err:
192    if (ncons)
193        NAME_CONSTRAINTS_free(ncons);
194    if (sub)
195        GENERAL_SUBTREE_free(sub);
196
197    return NULL;
198}
199
200static int i2r_NAME_CONSTRAINTS(const X509V3_EXT_METHOD *method, void *a,
201                                BIO *bp, int ind)
202{
203    NAME_CONSTRAINTS *ncons = a;
204    do_i2r_name_constraints(method, ncons->permittedSubtrees,
205                            bp, ind, "Permitted");
206    do_i2r_name_constraints(method, ncons->excludedSubtrees,
207                            bp, ind, "Excluded");
208    return 1;
209}
210
211static int do_i2r_name_constraints(const X509V3_EXT_METHOD *method,
212                                   STACK_OF(GENERAL_SUBTREE) *trees,
213                                   BIO *bp, int ind, char *name)
214{
215    GENERAL_SUBTREE *tree;
216    int i;
217    if (sk_GENERAL_SUBTREE_num(trees) > 0)
218        BIO_printf(bp, "%*s%s:\n", ind, "", name);
219    for (i = 0; i < sk_GENERAL_SUBTREE_num(trees); i++) {
220        tree = sk_GENERAL_SUBTREE_value(trees, i);
221        BIO_printf(bp, "%*s", ind + 2, "");
222        if (tree->base->type == GEN_IPADD)
223            print_nc_ipadd(bp, tree->base->d.ip);
224        else
225            GENERAL_NAME_print(bp, tree->base);
226        BIO_puts(bp, "\n");
227    }
228    return 1;
229}
230
231static int print_nc_ipadd(BIO *bp, ASN1_OCTET_STRING *ip)
232{
233    int i, len;
234    unsigned char *p;
235    p = ip->data;
236    len = ip->length;
237    BIO_puts(bp, "IP:");
238    if (len == 8) {
239        BIO_printf(bp, "%d.%d.%d.%d/%d.%d.%d.%d",
240                   p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
241    } else if (len == 32) {
242        for (i = 0; i < 16; i++) {
243            BIO_printf(bp, "%X", p[0] << 8 | p[1]);
244            p += 2;
245            if (i == 7)
246                BIO_puts(bp, "/");
247            else if (i != 15)
248                BIO_puts(bp, ":");
249        }
250    } else
251        BIO_printf(bp, "IP Address:<invalid>");
252    return 1;
253}
254
255/*-
256 * Check a certificate conforms to a specified set of constraints.
257 * Return values:
258 *  X509_V_OK: All constraints obeyed.
259 *  X509_V_ERR_PERMITTED_VIOLATION: Permitted subtree violation.
260 *  X509_V_ERR_EXCLUDED_VIOLATION: Excluded subtree violation.
261 *  X509_V_ERR_SUBTREE_MINMAX: Min or max values present and matching type.
262 *  X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE:  Unsupported constraint type.
263 *  X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX: bad unsupported constraint syntax.
264 *  X509_V_ERR_UNSUPPORTED_NAME_SYNTAX: bad or unsupported syntax of name
265 */
266
267int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc)
268{
269    int r, i;
270    X509_NAME *nm;
271
272    nm = X509_get_subject_name(x);
273
274    if (X509_NAME_entry_count(nm) > 0) {
275        GENERAL_NAME gntmp;
276        gntmp.type = GEN_DIRNAME;
277        gntmp.d.directoryName = nm;
278
279        r = nc_match(&gntmp, nc);
280
281        if (r != X509_V_OK)
282            return r;
283
284        gntmp.type = GEN_EMAIL;
285
286        /* Process any email address attributes in subject name */
287
288        for (i = -1;;) {
289            X509_NAME_ENTRY *ne;
290            i = X509_NAME_get_index_by_NID(nm, NID_pkcs9_emailAddress, i);
291            if (i == -1)
292                break;
293            ne = X509_NAME_get_entry(nm, i);
294            gntmp.d.rfc822Name = X509_NAME_ENTRY_get_data(ne);
295            if (gntmp.d.rfc822Name->type != V_ASN1_IA5STRING)
296                return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
297
298            r = nc_match(&gntmp, nc);
299
300            if (r != X509_V_OK)
301                return r;
302        }
303
304    }
305
306    for (i = 0; i < sk_GENERAL_NAME_num(x->altname); i++) {
307        GENERAL_NAME *gen = sk_GENERAL_NAME_value(x->altname, i);
308        r = nc_match(gen, nc);
309        if (r != X509_V_OK)
310            return r;
311    }
312
313    return X509_V_OK;
314
315}
316
317static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc)
318{
319    GENERAL_SUBTREE *sub;
320    int i, r, match = 0;
321
322    /*
323     * Permitted subtrees: if any subtrees exist of matching the type at
324     * least one subtree must match.
325     */
326
327    for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) {
328        sub = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i);
329        if (gen->type != sub->base->type)
330            continue;
331        if (sub->minimum || sub->maximum)
332            return X509_V_ERR_SUBTREE_MINMAX;
333        /* If we already have a match don't bother trying any more */
334        if (match == 2)
335            continue;
336        if (match == 0)
337            match = 1;
338        r = nc_match_single(gen, sub->base);
339        if (r == X509_V_OK)
340            match = 2;
341        else if (r != X509_V_ERR_PERMITTED_VIOLATION)
342            return r;
343    }
344
345    if (match == 1)
346        return X509_V_ERR_PERMITTED_VIOLATION;
347
348    /* Excluded subtrees: must not match any of these */
349
350    for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); i++) {
351        sub = sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i);
352        if (gen->type != sub->base->type)
353            continue;
354        if (sub->minimum || sub->maximum)
355            return X509_V_ERR_SUBTREE_MINMAX;
356
357        r = nc_match_single(gen, sub->base);
358        if (r == X509_V_OK)
359            return X509_V_ERR_EXCLUDED_VIOLATION;
360        else if (r != X509_V_ERR_PERMITTED_VIOLATION)
361            return r;
362
363    }
364
365    return X509_V_OK;
366
367}
368
369static int nc_match_single(GENERAL_NAME *gen, GENERAL_NAME *base)
370{
371    switch (base->type) {
372    case GEN_DIRNAME:
373        return nc_dn(gen->d.directoryName, base->d.directoryName);
374
375    case GEN_DNS:
376        return nc_dns(gen->d.dNSName, base->d.dNSName);
377
378    case GEN_EMAIL:
379        return nc_email(gen->d.rfc822Name, base->d.rfc822Name);
380
381    case GEN_URI:
382        return nc_uri(gen->d.uniformResourceIdentifier,
383                      base->d.uniformResourceIdentifier);
384
385    default:
386        return X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE;
387    }
388
389}
390
391/*
392 * directoryName name constraint matching. The canonical encoding of
393 * X509_NAME makes this comparison easy. It is matched if the subtree is a
394 * subset of the name.
395 */
396
397static int nc_dn(X509_NAME *nm, X509_NAME *base)
398{
399    /* Ensure canonical encodings are up to date.  */
400    if (nm->modified && i2d_X509_NAME(nm, NULL) < 0)
401        return X509_V_ERR_OUT_OF_MEM;
402    if (base->modified && i2d_X509_NAME(base, NULL) < 0)
403        return X509_V_ERR_OUT_OF_MEM;
404    if (base->canon_enclen > nm->canon_enclen)
405        return X509_V_ERR_PERMITTED_VIOLATION;
406    if (memcmp(base->canon_enc, nm->canon_enc, base->canon_enclen))
407        return X509_V_ERR_PERMITTED_VIOLATION;
408    return X509_V_OK;
409}
410
411static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base)
412{
413    char *baseptr = (char *)base->data;
414    char *dnsptr = (char *)dns->data;
415    /* Empty matches everything */
416    if (!*baseptr)
417        return X509_V_OK;
418    /*
419     * Otherwise can add zero or more components on the left so compare RHS
420     * and if dns is longer and expect '.' as preceding character.
421     */
422    if (dns->length > base->length) {
423        dnsptr += dns->length - base->length;
424        if (*baseptr != '.' && dnsptr[-1] != '.')
425            return X509_V_ERR_PERMITTED_VIOLATION;
426    }
427
428    if (ia5casecmp(baseptr, dnsptr))
429        return X509_V_ERR_PERMITTED_VIOLATION;
430
431    return X509_V_OK;
432
433}
434
435static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base)
436{
437    const char *baseptr = (char *)base->data;
438    const char *emlptr = (char *)eml->data;
439
440    const char *baseat = strchr(baseptr, '@');
441    const char *emlat = strchr(emlptr, '@');
442    if (!emlat)
443        return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
444    /* Special case: inital '.' is RHS match */
445    if (!baseat && (*baseptr == '.')) {
446        if (eml->length > base->length) {
447            emlptr += eml->length - base->length;
448            if (ia5casecmp(baseptr, emlptr) == 0)
449                return X509_V_OK;
450        }
451        return X509_V_ERR_PERMITTED_VIOLATION;
452    }
453
454    /* If we have anything before '@' match local part */
455
456    if (baseat) {
457        if (baseat != baseptr) {
458            if ((baseat - baseptr) != (emlat - emlptr))
459                return X509_V_ERR_PERMITTED_VIOLATION;
460            /* Case sensitive match of local part */
461            if (strncmp(baseptr, emlptr, emlat - emlptr))
462                return X509_V_ERR_PERMITTED_VIOLATION;
463        }
464        /* Position base after '@' */
465        baseptr = baseat + 1;
466    }
467    emlptr = emlat + 1;
468    /* Just have hostname left to match: case insensitive */
469    if (ia5casecmp(baseptr, emlptr))
470        return X509_V_ERR_PERMITTED_VIOLATION;
471
472    return X509_V_OK;
473
474}
475
476static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base)
477{
478    const char *baseptr = (char *)base->data;
479    const char *hostptr = (char *)uri->data;
480    const char *p = strchr(hostptr, ':');
481    int hostlen;
482    /* Check for foo:// and skip past it */
483    if (!p || (p[1] != '/') || (p[2] != '/'))
484        return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
485    hostptr = p + 3;
486
487    /* Determine length of hostname part of URI */
488
489    /* Look for a port indicator as end of hostname first */
490
491    p = strchr(hostptr, ':');
492    /* Otherwise look for trailing slash */
493    if (!p)
494        p = strchr(hostptr, '/');
495
496    if (!p)
497        hostlen = strlen(hostptr);
498    else
499        hostlen = p - hostptr;
500
501    if (hostlen == 0)
502        return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX;
503
504    /* Special case: inital '.' is RHS match */
505    if (*baseptr == '.') {
506        if (hostlen > base->length) {
507            p = hostptr + hostlen - base->length;
508            if (ia5ncasecmp(p, baseptr, base->length) == 0)
509                return X509_V_OK;
510        }
511        return X509_V_ERR_PERMITTED_VIOLATION;
512    }
513
514    if ((base->length != (int)hostlen)
515        || ia5ncasecmp(hostptr, baseptr, hostlen))
516        return X509_V_ERR_PERMITTED_VIOLATION;
517
518    return X509_V_OK;
519
520}
521