Merge pull request #434 from cconlon/endpointIdCaseInsensitive

JSSE: match endpoint identification algorithm names case-insensitively
pull/438/head
Ruby Martin 2026-10-16 12:51:36 -05:00 committed by GitHub
commit d090391518
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 277 additions and 46 deletions

View File

@ -192,6 +192,7 @@ public class WolfSSLInternalVerifyCb implements WolfSSLVerifyCallback {
*/
private int verifyHostnameForExternalTM(X509Certificate peer) {
int type;
String endpointIdAlgo = null;
String peerHost = null;
SSLSession session = null;
@ -209,8 +210,8 @@ public class WolfSSLInternalVerifyCb implements WolfSSLVerifyCallback {
/* Only HTTPS and LDAPS are supported. Fail if endpoint ID was
* explicitly set to something else (typo or unsupported algo). */
if (!endpointIdAlgo.equals("HTTPS") &&
!endpointIdAlgo.equals("LDAPS")) {
type = WolfSSLTrustX509.getHostnameType(endpointIdAlgo);
if (type == WolfSSLTrustX509.HOSTNAME_TYPE_UNSUPPORTED) {
final String tmpAlgoUnsup = endpointIdAlgo;
this.verifyException = new CertificateException(
"Unsupported endpoint identification algorithm: " +
@ -296,7 +297,7 @@ public class WolfSSLInternalVerifyCb implements WolfSSLVerifyCallback {
try {
wCert = new WolfSSLCertificate(peer.getEncoded());
/* LDAPS requires wildcard left-most matching only */
long flags = endpointIdAlgo.equals("LDAPS") ?
long flags = (type == WolfSSLTrustX509.HOSTNAME_TYPE_LDAPS) ?
WolfSSL.WOLFSSL_LEFT_MOST_WILDCARD_ONLY : 0;
int ret = WolfSSLUtil.verifyHostnameOrIp(wCert, peerHost, flags);
if (ret == WolfSSL.SSL_SUCCESS) {

View File

@ -60,10 +60,12 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
private KeyStore store = null;
/** Endpoint Identification Algorithm not supported */
static final int HOSTNAME_TYPE_UNSUPPORTED = 0;
/** X509ExtendedTrustManager hostname type HTTPS */
private static int HOSTNAME_TYPE_HTTPS = 1;
static final int HOSTNAME_TYPE_HTTPS = 1;
/** X509ExtendedTrustManager hostname type LDAPS */
private static int HOSTNAME_TYPE_LDAPS = 2;
static final int HOSTNAME_TYPE_LDAPS = 2;
/**
* Create new WolfSSLTrustX509 object
@ -801,6 +803,28 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
}
}
/**
* Get hostname verification type for an Endpoint Identification
* Algorithm name. Names are matched case-insensitively.
*
* @param endpointIdAlgo Endpoint Identification Algorithm name
*
* @return HOSTNAME_TYPE_HTTPS, HOSTNAME_TYPE_LDAPS, or
* HOSTNAME_TYPE_UNSUPPORTED if name is not supported
*/
static int getHostnameType(String endpointIdAlgo) {
if ("HTTPS".equalsIgnoreCase(endpointIdAlgo)) {
return HOSTNAME_TYPE_HTTPS;
}
if ("LDAPS".equalsIgnoreCase(endpointIdAlgo)) {
return HOSTNAME_TYPE_LDAPS;
}
return HOSTNAME_TYPE_UNSUPPORTED;
}
/**
* Verify hostname of certificate.
*
@ -839,25 +863,14 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
/* If endpoint ID algo is null or empty, skips hostname verify */
if (endpointIdAlgo != null && !endpointIdAlgo.isEmpty()) {
if (endpointIdAlgo.equals("HTTPS")) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "verifying hostname, endpoint identification " +
"algorithm = HTTPS");
verifyHostnameByType(cert, (SSLSocket)socket,
null, isClient, HOSTNAME_TYPE_HTTPS);
}
else if (endpointIdAlgo.equals("LDAPS")) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "verifying hostname, endpoint identification " +
"algorithm = LDAPS");
verifyHostnameByType(cert, (SSLSocket)socket,
null, isClient, HOSTNAME_TYPE_LDAPS);
}
else {
int type = getHostnameType(endpointIdAlgo);
if (type == HOSTNAME_TYPE_UNSUPPORTED) {
throw new CertificateException(
"Unsupported Endpoint Identification Algorithm: " +
endpointIdAlgo);
}
verifyHostnameByType(cert, (SSLSocket)socket, null,
isClient, type);
}
else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
@ -879,25 +892,13 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
/* If endpoint ID algo is null or empty, skips hostname verify */
if (endpointIdAlgo != null && !endpointIdAlgo.isEmpty()) {
if (endpointIdAlgo.equals("HTTPS")) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "verifying hostname, endpoint identification " +
"algorithm = HTTPS");
verifyHostnameByType(cert, null, engine, isClient,
HOSTNAME_TYPE_HTTPS);
}
else if (endpointIdAlgo.equals("LDAPS")) {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
() -> "verifying hostname, endpoint identification " +
"algorithm = LDAPS");
verifyHostnameByType(cert, null, engine, isClient,
HOSTNAME_TYPE_LDAPS);
}
else {
int type = getHostnameType(endpointIdAlgo);
if (type == HOSTNAME_TYPE_UNSUPPORTED) {
throw new CertificateException(
"Unsupported Endpoint Identification Algorithm: " +
endpointIdAlgo);
}
verifyHostnameByType(cert, null, engine, isClient, type);
}
else {
WolfSSLDebug.log(getClass(), WolfSSLDebug.INFO,
@ -954,8 +955,9 @@ public final class WolfSSLTrustX509 extends X509ExtendedTrustManager {
*
* Does hostname verification internally if Endpoint Identification
* Algorithm has been set by application in SSLParameters, and that
* Algorithm matches "HTTPS" or "LDAPS". If "HTTPS" is set, hostname
* verification is done using SNI first then peer host value.
* Algorithm matches "HTTPS" or "LDAPS" (case-insensitive). If "HTTPS"
* is set, hostname verification is done using SNI first then peer host
* value.
*
* Other Endpoint Identification Algorithms besides "HTTPS" and "LDAPS"
* are not currently supported.

View File

@ -1652,8 +1652,8 @@ public class WolfSSLTrustX509Test {
/* LDAPS hostname verification test, wildcard success */
testX509ExtTrustMgrSSLEngineWildcardSuccessLDAPS();
/* SSLEngine should fail if trying to use bad endoint alg */
testX509ExtTrustMgrSSLEngineEndpointAlgFail();
/* SSLEngine LDAPS should fail when only SNI matches server cert */
testX509ExtTrustMgrSSLEngineLDAPSIgnoresSNIFail();
}
@ -2305,10 +2305,238 @@ public class WolfSSLTrustX509Test {
cliParams.setEndpointIdentificationAlgorithm(endpointIdAlgo);
client.setSSLParameters(cliParams);
return tf.testConnection(server, client, null, null, endpointIdAlgo);
return tf.testConnection(server, client, null, null,
"Test endpoint ID");
}
private void testX509ExtTrustMgrSSLEngineEndpointAlgFail()
/* Endpoint identification algorithm names are case-insensitive. Apache
* HttpClient 5 sets "https" in lower case. Each path also checks a
* hostname mismatch to confirm verification ran and was not skipped. */
@Test
public void testEndpointIdAlgorithmCaseInsensitive() throws Exception {
KeyStore sniCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "example.com", true);
KeyStore hostCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "localhost", true);
KeyStore otherCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "bar.example.com", true);
KeyStore full = tf.generateSelfSignedCertJKS(
"invalidname.com", "*.example.com", true);
/* SSLSocket, WolfSSLTrustX509 */
assertNull(connectSSLSocketEndpointAlgo(
sniCert, "https", "example.com"));
assertNull(connectSSLSocketEndpointAlgo(
sniCert, "Https", "example.com"));
assertNotNull(connectSSLSocketEndpointAlgo(
sniCert, "https", "www.invalid.com"));
assertNull(connectSSLSocketEndpointAlgo(hostCert, "ldaps", null));
assertNull(connectSSLSocketEndpointAlgo(hostCert, "LdApS", null));
assertNotNull(connectSSLSocketEndpointAlgo(otherCert, "ldaps", null));
/* SSLEngine, WolfSSLTrustX509. Distinct ports so no connection
* resumes an earlier session. */
assertEquals(0, connectSSLEngineEndpointAlgo(null,
"https", "wolfSSL auth test", "example.com", 11120));
assertEquals(0, connectSSLEngineEndpointAlgo(null,
"hTtPs", "wolfSSL auth test", "example.com", 11121));
assertNotEquals(0, connectSSLEngineEndpointAlgo(null,
"https", "wolfSSL auth test", "www.invalid.com", 11122));
assertEquals(0, connectSSLEngineEndpointAlgo(null,
"ldaps", "example.com", null, 11123));
assertEquals(0, connectSSLEngineEndpointAlgo(null,
"LdApS", "example.com", null, 11124));
assertNotEquals(0, connectSSLEngineEndpointAlgo(null,
"ldaps", "www.invalid.com", null, 11125));
/* Basic X509TrustManager, peer host is foo.example.com */
assertEquals(0, connectWithBasicTrustManager(full, "https", 11126));
assertEquals(0, connectWithBasicTrustManager(full, "Https", 11127));
assertNotEquals(0,
connectWithBasicTrustManager(otherCert, "https", 11128));
assertEquals(0, connectWithBasicTrustManager(full, "ldaps", 11129));
assertEquals(0, connectWithBasicTrustManager(full, "LdApS", 11130));
assertNotEquals(0,
connectWithBasicTrustManager(otherCert, "ldaps", 11131));
}
/* Lower case "ldaps" must select LDAPS rules, not HTTPS ones. LDAPS
* ignores SNI and only allows a full left-most wildcard label. Each
* HTTPS success shows the LDAPS failure comes from those rules. */
@Test
public void testEndpointIdAlgorithmLowerCaseLDAPSRules()
throws Exception {
KeyStore sniCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "example.com", true);
KeyStore partial = tf.generateSelfSignedCertJKS(
"invalidname.com", "f*.example.com", true);
/* SSLSocket, SNI matches cert but peer host "localhost" does not */
assertNull(connectSSLSocketEndpointAlgo(
sniCert, "https", "example.com"));
assertNotNull(connectSSLSocketEndpointAlgo(
sniCert, "ldaps", "example.com"));
/* SSLEngine, partial wildcard against foo.example.com */
assertEquals(0, connectSSLEngineEndpointAlgo(partial,
"https", "foo.example.com", null, 11140));
assertNotEquals(0, connectSSLEngineEndpointAlgo(partial,
"ldaps", "foo.example.com", null, 11141));
/* Basic X509TrustManager, partial wildcard against foo.example.com */
assertEquals(0, connectWithBasicTrustManager(partial, "https", 11142));
assertNotEquals(0,
connectWithBasicTrustManager(partial, "ldaps", 11143));
}
/* Unknown algorithm names must fail the handshake even when the hostname
* matches, rather than skip verification. Case-insensitive matching must
* not loosen this to near misses. An empty name skips verification. */
@Test
public void testEndpointIdAlgorithmUnknownOrEmpty() throws Exception {
String[] unknown = { "HTTP", "https ", " HTTPS", "HTTPS1", "LDAPS/" };
KeyStore sniCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "example.com", true);
KeyStore hostCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "localhost", true);
KeyStore otherCert = tf.generateSelfSignedCertJKS(
"invalidname.com", "bar.example.com", true);
KeyStore full = tf.generateSelfSignedCertJKS(
"invalidname.com", "*.example.com", true);
int port = 11150;
/* Each setup below passes under both HTTPS and LDAPS rules, so a
* failure with an unknown name can only come from rejecting it */
for (String alg : new String[] { "HTTPS", "LDAPS" }) {
assertNull(alg, connectSSLSocketEndpointAlgo(hostCert, alg, null));
assertEquals(alg, 0, connectSSLEngineEndpointAlgo(null,
alg, "example.com", "example.com", port++));
assertEquals(alg, 0,
connectWithBasicTrustManager(full, alg, port++));
}
for (String alg : unknown) {
assertNotNull(alg,
connectSSLSocketEndpointAlgo(hostCert, alg, null));
assertNotEquals(alg, 0, connectSSLEngineEndpointAlgo(null,
alg, "example.com", "example.com", port++));
assertNotEquals(alg, 0,
connectWithBasicTrustManager(full, alg, port++));
}
/* Empty name, hostname mismatch is not checked */
assertNull(connectSSLSocketEndpointAlgo(
sniCert, "", "www.invalid.com"));
assertEquals(0, connectSSLEngineEndpointAlgo(null,
"", "wolfSSL auth test", "www.invalid.com", port++));
assertEquals(0, connectWithBasicTrustManager(otherCert, "", port++));
}
/* Connect SSLSocket client to "localhost", with server presenting the
* cert in srvCertStore. sni may be null. Returns first exception from
* client or server, or null on success. */
private Exception connectSSLSocketEndpointAlgo(KeyStore srvCertStore,
String endpointIdAlgo, String sni) throws Exception {
Exception cliException = null;
SSLContext srvCtx = tf.createSSLContext("TLSv1.2", provider,
tf.createTrustManager("SunX509", tf.caClientJKS, provider),
tf.createKeyManager("SunX509", srvCertStore, provider));
SSLContext cliCtx = tf.createSSLContext("TLSv1.2", provider,
tf.createTrustManager("SunX509", srvCertStore, provider),
tf.createKeyManager("SunX509", tf.clientJKS, provider));
SSLServerSocket ss = (SSLServerSocket)srvCtx.getServerSocketFactory()
.createServerSocket(0);
TestArgs serverArgs = new TestArgs(null, null, true, true, true, null);
TestSSLSocketServer server = new TestSSLSocketServer(
srvCtx, ss, serverArgs, 1);
server.start();
SSLSocket sock = (SSLSocket)cliCtx.getSocketFactory().createSocket();
try {
sock.connect(new InetSocketAddress("localhost", ss.getLocalPort()));
SSLParameters params = sock.getSSLParameters();
params.setEndpointIdentificationAlgorithm(endpointIdAlgo);
if (sni != null) {
List<SNIServerName> sniNames = new ArrayList<>(1);
sniNames.add(new SNIHostName(sni));
params.setServerNames(sniNames);
}
sock.setSSLParameters(params);
sock.startHandshake();
sock.getOutputStream().write('A');
assertEquals((int)'B', sock.getInputStream().read());
} catch (Exception e) {
cliException = e;
} finally {
sock.close();
server.join(1000);
ss.close();
}
if (cliException != null) {
return cliException;
}
return server.getException();
}
/* Connect SSLEngine client to server with given endpoint identification
* algorithm, peer host, and optional SNI. Server presents the cert in
* srvCertStore, or default server cert if null. Returns 0 on success. */
private int connectSSLEngineEndpointAlgo(KeyStore srvCertStore,
String endpointIdAlgo, String host, String sni, int port)
throws Exception {
SSLContext srvCtx;
SSLContext cliCtx;
if (srvCertStore == null) {
srvCtx = tf.createSSLContext("TLS", provider);
cliCtx = srvCtx;
}
else {
srvCtx = tf.createSSLContext("TLS", provider,
tf.createTrustManager("SunX509", tf.caClientJKS, provider),
tf.createKeyManager("SunX509", srvCertStore, provider));
cliCtx = tf.createSSLContext("TLS", provider,
tf.createTrustManager("SunX509", srvCertStore, provider),
tf.createKeyManager("SunX509", tf.clientJKS, provider));
}
SSLEngine server = srvCtx.createSSLEngine();
SSLEngine client = cliCtx.createSSLEngine(host, port);
server.setWantClientAuth(true);
server.setNeedClientAuth(true);
client.setUseClientMode(true);
server.setUseClientMode(false);
SSLParameters cliParams = client.getSSLParameters();
cliParams.setEndpointIdentificationAlgorithm(endpointIdAlgo);
if (sni != null) {
List<SNIServerName> sniNames = new ArrayList<>(1);
sniNames.add(new SNIHostName(sni));
cliParams.setServerNames(sniNames);
}
client.setSSLParameters(cliParams);
return tf.testConnection(server, client, null, null,
"Test endpoint ID");
}
private void testX509ExtTrustMgrSSLEngineLDAPSIgnoresSNIFail()
throws CertificateException, IOException, Exception {
int ret;
@ -2326,13 +2554,13 @@ public class WolfSSLTrustX509Test {
SSLParameters cliParams = client.getSSLParameters();
/* Enable Endpoint Identification for hostname verification on client.
* We only support "HTTPS" so setting "LDAPS" here should cause a
* failure. */
/* Enable LDAPS Endpoint Identification on client. LDAPS verifies
* only the peer host, "wolfSSL auth test", which does not match the
* peer cert, so this should cause a failure. */
cliParams.setEndpointIdentificationAlgorithm("LDAPS");
/* Set SNI, used for hostname verification of server cert. Peer cert
* has altName set to "example.com" */
/* Set SNI to "example.com", which matches the peer cert altName.
* HTTPS would pass using this, but LDAPS ignores SNI. */
SNIHostName sniName = new SNIHostName("example.com");
List<SNIServerName> sniNames = new ArrayList<>(1);
sniNames.add(sniName);