1 /* 2 * Copyright (c) 2000, 2006, 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.security.sasl.gsskerb; 27 28 import java.io.IOException; 29 import java.util.Map; 30 import java.util.logging.Logger; 31 import java.util.logging.Level; 32 import javax.security.sasl.*; 33 34 // JAAS 35 import javax.security.auth.callback.CallbackHandler; 36 37 // JGSS 38 import org.ietf.jgss.*; 39 40 /** 41 * Implements the GSSAPI SASL client mechanism for Kerberos V5. 42 * (<A HREF="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</A>, 43 * <a HREF="http://www.ietf.org/internet-drafts/draft-ietf-cat-sasl-gssapi-04.txt">draft-ietf-cat-sasl-gssapi-04.txt</a>). 44 * It uses the Java Bindings for GSSAPI 45 * (<A HREF="http://www.ietf.org/rfc/rfc2853.txt">RFC 2853</A>) 46 * for getting GSSAPI/Kerberos V5 support. 47 * 48 * The client/server interactions are: 49 * C0: bind (GSSAPI, initial response) 50 * S0: sasl-bind-in-progress, challenge 1 (output of accept_sec_context or []) 51 * C1: bind (GSSAPI, response 1 (output of init_sec_context or [])) 52 * S1: sasl-bind-in-progress challenge 2 (security layer, server max recv size) 53 * C2: bind (GSSAPI, response 2 (security layer, client max recv size, authzid)) 54 * S2: bind success response 55 * 56 * Expects the client's credentials to be supplied from the 57 * javax.security.sasl.credentials property or from the thread's Subject. 58 * Otherwise the underlying KRB5 mech will attempt to acquire Kerberos creds 59 * by logging into Kerberos (via default TextCallbackHandler). 60 * These creds will be used for exchange with server. 61 * 62 * Required callbacks: none. 63 * 64 * Environment properties that affect behavior of implementation: 65 * 66 * javax.security.sasl.qop 67 * - quality of protection; list of auth, auth-int, auth-conf; default is "auth" 68 * javax.security.sasl.maxbuf 69 * - max receive buffer size; default is 65536 70 * javax.security.sasl.sendmaxbuffer 71 * - max send buffer size; default is 65536; (min with server max recv size) 72 * 73 * javax.security.sasl.server.authentication 74 * - "true" means require mutual authentication; default is "false" 75 * 76 * javax.security.sasl.credentials 77 * - an {@link org.ietf.jgss.GSSCredential} used for delegated authentication. 78 * 79 * @author Rosanna Lee 80 */ 81 82 final class GssKrb5Client extends GssKrb5Base implements SaslClient { 83 // ---------------- Constants ----------------- 84 private static final String MY_CLASS_NAME = GssKrb5Client.class.getName(); 85 86 private boolean finalHandshake = false; 87 private boolean mutual = false; // default false 88 private byte[] authzID; 89 90 /** 91 * Creates a SASL mechanism with client credentials that it needs 92 * to participate in GSS-API/Kerberos v5 authentication exchange 93 * with the server. 94 */ 95 GssKrb5Client(String authzID, String protocol, String serverName, 96 Map props, CallbackHandler cbh) throws SaslException { 97 98 super(props, MY_CLASS_NAME); 99 100 String service = protocol + "@" + serverName; 101 logger.log(Level.FINE, "KRB5CLNT01:Requesting service name: {0}", 102 service); 103 104 try { 105 GSSManager mgr = GSSManager.getInstance(); 106 107 // Create the name for the requested service entity for Krb5 mech 108 GSSName acceptorName = mgr.createName(service, 109 GSSName.NT_HOSTBASED_SERVICE, KRB5_OID); 110 111 // Parse properties to check for supplied credentials 112 GSSCredential credentials = null; 113 if (props != null) { 114 Object prop = props.get(Sasl.CREDENTIALS); 115 if (prop != null && prop instanceof GSSCredential) { 116 credentials = (GSSCredential) prop; 117 logger.log(Level.FINE, 118 "KRB5CLNT01:Using the credentials supplied in " + 119 "javax.security.sasl.credentials"); 120 } 121 } 122 123 // Create a context using credentials for Krb5 mech 124 secCtx = mgr.createContext(acceptorName, 125 KRB5_OID, /* mechanism */ 126 credentials, /* credentials */ 127 GSSContext.INDEFINITE_LIFETIME); 128 129 // Request credential delegation when credentials have been supplied 130 if (credentials != null) { 131 secCtx.requestCredDeleg(true); 132 } 133 134 // Parse properties to set desired context options 135 if (props != null) { 136 // Mutual authentication 137 String prop = (String)props.get(Sasl.SERVER_AUTH); 138 if (prop != null) { 139 mutual = "true".equalsIgnoreCase(prop); 140 } 141 } 142 secCtx.requestMutualAuth(mutual); 143 144 // Always specify potential need for integrity and confidentiality 145 // Decision will be made during final handshake 146 secCtx.requestConf(true); 147 secCtx.requestInteg(true); 148 149 } catch (GSSException e) { 150 throw new SaslException("Failure to initialize security context", e); 151 } 152 153 if (authzID != null && authzID.length() > 0) { 154 try { 155 this.authzID = authzID.getBytes("UTF8"); 156 } catch (IOException e) { 157 throw new SaslException("Cannot encode authorization ID", e); 158 } 159 } 160 } 161 162 public boolean hasInitialResponse() { 163 return true; 164 } 165 166 /** 167 * Processes the challenge data. 168 * 169 * The server sends a challenge data using which the client must 170 * process using GSS_Init_sec_context. 171 * As per RFC 2222, when GSS_S_COMPLETE is returned, we do 172 * an extra handshake to determine the negotiated security protection 173 * and buffer sizes. 174 * 175 * @param challengeData A non-null byte array containing the 176 * challenge data from the server. 177 * @return A non-null byte array containing the response to be 178 * sent to the server. 179 */ 180 public byte[] evaluateChallenge(byte[] challengeData) throws SaslException { 181 if (completed) { 182 throw new IllegalStateException( 183 "GSSAPI authentication already complete"); 184 } 185 186 if (finalHandshake) { 187 return doFinalHandshake(challengeData); 188 } else { 189 190 // Security context not established yet; continue with init 191 192 try { 193 byte[] gssOutToken = secCtx.initSecContext(challengeData, 194 0, challengeData.length); 195 if (logger.isLoggable(Level.FINER)) { 196 traceOutput(MY_CLASS_NAME, "evaluteChallenge", 197 "KRB5CLNT02:Challenge: [raw]", challengeData); 198 traceOutput(MY_CLASS_NAME, "evaluateChallenge", 199 "KRB5CLNT03:Response: [after initSecCtx]", gssOutToken); 200 } 201 202 if (secCtx.isEstablished()) { 203 finalHandshake = true; 204 if (gssOutToken == null) { 205 // RFC 2222 7.2.1: Client responds with no data 206 return EMPTY; 207 } 208 } 209 210 return gssOutToken; 211 } catch (GSSException e) { 212 throw new SaslException("GSS initiate failed", e); 213 } 214 } 215 } 216 217 private byte[] doFinalHandshake(byte[] challengeData) throws SaslException { 218 try { 219 // Security context already established. challengeData 220 // should contain security layers and server's maximum buffer size 221 222 if (logger.isLoggable(Level.FINER)) { 223 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 224 "KRB5CLNT04:Challenge [raw]:", challengeData); 225 } 226 227 if (challengeData.length == 0) { 228 // Received S0, should return [] 229 return EMPTY; 230 } 231 232 // Received S1 (security layer, server max recv size) 233 234 byte[] gssOutToken = secCtx.unwrap(challengeData, 0, 235 challengeData.length, new MessageProp(0, false)); 236 237 // First octet is a bit-mask specifying the protections 238 // supported by the server 239 if (logger.isLoggable(Level.FINE)) { 240 if (logger.isLoggable(Level.FINER)) { 241 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 242 "KRB5CLNT05:Challenge [unwrapped]:", gssOutToken); 243 } 244 logger.log(Level.FINE, "KRB5CLNT06:Server protections: {0}", 245 new Byte(gssOutToken[0])); 246 } 247 248 // Client selects preferred protection 249 // qop is ordered list of qop values 250 byte selectedQop = findPreferredMask(gssOutToken[0], qop); 251 if (selectedQop == 0) { 252 throw new SaslException( 253 "No common protection layer between client and server"); 254 } 255 256 if ((selectedQop&PRIVACY_PROTECTION) != 0) { 257 privacy = true; 258 integrity = true; 259 } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) { 260 integrity = true; 261 } 262 263 // 2nd-4th octets specifies maximum buffer size expected by 264 // server (in network byte order) 265 int srvMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3); 266 267 // Determine the max send buffer size based on what the 268 // server is able to receive and our specified max 269 sendMaxBufSize = (sendMaxBufSize == 0) ? srvMaxBufSize : 270 Math.min(sendMaxBufSize, srvMaxBufSize); 271 272 // Update context to limit size of returned buffer 273 rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy, 274 sendMaxBufSize); 275 276 if (logger.isLoggable(Level.FINE)) { 277 logger.log(Level.FINE, 278 "KRB5CLNT07:Client max recv size: {0}; server max recv size: {1}; rawSendSize: {2}", 279 new Object[] {new Integer(recvMaxBufSize), 280 new Integer(srvMaxBufSize), 281 new Integer(rawSendSize)}); 282 } 283 284 // Construct negotiated security layers and client's max 285 // receive buffer size and authzID 286 int len = 4; 287 if (authzID != null) { 288 len += authzID.length; 289 } 290 291 byte[] gssInToken = new byte[len]; 292 gssInToken[0] = selectedQop; 293 294 if (logger.isLoggable(Level.FINE)) { 295 logger.log(Level.FINE, 296 "KRB5CLNT08:Selected protection: {0}; privacy: {1}; integrity: {2}", 297 new Object[]{new Byte(selectedQop), 298 Boolean.valueOf(privacy), 299 Boolean.valueOf(integrity)}); 300 } 301 302 intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3); 303 if (authzID != null) { 304 // copy authorization id 305 System.arraycopy(authzID, 0, gssInToken, 4, authzID.length); 306 logger.log(Level.FINE, "KRB5CLNT09:Authzid: {0}", authzID); 307 } 308 309 if (logger.isLoggable(Level.FINER)) { 310 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 311 "KRB5CLNT10:Response [raw]", gssInToken); 312 } 313 314 gssOutToken = secCtx.wrap(gssInToken, 315 0, gssInToken.length, 316 new MessageProp(0 /* qop */, false /* privacy */)); 317 318 if (logger.isLoggable(Level.FINER)) { 319 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 320 "KRB5CLNT11:Response [after wrap]", gssOutToken); 321 } 322 323 completed = true; // server authenticated 324 msgProp = new MessageProp(JGSS_QOP, privacy); 325 326 return gssOutToken; 327 } catch (GSSException e) { 328 throw new SaslException("Final handshake failed", e); 329 } 330 } 331 }