/**
* Copyright 2007-2015, Kaazing Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kaazing.specification.socks5.internal;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
import org.ietf.jgss.GSSContext;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSName;
import org.ietf.jgss.MessageProp;
import org.ietf.jgss.Oid;
import org.kaazing.k3po.lang.el.Function;
import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi;
/**
* Functions that represent calls to the GSS-API authentication scheme such
* that robot scripts in this project can perform GSS-API authentication.
*/
public final class Functions {
//
// The OID of the Kerberos version 5 GSS-API mechanism as defined
// in RFC 1964. Used to tell GSS-API that it must use Kerberos.
//
private static final Oid krb5Oid;
static {
// set up System properties for use with these functions
// FIXME: Is there a way to do a configuration that has all of these set properly?
// The bcsLogin.conf should have the data, but is not quite working as expected.
System.setProperty("java.security.auth.login.config", "docker-kdc/bcsLogin.conf");
System.setProperty("java.security.krb5.conf", "docker-kdc/krb5.conf");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("sun.security.krb5.principal", "test1/kdc.km.test@KM.TEST");
try {
krb5Oid = new Oid("1.2.840.113554.1.2.2");
} catch (GSSException ex) {
throw new RuntimeException("Exception creating kerberos OID", ex);
}
}
/**
* Create a GSS Context from a clients point of view.
* @param server the name of the host for which the GSS Context is being created
* @return the GSS Context that a client can use to exchange security tokens for
* a secure channel, then wrap()/unpack() messages.
*/
@Function
public static GSSContext createClientGSSContext(String server) {
try {
GSSManager manager = GSSManager.getInstance();
GSSName serverName = manager.createName(server, null);
GSSContext context = manager.createContext(serverName,
krb5Oid,
null,
GSSContext.DEFAULT_LIFETIME);
context.requestMutualAuth(true); // Mutual authentication
context.requestConf(true); // Will use encryption later
context.requestInteg(true); // Will use integrity later
return context;
} catch (GSSException ex) {
throw new RuntimeException("Exception creating client GSSContext", ex);
}
}
/**
* Create a token, from a clients point of view, for establishing a secure
* communication channel. This is a client side token so it needs to bootstrap
* the token creation.
* @param context GSSContext for which a connection has been established to the remote peer
* @return a byte[] that represents the token a client can send to a server for
* establishing a secure communication channel.
*/
@Function
public static byte[] getClientToken(GSSContext context) {
byte[] initialToken = new byte[0];
if (!context.isEstablished()) {
try {
// token is ignored on the first call
initialToken = context.initSecContext(initialToken, 0, initialToken.length);
return getTokenWithLengthPrefix(initialToken);
} catch (GSSException ex) {
throw new RuntimeException("Exception getting client token", ex);
}
}
return null;
}
/**
* Utility method for creating a MessageProp object using the default
* Quality of Protection (0) and the given privacy setting.
* @param usePrivacy whether or not the messages being sent using this MessageProp
* require privacy
* @return the newly created MessageProp object with default QoP
*/
@Function
public static MessageProp createMessageProp(boolean usePrivacy) {
// default Quality of Protection (0), use privacy (passed int)
return new MessageProp(0, usePrivacy);
}
/**
* Utility method to call GSSContext.wrap() on a message which will create a byte[]
* that can be sent to a remote peer.
* @param context GSSContext for which a connection has been established to the remote peer
* @param prop the MessageProp object that is used to provide Quality of Protection of the message
* @param message the bytes of the message to be sent to the remote peer
* @return the protected bytes of the message that can be sent to and unpacked by the remote peer
*/
@Function
public static byte[] wrapMessage(GSSContext context, MessageProp prop, byte[] message) {
try {
// wrap the data and return the encrypted token
byte[] initialToken = context.wrap(message, 0, message.length, prop);
return getTokenWithLengthPrefix(initialToken);
} catch (GSSException ex) {
throw new RuntimeException("Exception wrapping message", ex);
}
}
/**
* Verify a message integrity check sent by a peer. If the MIC correctly identifies the
* message then the peer knows that the remote peer correctly received the message.
* @param context GSSContext for which a connection has been established to the remote peer
* @param prop the MessageProp that was used to wrap the original message
* @param message the bytes of the original message
* @param mic the bytes received from the remote peer that represent the MIC (like a checksum)
* @return a boolean whether or not the MIC was correctly verified
*/
@Function
public static boolean verifyMIC(GSSContext context, MessageProp prop, byte[] message, byte[] mic) {
try {
context.verifyMIC(mic, 0, mic.length,
message, 0, message.length,
prop);
return true;
} catch (GSSException ex) {
throw new RuntimeException("Exception verifying mic", ex);
}
}
/**
* Create a GSS Context not tied to any server name. Peers acting as a server
* create their context this way.
* @return the newly created GSS Context
*/
@Function
public static GSSContext createServerGSSContext() {
System.out.println("createServerGSSContext()...");
try {
final GSSManager manager = GSSManager.getInstance();
//
// Create server credentials to accept kerberos tokens. This should
// make use of the sun.security.krb5.principal system property to
// authenticate with the KDC.
//
GSSCredential serverCreds;
try {
serverCreds = Subject.doAs(new Subject(), new PrivilegedExceptionAction<GSSCredential>() {
public GSSCredential run() throws GSSException {
return manager.createCredential(null, GSSCredential.INDEFINITE_LIFETIME, krb5Oid,
GSSCredential.ACCEPT_ONLY);
}
});
} catch (PrivilegedActionException e) {
throw new RuntimeException("Exception creating server credentials", e);
}
//
// Create the GSSContext used to process requests from clients. The client
// requets should use Kerberos since the server credentials are Kerberos
// based.
//
GSSContext retVal = manager.createContext(serverCreds);
System.out.println("createServerGSSContext(), context: " + retVal);
return retVal;
} catch (GSSException ex) {
System.out.println("createServerGSSContext(), finished with exception");
throw new RuntimeException("Exception creating server GSSContext", ex);
}
}
/**
* Accept a client token to establish a secure communication channel.
* @param context GSSContext for which a connection has been established to the remote peer
* @param token the client side token (client side, as in the token had
* to be bootstrapped by the client and this peer uses that token
* to update the GSSContext)
* @return a boolean to indicate whether the token was used to successfully
* establish a communication channel
*/
@Function
public static boolean acceptClientToken(GSSContext context, byte[] token) {
try {
if (!context.isEstablished()) {
byte[] nextToken = context.acceptSecContext(token, 0, token.length);
return nextToken == null;
}
return true;
} catch (GSSException ex) {
throw new RuntimeException("Exception accepting client token", ex);
}
}
/**
* Generate a message integrity check for a given received message.
* @param context GSSContext for which a connection has been established to the remote peer
* @param prop the MessageProp used for exchanging messages
* @param message the bytes of the received message
* @return the bytes of the message integrity check (like a checksum) that is
* sent to a peer for verifying that the message was received correctly
*/
@Function
public static byte[] generateMIC(GSSContext context, MessageProp prop, byte[] message) {
try {
// Ensure the default Quality-of-Protection is applied.
prop.setQOP(0);
byte[] initialToken = context.getMIC(message, 0, message.length, prop);
return getTokenWithLengthPrefix(initialToken);
} catch (GSSException ex) {
throw new RuntimeException("Exception generating MIC for message", ex);
}
}
private static byte[] getTokenWithLengthPrefix(byte[] initialToken) {
byte[] token = new byte[initialToken.length + 4];
token[0] = (byte) ((initialToken.length >>> 24) & 0XFF);
token[1] = (byte) ((initialToken.length >>> 16) & 0XFF);
token[2] = (byte) ((initialToken.length >>> 8) & 0XFF);
token[3] = (byte) ((initialToken.length >>> 0) & 0XFF);
System.arraycopy(initialToken, 0, token, 4, initialToken.length);
return token;
}
/**
* Internal class that maps the functions to the socks5 namespace.
*/
public static class Mapper extends FunctionMapperSpi.Reflective {
/**
* Default constructor.
*/
public Mapper() {
super(Functions.class);
}
@Override
public String getPrefixName() {
return "socks5";
}
}
private Functions() {
// utility
}
}