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