tlslite-ng icon indicating copy to clipboard operation
tlslite-ng copied to clipboard

Implement certificate signature checking

Open tomato42 opened this issue 8 years ago • 5 comments

Allow for loading of a set of CA certificates (or support for the OpenSSL CApath) and verification if the signatures on the certificates were made by the CAs.

tomato42 avatar Jul 28 '16 08:07 tomato42

I have done this using a fork of M2Crypto which adds x509_verify_cert and related bindings for standalone certificate chain validation.

import certifi
from M2Crypto import m2, X509 as M2CryptoX509
from tlslite.x509certchain import X509CertChain

def verifyServerCertChain(serverName: str, serverCertChain: X509CertChain) -> bool:
    """Returns True if the given serverCertChain is trusted, False otherwise"""
    untrustedChain = M2CryptoX509.X509_Stack()
    for cert in serverCertChain.x509List:
        untrustedChain.push(M2CryptoX509.load_cert_der_string(bytes(cert.writeBytes())))
    target = untrustedChain[0]
    # load the trusted CA roots
    trustedCAStore = M2CryptoX509.X509_Store()
    trustedCAStore.load_info(certifi.where())
    # verify hostname
    if m2.X509_check_host(target._ptr(), serverName, len(serverName), 0, None) <= 0:
        return False
    # validate certificate chain against the trusted CA roots
    ctx = m2.X509_STORE_CTX_new()
    if not ctx:
        raise RuntimeError('could not create X509_STORE_CTX_new')
    result = m2.X509_STORE_CTX_init(
        ctx,                   # X509_STORE_CTX *ctx
        trustedCAStore._ptr(), # X509_STORE *trust_store
        target._ptr(),         # X509 *target
        untrustedChain._ptr()  # STACK_OF(X509) *untrusted
    )
    if result <= 0:
        raise RuntimeError('could not call X509_STORE_CTX_init')
    result = m2.X509_verify_cert(ctx)
    m2.x509_store_ctx_free(ctx)
    if result == 1:
        return True
    elif result <= 0:
        return False
    else:
        raise RuntimeError('unexpected X509_verify_cert result')

I have used certifi to load a bundled CA root, so this verification doesn't use system CA store. It's better for cross-platform support.

The ideal solution for tlslite-ng requires first a pure Python implementation of certificate chain validation, but this is an immediate solution for who can use M2Crypto. At least by using OpenSSL bindings for this is fast and secure.

HMaker avatar Apr 02 '23 16:04 HMaker

I'm not sure if delegating that to m2crypto has much utility... if you already have it, why not just use m2crypto for TLS?

Second of all, the path verification should also inform openssl that we're checking either client or server certificate by providing purpose or the check.

Finally, any functionality like that would have to be optional.

tomato42 avatar Apr 03 '23 00:04 tomato42

Ok, I made it set the purpose to X509_PURPOSE_SSL_SERVER since we are SSL client

import certifi
from M2Crypto import m2, X509 as M2CryptoX509
from tlslite.x509certchain import X509CertChain

def verifyServerCertChain(serverName: str, serverCertChain: X509CertChain) -> bool:
    """Returns True if the given serverCertChain is trusted, False otherwise"""
    untrustedChain = M2CryptoX509.X509_Stack()
    for cert in serverCertChain.x509List:
        untrustedChain.push(M2CryptoX509.load_cert_der_string(bytes(cert.writeBytes())))
    target = untrustedChain[0]
    # load the trusted CA roots
    trustedCAStore = M2CryptoX509.X509_Store()
    trustedCAStore.load_info(certifi.where())
    # validate the certificate chain against the trusted CA store
    ctx = m2.X509_STORE_CTX_new()
    if not ctx:
        raise RuntimeError('could not call X509_STORE_CTX_new')
    verify_params = m2.X509_VERIFY_PARAM_new()
    if not verify_params:
        m2.x509_store_ctx_free(ctx)
        raise RuntimeError('could not call X509_VERIFY_PARAM_new')
    m2.X509_STORE_CTX_set0_param(ctx, verify_params)
    verify_params = None # do not use after X509_STORE_CTX_set0_param
    result = m2.X509_STORE_CTX_set_purpose(ctx, 2) # X509_PURPOSE_SSL_SERVER = 2
    if result <= 0:
        m2.x509_store_ctx_free(ctx)
        raise RuntimeError('could not set X509_STORE_CTX_set_purpose')
    # verify hostname
    verify_params = m2.X509_STORE_CTX_get0_param(ctx)
    result = m2.X509_VERIFY_PARAM_set1_host(verify_params, serverName, len(serverName))
    if result <= 0:
        m2.x509_store_ctx_free(ctx)
        raise RuntimeError('could not set X509_VERIFY_PARAM_set1_host')
    result = m2.X509_STORE_CTX_init(
        ctx,                         # X509_STORE_CTX *ctx
        trustedCAStore._ptr(),       # X509_STORE *trust_store
        target._ptr(),               # X509 *target
        untrustedChain._ptr()        # STACK_OF(X509) *untrusted
    )
    if result <= 0:
        m2.x509_store_ctx_free(ctx)
        raise RuntimeError('could not call X509_STORE_CTX_init')
    result = m2.X509_verify_cert(ctx)
    m2.x509_store_ctx_free(ctx)
    if result == 1:
        return True
    elif result <= 0:
        return False
    else:
        raise RuntimeError('unexpected X509_verify_cert result')

I use tlslite-ng because it gives me full control of the ClientHello, so I can add or remove extensions.

And yes this can be optional, just check if M2Crypto is installed before enabling it. Or add a new method set_verify_callback(callback) to TLSConnection so people can set M2Crypto-backed verifier if it's available (or any custom verifier).

HMaker avatar Apr 03 '23 17:04 HMaker

will you create a PR implementing that?

tomato42 avatar Apr 04 '23 14:04 tomato42

Only when my changes to M2Crypto gets merged https://gitlab.com/m2crypto/m2crypto/-/merge_requests/287

HMaker avatar Apr 11 '23 01:04 HMaker