Skip to content

Commit

Permalink
Check chain extensions also for trusted certificates
Browse files Browse the repository at this point in the history
This includes basic constraints, key usages, issuer EKUs and auxiliary
trust OIDs (given a trust suitably related to the intended purpose).

Added tests and updated documentation.

Reviewed-by: Dr. Stephen Henson <steve@openssl.org>
  • Loading branch information
Viktor Dukhovni committed Feb 1, 2016
1 parent 1b4cf96 commit 0daccd4
Show file tree
Hide file tree
Showing 15 changed files with 315 additions and 98 deletions.
13 changes: 12 additions & 1 deletion apps/opt.c
Original file line number Diff line number Diff line change
Expand Up @@ -496,14 +496,25 @@ int opt_verify(int opt, X509_VERIFY_PARAM *vpm)
X509_VERIFY_PARAM_add0_policy(vpm, otmp);
break;
case OPT_V_PURPOSE:
/* purpose name -> purpose index */
i = X509_PURPOSE_get_by_sname(opt_arg());
if (i < 0) {
BIO_printf(bio_err, "%s: Invalid purpose %s\n", prog, opt_arg());
return 0;
}

/* purpose index -> purpose object */
xptmp = X509_PURPOSE_get0(i);

/* purpose object -> purpose value */
i = X509_PURPOSE_get_id(xptmp);
X509_VERIFY_PARAM_set_purpose(vpm, i);

if (!X509_VERIFY_PARAM_set_purpose(vpm, i)) {
BIO_printf(bio_err,
"%s: Internal error setting purpose %s\n",
prog, opt_arg());
return 0;
}
break;
case OPT_V_VERIFY_NAME:
vtmp = X509_VERIFY_PARAM_lookup(opt_arg());
Expand Down
19 changes: 11 additions & 8 deletions crypto/x509/x509_trs.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ static int trust_1oidany(X509_TRUST *trust, X509 *x, int flags)

static int trust_1oid(X509_TRUST *trust, X509 *x, int flags)
{
if (x->aux)
if (x->aux && (x->aux->trust || x->aux->reject))
return obj_trust(trust->arg1, x, flags);
return X509_TRUST_UNTRUSTED;
}
Expand All @@ -293,23 +293,26 @@ static int trust_compat(X509_TRUST *trust, X509 *x, int flags)

static int obj_trust(int id, X509 *x, int flags)
{
ASN1_OBJECT *obj;
X509_CERT_AUX *ax = x->aux;
int i;
X509_CERT_AUX *ax;
ax = x->aux;

if (!ax)
return X509_TRUST_UNTRUSTED;
if (ax->reject) {
for (i = 0; i < sk_ASN1_OBJECT_num(ax->reject); i++) {
obj = sk_ASN1_OBJECT_value(ax->reject, i);
if (OBJ_obj2nid(obj) == id)
ASN1_OBJECT *obj = sk_ASN1_OBJECT_value(ax->reject, i);
int nid = OBJ_obj2nid(obj);

if (nid == id || nid == NID_anyExtendedKeyUsage)
return X509_TRUST_REJECTED;
}
}
if (ax->trust) {
for (i = 0; i < sk_ASN1_OBJECT_num(ax->trust); i++) {
obj = sk_ASN1_OBJECT_value(ax->trust, i);
if (OBJ_obj2nid(obj) == id)
ASN1_OBJECT *obj = sk_ASN1_OBJECT_value(ax->trust, i);
int nid = OBJ_obj2nid(obj);

if (nid == id || nid == NID_anyExtendedKeyUsage)
return X509_TRUST_TRUSTED;
}
/*
Expand Down
118 changes: 85 additions & 33 deletions crypto/x509/x509_vfy.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,18 +362,61 @@ static STACK_OF(X509) *lookup_certs_sk(X509_STORE_CTX *ctx, X509_NAME *nm)
return sk;
}

/*
* Check EE or CA certificate purpose. For trusted certificates explicit local
* auxiliary trust can be used to override EKU-restrictions.
*/
static int check_purpose(X509_STORE_CTX *ctx, X509 *x, int purpose, int depth,
int must_be_ca)
{
int pu_ok = X509_check_purpose(x, purpose, must_be_ca > 0);
int tr_ok = X509_TRUST_UNTRUSTED;

/*
* For trusted certificates we want to see whether any auxiliary trust
* settings override the purpose constraints we failed to meet above.
*
* This is complicated by the fact that the trust ordinals in
* ctx->param->trust are entirely independent of the purpose ordinals in
* ctx->param->purpose!
*
* What connects them is their mutual initialization via calls from
* X509_STORE_CTX_set_default() into X509_VERIFY_PARAM_lookup() which sets
* related values of both param->trust and param->purpose. It is however
* typically possible to infer associated trust values from a purpose value
* via the X509_PURPOSE API.
*
* Therefore, we can only check for trust overrides when the purpose we're
* checking is the same as ctx->param->purpose and ctx->param->trust is
* also set, or can be inferred from the purpose.
*/
if (depth >= ctx->num_untrusted && purpose == ctx->param->purpose)
tr_ok = X509_check_trust(x, ctx->param->trust, X509_TRUST_NO_SS_COMPAT);

if (tr_ok != X509_TRUST_REJECTED &&
(pu_ok == 1 ||
(pu_ok != 0 && (ctx->param->flags & X509_V_FLAG_X509_STRICT) == 0)))
return 1;

ctx->error = X509_V_ERR_INVALID_PURPOSE;
ctx->error_depth = depth;
ctx->current_cert = x;
return ctx->verify_cb(0, ctx);
}

/*
* Check a certificate chains extensions for consistency with the supplied
* purpose
*/

static int check_chain_extensions(X509_STORE_CTX *ctx)
{
int i, ok = 0, must_be_ca, plen = 0;
int i, must_be_ca, plen = 0;
X509 *x;
int proxy_path_length = 0;
int purpose;
int allow_proxy_certs;
int num = sk_X509_num(ctx->chain);

/*-
* must_be_ca can have 1 of 3 values:
Expand Down Expand Up @@ -402,26 +445,23 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
purpose = ctx->param->purpose;
}

/* Check all untrusted certificates */
for (i = 0; i == 0 || i < ctx->num_untrusted; i++) {
for (i = 0; i < num; i++) {
int ret;
x = sk_X509_value(ctx->chain, i);
if (!(ctx->param->flags & X509_V_FLAG_IGNORE_CRITICAL)
&& (x->ex_flags & EXFLAG_CRITICAL)) {
ctx->error = X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION;
ctx->error_depth = i;
ctx->current_cert = x;
ok = ctx->verify_cb(0, ctx);
if (!ok)
goto end;
if (!ctx->verify_cb(0, ctx))
return 0;
}
if (!allow_proxy_certs && (x->ex_flags & EXFLAG_PROXY)) {
ctx->error = X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED;
ctx->error_depth = i;
ctx->current_cert = x;
ok = ctx->verify_cb(0, ctx);
if (!ok)
goto end;
if (!ctx->verify_cb(0, ctx))
return 0;
}
ret = X509_check_ca(x);
switch (must_be_ca) {
Expand Down Expand Up @@ -453,22 +493,12 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
if (ret == 0) {
ctx->error_depth = i;
ctx->current_cert = x;
ok = ctx->verify_cb(0, ctx);
if (!ok)
goto end;
if (! ctx->verify_cb(0, ctx))
return 0;
}
if (ctx->param->purpose > 0) {
ret = X509_check_purpose(x, purpose, must_be_ca > 0);
if ((ret == 0)
|| ((ctx->param->flags & X509_V_FLAG_X509_STRICT)
&& (ret != 1))) {
ctx->error = X509_V_ERR_INVALID_PURPOSE;
ctx->error_depth = i;
ctx->current_cert = x;
ok = ctx->verify_cb(0, ctx);
if (!ok)
goto end;
}
if (purpose > 0) {
if (!check_purpose(ctx, x, purpose, i, must_be_ca))
return 0;
}
/* Check pathlen if not self issued */
if ((i > 1) && !(x->ex_flags & EXFLAG_SI)
Expand All @@ -477,9 +507,8 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
ctx->error = X509_V_ERR_PATH_LENGTH_EXCEEDED;
ctx->error_depth = i;
ctx->current_cert = x;
ok = ctx->verify_cb(0, ctx);
if (!ok)
goto end;
if (!ctx->verify_cb(0, ctx))
return 0;
}
/* Increment path length if not self issued */
if (!(x->ex_flags & EXFLAG_SI))
Expand All @@ -494,18 +523,15 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
ctx->error = X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED;
ctx->error_depth = i;
ctx->current_cert = x;
ok = ctx->verify_cb(0, ctx);
if (!ok)
goto end;
if (!ctx->verify_cb(0, ctx))
return 0;
}
proxy_path_length++;
must_be_ca = 0;
} else
must_be_ca = 1;
}
ok = 1;
end:
return ok;
return 1;
}

static int check_name_constraints(X509_STORE_CTX *ctx)
Expand Down Expand Up @@ -2016,11 +2042,20 @@ void X509_STORE_CTX_set0_crls(X509_STORE_CTX *ctx, STACK_OF(X509_CRL) *sk)

int X509_STORE_CTX_set_purpose(X509_STORE_CTX *ctx, int purpose)
{
/*
* XXX: Why isn't this function always used to set the associated trust?
* Should there even be a VPM->trust field at all? Or should the trust
* always be inferred from the purpose by X509_STORE_CTX_init().
*/
return X509_STORE_CTX_purpose_inherit(ctx, 0, purpose, 0);
}

int X509_STORE_CTX_set_trust(X509_STORE_CTX *ctx, int trust)
{
/*
* XXX: See above, this function would only be needed when the default
* trust for the purpose needs an override in a corner case.
*/
return X509_STORE_CTX_purpose_inherit(ctx, 0, 0, trust);
}

Expand Down Expand Up @@ -2054,6 +2089,11 @@ int X509_STORE_CTX_purpose_inherit(X509_STORE_CTX *ctx, int def_purpose,
ptmp = X509_PURPOSE_get0(idx);
if (ptmp->trust == X509_TRUST_DEFAULT) {
idx = X509_PURPOSE_get_by_id(def_purpose);
/*
* XXX: In the two callers above def_purpose is always 0, which is
* not a known value, so idx will always be -1. How is the
* X509_TRUST_DEFAULT case actually supposed to be handled?
*/
if (idx == -1) {
X509err(X509_F_X509_STORE_CTX_PURPOSE_INHERIT,
X509_R_UNKNOWN_PURPOSE_ID);
Expand Down Expand Up @@ -2211,6 +2251,18 @@ int X509_STORE_CTX_init(X509_STORE_CTX *ctx, X509_STORE *store, X509 *x509,
goto err;
}

/*
* XXX: For now, continue to inherit trust from VPM, but infer from the
* purpose if this still yields the default value.
*/
if (ctx->param->trust == X509_TRUST_DEFAULT) {
int idx = X509_PURPOSE_get_by_id(ctx->param->purpose);
X509_PURPOSE *xp = X509_PURPOSE_get0(idx);

if (xp != NULL)
ctx->param->trust = X509_PURPOSE_get_trust(xp);
}

if (CRYPTO_new_ex_data(CRYPTO_EX_INDEX_X509_STORE_CTX, ctx,
&ctx->ex_data))
return 1;
Expand Down
6 changes: 3 additions & 3 deletions crypto/x509/x509_vpm.c
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ static void x509_verify_param_zero(X509_VERIFY_PARAM *param)
return;
param->name = NULL;
param->purpose = 0;
param->trust = 0;
param->trust = X509_TRUST_DEFAULT;
/*
* param->inh_flags = X509_VP_FLAG_DEFAULT;
*/
Expand Down Expand Up @@ -243,7 +243,7 @@ int X509_VERIFY_PARAM_inherit(X509_VERIFY_PARAM *dest,
to_overwrite = 0;

x509_verify_param_copy(purpose, 0);
x509_verify_param_copy(trust, 0);
x509_verify_param_copy(trust, X509_TRUST_DEFAULT);
x509_verify_param_copy(depth, -1);

/* If overwrite or check time not set, copy across */
Expand Down Expand Up @@ -511,7 +511,7 @@ static const X509_VERIFY_PARAM default_table[] = {
"default", /* X509 default parameters */
0, /* Check time */
0, /* internal flags */
0, /* flags */
X509_V_FLAG_TRUSTED_FIRST, /* flags */
0, /* purpose */
0, /* trust */
100, /* depth */
Expand Down
26 changes: 16 additions & 10 deletions doc/apps/verify.pod
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,16 @@ When constructing the certificate chain, use the trusted certificates specified
via B<-CAfile>, B<-CApath> or B<-trusted> before any certificates specified via
B<-untrusted>.
This can be useful in environments with Bridge or Cross-Certified CAs.
As of OpenSSL 1.1.0 this option is on by default and cannot be disabled.

=item B<-no_alt_chains>

When building a certificate chain, if the first certificate chain found is not
trusted, then OpenSSL will continue to check to see if an alternative chain can
be found that is trusted. With this option that behaviour is suppressed so that
only the first chain found is ever used. Using this option will force the
behaviour to match that of OpenSSL versions prior to 1.1.0.
By default, unless B<-trusted_first> is specified, when building a certificate
chain, if the first certificate chain found is not trusted, then OpenSSL will
attempt to replace untrusted issuer certificates with certificates from the
trust store to see if an alternative chain can be found that is trusted.
As of OpenSSL 1.1.0, with B<-trusted_first> always on, this option has no
effect.

=item B<-untrusted file>

Expand Down Expand Up @@ -264,13 +266,17 @@ the subject certificate.

Use default verification policies like trust model and required certificate
policies identified by B<name>.
The trust model determines which auxiliary trust or reject OIDs are applicable
to verifying the given certificate chain.
See the B<-addtrust> and B<-addreject> options of the L<x509(1)> command-line
utility.
Supported policy names include: B<default>, B<pkcs7>, B<smime_sign>,
B<ssl_client>, B<ssl_server>.
This checks not only the purpose of the leaf certificate, but also the
trust settings of the trusted CAs.
When in doubt, use this option rather than B<-purpose>.
The B<-verify_name> option more closely matches how certificates are checked in
e.g. SSL and S/MIME.
These mimics the combinations of purpose and trust settings used in SSL, CMS
and S/MIME.
As of OpenSSL 1.1.0, the trust model is inferred from the purpose when not
specified, so the B<-verify_name> options are functionally equivalent to the
corresponding B<-purpose> settings.

=item B<-x509_strict>

Expand Down
9 changes: 6 additions & 3 deletions doc/apps/x509.pod
Original file line number Diff line number Diff line change
Expand Up @@ -289,9 +289,12 @@ clears all the prohibited or rejected uses of the certificate.

=item B<-addtrust arg>

adds a trusted certificate use. Any object name can be used here
but currently only B<clientAuth> (SSL client use), B<serverAuth>
(SSL server use) and B<emailProtection> (S/MIME email) are used.
adds a trusted certificate use.
Any object name can be used here but currently only B<clientAuth> (SSL client
use), B<serverAuth> (SSL server use), B<emailProtection> (S/MIME email) and
B<anyExtendedKeyUsage> are used.
As of OpenSSL 1.1.0, the last of these blocks all purposes when rejected or
enables all purposes when trusted.
Other OpenSSL applications may define additional uses.

=item B<-addreject arg>
Expand Down
17 changes: 13 additions & 4 deletions doc/crypto/X509_VERIFY_PARAM_set_flags.pod
Original file line number Diff line number Diff line change
Expand Up @@ -197,11 +197,20 @@ verification. If this flag is set then additional status codes will be sent
to the verification callback and it B<must> be prepared to handle such cases
without assuming they are hard errors.

If B<X509_V_FLAG_TRUSTED_FIRST> is set, when constructing the certificate chain,
L<X509_verify_cert(3)> will search the trust store for issuer certificates before
searching the provided untrusted certificates.
As of OpenSSL 1.1.0 this option is on by default and cannot be disabled.

The B<X509_V_FLAG_NO_ALT_CHAINS> flag suppresses checking for alternative
chains. By default, when building a certificate chain, if the first certificate
chain found is not trusted, then OpenSSL will continue to check to see if an
alternative chain can be found that is trusted. With this flag set the behaviour
will match that of OpenSSL versions prior to 1.1.0.
chains.
By default, unless B<X509_V_FLAG_TRUSTED_FIRST> is set, when building a
certificate chain, if the first certificate chain found is not trusted, then
OpenSSL will attempt to replace untrusted certificates supplied by the peer
with certificates from the trust store to see if an alternative chain can be
found that is trusted.
As of OpenSSL 1.1.0, with B<X509_V_FLAG_TRUSTED_FIRST> always set, this option
has no effect.

The B<X509_V_FLAG_NO_CHECK_TIME> flag suppresses checking the validity period
of certificates and CRLs against the current time. If X509_VERIFY_PARAM_set_time()
Expand Down
Loading

2 comments on commit 0daccd4

@xnox
Copy link
Contributor

@xnox xnox commented on 0daccd4 Jun 25, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is a backport of this patch, or similar functionality available for the 1.0.2 series?

Letsencrypt has switched to a default chain, that will break connectivity for users that trust letsencrypt CA and use 1.0.2 openssl series.

I.e. making X509_V_FLAG_TRUSTED_FIRST the default.

@galt
Copy link

@galt galt commented on 0daccd4 Oct 9, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openssl1.0.2 for our clients using our software is well broken by this issue with LetsEncypt certs.
Please set X509_V_FLAG_TRUSTED_FIRST to be the default.
Then it can get picked up by various Linux distros.

Please sign in to comment.