F-6307: reject presented but untrusted client cert under wantClientAuth

pull/381/head
Chris Conlon 2026-07-16 16:41:38 -06:00
parent 05fbad13f9
commit 09fbfdff88
3 changed files with 128 additions and 14 deletions

View File

@ -638,18 +638,17 @@ public class WolfSSLInternalVerifyCb implements WolfSSLVerifyCallback {
else if ((!this.clientMode) && (this.params != null) &&
this.params.getWantClientAuth() &&
(!this.params.getNeedClientAuth())) {
/* wantClientAuth is set and client sent a certificate.
* Try to verify via TrustManager, but don't fail the
* handshake if verification fails matches SunJSSE
* behavior where wantClientAuth is non-fatal. */
if (VerifyCertChainWithTrustManager(x509certs, authType)) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "wantClientAuth: client cert verified successfully");
} else {
/* wantClientAuth only makes an absent client cert non-fatal.
* A cert that is presented must still validate, matching SunJSSE,
* so abort the handshake if the TrustManager rejects it. */
if (VerifyCertChainWithTrustManager(x509certs, authType) == false) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "wantClientAuth: client cert verification failed, " +
"continuing handshake (non-fatal)");
"aborting handshake");
return 0;
}
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "wantClientAuth: client cert verified successfully");
}
else {
/* Poll X509TrustManager / X509ExtendedTrustManager for certificate

View File

@ -1006,22 +1006,27 @@ public class WolfSSLEngineTest {
};
/* All combinations with external X509ExtendedTrustManager registered
* which will trust NO client certs and ALL server certs */
* which will trust NO client certs and ALL server certs. The client
* presents a cert, so when the server requests one (want or need) it
* fails validation and the handshake aborts. wantClientAuth only makes
* an absent client cert non-fatal, matching SunJSSE, so serverWant with
* a presented-but-rejected cert is expected to fail. Only the cases
* where the server requests no client cert succeed. */
PeerAuthConfig[] configsDefaultManagers = new PeerAuthConfig[] {
new PeerAuthConfig(true, true, true, true, false),
new PeerAuthConfig(true, true, true, false, true),
new PeerAuthConfig(true, true, true, false, false),
new PeerAuthConfig(true, true, false, true, false),
new PeerAuthConfig(true, true, false, false, true),
new PeerAuthConfig(true, false, true, true, false),
new PeerAuthConfig(true, false, true, false, true),
new PeerAuthConfig(true, false, true, false, false),
new PeerAuthConfig(true, false, false, true, false),
new PeerAuthConfig(true, false, false, false, true),
new PeerAuthConfig(false, true, true, true, false),
new PeerAuthConfig(false, true, true, false, true),
new PeerAuthConfig(false, true, true, false, false),
new PeerAuthConfig(false, true, false, true, false),
new PeerAuthConfig(false, true, false, false, true),
new PeerAuthConfig(false, false, true, true, false),
new PeerAuthConfig(false, false, true, false, true),
new PeerAuthConfig(false, false, true, false, false),
new PeerAuthConfig(false, false, false, true, false),
new PeerAuthConfig(false, false, false, false, true)
};

View File

@ -2173,6 +2173,116 @@ public class WolfSSLSocketTest {
}
/**
* With wantClientAuth(true) and needClientAuth(false), a client that
* presents a certificate the server cannot validate must abort the
* handshake, matching SunJSSE. wantClientAuth only makes an absent
* client cert non-fatal, not an invalid one.
*/
@Test
public void testWantClientAuthRejectsUntrustedClientCert()
throws Exception {
/* Negative case: server does not trust the presented client cert.
* Server truststore (caServerJKS) does not contain the CA that signed
* the client cert, so a presented client cert cannot be validated. */
SSLContext srvCtx = tf.createSSLContext("TLSv1.2", ctxProvider,
tf.createTrustManager("SunX509", tf.caServerJKS, ctxProvider),
tf.createKeyManager("SunX509", tf.serverJKS, ctxProvider));
/* Client trusts the server and has a client cert (clientJKS) to
* present when the server sends a CertificateRequest. */
SSLContext cliCtx = tf.createSSLContext("TLSv1.2", ctxProvider,
tf.createTrustManager("SunX509", tf.caServerJKS, ctxProvider),
tf.createKeyManager("SunX509", tf.clientJKS, ctxProvider));
SSLServerSocket ss = (SSLServerSocket)srvCtx.getServerSocketFactory()
.createServerSocket(0);
SSLSocket cs = (SSLSocket)cliCtx.getSocketFactory().createSocket();
cs.connect(new InetSocketAddress(ss.getLocalPort()));
final SSLSocket server = (SSLSocket)ss.accept();
server.setWantClientAuth(true);
server.setNeedClientAuth(false);
ExecutorService es = Executors.newSingleThreadExecutor();
Future<Void> serverFuture = es.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server.startHandshake();
fail("Server accepted an untrusted client cert under " +
"wantClientAuth");
} catch (SSLException e) {
/* expected: presented client cert failed validation */
}
server.close();
return null;
}
});
try {
cs.startHandshake();
fail("Client handshake succeeded despite server rejecting cert");
} catch (SSLException e) {
/* expected: server sent a fatal alert */
}
cs.close();
es.shutdown();
serverFuture.get();
ss.close();
/* Server truststore (caClientJKS) trusts the client cert, so a valid
* presented client cert must let the handshake complete. */
SSLContext srvCtx2 = tf.createSSLContext("TLSv1.2", ctxProvider,
tf.createTrustManager("SunX509", tf.caClientJKS, ctxProvider),
tf.createKeyManager("SunX509", tf.serverJKS, ctxProvider));
SSLContext cliCtx2 = tf.createSSLContext("TLSv1.2", ctxProvider,
tf.createTrustManager("SunX509", tf.caServerJKS, ctxProvider),
tf.createKeyManager("SunX509", tf.clientJKS, ctxProvider));
ss = (SSLServerSocket)srvCtx2.getServerSocketFactory()
.createServerSocket(0);
cs = (SSLSocket)cliCtx2.getSocketFactory().createSocket();
cs.connect(new InetSocketAddress(ss.getLocalPort()));
final SSLSocket server2 = (SSLSocket)ss.accept();
server2.setWantClientAuth(true);
server2.setNeedClientAuth(false);
es = Executors.newSingleThreadExecutor();
serverFuture = es.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
try {
server2.startHandshake();
} catch (SSLException e) {
fail("Server rejected a valid client cert under " +
"wantClientAuth");
}
server2.close();
return null;
}
});
try {
cs.startHandshake();
} catch (SSLException e) {
fail("Client handshake failed with a valid client cert");
}
cs.close();
es.shutdown();
serverFuture.get();
ss.close();
}
@Test
public void testProtocolTLSv10() throws Exception {