Name the certificate verification failures in ssl OSErrors - #11183
Open
dhalbert wants to merge 1 commit into
Open
Name the certificate verification failures in ssl OSErrors#11183dhalbert wants to merge 1 commit into
dhalbert wants to merge 1 commit into
Conversation
A failed handshake reported only MBEDTLS_ERR_X509_CERT_VERIFY_FAILED (-9984), discarding the MBEDTLS_X509_BADCERT_* bitmask that records which check actually failed. Read mbedtls_ssl_get_verify_result() in do_handshake() before mbedtls_ssl_free() frees the session it lives in, and name each set bit with mbedtls_x509_crt_verify_info(): OSError: (-9984, 'MBEDTLS_ERR_X509_CERT_VERIFY_FAILED; The certificate Common Name (CN) does not match with the expected CN') Several bits are commonly set at once, since mbedtls ors together the flags of every certificate in the chain, so ask for one bit at a time and append each name to a vstr. verify_info() writes all of its output or none of it, so requesting the whole mask at once would drop the entire list when it did not fit. All ones is mbedtls's "nothing to report" sentinel: it poisons the flags when a verify callback fails, and mbedtls_ssl_get_verify_result() returns 0xFFFFFFFF with no session. Screen for it, and only ask for the flags on MBEDTLS_ERR_X509_CERT_VERIFY_FAILED, so those cases report the error name alone rather than the entire table. Turn on CONFIG_MBEDTLS_ERROR_STRINGS for espressif so it gets error names at all; the other mbedtls ports already set MBEDTLS_ERROR_C. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Collaborator
Author
|
An observation about using Claude for this: I inadvertently set the level for Opus 5 to "medium" instead of "high", and found I had to do a lot more coaching. It was writing code that would work, but it wasn't that clever, and I had to set it back on track several times. For instance, I had to tell it to use |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Authored by Claude Code with guidance by @dhalbert. This message edited by @dhalbert for clarity.
Every certificate verification failure used to be reported as the same bare error number, so a hostname mismatch was indistinguishable from an untrusted CA or a bad signature algorithm.
mbedtls_ssl_get_verify_result()was never called, so theMBEDTLS_X509_BADCERT_*bitmask that records which check failed was thrown away.Now
do_handshake()reads that bitmask beforembedtls_ssl_free()frees the session it lives in, andmbedtls_raise_error_flags()gets an error message for each set bit usingmbedtls_x509_crt_verify_info(). For example:CONFIG_MBEDTLS_ERROR_STRINGSis now turned on for espressif to enableMBEDTLS_ERROR_Cso error names are available; the other mbedtls ports already definedMBEDTLS_ERROR_C.Notes on the implementation
mbedtls_x509_crt_verify_info()once for each flag bit and append each string to the error message so we don't have to worry about sizing a buffer large enough for long output(uint32_t) -1when a verify callback fails, andmbedtls_ssl_get_verify_result()returns0xFFFFFFFFwhen there is no session at all. So ignore that flag word and only look at flags on VERIFY_FAILED.vstr. In the unlikely eventvstrraisesMemoryErrordue to heap exhaustion, the message construction is caught by a wrappednlr_push()/nlr_pop()and falls back to a bareOSError(err).Limits on which errors will appear
MBEDTLS_ERR_X509_FATAL_ERRORand sets the flags to all ones (x509_crt.c, "prevent misuse of the vrfy callback"). So anything reducing toNOT_TRUSTEDreports only the error name. Failures encountered outside that callback — CN mismatch, unacceptable hash/PK/key — are named. Passing a CA withload_verify_locations()installs no callback, and there the full flags survive.BADCERT_EXPIRED,BADCERT_FUTUREand the two CRL date bits can never be set on any CircuitPython build, becauseMBEDTLS_HAVE_TIME_DATEis deliberately off (lib/mbedtls_config/mbedtls_config.h), CircuitPython does not know the wall clock time unless it is set.Testing
Built and run on hardware: Metro ESP32-S3 (esp-idf mbedtls) and Pico 2 W (
lib/mbedtls). Both ports build clean.Default context, against badssl.com:
With an unrelated CA loaded via
load_verify_locations(), which exercises the no-callback path and the multi-flag case: