1 /* 2 * Copyright (c) 1999, 2011, Oracle and/or its affiliates. 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 26 package com.sun.jndi.ldap.sasl; 27 28 import java.io.*; 29 import java.security.cert.X509Certificate; 30 import java.util.Vector; 31 import java.util.Hashtable; 32 import java.util.StringTokenizer; 33 34 import javax.naming.AuthenticationException; 35 import javax.naming.AuthenticationNotSupportedException; 36 import javax.naming.NamingException; 37 38 import javax.naming.ldap.Control; 39 40 import javax.security.auth.callback.CallbackHandler; 41 import javax.security.sasl.*; 42 import com.sun.jndi.ldap.Connection; 43 import com.sun.jndi.ldap.LdapClient; 44 import com.sun.jndi.ldap.LdapResult; 45 import com.sun.jndi.ldap.sasl.TlsChannelBinding.TlsChannelBindingType; 46 47 /** 48 * Handles SASL support. 49 * 50 * @author Vincent Ryan 51 * @author Rosanna Lee 52 */ 53 54 final public class LdapSasl { 55 // SASL stuff 56 private static final String SASL_CALLBACK = "java.naming.security.sasl.callback"; 57 private static final String SASL_AUTHZ_ID = 58 "java.naming.security.sasl.authorizationId"; 59 private static final String SASL_REALM = 60 "java.naming.security.sasl.realm"; 61 62 private static final int LDAP_SUCCESS = 0; 63 private static final int LDAP_SASL_BIND_IN_PROGRESS = 14; // LDAPv3 64 65 private LdapSasl() { 66 } 67 68 /** 69 * Performs SASL bind. 70 * Creates a SaslClient by using a default CallbackHandler 71 * that uses the Context.SECURITY_PRINCIPAL and Context.SECURITY_CREDENTIALS 72 * properties to satisfy the callbacks, and by using the 73 * SASL_AUTHZ_ID property as the authorization id. If the SASL_AUTHZ_ID 74 * property has not been set, Context.SECURITY_PRINCIPAL is used. 75 * If SASL_CALLBACK has been set, use that instead of the default 76 * CallbackHandler. 77 * <p> 78 * If bind is successful and the selected SASL mechanism has a security 79 * layer, set inStream and outStream to be filter streams that use 80 * the security layer. These will be used for subsequent communication 81 * with the server. 82 * 83 * @param conn The non-null connection to use for sending an LDAP BIND 84 * @param server Non-null string name of host to connect to 85 * @param dn Non-null DN to bind as; also used as authentication ID 86 * @param pw Possibly null password; can be byte[], char[] or String 87 * @param authMech A non-null space-separated list of SASL authentication 88 * mechanisms. 89 * @param env The possibly null environment of the context, possibly containing 90 * properties for used by SASL mechanisms 91 * @param bindCtls The possibly null controls to accompany the bind 92 * @return LdapResult containing status of the bind 93 */ 94 @SuppressWarnings("unchecked") 95 public static LdapResult saslBind(LdapClient clnt, Connection conn, 96 String server, String dn, Object pw, 97 String authMech, Hashtable<?,?> env, Control[] bindCtls) 98 throws IOException, NamingException { 99 100 SaslClient saslClnt = null; 101 boolean cleanupHandler = false; 102 103 // Use supplied callback handler or create default 104 CallbackHandler cbh = 105 (env != null) ? (CallbackHandler)env.get(SASL_CALLBACK) : null; 106 if (cbh == null) { 107 cbh = new DefaultCallbackHandler(dn, pw, (String)env.get(SASL_REALM)); 108 cleanupHandler = true; 109 } 110 111 // Prepare parameters for creating SASL client 112 String authzId = (env != null) ? (String)env.get(SASL_AUTHZ_ID) : null; 113 String[] mechs = getSaslMechanismNames(authMech); 114 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 123 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 144 // Create SASL client to use using SASL package 145 saslClnt = Sasl.createSaslClient( 146 mechs, authzId, "ldap", server, envProps, cbh); 147 148 if (saslClnt == null) { 149 throw new AuthenticationNotSupportedException(authMech); 150 } 151 152 LdapResult res; 153 String mechName = saslClnt.getMechanismName(); 154 byte[] response = saslClnt.hasInitialResponse() ? 155 saslClnt.evaluateChallenge(NO_BYTES) : null; 156 157 res = clnt.ldapBind(null, response, bindCtls, mechName, true); 158 159 while (!saslClnt.isComplete() && 160 (res.status == LDAP_SASL_BIND_IN_PROGRESS || 161 res.status == LDAP_SUCCESS)) { 162 163 response = saslClnt.evaluateChallenge( 164 res.serverCreds != null? res.serverCreds : NO_BYTES); 165 if (res.status == LDAP_SUCCESS) { 166 if (response != null) { 167 throw new AuthenticationException( 168 "SASL client generated response after success"); 169 } 170 break; 171 } 172 res = clnt.ldapBind(null, response, bindCtls, mechName, true); 173 } 174 175 if (res.status == LDAP_SUCCESS) { 176 if (!saslClnt.isComplete()) { 177 throw new AuthenticationException( 178 "SASL authentication not complete despite server claims"); 179 } 180 181 String qop = (String) saslClnt.getNegotiatedProperty(Sasl.QOP); 182 183 // If negotiated integrity or privacy, 184 if (qop != null && (qop.equalsIgnoreCase("auth-int") 185 || qop.equalsIgnoreCase("auth-conf"))) { 186 187 InputStream newIn = new SaslInputStream(saslClnt, 188 conn.inStream); 189 OutputStream newOut = new SaslOutputStream(saslClnt, 190 conn.outStream); 191 192 conn.replaceStreams(newIn, newOut); 193 } else { 194 saslClnt.dispose(); 195 } 196 } 197 return res; 198 } catch (SaslException e) { 199 NamingException ne = new AuthenticationException( 200 authMech); 201 ne.setRootCause(e); 202 throw ne; 203 } finally { 204 if (cleanupHandler) { 205 ((DefaultCallbackHandler)cbh).clearPassword(); 206 } 207 } 208 } 209 210 /** 211 * Returns an array of SASL mechanisms given a string of space 212 * separated SASL mechanism names. 213 * @param The non-null string containing the mechanism names 214 * @return A non-null array of String; each element of the array 215 * contains a single mechanism name. 216 */ 217 private static String[] getSaslMechanismNames(String str) { 218 StringTokenizer parser = new StringTokenizer(str); 219 Vector<String> mechs = new Vector<>(10); 220 while (parser.hasMoreTokens()) { 221 mechs.addElement(parser.nextToken()); 222 } 223 String[] mechNames = new String[mechs.size()]; 224 for (int i = 0; i < mechs.size(); i++) { 225 mechNames[i] = mechs.elementAt(i); 226 } 227 return mechNames; 228 } 229 230 private static final byte[] NO_BYTES = new byte[0]; 231 }