openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

classic Classic list List threaded Threaded
9 messages Options
Reply | Threaded
Open this post in threaded view
|

openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Peter Magnusson
Hi,
It is my understanding "openssl verify" should raise
X509_V_ERR_PATH_LENGTH_EXCEEDED should be raised if pathlen=0
intermediate issues a new CA, but that does not seem to occur when I
test with a couple pf openssl versions.

I am not sure due to limited understanding of the code, but I am
wonder if there isn't an off-by-one or out-of-order increment error in
x509_vfy.c in this check: (plen > (x->ex_pathlen + proxy_path_length +
1))). if plen=1 and x->ex_pathlen=0, the check would become 1>1
(false) while it was expected to raise an error?

openssl verify -verbose -CAfile root.pem -untrusted intermediate.pem evil.pem
evil.pem: OK

openssl x509 -text -in root.pem | egrep -a1 X509v3
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                A5:70:7B:56:F1:93:E9:CC:FD:15:EF:FA:64:67:41:99:6F:40:DA:C5
--
--
                A5:70:7B:56:F1:93:E9:CC:FD:15:EF:FA:64:67:41:99:6F:40:DA:C5
            X509v3 Authority Key Identifier:

keyid:A5:70:7B:56:F1:93:E9:CC:FD:15:EF:FA:64:67:41:99:6F:40:DA:C5
--
--
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
--
--
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1


openssl x509 -text -in intermediate.pem | egrep -a1 X509v3
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                B5:5A:8A:64:CE:A4:1E:51:6F:AB:E4:8E:E3:71:8D:EF:2D:42:A7:AD
--
--
                B5:5A:8A:64:CE:A4:1E:51:6F:AB:E4:8E:E3:71:8D:EF:2D:42:A7:AD
            X509v3 Authority Key Identifier:

keyid:A5:70:7B:56:F1:93:E9:CC:FD:15:EF:FA:64:67:41:99:6F:40:DA:C5
--
--
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
--
--
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0


openssl x509 -text -in evil.pem | egrep -a1 X509v3
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                81:3A:5A:BD:9E:6C:08:0F:C7:6A:31:A2:0D:0F:2A:02:62:BE:63:12
--
--
                81:3A:5A:BD:9E:6C:08:0F:C7:6A:31:A2:0D:0F:2A:02:62:BE:63:12
            X509v3 Authority Key Identifier:

keyid:B5:5A:8A:64:CE:A4:1E:51:6F:AB:E4:8E:E3:71:8D:EF:2D:42:A7:AD
--
--
            X509v3 Basic Constraints: critical
                CA:TRUE
--
--
                CA:TRUE
            X509v3 Key Usage:
                Certificate Sign, CRL Sign
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Viktor Dukhovni
On Wed, Oct 03, 2018 at 02:51:57PM +0200, Peter Magnusson wrote:

> $ openssl verify -verbose -CAfile root.pem -untrusted intermediate.pem evil.pem
> evil.pem: OK

This is expected to work when intermediate.pem has pathlen 0, because
you're verifying "evil.pem" as a *leaf* certificate, its CA:true
is irrelevant when it is the last (leaf) certificate in the chain.

An actually unexpected result would be:

    $ openssl verify -verbose -CAfile root.pem -untrusted intermediate.pem -untrusted evil.pem badee.pem
    badee.pem: OK

where badee.pem is signed by evil.pem.  The path length constraint
is not a constraint against issuing EE certs with CA:true, it only
constraints the number additional intermediate (non-self-issued)
CAs in a valid path.  In your example that number is zero.

   https://tools.ietf.org/html/rfc5280#section-4.2.1.9

   The pathLenConstraint field is meaningful only if the cA boolean is
   asserted and the key usage extension, if present, asserts the
   keyCertSign bit (Section 4.2.1.3).  In this case, it gives the
   maximum number of non-self-issued intermediate certificates that may
   follow this certificate in a valid certification path.

--
        Viktor.
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Peter Magnusson
The following test case attempts to validates evilserver.pem, issued
by evilca.pem.

evil*.pem is violating:
1/ pathlen=0 constraint of the compromised intermediate.pem (issuer of
evilserver.pem)
2/ pathlen=1 constraint of the non-compromised root-ca.pem (issuer of
intermediate.pem)

The particular example execution is from Mac (LibreSSL) but same
behaviour observed with e.g. homebrew as well.

https://github.com/blaufish/openssl-pathlen

openssl x509 -text -in root.pem | grep -a1 "X509v3 Basic"
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:1
openssl x509 -text -in intermediate.pem | grep -a1 "X509v3 Basic"
                Certificate Sign, CRL Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
openssl x509 -text -in evilca.pem | grep -a1 "X509v3 Basic"

            X509v3 Basic Constraints: critical
                CA:TRUE
openssl verify -verbose -CAfile root.pem -untrusted untrusted.pem evilserver.pem
evilserver.pem: OK


On Wed, Oct 3, 2018 at 4:51 PM Viktor Dukhovni
<[hidden email]> wrote:

>
> On Wed, Oct 03, 2018 at 02:51:57PM +0200, Peter Magnusson wrote:
>
> > $ openssl verify -verbose -CAfile root.pem -untrusted intermediate.pem evil.pem
> > evil.pem: OK
>
> This is expected to work when intermediate.pem has pathlen 0, because
> you're verifying "evil.pem" as a *leaf* certificate, its CA:true
> is irrelevant when it is the last (leaf) certificate in the chain.
>
> An actually unexpected result would be:
>
>     $ openssl verify -verbose -CAfile root.pem -untrusted intermediate.pem -untrusted evil.pem badee.pem
>     badee.pem: OK
>
> where badee.pem is signed by evil.pem.  The path length constraint
> is not a constraint against issuing EE certs with CA:true, it only
> constraints the number additional intermediate (non-self-issued)
> CAs in a valid path.  In your example that number is zero.
>
>    https://tools.ietf.org/html/rfc5280#section-4.2.1.9
>
>    The pathLenConstraint field is meaningful only if the cA boolean is
>    asserted and the key usage extension, if present, asserts the
>    keyCertSign bit (Section 4.2.1.3).  In this case, it gives the
>    maximum number of non-self-issued intermediate certificates that may
>    follow this certificate in a valid certification path.
>
> --
>         Viktor.
> --
> openssl-users mailing list
> To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Peter Magnusson
Is this expected?  (plen > (x->ex_pathlen + proxy_path_length + 1))
evaluates to false (constraint not violated) when checking constraint
0 against plen=1 (constraint violated as far as I can understand?).

Doesn't make much sense to me. Is there something I haven't understood
about how the constraint is supposed to work?

******* important variables *******
*** check_chain_extensions:523 i=0
*** check_chain_extensions:524 plen=0
*** check_chain_extensions:525 x->ex_pathlen=-1
******* if statement components *******
*** check_chain_extensions:527 i > 1=0
*** check_chain_extensions:528 !(x->ex_flags & EXFLAG_SI)=0
*** check_chain_extensions:529 (x->ex_pathlen != -1)=0
*** check_chain_extensions:530 (plen > (x->ex_pathlen +
proxy_path_length + 1))=0
******* important variables *******
*** check_chain_extensions:523 i=1
*** check_chain_extensions:524 plen=0
*** check_chain_extensions:525 x->ex_pathlen=-1
******* if statement components *******
*** check_chain_extensions:527 i > 1=0
*** check_chain_extensions:528 !(x->ex_flags & EXFLAG_SI)=1
*** check_chain_extensions:529 (x->ex_pathlen != -1)=0
*** check_chain_extensions:530 (plen > (x->ex_pathlen +
proxy_path_length + 1))=0
******* important variables *******
*** check_chain_extensions:523 i=2
*** check_chain_extensions:524 plen=1
*** check_chain_extensions:525 x->ex_pathlen=0
******* if statement components *******
*** check_chain_extensions:527 i > 1=1
*** check_chain_extensions:528 !(x->ex_flags & EXFLAG_SI)=1
*** check_chain_extensions:529 (x->ex_pathlen != -1)=1
*** check_chain_extensions:530 (plen > (x->ex_pathlen +
proxy_path_length + 1))=0
******* important variables *******
*** check_chain_extensions:523 i=3
*** check_chain_extensions:524 plen=2
*** check_chain_extensions:525 x->ex_pathlen=1
******* if statement components *******
*** check_chain_extensions:527 i > 1=1
*** check_chain_extensions:528 !(x->ex_flags & EXFLAG_SI)=0
*** check_chain_extensions:529 (x->ex_pathlen != -1)=1
*** check_chain_extensions:530 (plen > (x->ex_pathlen +
proxy_path_length + 1))=0
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Viktor Dukhovni
In reply to this post by Peter Magnusson
On Wed, Oct 03, 2018 at 07:16:51PM +0200, Peter Magnusson wrote:

> The following test case attempts to validates evilserver.pem, issued
> by evilca.pem.

More specifically, we see that in this test the leaf server certificate
has the same subject and issuer, so EXFLAG_SI is set for that
certificate, and it did not count in the path length:

    $ /usr/local/bin/openssl verify -show_chain -verbose -trusted root.pem -untrusted untrusted.pem evilserver.pem  
    evilserver.pem: OK
    Chain:
    depth=0: C = SE, ST = EvilServer, L = EvilServer, O = EvilServer, OU = EvilServer, CN = EvilServer (untrusted)
    depth=1: C = SE, ST = EvilServer, L = EvilServer, O = EvilServer, OU = EvilServer, CN = EvilServer (untrusted)
    depth=2: C = SE, ST = Intermediate, O = Intermediate, OU = Intermediate, CN = Intermediate (untrusted)
    depth=3: C = SE, ST = Root, L = Root, O = Root, OU = Root, CN = Root

but this corner-case is not correct, the concept of "self-issued"
only applies to CAs, so for the leaf to be skipped it would have
the be a self-issued CA.  Try the patch below:

--
        Viktor.

diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
index 3a60d412da..77ca325d54 100644
--- a/crypto/x509/x509_vfy.c
+++ b/crypto/x509/x509_vfy.c
@@ -445,6 +445,7 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
     int i, must_be_ca, plen = 0;
     X509 *x;
     int proxy_path_length = 0;
+    int is_ca;
     int purpose;
     int allow_proxy_certs;
     int num = sk_X509_num(ctx->chain);
@@ -484,7 +485,7 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
                                 X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED))
                 return 0;
         }
-        ret = X509_check_ca(x);
+        ret = is_ca = X509_check_ca(x);
         switch (must_be_ca) {
         case -1:
             if ((ctx->param->flags & X509_V_FLAG_X509_STRICT)
@@ -524,8 +525,8 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
             if (!verify_cb_cert(ctx, x, i, X509_V_ERR_PATH_LENGTH_EXCEEDED))
                 return 0;
         }
-        /* Increment path length if not self issued */
-        if (!(x->ex_flags & EXFLAG_SI))
+        /* Increment path length if not a self issued CA */
+        if (!(is_ca && x->ex_flags & EXFLAG_SI))
             plen++;
         /*
          * If this certificate is a proxy certificate, the next certificate
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Peter Magnusson
Your patch does seem to resolve the test case.

II have maximised confusion by generating a CSR with the same textual
information for EvilCA as EvilServer.

I don't think the chain includes any self signed certificates except the root;
73:40:2A:49:4B:AA:69:06:CF:45:F3:24:A6:B6:76:6A:10:97:74:D6 (root, self issued)
DC:99:4E:EE:8A:5C:75:D3:C7:5E:03:1E:73:57:F2:C4:C5:89:FD:70 issued by
73:40:2A:49:4B:AA:69:06:CF:45:F3:24:A6:B6:76:6A:10:97:74:D6.
17:49:AA:01:F6:25:85:23:3F:A6:7A:43:D3:97:2A:F8:74:27:89:A0 issued by
DC:99:4E:EE:8A:5C:75:D3:C7:5E:03:1E:73:57:F2:C4:C5:89:FD:70.
1F:95:2F:26:9D:E1:37:BD:1F:9C:B5:51:FC:28:9C:EA:9F:1E:C8:B6 issued by
17:49:AA:01:F6:25:85:23:3F:A6:7A:43:D3:97:2A:F8:74:27:89:A0.

Modulus of evilca.pem begins with 00:cd:ba:9f and modulus of
evilserver.pem begins with 00:af:83:6f, so they are different even if
both have  Subject: C=SE, ST=EvilServer, L=EvilServer, O=EvilServer,
OU=EvilServer, CN=EvilServer.

Funnily enough I don't trigger the edge case on regenerated files with
correct Subject information.

openssl x509 -text -in root.pem | egrep -a1 "X509v3 .* Key Identifier"
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                73:40:2A:49:4B:AA:69:06:CF:45:F3:24:A6:B6:76:6A:10:97:74:D6
--
--
                73:40:2A:49:4B:AA:69:06:CF:45:F3:24:A6:B6:76:6A:10:97:74:D6
            X509v3 Authority Key Identifier:

keyid:73:40:2A:49:4B:AA:69:06:CF:45:F3:24:A6:B6:76:6A:10:97:74:D6
openssl x509 -text -in intermediate.pem | egrep -a1 "X509v3 .* Key Identifier"
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                DC:99:4E:EE:8A:5C:75:D3:C7:5E:03:1E:73:57:F2:C4:C5:89:FD:70
--
--
                DC:99:4E:EE:8A:5C:75:D3:C7:5E:03:1E:73:57:F2:C4:C5:89:FD:70
            X509v3 Authority Key Identifier:

keyid:73:40:2A:49:4B:AA:69:06:CF:45:F3:24:A6:B6:76:6A:10:97:74:D6
openssl x509 -text -in evilca.pem | grep -a1 "X509v3 .* Key Identifier"
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                17:49:AA:01:F6:25:85:23:3F:A6:7A:43:D3:97:2A:F8:74:27:89:A0
--
--
                17:49:AA:01:F6:25:85:23:3F:A6:7A:43:D3:97:2A:F8:74:27:89:A0
            X509v3 Authority Key Identifier:

keyid:DC:99:4E:EE:8A:5C:75:D3:C7:5E:03:1E:73:57:F2:C4:C5:89:FD:70
openssl x509 -text -in evilserver.pem | egrep -a1 "X509v3 .* Key Identifier"
                TLS Web Server Authentication
            X509v3 Subject Key Identifier:
                1F:95:2F:26:9D:E1:37:BD:1F:9C:B5:51:FC:28:9C:EA:9F:1E:C8:B6
--
--
                1F:95:2F:26:9D:E1:37:BD:1F:9C:B5:51:FC:28:9C:EA:9F:1E:C8:B6
            X509v3 Authority Key Identifier:

keyid:17:49:AA:01:F6:25:85:23:3F:A6:7A:43:D3:97:2A:F8:74:27:89:A0
On Thu, Oct 4, 2018 at 12:26 PM Viktor Dukhovni
<[hidden email]> wrote:

>
> On Wed, Oct 03, 2018 at 07:16:51PM +0200, Peter Magnusson wrote:
>
> > The following test case attempts to validates evilserver.pem, issued
> > by evilca.pem.
>
> More specifically, we see that in this test the leaf server certificate
> has the same subject and issuer, so EXFLAG_SI is set for that
> certificate, and it did not count in the path length:
>
>     $ /usr/local/bin/openssl verify -show_chain -verbose -trusted root.pem -untrusted untrusted.pem evilserver.pem
>     evilserver.pem: OK
>     Chain:
>     depth=0: C = SE, ST = EvilServer, L = EvilServer, O = EvilServer, OU = EvilServer, CN = EvilServer (untrusted)
>     depth=1: C = SE, ST = EvilServer, L = EvilServer, O = EvilServer, OU = EvilServer, CN = EvilServer (untrusted)
>     depth=2: C = SE, ST = Intermediate, O = Intermediate, OU = Intermediate, CN = Intermediate (untrusted)
>     depth=3: C = SE, ST = Root, L = Root, O = Root, OU = Root, CN = Root
>
> but this corner-case is not correct, the concept of "self-issued"
> only applies to CAs, so for the leaf to be skipped it would have
> the be a self-issued CA.  Try the patch below:
>
> --
>         Viktor.
>
> diff --git a/crypto/x509/x509_vfy.c b/crypto/x509/x509_vfy.c
> index 3a60d412da..77ca325d54 100644
> --- a/crypto/x509/x509_vfy.c
> +++ b/crypto/x509/x509_vfy.c
> @@ -445,6 +445,7 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
>      int i, must_be_ca, plen = 0;
>      X509 *x;
>      int proxy_path_length = 0;
> +    int is_ca;
>      int purpose;
>      int allow_proxy_certs;
>      int num = sk_X509_num(ctx->chain);
> @@ -484,7 +485,7 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
>                                  X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED))
>                  return 0;
>          }
> -        ret = X509_check_ca(x);
> +        ret = is_ca = X509_check_ca(x);
>          switch (must_be_ca) {
>          case -1:
>              if ((ctx->param->flags & X509_V_FLAG_X509_STRICT)
> @@ -524,8 +525,8 @@ static int check_chain_extensions(X509_STORE_CTX *ctx)
>              if (!verify_cb_cert(ctx, x, i, X509_V_ERR_PATH_LENGTH_EXCEEDED))
>                  return 0;
>          }
> -        /* Increment path length if not self issued */
> -        if (!(x->ex_flags & EXFLAG_SI))
> +        /* Increment path length if not a self issued CA */
> +        if (!(is_ca && x->ex_flags & EXFLAG_SI))
>              plen++;
>          /*
>           * If this certificate is a proxy certificate, the next certificate
> --
> openssl-users mailing list
> To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Viktor Dukhovni
On Thu, Oct 04, 2018 at 02:07:55PM +0200, Peter Magnusson wrote:

> Modulus of evilca.pem begins with 00:cd:ba:9f and modulus of
> evilserver.pem begins with 00:af:83:6f, so they are different even if
> both have  Subject: C=SE, ST=EvilServer, L=EvilServer, O=EvilServer,
> OU=EvilServer, CN=EvilServer.

That's the difference between self-signed and self-issued.  The
root CA is self-signed.  Your previous EE cert would have been
self-issued, had it been a CA.  But it had CA:FALSE, which makes
it not self-issued per RFC5280, as that classification applies only
to CAs.

> Funnily enough I don't trigger the edge case on regenerated files with
> correct Subject information.

That's not "funnily enough", that's expected, if my analysis of the
problem is correct, i.e. the problem is that the existing code
treats even non-CA leaf certs as self-issued provided the subject
and issuer match.  This throws the path length constraint checks off
by 1 in just the case of "self-issued but for the CA bit" EE certs.

The proposed patch is intended to resolve that issue.  If my analysis
is correct (please test any more interesting combinations you can
come up with), then the patch should be merged into the existing
OpenSSL supported releases and perhaps also related OpenSSL forks
(either or both of LibreSSL or BoringSSL that have not changed the
code in question).

--
        Viktor.
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Viktor Dukhovni
In reply to this post by Viktor Dukhovni


> On Oct 4, 2018, at 6:25 AM, Viktor Dukhovni <[hidden email]> wrote:
>
> but this corner-case is not correct, the concept of "self-issued"
> only applies to CAs, so for the leaf to be skipped it would have
> the be a self-issued CA.  Try the patch below:

I've simplified the patch in https://github.com/openssl/openssl/pull/7353
please take a look.

--
        Viktor.

--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
Reply | Threaded
Open this post in threaded view
|

Re: openssl verify accepting CA certs issued by intermediate with CA:TRUE, pathlen:0

Peter Magnusson
Thanks, I provided some input regarding off by one calculation of plen
still present in the patch.

You are very much correct on the definition of self-issued; rfc5280,
"A certificate is self-issued if the same DN appears in the subject
and issuer fields (the two DNs are the same if they match according to
the rules specified in Section 7.1)." Effectively path length
constraint is useless for limiting impact of a temporary CA breach, as
attacker can just issue an intermediate authority with a DN that
matches the definition of self-issued. The feature simply doesn't
provide the functionality I presumed it was it core purpose of
providing. It was very lucky for me I messed up my DN's so I could
learn that, thank you *very* much for your input, that was very useful
to be aware of!

Best Regards
//P
On Fri, Oct 5, 2018 at 7:10 AM Viktor Dukhovni
<[hidden email]> wrote:

>
>
>
> > On Oct 4, 2018, at 6:25 AM, Viktor Dukhovni <[hidden email]> wrote:
> >
> > but this corner-case is not correct, the concept of "self-issued"
> > only applies to CAs, so for the leaf to be skipped it would have
> > the be a self-issued CA.  Try the patch below:
>
> I've simplified the patch in https://github.com/openssl/openssl/pull/7353
> please take a look.
>
> --
>         Viktor.
>
> --
> openssl-users mailing list
> To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users
--
openssl-users mailing list
To unsubscribe: https://mta.openssl.org/mailman/listinfo/openssl-users