1 /* 2 * Copyright (c) 2000, 2020, 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.util.Map; 29 import java.util.logging.Level; 30 import javax.security.sasl.*; 31 32 import static java.nio.charset.StandardCharsets.UTF_8; 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 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 // mutual is by default true if there is a security layer 134 boolean mutual; 135 if ((allQop & INTEGRITY_ONLY_PROTECTION) != 0 136 || (allQop & PRIVACY_PROTECTION) != 0) { 137 mutual = true; 138 secCtx.requestSequenceDet(true); 139 } else { 140 mutual = false; 141 } 142 143 // User can override default mutual flag 144 if (props != null) { 145 // Mutual authentication 146 String prop = (String)props.get(Sasl.SERVER_AUTH); 147 if (prop != null) { 148 mutual = "true".equalsIgnoreCase(prop); 149 } 150 } 151 secCtx.requestMutualAuth(mutual); 152 153 // Always specify potential need for integrity and confidentiality 154 // Decision will be made during final handshake 155 secCtx.requestConf(true); 156 secCtx.requestInteg(true); 157 158 } catch (GSSException e) { 159 throw new SaslException("Failure to initialize security context", e); 160 } 161 162 if (authzID != null && authzID.length() > 0) { 163 this.authzID = authzID.getBytes(UTF_8); 164 } 165 } 166 167 public boolean hasInitialResponse() { 168 return true; 169 } 170 171 /** 172 * Processes the challenge data. 173 * 174 * The server sends a challenge data using which the client must 175 * process using GSS_Init_sec_context. 176 * As per RFC 2222, when GSS_S_COMPLETE is returned, we do 177 * an extra handshake to determine the negotiated security protection 178 * and buffer sizes. 179 * 180 * @param challengeData A non-null byte array containing the 181 * challenge data from the server. 182 * @return A non-null byte array containing the response to be 183 * sent to the server. 184 */ 185 public byte[] evaluateChallenge(byte[] challengeData) throws SaslException { 186 if (completed) { 187 throw new IllegalStateException( 188 "GSSAPI authentication already complete"); 189 } 190 191 if (finalHandshake) { 192 return doFinalHandshake(challengeData); 193 } else { 194 195 // Security context not established yet; continue with init 196 197 try { 198 byte[] gssOutToken = secCtx.initSecContext(challengeData, 199 0, challengeData.length); 200 if (logger.isLoggable(Level.FINER)) { 201 traceOutput(MY_CLASS_NAME, "evaluteChallenge", 202 "KRB5CLNT02:Challenge: [raw]", challengeData); 203 traceOutput(MY_CLASS_NAME, "evaluateChallenge", 204 "KRB5CLNT03:Response: [after initSecCtx]", gssOutToken); 205 } 206 207 if (secCtx.isEstablished()) { 208 finalHandshake = true; 209 if (gssOutToken == null) { 210 // RFC 2222 7.2.1: Client responds with no data 211 return EMPTY; 212 } 213 } 214 215 return gssOutToken; 216 } catch (GSSException e) { 217 throw new SaslException("GSS initiate failed", e); 218 } 219 } 220 } 221 222 private byte[] doFinalHandshake(byte[] challengeData) throws SaslException { 223 try { 224 // Security context already established. challengeData 225 // should contain security layers and server's maximum buffer size 226 227 if (logger.isLoggable(Level.FINER)) { 228 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 229 "KRB5CLNT04:Challenge [raw]:", challengeData); 230 } 231 232 if (challengeData.length == 0) { 233 // Received S0, should return [] 234 return EMPTY; 235 } 236 237 // Received S1 (security layer, server max recv size) 238 239 MessageProp msgProp = new MessageProp(false); 240 byte[] gssOutToken = secCtx.unwrap(challengeData, 0, 241 challengeData.length, msgProp); 242 checkMessageProp("Handshake failure: ", msgProp); 243 244 // First octet is a bit-mask specifying the protections 245 // supported by the server 246 if (logger.isLoggable(Level.FINE)) { 247 if (logger.isLoggable(Level.FINER)) { 248 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 249 "KRB5CLNT05:Challenge [unwrapped]:", gssOutToken); 250 } 251 logger.log(Level.FINE, "KRB5CLNT06:Server protections: {0}", 252 gssOutToken[0]); 253 } 254 255 // Client selects preferred protection 256 // qop is ordered list of qop values 257 byte selectedQop = findPreferredMask(gssOutToken[0], qop); 258 if (selectedQop == 0) { 259 throw new SaslException( 260 "No common protection layer between client and server"); 261 } 262 263 if ((selectedQop&PRIVACY_PROTECTION) != 0) { 264 privacy = true; 265 integrity = true; 266 } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) { 267 integrity = true; 268 } 269 270 // 2nd-4th octets specifies maximum buffer size expected by 271 // server (in network byte order) 272 int srvMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3); 273 274 // Determine the max send buffer size based on what the 275 // server is able to receive and our specified max 276 sendMaxBufSize = (sendMaxBufSize == 0) ? srvMaxBufSize : 277 Math.min(sendMaxBufSize, srvMaxBufSize); 278 279 // Update context to limit size of returned buffer 280 rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy, 281 sendMaxBufSize); 282 283 if (logger.isLoggable(Level.FINE)) { 284 logger.log(Level.FINE, 285 "KRB5CLNT07:Client max recv size: {0}; server max recv size: {1}; rawSendSize: {2}", 286 new Object[] {recvMaxBufSize, 287 srvMaxBufSize, 288 rawSendSize}); 289 } 290 291 // Construct negotiated security layers and client's max 292 // receive buffer size and authzID 293 int len = 4; 294 if (authzID != null) { 295 len += authzID.length; 296 } 297 298 byte[] gssInToken = new byte[len]; 299 gssInToken[0] = selectedQop; 300 301 if (logger.isLoggable(Level.FINE)) { 302 logger.log(Level.FINE, 303 "KRB5CLNT08:Selected protection: {0}; privacy: {1}; integrity: {2}", 304 new Object[]{selectedQop, 305 Boolean.valueOf(privacy), 306 Boolean.valueOf(integrity)}); 307 } 308 309 if (privacy || integrity) { 310 // Last paragraph of RFC 4752 3.1: size ... MUST be 0 if the 311 // client does not support any security layer 312 intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3); 313 } 314 if (authzID != null) { 315 // copy authorization id 316 System.arraycopy(authzID, 0, gssInToken, 4, authzID.length); 317 logger.log(Level.FINE, "KRB5CLNT09:Authzid: {0}", authzID); 318 } 319 320 if (logger.isLoggable(Level.FINER)) { 321 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 322 "KRB5CLNT10:Response [raw]", gssInToken); 323 } 324 325 gssOutToken = secCtx.wrap(gssInToken, 326 0, gssInToken.length, 327 new MessageProp(0 /* qop */, false /* privacy */)); 328 329 if (logger.isLoggable(Level.FINER)) { 330 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 331 "KRB5CLNT11:Response [after wrap]", gssOutToken); 332 } 333 334 completed = true; // server authenticated 335 336 return gssOutToken; 337 } catch (GSSException e) { 338 throw new SaslException("Final handshake failed", e); 339 } 340 } 341 }