diff -r 4ebc2e2fb97c -r 71c04702a3d5 src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Client.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.security.jgss/share/classes/com/sun/security/sasl/gsskerb/GssKrb5Client.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,333 @@
+/*
+ * Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package com.sun.security.sasl.gsskerb;
+
+import java.io.IOException;
+import java.util.Map;
+import java.util.logging.Level;
+import javax.security.sasl.*;
+
+// JAAS
+import javax.security.auth.callback.CallbackHandler;
+
+// JGSS
+import org.ietf.jgss.*;
+
+/**
+ * Implements the GSSAPI SASL client mechanism for Kerberos V5.
+ * (RFC 2222,
+ * draft-ietf-cat-sasl-gssapi-04.txt).
+ * It uses the Java Bindings for GSSAPI
+ * (RFC 2853)
+ * for getting GSSAPI/Kerberos V5 support.
+ *
+ * The client/server interactions are:
+ * C0: bind (GSSAPI, initial response)
+ * S0: sasl-bind-in-progress, challenge 1 (output of accept_sec_context or [])
+ * C1: bind (GSSAPI, response 1 (output of init_sec_context or []))
+ * S1: sasl-bind-in-progress challenge 2 (security layer, server max recv size)
+ * C2: bind (GSSAPI, response 2 (security layer, client max recv size, authzid))
+ * S2: bind success response
+ *
+ * Expects the client's credentials to be supplied from the
+ * javax.security.sasl.credentials property or from the thread's Subject.
+ * Otherwise the underlying KRB5 mech will attempt to acquire Kerberos creds
+ * by logging into Kerberos (via default TextCallbackHandler).
+ * These creds will be used for exchange with server.
+ *
+ * Required callbacks: none.
+ *
+ * Environment properties that affect behavior of implementation:
+ *
+ * javax.security.sasl.qop
+ * - quality of protection; list of auth, auth-int, auth-conf; default is "auth"
+ * javax.security.sasl.maxbuf
+ * - max receive buffer size; default is 65536
+ * javax.security.sasl.sendmaxbuffer
+ * - max send buffer size; default is 65536; (min with server max recv size)
+ *
+ * javax.security.sasl.server.authentication
+ * - "true" means require mutual authentication; default is "false"
+ *
+ * javax.security.sasl.credentials
+ * - an {@link org.ietf.jgss.GSSCredential} used for delegated authentication.
+ *
+ * @author Rosanna Lee
+ */
+
+final class GssKrb5Client extends GssKrb5Base implements SaslClient {
+ // ---------------- Constants -----------------
+ private static final String MY_CLASS_NAME = GssKrb5Client.class.getName();
+
+ private boolean finalHandshake = false;
+ private boolean mutual = false; // default false
+ private byte[] authzID;
+
+ /**
+ * Creates a SASL mechanism with client credentials that it needs
+ * to participate in GSS-API/Kerberos v5 authentication exchange
+ * with the server.
+ */
+ GssKrb5Client(String authzID, String protocol, String serverName,
+ Map props, CallbackHandler cbh) throws SaslException {
+
+ super(props, MY_CLASS_NAME);
+
+ String service = protocol + "@" + serverName;
+ logger.log(Level.FINE, "KRB5CLNT01:Requesting service name: {0}",
+ service);
+
+ try {
+ GSSManager mgr = GSSManager.getInstance();
+
+ // Create the name for the requested service entity for Krb5 mech
+ GSSName acceptorName = mgr.createName(service,
+ GSSName.NT_HOSTBASED_SERVICE, KRB5_OID);
+
+ // Parse properties to check for supplied credentials
+ GSSCredential credentials = null;
+ if (props != null) {
+ Object prop = props.get(Sasl.CREDENTIALS);
+ if (prop != null && prop instanceof GSSCredential) {
+ credentials = (GSSCredential) prop;
+ logger.log(Level.FINE,
+ "KRB5CLNT01:Using the credentials supplied in " +
+ "javax.security.sasl.credentials");
+ }
+ }
+
+ // Create a context using credentials for Krb5 mech
+ secCtx = mgr.createContext(acceptorName,
+ KRB5_OID, /* mechanism */
+ credentials, /* credentials */
+ GSSContext.INDEFINITE_LIFETIME);
+
+ // Request credential delegation when credentials have been supplied
+ if (credentials != null) {
+ secCtx.requestCredDeleg(true);
+ }
+
+ // Parse properties to set desired context options
+ if (props != null) {
+ // Mutual authentication
+ String prop = (String)props.get(Sasl.SERVER_AUTH);
+ if (prop != null) {
+ mutual = "true".equalsIgnoreCase(prop);
+ }
+ }
+ secCtx.requestMutualAuth(mutual);
+
+ // Always specify potential need for integrity and confidentiality
+ // Decision will be made during final handshake
+ secCtx.requestConf(true);
+ secCtx.requestInteg(true);
+
+ } catch (GSSException e) {
+ throw new SaslException("Failure to initialize security context", e);
+ }
+
+ if (authzID != null && authzID.length() > 0) {
+ try {
+ this.authzID = authzID.getBytes("UTF8");
+ } catch (IOException e) {
+ throw new SaslException("Cannot encode authorization ID", e);
+ }
+ }
+ }
+
+ public boolean hasInitialResponse() {
+ return true;
+ }
+
+ /**
+ * Processes the challenge data.
+ *
+ * The server sends a challenge data using which the client must
+ * process using GSS_Init_sec_context.
+ * As per RFC 2222, when GSS_S_COMPLETE is returned, we do
+ * an extra handshake to determine the negotiated security protection
+ * and buffer sizes.
+ *
+ * @param challengeData A non-null byte array containing the
+ * challenge data from the server.
+ * @return A non-null byte array containing the response to be
+ * sent to the server.
+ */
+ public byte[] evaluateChallenge(byte[] challengeData) throws SaslException {
+ if (completed) {
+ throw new IllegalStateException(
+ "GSSAPI authentication already complete");
+ }
+
+ if (finalHandshake) {
+ return doFinalHandshake(challengeData);
+ } else {
+
+ // Security context not established yet; continue with init
+
+ try {
+ byte[] gssOutToken = secCtx.initSecContext(challengeData,
+ 0, challengeData.length);
+ if (logger.isLoggable(Level.FINER)) {
+ traceOutput(MY_CLASS_NAME, "evaluteChallenge",
+ "KRB5CLNT02:Challenge: [raw]", challengeData);
+ traceOutput(MY_CLASS_NAME, "evaluateChallenge",
+ "KRB5CLNT03:Response: [after initSecCtx]", gssOutToken);
+ }
+
+ if (secCtx.isEstablished()) {
+ finalHandshake = true;
+ if (gssOutToken == null) {
+ // RFC 2222 7.2.1: Client responds with no data
+ return EMPTY;
+ }
+ }
+
+ return gssOutToken;
+ } catch (GSSException e) {
+ throw new SaslException("GSS initiate failed", e);
+ }
+ }
+ }
+
+ private byte[] doFinalHandshake(byte[] challengeData) throws SaslException {
+ try {
+ // Security context already established. challengeData
+ // should contain security layers and server's maximum buffer size
+
+ if (logger.isLoggable(Level.FINER)) {
+ traceOutput(MY_CLASS_NAME, "doFinalHandshake",
+ "KRB5CLNT04:Challenge [raw]:", challengeData);
+ }
+
+ if (challengeData.length == 0) {
+ // Received S0, should return []
+ return EMPTY;
+ }
+
+ // Received S1 (security layer, server max recv size)
+
+ byte[] gssOutToken = secCtx.unwrap(challengeData, 0,
+ challengeData.length, new MessageProp(0, false));
+
+ // First octet is a bit-mask specifying the protections
+ // supported by the server
+ if (logger.isLoggable(Level.FINE)) {
+ if (logger.isLoggable(Level.FINER)) {
+ traceOutput(MY_CLASS_NAME, "doFinalHandshake",
+ "KRB5CLNT05:Challenge [unwrapped]:", gssOutToken);
+ }
+ logger.log(Level.FINE, "KRB5CLNT06:Server protections: {0}",
+ gssOutToken[0]);
+ }
+
+ // Client selects preferred protection
+ // qop is ordered list of qop values
+ byte selectedQop = findPreferredMask(gssOutToken[0], qop);
+ if (selectedQop == 0) {
+ throw new SaslException(
+ "No common protection layer between client and server");
+ }
+
+ if ((selectedQop&PRIVACY_PROTECTION) != 0) {
+ privacy = true;
+ integrity = true;
+ } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) {
+ integrity = true;
+ }
+
+ // 2nd-4th octets specifies maximum buffer size expected by
+ // server (in network byte order)
+ int srvMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3);
+
+ // Determine the max send buffer size based on what the
+ // server is able to receive and our specified max
+ sendMaxBufSize = (sendMaxBufSize == 0) ? srvMaxBufSize :
+ Math.min(sendMaxBufSize, srvMaxBufSize);
+
+ // Update context to limit size of returned buffer
+ rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy,
+ sendMaxBufSize);
+
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE,
+"KRB5CLNT07:Client max recv size: {0}; server max recv size: {1}; rawSendSize: {2}",
+ new Object[] {recvMaxBufSize,
+ srvMaxBufSize,
+ rawSendSize});
+ }
+
+ // Construct negotiated security layers and client's max
+ // receive buffer size and authzID
+ int len = 4;
+ if (authzID != null) {
+ len += authzID.length;
+ }
+
+ byte[] gssInToken = new byte[len];
+ gssInToken[0] = selectedQop;
+
+ if (logger.isLoggable(Level.FINE)) {
+ logger.log(Level.FINE,
+ "KRB5CLNT08:Selected protection: {0}; privacy: {1}; integrity: {2}",
+ new Object[]{selectedQop,
+ Boolean.valueOf(privacy),
+ Boolean.valueOf(integrity)});
+ }
+
+ if (privacy || integrity) {
+ // Last paragraph of RFC 4752 3.1: size ... MUST be 0 if the
+ // client does not support any security layer
+ intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3);
+ }
+ if (authzID != null) {
+ // copy authorization id
+ System.arraycopy(authzID, 0, gssInToken, 4, authzID.length);
+ logger.log(Level.FINE, "KRB5CLNT09:Authzid: {0}", authzID);
+ }
+
+ if (logger.isLoggable(Level.FINER)) {
+ traceOutput(MY_CLASS_NAME, "doFinalHandshake",
+ "KRB5CLNT10:Response [raw]", gssInToken);
+ }
+
+ gssOutToken = secCtx.wrap(gssInToken,
+ 0, gssInToken.length,
+ new MessageProp(0 /* qop */, false /* privacy */));
+
+ if (logger.isLoggable(Level.FINER)) {
+ traceOutput(MY_CLASS_NAME, "doFinalHandshake",
+ "KRB5CLNT11:Response [after wrap]", gssOutToken);
+ }
+
+ completed = true; // server authenticated
+
+ return gssOutToken;
+ } catch (GSSException e) {
+ throw new SaslException("Final handshake failed", e);
+ }
+ }
+}