323 lines · 12.1 KB · Java
RawDownload
/* * Copyright (c) 2000, 2010, 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 javax.security.sasl.*;import java.io.*;import java.util.Map;import java.util.logging.Logger;import java.util.logging.Level; // JAASimport javax.security.auth.callback.*; // JGSSimport org.ietf.jgss.*; /**  * Implements the GSSAPI SASL server mechanism for Kerberos V5.  * (<A HREF="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</A>,  * <a HREF="http://www.ietf.org/internet-drafts/draft-ietf-cat-sasl-gssapi-00.txt">draft-ietf-cat-sasl-gssapi-00.txt</a>).  *  * Expects thread's Subject to contain server's Kerberos credentials  * - If not, underlying KRB5 mech will attempt to acquire Kerberos creds  *   by logging into Kerberos (via default TextCallbackHandler).  * - These creds will be used for exchange with client.  *  * Required callbacks:  * - AuthorizeCallback  *      handler must verify that authid/authzids are allowed and set  *      authorized ID to be the canonicalized authzid (if applicable).  *  * 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 client max recv size)  *  * @author Rosanna Lee  */final class GssKrb5Server extends GssKrb5Base implements SaslServer {    private static final String MY_CLASS_NAME = GssKrb5Server.class.getName();     private int handshakeStage = 0;    private String peer;    private String authzid;    private CallbackHandler cbh;     /**     * Creates a SASL mechanism with server credentials that it needs     * to participate in GSS-API/Kerberos v5 authentication exchange     * with the client.     */    GssKrb5Server(String protocol, String serverName,        Map props, CallbackHandler cbh) throws SaslException {         super(props, MY_CLASS_NAME);         this.cbh = cbh;        String service = protocol + "@" + serverName;         logger.log(Level.FINE, "KRB5SRV01:Using service name: {0}", service);         try {            GSSManager mgr = GSSManager.getInstance();             // Create the name for the requested service entity for Krb5 mech            GSSName serviceName = mgr.createName(service,                GSSName.NT_HOSTBASED_SERVICE, KRB5_OID);             GSSCredential cred = mgr.createCredential(serviceName,                GSSCredential.INDEFINITE_LIFETIME,                KRB5_OID, GSSCredential.ACCEPT_ONLY);             // Create a context using the server's credentials            secCtx = mgr.createContext(cred);             if ((allQop&INTEGRITY_ONLY_PROTECTION) != 0) {                // Might need integrity                secCtx.requestInteg(true);            }             if ((allQop&PRIVACY_PROTECTION) != 0) {                // Might need privacy                secCtx.requestConf(true);            }        } catch (GSSException e) {            throw new SaslException("Failure to initialize security context", e);        }        logger.log(Level.FINE, "KRB5SRV02:Initialization complete");    }      /**     * Processes the response data.     *     * The client sends response data to which the server must     * process using GSS_accept_sec_context.     * As per RFC 2222, the GSS authenication completes (GSS_S_COMPLETE)     * we do an extra hand shake to determine the negotiated security protection     * and buffer sizes.     *     * @param responseData A non-null but possible empty byte array containing the     * response data from the client.     * @return A non-null byte array containing the challenge to be     * sent to the client, or null when no more data is to be sent.     */    public byte[] evaluateResponse(byte[] responseData) throws SaslException {        if (completed) {            throw new SaslException(                "SASL authentication already complete");        }         if (logger.isLoggable(Level.FINER)) {            traceOutput(MY_CLASS_NAME, "evaluateResponse",                "KRB5SRV03:Response [raw]:", responseData);        }         switch (handshakeStage) {        case 1:            return doHandshake1(responseData);         case 2:            return doHandshake2(responseData);         default:            // Security context not established yet; continue with accept             try {                byte[] gssOutToken = secCtx.acceptSecContext(responseData,                    0, responseData.length);                 if (logger.isLoggable(Level.FINER)) {                    traceOutput(MY_CLASS_NAME, "evaluateResponse",                        "KRB5SRV04:Challenge: [after acceptSecCtx]", gssOutToken);                }                 if (secCtx.isEstablished()) {                    handshakeStage = 1;                     peer = secCtx.getSrcName().toString();                     logger.log(Level.FINE, "KRB5SRV05:Peer name is : {0}", peer);                     if (gssOutToken == null) {                        return doHandshake1(EMPTY);                    }                }                 return gssOutToken;            } catch (GSSException e) {                throw new SaslException("GSS initiate failed", e);            }        }    }     private byte[] doHandshake1(byte[] responseData) throws SaslException {        try {            // Security context already established. responseData            // should contain no data            if (responseData != null && responseData.length > 0) {                throw new SaslException(                    "Handshake expecting no response data from server");            }             // Construct 4 octets of data:            // First octet contains bitmask specifying protections supported            // 2nd-4th octets contains max receive buffer of server             byte[] gssInToken = new byte[4];            gssInToken[0] = allQop;            intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3);             if (logger.isLoggable(Level.FINE)) {                logger.log(Level.FINE,                    "KRB5SRV06:Supported protections: {0}; recv max buf size: {1}",                    new Object[]{new Byte(allQop),                                 new Integer(recvMaxBufSize)});            }             handshakeStage = 2;  // progress to next stage             if (logger.isLoggable(Level.FINER)) {                traceOutput(MY_CLASS_NAME, "doHandshake1",                    "KRB5SRV07:Challenge [raw]", gssInToken);            }             byte[] gssOutToken = secCtx.wrap(gssInToken, 0, gssInToken.length,                new MessageProp(0 /* gop */, false /* privacy */));             if (logger.isLoggable(Level.FINER)) {                traceOutput(MY_CLASS_NAME, "doHandshake1",                    "KRB5SRV08:Challenge [after wrap]", gssOutToken);            }            return gssOutToken;         } catch (GSSException e) {            throw new SaslException("Problem wrapping handshake1", e);        }    }     private byte[] doHandshake2(byte[] responseData) throws SaslException {        try {            // Expecting 4 octets from client selected protection            // and client's receive buffer size            byte[] gssOutToken = secCtx.unwrap(responseData, 0,                responseData.length, new MessageProp(0, false));             if (logger.isLoggable(Level.FINER)) {                traceOutput(MY_CLASS_NAME, "doHandshake2",                    "KRB5SRV09:Response [after unwrap]", gssOutToken);            }             // First octet is a bit-mask specifying the selected protection            byte selectedQop = gssOutToken[0];            if ((selectedQop&allQop) == 0) {                throw new SaslException("Client selected unsupported protection: "                    + selectedQop);            }            if ((selectedQop&PRIVACY_PROTECTION) != 0) {                privacy = true;                integrity = true;            } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) {                integrity = true;            }            msgProp = new MessageProp(JGSS_QOP, privacy);             // 2nd-4th octets specifies maximum buffer size expected by            // client (in network byte order). This is the server's send            // buffer maximum.            int clntMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3);             // Determine the max send buffer size based on what the            // client is able to receive and our specified max            sendMaxBufSize = (sendMaxBufSize == 0) ? clntMaxBufSize :                Math.min(sendMaxBufSize, clntMaxBufSize);             // Update context to limit size of returned buffer            rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy,                sendMaxBufSize);             if (logger.isLoggable(Level.FINE)) {                logger.log(Level.FINE,            "KRB5SRV10:Selected protection: {0}; privacy: {1}; integrity: {2}",                    new Object[]{new Byte(selectedQop),                                 Boolean.valueOf(privacy),                                 Boolean.valueOf(integrity)});                logger.log(Level.FINE,"KRB5SRV11:Client max recv size: {0}; server max send size: {1}; rawSendSize: {2}",                    new Object[] {new Integer(clntMaxBufSize),                                  new Integer(sendMaxBufSize),                                  new Integer(rawSendSize)});            }             // Get authorization identity, if any            if (gssOutToken.length > 4) {                try {                    authzid = new String(gssOutToken, 4,                        gssOutToken.length - 4, "UTF-8");                } catch (UnsupportedEncodingException uee) {                    throw new SaslException ("Cannot decode authzid", uee);                }            } else {                authzid = peer;            }            logger.log(Level.FINE, "KRB5SRV12:Authzid: {0}", authzid);             AuthorizeCallback acb = new AuthorizeCallback(peer, authzid);             // In Kerberos, realm is embedded in peer name            cbh.handle(new Callback[] {acb});            if (acb.isAuthorized()) {                authzid = acb.getAuthorizedID();                completed = true;            } else {                // Authorization failed                throw new SaslException(peer +                    " is not authorized to connect as " + authzid);            }             return null;        } catch (GSSException e) {            throw new SaslException("Final handshake step failed", e);        } catch (IOException e) {            throw new SaslException("Problem with callback handler", e);        } catch (UnsupportedCallbackException e) {            throw new SaslException("Problem with callback handler", e);        }    }     public String getAuthorizationID() {        if (completed) {            return authzid;        } else {            throw new IllegalStateException("Authentication incomplete");        }    }}