[Sorry about that. The link that sent me there said "post to this thread", that didn't sound like "create a new topic".]
That CLI example works for me, is there a clean parallel approach in Python? I am loading root certificates into a trust store and then iterating backwards along the chain. Sounds like I want to do that with shorter versions of the chain first so I I will catch the newer trust anchor.
Summary: a certificate verifier shouldn't assume that the served chain is the one true path to one specific root. Instead, consider it as a pool of helpful but untrusted certificates, which may be informative in the certificate verifier's attempt to find a path to some root that it trusts.
I would also particularly advise against using openssl verify, since its arguments are very unintuitive and hard to get right, even for experts. I've gotten it wrong a bunch of times, and so has nearly every other expert I've seen on this forum. And even if you do get the arguments right, you still have a tool that is making the crucial incorrect assumption described in the blog post above.
My general advice would be to use the path building and verification from your TLS client library. Most TLS client libraries don't really expose that as a separate functionality, though: they just do it when negotiating a handshake.
Can you tell us more about what you're trying to do? That will help with suggesting the most appropriate solution.
I'm trying to programatically verify certificates that get automatically renewed aren't broken. I don't have any reason to expect they would be, but if I did start getting broken certificates, don't detect it and start throwing away perfectly good old certificates with a good chunk of life left in them—it would be really bad, lot of stuff would stop working, it would ruin my day, to say the least.
I think I have it figured out how to do it, next I just need to duplicate what I did in clean code, in the real environment, etc.
It is very improbable to get broken certificates, but it is entirely possible to get broken chains as - historically - many clients have hardcoded the chains/intermediates or do not properly handle the chain selection.
Below are links to two functions in a client I wrote. They are written to use pure Python but fallback to OpenSSL commandline if the requisite libraries are not available.
The trick in the implementations is that you need to do everything in a sandboxed environment. With Python (or libraries) that means using an empty trust store and iteratively building it up to contain each element. With OpenSSL command line, you emulate this by explicitly passing in the root and intermediate. If you fail to properly sandbox the checks, you run the risk of having the checks influenced by certificates in the system/library trust store.
ensure_chain - validates from a root down to a chain
I'm just going to second these: Basically all of the time if you have a problem, it isn't going to be that the CA signed something "wrong" in such a way that a client wouldn't accept it, but a problem with the configuration and installation of the certificate into your web server. Really you probably just want your script to install the certificate (in a test environment of some sort if you're too nervous about touching your production), and try connecting to it in a way that's as close as possible to how your actual clients are. And if that works, consider the test passed. (And if it fails, roll back the changes to the prior configuration and sound the alarm bells.) And that will test that you got a valid certificate, as well as test that your server configuration is working, and usually you want to ensure that both of those are true to avoid the ruining of your day that you're concerned about.