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