From e9746e03ee222b0123ac118f8952e161e7d48a2d Mon Sep 17 00:00:00 2001 From: "Dr. Stephen Henson" Date: Fri, 8 Aug 2008 15:35:29 +0000 Subject: [PATCH] Initial support for name constraints certificate extension. TODO: robustness checking on name forms. --- CHANGES | 14 ++ crypto/asn1/x_x509.c | 2 + crypto/ossl_typ.h | 1 + crypto/x509/x509.h | 2 + crypto/x509/x509_txt.c | 14 ++ crypto/x509/x509_vfy.c | 43 ++++++ crypto/x509/x509_vfy.h | 7 + crypto/x509v3/v3_ncons.c | 281 +++++++++++++++++++++++++++++++++++++++ crypto/x509v3/v3_purp.c | 5 + crypto/x509v3/x509v3.h | 6 +- 10 files changed, 373 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 9b524ddcfe..0d83a86d23 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,18 @@ Changes between 0.9.8i and 0.9.9 [xx XXX xxxx] + *) Fixes to pathlength constraint, self issued certificate handling, + policy processing to align with RFC3280 and PKITS tests. + + This work was sponsored by Google. + [Steve Henson] + + *) Support for name constraints certificate extension. DN, email, DNS + and URI types are currently supported. + + This work was sponsored by Google. + [Steve Henson] + *) To cater for systems that provide a pointer-based thread ID rather than numeric, deprecate the current numeric thread ID mechanism and replace it with a structure and associated callback type. This @@ -31,6 +43,8 @@ *) Initial support for different CRL issuing certificates. This covers a simple case where the self issued certificates in the chain exist and the real CRL issuer is higher in the existing chain. + + This work was sponsored by Google. [Steve Henson] *) Removed effectively defunct crypto/store from the build. diff --git a/crypto/asn1/x_x509.c b/crypto/asn1/x_x509.c index a3cae2c128..dafd3cc921 100644 --- a/crypto/asn1/x_x509.c +++ b/crypto/asn1/x_x509.c @@ -116,6 +116,8 @@ static int x509_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it, AUTHORITY_KEYID_free(ret->akid); CRL_DIST_POINTS_free(ret->crldp); policy_cache_free(ret->policy_cache); + GENERAL_NAMES_free(ret->altname); + NAME_CONSTRAINTS_free(ret->nc); #ifndef OPENSSL_NO_RFC3779 sk_IPAddressFamily_pop_free(ret->rfc3779_addr, IPAddressFamily_free); ASIdentifiers_free(ret->rfc3779_asid); diff --git a/crypto/ossl_typ.h b/crypto/ossl_typ.h index c9b494c854..786f2bc26b 100644 --- a/crypto/ossl_typ.h +++ b/crypto/ossl_typ.h @@ -177,6 +177,7 @@ typedef struct X509_POLICY_CACHE_st X509_POLICY_CACHE; typedef struct AUTHORITY_KEYID_st AUTHORITY_KEYID; typedef struct DIST_POINT_st DIST_POINT; typedef struct ISSUING_DIST_POINT_st ISSUING_DIST_POINT; +typedef struct NAME_CONSTRAINTS_st NAME_CONSTRAINTS; /* If placed in pkcs12.h, we end up with a circular depency with pkcs7.h */ #define DECLARE_PKCS12_STACK_OF(type) /* Nothing */ diff --git a/crypto/x509/x509.h b/crypto/x509/x509.h index 036ba7a439..adbdc65891 100644 --- a/crypto/x509/x509.h +++ b/crypto/x509/x509.h @@ -294,6 +294,8 @@ struct x509_st AUTHORITY_KEYID *akid; X509_POLICY_CACHE *policy_cache; STACK_OF(DIST_POINT) *crldp; + STACK_OF(GENERAL_NAME) *altname; + NAME_CONSTRAINTS *nc; #ifndef OPENSSL_NO_RFC3779 STACK_OF(IPAddressFamily) *rfc3779_addr; struct ASIdentifiers_st *rfc3779_asid; diff --git a/crypto/x509/x509_txt.c b/crypto/x509/x509_txt.c index 3851869762..2017f86a77 100644 --- a/crypto/x509/x509_txt.c +++ b/crypto/x509/x509_txt.c @@ -168,6 +168,20 @@ const char *X509_verify_cert_error_string(long n) return("Unsupported extension feature"); case X509_V_ERR_UNNESTED_RESOURCE: return("RFC 3779 resource not subset of parent's resources"); + + case X509_V_ERR_PERMITTED_VIOLATION: + return("permitted subtree violation"); + case X509_V_ERR_EXCLUDED_VIOLATION: + return("excluded subtree violation"); + case X509_V_ERR_SUBTREE_MINMAX: + return("name constraints minimum and maximum not supported"); + case X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE: + return("unsupported name constraint type"); + case X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX: + return("unsupported or invalid name constraint syntax"); + case X509_V_ERR_UNSUPPORTED_NAME_SYNTAX: + return("unsupported or invalid name syntax"); + default: BIO_snprintf(buf,sizeof buf,"error number %ld",n); return(buf); diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c index 1b1630a9d6..fe47dcba52 100644 --- a/crypto/x509/x509_vfy.c +++ b/crypto/x509/x509_vfy.c @@ -74,6 +74,7 @@ static int null_callback(int ok,X509_STORE_CTX *e); static int check_issued(X509_STORE_CTX *ctx, X509 *x, X509 *issuer); static X509 *find_issuer(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *x); static int check_chain_extensions(X509_STORE_CTX *ctx); +static int check_name_constraints(X509_STORE_CTX *ctx); static int check_trust(X509_STORE_CTX *ctx); static int check_revocation(X509_STORE_CTX *ctx); static int check_cert(X509_STORE_CTX *ctx); @@ -291,6 +292,12 @@ int X509_verify_cert(X509_STORE_CTX *ctx) if (!ok) goto end; + /* Check name constraints */ + + ok = check_name_constraints(ctx); + + if (!ok) goto end; + /* The chain extensions are OK: check trust */ if (param->trust > 0) ok = check_trust(ctx); @@ -538,6 +545,42 @@ static int check_chain_extensions(X509_STORE_CTX *ctx) #endif } +static int check_name_constraints(X509_STORE_CTX *ctx) + { + X509 *x; + int i, j, rv; + /* Check name constraints for all certificates */ + for (i = sk_X509_num(ctx->chain) - 1; i >= 0; i--) + { + x = sk_X509_value(ctx->chain, i); + /* Ignore self issued certs unless last in chain */ + if (i && (x->ex_flags & EXFLAG_SI)) + continue; + /* Check against constraints for all certificates higher in + * chain including trust anchor. Trust anchor not strictly + * speaking needed but if it includes constraints it is to be + * assumed it expects them to be obeyed. + */ + for (j = sk_X509_num(ctx->chain) - 1; j > i; j--) + { + NAME_CONSTRAINTS *nc = sk_X509_value(ctx->chain, j)->nc; + if (nc) + { + rv = NAME_CONSTRAINTS_check(x, nc); + if (rv != X509_V_OK) + { + ctx->error = rv; + ctx->error_depth = i; + ctx->current_cert = x; + if (!ctx->verify_cb(0,ctx)) + return 0; + } + } + } + } + return 1; + } + static int check_trust(X509_STORE_CTX *ctx) { #ifdef OPENSSL_NO_CHAIN_VERIFY diff --git a/crypto/x509/x509_vfy.h b/crypto/x509/x509_vfy.h index 955af8341a..faf641f037 100644 --- a/crypto/x509/x509_vfy.h +++ b/crypto/x509/x509_vfy.h @@ -341,6 +341,13 @@ void X509_STORE_CTX_set_depth(X509_STORE_CTX *ctx, int depth); #define X509_V_ERR_UNNESTED_RESOURCE 46 +#define X509_V_ERR_PERMITTED_VIOLATION 47 +#define X509_V_ERR_EXCLUDED_VIOLATION 48 +#define X509_V_ERR_SUBTREE_MINMAX 49 +#define X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE 51 +#define X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX 52 +#define X509_V_ERR_UNSUPPORTED_NAME_SYNTAX 53 + /* The application is not happy */ #define X509_V_ERR_APPLICATION_VERIFICATION 50 diff --git a/crypto/x509v3/v3_ncons.c b/crypto/x509v3/v3_ncons.c index 42e7f5a879..511fbd73ca 100644 --- a/crypto/x509v3/v3_ncons.c +++ b/crypto/x509v3/v3_ncons.c @@ -72,6 +72,13 @@ static int do_i2r_name_constraints(X509V3_EXT_METHOD *method, BIO *bp, int ind, char *name); static int print_nc_ipadd(BIO *bp, ASN1_OCTET_STRING *ip); +static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc); +static int nc_match_single(GENERAL_NAME *sub, GENERAL_NAME *gen); +static int nc_dn(X509_NAME *sub, X509_NAME *nm); +static int nc_dns(ASN1_IA5STRING *sub, ASN1_IA5STRING *dns); +static int nc_email(ASN1_IA5STRING *sub, ASN1_IA5STRING *eml); +static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base); + const X509V3_EXT_METHOD v3_name_constraints = { NID_name_constraints, 0, ASN1_ITEM_ref(NAME_CONSTRAINTS), @@ -218,3 +225,277 @@ static int print_nc_ipadd(BIO *bp, ASN1_OCTET_STRING *ip) return 1; } +/* Check a certificate conforms to a specified set of constraints. + * Return values: + * X509_V_OK: All constraints obeyed. + * X509_V_ERR_PERMITTED_VIOLATION: Permitted subtree violation. + * X509_V_ERR_EXCLUDED_VIOLATION: Excluded subtree violation. + * X509_V_ERR_SUBTREE_MINMAX: Min or max values present and matching type. + * X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE: Unsupported constraint type. + * X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX: bad unsupported constraint syntax. + * X509_V_ERR_UNSUPPORTED_NAME_SYNTAX: bad or unsupported syntax of name + + */ + +int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc) + { + int r, i; + X509_NAME *nm; + + nm = X509_get_subject_name(x); + + if (X509_NAME_entry_count(nm) > 0) + { + GENERAL_NAME gntmp; + gntmp.type = GEN_DIRNAME; + gntmp.d.directoryName = nm; + + r = nc_match(&gntmp, nc); + + if (r != X509_V_OK) + return r; + + gntmp.type = GEN_EMAIL; + + + /* Process any email address attributes in subject name */ + + for (i = -1;;) + { + X509_NAME_ENTRY *ne; + i = X509_NAME_get_index_by_NID(nm, + NID_pkcs9_emailAddress, + i); + if (i == -1) + break; + ne = X509_NAME_get_entry(nm, i); + gntmp.d.rfc822Name = X509_NAME_ENTRY_get_data(ne); + if (gntmp.d.rfc822Name->type != V_ASN1_IA5STRING) + return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; + + r = nc_match(&gntmp, nc); + + if (r != X509_V_OK) + return r; + } + + } + + for (i = 0; i < sk_GENERAL_NAME_num(x->altname); i++) + { + GENERAL_NAME *gen = sk_GENERAL_NAME_value(x->altname, i); + r = nc_match(gen, nc); + if (r != X509_V_OK) + return r; + } + + return X509_V_OK; + + } + +static int nc_match(GENERAL_NAME *gen, NAME_CONSTRAINTS *nc) + { + GENERAL_SUBTREE *sub; + int i, r, match = 0; + + /* Permitted subtrees: if any subtrees exist of matching the type + * at least one subtree must match. + */ + + for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->permittedSubtrees); i++) + { + sub = sk_GENERAL_SUBTREE_value(nc->permittedSubtrees, i); + if (gen->type != sub->base->type) + continue; + if (sub->minimum || sub->maximum) + return X509_V_ERR_SUBTREE_MINMAX; + /* If we already have a match don't bother trying any more */ + if (match == 2) + continue; + if (match == 0) + match = 1; + r = nc_match_single(gen, sub->base); + if (r == X509_V_OK) + match = 2; + else if (r != X509_V_ERR_PERMITTED_VIOLATION) + return r; + } + + if (match == 1) + return X509_V_ERR_PERMITTED_VIOLATION; + + /* Excluded subtrees: must not match any of these */ + + for (i = 0; i < sk_GENERAL_SUBTREE_num(nc->excludedSubtrees); i++) + { + sub = sk_GENERAL_SUBTREE_value(nc->excludedSubtrees, i); + if (gen->type != sub->base->type) + continue; + if (sub->minimum || sub->maximum) + return X509_V_ERR_SUBTREE_MINMAX; + + r = nc_match_single(gen, sub->base); + if (r == X509_V_OK) + return X509_V_ERR_EXCLUDED_VIOLATION; + else if (r != X509_V_ERR_PERMITTED_VIOLATION) + return r; + + } + + return X509_V_OK; + + } + +static int nc_match_single(GENERAL_NAME *gen, GENERAL_NAME *base) + { + switch(base->type) + { + case GEN_DIRNAME: + return nc_dn(gen->d.directoryName, base->d.directoryName); + + case GEN_DNS: + return nc_dns(gen->d.dNSName, base->d.dNSName); + + case GEN_EMAIL: + return nc_email(gen->d.rfc822Name, base->d.rfc822Name); + + case GEN_URI: + return nc_uri(gen->d.uniformResourceIdentifier, + base->d.uniformResourceIdentifier); + + default: + return X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE; + } + + } + +/* directoryName name constraint matching. + * The canonical encoding of X509_NAME makes this comparison easy. It is + * matched if the subtree is a subset of the name. + */ + +static int nc_dn(X509_NAME *nm, X509_NAME *base) + { + if (base->canon_enclen > nm->canon_enclen) + return X509_V_ERR_PERMITTED_VIOLATION; + if (memcmp(base->canon_enc, nm->canon_enc, base->canon_enclen)) + return X509_V_ERR_PERMITTED_VIOLATION; + return X509_V_OK; + } + +static int nc_dns(ASN1_IA5STRING *dns, ASN1_IA5STRING *base) + { + char *baseptr = (char *)base->data; + char *dnsptr = (char *)dns->data; + /* Empty matches everything */ + if (!*baseptr) + return X509_V_OK; + /* Otherwise can add zero or more components on the left so + * compare RHS and if dns is longer and expect '.' as preceding + * character. + */ + if (dns->length > base->length) + { + dnsptr += dns->length - base->length; + if (dnsptr[-1] != '.') + return X509_V_ERR_PERMITTED_VIOLATION; + } + + if (strcasecmp(baseptr, dnsptr)) + return X509_V_ERR_PERMITTED_VIOLATION; + + return X509_V_OK; + + } + +static int nc_email(ASN1_IA5STRING *eml, ASN1_IA5STRING *base) + { + const char *baseptr = (char *)base->data; + const char *emlptr = (char *)eml->data; + + const char *baseat = strchr(baseptr, '@'); + const char *emlat = strchr(emlptr, '@'); + if (!emlat) + return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; + /* Special case: inital '.' is RHS match */ + if (!baseat && (*baseptr == '.')) + { + if (eml->length > base->length) + { + emlptr += eml->length - base->length; + if (!strcasecmp(baseptr, emlptr)) + return X509_V_OK; + } + return X509_V_ERR_PERMITTED_VIOLATION; + } + + /* If we have anything before '@' match local part */ + + if (baseat) + { + if (baseat != baseptr) + { + if ((baseat - baseptr) != (emlat - emlptr)) + return X509_V_ERR_PERMITTED_VIOLATION; + /* Case sensitive match of local part */ + if (strncmp(baseptr, emlptr, emlat - emlptr)) + return X509_V_ERR_PERMITTED_VIOLATION; + } + /* Position base after '@' */ + baseptr = baseat + 1; + } + emlptr = emlat + 1; + /* Just have hostname left to match: case insensitive */ + if (strcasecmp(baseptr, emlptr)) + return X509_V_ERR_PERMITTED_VIOLATION; + + return X509_V_OK; + + } + +static int nc_uri(ASN1_IA5STRING *uri, ASN1_IA5STRING *base) + { + const char *baseptr = (char *)base->data; + const char *hostptr = (char *)uri->data; + const char *p = strchr(hostptr, ':'); + size_t hostlen; + /* Check for foo:// and skip past it */ + if (!p || (p[1] != '/') || (p[2] != '/')) + return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; + hostptr = p + 3; + + /* Determine length of hostname part of URI */ + + /* Look for a port indicator as end of hostname first */ + + p = strchr(hostptr, ':'); + /* Otherwise look for trailing slash */ + if (!p) + p = strchr(hostptr, '/'); + + if (!p) + hostlen = strlen(hostptr); + else + hostlen = p - hostptr; + + if (hostlen == 0) + return X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; + + /* Special case: inital '.' is RHS match */ + if (*baseptr == '.') + { + if (hostlen > base->length) + { + p = hostptr + hostlen - base->length; + if (!strncasecmp(p, baseptr, base->length)) + return X509_V_OK; + } + return X509_V_ERR_PERMITTED_VIOLATION; + } + + if ((base->length != hostlen) || strncasecmp(hostptr, baseptr, hostlen)) + return X509_V_ERR_PERMITTED_VIOLATION; + + return X509_V_OK; + + } diff --git a/crypto/x509v3/v3_purp.c b/crypto/x509v3/v3_purp.c index caf80e6d3f..7bb6658b90 100644 --- a/crypto/x509v3/v3_purp.c +++ b/crypto/x509v3/v3_purp.c @@ -295,6 +295,7 @@ int X509_supported_extension(X509_EXTENSION *ex) #endif NID_policy_constraints, /* 401 */ NID_proxyCertInfo, /* 663 */ + NID_name_constraints, /* 666 */ NID_inhibit_any_policy /* 748 */ }; @@ -448,6 +449,10 @@ static void x509v3_cache_extensions(X509 *x) } x->skid =X509_get_ext_d2i(x, NID_subject_key_identifier, NULL, NULL); x->akid =X509_get_ext_d2i(x, NID_authority_key_identifier, NULL, NULL); + x->altname = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + x->nc = X509_get_ext_d2i(x, NID_name_constraints, &i, NULL); + if (!x->nc && (i != -1)) + x->ex_flags |= EXFLAG_INVALID; setup_crldp(x); diff --git a/crypto/x509v3/x509v3.h b/crypto/x509v3/x509v3.h index 5babc2b353..c4a8cfca84 100644 --- a/crypto/x509v3/x509v3.h +++ b/crypto/x509v3/x509v3.h @@ -305,10 +305,10 @@ typedef struct GENERAL_SUBTREE_st { DECLARE_STACK_OF(GENERAL_SUBTREE) -typedef struct NAME_CONSTRAINTS_st { +struct NAME_CONSTRAINTS_st { STACK_OF(GENERAL_SUBTREE) *permittedSubtrees; STACK_OF(GENERAL_SUBTREE) *excludedSubtrees; -} NAME_CONSTRAINTS; +}; typedef struct POLICY_CONSTRAINTS_st { ASN1_INTEGER *requireExplicitPolicy; @@ -549,6 +549,8 @@ DECLARE_ASN1_FUNCTIONS(ISSUING_DIST_POINT) int DIST_POINT_set_dpname(DIST_POINT_NAME *dpn, X509_NAME *iname); +int NAME_CONSTRAINTS_check(X509 *x, NAME_CONSTRAINTS *nc); + DECLARE_ASN1_FUNCTIONS(ACCESS_DESCRIPTION) DECLARE_ASN1_FUNCTIONS(AUTHORITY_INFO_ACCESS) -- 2.34.1