tlslite-ng
tlslite-ng copied to clipboard
Implement certificate signature checking
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.
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.
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.
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).
will you create a PR implementing that?
Only when my changes to M2Crypto gets merged https://gitlab.com/m2crypto/m2crypto/-/merge_requests/287