133N/A/*
1879N/A * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
133N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
133N/A *
133N/A * This code is free software; you can redistribute it and/or modify it
133N/A * under the terms of the GNU General Public License version 2 only, as
133N/A * published by the Free Software Foundation. Oracle designates this
133N/A * particular file as subject to the "Classpath" exception as provided
133N/A * by Oracle in the LICENSE file that accompanied this code.
133N/A *
133N/A * This code is distributed in the hope that it will be useful, but WITHOUT
133N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
133N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
133N/A * version 2 for more details (a copy is included in the LICENSE file that
133N/A * accompanied this code).
133N/A *
133N/A * You should have received a copy of the GNU General Public License version
133N/A * 2 along with this work; if not, write to the Free Software Foundation,
1472N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
1472N/A *
1472N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
133N/A * or visit www.oracle.com if you need additional information or have any
133N/A * questions.
133N/A */
1879N/A
1879N/Apackage com.sun.security.sasl.gsskerb;
1879N/A
1879N/Aimport javax.security.sasl.*;
1879N/Aimport java.io.*;
1879N/Aimport java.util.Map;
1879N/Aimport java.util.logging.Logger;
1879N/Aimport java.util.logging.Level;
133N/A
133N/A// JAAS
133N/Aimport javax.security.auth.callback.*;
133N/A
133N/A// JGSS
133N/Aimport org.ietf.jgss.*;
133N/A
133N/A/**
133N/A * Implements the GSSAPI SASL server mechanism for Kerberos V5.
133N/A * (<A HREF="http://www.ietf.org/rfc/rfc2222.txt">RFC 2222</A>,
133N/A * <a HREF="http://www.ietf.org/internet-drafts/draft-ietf-cat-sasl-gssapi-00.txt">draft-ietf-cat-sasl-gssapi-00.txt</a>).
133N/A *
133N/A * Expects thread's Subject to contain server's Kerberos credentials
133N/A * - If not, underlying KRB5 mech will attempt to acquire Kerberos creds
133N/A * by logging into Kerberos (via default TextCallbackHandler).
133N/A * - These creds will be used for exchange with client.
133N/A *
133N/A * Required callbacks:
133N/A * - AuthorizeCallback
133N/A * handler must verify that authid/authzids are allowed and set
133N/A * authorized ID to be the canonicalized authzid (if applicable).
133N/A *
133N/A * Environment properties that affect behavior of implementation:
133N/A *
133N/A * javax.security.sasl.qop
133N/A * - quality of protection; list of auth, auth-int, auth-conf; default is "auth"
133N/A * javax.security.sasl.maxbuf
133N/A * - max receive buffer size; default is 65536
133N/A * javax.security.sasl.sendmaxbuffer
133N/A * - max send buffer size; default is 65536; (min with client max recv size)
133N/A *
133N/A * @author Rosanna Lee
133N/A */
133N/Afinal class GssKrb5Server extends GssKrb5Base implements SaslServer {
133N/A private static final String MY_CLASS_NAME = GssKrb5Server.class.getName();
133N/A
133N/A private int handshakeStage = 0;
133N/A private String peer;
133N/A private String authzid;
133N/A private CallbackHandler cbh;
133N/A
133N/A /**
133N/A * Creates a SASL mechanism with server credentials that it needs
133N/A * to participate in GSS-API/Kerberos v5 authentication exchange
133N/A * with the client.
133N/A */
133N/A GssKrb5Server(String protocol, String serverName,
133N/A Map props, CallbackHandler cbh) throws SaslException {
133N/A
133N/A super(props, MY_CLASS_NAME);
133N/A
133N/A this.cbh = cbh;
133N/A String service = protocol + "@" + serverName;
133N/A
133N/A logger.log(Level.FINE, "KRB5SRV01:Using service name: {0}", service);
133N/A
133N/A try {
133N/A GSSManager mgr = GSSManager.getInstance();
133N/A
133N/A // Create the name for the requested service entity for Krb5 mech
133N/A GSSName serviceName = mgr.createName(service,
133N/A GSSName.NT_HOSTBASED_SERVICE, KRB5_OID);
133N/A
133N/A GSSCredential cred = mgr.createCredential(serviceName,
133N/A GSSCredential.INDEFINITE_LIFETIME,
133N/A KRB5_OID, GSSCredential.ACCEPT_ONLY);
133N/A
133N/A // Create a context using the server's credentials
133N/A secCtx = mgr.createContext(cred);
133N/A
133N/A if ((allQop&INTEGRITY_ONLY_PROTECTION) != 0) {
133N/A // Might need integrity
133N/A secCtx.requestInteg(true);
133N/A }
133N/A
133N/A if ((allQop&PRIVACY_PROTECTION) != 0) {
133N/A // Might need privacy
133N/A secCtx.requestConf(true);
133N/A }
133N/A } catch (GSSException e) {
133N/A throw new SaslException("Failure to initialize security context", e);
133N/A }
133N/A logger.log(Level.FINE, "KRB5SRV02:Initialization complete");
133N/A }
133N/A
133N/A
133N/A /**
133N/A * Processes the response data.
133N/A *
133N/A * The client sends response data to which the server must
133N/A * process using GSS_accept_sec_context.
133N/A * As per RFC 2222, the GSS authenication completes (GSS_S_COMPLETE)
133N/A * we do an extra hand shake to determine the negotiated security protection
133N/A * and buffer sizes.
133N/A *
133N/A * @param responseData A non-null but possible empty byte array containing the
133N/A * response data from the client.
133N/A * @return A non-null byte array containing the challenge to be
133N/A * sent to the client, or null when no more data is to be sent.
133N/A */
133N/A public byte[] evaluateResponse(byte[] responseData) throws SaslException {
133N/A if (completed) {
133N/A throw new SaslException(
133N/A "SASL authentication already complete");
133N/A }
133N/A
133N/A if (logger.isLoggable(Level.FINER)) {
133N/A traceOutput(MY_CLASS_NAME, "evaluateResponse",
133N/A "KRB5SRV03:Response [raw]:", responseData);
133N/A }
133N/A
133N/A switch (handshakeStage) {
133N/A case 1:
133N/A return doHandshake1(responseData);
133N/A
133N/A case 2:
133N/A return doHandshake2(responseData);
133N/A
133N/A default:
133N/A // Security context not established yet; continue with accept
133N/A
133N/A try {
133N/A byte[] gssOutToken = secCtx.acceptSecContext(responseData,
133N/A 0, responseData.length);
133N/A
133N/A if (logger.isLoggable(Level.FINER)) {
133N/A traceOutput(MY_CLASS_NAME, "evaluateResponse",
133N/A "KRB5SRV04:Challenge: [after acceptSecCtx]", gssOutToken);
133N/A }
133N/A
133N/A if (secCtx.isEstablished()) {
133N/A handshakeStage = 1;
133N/A
133N/A peer = secCtx.getSrcName().toString();
133N/A
133N/A logger.log(Level.FINE, "KRB5SRV05:Peer name is : {0}", peer);
133N/A
133N/A if (gssOutToken == null) {
133N/A return doHandshake1(EMPTY);
133N/A }
133N/A }
133N/A
133N/A return gssOutToken;
133N/A } catch (GSSException e) {
133N/A throw new SaslException("GSS initiate failed", e);
133N/A }
133N/A }
133N/A }
133N/A
133N/A private byte[] doHandshake1(byte[] responseData) throws SaslException {
133N/A try {
133N/A // Security context already established. responseData
133N/A // should contain no data
133N/A if (responseData != null && responseData.length > 0) {
133N/A throw new SaslException(
133N/A "Handshake expecting no response data from server");
133N/A }
133N/A
133N/A // Construct 4 octets of data:
133N/A // First octet contains bitmask specifying protections supported
133N/A // 2nd-4th octets contains max receive buffer of server
133N/A
133N/A byte[] gssInToken = new byte[4];
133N/A gssInToken[0] = allQop;
133N/A intToNetworkByteOrder(recvMaxBufSize, gssInToken, 1, 3);
133N/A
133N/A if (logger.isLoggable(Level.FINE)) {
133N/A logger.log(Level.FINE,
133N/A "KRB5SRV06:Supported protections: {0}; recv max buf size: {1}",
133N/A new Object[]{new Byte(allQop),
133N/A new Integer(recvMaxBufSize)});
133N/A }
133N/A
133N/A handshakeStage = 2; // progress to next stage
133N/A
133N/A if (logger.isLoggable(Level.FINER)) {
133N/A traceOutput(MY_CLASS_NAME, "doHandshake1",
133N/A "KRB5SRV07:Challenge [raw]", gssInToken);
133N/A }
133N/A
133N/A byte[] gssOutToken = secCtx.wrap(gssInToken, 0, gssInToken.length,
133N/A new MessageProp(0 /* gop */, false /* privacy */));
1879N/A
1879N/A 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;
}
// 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");
}
}
}