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 sun.security.jgss.krb5.internal.TlsChannelBindingImpl; 39 import org.ietf.jgss.*; 40 41 /** 42 * Implements the GSSAPI SASL client mechanism for Kerberos V5. 43 * (<A HREF="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</A>, 44 * <a HREF="http://www.ietf.org/internet-drafts/draft-ietf-cat-sasl-gssapi-04.txt">draft-ietf-cat-sasl-gssapi-04.txt</a>). 45 * It uses the Java Bindings for GSSAPI 46 * (<A HREF="http://www.ietf.org/rfc/rfc2853.txt">RFC 2853</A>) 47 * for getting GSSAPI/Kerberos V5 support. 48 * 49 * The client/server interactions are: 50 * C0: bind (GSSAPI, initial response) 51 * S0: sasl-bind-in-progress, challenge 1 (output of accept_sec_context or []) 52 * C1: bind (GSSAPI, response 1 (output of init_sec_context or [])) 53 * S1: sasl-bind-in-progress challenge 2 (security layer, server max recv size) 54 * C2: bind (GSSAPI, response 2 (security layer, client max recv size, authzid)) 55 * S2: bind success response 56 * 57 * Expects the client's credentials to be supplied from the 58 * javax.security.sasl.credentials property or from the thread's Subject. 59 * Otherwise the underlying KRB5 mech will attempt to acquire Kerberos creds 60 * by logging into Kerberos (via default TextCallbackHandler). 61 * These creds will be used for exchange with server. 62 * 63 * Required callbacks: none. 64 * 65 * Environment properties that affect behavior of implementation: 66 * 67 * javax.security.sasl.qop 68 * - quality of protection; list of auth, auth-int, auth-conf; default is "auth" 69 * javax.security.sasl.maxbuf 70 * - max receive buffer size; default is 65536 71 * javax.security.sasl.sendmaxbuffer 72 * - max send buffer size; default is 65536; (min with server max recv size) 73 * 74 * javax.security.sasl.server.authentication 75 * - "true" means require mutual authentication; default is "false" 76 * 77 * javax.security.sasl.credentials 78 * - an {@link org.ietf.jgss.GSSCredential} used for delegated authentication. 79 * 80 * @author Rosanna Lee 81 */ 82 83 final class GssKrb5Client extends GssKrb5Base implements SaslClient { 84 // ---------------- Constants ----------------- 85 private static final String MY_CLASS_NAME = GssKrb5Client.class.getName(); 86 87 private boolean finalHandshake = 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<String, ?> 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 // mutual is by default true if there is a security layer 135 boolean mutual; 136 if ((allQop & INTEGRITY_ONLY_PROTECTION) != 0 137 || (allQop & PRIVACY_PROTECTION) != 0) { 138 mutual = true; 139 secCtx.requestSequenceDet(true); 140 } else { 141 mutual = false; 142 } 143 144 // User can override default mutual flag 145 if (props != null) { 146 // Mutual authentication 147 String prop = (String)props.get(Sasl.SERVER_AUTH); 148 if (prop != null) { 149 mutual = "true".equalsIgnoreCase(prop); 150 } 151 } 152 secCtx.requestMutualAuth(mutual); 153 154 if (props != null) { 155 // TLS Channel Binding 156 byte[] tlsCB = (byte[])props.get("jdk.internal.sasl.tlschannelbinding"); 157 if (tlsCB != null) { 158 secCtx.setChannelBinding(new TlsChannelBindingImpl(tlsCB)); 159 } 160 } 161 162 // Always specify potential need for integrity and confidentiality 163 // Decision will be made during final handshake 164 secCtx.requestConf(true); 165 secCtx.requestInteg(true); 166 167 } catch (GSSException e) { 168 throw new SaslException("Failure to initialize security context", e); 169 } 170 171 if (authzID != null && authzID.length() > 0) { 172 this.authzID = authzID.getBytes(UTF_8); 173 } 174 } 175 176 public boolean hasInitialResponse() { 177 return true; 178 } 179 180 /** 181 * Processes the challenge data. 182 * 183 * The server sends a challenge data using which the client must 184 * process using GSS_Init_sec_context. 185 * As per RFC 2222, when GSS_S_COMPLETE is returned, we do 186 * an extra handshake to determine the negotiated security protection 187 * and buffer sizes. 188 * 189 * @param challengeData A non-null byte array containing the 190 * challenge data from the server. 191 * @return A non-null byte array containing the response to be 192 * sent to the server. 193 */ 194 public byte[] evaluateChallenge(byte[] challengeData) throws SaslException { 195 if (completed) { 196 throw new IllegalStateException( 197 "GSSAPI authentication already complete"); 198 } 199 200 if (finalHandshake) { 201 return doFinalHandshake(challengeData); 202 } else { 203 204 // Security context not established yet; continue with init 205 206 try { 207 byte[] gssOutToken = secCtx.initSecContext(challengeData, 208 0, challengeData.length); 209 if (logger.isLoggable(Level.FINER)) { 210 traceOutput(MY_CLASS_NAME, "evaluteChallenge", 211 "KRB5CLNT02:Challenge: [raw]", challengeData); 212 traceOutput(MY_CLASS_NAME, "evaluateChallenge", 213 "KRB5CLNT03:Response: [after initSecCtx]", gssOutToken); 214 } 215 216 if (secCtx.isEstablished()) { 217 finalHandshake = true; 218 if (gssOutToken == null) { 219 // RFC 2222 7.2.1: Client responds with no data 220 return EMPTY; 221 } 222 } 223 224 return gssOutToken; 225 } catch (GSSException e) { 226 throw new SaslException("GSS initiate failed", e); 227 } 228 } 229 } 230 231 private byte[] doFinalHandshake(byte[] challengeData) throws SaslException { 232 try { 233 // Security context already established. challengeData 234 // should contain security layers and server's maximum buffer size 235 236 if (logger.isLoggable(Level.FINER)) { 237 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 238 "KRB5CLNT04:Challenge [raw]:", challengeData); 239 } 240 241 if (challengeData.length == 0) { 242 // Received S0, should return [] 243 return EMPTY; 244 } 245 246 // Received S1 (security layer, server max recv size) 247 248 MessageProp msgProp = new MessageProp(false); 249 byte[] gssOutToken = secCtx.unwrap(challengeData, 0, 250 challengeData.length, msgProp); 251 checkMessageProp("Handshake failure: ", msgProp); 252 253 // First octet is a bit-mask specifying the protections 254 // supported by the server 255 if (logger.isLoggable(Level.FINE)) { 256 if (logger.isLoggable(Level.FINER)) { 257 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 258 "KRB5CLNT05:Challenge [unwrapped]:", gssOutToken); 259 } 260 logger.log(Level.FINE, "KRB5CLNT06:Server protections: {0}", 261 gssOutToken[0]); 262 } 263 264 // Client selects preferred protection 265 // qop is ordered list of qop values 266 byte selectedQop = findPreferredMask(gssOutToken[0], qop); 267 if (selectedQop == 0) { 268 throw new SaslException( 269 "No common protection layer between client and server"); 270 } 271 272 if ((selectedQop&PRIVACY_PROTECTION) != 0) { 273 privacy = true; 274 integrity = true; 275 } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) { 276 integrity = true; 277 } 278 279 // 2nd-4th octets specifies maximum buffer size expected by 280 // server (in network byte order) 281 int srvMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3); 282 283 // Determine the max send buffer size based on what the 284 // server is able to receive and our specified max 285 sendMaxBufSize = (sendMaxBufSize == 0) ? srvMaxBufSize : 286 Math.min(sendMaxBufSize, srvMaxBufSize); 287 288 // Update context to limit size of returned buffer 289 rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy, 290 sendMaxBufSize); 291 292 if (logger.isLoggable(Level.FINE)) { 293 logger.log(Level.FINE, 294 "KRB5CLNT07:Client max recv size: {0}; server max recv size: {1}; rawSendSize: {2}", 295 new Object[] {recvMaxBufSize, 296 srvMaxBufSize, 297 rawSendSize}); 298 } 299 300 // Construct negotiated security layers and client's max 301 // receive buffer size and authzID 302 int len = 4; 303 if (authzID != null) { 304 len += authzID.length; 305 } 306 307 byte[] gssInToken = new byte[len]; 308 gssInToken[0] = selectedQop; 309 310 if (logger.isLoggable(Level.FINE)) { 311 logger.log(Level.FINE, 312 "KRB5CLNT08:Selected protection: {0}; privacy: {1}; integrity: {2}", 313 new Object[]{selectedQop, 314 Boolean.valueOf(privacy), 315 Boolean.valueOf(integrity)}); 316 } 317 318 if (privacy || integrity) { 319 // Last paragraph of RFC 4752 3.1: size ... MUST be 0 if the 320 // client does not support any security layer 321 intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3); 322 } 323 if (authzID != null) { 324 // copy authorization id 325 System.arraycopy(authzID, 0, gssInToken, 4, authzID.length); 326 logger.log(Level.FINE, "KRB5CLNT09:Authzid: {0}", authzID); 327 } 328 329 if (logger.isLoggable(Level.FINER)) { 330 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 331 "KRB5CLNT10:Response [raw]", gssInToken); 332 } 333 334 gssOutToken = secCtx.wrap(gssInToken, 335 0, gssInToken.length, 336 new MessageProp(0 /* qop */, false /* privacy */)); 337 338 if (logger.isLoggable(Level.FINER)) { 339 traceOutput(MY_CLASS_NAME, "doFinalHandshake", 340 "KRB5CLNT11:Response [after wrap]", gssOutToken); 341 } 342 343 completed = true; // server authenticated 344 345 return gssOutToken; 346 } catch (GSSException e) { 347 throw new SaslException("Final handshake failed", e); 348 } 349 } 350 }