/* * Copyright (c) 2000, 2015, 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; import org.ietf.jgss.*; import sun.security.jgss.spi.*; import sun.security.util.ObjectIdentifier; import java.io.InputStream; import java.io.OutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Modifier; /** * This class represents the JGSS security context and its associated * operations. JGSS security contexts are established between * peers using locally established credentials. Multiple contexts * may exist simultaneously between a pair of peers, using the same * or different set of credentials. The JGSS is independent of * the underlying transport protocols and depends on its callers to * transport the tokens between peers. * <p> * The context object can be thought of as having 3 implicit states: * before it is established, during its context establishment, and * after a fully established context exists. * <p> * Before the context establishment phase is initiated, the context * initiator may request specific characteristics desired of the * established context. These can be set using the set methods. After the * context is established, the caller can check the actual characteristic * and services offered by the context using the query methods. * <p> * The context establishment phase begins with the first call to the * initSecContext method by the context initiator. During this phase the * initSecContext and acceptSecContext methods will produce GSS-API * authentication tokens which the calling application needs to send to its * peer. The initSecContext and acceptSecContext methods may * return a CONTINUE_NEEDED code which indicates that a token is needed * from its peer in order to continue the context establishment phase. A * return code of COMPLETE signals that the local end of the context is * established. This may still require that a token be sent to the peer, * depending if one is produced by GSS-API. The isEstablished method can * also be used to determine if the local end of the context has been * fully established. During the context establishment phase, the * isProtReady method may be called to determine if the context can be * used for the per-message operations. This allows implementation to * use per-message operations on contexts which aren't fully established. * <p> * After the context has been established or the isProtReady method * returns "true", the query routines can be invoked to determine the actual * characteristics and services of the established context. The * application can also start using the per-message methods of wrap and * getMIC to obtain cryptographic operations on application supplied data. * <p> * When the context is no longer needed, the application should call * dispose to release any system resources the context may be using. * <DL><DT><B>RFC 2078</b> * <DD>This class corresponds to the context level calls together with * the per message calls of RFC 2078. The gss_init_sec_context and * gss_accept_sec_context calls have been made simpler by only taking * required parameters. The context can have its properties set before * the first call to initSecContext. The supplementary status codes for the * per-message operations are returned in an instance of the MessageProp * class, which is used as an argument in these calls.</dl> */ public class GSSContextImpl implements GSSContext { private GSSManagerImpl gssManager; private boolean initiator; // private flags for the context state private static final int PRE_INIT = 1; private static final int IN_PROGRESS = 2; private static final int READY = 3; private static final int DELETED = 4; // instance variables private int currentState = PRE_INIT; private GSSContextSpi mechCtxt = null; private Oid mechOid = null; private ObjectIdentifier objId = null; private GSSCredentialImpl myCred = null; private GSSNameImpl srcName = null; private GSSNameImpl targName = null; private int reqLifetime = INDEFINITE_LIFETIME; private ChannelBinding channelBindings = null; private boolean reqConfState = true; private boolean reqIntegState = true; private boolean reqMutualAuthState = true; private boolean reqReplayDetState = true; private boolean reqSequenceDetState = true; private boolean reqCredDelegState = false; private boolean reqAnonState = false; private boolean reqDelegPolicyState = false; public GSSContextImpl() { // Useless } // Used by new ExtendedGSSContext.ExtendedGSSContextImpl(ctxt) protected GSSContextImpl(GSSContextImpl src) { for (Field f: GSSContextImpl.class.getDeclaredFields()) { if (!Modifier.isStatic(f.getModifiers())) { try { f.set(this, f.get(src)); } catch (Exception e) { throw new RuntimeException(e); } } } } /** * Creates a GSSContextImp on the context initiator's side. */ public GSSContextImpl(GSSManagerImpl gssManager, GSSName peer, Oid mech, GSSCredential myCred, int lifetime) throws GSSException { if ((peer == null) || !(peer instanceof GSSNameImpl)) { throw new GSSException(GSSException.BAD_NAME); } if (mech == null) mech = ProviderList.DEFAULT_MECH_OID; this.gssManager = gssManager; this.myCred = (GSSCredentialImpl) myCred; // XXX Check first reqLifetime = lifetime; targName = (GSSNameImpl)peer; this.mechOid = mech; initiator = true; } /** * Creates a GSSContextImpl on the context acceptor's side. */ public GSSContextImpl(GSSManagerImpl gssManager, GSSCredential myCred) throws GSSException { this.gssManager = gssManager; this.myCred = (GSSCredentialImpl) myCred; // XXX Check first initiator = false; } /** * Creates a GSSContextImpl out of a previously exported * GSSContext. * * @see #isTransferable */ public GSSContextImpl(GSSManagerImpl gssManager, byte[] interProcessToken) throws GSSException { this.gssManager = gssManager; mechCtxt = gssManager.getMechanismContext(interProcessToken); initiator = mechCtxt.isInitiator(); this.mechOid = mechCtxt.getMech(); } public byte[] initSecContext(byte inputBuf[], int offset, int len) throws GSSException { /* * Size of ByteArrayOutputStream will double each time that extra * bytes are to be written. Usually, without delegation, a GSS * initial token containing the Kerberos AP-REQ is between 400 and * 600 bytes. */ ByteArrayOutputStream bos = new ByteArrayOutputStream(600); ByteArrayInputStream bin = new ByteArrayInputStream(inputBuf, offset, len); int size = initSecContext(bin, bos); return (size == 0? null : bos.toByteArray()); } public int initSecContext(InputStream inStream, OutputStream outStream) throws GSSException { if (mechCtxt != null && currentState != IN_PROGRESS) { throw new GSSExceptionImpl(GSSException.FAILURE, "Illegal call to initSecContext"); } GSSHeader gssHeader = null; int inTokenLen = -1; GSSCredentialSpi credElement = null; boolean firstToken = false; try { if (mechCtxt == null) { if (myCred != null) { try { credElement = myCred.getElement(mechOid, true); } catch (GSSException ge) { if (GSSUtil.isSpNegoMech(mechOid) && ge.getMajor() == GSSException.NO_CRED) { credElement = myCred.getElement (myCred.getMechs()[0], true); } else { throw ge; } } } GSSNameSpi nameElement = targName.getElement(mechOid); mechCtxt = gssManager.getMechanismContext(nameElement, credElement, reqLifetime, mechOid); mechCtxt.requestConf(reqConfState); mechCtxt.requestInteg(reqIntegState); mechCtxt.requestCredDeleg(reqCredDelegState); mechCtxt.requestMutualAuth(reqMutualAuthState); mechCtxt.requestReplayDet(reqReplayDetState); mechCtxt.requestSequenceDet(reqSequenceDetState); mechCtxt.requestAnonymity(reqAnonState); mechCtxt.setChannelBinding(channelBindings); mechCtxt.requestDelegPolicy(reqDelegPolicyState); objId = new ObjectIdentifier(mechOid.toString()); currentState = IN_PROGRESS; firstToken = true; } else { if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || GSSUtil.isSpNegoMech(mechOid)) { // do not parse GSS header for native provider or SPNEGO // mech } else { // parse GSS header gssHeader = new GSSHeader(inStream); if (!gssHeader.getOid().equals(objId)) throw new GSSExceptionImpl (GSSException.DEFECTIVE_TOKEN, "Mechanism not equal to " + mechOid.toString() + " in initSecContext token"); inTokenLen = gssHeader.getMechTokenLength(); } } byte[] obuf = mechCtxt.initSecContext(inStream, inTokenLen); int retVal = 0; if (obuf != null) { retVal = obuf.length; if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || (!firstToken && GSSUtil.isSpNegoMech(mechOid))) { // do not add GSS header for native provider or SPNEGO // except for the first SPNEGO token } else { // add GSS header gssHeader = new GSSHeader(objId, obuf.length); retVal += gssHeader.encode(outStream); } outStream.write(obuf); } if (mechCtxt.isEstablished()) currentState = READY; return retVal; } catch (IOException e) { throw new GSSExceptionImpl(GSSException.DEFECTIVE_TOKEN, e.getMessage()); } } public byte[] acceptSecContext(byte inTok[], int offset, int len) throws GSSException { /* * Usually initial GSS token containing a Kerberos AP-REP is less * than 100 bytes. */ ByteArrayOutputStream bos = new ByteArrayOutputStream(100); acceptSecContext(new ByteArrayInputStream(inTok, offset, len), bos); byte[] out = bos.toByteArray(); return (out.length == 0) ? null : out; } public void acceptSecContext(InputStream inStream, OutputStream outStream) throws GSSException { if (mechCtxt != null && currentState != IN_PROGRESS) { throw new GSSExceptionImpl(GSSException.FAILURE, "Illegal call to acceptSecContext"); } GSSHeader gssHeader = null; int inTokenLen = -1; GSSCredentialSpi credElement = null; try { if (mechCtxt == null) { // mechOid will be null for an acceptor's context gssHeader = new GSSHeader(inStream); inTokenLen = gssHeader.getMechTokenLength(); /* * Convert ObjectIdentifier to Oid */ objId = gssHeader.getOid(); mechOid = new Oid(objId.toString()); // System.out.println("Entered GSSContextImpl.acceptSecContext" // + " with mechanism = " + mechOid); if (myCred != null) { credElement = myCred.getElement(mechOid, false); } mechCtxt = gssManager.getMechanismContext(credElement, mechOid); mechCtxt.setChannelBinding(channelBindings); currentState = IN_PROGRESS; } else { if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || (GSSUtil.isSpNegoMech(mechOid))) { // do not parse GSS header for native provider and SPNEGO } else { // parse GSS Header gssHeader = new GSSHeader(inStream); if (!gssHeader.getOid().equals(objId)) throw new GSSExceptionImpl (GSSException.DEFECTIVE_TOKEN, "Mechanism not equal to " + mechOid.toString() + " in acceptSecContext token"); inTokenLen = gssHeader.getMechTokenLength(); } } byte[] obuf = mechCtxt.acceptSecContext(inStream, inTokenLen); if (obuf != null) { int retVal = obuf.length; if (mechCtxt.getProvider().getName().equals("SunNativeGSS") || (GSSUtil.isSpNegoMech(mechOid))) { // do not add GSS header for native provider and SPNEGO } else { // add GSS header gssHeader = new GSSHeader(objId, obuf.length); retVal += gssHeader.encode(outStream); } outStream.write(obuf); } if (mechCtxt.isEstablished()) { currentState = READY; } } catch (IOException e) { throw new GSSExceptionImpl(GSSException.DEFECTIVE_TOKEN, e.getMessage()); } } public boolean isEstablished() { if (mechCtxt == null) return false; else return (currentState == READY); } public int getWrapSizeLimit(int qop, boolean confReq, int maxTokenSize) throws GSSException { if (mechCtxt != null) return mechCtxt.getWrapSizeLimit(qop, confReq, maxTokenSize); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public byte[] wrap(byte inBuf[], int offset, int len, MessageProp msgProp) throws GSSException { if (mechCtxt != null) return mechCtxt.wrap(inBuf, offset, len, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public void wrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException { if (mechCtxt != null) mechCtxt.wrap(inStream, outStream, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public byte [] unwrap(byte[] inBuf, int offset, int len, MessageProp msgProp) throws GSSException { if (mechCtxt != null) return mechCtxt.unwrap(inBuf, offset, len, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public void unwrap(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException { if (mechCtxt != null) mechCtxt.unwrap(inStream, outStream, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public byte[] getMIC(byte []inMsg, int offset, int len, MessageProp msgProp) throws GSSException { if (mechCtxt != null) return mechCtxt.getMIC(inMsg, offset, len, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public void getMIC(InputStream inStream, OutputStream outStream, MessageProp msgProp) throws GSSException { if (mechCtxt != null) mechCtxt.getMIC(inStream, outStream, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public void verifyMIC(byte[] inTok, int tokOffset, int tokLen, byte[] inMsg, int msgOffset, int msgLen, MessageProp msgProp) throws GSSException { if (mechCtxt != null) mechCtxt.verifyMIC(inTok, tokOffset, tokLen, inMsg, msgOffset, msgLen, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public void verifyMIC(InputStream tokStream, InputStream msgStream, MessageProp msgProp) throws GSSException { if (mechCtxt != null) mechCtxt.verifyMIC(tokStream, msgStream, msgProp); else throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); } public byte[] export() throws GSSException { // Defaults to null to match old behavior byte[] result = null; // Only allow context export from native provider since JGSS // still has not defined its own interprocess token format if (mechCtxt.isTransferable() && mechCtxt.getProvider().getName().equals("SunNativeGSS")) { result = mechCtxt.export(); } return result; } public void requestMutualAuth(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqMutualAuthState = state; } public void requestReplayDet(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqReplayDetState = state; } public void requestSequenceDet(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqSequenceDetState = state; } public void requestCredDeleg(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqCredDelegState = state; } public void requestAnonymity(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqAnonState = state; } public void requestConf(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqConfState = state; } public void requestInteg(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqIntegState = state; } public void requestLifetime(int lifetime) throws GSSException { if (mechCtxt == null && initiator) reqLifetime = lifetime; } public void setChannelBinding(ChannelBinding channelBindings) throws GSSException { if (mechCtxt == null) this.channelBindings = channelBindings; } public boolean getCredDelegState() { if (mechCtxt != null) return mechCtxt.getCredDelegState(); else return reqCredDelegState; } public boolean getMutualAuthState() { if (mechCtxt != null) return mechCtxt.getMutualAuthState(); else return reqMutualAuthState; } public boolean getReplayDetState() { if (mechCtxt != null) return mechCtxt.getReplayDetState(); else return reqReplayDetState; } public boolean getSequenceDetState() { if (mechCtxt != null) return mechCtxt.getSequenceDetState(); else return reqSequenceDetState; } public boolean getAnonymityState() { if (mechCtxt != null) return mechCtxt.getAnonymityState(); else return reqAnonState; } public boolean isTransferable() throws GSSException { if (mechCtxt != null) return mechCtxt.isTransferable(); else return false; } public boolean isProtReady() { if (mechCtxt != null) return mechCtxt.isProtReady(); else return false; } public boolean getConfState() { if (mechCtxt != null) return mechCtxt.getConfState(); else return reqConfState; } public boolean getIntegState() { if (mechCtxt != null) return mechCtxt.getIntegState(); else return reqIntegState; } public int getLifetime() { if (mechCtxt != null) return mechCtxt.getLifetime(); else return reqLifetime; } public GSSName getSrcName() throws GSSException { if (srcName == null) { srcName = GSSNameImpl.wrapElement (gssManager, mechCtxt.getSrcName()); } return srcName; } public GSSName getTargName() throws GSSException { if (targName == null) { targName = GSSNameImpl.wrapElement (gssManager, mechCtxt.getTargName()); } return targName; } public Oid getMech() throws GSSException { if (mechCtxt != null) { return mechCtxt.getMech(); } return mechOid; } public GSSCredential getDelegCred() throws GSSException { if (mechCtxt == null) throw new GSSExceptionImpl(GSSException.NO_CONTEXT, "No mechanism context yet!"); GSSCredentialSpi delCredElement = mechCtxt.getDelegCred(); return (delCredElement == null ? null : GSSManagerImpl.wrap(new GSSCredentialImpl(gssManager, delCredElement))); } public boolean isInitiator() throws GSSException { return initiator; } public void dispose() throws GSSException { currentState = DELETED; if (mechCtxt != null) { mechCtxt.dispose(); mechCtxt = null; } myCred = null; srcName = null; targName = null; } // ExtendedGSSContext methods: public Object inquireSecContext(String type) throws GSSException { if (mechCtxt == null) { throw new GSSException(GSSException.NO_CONTEXT); } return mechCtxt.inquireSecContext(type); } public void requestDelegPolicy(boolean state) throws GSSException { if (mechCtxt == null && initiator) reqDelegPolicyState = state; } public boolean getDelegPolicyState() { if (mechCtxt != null) return mechCtxt.getDelegPolicyState(); else return reqDelegPolicyState; } }