Skip to content
This repository has been archived by the owner on Feb 2, 2023. It is now read-only.

Commit

Permalink
8245527: LDAP Channel Binding support for Java GSS/Kerberos
Browse files Browse the repository at this point in the history
Backport-of: cfa3f7493149170f2b23a516bc95110dab43fd06
  • Loading branch information
Yuri Nesterenko committed Jul 12, 2022
1 parent 148dbd4 commit 2a32692
Show file tree
Hide file tree
Showing 10 changed files with 543 additions and 12 deletions.
62 changes: 61 additions & 1 deletion src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -349,6 +357,7 @@ private Socket createSocket(String host, int port, String socketFactory,
param.setEndpointIdentificationAlgorithm("LDAPS");
sslSocket.setSSLParameters(param);
}
sslSocket.addHandshakeCompletedListener(this);
if (connectTimeout > 0) {
int socketTimeout = sslSocket.getSoTimeout();
sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value
Expand Down Expand Up @@ -644,6 +653,15 @@ void cleanup(Control[] reqCtls, boolean notifyParent) {
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;
Expand Down Expand Up @@ -1008,4 +1026,46 @@ private static byte[] readFully(InputStream is, int length)
}
return buf;
}

private final CompletableFuture<X509Certificate> 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;
}
}
Expand Up @@ -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;
Expand All @@ -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.
Expand Down Expand Up @@ -110,10 +112,38 @@ public static LdapResult saslBind(LdapClient clnt, Connection conn,
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<String, Object> envProps = (Hashtable<String, Object>) 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<String, Object>) 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<String, ?>)env, cbh);
mechs, authzId, "ldap", server, envProps, cbh);

if (saslClnt == null) {
throw new AuthenticationNotSupportedException(authMech);
Expand Down
@@ -0,0 +1,146 @@
/*
* 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
* <a href="https://www.ietf.org/rfc/rfc5929.txt">
* Channel Bindings for TLS</a>
*
* Format of the Channel Binding data is also defined in
* <a href="https://www.ietf.org/rfc/rfc5056.txt">
* On the Use of Channel Bindings to Secure Channels</a>
* 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 is defined by RFC 5929 but is not supported
* by the current LDAP stack.
*/
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
* @return TLS Channel Binding type or null if
* "com.sun.jndi.ldap.tls.cbtype" property has not been set.
* @throws NamingException
*/
public static TlsChannelBindingType parseType(String cbType) throws NamingException {
if (cbType != null) {
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-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;
}
}
14 changes: 14 additions & 0 deletions src/java.naming/share/classes/module-info.java
Expand Up @@ -59,6 +59,20 @@
* <br>If this property is not specified, the default is to wait
* for the response until it is received.
* </li>
* <li>{@code com.sun.jndi.ldap.tls.cbtype}:
* <br>The value of this property is the string representing the TLS
* Channel Binding type required for an LDAP connection over SSL/TLS.
* Possible value is :
* <ul>
* <li>"tls-server-end-point" - Channel Binding data is created on
* the basis of the TLS server certificate.
* </li>
* </ul>
* <br>"tls-unique" TLS Channel Binding type is specified in RFC-5929
* but not supported.
* <br>If this property is not specified, the client does not send
* channel binding information to the server.
* </li>
* </ul>
*
* @provides javax.naming.ldap.spi.LdapDnsProvider
Expand Down
2 changes: 2 additions & 0 deletions src/java.security.jgss/share/classes/module-info.java
Expand Up @@ -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
Expand Down
Expand Up @@ -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 {

Expand All @@ -57,6 +58,7 @@ abstract class InitialToken extends Krb5Token {
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;
Expand Down Expand Up @@ -333,8 +335,8 @@ public void setContextFlags(Krb5Context context) {
}
}

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;
Expand All @@ -344,7 +346,7 @@ else if (addr instanceof Inet6Address)
}

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) {
Expand Down Expand Up @@ -375,8 +377,16 @@ private byte[] computeChannelBinding(ChannelBinding channelBinding)
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) {
Expand Down

1 comment on commit 2a32692

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.