Skip to content

Commit

Permalink
X509: Add support for directly checking leaf cert EKU
Browse files Browse the repository at this point in the history
  • Loading branch information
DDvO committed Sep 12, 2024
1 parent 3cd5aeb commit 4d2d083
Show file tree
Hide file tree
Showing 28 changed files with 290 additions and 28 deletions.
7 changes: 6 additions & 1 deletion apps/include/opt.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
*/
# define OPT_V_ENUM \
OPT_V__FIRST=2000, \
OPT_V_POLICY, OPT_V_PURPOSE, OPT_V_VERIFY_NAME, OPT_V_VERIFY_DEPTH, \
OPT_V_POLICY, OPT_V_PURPOSE, OPT_V_EKU, \
OPT_V_VERIFY_NAME, OPT_V_VERIFY_DEPTH, \
OPT_V_ATTIME, OPT_V_VERIFY_HOSTNAME, OPT_V_VERIFY_EMAIL, \
OPT_V_VERIFY_IP, OPT_V_IGNORE_CRITICAL, OPT_V_ISSUER_CHECKS, \
OPT_V_CRL_CHECK, OPT_V_CRL_CHECK_ALL, OPT_V_POLICY_CHECK, \
Expand All @@ -38,6 +39,8 @@
{ "policy", OPT_V_POLICY, 's', "adds policy to the acceptable policy set"}, \
{ "purpose", OPT_V_PURPOSE, 's', \
"certificate chain purpose"}, \
{ "eku", OPT_V_EKU, 's', \
"certificate Extended Key Usage, e.g., serverAuth"}, \
{ "verify_name", OPT_V_VERIFY_NAME, 's', "verification policy name"}, \
{ "verify_depth", OPT_V_VERIFY_DEPTH, 'n', \
"chain depth limit" }, \
Expand Down Expand Up @@ -88,6 +91,7 @@
OPT_V__FIRST: case OPT_V__LAST: break; \
case OPT_V_POLICY: \
case OPT_V_PURPOSE: \
case OPT_V_EKU: \
case OPT_V_VERIFY_NAME: \
case OPT_V_VERIFY_DEPTH: \
case OPT_V_VERIFY_AUTH_LEVEL: \
Expand Down Expand Up @@ -409,6 +413,7 @@ int opt_cipher_silent(const char *name, EVP_CIPHER **cipherp);
int opt_check_md(const char *name);
int opt_md(const char *name, EVP_MD **mdp);
int opt_md_silent(const char *name, EVP_MD **mdp);
int opt_oid(const char *name, const char *desc);

int opt_int(const char *arg, int *result);
void opt_set_unknown_name(const char *name);
Expand Down
19 changes: 16 additions & 3 deletions apps/lib/opt.c
Original file line number Diff line number Diff line change
Expand Up @@ -446,9 +446,7 @@ int opt_cipher(const char *name, EVP_CIPHER **cipherp)
return ret;
}

/*
* Parse message digest name, put it in *EVP_MD; return 0 on failure, else 1.
*/
/* Parse message digest name, put it in *EVP_MD; return 0 on failure, else 1. */
int opt_md_silent(const char *name, EVP_MD **mdp)
{
EVP_MD *md;
Expand Down Expand Up @@ -490,6 +488,16 @@ int opt_check_md(const char *name)
return 0;
}

/* Parse an OID name; returns its NID or 0 on failure. */
int opt_oid(const char *name, const char *desc)
{
int nid = OBJ_txt2nid(name);

if (nid == 0)
opt_printf_stderr("%s: Invalid OID name for %s: %s\n", prog, desc, name);
return nid;
}

/* Look through a list of name/value pairs. */
int opt_pair(const char *name, const OPT_PAIR* pairs, int *result)
{
Expand Down Expand Up @@ -753,6 +761,11 @@ int opt_verify(int opt, X509_VERIFY_PARAM *vpm)
return 0;
}
break;
case OPT_V_EKU:
if ((i = opt_oid(opt_arg(), "Extended Key Usage")) == 0)
return 0;
X509_VERIFY_PARAM_set_eku(vpm, i);
break;
case OPT_V_VERIFY_NAME:
vtmp = X509_VERIFY_PARAM_lookup(opt_arg());
if (vtmp == NULL) {
Expand Down
24 changes: 19 additions & 5 deletions crypto/x509/v3_purp.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ static int check_purpose_ocsp_helper(const X509_PURPOSE *xp, const X509 *x,
static int xp_cmp(const X509_PURPOSE *const *a, const X509_PURPOSE *const *b);
static void xptable_free(X509_PURPOSE *p);

/* Directly check for allowed extended key usage (EKU) */
int X509_check_eku(X509 *x, int nid)
{
int eku, i, n = sk_ASN1_OBJECT_num(x->ex_ekus);

if (nid == NID_anyExtendedKeyUsage || n <= 0)
return 1;
for (i = 0; i < n; i++) {
eku = OBJ_obj2nid(sk_ASN1_OBJECT_value(x->ex_ekus, i));
if (eku == NID_anyExtendedKeyUsage || eku == nid)
return 1;
}
return 0;
}

static X509_PURPOSE xstandard[] = {
{X509_PURPOSE_SSL_CLIENT, X509_TRUST_SSL_CLIENT, 0,
check_purpose_ssl_client, "SSL client", "sslclient", NULL},
Expand Down Expand Up @@ -403,7 +418,6 @@ int ossl_x509v3_cache_extensions(X509 *x)
PROXY_CERT_INFO_EXTENSION *pci;
ASN1_BIT_STRING *usage;
ASN1_BIT_STRING *ns;
EXTENDED_KEY_USAGE *extusage;
int i;
int res;

Expand Down Expand Up @@ -491,10 +505,11 @@ int ossl_x509v3_cache_extensions(X509 *x)

/* Handle extended key usage */
x->ex_xkusage = 0;
if ((extusage = X509_get_ext_d2i(x, NID_ext_key_usage, &i, NULL)) != NULL) {
x->ex_ekus = X509_get_ext_d2i(x, NID_ext_key_usage, &i, NULL);
if (x->ex_ekus != NULL) {
x->ex_flags |= EXFLAG_XKUSAGE;
for (i = 0; i < sk_ASN1_OBJECT_num(extusage); i++) {
switch (OBJ_obj2nid(sk_ASN1_OBJECT_value(extusage, i))) {
for (i = 0; i < sk_ASN1_OBJECT_num(x->ex_ekus); i++) {
switch (OBJ_obj2nid(sk_ASN1_OBJECT_value(x->ex_ekus, i))) {
case NID_server_auth:
x->ex_xkusage |= XKU_SSL_SERVER;
break;
Expand Down Expand Up @@ -528,7 +543,6 @@ int ossl_x509v3_cache_extensions(X509 *x)
break;
}
}
sk_ASN1_OBJECT_pop_free(extusage, ASN1_OBJECT_free);
} else if (i != -1) {
x->ex_flags |= EXFLAG_INVALID;
}
Expand Down
1 change: 1 addition & 0 deletions crypto/x509/x509_local.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ struct X509_VERIFY_PARAM_st {
time_t check_time; /* Time to use */
uint32_t inh_flags; /* Inheritance flags */
unsigned long flags; /* Various verify flags */
int eku; /* Extended Key Usage NID to check leaf certs */
int purpose; /* purpose to check untrusted certificates */
int trust; /* trust setting to check */
int depth; /* Verify depth */
Expand Down
3 changes: 2 additions & 1 deletion crypto/x509/x509_txt.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,8 @@ const char *X509_verify_cert_error_string(long n)
return "Certificate public key has explicit ECC parameters";
case X509_V_ERR_RPK_UNTRUSTED:
return "Raw public key untrusted, no trusted keys configured";

case X509_V_ERR_INVALID_EXTENDED_KEY_USAGE:
return "unsupported certificate Extended Key Usage";
/*
* Entries must be kept consistent with include/openssl/x509_vfy.h.in
* and with doc/man3/X509_STORE_CTX_get_error.pod
Expand Down
18 changes: 13 additions & 5 deletions crypto/x509/x509_vfy.c
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,10 @@ static int verify_cb_cert(X509_STORE_CTX *ctx, X509 *x, int depth, int err)
}

#define CB_FAIL_IF(cond, ctx, cert, depth, err) \
if ((cond) && verify_cb_cert(ctx, cert, depth, err) == 0) \
return 0
do { \
if ((cond) && verify_cb_cert(ctx, cert, depth, err) == 0) \
return 0; \
} while (0)

/*-
* Inform the verify callback of an error, CRL-specific variant. Here, the
Expand Down Expand Up @@ -456,7 +458,7 @@ static STACK_OF(X509) *lookup_certs_sk(X509_STORE_CTX *ctx, const X509_NAME *nm)

/*
* Check EE or CA certificate purpose. For trusted certificates explicit local
* auxiliary trust can be used to override EKU-restrictions.
* auxiliary trust can be used to override EKU-restrictions in these certs.
* Sadly, returns 0 also on internal error in ctx->verify_cb().
*/
static int check_purpose(X509_STORE_CTX *ctx, X509 *x, int purpose, int depth,
Expand Down Expand Up @@ -507,14 +509,16 @@ static int check_purpose(X509_STORE_CTX *ctx, X509 *x, int purpose, int depth,
}

/*-
* Check extensions of a cert chain for consistency with the supplied purpose.
* Check extensions of a cert chain for consistency with the supplied purpose
* and leaf cert EKU.
* Sadly, returns 0 also on internal error in ctx->verify_cb().
*/
static int check_extensions(X509_STORE_CTX *ctx)
{
int i, must_be_ca, plen = 0;
X509 *x;
int ret, proxy_path_length = 0;
int eku = ctx->param->eku;
int purpose, allow_proxy_certs, num = sk_X509_num(ctx->chain);

/*-
Expand Down Expand Up @@ -642,8 +646,12 @@ static int check_extensions(X509_STORE_CTX *ctx)
}
}

if (eku != 0 && i < ctx->num_untrusted)
/* EKU requirement overrides purpose check for untrusted certs */
CB_FAIL_IF(X509_check_eku(x, eku) <= 0, ctx, x, i,
X509_V_ERR_INVALID_EXTENDED_KEY_USAGE);
/* check_purpose() makes the callback as needed */
if (purpose > 0 && !check_purpose(ctx, x, purpose, i, must_be_ca))
else if (purpose > 0 && !check_purpose(ctx, x, purpose, i, must_be_ca))
return 0;
/* Check path length */
CB_FAIL_IF(i > 1 && x->ex_pathlen != -1
Expand Down
14 changes: 14 additions & 0 deletions crypto/x509/x509_vpm.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ int X509_VERIFY_PARAM_inherit(X509_VERIFY_PARAM *dest,
to_default = (inh_flags & X509_VP_FLAG_DEFAULT) != 0;
to_overwrite = (inh_flags & X509_VP_FLAG_OVERWRITE) != 0;

x509_verify_param_copy(eku, 0);
x509_verify_param_copy(purpose, 0);
x509_verify_param_copy(trust, X509_TRUST_DEFAULT);
x509_verify_param_copy(depth, -1);
Expand Down Expand Up @@ -296,6 +297,13 @@ int X509_VERIFY_PARAM_set_inh_flags(X509_VERIFY_PARAM *param, uint32_t flags)
return 1;
}

int X509_VERIFY_PARAM_set_eku(X509_VERIFY_PARAM *param, int eku)
{
/* Should check range of acceptable EKU OIDs, but this is fuzzy */
param->eku = eku;
return 1;
}

int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *param, int purpose)
{
return X509_PURPOSE_set(&param->purpose, purpose);
Expand Down Expand Up @@ -509,6 +517,7 @@ static const X509_VERIFY_PARAM default_table[] = {
0, /* check time to use */
0, /* inheritance flags */
0, /* flags */
0, /* eku */
X509_PURPOSE_CODE_SIGN, /* purpose */
X509_TRUST_OBJECT_SIGN, /* trust */
-1, /* depth */
Expand All @@ -521,6 +530,7 @@ static const X509_VERIFY_PARAM default_table[] = {
0, /* check time to use */
0, /* inheritance flags */
X509_V_FLAG_TRUSTED_FIRST, /* flags */
0, /* eku */
0, /* purpose */
0, /* trust */
100, /* depth */
Expand All @@ -533,6 +543,7 @@ static const X509_VERIFY_PARAM default_table[] = {
0, /* check time to use */
0, /* inheritance flags */
0, /* flags */
0, /* eku */
X509_PURPOSE_SMIME_SIGN, /* purpose */
X509_TRUST_EMAIL, /* trust */
-1, /* depth */
Expand All @@ -545,6 +556,7 @@ static const X509_VERIFY_PARAM default_table[] = {
0, /* check time to use */
0, /* inheritance flags */
0, /* flags */
0, /* eku */
X509_PURPOSE_SMIME_SIGN, /* purpose */
X509_TRUST_EMAIL, /* trust */
-1, /* depth */
Expand All @@ -557,6 +569,7 @@ static const X509_VERIFY_PARAM default_table[] = {
0, /* check time to use */
0, /* inheritance flags */
0, /* flags */
0, /* eku */
X509_PURPOSE_SSL_CLIENT, /* purpose */
X509_TRUST_SSL_CLIENT, /* trust */
-1, /* depth */
Expand All @@ -569,6 +582,7 @@ static const X509_VERIFY_PARAM default_table[] = {
0, /* check time to use */
0, /* inheritance flags */
0, /* flags */
0, /* eku */
X509_PURPOSE_SSL_SERVER, /* purpose */
X509_TRUST_SSL_SERVER, /* trust */
-1, /* depth */
Expand Down
3 changes: 3 additions & 0 deletions crypto/x509/x_x509.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ static int x509_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
X509_CERT_AUX_free(ret->aux);
ASN1_OCTET_STRING_free(ret->skid);
AUTHORITY_KEYID_free(ret->akid);
sk_ASN1_OBJECT_pop_free(ret->ex_ekus, ASN1_OBJECT_free);
CRL_DIST_POINTS_free(ret->crldp);
ossl_policy_cache_free(ret->policy_cache);
GENERAL_NAMES_free(ret->altname);
Expand Down Expand Up @@ -76,6 +77,7 @@ static int x509_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
#endif
ret->distinguishing_id = NULL;
ret->aux = NULL;
ret->ex_ekus = NULL;
ret->crldp = NULL;
if (!CRYPTO_new_ex_data(CRYPTO_EX_INDEX_X509, ret, &ret->ex_data))
return 0;
Expand All @@ -86,6 +88,7 @@ static int x509_cb(int operation, ASN1_VALUE **pval, const ASN1_ITEM *it,
X509_CERT_AUX_free(ret->aux);
ASN1_OCTET_STRING_free(ret->skid);
AUTHORITY_KEYID_free(ret->akid);
sk_ASN1_OBJECT_pop_free(ret->ex_ekus, ASN1_OBJECT_free);
CRL_DIST_POINTS_free(ret->crldp);
ossl_policy_cache_free(ret->policy_cache);
GENERAL_NAMES_free(ret->altname);
Expand Down
6 changes: 6 additions & 0 deletions doc/build.info
Original file line number Diff line number Diff line change
Expand Up @@ -2327,6 +2327,10 @@ DEPEND[html/man3/SSL_CTX_set_default_passwd_cb.html]=man3/SSL_CTX_set_default_pa
GENERATE[html/man3/SSL_CTX_set_default_passwd_cb.html]=man3/SSL_CTX_set_default_passwd_cb.pod
DEPEND[man/man3/SSL_CTX_set_default_passwd_cb.3]=man3/SSL_CTX_set_default_passwd_cb.pod
GENERATE[man/man3/SSL_CTX_set_default_passwd_cb.3]=man3/SSL_CTX_set_default_passwd_cb.pod
DEPEND[html/man3/SSL_CTX_set_eku.html]=man3/SSL_CTX_set_eku.pod
GENERATE[html/man3/SSL_CTX_set_eku.html]=man3/SSL_CTX_set_eku.pod
DEPEND[man/man3/SSL_CTX_set_eku.3]=man3/SSL_CTX_set_eku.pod
GENERATE[man/man3/SSL_CTX_set_eku.3]=man3/SSL_CTX_set_eku.pod
DEPEND[html/man3/SSL_CTX_set_generate_session_id.html]=man3/SSL_CTX_set_generate_session_id.pod
GENERATE[html/man3/SSL_CTX_set_generate_session_id.html]=man3/SSL_CTX_set_generate_session_id.pod
DEPEND[man/man3/SSL_CTX_set_generate_session_id.3]=man3/SSL_CTX_set_generate_session_id.pod
Expand Down Expand Up @@ -3585,6 +3589,7 @@ html/man3/SSL_CTX_set_client_hello_cb.html \
html/man3/SSL_CTX_set_ct_validation_callback.html \
html/man3/SSL_CTX_set_ctlog_list_file.html \
html/man3/SSL_CTX_set_default_passwd_cb.html \
html/man3/SSL_CTX_set_eku.html \
html/man3/SSL_CTX_set_generate_session_id.html \
html/man3/SSL_CTX_set_info_callback.html \
html/man3/SSL_CTX_set_keylog_callback.html \
Expand Down Expand Up @@ -4248,6 +4253,7 @@ man/man3/SSL_CTX_set_client_hello_cb.3 \
man/man3/SSL_CTX_set_ct_validation_callback.3 \
man/man3/SSL_CTX_set_ctlog_list_file.3 \
man/man3/SSL_CTX_set_default_passwd_cb.3 \
man/man3/SSL_CTX_set_eku.3 \
man/man3/SSL_CTX_set_generate_session_id.3 \
man/man3/SSL_CTX_set_info_callback.3 \
man/man3/SSL_CTX_set_keylog_callback.3 \
Expand Down
9 changes: 5 additions & 4 deletions doc/man1/openssl-verification-options.pod
Original file line number Diff line number Diff line change
Expand Up @@ -151,12 +151,13 @@ The first step is to check that each certificate is well-formed.
Part of these checks are enabled only if the B<-x509_strict> option is given.

The second step is to check the extensions of every untrusted certificate
for consistency with the supplied purpose.
If the B<-purpose> option is not given then no such checks are done
except for SSL/TLS connection setup,
for consistency with the supplied purpose or Extended Key Usage (EKU),
where the latter overrides any purpose requirements for untrusted certificates.
If the B<-purpose> and B<-eku> options are not given
then no such checks are done except for SSL/TLS connection setup,
where by default C<sslserver> or C<sslclient>, are checked.
The target or "leaf" certificate, as well as any other untrusted certificates,
must have extensions compatible with the specified purpose.
must have extensions compatible with the given EKU or specified purpose.
All certificates except the target or "leaf" must also be valid CA certificates.
The precise extensions required are described in more detail in
L<openssl-x509(1)/CERTIFICATE EXTENSIONS>.
Expand Down
49 changes: 49 additions & 0 deletions doc/man3/SSL_CTX_set_eku.pod
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
=pod

=head1 NAME

SSL_CTX_set_eku,
SSL_set_eku
- set peer certificate usage requirements to be verified

=head1 SYNOPSIS

#include <openssl/ssl.h>

int SSL_CTX_set_eku(SSL_CTX *ctx, int nid);
int SSL_set_eku(SSL *ssl, int nid);

=head1 DESCRIPTION

SSL_CTX_set_eku() and SSL_CTX_set_eku() sets the Extended Key Usage (EKU)
in the verification parameters of I<ctx> or I<ssl>, respectively, to I<nid>.
This must be an NID corresponding to an EKU OID, for example B<NID_server_auth>,
which may have been obtained by parsing a text string using L<OBJ_txt2nid(3)>.
It determines the acceptable EKU for the peer certificate and its chain,
If provided, it overrides any purpose requirements for untrusted certificates.

=head1 RETURN VALUES

SSL_CTX_set_eku() and SSL_CTX_set_eku()
return 1 for success and 0 for failure.

=head1 SEE ALSO

L<OBJ_txt2nid(3)>,
L<SSL_CTX_set_verify(3)>

=head1 HISTORY

The SSL_CTX_set_eku() and SSL_CTX_set_eku()
functions were added in OpenSSL 3.0.

=head1 COPYRIGHT

Copyright 2000-2018 The OpenSSL Project Authors. All Rights Reserved.

Licensed under the Apache License 2.0 (the "License"). You may not use
this file except in compliance with the License. You can obtain a copy
in the file LICENSE in the source distribution or at
L<https://www.openssl.org/source/license.html>.

=cut
4 changes: 4 additions & 0 deletions doc/man3/X509_STORE_CTX_get_error.pod
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,10 @@ consistent with the supplied purpose.
No TLS records were configured to validate the raw public key, or DANE was not
enabled on the connection.

=item B<X509_V_ERR_INVALID_PURPOSE: unsupported Extended Key Usage>

The target certificate cannot be used for the specified Extended Key Usage (EKU).

=back

=head1 NOTES
Expand Down
Loading

0 comments on commit 4d2d083

Please sign in to comment.