Check chain extensions also for trusted certificates
authorViktor Dukhovni <openssl-users@dukhovni.org>
Thu, 28 Jan 2016 08:01:45 +0000 (03:01 -0500)
committerViktor Dukhovni <openssl-users@dukhovni.org>
Mon, 1 Feb 2016 02:23:23 +0000 (21:23 -0500)
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>
15 files changed:
apps/opt.c
crypto/x509/x509_trs.c
crypto/x509/x509_vfy.c
crypto/x509/x509_vpm.c
doc/apps/verify.pod
doc/apps/x509.pod
doc/crypto/X509_VERIFY_PARAM_set_flags.pod
include/openssl/x509.h
test/certs/root+anyEKU.pem [new file with mode: 0644]
test/certs/root-anyEKU.pem [new file with mode: 0644]
test/certs/root2+clientAuth.pem [new file with mode: 0644]
test/certs/root2+serverAuth.pem [new file with mode: 0644]
test/certs/root2-serverAuth.pem [new file with mode: 0644]
test/certs/setup.sh
test/recipes/25-test_verify.t

index 14e05de..2fbc9fe 100644 (file)
@@ -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());
index 7392c55..c81c725 100644 (file)
@@ -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;
 }
@@ -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;
         }
         /*
index 39d37b9..14d6a8d 100644 (file)
@@ -363,17 +363,60 @@ static STACK_OF(X509) *lookup_certs_sk(X509_STORE_CTX *ctx, X509_NAME *nm)
 }
 
 /*
+ * 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:
@@ -402,8 +445,7 @@ 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)
@@ -411,17 +453,15 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
             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) {
@@ -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)
@@ -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))
@@ -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)
@@ -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);
 }
 
@@ -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);
@@ -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;
index 295ce88..41b0fde 100644 (file)
@@ -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;
      */
@@ -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 */
@@ -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 */
index e15a5de..6d54592 100644 (file)
@@ -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>
 
@@ -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>
 
index 1c98e9d..637eedc 100644 (file)
@@ -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>
index a2219d2..53a063a 100644 (file)
@@ -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()
index 31f784d..3a1c5e2 100644 (file)
@@ -183,7 +183,7 @@ DEFINE_STACK_OF(X509_TRUST)
 
 /* standard trust ids */
 
-# define X509_TRUST_DEFAULT      -1/* Only valid in purpose settings */
+# define X509_TRUST_DEFAULT      /* Only valid in purpose settings */
 
 # define X509_TRUST_COMPAT       1
 # define X509_TRUST_SSL_CLIENT   2
diff --git a/test/certs/root+anyEKU.pem b/test/certs/root+anyEKU.pem
new file mode 100644 (file)
index 0000000..97e0732
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN TRUSTED CERTIFICATE-----
+MIIC8TCCAdmgAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290
+IENBMCAXDTE2MDExNTA4MTk0OVoYDzIxMTYwMTE2MDgxOTQ5WjASMRAwDgYDVQQD
+DAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4eYA9Qa8
+oEY4eQ8/HnEZE20C3yubdmv8rLAh7daRCEI7pWM17FJboKJKxdYAlAOXWj25ZyjS
+feMhXKTtxjyNjoTRnVTDPdl0opZ2Z3H5xhpQd7P9eO5b4OOMiSPCmiLsPtQ3ngfN
+wCtVERc6NEIcaQ06GLDtFZRexv2eh8Yc55QaksBfBcFzQ+UD3gmRySTO2I6Lfi7g
+MUjRhipqVSZ66As2Tpex4KTJ2lxpSwOACFaDox+yKrjBTP7FsU3UwAGq7b7OJb3u
+aa32B81uK6GJVPVo65gJ7clgZsszYkoDsGjWDqtfwTVVfv1G7rrr3Laio+2Ff3ff
+tWgiQ35mJCOvxQIDAQABo1AwTjAdBgNVHQ4EFgQUjvUlrx6ba4Q9fICayVOcTXL3
+o1IwHwYDVR0jBBgwFoAUjvUlrx6ba4Q9fICayVOcTXL3o1IwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAyRRJx27WYOogPXZpPfAMt8ptapr/ugLWGLlw
+bzKySoyLpoV2/YNAvTAGB90iFq6x/ujjrK41/ES0p3v38/Qfuxo24gcZgc/oYLV2
+UqR+uGCx68p2OWLYctBsARtYWOEgPhHFb9aVxcOQKyZHtivDX0wLGX+nqZoHX9IY
+mc0sbpRBRMzxRsChbzD5re9kZ5NrgkjA6DJ7jYh2GitOM6oIU3Dd9+pk3bCEkFUg
+Ry9qN/k+AyeqH1Qcb5LU+MTmlw8bmyzmMOBZgdegtO4HshcBMO054KSB3WSfBPDO
+bEhZ0vm/lw63TGi88yIMtlkmcU2g0RKpeQI96G6QeqHyKF3p8DAIMAYGBFUdJQA=
+-----END TRUSTED CERTIFICATE-----
diff --git a/test/certs/root-anyEKU.pem b/test/certs/root-anyEKU.pem
new file mode 100644 (file)
index 0000000..712b1f5
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN TRUSTED CERTIFICATE-----
+MIIC8TCCAdmgAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290
+IENBMCAXDTE2MDExNTA4MTk0OVoYDzIxMTYwMTE2MDgxOTQ5WjASMRAwDgYDVQQD
+DAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4eYA9Qa8
+oEY4eQ8/HnEZE20C3yubdmv8rLAh7daRCEI7pWM17FJboKJKxdYAlAOXWj25ZyjS
+feMhXKTtxjyNjoTRnVTDPdl0opZ2Z3H5xhpQd7P9eO5b4OOMiSPCmiLsPtQ3ngfN
+wCtVERc6NEIcaQ06GLDtFZRexv2eh8Yc55QaksBfBcFzQ+UD3gmRySTO2I6Lfi7g
+MUjRhipqVSZ66As2Tpex4KTJ2lxpSwOACFaDox+yKrjBTP7FsU3UwAGq7b7OJb3u
+aa32B81uK6GJVPVo65gJ7clgZsszYkoDsGjWDqtfwTVVfv1G7rrr3Laio+2Ff3ff
+tWgiQ35mJCOvxQIDAQABo1AwTjAdBgNVHQ4EFgQUjvUlrx6ba4Q9fICayVOcTXL3
+o1IwHwYDVR0jBBgwFoAUjvUlrx6ba4Q9fICayVOcTXL3o1IwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEAyRRJx27WYOogPXZpPfAMt8ptapr/ugLWGLlw
+bzKySoyLpoV2/YNAvTAGB90iFq6x/ujjrK41/ES0p3v38/Qfuxo24gcZgc/oYLV2
+UqR+uGCx68p2OWLYctBsARtYWOEgPhHFb9aVxcOQKyZHtivDX0wLGX+nqZoHX9IY
+mc0sbpRBRMzxRsChbzD5re9kZ5NrgkjA6DJ7jYh2GitOM6oIU3Dd9+pk3bCEkFUg
+Ry9qN/k+AyeqH1Qcb5LU+MTmlw8bmyzmMOBZgdegtO4HshcBMO054KSB3WSfBPDO
+bEhZ0vm/lw63TGi88yIMtlkmcU2g0RKpeQI96G6QeqHyKF3p8DAIoAYGBFUdJQA=
+-----END TRUSTED CERTIFICATE-----
diff --git a/test/certs/root2+clientAuth.pem b/test/certs/root2+clientAuth.pem
new file mode 100644 (file)
index 0000000..41355b0
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN TRUSTED CERTIFICATE-----
+MIIC8TCCAdmgAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290
+IENBMCAXDTE2MDExNTA4MTk0OVoYDzIxMTYwMTE2MDgxOTQ5WjASMRAwDgYDVQQD
+DAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyB6dJAD5
+wbStQf4HE0EhldtDShNVQ/jhDu6s2Ka30FdP4ml1+c2Py7ODUSjSCegXaBIOXCA+
+R0zaBAJ3ZeqXx3UrE9PiXaHRGZcoPtX4mK9IOHhIdxwPUa6ceSOJn4cHY+p0cFLp
+/5bnUErp4IqbL1bMd4v8fFxJ0ZDGJahfLiurnYUyalaNCHK+hK2+RaeRgPlsXfiU
+/vwhhjFhdhixbPm8l+S+2xNySV1JAAzrUvEDdNZ0iBvuVcS2mlhSKTht5Zeg+0C6
+7kYYqxM9CVZCwcV/aSUImwjeFsNMJsl/nFyEacu6vXz0rjvLwPzTAeVYZy592Gwv
+akWOtiDdap7WJQIDAQABo1AwTjAdBgNVHQ4EFgQUnM5mQjCrHAgmX3MZbd8Pp65Y
+Uh4wHwYDVR0jBBgwFoAUnM5mQjCrHAgmX3MZbd8Pp65YUh4wDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEADkH6+rUX2QD5TMBn8x4PR9mTQsxhD2k8K2bv
+NpbsWX0ta2pDPhiBpIbrTrTmw656MMRkwMLYIAX7BFhyjO9gO0nVXfU1SSTDsso+
+qu/K1t2US/rLeJQn8gYiTw6AqmvxHOndLaZQrYef4rUzsYnahNzxcoS1FMVxoJFM
+o+1Wo0BFBlASv5Az0iFfjd1Uy3+AHB41+2vczNIWSki3mg4hzus2PSS4AA9IYeh+
+zU/HJMddnVedLKNstTAfR85ftACtsP6JhBqCBqC4mCVsN2ZlgucETbsOMyWYB4+y
+9b6JIYDA1wxNVBXwN+D4MyALxjmjwcTsL6pXgoVc0JEJWVqQ1zAMMAoGCCsGAQUF
+BwMC
+-----END TRUSTED CERTIFICATE-----
diff --git a/test/certs/root2+serverAuth.pem b/test/certs/root2+serverAuth.pem
new file mode 100644 (file)
index 0000000..52053f1
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN TRUSTED CERTIFICATE-----
+MIIC8TCCAdmgAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290
+IENBMCAXDTE2MDExNTA4MTk0OVoYDzIxMTYwMTE2MDgxOTQ5WjASMRAwDgYDVQQD
+DAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyB6dJAD5
+wbStQf4HE0EhldtDShNVQ/jhDu6s2Ka30FdP4ml1+c2Py7ODUSjSCegXaBIOXCA+
+R0zaBAJ3ZeqXx3UrE9PiXaHRGZcoPtX4mK9IOHhIdxwPUa6ceSOJn4cHY+p0cFLp
+/5bnUErp4IqbL1bMd4v8fFxJ0ZDGJahfLiurnYUyalaNCHK+hK2+RaeRgPlsXfiU
+/vwhhjFhdhixbPm8l+S+2xNySV1JAAzrUvEDdNZ0iBvuVcS2mlhSKTht5Zeg+0C6
+7kYYqxM9CVZCwcV/aSUImwjeFsNMJsl/nFyEacu6vXz0rjvLwPzTAeVYZy592Gwv
+akWOtiDdap7WJQIDAQABo1AwTjAdBgNVHQ4EFgQUnM5mQjCrHAgmX3MZbd8Pp65Y
+Uh4wHwYDVR0jBBgwFoAUnM5mQjCrHAgmX3MZbd8Pp65YUh4wDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEADkH6+rUX2QD5TMBn8x4PR9mTQsxhD2k8K2bv
+NpbsWX0ta2pDPhiBpIbrTrTmw656MMRkwMLYIAX7BFhyjO9gO0nVXfU1SSTDsso+
+qu/K1t2US/rLeJQn8gYiTw6AqmvxHOndLaZQrYef4rUzsYnahNzxcoS1FMVxoJFM
+o+1Wo0BFBlASv5Az0iFfjd1Uy3+AHB41+2vczNIWSki3mg4hzus2PSS4AA9IYeh+
+zU/HJMddnVedLKNstTAfR85ftACtsP6JhBqCBqC4mCVsN2ZlgucETbsOMyWYB4+y
+9b6JIYDA1wxNVBXwN+D4MyALxjmjwcTsL6pXgoVc0JEJWVqQ1zAMMAoGCCsGAQUF
+BwMB
+-----END TRUSTED CERTIFICATE-----
diff --git a/test/certs/root2-serverAuth.pem b/test/certs/root2-serverAuth.pem
new file mode 100644 (file)
index 0000000..dae848a
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN TRUSTED CERTIFICATE-----
+MIIC8TCCAdmgAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290
+IENBMCAXDTE2MDExNTA4MTk0OVoYDzIxMTYwMTE2MDgxOTQ5WjASMRAwDgYDVQQD
+DAdSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyB6dJAD5
+wbStQf4HE0EhldtDShNVQ/jhDu6s2Ka30FdP4ml1+c2Py7ODUSjSCegXaBIOXCA+
+R0zaBAJ3ZeqXx3UrE9PiXaHRGZcoPtX4mK9IOHhIdxwPUa6ceSOJn4cHY+p0cFLp
+/5bnUErp4IqbL1bMd4v8fFxJ0ZDGJahfLiurnYUyalaNCHK+hK2+RaeRgPlsXfiU
+/vwhhjFhdhixbPm8l+S+2xNySV1JAAzrUvEDdNZ0iBvuVcS2mlhSKTht5Zeg+0C6
+7kYYqxM9CVZCwcV/aSUImwjeFsNMJsl/nFyEacu6vXz0rjvLwPzTAeVYZy592Gwv
+akWOtiDdap7WJQIDAQABo1AwTjAdBgNVHQ4EFgQUnM5mQjCrHAgmX3MZbd8Pp65Y
+Uh4wHwYDVR0jBBgwFoAUnM5mQjCrHAgmX3MZbd8Pp65YUh4wDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQsFAAOCAQEADkH6+rUX2QD5TMBn8x4PR9mTQsxhD2k8K2bv
+NpbsWX0ta2pDPhiBpIbrTrTmw656MMRkwMLYIAX7BFhyjO9gO0nVXfU1SSTDsso+
+qu/K1t2US/rLeJQn8gYiTw6AqmvxHOndLaZQrYef4rUzsYnahNzxcoS1FMVxoJFM
+o+1Wo0BFBlASv5Az0iFfjd1Uy3+AHB41+2vczNIWSki3mg4hzus2PSS4AA9IYeh+
+zU/HJMddnVedLKNstTAfR85ftACtsP6JhBqCBqC4mCVsN2ZlgucETbsOMyWYB4+y
+9b6JIYDA1wxNVBXwN+D4MyALxjmjwcTsL6pXgoVc0JEJWVqQ1zAMoAoGCCsGAQUF
+BwMB
+-----END TRUSTED CERTIFICATE-----
index b50f7e3..795ff4a 100755 (executable)
@@ -2,7 +2,7 @@
 
 # Primary root: root-cert
 # root certs variants: CA:false, key2, DN2
-# trust variants: +serverAuth -serverAuth +clientAuth
+# trust variants: +serverAuth -serverAuth +clientAuth +anyEKU -anyEKU
 #
 ./mkcert.sh genroot "Root CA" root-key root-cert
 ./mkcert.sh genss "Root CA" root-key root-nonca
@@ -15,6 +15,16 @@ openssl x509 -in root-cert.pem -trustout \
     -addreject serverAuth -out root-serverAuth.pem
 openssl x509 -in root-cert.pem -trustout \
     -addtrust clientAuth -out root+clientAuth.pem
+openssl x509 -in root-cert.pem -trustout \
+    -addreject anyExtendedKeyUsage -out root-anyEKU.pem
+openssl x509 -in root-cert.pem -trustout \
+    -addtrust anyExtendedKeyUsage -out root+anyEKU.pem
+openssl x509 -in root-cert2.pem -trustout \
+    -addtrust serverAuth -out root2+serverAuth.pem
+openssl x509 -in root-cert2.pem -trustout \
+    -addreject serverAuth -out root2-serverAuth.pem
+openssl x509 -in root-cert2.pem -trustout \
+    -addtrust clientAuth -out root2+clientAuth.pem
 
 # Primary intermediate ca: ca-cert
 # ca variants: CA:false, key2, DN2, issuer2, expired
index 56d8307..93d993d 100644 (file)
@@ -9,80 +9,110 @@ use OpenSSL::Test qw/:DEFAULT top_file/;
 setup("test_verify");
 
 sub verify {
-    my ($cert, $vname, $trusted, $untrusted, @opts) = @_;
-    my @args = qw(openssl verify -verify_name);
+    my ($cert, $purpose, $trusted, $untrusted, @opts) = @_;
+    my @args = qw(openssl verify -purpose);
     my @path = qw(test certs);
-    push(@args, "$vname", @opts);
+    push(@args, "$purpose", @opts);
     for (@$trusted) { push(@args, "-trusted", top_file(@path, "$_.pem")) }
     for (@$untrusted) { push(@args, "-untrusted", top_file(@path, "$_.pem")) }
     push(@args, top_file(@path, "$cert.pem"));
     run(app([@args]));
 }
 
-plan tests => 29;
+plan tests => 38;
 
 # Canonical success
-ok(verify("ee-cert", "ssl_server", ["root-cert"], ["ca-cert"]),
+ok(verify("ee-cert", "sslserver", ["root-cert"], ["ca-cert"]),
    "verify valid chain");
 
 # Root CA variants
-ok(verify("ee-cert", "ssl_server", [qw(root-nonca)], [qw(ca-cert)]),
-   "Trusted certs not subject to CA:true checks");
-ok(!verify("ee-cert", "ssl_server", [qw(root-cert2)], [qw(ca-cert)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-nonca)], [qw(ca-cert)]),
+   "Trusted CA certs now subject to CA:true checks");
+ok(!verify("ee-cert", "sslserver", [qw(root-cert2)], [qw(ca-cert)]),
    "fail wrong root key");
-ok(!verify("ee-cert", "ssl_server", [qw(root-name2)], [qw(ca-cert)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-name2)], [qw(ca-cert)]),
    "fail wrong root DN");
-ok(verify("ee-cert", "ssl_server", [qw(root+serverAuth)], [qw(ca-cert)]),
+ok(verify("ee-cert", "sslserver", [qw(root+serverAuth)], [qw(ca-cert)]),
    "accept right EKU");
-ok(!verify("ee-cert", "ssl_server", [qw(root-serverAuth)], [qw(ca-cert)]),
+ok(verify("ee-cert", "sslserver", [qw(root+anyEKU)], [qw(ca-cert)]),
+   "accept anyEKU");
+ok(!verify("ee-cert", "sslserver", [qw(root-serverAuth)], [qw(ca-cert)]),
    "fail rejected EKU");
-ok(!verify("ee-cert", "ssl_server", [qw(root+clientAuth)], [qw(ca-cert)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-anyEKU)], [qw(ca-cert)]),
+   "fail rejected anyEKU");
+ok(!verify("ee-cert", "sslserver", [qw(root+clientAuth)], [qw(ca-cert)]),
    "fail wrong EKU");
 
+# Check that trusted-first is on by setting up paths to different roots
+# depending on whether the intermediate is the trusted or untrusted one.
+#
+ok(verify("ee-cert", "sslserver", [qw(root-serverAuth root-cert2 ca-root2)],
+          [qw(ca-cert)]),
+   "verify trusted-first path");
+ok(verify("ee-cert", "sslserver", [qw(root-cert root2+serverAuth ca-root2)],
+          [qw(ca-cert)]),
+   "verify trusted-first path right EKU");
+ok(!verify("ee-cert", "sslserver", [qw(root-cert root2-serverAuth ca-root2)],
+           [qw(ca-cert)]),
+   "fail trusted-first path rejected EKU");
+ok(!verify("ee-cert", "sslserver", [qw(root-cert root2+clientAuth ca-root2)],
+           [qw(ca-cert)]),
+   "fail trusted-first path wrong EKU");
+
 # CA variants
-ok(!verify("ee-cert", "ssl_server", [qw(root-cert)], [qw(ca-nonca)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-cert)], [qw(ca-nonca)]),
    "fail non-CA");
-ok(!verify("ee-cert", "ssl_server", [qw(root-cert)], [qw(ca-cert2)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-cert)], [qw(ca-cert2)]),
    "fail wrong CA key");
-ok(!verify("ee-cert", "ssl_server", [qw(root-cert)], [qw(ca-name2)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-cert)], [qw(ca-name2)]),
    "fail wrong CA DN");
-ok(!verify("ee-cert", "ssl_server", [qw(root-cert)], [qw(ca-root2)]),
+ok(!verify("ee-cert", "sslserver", [qw(root-cert)], [qw(ca-root2)]),
    "fail wrong CA issuer");
-ok(!verify("ee-cert", "ssl_server", [], [qw(ca-cert)], "-partial_chain"),
+ok(!verify("ee-cert", "sslserver", [], [qw(ca-cert)], "-partial_chain"),
    "fail untrusted partial");
-ok(!verify("ee-cert", "ssl_server", [], [qw(ca+serverAuth)], "-partial_chain"),
+ok(!verify("ee-cert", "sslserver", [], [qw(ca+serverAuth)], "-partial_chain"),
    "fail untrusted EKU partial");
-ok(verify("ee-cert", "ssl_server", [qw(ca+serverAuth)], [], "-partial_chain"),
+ok(verify("ee-cert", "sslserver", [qw(ca+serverAuth)], [], "-partial_chain"),
    "accept trusted EKU partial");
-ok(!verify("ee-cert", "ssl_server", [qw(ca-serverAuth)], [], "-partial_chain"),
+ok(!verify("ee-cert", "sslserver", [qw(ca-serverAuth)], [], "-partial_chain"),
    "fail rejected EKU partial");
-ok(!verify("ee-cert", "ssl_server", [qw(ca+clientAuth)], [], "-partial_chain"),
+ok(!verify("ee-cert", "sslserver", [qw(ca+clientAuth)], [], "-partial_chain"),
    "fail wrong EKU partial");
 
+# We now test auxiliary trust even for intermediate trusted certs without
+# -partial_chain.  Note that "-trusted_first" is now always on and cannot
+# be disabled.
+ok(verify("ee-cert", "sslserver", [qw(root-cert ca+serverAuth)], [qw(ca-cert)]),
+   "accept trusted EKU");
+ok(!verify("ee-cert", "sslserver", [qw(root-cert ca-serverAuth)], [qw(ca-cert)]),
+   "fail rejected EKU");
+ok(!verify("ee-cert", "sslserver", [qw(root-cert ca+clientAuth)], [qw(ca-cert)]),
+   "fail wrong EKU");
+
 # EE variants
-ok(verify("ee-client", "ssl_client", [qw(root-cert)], [qw(ca-cert)]),
+ok(verify("ee-client", "sslclient", [qw(root-cert)], [qw(ca-cert)]),
    "accept client cert");
-ok(!verify("ee-client", "ssl_server", [qw(root-cert)], [qw(ca-cert)]),
+ok(!verify("ee-client", "sslserver", [qw(root-cert)], [qw(ca-cert)]),
    "fail wrong leaf purpose");
-ok(!verify("ee-cert", "ssl_client", [qw(root-cert)], [qw(ca-cert)]),
+ok(!verify("ee-cert", "sslclient", [qw(root-cert)], [qw(ca-cert)]),
    "fail wrong leaf purpose");
-ok(!verify("ee-cert2", "ssl_server", [qw(root-cert)], [qw(ca-cert)]),
+ok(!verify("ee-cert2", "sslserver", [qw(root-cert)], [qw(ca-cert)]),
    "fail wrong CA key");
-ok(!verify("ee-name2", "ssl_server", [qw(root-cert)], [qw(ca-cert)]),
+ok(!verify("ee-name2", "sslserver", [qw(root-cert)], [qw(ca-cert)]),
    "fail wrong CA name");
-ok(!verify("ee-expired", "ssl_server", [qw(root-cert)], [qw(ca-cert)]),
+ok(!verify("ee-expired", "sslserver", [qw(root-cert)], [qw(ca-cert)]),
    "fail expired leaf");
-ok(verify("ee-cert", "ssl_server", [qw(ee-cert)], [], "-partial_chain"),
+ok(verify("ee-cert", "sslserver", [qw(ee-cert)], [], "-partial_chain"),
    "accept last-resort direct leaf match");
-ok(verify("ee-client", "ssl_client", [qw(ee-client)], [], "-partial_chain"),
+ok(verify("ee-client", "sslclient", [qw(ee-client)], [], "-partial_chain"),
    "accept last-resort direct leaf match");
-ok(!verify("ee-cert", "ssl_server", [qw(ee-client)], [], "-partial_chain"),
+ok(!verify("ee-cert", "sslserver", [qw(ee-client)], [], "-partial_chain"),
    "fail last-resort direct leaf non-match");
-ok(verify("ee-cert", "ssl_server", [qw(ee+serverAuth)], [], "-partial_chain"),
+ok(verify("ee-cert", "sslserver", [qw(ee+serverAuth)], [], "-partial_chain"),
    "accept direct match with trusted EKU");
-ok(!verify("ee-cert", "ssl_server", [qw(ee-serverAuth)], [], "-partial_chain"),
+ok(!verify("ee-cert", "sslserver", [qw(ee-serverAuth)], [], "-partial_chain"),
    "reject direct match with rejected EKU");
-ok(verify("ee-client", "ssl_client", [qw(ee+clientAuth)], [], "-partial_chain"),
+ok(verify("ee-client", "sslclient", [qw(ee+clientAuth)], [], "-partial_chain"),
    "accept direct match with trusted EKU");
-ok(!verify("ee-client", "ssl_client", [qw(ee-clientAuth)], [], "-partial_chain"),
+ok(!verify("ee-client", "sslclient", [qw(ee-clientAuth)], [], "-partial_chain"),
    "reject direct match with rejected EKU");