1455 lines
52 KiB
Java
1455 lines
52 KiB
Java
/*
|
|
* Copyright (c) 2000, 2019, 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 sun.security.jgss.krb5;
|
|
|
|
import com.sun.security.jgss.InquireType;
|
|
import org.ietf.jgss.*;
|
|
import sun.misc.HexDumpEncoder;
|
|
import sun.security.jgss.GSSUtil;
|
|
import sun.security.jgss.GSSCaller;
|
|
import sun.security.jgss.spi.*;
|
|
import sun.security.jgss.TokenTracker;
|
|
import sun.security.krb5.*;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.io.IOException;
|
|
import java.security.Provider;
|
|
import java.security.AccessController;
|
|
import java.security.AccessControlContext;
|
|
import java.security.Key;
|
|
import java.security.PrivilegedExceptionAction;
|
|
import java.security.PrivilegedActionException;
|
|
import javax.crypto.Cipher;
|
|
import javax.security.auth.Subject;
|
|
import javax.security.auth.kerberos.*;
|
|
import sun.security.krb5.internal.Ticket;
|
|
|
|
/**
|
|
* Implements the mechanism specific context class for the Kerberos v5
|
|
* GSS-API mechanism.
|
|
*
|
|
* @author Mayank Upadhyay
|
|
* @author Ram Marti
|
|
* @since 1.4
|
|
*/
|
|
class Krb5Context implements GSSContextSpi {
|
|
|
|
/*
|
|
* The different states that this context can be in.
|
|
*/
|
|
|
|
private static final int STATE_NEW = 1;
|
|
private static final int STATE_IN_PROCESS = 2;
|
|
private static final int STATE_DONE = 3;
|
|
private static final int STATE_DELETED = 4;
|
|
|
|
private int state = STATE_NEW;
|
|
|
|
public static final int SESSION_KEY = 0;
|
|
public static final int INITIATOR_SUBKEY = 1;
|
|
public static final int ACCEPTOR_SUBKEY = 2;
|
|
|
|
/*
|
|
* Optional features that the application can set and their default
|
|
* values.
|
|
*/
|
|
|
|
private boolean credDelegState = false; // now only useful at client
|
|
private boolean mutualAuthState = true;
|
|
private boolean replayDetState = true;
|
|
private boolean sequenceDetState = true;
|
|
private boolean confState = true;
|
|
private boolean integState = true;
|
|
private boolean delegPolicyState = false;
|
|
|
|
private boolean isConstrainedDelegationTried = false;
|
|
|
|
private int mySeqNumber;
|
|
private int peerSeqNumber;
|
|
private int keySrc;
|
|
private TokenTracker peerTokenTracker;
|
|
|
|
private CipherHelper cipherHelper = null;
|
|
|
|
/*
|
|
* Separate locks for the sequence numbers allow the application to
|
|
* receive tokens at the same time that it is sending tokens. Note
|
|
* that the application must synchronize the generation and
|
|
* transmission of tokens such that tokens are processed in the same
|
|
* order that they are generated. This is important when sequence
|
|
* checking of per-message tokens is enabled.
|
|
*/
|
|
|
|
private Object mySeqNumberLock = new Object();
|
|
private Object peerSeqNumberLock = new Object();
|
|
|
|
private EncryptionKey key;
|
|
private Krb5NameElement myName;
|
|
private Krb5NameElement peerName;
|
|
private int lifetime;
|
|
private boolean initiator;
|
|
private ChannelBinding channelBinding;
|
|
|
|
private Krb5CredElement myCred;
|
|
private Krb5CredElement delegatedCred; // Set only on acceptor side
|
|
|
|
// XXX See if the required info from these can be extracted and
|
|
// stored elsewhere
|
|
private Credentials serviceCreds;
|
|
private KrbApReq apReq;
|
|
Ticket serviceTicket;
|
|
final private GSSCaller caller;
|
|
private static final boolean DEBUG = Krb5Util.DEBUG;
|
|
|
|
/**
|
|
* Constructor for Krb5Context to be called on the context initiator's
|
|
* side.
|
|
*/
|
|
Krb5Context(GSSCaller caller, Krb5NameElement peerName, Krb5CredElement myCred,
|
|
int lifetime)
|
|
throws GSSException {
|
|
|
|
if (peerName == null)
|
|
throw new IllegalArgumentException("Cannot have null peer name");
|
|
|
|
this.caller = caller;
|
|
this.peerName = peerName;
|
|
this.myCred = myCred;
|
|
this.lifetime = lifetime;
|
|
this.initiator = true;
|
|
}
|
|
|
|
/**
|
|
* Constructor for Krb5Context to be called on the context acceptor's
|
|
* side.
|
|
*/
|
|
Krb5Context(GSSCaller caller, Krb5CredElement myCred)
|
|
throws GSSException {
|
|
this.caller = caller;
|
|
this.myCred = myCred;
|
|
this.initiator = false;
|
|
}
|
|
|
|
/**
|
|
* Constructor for Krb5Context to import a previously exported context.
|
|
*/
|
|
public Krb5Context(GSSCaller caller, byte [] interProcessToken)
|
|
throws GSSException {
|
|
throw new GSSException(GSSException.UNAVAILABLE,
|
|
-1, "GSS Import Context not available");
|
|
}
|
|
|
|
/**
|
|
* Method to determine if the context can be exported and then
|
|
* re-imported.
|
|
*/
|
|
public final boolean isTransferable() throws GSSException {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* The lifetime remaining for this context.
|
|
*/
|
|
public final int getLifetime() {
|
|
// XXX Return service ticket lifetime
|
|
return GSSContext.INDEFINITE_LIFETIME;
|
|
}
|
|
|
|
/*
|
|
* Methods that may be invoked by the GSS framework in response
|
|
* to an application request for setting/getting these
|
|
* properties.
|
|
*
|
|
* These can only be called on the initiator side.
|
|
*
|
|
* Notice that an application can only request these
|
|
* properties. The mechanism may or may not support them. The
|
|
* application must make getXXX calls after context establishment
|
|
* to see if the mechanism implementations on both sides support
|
|
* these features. requestAnonymity is an exception where the
|
|
* application will want to call getAnonymityState prior to sending any
|
|
* GSS token during context establishment.
|
|
*
|
|
* Also note that the requests can only be placed before context
|
|
* establishment starts. i.e. when state is STATE_NEW
|
|
*/
|
|
|
|
/**
|
|
* Requests the desired lifetime. Can only be used on the context
|
|
* initiator's side.
|
|
*/
|
|
public void requestLifetime(int lifetime) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator())
|
|
this.lifetime = lifetime;
|
|
}
|
|
|
|
/**
|
|
* Requests that confidentiality be available.
|
|
*/
|
|
public final void requestConf(boolean value) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator())
|
|
confState = value;
|
|
}
|
|
|
|
/**
|
|
* Is confidentiality available?
|
|
*/
|
|
public final boolean getConfState() {
|
|
return confState;
|
|
}
|
|
|
|
/**
|
|
* Requests that integrity be available.
|
|
*/
|
|
public final void requestInteg(boolean value) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator())
|
|
integState = value;
|
|
}
|
|
|
|
/**
|
|
* Is integrity available?
|
|
*/
|
|
public final boolean getIntegState() {
|
|
return integState;
|
|
}
|
|
|
|
/**
|
|
* Requests that credential delegation be done during context
|
|
* establishment.
|
|
*/
|
|
public final void requestCredDeleg(boolean value) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator()) {
|
|
if (myCred == null || !(myCred instanceof Krb5ProxyCredential)) {
|
|
credDelegState = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is credential delegation enabled?
|
|
*/
|
|
public final boolean getCredDelegState() {
|
|
if (isInitiator()) {
|
|
return credDelegState;
|
|
} else {
|
|
// Server side deleg state is not flagged by credDelegState.
|
|
// It can use constrained delegation.
|
|
tryConstrainedDelegation();
|
|
return delegatedCred != null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests that mutual authentication be done during context
|
|
* establishment. Since this is fromm the client's perspective, it
|
|
* essentially requests that the server be authenticated.
|
|
*/
|
|
public final void requestMutualAuth(boolean value) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator()) {
|
|
mutualAuthState = value;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is mutual authentication enabled? Since this is from the client's
|
|
* perspective, it essentially meas that the server is being
|
|
* authenticated.
|
|
*/
|
|
public final boolean getMutualAuthState() {
|
|
return mutualAuthState;
|
|
}
|
|
|
|
/**
|
|
* Requests that replay detection be done on the GSS wrap and MIC
|
|
* tokens.
|
|
*/
|
|
public final void requestReplayDet(boolean value) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator())
|
|
replayDetState = value;
|
|
}
|
|
|
|
/**
|
|
* Is replay detection enabled on the GSS wrap and MIC tokens?
|
|
* We enable replay detection if sequence checking is enabled.
|
|
*/
|
|
public final boolean getReplayDetState() {
|
|
return replayDetState || sequenceDetState;
|
|
}
|
|
|
|
/**
|
|
* Requests that sequence checking be done on the GSS wrap and MIC
|
|
* tokens.
|
|
*/
|
|
public final void requestSequenceDet(boolean value) throws GSSException {
|
|
if (state == STATE_NEW && isInitiator())
|
|
sequenceDetState = value;
|
|
}
|
|
|
|
/**
|
|
* Is sequence checking enabled on the GSS Wrap and MIC tokens?
|
|
* We enable sequence checking if replay detection is enabled.
|
|
*/
|
|
public final boolean getSequenceDetState() {
|
|
return sequenceDetState || replayDetState;
|
|
}
|
|
|
|
/**
|
|
* Requests that the deleg policy be respected.
|
|
*/
|
|
public final void requestDelegPolicy(boolean value) {
|
|
if (state == STATE_NEW && isInitiator())
|
|
delegPolicyState = value;
|
|
}
|
|
|
|
/**
|
|
* Is deleg policy respected?
|
|
*/
|
|
public final boolean getDelegPolicyState() {
|
|
return delegPolicyState;
|
|
}
|
|
|
|
/*
|
|
* Anonymity is a little different in that after an application
|
|
* requests anonymity it will want to know whether the mechanism
|
|
* can support it or not, prior to sending any tokens across for
|
|
* context establishment. Since this is from the initiator's
|
|
* perspective, it essentially requests that the initiator be
|
|
* anonymous.
|
|
*/
|
|
|
|
public final void requestAnonymity(boolean value) throws GSSException {
|
|
// Ignore silently. Application will check back with
|
|
// getAnonymityState.
|
|
}
|
|
|
|
// RFC 2853 actually calls for this to be called after context
|
|
// establishment to get the right answer, but that is
|
|
// incorrect. The application may not want to send over any
|
|
// tokens if anonymity is not available.
|
|
public final boolean getAnonymityState() {
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Package private methods invoked by other Krb5 plugin classes.
|
|
*/
|
|
|
|
/**
|
|
* Get the context specific DESCipher instance, invoked in
|
|
* MessageToken.init()
|
|
*/
|
|
final CipherHelper getCipherHelper(EncryptionKey ckey) throws GSSException {
|
|
EncryptionKey cipherKey = null;
|
|
if (cipherHelper == null) {
|
|
cipherKey = (getKey() == null) ? ckey: getKey();
|
|
cipherHelper = new CipherHelper(cipherKey);
|
|
}
|
|
return cipherHelper;
|
|
}
|
|
|
|
final int incrementMySequenceNumber() {
|
|
int retVal;
|
|
synchronized (mySeqNumberLock) {
|
|
retVal = mySeqNumber;
|
|
mySeqNumber = retVal + 1;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
final void resetMySequenceNumber(int seqNumber) {
|
|
if (DEBUG) {
|
|
System.out.println("Krb5Context setting mySeqNumber to: "
|
|
+ seqNumber);
|
|
}
|
|
synchronized (mySeqNumberLock) {
|
|
mySeqNumber = seqNumber;
|
|
}
|
|
}
|
|
|
|
final void resetPeerSequenceNumber(int seqNumber) {
|
|
if (DEBUG) {
|
|
System.out.println("Krb5Context setting peerSeqNumber to: "
|
|
+ seqNumber);
|
|
}
|
|
synchronized (peerSeqNumberLock) {
|
|
peerSeqNumber = seqNumber;
|
|
peerTokenTracker = new TokenTracker(peerSeqNumber);
|
|
}
|
|
}
|
|
|
|
final void setKey(int keySrc, EncryptionKey key) throws GSSException {
|
|
this.key = key;
|
|
this.keySrc = keySrc;
|
|
// %%% to do: should clear old cipherHelper first
|
|
cipherHelper = new CipherHelper(key); // Need to use new key
|
|
}
|
|
|
|
public final int getKeySrc() {
|
|
return keySrc;
|
|
}
|
|
|
|
private final EncryptionKey getKey() {
|
|
return key;
|
|
}
|
|
|
|
/**
|
|
* Called on the acceptor side to store the delegated credentials
|
|
* received in the AcceptSecContextToken.
|
|
*/
|
|
final void setDelegCred(Krb5CredElement delegatedCred) {
|
|
this.delegatedCred = delegatedCred;
|
|
}
|
|
|
|
/*
|
|
* While the application can only request the following features,
|
|
* other classes in the package can call the actual set methods
|
|
* for them. They are called as context establishment tokens are
|
|
* received on an acceptor side and the context feature list that
|
|
* the initiator wants becomes known.
|
|
*/
|
|
|
|
/*
|
|
* This method is also called by InitialToken.OverloadedChecksum if the
|
|
* TGT is not forwardable and the user requested delegation.
|
|
*/
|
|
final void setCredDelegState(boolean state) {
|
|
credDelegState = state;
|
|
}
|
|
|
|
final void setMutualAuthState(boolean state) {
|
|
mutualAuthState = state;
|
|
}
|
|
|
|
final void setReplayDetState(boolean state) {
|
|
replayDetState = state;
|
|
}
|
|
|
|
final void setSequenceDetState(boolean state) {
|
|
sequenceDetState = state;
|
|
}
|
|
|
|
final void setConfState(boolean state) {
|
|
confState = state;
|
|
}
|
|
|
|
final void setIntegState(boolean state) {
|
|
integState = state;
|
|
}
|
|
|
|
final void setDelegPolicyState(boolean state) {
|
|
delegPolicyState = state;
|
|
}
|
|
|
|
/**
|
|
* Sets the channel bindings to be used during context
|
|
* establishment.
|
|
*/
|
|
public final void setChannelBinding(ChannelBinding channelBinding)
|
|
throws GSSException {
|
|
this.channelBinding = channelBinding;
|
|
}
|
|
|
|
final ChannelBinding getChannelBinding() {
|
|
return channelBinding;
|
|
}
|
|
|
|
/**
|
|
* Returns the mechanism oid.
|
|
*
|
|
* @return the Oid of this context
|
|
*/
|
|
public final Oid getMech() {
|
|
return (Krb5MechFactory.GSS_KRB5_MECH_OID);
|
|
}
|
|
|
|
/**
|
|
* Returns the context initiator name.
|
|
*
|
|
* @return initiator name
|
|
* @exception GSSException
|
|
*/
|
|
public final GSSNameSpi getSrcName() throws GSSException {
|
|
return (isInitiator()? myName : peerName);
|
|
}
|
|
|
|
/**
|
|
* Returns the context acceptor.
|
|
*
|
|
* @return context acceptor(target) name
|
|
* @exception GSSException
|
|
*/
|
|
public final GSSNameSpi getTargName() throws GSSException {
|
|
return (!isInitiator()? myName : peerName);
|
|
}
|
|
|
|
/**
|
|
* Returns the delegated credential for the context. This
|
|
* is an optional feature of contexts which not all
|
|
* mechanisms will support. A context can be requested to
|
|
* support credential delegation by using the <b>CRED_DELEG</b>,
|
|
* or it can request for a constrained delegation.
|
|
* This is only valid on the acceptor side of the context.
|
|
* @return GSSCredentialSpi object for the delegated credential
|
|
* @exception GSSException
|
|
* @see GSSContext#getDelegCredState
|
|
*/
|
|
public final GSSCredentialSpi getDelegCred() throws GSSException {
|
|
if (state != STATE_IN_PROCESS && state != STATE_DONE)
|
|
throw new GSSException(GSSException.NO_CONTEXT);
|
|
if (isInitiator()) {
|
|
throw new GSSException(GSSException.NO_CRED);
|
|
}
|
|
tryConstrainedDelegation();
|
|
if (delegatedCred == null) {
|
|
throw new GSSException(GSSException.NO_CRED);
|
|
}
|
|
return delegatedCred;
|
|
}
|
|
|
|
private void tryConstrainedDelegation() {
|
|
if (state != STATE_IN_PROCESS && state != STATE_DONE) {
|
|
return;
|
|
}
|
|
// We will only try constrained delegation once (if necessary).
|
|
if (!isConstrainedDelegationTried) {
|
|
if (delegatedCred == null) {
|
|
if (DEBUG) {
|
|
System.out.println(">>> Constrained deleg from " + caller);
|
|
}
|
|
// The constrained delegation part. The acceptor needs to have
|
|
// isInitiator=true in order to get a TGT, either earlier at
|
|
// logon stage, if useSubjectCredsOnly, or now.
|
|
try {
|
|
delegatedCred = new Krb5ProxyCredential(
|
|
Krb5InitCredential.getInstance(
|
|
GSSCaller.CALLER_ACCEPT, myName, lifetime),
|
|
peerName, serviceTicket);
|
|
} catch (GSSException gsse) {
|
|
// OK, delegatedCred is null then
|
|
}
|
|
}
|
|
isConstrainedDelegationTried = true;
|
|
}
|
|
}
|
|
/**
|
|
* Tests if this is the initiator side of the context.
|
|
*
|
|
* @return boolean indicating if this is initiator (true)
|
|
* or target (false)
|
|
*/
|
|
public final boolean isInitiator() {
|
|
return initiator;
|
|
}
|
|
|
|
/**
|
|
* Tests if the context can be used for per-message service.
|
|
* Context may allow the calls to the per-message service
|
|
* functions before being fully established.
|
|
*
|
|
* @return boolean indicating if per-message methods can
|
|
* be called.
|
|
*/
|
|
public final boolean isProtReady() {
|
|
return (state == STATE_DONE);
|
|
}
|
|
|
|
/**
|
|
* Initiator context establishment call. This method may be
|
|
* required to be called several times. A CONTINUE_NEEDED return
|
|
* call indicates that more calls are needed after the next token
|
|
* is received from the peer.
|
|
*
|
|
* @param is contains the token received from the peer. On the
|
|
* first call it will be ignored.
|
|
* @return any token required to be sent to the peer
|
|
* It is responsibility of the caller
|
|
* to send the token to its peer for processing.
|
|
* @exception GSSException
|
|
*/
|
|
public final byte[] initSecContext(InputStream is, int mechTokenSize)
|
|
throws GSSException {
|
|
|
|
byte[] retVal = null;
|
|
InitialToken token = null;
|
|
int errorCode = GSSException.FAILURE;
|
|
if (DEBUG) {
|
|
System.out.println("Entered Krb5Context.initSecContext with " +
|
|
"state=" + printState(state));
|
|
}
|
|
if (!isInitiator()) {
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"initSecContext on an acceptor " +
|
|
"GSSContext");
|
|
}
|
|
|
|
try {
|
|
if (state == STATE_NEW) {
|
|
state = STATE_IN_PROCESS;
|
|
|
|
errorCode = GSSException.NO_CRED;
|
|
|
|
if (myCred == null) {
|
|
myCred = Krb5InitCredential.getInstance(caller, myName,
|
|
GSSCredential.DEFAULT_LIFETIME);
|
|
myCred = Krb5ProxyCredential.tryImpersonation(
|
|
caller, (Krb5InitCredential)myCred);
|
|
} else if (!myCred.isInitiatorCredential()) {
|
|
throw new GSSException(errorCode, -1,
|
|
"No TGT available");
|
|
}
|
|
myName = (Krb5NameElement) myCred.getName();
|
|
Credentials tgt;
|
|
final Krb5ProxyCredential second;
|
|
if (myCred instanceof Krb5InitCredential) {
|
|
second = null;
|
|
tgt = ((Krb5InitCredential) myCred).getKrb5Credentials();
|
|
} else {
|
|
second = (Krb5ProxyCredential) myCred;
|
|
tgt = second.self.getKrb5Credentials();
|
|
}
|
|
|
|
checkPermission(peerName.getKrb5PrincipalName().getName(),
|
|
"initiate");
|
|
/*
|
|
* If useSubjectCredsonly is true then
|
|
* we check whether we already have the ticket
|
|
* for this service in the Subject and reuse it
|
|
*/
|
|
|
|
final AccessControlContext acc =
|
|
AccessController.getContext();
|
|
|
|
if (GSSUtil.useSubjectCredsOnly(caller)) {
|
|
KerberosTicket kerbTicket = null;
|
|
try {
|
|
// get service ticket from caller's subject
|
|
kerbTicket = AccessController.doPrivileged(
|
|
new PrivilegedExceptionAction<KerberosTicket>() {
|
|
public KerberosTicket run() throws Exception {
|
|
// XXX to be cleaned
|
|
// highly consider just calling:
|
|
// Subject.getSubject
|
|
// SubjectComber.find
|
|
// instead of Krb5Util.getServiceTicket
|
|
return Krb5Util.getServiceTicket(
|
|
GSSCaller.CALLER_UNKNOWN,
|
|
// since it's useSubjectCredsOnly here,
|
|
// don't worry about the null
|
|
second == null ?
|
|
myName.getKrb5PrincipalName().getName():
|
|
second.getName().getKrb5PrincipalName().getName(),
|
|
peerName.getKrb5PrincipalName().getName(),
|
|
acc);
|
|
}});
|
|
} catch (PrivilegedActionException e) {
|
|
if (DEBUG) {
|
|
System.out.println("Attempt to obtain service"
|
|
+ " ticket from the subject failed!");
|
|
}
|
|
}
|
|
if (kerbTicket != null) {
|
|
if (DEBUG) {
|
|
System.out.println("Found service ticket in " +
|
|
"the subject" +
|
|
kerbTicket);
|
|
}
|
|
|
|
// convert Ticket to serviceCreds
|
|
// XXX Should merge these two object types
|
|
// avoid converting back and forth
|
|
serviceCreds = Krb5Util.ticketToCreds(kerbTicket);
|
|
}
|
|
}
|
|
if (serviceCreds == null) {
|
|
// either we did not find the serviceCreds in the
|
|
// Subject or useSubjectCreds is false
|
|
if (DEBUG) {
|
|
System.out.println("Service ticket not found in " +
|
|
"the subject");
|
|
}
|
|
// Get Service ticket using the Kerberos protocols
|
|
if (second == null) {
|
|
serviceCreds = Credentials.acquireServiceCreds(
|
|
peerName.getKrb5PrincipalName().getName(),
|
|
tgt);
|
|
} else {
|
|
serviceCreds = Credentials.acquireS4U2proxyCreds(
|
|
peerName.getKrb5PrincipalName().getName(),
|
|
second.tkt,
|
|
second.getName().getKrb5PrincipalName(),
|
|
tgt);
|
|
}
|
|
if (GSSUtil.useSubjectCredsOnly(caller)) {
|
|
final Subject subject =
|
|
AccessController.doPrivileged(
|
|
new java.security.PrivilegedAction<Subject>() {
|
|
public Subject run() {
|
|
return (Subject.getSubject(acc));
|
|
}
|
|
});
|
|
if (subject != null &&
|
|
!subject.isReadOnly()) {
|
|
/*
|
|
* Store the service credentials as
|
|
* javax.security.auth.kerberos.KerberosTicket in
|
|
* the Subject. We could wait until the context is
|
|
* successfully established; however it is easier
|
|
* to do it here and there is no harm.
|
|
*/
|
|
final KerberosTicket kt =
|
|
Krb5Util.credsToTicket(serviceCreds);
|
|
AccessController.doPrivileged (
|
|
new java.security.PrivilegedAction<Void>() {
|
|
public Void run() {
|
|
subject.getPrivateCredentials().add(kt);
|
|
return null;
|
|
}
|
|
});
|
|
} else {
|
|
// log it for debugging purpose
|
|
if (DEBUG) {
|
|
System.out.println("Subject is " +
|
|
"readOnly;Kerberos Service "+
|
|
"ticket not stored");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
errorCode = GSSException.FAILURE;
|
|
token = new InitSecContextToken(this, tgt, serviceCreds);
|
|
apReq = ((InitSecContextToken)token).getKrbApReq();
|
|
retVal = token.encode();
|
|
myCred = null;
|
|
if (!getMutualAuthState()) {
|
|
state = STATE_DONE;
|
|
}
|
|
if (DEBUG) {
|
|
System.out.println("Created InitSecContextToken:\n"+
|
|
new HexDumpEncoder().encodeBuffer(retVal));
|
|
}
|
|
} else if (state == STATE_IN_PROCESS) {
|
|
// No need to write anything;
|
|
// just validate the incoming token
|
|
new AcceptSecContextToken(this, serviceCreds, apReq, is);
|
|
serviceCreds = null;
|
|
apReq = null;
|
|
state = STATE_DONE;
|
|
} else {
|
|
// XXX Use logging API?
|
|
if (DEBUG) {
|
|
System.out.println(state);
|
|
}
|
|
}
|
|
} catch (KrbException e) {
|
|
if (DEBUG) {
|
|
e.printStackTrace();
|
|
}
|
|
GSSException gssException =
|
|
new GSSException(errorCode, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(errorCode, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
public final boolean isEstablished() {
|
|
return (state == STATE_DONE);
|
|
}
|
|
|
|
/**
|
|
* Acceptor's context establishment call. This method may be
|
|
* required to be called several times. A CONTINUE_NEEDED return
|
|
* call indicates that more calls are needed after the next token
|
|
* is received from the peer.
|
|
*
|
|
* @param is contains the token received from the peer.
|
|
* @return any token required to be sent to the peer
|
|
* It is responsibility of the caller
|
|
* to send the token to its peer for processing.
|
|
* @exception GSSException
|
|
*/
|
|
public final byte[] acceptSecContext(InputStream is, int mechTokenSize)
|
|
throws GSSException {
|
|
|
|
byte[] retVal = null;
|
|
|
|
if (DEBUG) {
|
|
System.out.println("Entered Krb5Context.acceptSecContext with " +
|
|
"state=" + printState(state));
|
|
}
|
|
|
|
if (isInitiator()) {
|
|
throw new GSSException(GSSException.FAILURE, -1,
|
|
"acceptSecContext on an initiator " +
|
|
"GSSContext");
|
|
}
|
|
try {
|
|
if (state == STATE_NEW) {
|
|
state = STATE_IN_PROCESS;
|
|
if (myCred == null) {
|
|
myCred = Krb5AcceptCredential.getInstance(caller, myName);
|
|
} else if (!myCred.isAcceptorCredential()) {
|
|
throw new GSSException(GSSException.NO_CRED, -1,
|
|
"No Secret Key available");
|
|
}
|
|
myName = (Krb5NameElement) myCred.getName();
|
|
|
|
// If there is already a bound name, check now
|
|
if (myName != null) {
|
|
Krb5MechFactory.checkAcceptCredPermission(myName, myName);
|
|
}
|
|
|
|
InitSecContextToken token = new InitSecContextToken(this,
|
|
(Krb5AcceptCredential) myCred, is);
|
|
PrincipalName clientName = token.getKrbApReq().getClient();
|
|
peerName = Krb5NameElement.getInstance(clientName);
|
|
|
|
// If unbound, check after the bound name is found
|
|
if (myName == null) {
|
|
myName = Krb5NameElement.getInstance(
|
|
token.getKrbApReq().getCreds().getServer());
|
|
Krb5MechFactory.checkAcceptCredPermission(myName, myName);
|
|
}
|
|
|
|
if (getMutualAuthState()) {
|
|
retVal = new AcceptSecContextToken(this,
|
|
token.getKrbApReq()).encode();
|
|
}
|
|
serviceTicket = token.getKrbApReq().getCreds().getTicket();
|
|
myCred = null;
|
|
state = STATE_DONE;
|
|
} else {
|
|
// XXX Use logging API?
|
|
if (DEBUG) {
|
|
System.out.println(state);
|
|
}
|
|
}
|
|
} catch (KrbException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
} catch (IOException e) {
|
|
if (DEBUG) {
|
|
e.printStackTrace();
|
|
}
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
|
|
return retVal;
|
|
}
|
|
|
|
/**
|
|
* Queries the context for largest data size to accommodate
|
|
* the specified protection and be <= maxTokSize.
|
|
*
|
|
* @param qop the quality of protection that the context will be
|
|
* asked to provide.
|
|
* @param confReq a flag indicating whether confidentiality will be
|
|
* requested or not
|
|
* @param outputSize the maximum size of the output token
|
|
* @return the maximum size for the input message that can be
|
|
* provided to the wrap() method in order to guarantee that these
|
|
* requirements are met.
|
|
* @throws GSSException
|
|
*/
|
|
public final int getWrapSizeLimit(int qop, boolean confReq,
|
|
int maxTokSize) throws GSSException {
|
|
|
|
int retVal = 0;
|
|
if (cipherHelper.getProto() == 0) {
|
|
retVal = WrapToken.getSizeLimit(qop, confReq, maxTokSize,
|
|
getCipherHelper(null));
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
retVal = WrapToken_v2.getSizeLimit(qop, confReq, maxTokSize,
|
|
getCipherHelper(null));
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
* Per-message calls depend on the sequence number. The sequence number
|
|
* synchronization is at a finer granularity because wrap and getMIC
|
|
* care about the local sequence number (mySeqNumber) where are unwrap
|
|
* and verifyMIC care about the remote sequence number (peerSeqNumber).
|
|
*/
|
|
|
|
public final byte[] wrap(byte inBuf[], int offset, int len,
|
|
MessageProp msgProp) throws GSSException {
|
|
|
|
if (state != STATE_DONE) {
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Wrap called in invalid state!");
|
|
}
|
|
|
|
byte[] encToken = null;
|
|
try {
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token =
|
|
new WrapToken(this, msgProp, inBuf, offset, len);
|
|
encToken = token.encode();
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token =
|
|
new WrapToken_v2(this, msgProp, inBuf, offset, len);
|
|
encToken = token.encode();
|
|
}
|
|
if (DEBUG) {
|
|
System.out.println("Krb5Context.wrap: token=["
|
|
+ getHexBytes(encToken, 0, encToken.length)
|
|
+ "]");
|
|
}
|
|
return encToken;
|
|
} catch (IOException e) {
|
|
encToken = null;
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
}
|
|
|
|
public final int wrap(byte inBuf[], int inOffset, int len,
|
|
byte[] outBuf, int outOffset,
|
|
MessageProp msgProp) throws GSSException {
|
|
|
|
if (state != STATE_DONE)
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Wrap called in invalid state!");
|
|
|
|
int retVal = 0;
|
|
try {
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token =
|
|
new WrapToken(this, msgProp, inBuf, inOffset, len);
|
|
retVal = token.encode(outBuf, outOffset);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token =
|
|
new WrapToken_v2(this, msgProp, inBuf, inOffset, len);
|
|
retVal = token.encode(outBuf, outOffset);
|
|
}
|
|
if (DEBUG) {
|
|
System.out.println("Krb5Context.wrap: token=["
|
|
+ getHexBytes(outBuf, outOffset, retVal)
|
|
+ "]");
|
|
}
|
|
return retVal;
|
|
} catch (IOException e) {
|
|
retVal = 0;
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
}
|
|
|
|
public final void wrap(byte inBuf[], int offset, int len,
|
|
OutputStream os, MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
if (state != STATE_DONE)
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Wrap called in invalid state!");
|
|
|
|
byte[] encToken = null;
|
|
try {
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token =
|
|
new WrapToken(this, msgProp, inBuf, offset, len);
|
|
token.encode(os);
|
|
if (DEBUG) {
|
|
encToken = token.encode();
|
|
}
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token =
|
|
new WrapToken_v2(this, msgProp, inBuf, offset, len);
|
|
token.encode(os);
|
|
if (DEBUG) {
|
|
encToken = token.encode();
|
|
}
|
|
}
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
|
|
if (DEBUG) {
|
|
System.out.println("Krb5Context.wrap: token=["
|
|
+ getHexBytes(encToken, 0, encToken.length)
|
|
+ "]");
|
|
}
|
|
}
|
|
|
|
public final void wrap(InputStream is, OutputStream os,
|
|
MessageProp msgProp) throws GSSException {
|
|
|
|
byte[] data;
|
|
try {
|
|
data = new byte[is.available()];
|
|
is.read(data);
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
wrap(data, 0, data.length, os, msgProp);
|
|
}
|
|
|
|
public final byte[] unwrap(byte inBuf[], int offset, int len,
|
|
MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
if (DEBUG) {
|
|
System.out.println("Krb5Context.unwrap: token=["
|
|
+ getHexBytes(inBuf, offset, len)
|
|
+ "]");
|
|
}
|
|
|
|
if (state != STATE_DONE) {
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
" Unwrap called in invalid state!");
|
|
}
|
|
|
|
byte[] data = null;
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token =
|
|
new WrapToken(this, inBuf, offset, len, msgProp);
|
|
data = token.getData();
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token =
|
|
new WrapToken_v2(this, inBuf, offset, len, msgProp);
|
|
data = token.getData();
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
public final int unwrap(byte inBuf[], int inOffset, int len,
|
|
byte[] outBuf, int outOffset,
|
|
MessageProp msgProp) throws GSSException {
|
|
|
|
if (state != STATE_DONE)
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Unwrap called in invalid state!");
|
|
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token =
|
|
new WrapToken(this, inBuf, inOffset, len, msgProp);
|
|
len = token.getData(outBuf, outOffset);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token =
|
|
new WrapToken_v2(this, inBuf, inOffset, len, msgProp);
|
|
len = token.getData(outBuf, outOffset);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
public final int unwrap(InputStream is,
|
|
byte[] outBuf, int outOffset,
|
|
MessageProp msgProp) throws GSSException {
|
|
|
|
if (state != STATE_DONE)
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Unwrap called in invalid state!");
|
|
|
|
int len = 0;
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token = new WrapToken(this, is, msgProp);
|
|
len = token.getData(outBuf, outOffset);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token = new WrapToken_v2(this, is, msgProp);
|
|
len = token.getData(outBuf, outOffset);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
}
|
|
return len;
|
|
}
|
|
|
|
|
|
public final void unwrap(InputStream is, OutputStream os,
|
|
MessageProp msgProp) throws GSSException {
|
|
|
|
if (state != STATE_DONE)
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Unwrap called in invalid state!");
|
|
|
|
byte[] data = null;
|
|
if (cipherHelper.getProto() == 0) {
|
|
WrapToken token = new WrapToken(this, is, msgProp);
|
|
data = token.getData();
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
WrapToken_v2 token = new WrapToken_v2(this, is, msgProp);
|
|
data = token.getData();
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
}
|
|
|
|
try {
|
|
os.write(data);
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
}
|
|
|
|
public final byte[] getMIC(byte []inMsg, int offset, int len,
|
|
MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
byte[] micToken = null;
|
|
try {
|
|
if (cipherHelper.getProto() == 0) {
|
|
MicToken token =
|
|
new MicToken(this, msgProp, inMsg, offset, len);
|
|
micToken = token.encode();
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
MicToken_v2 token =
|
|
new MicToken_v2(this, msgProp, inMsg, offset, len);
|
|
micToken = token.encode();
|
|
}
|
|
return micToken;
|
|
} catch (IOException e) {
|
|
micToken = null;
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
}
|
|
|
|
private int getMIC(byte []inMsg, int offset, int len,
|
|
byte[] outBuf, int outOffset,
|
|
MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
int retVal = 0;
|
|
try {
|
|
if (cipherHelper.getProto() == 0) {
|
|
MicToken token =
|
|
new MicToken(this, msgProp, inMsg, offset, len);
|
|
retVal = token.encode(outBuf, outOffset);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
MicToken_v2 token =
|
|
new MicToken_v2(this, msgProp, inMsg, offset, len);
|
|
retVal = token.encode(outBuf, outOffset);
|
|
}
|
|
return retVal;
|
|
} catch (IOException e) {
|
|
retVal = 0;
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Checksum calculation requires a byte[]. Hence might as well pass
|
|
* a byte[] into the MicToken constructor. However, writing the
|
|
* token can be optimized for cases where the application passed in
|
|
* an OutputStream.
|
|
*/
|
|
|
|
private void getMIC(byte[] inMsg, int offset, int len,
|
|
OutputStream os, MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
try {
|
|
if (cipherHelper.getProto() == 0) {
|
|
MicToken token =
|
|
new MicToken(this, msgProp, inMsg, offset, len);
|
|
token.encode(os);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
MicToken_v2 token =
|
|
new MicToken_v2(this, msgProp, inMsg, offset, len);
|
|
token.encode(os);
|
|
}
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
}
|
|
|
|
public final void getMIC(InputStream is, OutputStream os,
|
|
MessageProp msgProp) throws GSSException {
|
|
byte[] data;
|
|
try {
|
|
data = new byte[is.available()];
|
|
is.read(data);
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
getMIC(data, 0, data.length, os, msgProp);
|
|
}
|
|
|
|
public final void verifyMIC(byte []inTok, int tokOffset, int tokLen,
|
|
byte[] inMsg, int msgOffset, int msgLen,
|
|
MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
if (cipherHelper.getProto() == 0) {
|
|
MicToken token =
|
|
new MicToken(this, inTok, tokOffset, tokLen, msgProp);
|
|
token.verify(inMsg, msgOffset, msgLen);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
MicToken_v2 token =
|
|
new MicToken_v2(this, inTok, tokOffset, tokLen, msgProp);
|
|
token.verify(inMsg, msgOffset, msgLen);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
}
|
|
}
|
|
|
|
private void verifyMIC(InputStream is,
|
|
byte[] inMsg, int msgOffset, int msgLen,
|
|
MessageProp msgProp)
|
|
throws GSSException {
|
|
|
|
if (cipherHelper.getProto() == 0) {
|
|
MicToken token = new MicToken(this, is, msgProp);
|
|
token.verify(inMsg, msgOffset, msgLen);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
} else if (cipherHelper.getProto() == 1) {
|
|
MicToken_v2 token = new MicToken_v2(this, is, msgProp);
|
|
token.verify(inMsg, msgOffset, msgLen);
|
|
setSequencingAndReplayProps(token, msgProp);
|
|
}
|
|
}
|
|
|
|
public final void verifyMIC(InputStream is, InputStream msgStr,
|
|
MessageProp mProp) throws GSSException {
|
|
byte[] msg;
|
|
try {
|
|
msg = new byte[msgStr.available()];
|
|
msgStr.read(msg);
|
|
} catch (IOException e) {
|
|
GSSException gssException =
|
|
new GSSException(GSSException.FAILURE, -1, e.getMessage());
|
|
gssException.initCause(e);
|
|
throw gssException;
|
|
}
|
|
verifyMIC(is, msg, 0, msg.length, mProp);
|
|
}
|
|
|
|
/**
|
|
* Produces a token representing this context. After this call
|
|
* the context will no longer be usable until an import is
|
|
* performed on the returned token.
|
|
*
|
|
* @param os the output token will be written to this stream
|
|
* @exception GSSException
|
|
*/
|
|
public final byte [] export() throws GSSException {
|
|
throw new GSSException(GSSException.UNAVAILABLE, -1,
|
|
"GSS Export Context not available");
|
|
}
|
|
|
|
/**
|
|
* Releases context resources and terminates the
|
|
* context between 2 peer.
|
|
*
|
|
* @exception GSSException with major codes NO_CONTEXT, FAILURE.
|
|
*/
|
|
|
|
public final void dispose() throws GSSException {
|
|
state = STATE_DELETED;
|
|
delegatedCred = null;
|
|
}
|
|
|
|
public final Provider getProvider() {
|
|
return Krb5MechFactory.PROVIDER;
|
|
}
|
|
|
|
/**
|
|
* Sets replay and sequencing information for a message token received
|
|
* form the peer.
|
|
*/
|
|
private void setSequencingAndReplayProps(MessageToken token,
|
|
MessageProp prop) {
|
|
if (replayDetState || sequenceDetState) {
|
|
int seqNum = token.getSequenceNumber();
|
|
peerTokenTracker.getProps(seqNum, prop);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets replay and sequencing information for a message token received
|
|
* form the peer.
|
|
*/
|
|
private void setSequencingAndReplayProps(MessageToken_v2 token,
|
|
MessageProp prop) {
|
|
if (replayDetState || sequenceDetState) {
|
|
int seqNum = token.getSequenceNumber();
|
|
peerTokenTracker.getProps(seqNum, prop);
|
|
}
|
|
}
|
|
|
|
private void checkPermission(String principal, String action) {
|
|
SecurityManager sm = System.getSecurityManager();
|
|
if (sm != null) {
|
|
ServicePermission perm =
|
|
new ServicePermission(principal, action);
|
|
sm.checkPermission(perm);
|
|
}
|
|
}
|
|
|
|
private static String getHexBytes(byte[] bytes, int pos, int len) {
|
|
|
|
StringBuffer sb = new StringBuffer();
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
int b1 = (bytes[i]>>4) & 0x0f;
|
|
int b2 = bytes[i] & 0x0f;
|
|
|
|
sb.append(Integer.toHexString(b1));
|
|
sb.append(Integer.toHexString(b2));
|
|
sb.append(' ');
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
private static String printState(int state) {
|
|
switch (state) {
|
|
case STATE_NEW:
|
|
return ("STATE_NEW");
|
|
case STATE_IN_PROCESS:
|
|
return ("STATE_IN_PROCESS");
|
|
case STATE_DONE:
|
|
return ("STATE_DONE");
|
|
case STATE_DELETED:
|
|
return ("STATE_DELETED");
|
|
default:
|
|
return ("Unknown state " + state);
|
|
}
|
|
}
|
|
|
|
GSSCaller getCaller() {
|
|
// Currently used by InitialToken only
|
|
return caller;
|
|
}
|
|
|
|
/**
|
|
* The session key returned by inquireSecContext(KRB5_INQ_SSPI_SESSION_KEY)
|
|
*/
|
|
static class KerberosSessionKey implements Key {
|
|
private static final long serialVersionUID = 699307378954123869L;
|
|
|
|
private final EncryptionKey key;
|
|
|
|
KerberosSessionKey(EncryptionKey key) {
|
|
this.key = key;
|
|
}
|
|
|
|
@Override
|
|
public String getAlgorithm() {
|
|
return Integer.toString(key.getEType());
|
|
}
|
|
|
|
@Override
|
|
public String getFormat() {
|
|
return "RAW";
|
|
}
|
|
|
|
@Override
|
|
public byte[] getEncoded() {
|
|
return key.getBytes().clone();
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
return "Kerberos session key: etype=" + key.getEType()
|
|
+ ", " + Krb5Util.keyInfo(key.getBytes());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the mechanism-specific attribute associated with {@code type}.
|
|
*/
|
|
public Object inquireSecContext(InquireType type)
|
|
throws GSSException {
|
|
if (!isEstablished()) {
|
|
throw new GSSException(GSSException.NO_CONTEXT, -1,
|
|
"Security context not established.");
|
|
}
|
|
switch (type) {
|
|
case KRB5_GET_SESSION_KEY:
|
|
return new KerberosSessionKey(key);
|
|
case KRB5_GET_TKT_FLAGS:
|
|
return tktFlags.clone();
|
|
case KRB5_GET_AUTHZ_DATA:
|
|
if (isInitiator()) {
|
|
throw new GSSException(GSSException.UNAVAILABLE, -1,
|
|
"AuthzData not available on initiator side.");
|
|
} else {
|
|
return (authzData==null)?null:authzData.clone();
|
|
}
|
|
case KRB5_GET_AUTHTIME:
|
|
return authTime;
|
|
}
|
|
throw new GSSException(GSSException.UNAVAILABLE, -1,
|
|
"Inquire type not supported.");
|
|
}
|
|
|
|
// Helpers for inquireSecContext
|
|
private boolean[] tktFlags;
|
|
private String authTime;
|
|
private com.sun.security.jgss.AuthorizationDataEntry[] authzData;
|
|
|
|
public void setTktFlags(boolean[] tktFlags) {
|
|
this.tktFlags = tktFlags;
|
|
}
|
|
|
|
public void setAuthTime(String authTime) {
|
|
this.authTime = authTime;
|
|
}
|
|
|
|
public void setAuthzData(com.sun.security.jgss.AuthorizationDataEntry[] authzData) {
|
|
this.authzData = authzData;
|
|
}
|
|
}
|