]> git.ipfire.org Git - thirdparty/openssl.git/commitdiff
x509_vfy.c: Restore rejection of expired trusted (root) certificate
authorDr. David von Oheimb <David.von.Oheimb@siemens.com>
Tue, 1 Dec 2020 13:22:16 +0000 (14:22 +0100)
committerDr. David von Oheimb <David.von.Oheimb@siemens.com>
Thu, 3 Dec 2020 13:06:49 +0000 (14:06 +0100)
The certificate path validation procedure specified in RFC 5280 does not
include checking the validity period of the trusted (root) certificate.
Still it is common good practice to perform this check.
Also OpenSSL did this until commit 0e7b1383e, which accidentally killed it.

The current commit restores the previous behavior.
It also removes the cause of that bug, namely counter-intuitive design
of the internal function check_issued(), which was complicated by checks
that actually belong to some other internal function, namely find_issuer().

Moreover, this commit adds a regression check and proper documentation of
the root cert validity period check feature, which had been missing so far.

Fixes #13427

Reviewed-by: Tomas Mraz <tmraz@fedoraproject.org>
(Merged from https://github.com/openssl/openssl/pull/13590)

crypto/x509/x509_cmp.c
crypto/x509/x509_vfy.c
doc/man1/openssl.pod
doc/man3/X509_STORE_set_verify_cb_func.pod
test/certs/root-expired.pem [new file with mode: 0644]
test/certs/setup.sh
test/recipes/25-test_verify.t

index da451eccce87fb3fbd43d7ed75d0d44e51132d7d..9c968b49b0207dd441f7d863df8f08ae1f6e9cc0 100644 (file)
@@ -142,6 +142,8 @@ int X509_cmp(const X509 *a, const X509 *b)
 {
     int rv;
 
+    if (a == b) /* for efficiency */
+        return 0;
     /* ensure hash is valid */
     if (X509_check_purpose((X509 *)a, -1, 0) != 1)
         return -2;
index 2da11ecf9d977c975b20f2898059a2cfa7739e2d..fc470d950ee257e2e2fc24b3b223bcbe2f20a101 100644 (file)
@@ -303,8 +303,20 @@ int X509_verify_cert(X509_STORE_CTX *ctx)
     return ret;
 }
 
+static int sk_X509_contains(STACK_OF(X509) *sk, X509 *cert)
+{
+    int i, n = sk_X509_num(sk);
+
+    for (i = 0; i < n; i++)
+        if (X509_cmp(sk_X509_value(sk, i), cert) == 0)
+            return 1;
+    return 0;
+}
+
 /*
- * Given a STACK_OF(X509) find the issuer of cert (if any)
+ * Find in given STACK_OF(X509) sk a non-expired issuer cert (if any) of given cert x.
+ * The issuer must not be the same as x and must not yet be in ctx->chain, where the
+ * exceptional case x is self-issued and ctx->chain has just one element is allowed.
  */
 static X509 *find_issuer(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *x)
 {
@@ -317,7 +329,9 @@ static X509 *find_issuer(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *x)
          * Below check 'issuer != x' is an optimization and safety precaution:
          * Candidate issuer cert cannot be the same as the subject cert 'x'.
          */
-        if (issuer != x && ctx->check_issued(ctx, x, issuer)) {
+        if (issuer != x && ctx->check_issued(ctx, x, issuer)
+            && (((x->ex_flags & EXFLAG_SI) != 0 && sk_X509_num(ctx->chain) == 1)
+                || !sk_X509_contains(ctx->chain, issuer))) {
             rv = issuer;
             if (x509_check_cert_time(ctx, rv, -1))
                 break;
@@ -326,26 +340,10 @@ static X509 *find_issuer(X509_STORE_CTX *ctx, STACK_OF(X509) *sk, X509 *x)
     return rv;
 }
 
-/*
- * Check that the given certificate 'x' is issued by the certificate 'issuer'
- * and the issuer is not yet in ctx->chain, where the exceptional case
- * that 'x' is self-issued and ctx->chain has just one element is allowed.
- */
-static int check_issued(X509_STORE_CTX *ctx, X509 *x, X509 *issuer)
+/* Check that the given certificate 'x' is issued by the certificate 'issuer' */
+static int check_issued(ossl_unused X509_STORE_CTX *ctx, X509 *x, X509 *issuer)
 {
-    if (x509_likely_issued(issuer, x) != X509_V_OK)
-        return 0;
-    if ((x->ex_flags & EXFLAG_SI) == 0 || sk_X509_num(ctx->chain) != 1) {
-        int i;
-        X509 *ch;
-
-        for (i = 0; i < sk_X509_num(ctx->chain); i++) {
-            ch = sk_X509_value(ctx->chain, i);
-            if (ch == issuer || X509_cmp(ch, issuer) == 0)
-                return 0;
-        }
-    }
-    return 1;
+    return x509_likely_issued(issuer, x) == X509_V_OK;
 }
 
 /* Alternative lookup method: look from a STACK stored in other_ctx */
@@ -1827,7 +1825,7 @@ static int internal_verify(X509_STORE_CTX *ctx)
             }
         }
 
- check_cert_time:
+    check_cert_time: /* in addition to RFC 5280, do also for trusted (root) cert */
         /* Calls verify callback as needed */
         if (!x509_check_cert_time(ctx, xs, n))
             return 0;
index 0bb37d415d6d199c9bb5188a06e96ddd5e77baa6..ace96274f9154ae791fa9375ed98e52388477408 100644 (file)
@@ -847,11 +847,12 @@ For compatibility with previous versions of OpenSSL,
 a certificate with no trust settings is considered to be valid for all purposes.
 
 The fourth, and final, step is to check the validity of the certificate chain.
-The validity period is checked against the system time
-and the C<notBefore> and C<notAfter> dates in each certificate.
-The B<-attime> flag may be used to specify a time other than "now."
-The certificate signatures are also checked at this point
-(except for the signature of the self-signed "root CA" certificate,
+For each element in the chain, including the root CA certificate,
+the validity period as specified by the C<notBefore> and C<notAfter> fields
+is checked against the current system time.
+The B<-attime> flag may be used to use a reference time other than "now."
+The certificate signature is checked as well
+(except for the signature of the typically self-signed root CA certificate,
 which is verified only if the B<-check_ss_sig> option is given).
 When verifying a certificate signature
 the keyUsage extension (if present) of the candidate issuer certificate
index 84b216ffbe75918c615a98474473efa4316fd0dc..515a427aa3295c242d18864276ec380464aa37ed 100644 (file)
@@ -145,9 +145,7 @@ I<If no function to get the issuer is provided, the internal default
 function will be used instead.>
 
 X509_STORE_set_check_issued() sets the function to check that a given
-certificate B<x> is issued by the issuer certificate B<issuer> and
-the issuer is not yet in the chain contained in <ctx>, where the exceptional
-case that B<x> is self-issued and ctx->chain has just one element is allowed.
+certificate B<x> is issued by the issuer certificate B<issuer>.
 This function must return 0 on failure (among others if B<x> hasn't
 been issued with B<issuer>) and 1 on success.
 I<If no function to get the issuer is provided, the internal default
diff --git a/test/certs/root-expired.pem b/test/certs/root-expired.pem
new file mode 100644 (file)
index 0000000..8204fb3
--- /dev/null
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC8jCCAdqgAwIBAgIBATANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdSb290
+IENBMB4XDTIwMTIwMjExNTQ0MVoXDTIwMTIwMTExNTQ0MVowEjEQMA4GA1UEAwwH
+Um9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOHmAPUGvKBG
+OHkPPx5xGRNtAt8rm3Zr/KywIe3WkQhCO6VjNexSW6CiSsXWAJQDl1o9uWco0n3j
+IVyk7cY8jY6E0Z1Uwz3ZdKKWdmdx+cYaUHez/XjuW+DjjIkjwpoi7D7UN54HzcAr
+VREXOjRCHGkNOhiw7RWUXsb9nofGHOeUGpLAXwXBc0PlA94JkckkztiOi34u4DFI
+0YYqalUmeugLNk6XseCkydpcaUsDgAhWg6Mfsiq4wUz+xbFN1MABqu2+ziW97mmt
+9gfNbiuhiVT1aOuYCe3JYGbLM2JKA7Bo1g6rX8E1VX79Ru6669y2oqPthX9337Vo
+IkN+ZiQjr8UCAwEAAaNTMFEwHQYDVR0OBBYEFI71Ja8em2uEPXyAmslTnE1y96NS
+MB8GA1UdIwQYMBaAFI71Ja8em2uEPXyAmslTnE1y96NSMA8GA1UdEwEB/wQFMAMB
+Af8wDQYJKoZIhvcNAQELBQADggEBAIIJIaT7B8PVjb9SrcjS2M5NfgjOftvrPrxf
+KvWs+6m0+2RdHGAHScrIWZsCGSkmuLE96hKqfM33aQLu3gFJmwdO+HcKlEw6Dg0e
+Br0fROcBIqjK5aS2ZQjqUyZR1CQ5F3Arlcd4RIrzsBPwBu7sO5pcEzc2c8A0DDkm
+zenRZ/SpOJAmghk8ek25gJewCsRk2TR8Ln+Qym41FZJlhQb6gxHZX0U7aRasANdQ
+MNSNgQ7HS4pSmticPg+tuKyOO+B9HHJeKRbWe6JLRYz7UyUrmWoMOrfmZFbZ66Xo
+eflbkjIhEAZ/lqR2Wd3TezilUG8QVZN77Y2oQbR1QyoaWeHRkco=
+-----END CERTIFICATE-----
index dbc2fc71be4232d849239abf25a09d42d912d167..d7cdf06e7f37e027b01f74bc02d05aa9866ba7e1 100755 (executable)
@@ -81,6 +81,7 @@ openssl x509 -in sroot-cert.pem -trustout \
 # trust variants: +serverAuth, -serverAuth, +clientAuth, -clientAuth, -anyEKU, +anyEKU
 #
 ./mkcert.sh genca "CA" ca-key ca-cert root-key root-cert
+DAYS=-1 ./mkcert.sh genroot "Root CA" root-key root-expired
 ./mkcert.sh genee "CA" ca-key ca-nonca root-key root-cert
 ./mkcert.sh gen_nonbc_ca "CA" ca-key ca-nonbc root-key root-cert
 ./mkcert.sh genca "CA" ca-key2 ca-cert2 root-key root-cert
index 9bbabd0fa3c1f637fb42c0841f811cf8de28afac..5293530b228d6f4b6c51a02cfeefa5fa3e487914 100644 (file)
@@ -27,7 +27,7 @@ sub verify {
     run(app([@args]));
 }
 
-plan tests => 151;
+plan tests => 153;
 
 # Canonical success
 ok(verify("ee-cert", "sslserver", ["root-cert"], ["ca-cert"]),
@@ -141,6 +141,10 @@ ok(!verify("ee-cert", "sslserver", [], [qw(ca-cert)], "-partial_chain"),
    "fail untrusted partial chain");
 ok(verify("ee-cert", "sslserver", [qw(ca-cert)], [], "-partial_chain"),
    "accept trusted partial chain");
+ok(!verify("ee-cert", "sslserver", [qw(ca-expired)], [], "-partial_chain"),
+   "reject expired trusted partial chain"); # this check is beyond RFC 5280
+ok(!verify("ee-cert", "sslserver", [qw(root-expired)], [qw(ca-cert)]),
+   "reject expired trusted root"); # this check is beyond RFC 5280
 ok(verify("ee-cert", "sslserver", [qw(sca-cert)], [], "-partial_chain"),
    "accept partial chain with server purpose");
 ok(!verify("ee-cert", "sslserver", [qw(cca-cert)], [], "-partial_chain"),