| /* |
| * Copyright (c) 2000, 2004, 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; |
| |
| // JAAS |
| import javax.security.auth.callback.*; |
| |
| // JGSS |
| import org.ietf.jgss.*; |
| |
| /** |
| * Implements the GSSAPI SASL server mechanism for Kerberos V5. |
| * (<A HREF="ftp://ftp.isi.edu/in-notes/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"); |
| } |
| } |
| } |