--- old/src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java 2020-08-14 15:34:15.000000000 +0300 +++ new/src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java 2020-08-14 15:34:15.000000000 +0300 @@ -46,9 +46,17 @@ import java.lang.reflect.InvocationTargetException; import java.security.AccessController; import java.security.PrivilegedAction; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import javax.net.SocketFactory; import javax.net.ssl.SSLParameters; +import javax.net.ssl.HandshakeCompletedEvent; +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.security.sasl.SaslException; /** * A thread that creates a connection to an LDAP server. @@ -109,7 +117,7 @@ * @author Rosanna Lee * @author Jagane Sundar */ -public final class Connection implements Runnable { +public final class Connection implements Runnable, HandshakeCompletedListener { private static final boolean debug = false; private static final int dump = 0; // > 0 r, > 1 rw @@ -342,6 +350,7 @@ param.setEndpointIdentificationAlgorithm("LDAPS"); sslSocket.setSSLParameters(param); } + sslSocket.addHandshakeCompletedListener(this); if (connectTimeout > 0) { int socketTimeout = sslSocket.getSoTimeout(); sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value @@ -637,6 +646,15 @@ ldr = ldr.next; } } + if (isTlsConnection()) { + if (closureReason != null) { + CommunicationException ce = new CommunicationException(); + ce.setRootCause(closureReason); + tlsHandshakeCompleted.completeExceptionally(ce); + } else { + tlsHandshakeCompleted.cancel(false); + } + } sock = null; } nparent = notifyParent; @@ -972,4 +990,46 @@ } return buf; } + + private CompletableFuture tlsHandshakeCompleted = + new CompletableFuture<>(); + + @Override + public void handshakeCompleted(HandshakeCompletedEvent event) { + try { + X509Certificate tlsServerCert = null; + Certificate[] certs; + if (event.getSocket().getUseClientMode()) { + certs = event.getPeerCertificates(); + } else { + certs = event.getLocalCertificates(); + } + if (certs != null && certs.length > 0 && + certs[0] instanceof X509Certificate) { + tlsServerCert = (X509Certificate) certs[0]; + } + tlsHandshakeCompleted.complete(tlsServerCert); + } catch (SSLPeerUnverifiedException ex) { + CommunicationException ce = new CommunicationException(); + ce.setRootCause(closureReason); + tlsHandshakeCompleted.completeExceptionally(ex); + } + } + + public boolean isTlsConnection() { + return sock instanceof SSLSocket; + } + + public X509Certificate getTlsServerCertificate() + throws SaslException { + try { + if (isTlsConnection()) + return tlsHandshakeCompleted.get(); + } catch (InterruptedException iex) { + throw new SaslException("TLS Handshake Exception ", iex); + } catch (ExecutionException eex) { + throw new SaslException("TLS Handshake Exception ", eex.getCause()); + } + return null; + } } --- old/src/java.naming/share/classes/com/sun/jndi/ldap/sasl/LdapSasl.java 2020-08-14 15:34:16.000000000 +0300 +++ new/src/java.naming/share/classes/com/sun/jndi/ldap/sasl/LdapSasl.java 2020-08-14 15:34:16.000000000 +0300 @@ -26,6 +26,7 @@ package com.sun.jndi.ldap.sasl; import java.io.*; +import java.security.cert.X509Certificate; import java.util.Vector; import java.util.Hashtable; import java.util.StringTokenizer; @@ -41,6 +42,7 @@ import com.sun.jndi.ldap.Connection; import com.sun.jndi.ldap.LdapClient; import com.sun.jndi.ldap.LdapResult; +import com.sun.jndi.ldap.sasl.TlsChannelBinding.TlsChannelBindingType; /** * Handles SASL support. @@ -110,10 +112,38 @@ String authzId = (env != null) ? (String)env.get(SASL_AUTHZ_ID) : null; String[] mechs = getSaslMechanismNames(authMech); + // Internal TLS Channel Binding property cannot be set explicitly + if (env.get(TlsChannelBinding.CHANNEL_BINDING) != null) { + throw new NamingException(TlsChannelBinding.CHANNEL_BINDING + + " property cannot be set explicitly"); + } + + Hashtable envProps = (Hashtable) env; + try { + // Prepare TLS Channel Binding data + if (conn.isTlsConnection()) { + TlsChannelBindingType cbType = + TlsChannelBinding.parseType( + (String)env.get(TlsChannelBinding.CHANNEL_BINDING_TYPE)); + if (cbType == TlsChannelBindingType.TLS_SERVER_END_POINT) { + // set tls-server-end-point channel binding + X509Certificate cert = conn.getTlsServerCertificate(); + if (cert != null) { + TlsChannelBinding tlsCB = + TlsChannelBinding.create(cert); + envProps = (Hashtable) env.clone(); + envProps.put(TlsChannelBinding.CHANNEL_BINDING, tlsCB.getData()); + } else { + throw new SaslException("No suitable certificate to generate " + + "TLS Channel Binding data"); + } + } + } + // Create SASL client to use using SASL package saslClnt = Sasl.createSaslClient( - mechs, authzId, "ldap", server, (Hashtable)env, cbh); + mechs, authzId, "ldap", server, envProps, cbh); if (saslClnt == null) { throw new AuthenticationNotSupportedException(authMech); --- old/src/java.naming/share/classes/module-info.java 2020-08-14 15:34:17.000000000 +0300 +++ new/src/java.naming/share/classes/module-info.java 2020-08-14 15:34:17.000000000 +0300 @@ -59,6 +59,21 @@ *
If this property is not specified, the default is to wait * for the response until it is received. * + *
  • {@systemProperty com.sun.jndi.ldap.tls.cbtype}: + *
    The value of this property is the string representing the TLS + * Channel Binding type required for an LDAP connection over SSL/TLS. + * Possible values are : + *
      + *
    • "tls-unique" - Channel Binding data is created on the basis + * of TLS Finished Message. Not supported right now. + *
    • + *
    • "tls-server-end-point" - Channel Binding data is created on + * the basis of the TLS server certificate. + *
    • + *
    + *
    If this property is not specified, the client does not send + * channel binding information to the server. + *
  • * * * @provides javax.naming.ldap.spi.LdapDnsProvider --- old/src/java.security.jgss/share/classes/module-info.java 2020-08-14 15:34:18.000000000 +0300 +++ new/src/java.security.jgss/share/classes/module-info.java 2020-08-14 15:34:17.000000000 +0300 @@ -41,6 +41,8 @@ jdk.security.jgss; exports sun.security.jgss.krb5 to jdk.security.auth; + exports sun.security.jgss.krb5.internal to + jdk.security.jgss; exports sun.security.krb5 to jdk.security.auth; exports sun.security.krb5.internal to --- old/src/java.security.jgss/share/classes/sun/security/jgss/krb5/InitialToken.java 2020-08-14 15:34:19.000000000 +0300 +++ new/src/java.security.jgss/share/classes/sun/security/jgss/krb5/InitialToken.java 2020-08-14 15:34:18.000000000 +0300 @@ -36,6 +36,7 @@ import java.util.Arrays; import sun.security.krb5.*; import sun.security.krb5.internal.Krb5; +import sun.security.jgss.krb5.internal.TlsChannelBindingImpl; abstract class InitialToken extends Krb5Token { @@ -57,6 +58,7 @@ private final byte[] CHECKSUM_FIRST_BYTES = {(byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00}; + private static final int CHANNEL_BINDING_AF_UNSPEC = 0; private static final int CHANNEL_BINDING_AF_INET = 2; private static final int CHANNEL_BINDING_AF_INET6 = 24; private static final int CHANNEL_BINDING_AF_NULL_ADDR = 255; @@ -333,8 +335,8 @@ } } - private int getAddrType(InetAddress addr) { - int addressType = CHANNEL_BINDING_AF_NULL_ADDR; + private int getAddrType(InetAddress addr, int defValue) { + int addressType = defValue; if (addr instanceof Inet4Address) addressType = CHANNEL_BINDING_AF_INET; @@ -344,7 +346,7 @@ } private byte[] getAddrBytes(InetAddress addr) throws GSSException { - int addressType = getAddrType(addr); + int addressType = getAddrType(addr, CHANNEL_BINDING_AF_NULL_ADDR); byte[] addressBytes = addr.getAddress(); if (addressBytes != null) { switch (addressType) { @@ -375,8 +377,16 @@ InetAddress acceptorAddress = channelBinding.getAcceptorAddress(); int size = 5*4; - int initiatorAddressType = getAddrType(initiatorAddress); - int acceptorAddressType = getAddrType(acceptorAddress); + // LDAP TLS Channel Binding requires CHANNEL_BINDING_AF_UNSPEC address type + // for unspecified initiator and acceptor addresses. + // CHANNEL_BINDING_AF_NULL_ADDR value should be used for unspecified address + // in all other cases. + int initiatorAddressType = getAddrType(initiatorAddress, + (channelBinding instanceof TlsChannelBindingImpl)? + CHANNEL_BINDING_AF_UNSPEC:CHANNEL_BINDING_AF_NULL_ADDR); + int acceptorAddressType = getAddrType(acceptorAddress, + (channelBinding instanceof TlsChannelBindingImpl)? + CHANNEL_BINDING_AF_UNSPEC:CHANNEL_BINDING_AF_NULL_ADDR); byte[] initiatorAddressBytes = null; if (initiatorAddress != null) { --- old/src/java.security.jgss/share/native/libj2gss/GSSLibStub.c 2020-08-14 15:34:20.000000000 +0300 +++ new/src/java.security.jgss/share/native/libj2gss/GSSLibStub.c 2020-08-14 15:34:19.000000000 +0300 @@ -34,6 +34,8 @@ const int TYPE_CRED_TIME = 11; const int TYPE_CRED_USAGE = 12; +static jclass tlsCBCl = NULL; + /* * Class: sun_security_jgss_wrapper_GSSLibStub * Method: init @@ -69,6 +71,17 @@ failed = loadNative(libName); (*env)->ReleaseStringUTFChars(env, jlibName, libName); + if (tlsCBCl == NULL) { + + /* initialize TLS Channel Binding class wrapper */ + jclass cl = (*env)->FindClass(env, + "sun/security/jgss/krb5/internal/TlsChannelBindingImpl"); + if (cl == NULL) { /* exception thrown */ + return JNI_FALSE; + } + tlsCBCl = (*env)->NewGlobalRef(env, cl); + } + if (!failed) { return JNI_TRUE; } else { @@ -154,11 +167,13 @@ if (cb == GSS_C_NO_CHANNEL_BINDINGS) return; /* release initiator address */ - if (cb->initiator_addrtype != GSS_C_AF_NULLADDR) { + if (cb->initiator_addrtype != GSS_C_AF_NULLADDR && + cb->initiator_addrtype != GSS_C_AF_UNSPEC) { resetGSSBuffer(&(cb->initiator_address)); } /* release acceptor address */ - if (cb->acceptor_addrtype != GSS_C_AF_NULLADDR) { + if (cb->acceptor_addrtype != GSS_C_AF_NULLADDR && + cb->acceptor_addrtype != GSS_C_AF_UNSPEC) { resetGSSBuffer(&(cb->acceptor_address)); } /* release application data */ @@ -189,9 +204,19 @@ } // initialize addrtype in CB first - cb->initiator_addrtype = GSS_C_AF_NULLADDR; - cb->acceptor_addrtype = GSS_C_AF_NULLADDR; - + // LDAP TLS Channel Binding requires GSS_C_AF_UNSPEC address type + // for unspecified initiator and acceptor addresses. + // GSS_C_AF_NULLADDR value should be used for unspecified address + // in all other cases. + + if ((*env)->IsInstanceOf(env, jcb, tlsCBCl)) { + // TLS Channel Binding requires unspecified addrtype=0 + cb->initiator_addrtype = GSS_C_AF_UNSPEC; + cb->acceptor_addrtype = GSS_C_AF_UNSPEC; + } else { + cb->initiator_addrtype = GSS_C_AF_NULLADDR; + cb->acceptor_addrtype = GSS_C_AF_NULLADDR; + } // addresses needs to be initialized to empty memset(&cb->initiator_address, 0, sizeof(cb->initiator_address)); memset(&cb->acceptor_address, 0, sizeof(cb->acceptor_address)); --- old/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Client.java 2020-08-14 15:34:20.000000000 +0300 +++ new/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Client.java 2020-08-14 15:34:20.000000000 +0300 @@ -35,6 +35,7 @@ import javax.security.auth.callback.CallbackHandler; // JGSS +import sun.security.jgss.krb5.internal.TlsChannelBindingImpl; import org.ietf.jgss.*; /** @@ -150,6 +151,14 @@ } secCtx.requestMutualAuth(mutual); + if (props != null) { + // TLS Channel Binding + byte[] tlsCB = (byte[])props.get("jdk.internal.sasl.tlschannelbinding"); + if (tlsCB != null) { + secCtx.setChannelBinding(new TlsChannelBindingImpl(tlsCB)); + } + } + // Always specify potential need for integrity and confidentiality // Decision will be made during final handshake secCtx.requestConf(true); --- /dev/null 2020-08-14 15:34:21.000000000 +0300 +++ new/src/java.naming/share/classes/com/sun/jndi/ldap/sasl/TlsChannelBinding.java 2020-08-14 15:34:21.000000000 +0300 @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2020, Azul Systems, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.sun.jndi.ldap.sasl; + +import javax.naming.NamingException; +import javax.security.sasl.SaslException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Hashtable; + +/** + * This class implements the Channel Binding for TLS as defined in + * + * Channel Bindings for TLS + * + * Format of the Channel Binding data is also defined in + * + * On the Use of Channel Bindings to Secure Channels + * section 2.1. + * + */ + +public class TlsChannelBinding { + + // TLS channel binding type property + public static final String CHANNEL_BINDING_TYPE = + "com.sun.jndi.ldap.tls.cbtype"; + + // internal TLS channel binding property + public static final String CHANNEL_BINDING = + "jdk.internal.sasl.tlschannelbinding"; + + public enum TlsChannelBindingType { + + /** + * Channel binding on the basis of TLS Finished message + */ + TLS_UNIQUE("tls-unique"), + /** + * Channel binding on the basis of TLS server certificate + */ + TLS_SERVER_END_POINT("tls-server-end-point"); + + public String getName() { + return name; + } + + final private String name; + TlsChannelBindingType(String name) { + this.name = name; + } + } + + /** + * Parse value of "com.sun.jndi.ldap.tls.cbtype" property + * @param cbType + * @throws NamingException + */ + public static TlsChannelBindingType parseType(String cbType) throws NamingException { + if (cbType != null) { + if (cbType.equals(TlsChannelBindingType.TLS_UNIQUE.getName())) { + throw new NamingException("Channel binding type " + + TlsChannelBindingType.TLS_UNIQUE.getName() + + " is not supported"); + } else if (cbType.equals(TlsChannelBindingType.TLS_SERVER_END_POINT.getName())) { + return TlsChannelBindingType.TLS_SERVER_END_POINT; + } else { + throw new NamingException("Illegal value for " + + CHANNEL_BINDING_TYPE + " property."); + } + } + return null; + } + + final private TlsChannelBindingType cbType; + final private byte[] cbData; + + /** + * Construct tls-unique Channel Binding data + * @param finishedMessage + * @throws SaslException + */ + public static TlsChannelBinding create(byte[] finishedMessage) throws SaslException { + throw new UnsupportedOperationException(TlsChannelBindingType.TLS_UNIQUE.getName() + + " channel binding is not supported"); + } + + /** + * Construct tls-server-end-point Channel Binding data + * @param serverCertificate + * @throws SaslException + */ + public static TlsChannelBinding create(X509Certificate serverCertificate) throws SaslException { + try { + final byte[] prefix = + TlsChannelBindingType.TLS_SERVER_END_POINT.getName().concat(":").getBytes(); + String hashAlg = serverCertificate.getSigAlgName(). + replace("SHA", "SHA-").toUpperCase(); + int ind = hashAlg.indexOf("WITH"); + if (ind > 0) { + hashAlg = hashAlg.substring(0, ind); + if (hashAlg.equals("MD5") || hashAlg.equals("SHA-1")) { + hashAlg = "SHA-256"; + } + } else { + hashAlg = "SHA-256"; + } + MessageDigest md = MessageDigest.getInstance(hashAlg); + byte[] hash = md.digest(serverCertificate.getEncoded()); + byte[] cbData = Arrays.copyOf(prefix, prefix.length + hash.length ); + System.arraycopy(hash, 0, cbData, prefix.length, hash.length); + return new TlsChannelBinding(TlsChannelBindingType.TLS_SERVER_END_POINT, cbData); + } catch (NoSuchAlgorithmException | CertificateEncodingException e) { + throw new SaslException("Cannot create TLS channel binding data", e); + } + } + + private TlsChannelBinding(TlsChannelBindingType cbType, byte[] cbData) { + this.cbType = cbType; + this.cbData = cbData; + } + + public TlsChannelBindingType getType() { + return cbType; + } + public byte[] getData() { + return cbData; + } +} --- /dev/null 2020-08-14 15:34:22.000000000 +0300 +++ new/src/java.security.jgss/share/classes/sun/security/jgss/krb5/internal/TlsChannelBindingImpl.java 2020-08-14 15:34:22.000000000 +0300 @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020, Azul Systems, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package sun.security.jgss.krb5.internal; + +import org.ietf.jgss.ChannelBinding; + +/** + * TLS Channel Binding wrapper class to determine internal + * tls channel binding implementation. + */ +public class TlsChannelBindingImpl extends ChannelBinding { + public TlsChannelBindingImpl(byte[] appData) { + super(appData); + } +} --- /dev/null 2020-08-14 15:34:23.000000000 +0300 +++ new/test/jdk/com/sun/jndi/ldap/LdapCBPropertiesTest.java 2020-08-14 15:34:23.000000000 +0300 @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2020, Azul Systems, Inc. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8245527 + * @library lib/ /test/lib + * @run main/othervm LdapCBPropertiesTest true true com.sun.jndi.ldap.tls.cbtype tls-server-end-point + * @run main/othervm LdapCBPropertiesTest false false com.sun.jndi.ldap.tls.cbtype tls-server-end-point + * @run main/othervm LdapCBPropertiesTest true true com.sun.jndi.ldap.tls.cbtype tls-server-end-point com.sun.jndi.ldap.connect.timeout 2000 + * @run main/othervm LdapCBPropertiesTest false false com.sun.jndi.ldap.tls.cbtype tls-server-end-point com.sun.jndi.ldap.connect.timeout 2000 + * @run main/othervm LdapCBPropertiesTest false true com.sun.jndi.ldap.tls.cbtype tls-unique + * @run main/othervm LdapCBPropertiesTest false true com.sun.jndi.ldap.tls.cbtype tls-unknown + * @run main/othervm LdapCBPropertiesTest false true jdk.internal.sasl.tlschannelbinding value + * @summary test new JNDI property to control the Channel Binding data + */ + +import javax.naming.AuthenticationException; +import javax.naming.CommunicationException; +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.DirContext; +import javax.naming.directory.InitialDirContext; +import java.net.InetAddress; +import java.net.URI; +import java.util.Hashtable; + +import org.ietf.jgss.GSSException; + +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLServerSocket; +import javax.net.ssl.SSLServerSocketFactory; +import javax.security.sasl.SaslException; + +import jdk.test.lib.net.URIBuilder; + +public class LdapCBPropertiesTest { + /* + * Where do we find the keystores? + */ + static String pathToStores = "../../../../javax/net/ssl/etc"; + static String keyStoreFile = "keystore"; + static String trustStoreFile = "truststore"; + static String passwd = "passphrase"; + + static boolean debug = false; + + public static void main(String[] args) throws Exception { + String keyFilename = + System.getProperty("test.src", "./") + "/" + pathToStores + + "/" + keyStoreFile; + String trustFilename = + System.getProperty("test.src", "./") + "/" + pathToStores + + "/" + trustStoreFile; + + System.setProperty("javax.net.ssl.keyStore", keyFilename); + System.setProperty("javax.net.ssl.keyStorePassword", passwd); + System.setProperty("javax.net.ssl.trustStore", trustFilename); + System.setProperty("javax.net.ssl.trustStorePassword", passwd); + + if (debug) + System.setProperty("javax.net.debug", "all"); + + /* + * Start the tests. + */ + new LdapCBPropertiesTest(args); + } + + /* + * Primary constructor, used to drive remainder of the test. + */ + LdapCBPropertiesTest(String[] args) throws Exception { + InetAddress loopback = InetAddress.getLoopbackAddress(); + SSLServerSocketFactory sslssf = + (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + SSLServerSocket sslServerSocket = + (SSLServerSocket) sslssf.createServerSocket(0, 0, loopback); + int serverPort = sslServerSocket.getLocalPort(); + + try (var ignore = new BaseLdapServer(sslServerSocket).start()) { + doClientSide(serverPort, args); + } + } + + /* + * Define the client side of the test. + * + * The server should start at this time already + */ + void doClientSide(int serverPort, String[] args) throws Exception { + boolean passed = false; + boolean shouldPass = Boolean.parseBoolean(args[0]); + boolean shouldConnect = Boolean.parseBoolean(args[1]); + // set disableEndpointIdentification to disable hostname verification + if (shouldConnect) { + System.setProperty( + "com.sun.jndi.ldap.object.disableEndpointIdentification", "true"); + } + + // Set up the environment for creating the initial context + Hashtable env = new Hashtable(); + URI uri = URIBuilder.newBuilder() + .scheme("ldaps") + .loopback() + .port(serverPort) + .build(); + env.put(Context.PROVIDER_URL, uri.toString()); + env.put(Context.INITIAL_CONTEXT_FACTORY, + "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI"); + + // read properties + for (int i = 2; i < args.length; i += 2) { + env.put(args[i], args[i + 1]); + if (debug) + System.out.println("Env=" + args[i] + "=" + args[i + 1]); + } + + try { + DirContext ctx = new InitialDirContext(env); + passed = shouldPass; + ctx.close(); + } catch (NamingException ne) { + // only NamingException is allowed + if (debug) + System.out.println("Exception=" + ne + " cause=" + ne.getRootCause()); + passed = handleNamingException(ne, shouldPass, shouldConnect); + } catch(Exception e) { + System.err.println("Failed: caught an unexpected Exception - " + e); + throw e; + } finally { + // test if internal property accessible to application + if(shouldPass && + env.get("jdk.internal.sasl.tlschannelbinding") != null) { + throw new Exception( + "Test FAILED: jdk.internal.sasl.tlschannelbinding should not be accessible"); + } + } + if (!passed) { + throw new Exception( + "Test FAILED: NamingException exception should be thrown"); + } + System.out.println("Test PASSED"); + } + + private static boolean handleNamingException(NamingException ne, boolean shouldPass, boolean shouldConnect) + throws NamingException { + if (ne instanceof AuthenticationException && + ne.getRootCause() instanceof SaslException) { + SaslException saslEx = (SaslException) ne.getRootCause(); + if (shouldConnect && saslEx.getCause() instanceof GSSException) { + // SSL connection successful, expected exception from SaslClient + if (shouldPass) + return true; + } + } + if (!shouldConnect) { + // SSL handshake fails + Exception ex = ne; + while(ex != null && !(ex instanceof CommunicationException)) { + ex = (Exception)ex.getCause(); + } + if (ex != null) { + if (ex.getCause() instanceof SSLException) { + if (!shouldPass) + return true; + } + } + } + if (!shouldPass && ne.getRootCause() == null) { + // Expected exception caused by Channel Binding parameter inconsistency + return true; + } + throw ne; + } +}