Cryptographic Failures with Mutual Tls
How Cryptographic Failures Manifests in Mutual TLS
Mutual TLS (mTLS) adds client‑certificate authentication to the standard TLS handshake, but cryptographic weaknesses can still appear in the handshake parameters, certificate validation logic, or key‑exchange algorithms. Common mTLS‑specific failure patterns include:
- Weak or deprecated protocol versions – allowing TLS 1.0/1.1 or SSLv3 enables attacks such as POODLE (CVE‑2014‑3566) or BEAST (CVE‑2011‑3389). In mTLS this also lets an attacker force a downgrade and then impersonate either party.
- Weak cipher suites – enabling CBC‑mode ciphers with MAC‑then‑Encrypt (e.g., TLS_RSA_WITH_AES_128_CBC_SHA) opens the door to Lucky‑13 style timing attacks. Export‑grade or NULL ciphers can be negotiated if the server does not enforce strong suites.
- Insufficient certificate validation – setting verification to
noneoroptionalallows a client to connect without presenting a valid client cert, or a server to accept a self‑signed or expired cert, enabling impersonation (see CVE‑2016‑2107, the “SSL/TLS MITM” scenario). - Anonymous Diffie‑Hellman (DH) or RSA key exchange – using
TLS_DH_anon_*suites provides no authentication, defeating the purpose of mTLS. - Improper key size – RSA keys < 2048 bits or elliptic‑curve curves with insufficient security (e.g., secp192r1) can be factored or solved via discrete log attacks.
These issues appear in the code paths where the TLS configuration is built. For example, in Go’s crypto/tls package, the Config fields MinVersion, CipherSuites, ClientAuth, and RootCAs directly control the handshake. In Node.js, the tls.createSecureContext options minVersion, ciphers, requestCert, and rejectUnauthorized play the same role. Misconfiguring any of these fields leads to the cryptographic failures described above.
Mutual TLS‑Specific Detection
middleBrick performs a black‑box scan of the API endpoint’s unauthenticated surface. During the scan it:
- Initiates a TLS handshake and records the protocol version offered by the server.
- Lists the cipher suites the server is willing to negotiate.
- Checks whether the server requests a client certificate (
CertificateRequestmessage) and whether it validates the presented client cert. - Validates the server’s certificate chain against trusted roots, checks expiration, and verifies hostname matching.
- Flags the use of TLS < 1.2, weak ciphers (CBC‑mode, export, NULL, anonymous DH), self‑signed or expired certs, and missing client‑cert validation.
For example, running the middleBrick CLI against an API yields a finding such as:
middlebrick scan https://api.example.com/health
[+] Cryptographic Failures (Mutual TLS)
- Protocol version: TLS 1.0 (should be >= TLS 1.2)
- Cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA (CBC‑mode, vulnerable to Lucky‑13)
- Server cert: self‑signed, expired 2022-05-01
- ClientAuth: none (server does not request or validate client cert)
The finding includes severity, remediation guidance, and maps to OWASP API Security Top 10 (M9 – Improper Inventory Management is unrelated; the finding maps to the broader Cryptographic Failures category). Because the scan is unauthenticated, it reveals exactly what an attacker can observe without any credentials, making it suitable for detecting misconfigurations in production‑exposed mTLS endpoints.
Mutual TLS‑Specific Remediation
Fixing cryptographic failures in mTLS involves tightening the TLS configuration on both client and server sides. Below are language‑specific examples that enforce TLS 1.2+, strong cipher suites, and proper certificate validation.
Go (crypto/tls)
import (
"crypto/tls"
)
func secureTLSConfig() *tls.Config {
return &tls.Config{
MinVersion: tls.VersionTLS12,
CipherSuites: []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
},
PreferServerCipherSuites: true,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: loadClientCAPool(), // function that returns *x509.CertPool
}
}
func loadClientCAPool() *x509.CertPool {
pool := x509.NewCertPool()
// Add trusted client CA certificates
pool.AppendCertsFromPEM(clientCAPEM)
return pool
}
Node.js (tls module)
const tls = require('tls');
const fs = require('fs');
const options = {
minVersion: 'TLSv1.2',
ciphers: 'TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256',
honorCipherOrder: true,
requestCert: true, // ask for client cert
rejectUnauthorized: true, // abort if client cert is not trusted
ca: fs.readFileSync('client-ca.pem'), // trusted client CAs
};
const server = tls.createServer(options, (socket) => {
socket.write('welcome\n');
socket.end();
});
server.listen(8443);
Python (ssl module)
import ssl
import socket
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.minimum_version = ssl.TLSVersion.TLSv1_2
context.maximum_version = ssl.TLSVersion.TLSv1_3
context.set_ciphers('ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305')
context.verify_mode = ssl.CERT_REQUIRED
context.load_verify_locations(cafile='client-ca.pem')
# For a server that also authenticates clients:
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.minimum_version = ssl.TLSVersion.TLSv1_2
server_context.set_ciphers('ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305')
server_context.verify_mode = ssl.CERT_REQUIRED
server_context.load_verify_locations(cafile='client-ca.pem')
server_context.load_cert_chain(certfile='server-cert.pem', keyfile='server-key.pem')
# Wrap socket
with socket.create_connection(('api.example.com', 443)) as sock:
with context.wrap_socket(sock, server_hostname='api.example.com') as ssock:
print(ssock.version())
After applying these changes, re‑run middleBrick to confirm that the findings disappear and the security score improves.