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

Commit 97ba018

Browse files
Alexey BakhtinYuri Nesterenko
authored andcommitted
8245527: LDAP Channel Binding support for Java GSS/Kerberos
Backport-of: cfa3f7493149170f2b23a516bc95110dab43fd06
1 parent 384445d commit 97ba018

File tree

10 files changed

+543
-12
lines changed

10 files changed

+543
-12
lines changed

src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,17 @@
4646
import java.lang.reflect.InvocationTargetException;
4747
import java.security.AccessController;
4848
import java.security.PrivilegedAction;
49+
import java.security.cert.Certificate;
50+
import java.security.cert.X509Certificate;
4951
import java.util.Arrays;
52+
import java.util.concurrent.CompletableFuture;
53+
import java.util.concurrent.ExecutionException;
5054
import javax.net.SocketFactory;
5155
import javax.net.ssl.SSLParameters;
56+
import javax.net.ssl.HandshakeCompletedEvent;
57+
import javax.net.ssl.HandshakeCompletedListener;
58+
import javax.net.ssl.SSLPeerUnverifiedException;
59+
import javax.security.sasl.SaslException;
5260

5361
/**
5462
* A thread that creates a connection to an LDAP server.
@@ -109,7 +117,7 @@
109117
* @author Rosanna Lee
110118
* @author Jagane Sundar
111119
*/
112-
public final class Connection implements Runnable {
120+
public final class Connection implements Runnable, HandshakeCompletedListener {
113121

114122
private static final boolean debug = false;
115123
private static final int dump = 0; // > 0 r, > 1 rw
@@ -349,6 +357,7 @@ private Socket createSocket(String host, int port, String socketFactory,
349357
param.setEndpointIdentificationAlgorithm("LDAPS");
350358
sslSocket.setSSLParameters(param);
351359
}
360+
sslSocket.addHandshakeCompletedListener(this);
352361
if (connectTimeout > 0) {
353362
int socketTimeout = sslSocket.getSoTimeout();
354363
sslSocket.setSoTimeout(connectTimeout); // reuse full timeout value
@@ -644,6 +653,15 @@ void cleanup(Control[] reqCtls, boolean notifyParent) {
644653
ldr = ldr.next;
645654
}
646655
}
656+
if (isTlsConnection()) {
657+
if (closureReason != null) {
658+
CommunicationException ce = new CommunicationException();
659+
ce.setRootCause(closureReason);
660+
tlsHandshakeCompleted.completeExceptionally(ce);
661+
} else {
662+
tlsHandshakeCompleted.cancel(false);
663+
}
664+
}
647665
sock = null;
648666
}
649667
nparent = notifyParent;
@@ -1008,4 +1026,46 @@ private static byte[] readFully(InputStream is, int length)
10081026
}
10091027
return buf;
10101028
}
1029+
1030+
private final CompletableFuture<X509Certificate> tlsHandshakeCompleted =
1031+
new CompletableFuture<>();
1032+
1033+
@Override
1034+
public void handshakeCompleted(HandshakeCompletedEvent event) {
1035+
try {
1036+
X509Certificate tlsServerCert = null;
1037+
Certificate[] certs;
1038+
if (event.getSocket().getUseClientMode()) {
1039+
certs = event.getPeerCertificates();
1040+
} else {
1041+
certs = event.getLocalCertificates();
1042+
}
1043+
if (certs != null && certs.length > 0 &&
1044+
certs[0] instanceof X509Certificate) {
1045+
tlsServerCert = (X509Certificate) certs[0];
1046+
}
1047+
tlsHandshakeCompleted.complete(tlsServerCert);
1048+
} catch (SSLPeerUnverifiedException ex) {
1049+
CommunicationException ce = new CommunicationException();
1050+
ce.setRootCause(closureReason);
1051+
tlsHandshakeCompleted.completeExceptionally(ex);
1052+
}
1053+
}
1054+
1055+
public boolean isTlsConnection() {
1056+
return sock instanceof SSLSocket;
1057+
}
1058+
1059+
public X509Certificate getTlsServerCertificate()
1060+
throws SaslException {
1061+
try {
1062+
if (isTlsConnection())
1063+
return tlsHandshakeCompleted.get();
1064+
} catch (InterruptedException iex) {
1065+
throw new SaslException("TLS Handshake Exception ", iex);
1066+
} catch (ExecutionException eex) {
1067+
throw new SaslException("TLS Handshake Exception ", eex.getCause());
1068+
}
1069+
return null;
1070+
}
10111071
}

src/java.naming/share/classes/com/sun/jndi/ldap/sasl/LdapSasl.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
package com.sun.jndi.ldap.sasl;
2727

2828
import java.io.*;
29+
import java.security.cert.X509Certificate;
2930
import java.util.Vector;
3031
import java.util.Hashtable;
3132
import java.util.StringTokenizer;
@@ -41,6 +42,7 @@
4142
import com.sun.jndi.ldap.Connection;
4243
import com.sun.jndi.ldap.LdapClient;
4344
import com.sun.jndi.ldap.LdapResult;
45+
import com.sun.jndi.ldap.sasl.TlsChannelBinding.TlsChannelBindingType;
4446

4547
/**
4648
* Handles SASL support.
@@ -110,10 +112,38 @@ public static LdapResult saslBind(LdapClient clnt, Connection conn,
110112
String authzId = (env != null) ? (String)env.get(SASL_AUTHZ_ID) : null;
111113
String[] mechs = getSaslMechanismNames(authMech);
112114

115+
// Internal TLS Channel Binding property cannot be set explicitly
116+
if (env.get(TlsChannelBinding.CHANNEL_BINDING) != null) {
117+
throw new NamingException(TlsChannelBinding.CHANNEL_BINDING +
118+
" property cannot be set explicitly");
119+
}
120+
121+
Hashtable<String, Object> envProps = (Hashtable<String, Object>) env;
122+
113123
try {
124+
// Prepare TLS Channel Binding data
125+
if (conn.isTlsConnection()) {
126+
TlsChannelBindingType cbType =
127+
TlsChannelBinding.parseType(
128+
(String)env.get(TlsChannelBinding.CHANNEL_BINDING_TYPE));
129+
if (cbType == TlsChannelBindingType.TLS_SERVER_END_POINT) {
130+
// set tls-server-end-point channel binding
131+
X509Certificate cert = conn.getTlsServerCertificate();
132+
if (cert != null) {
133+
TlsChannelBinding tlsCB =
134+
TlsChannelBinding.create(cert);
135+
envProps = (Hashtable<String, Object>) env.clone();
136+
envProps.put(TlsChannelBinding.CHANNEL_BINDING, tlsCB.getData());
137+
} else {
138+
throw new SaslException("No suitable certificate to generate " +
139+
"TLS Channel Binding data");
140+
}
141+
}
142+
}
143+
114144
// Create SASL client to use using SASL package
115145
saslClnt = Sasl.createSaslClient(
116-
mechs, authzId, "ldap", server, (Hashtable<String, ?>)env, cbh);
146+
mechs, authzId, "ldap", server, envProps, cbh);
117147

118148
if (saslClnt == null) {
119149
throw new AuthenticationNotSupportedException(authMech);
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright (c) 2020, Azul Systems, Inc. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
package com.sun.jndi.ldap.sasl;
26+
27+
import javax.naming.NamingException;
28+
import javax.security.sasl.SaslException;
29+
import java.security.MessageDigest;
30+
import java.security.NoSuchAlgorithmException;
31+
import java.security.cert.CertificateEncodingException;
32+
import java.security.cert.X509Certificate;
33+
import java.util.Arrays;
34+
import java.util.Hashtable;
35+
36+
/**
37+
* This class implements the Channel Binding for TLS as defined in
38+
* <a href="https://www.ietf.org/rfc/rfc5929.txt">
39+
* Channel Bindings for TLS</a>
40+
*
41+
* Format of the Channel Binding data is also defined in
42+
* <a href="https://www.ietf.org/rfc/rfc5056.txt">
43+
* On the Use of Channel Bindings to Secure Channels</a>
44+
* section 2.1.
45+
*
46+
*/
47+
48+
public class TlsChannelBinding {
49+
50+
// TLS channel binding type property
51+
public static final String CHANNEL_BINDING_TYPE =
52+
"com.sun.jndi.ldap.tls.cbtype";
53+
54+
// internal TLS channel binding property
55+
public static final String CHANNEL_BINDING =
56+
"jdk.internal.sasl.tlschannelbinding";
57+
58+
public enum TlsChannelBindingType {
59+
60+
/**
61+
* Channel binding on the basis of TLS Finished message.
62+
* TLS_UNIQUE is defined by RFC 5929 but is not supported
63+
* by the current LDAP stack.
64+
*/
65+
TLS_UNIQUE("tls-unique"),
66+
67+
/**
68+
* Channel binding on the basis of TLS server certificate.
69+
*/
70+
TLS_SERVER_END_POINT("tls-server-end-point");
71+
72+
public String getName() {
73+
return name;
74+
}
75+
76+
final private String name;
77+
TlsChannelBindingType(String name) {
78+
this.name = name;
79+
}
80+
}
81+
82+
/**
83+
* Parse value of "com.sun.jndi.ldap.tls.cbtype" property
84+
* @param cbType
85+
* @return TLS Channel Binding type or null if
86+
* "com.sun.jndi.ldap.tls.cbtype" property has not been set.
87+
* @throws NamingException
88+
*/
89+
public static TlsChannelBindingType parseType(String cbType) throws NamingException {
90+
if (cbType != null) {
91+
if (cbType.equals(TlsChannelBindingType.TLS_SERVER_END_POINT.getName())) {
92+
return TlsChannelBindingType.TLS_SERVER_END_POINT;
93+
} else {
94+
throw new NamingException("Illegal value for " +
95+
CHANNEL_BINDING_TYPE + " property.");
96+
}
97+
}
98+
return null;
99+
}
100+
101+
final private TlsChannelBindingType cbType;
102+
final private byte[] cbData;
103+
104+
/**
105+
* Construct tls-server-end-point Channel Binding data
106+
* @param serverCertificate
107+
* @throws SaslException
108+
*/
109+
public static TlsChannelBinding create(X509Certificate serverCertificate) throws SaslException {
110+
try {
111+
final byte[] prefix =
112+
TlsChannelBindingType.TLS_SERVER_END_POINT.getName().concat(":").getBytes();
113+
String hashAlg = serverCertificate.getSigAlgName().
114+
replace("SHA", "SHA-").toUpperCase();
115+
int ind = hashAlg.indexOf("WITH");
116+
if (ind > 0) {
117+
hashAlg = hashAlg.substring(0, ind);
118+
if (hashAlg.equals("MD5") || hashAlg.equals("SHA-1")) {
119+
hashAlg = "SHA-256";
120+
}
121+
} else {
122+
hashAlg = "SHA-256";
123+
}
124+
MessageDigest md = MessageDigest.getInstance(hashAlg);
125+
byte[] hash = md.digest(serverCertificate.getEncoded());
126+
byte[] cbData = Arrays.copyOf(prefix, prefix.length + hash.length );
127+
System.arraycopy(hash, 0, cbData, prefix.length, hash.length);
128+
return new TlsChannelBinding(TlsChannelBindingType.TLS_SERVER_END_POINT, cbData);
129+
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
130+
throw new SaslException("Cannot create TLS channel binding data", e);
131+
}
132+
}
133+
134+
private TlsChannelBinding(TlsChannelBindingType cbType, byte[] cbData) {
135+
this.cbType = cbType;
136+
this.cbData = cbData;
137+
}
138+
139+
public TlsChannelBindingType getType() {
140+
return cbType;
141+
}
142+
143+
public byte[] getData() {
144+
return cbData;
145+
}
146+
}

src/java.naming/share/classes/module-info.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,20 @@
5959
* <br>If this property is not specified, the default is to wait
6060
* for the response until it is received.
6161
* </li>
62+
* <li>{@code com.sun.jndi.ldap.tls.cbtype}:
63+
* <br>The value of this property is the string representing the TLS
64+
* Channel Binding type required for an LDAP connection over SSL/TLS.
65+
* Possible value is :
66+
* <ul>
67+
* <li>"tls-server-end-point" - Channel Binding data is created on
68+
* the basis of the TLS server certificate.
69+
* </li>
70+
* </ul>
71+
* <br>"tls-unique" TLS Channel Binding type is specified in RFC-5929
72+
* but not supported.
73+
* <br>If this property is not specified, the client does not send
74+
* channel binding information to the server.
75+
* </li>
6276
* </ul>
6377
*
6478
* @provides javax.naming.ldap.spi.LdapDnsProvider

src/java.security.jgss/share/classes/module-info.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
jdk.security.jgss;
4242
exports sun.security.jgss.krb5 to
4343
jdk.security.auth;
44+
exports sun.security.jgss.krb5.internal to
45+
jdk.security.jgss;
4446
exports sun.security.krb5 to
4547
jdk.security.auth;
4648
exports sun.security.krb5.internal to

src/java.security.jgss/share/classes/sun/security/jgss/krb5/InitialToken.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import java.util.Arrays;
3737
import sun.security.krb5.*;
3838
import sun.security.krb5.internal.Krb5;
39+
import sun.security.jgss.krb5.internal.TlsChannelBindingImpl;
3940

4041
abstract class InitialToken extends Krb5Token {
4142

@@ -57,6 +58,7 @@ abstract class InitialToken extends Krb5Token {
5758
private final byte[] CHECKSUM_FIRST_BYTES =
5859
{(byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00};
5960

61+
private static final int CHANNEL_BINDING_AF_UNSPEC = 0;
6062
private static final int CHANNEL_BINDING_AF_INET = 2;
6163
private static final int CHANNEL_BINDING_AF_INET6 = 24;
6264
private static final int CHANNEL_BINDING_AF_NULL_ADDR = 255;
@@ -333,8 +335,8 @@ public void setContextFlags(Krb5Context context) {
333335
}
334336
}
335337

336-
private int getAddrType(InetAddress addr) {
337-
int addressType = CHANNEL_BINDING_AF_NULL_ADDR;
338+
private int getAddrType(InetAddress addr, int defValue) {
339+
int addressType = defValue;
338340

339341
if (addr instanceof Inet4Address)
340342
addressType = CHANNEL_BINDING_AF_INET;
@@ -344,7 +346,7 @@ else if (addr instanceof Inet6Address)
344346
}
345347

346348
private byte[] getAddrBytes(InetAddress addr) throws GSSException {
347-
int addressType = getAddrType(addr);
349+
int addressType = getAddrType(addr, CHANNEL_BINDING_AF_NULL_ADDR);
348350
byte[] addressBytes = addr.getAddress();
349351
if (addressBytes != null) {
350352
switch (addressType) {
@@ -375,8 +377,16 @@ private byte[] computeChannelBinding(ChannelBinding channelBinding)
375377
InetAddress acceptorAddress = channelBinding.getAcceptorAddress();
376378
int size = 5*4;
377379

378-
int initiatorAddressType = getAddrType(initiatorAddress);
379-
int acceptorAddressType = getAddrType(acceptorAddress);
380+
// LDAP TLS Channel Binding requires CHANNEL_BINDING_AF_UNSPEC address type
381+
// for unspecified initiator and acceptor addresses.
382+
// CHANNEL_BINDING_AF_NULL_ADDR value should be used for unspecified address
383+
// in all other cases.
384+
int initiatorAddressType = getAddrType(initiatorAddress,
385+
(channelBinding instanceof TlsChannelBindingImpl) ?
386+
CHANNEL_BINDING_AF_UNSPEC : CHANNEL_BINDING_AF_NULL_ADDR);
387+
int acceptorAddressType = getAddrType(acceptorAddress,
388+
(channelBinding instanceof TlsChannelBindingImpl) ?
389+
CHANNEL_BINDING_AF_UNSPEC : CHANNEL_BINDING_AF_NULL_ADDR);
380390

381391
byte[] initiatorAddressBytes = null;
382392
if (initiatorAddress != null) {

0 commit comments

Comments
 (0)