Initial support for name constraints certificate extension.
TODO: robustness checking on name forms.
This commit is contained in:
parent
ab9c689ad3
commit
e9746e03ee
14
CHANGES
14
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.
|
||||
|
@ -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);
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user