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 }