I am using CyaSSL V1.5.0. The server I am connecting to has a certificate signed by an intermediate certificate signed by a Verisign root CA, something like:
(A) Verisign Class 3 Public Primary Certification Authority - G2
(B) VeriSign Class 3 Secure Server CA - G2
Certificates (B) and (C) are in the certificate chain sent from the server to the client. CyaSSL currently expects to verify each of these with one of the CA certificates registered with CyaSSL. Consequently, the application must register both (A) and (B) as trusted CAs with CyaSSL.
That's not the correct operation. The certificate chain should be verified bottom-up. (C) should be verified by (B); (B), the top certificate in the chain sent from the server, should be verified against one of the root CAs, in this case (A).
This approach is very common. An example is www.amazon.com.
Thanks for the report.
Different SSL libraries take vastly different approaches to server certificate verification. Some don't fail out even if the signature fails to verify. Some allow you to add a verify depth limit (the max allowed length of the chain) which has the side effect of not failing even if the depth is greater than the limit you give. Some allow an unlimited chain as long as the root is trusted. Some, like nss and firefox, do what CyaSSL currently does which is to only trust signers that are explicitly named. Firefox has both A and B registered as trusted signers.
I'm not sure CyaSSL could verify the chain bottom-up as you suggest (it currently does bottom-up). A is a trusted cert. Then we are presented with C which is signed by B. At this point we have no reason to trust B, even implicitly, we don't even have it's key yet since we've started at the bottom. This makes verifying C difficult in a bottom-up approach.
Now CyaSSL could easily be modified to do this, in fact, we may add it as an option. So far, using explicitly named trusted signers works well in embedded environments since the endpoints are usually known so the server's chain is known ahead of time and is usually 1 or 2 certs in total. Even in the case of 3 as above, it's not much more work to add two trusted signers than it is to correctly add one.
One concern I have is how far to carry this out. From your example say example.com used it's private key to sign D)'s certificate widget.com which used it's key to sign E)'s certificate etc. We're starting to get pretty far from our "trusted" certificate. Is a correctly working verify depth the solution in this case? I'm not sure. Any thoughts?
Ignore "bottom up"; that got us on the wrong path. The point is that the only top certificate in the chain from the server should be verified against a trusted certificate. The other certificates should be verified by the one above it in the chain. The only other SSL library I have used takes this approach. This is also the approach that is most like the algorithm in RFC3280; currently, CyaSSL does NOT conform to that RFC. That is really my point. The approach to certificate verification in RFC3280 should have been the starting place for the CyaSSL implementation. If you want to verify every certificate against a configured trusted certificate, fine; but that's additional. CyaSSL could actually accept completely invalid certificate chains, because it does not verify the chain-it just verifies each certificate in the chain.
The certificate depth is a good point. RFC3280 introduced a basic constraints extension to specify the "maximum depth of valid certification paths". Unfortunately, legacy certificates do not include that extension and modern ones may not set it. The most flexible approach is to provide an application callback, invoked upon completed certificate chain verification, that can accept or reject the chain based upon depth (or any other field, for that matter).
In my Firefox installation, only (A) appears as a "built-in" certificate. I am not sure that Firefox does really know about (B) and, if so, whether it uses its built-in copy to check (C) (rather than the copy from the server).
This is not a big issue for my application which always connects to the same server (for now, at least). However, I am still going to modify the source to perform the chain verification as it should be done.