1 /*
   2  * Copyright (c) 2000, 2004, 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 javax.security.sasl.*;
  29 import java.io.*;
  30 import java.util.Map;
  31 import java.util.logging.Logger;
  32 import java.util.logging.Level;
  33 
  34 // JAAS
  35 import javax.security.auth.callback.*;
  36 
  37 // JGSS
  38 import org.ietf.jgss.*;
  39 
  40 /**
  41   * Implements the GSSAPI SASL server 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-00.txt">draft-ietf-cat-sasl-gssapi-00.txt</a>).
  44   *
  45   * Expects thread's Subject to contain server's Kerberos credentials
  46   * - If not, underlying KRB5 mech will attempt to acquire Kerberos creds
  47   *   by logging into Kerberos (via default TextCallbackHandler).
  48   * - These creds will be used for exchange with client.
  49   *
  50   * Required callbacks:
  51   * - AuthorizeCallback
  52   *      handler must verify that authid/authzids are allowed and set
  53   *      authorized ID to be the canonicalized authzid (if applicable).
  54   *
  55   * Environment properties that affect behavior of implementation:
  56   *
  57   * javax.security.sasl.qop
  58   * - quality of protection; list of auth, auth-int, auth-conf; default is "auth"
  59   * javax.security.sasl.maxbuf
  60   * - max receive buffer size; default is 65536
  61   * javax.security.sasl.sendmaxbuffer
  62   * - max send buffer size; default is 65536; (min with client max recv size)
  63   *
  64   * @author Rosanna Lee
  65   */
  66 final class GssKrb5Server extends GssKrb5Base implements SaslServer {
  67     private static final String MY_CLASS_NAME = GssKrb5Server.class.getName();
  68 
  69     private int handshakeStage = 0;
  70     private String peer;
  71     private String authzid;
  72     private CallbackHandler cbh;
  73 
  74     /**
  75      * Creates a SASL mechanism with server credentials that it needs
  76      * to participate in GSS-API/Kerberos v5 authentication exchange
  77      * with the client.
  78      */
  79     GssKrb5Server(String protocol, String serverName,
  80         Map props, CallbackHandler cbh) throws SaslException {
  81 
  82         super(props, MY_CLASS_NAME);
  83 
  84         this.cbh = cbh;
  85         String service = protocol + "@" + serverName;
  86 
  87         logger.log(Level.FINE, "KRB5SRV01:Using service name: {0}", service);
  88 
  89         try {
  90             GSSManager mgr = GSSManager.getInstance();
  91 
  92             // Create the name for the requested service entity for Krb5 mech
  93             GSSName serviceName = mgr.createName(service,
  94                 GSSName.NT_HOSTBASED_SERVICE, KRB5_OID);
  95 
  96             GSSCredential cred = mgr.createCredential(serviceName,
  97                 GSSCredential.INDEFINITE_LIFETIME,
  98                 KRB5_OID, GSSCredential.ACCEPT_ONLY);
  99 
 100             // Create a context using the server's credentials
 101             secCtx = mgr.createContext(cred);
 102 
 103             if ((allQop&INTEGRITY_ONLY_PROTECTION) != 0) {
 104                 // Might need integrity
 105                 secCtx.requestInteg(true);
 106             }
 107 
 108             if ((allQop&PRIVACY_PROTECTION) != 0) {
 109                 // Might need privacy
 110                 secCtx.requestConf(true);
 111             }
 112         } catch (GSSException e) {
 113             throw new SaslException("Failure to initialize security context", e);
 114         }
 115         logger.log(Level.FINE, "KRB5SRV02:Initialization complete");
 116     }
 117 
 118 
 119     /**
 120      * Processes the response data.
 121      *
 122      * The client sends response data to which the server must
 123      * process using GSS_accept_sec_context.
 124      * As per RFC 2222, the GSS authenication completes (GSS_S_COMPLETE)
 125      * we do an extra hand shake to determine the negotiated security protection
 126      * and buffer sizes.
 127      *
 128      * @param responseData A non-null but possible empty byte array containing the
 129      * response data from the client.
 130      * @return A non-null byte array containing the challenge to be
 131      * sent to the client, or null when no more data is to be sent.
 132      */
 133     public byte[] evaluateResponse(byte[] responseData) throws SaslException {
 134         if (completed) {
 135             throw new SaslException(
 136                 "SASL authentication already complete");
 137         }
 138 
 139         if (logger.isLoggable(Level.FINER)) {
 140             traceOutput(MY_CLASS_NAME, "evaluateResponse",
 141                 "KRB5SRV03:Response [raw]:", responseData);
 142         }
 143 
 144         switch (handshakeStage) {
 145         case 1:
 146             return doHandshake1(responseData);
 147 
 148         case 2:
 149             return doHandshake2(responseData);
 150 
 151         default:
 152             // Security context not established yet; continue with accept
 153 
 154             try {
 155                 byte[] gssOutToken = secCtx.acceptSecContext(responseData,
 156                     0, responseData.length);
 157 
 158                 if (logger.isLoggable(Level.FINER)) {
 159                     traceOutput(MY_CLASS_NAME, "evaluateResponse",
 160                         "KRB5SRV04:Challenge: [after acceptSecCtx]", gssOutToken);
 161                 }
 162 
 163                 if (secCtx.isEstablished()) {
 164                     handshakeStage = 1;
 165 
 166                     peer = secCtx.getSrcName().toString();
 167 
 168                     logger.log(Level.FINE, "KRB5SRV05:Peer name is : {0}", peer);
 169 
 170                     if (gssOutToken == null) {
 171                         return doHandshake1(EMPTY);
 172                     }
 173                 }
 174 
 175                 return gssOutToken;
 176             } catch (GSSException e) {
 177                 throw new SaslException("GSS initiate failed", e);
 178             }
 179         }
 180     }
 181 
 182     private byte[] doHandshake1(byte[] responseData) throws SaslException {
 183         try {
 184             // Security context already established. responseData
 185             // should contain no data
 186             if (responseData != null && responseData.length > 0) {
 187                 throw new SaslException(
 188                     "Handshake expecting no response data from server");
 189             }
 190 
 191             // Construct 4 octets of data:
 192             // First octet contains bitmask specifying protections supported
 193             // 2nd-4th octets contains max receive buffer of server
 194 
 195             byte[] gssInToken = new byte[4];
 196             gssInToken[0] = allQop;
 197             intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3);
 198 
 199             if (logger.isLoggable(Level.FINE)) {
 200                 logger.log(Level.FINE,
 201                     "KRB5SRV06:Supported protections: {0}; recv max buf size: {1}",
 202                     new Object[]{new Byte(allQop),
 203                                  new Integer(recvMaxBufSize)});
 204             }
 205 
 206             handshakeStage = 2;  // progress to next stage
 207 
 208             if (logger.isLoggable(Level.FINER)) {
 209                 traceOutput(MY_CLASS_NAME, "doHandshake1",
 210                     "KRB5SRV07:Challenge [raw]", gssInToken);
 211             }
 212 
 213             byte[] gssOutToken = secCtx.wrap(gssInToken, 0, gssInToken.length,
 214                 new MessageProp(0 /* gop */, false /* privacy */));
 215 
 216             if (logger.isLoggable(Level.FINER)) {
 217                 traceOutput(MY_CLASS_NAME, "doHandshake1",
 218                     "KRB5SRV08:Challenge [after wrap]", gssOutToken);
 219             }
 220             return gssOutToken;
 221 
 222         } catch (GSSException e) {
 223             throw new SaslException("Problem wrapping handshake1", e);
 224         }
 225     }
 226 
 227     private byte[] doHandshake2(byte[] responseData) throws SaslException {
 228         try {
 229             // Expecting 4 octets from client selected protection
 230             // and client's receive buffer size
 231             byte[] gssOutToken = secCtx.unwrap(responseData, 0,
 232                 responseData.length, new MessageProp(0, false));
 233 
 234             if (logger.isLoggable(Level.FINER)) {
 235                 traceOutput(MY_CLASS_NAME, "doHandshake2",
 236                     "KRB5SRV09:Response [after unwrap]", gssOutToken);
 237             }
 238 
 239             // First octet is a bit-mask specifying the selected protection
 240             byte selectedQop = gssOutToken[0];
 241             if ((selectedQop&allQop) == 0) {
 242                 throw new SaslException("Client selected unsupported protection: "
 243                     + selectedQop);
 244             }
 245             if ((selectedQop&PRIVACY_PROTECTION) != 0) {
 246                 privacy = true;
 247                 integrity = true;
 248             } else if ((selectedQop&INTEGRITY_ONLY_PROTECTION) != 0) {
 249                 integrity = true;
 250             }
 251             msgProp = new MessageProp(JGSS_QOP, privacy);
 252 
 253             // 2nd-4th octets specifies maximum buffer size expected by
 254             // client (in network byte order). This is the server's send
 255             // buffer maximum.
 256             int clntMaxBufSize = networkByteOrderToInt(gssOutToken, 1, 3);
 257 
 258             // Determine the max send buffer size based on what the
 259             // client is able to receive and our specified max
 260             sendMaxBufSize = (sendMaxBufSize == 0) ? clntMaxBufSize :
 261                 Math.min(sendMaxBufSize, clntMaxBufSize);
 262 
 263             // Update context to limit size of returned buffer
 264             rawSendSize = secCtx.getWrapSizeLimit(JGSS_QOP, privacy,
 265                 sendMaxBufSize);
 266 
 267             if (logger.isLoggable(Level.FINE)) {
 268                 logger.log(Level.FINE,
 269             "KRB5SRV10:Selected protection: {0}; privacy: {1}; integrity: {2}",
 270                     new Object[]{new Byte(selectedQop),
 271                                  Boolean.valueOf(privacy),
 272                                  Boolean.valueOf(integrity)});
 273                 logger.log(Level.FINE,
 274 "KRB5SRV11:Client max recv size: {0}; server max send size: {1}; rawSendSize: {2}",
 275                     new Object[] {new Integer(clntMaxBufSize),
 276                                   new Integer(sendMaxBufSize),
 277                                   new Integer(rawSendSize)});
 278             }
 279 
 280             // Get authorization identity, if any
 281             if (gssOutToken.length > 4) {
 282                 try {
 283                     authzid = new String(gssOutToken, 4,
 284                         gssOutToken.length - 4, "UTF-8");
 285                 } catch (UnsupportedEncodingException uee) {
 286                     throw new SaslException ("Cannot decode authzid", uee);
 287                 }
 288             } else {
 289                 authzid = peer;
 290             }
 291             logger.log(Level.FINE, "KRB5SRV12:Authzid: {0}", authzid);
 292 
 293             AuthorizeCallback acb = new AuthorizeCallback(peer, authzid);
 294 
 295             // In Kerberos, realm is embedded in peer name
 296             cbh.handle(new Callback[] {acb});
 297             if (acb.isAuthorized()) {
 298                 authzid = acb.getAuthorizedID();
 299                 completed = true;
 300             } else {
 301                 // Authorization failed
 302                 throw new SaslException(peer +
 303                     " is not authorized to connect as " + authzid);
 304             }
 305 
 306             return null;
 307         } catch (GSSException e) {
 308             throw new SaslException("Final handshake step failed", e);
 309         } catch (IOException e) {
 310             throw new SaslException("Problem with callback handler", e);
 311         } catch (UnsupportedCallbackException e) {
 312             throw new SaslException("Problem with callback handler", e);
 313         }
 314     }
 315 
 316     public String getAuthorizationID() {
 317         if (completed) {
 318             return authzid;
 319         } else {
 320             throw new IllegalStateException("Authentication incomplete");
 321         }
 322     }
 323 }