| /** |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.kerby.kerberos.kerb.gssapi.krb5; |
| |
| import com.sun.security.jgss.InquireType; |
| import org.apache.kerby.kerberos.kerb.KrbException; |
| import org.apache.kerby.kerberos.kerb.gssapi.KerbyMechFactory; |
| import org.apache.kerby.kerberos.kerb.request.ApRequest; |
| import org.apache.kerby.kerberos.kerb.response.ApResponse; |
| import org.apache.kerby.kerberos.kerb.type.ad.AuthorizationData; |
| import org.apache.kerby.kerberos.kerb.type.ap.ApRep; |
| import org.apache.kerby.kerberos.kerb.type.ap.ApReq; |
| import org.apache.kerby.kerberos.kerb.type.ap.Authenticator; |
| import org.apache.kerby.kerberos.kerb.type.base.EncryptionKey; |
| import org.apache.kerby.kerberos.kerb.type.base.PrincipalName; |
| import org.apache.kerby.kerberos.kerb.type.kdc.EncKdcRepPart; |
| import org.apache.kerby.kerberos.kerb.type.ticket.EncTicketPart; |
| import org.apache.kerby.kerberos.kerb.type.ticket.SgtTicket; |
| import org.apache.kerby.kerberos.kerb.type.ticket.TicketFlags; |
| import org.ietf.jgss.ChannelBinding; |
| import org.ietf.jgss.GSSContext; |
| import org.ietf.jgss.GSSException; |
| import org.ietf.jgss.MessageProp; |
| import org.ietf.jgss.Oid; |
| import sun.security.jgss.GSSCaller; |
| import sun.security.jgss.spi.GSSContextSpi; |
| import sun.security.jgss.spi.GSSCredentialSpi; |
| import sun.security.jgss.spi.GSSNameSpi; |
| |
| import javax.security.auth.kerberos.KerberosTicket; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.security.Provider; |
| |
| @SuppressWarnings("PMD") |
| public class KerbyContext implements GSSContextSpi { |
| |
| private static final int STATE_NONE = 0; |
| private static final int STATE_ESTABLISHING = 1; |
| private static final int STATE_ESTABLISHED = 2; |
| private static final int STATE_DESTROYED = 3; |
| |
| private int ctxState = STATE_NONE; |
| |
| private final GSSCaller caller; |
| private KerbyCredElement myCred; |
| private boolean initiator; |
| private KerbyNameElement myName; |
| private KerbyNameElement peerName; |
| private int lifeTime; |
| private ChannelBinding channelBinding; |
| |
| private boolean mutualAuth = true; |
| private boolean replayDet = true; |
| private boolean sequenceDet = true; |
| private boolean credDeleg = false; |
| private boolean confState = true; |
| private boolean integState = true; |
| private boolean delegPolicy = false; |
| |
| public static final int INVALID_KEY = 0; |
| public static final int SESSION_KEY = 1; |
| public static final int INITIATOR_SUBKEY = 2; |
| public static final int ACCEPTOR_SUBKEY = 4; |
| private int keyComesFrom = INVALID_KEY; |
| |
| private EncryptionKey sessionKey; // used between client and app server |
| private TicketFlags ticketFlags; |
| private ApReq outApReq; |
| |
| // Called on initiator's side. |
| public KerbyContext(GSSCaller caller, KerbyNameElement peerName, KerbyCredElement 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; |
| |
| mySequenceNumberLock = new Object(); |
| peerSequenceNumberLock = new Object(); |
| } |
| |
| public KerbyContext(GSSCaller caller, KerbyAcceptCred myCred) |
| throws GSSException { |
| this.caller = caller; |
| this.myCred = myCred; |
| this.initiator = false; |
| |
| mySequenceNumberLock = new Object(); |
| peerSequenceNumberLock = new Object(); |
| } |
| |
| public KerbyContext(GSSCaller caller, byte[] interProcessToken) |
| throws GSSException { |
| throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported feature"); |
| } |
| |
| public Provider getProvider() { |
| return new org.apache.kerby.kerberos.kerb.gssapi.Provider(); |
| } |
| |
| public void requestLifetime(int lifeTime) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| this.lifeTime = lifeTime; |
| } |
| } |
| |
| public void requestMutualAuth(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| mutualAuth = state; |
| } |
| } |
| |
| public void requestReplayDet(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| replayDet = state; |
| } |
| } |
| |
| public void requestSequenceDet(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| replayDet = state; |
| } |
| } |
| |
| public void requestCredDeleg(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator() && myCred == null) { |
| credDeleg = state; |
| } |
| } |
| |
| public void requestAnonymity(boolean state) throws GSSException { |
| // anonymous context not supported |
| } |
| |
| public void requestConf(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| confState = state; |
| } |
| } |
| |
| public void requestInteg(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| integState = state; |
| } |
| } |
| |
| public void requestDelegPolicy(boolean state) throws GSSException { |
| if (ctxState == STATE_NONE && isInitiator()) { |
| delegPolicy = state; |
| } |
| } |
| |
| public void setChannelBinding(ChannelBinding cb) throws GSSException { |
| this.channelBinding = cb; |
| } |
| |
| public boolean getCredDelegState() { |
| return credDeleg; |
| } |
| |
| public boolean getMutualAuthState() { |
| return mutualAuth; |
| } |
| |
| public boolean getReplayDetState() { |
| return replayDet || sequenceDet; |
| } |
| |
| public boolean getSequenceDetState() { |
| return sequenceDet; |
| } |
| |
| public boolean getAnonymityState() { |
| return false; |
| } |
| |
| public boolean getDelegPolicyState() { |
| return delegPolicy; |
| } |
| |
| public boolean isTransferable() throws GSSException { |
| return false; |
| } |
| |
| public boolean isProtReady() { |
| return ctxState == STATE_ESTABLISHED; |
| } |
| |
| public boolean isInitiator() { |
| return initiator; |
| } |
| |
| public boolean getConfState() { |
| return confState; |
| } |
| |
| public boolean getIntegState() { |
| return integState; |
| } |
| |
| public int getLifetime() { |
| return GSSContext.INDEFINITE_LIFETIME; |
| } |
| |
| public boolean isEstablished() { |
| return ctxState == STATE_ESTABLISHED; |
| } |
| |
| public GSSNameSpi getSrcName() throws GSSException { |
| return isInitiator() ? myName : peerName; |
| } |
| |
| public GSSNameSpi getTargName() throws GSSException { |
| return !isInitiator() ? myName : peerName; |
| } |
| |
| public Oid getMech() throws GSSException { |
| return KerbyMechFactory.getOid(); |
| } |
| |
| public GSSCredentialSpi getDelegCred() throws GSSException { |
| throw new GSSException(GSSException.FAILURE, -1, "API not implemented"); // TODO: |
| } |
| |
| public byte[] initSecContext(InputStream is, int mechTokenSize) |
| throws GSSException { |
| if (!isInitiator()) { |
| throw new GSSException(GSSException.FAILURE, -1, "initSecContext called on acceptor"); |
| } |
| |
| byte[] ret = null; |
| |
| if (ctxState == STATE_NONE) { |
| |
| if (!myCred.isInitiatorCredential()) { |
| throw new GSSException(GSSException.NO_CRED, -1, "No TGT available"); |
| } |
| |
| // check if service ticket already exists |
| // if not, prepare to get it through TGS_REQ |
| SgtTicket sgtTicket = null; |
| String serviceName = peerName.getPrincipalName().getName(); |
| myName = (KerbyNameElement) myCred.getName(); |
| PrincipalName clientPrincipal = myName.getPrincipalName(); |
| |
| sgtTicket = KerbyUtil.getSgtCredentialFromContext(caller, clientPrincipal.getName(), serviceName); |
| |
| if (sgtTicket == null) { |
| sgtTicket = KerbyUtil.applySgtCredential(((KerbyInitCred) myCred).ticket, serviceName); |
| |
| // add this service credential to context |
| final KerberosTicket ticket = |
| KerbyUtil.convertKrbTicketToKerberosTicket(sgtTicket, myName.getPrincipalName().getName()); |
| CredUtils.addCredentialToSubject(ticket); |
| } |
| |
| ApRequest apRequest = new ApRequest(clientPrincipal, sgtTicket); |
| try { |
| outApReq = apRequest.getApReq(); |
| } catch (KrbException e) { |
| throw new GSSException(GSSException.FAILURE, -1, "Generate ApReq failed: " + e.getMessage()); |
| } |
| setupInitiatorContext(sgtTicket, apRequest); |
| try { |
| ret = outApReq.encode(); |
| } catch (IOException e) { |
| throw new GSSException(GSSException.FAILURE, -1, "Generate ApReq bytes failed: " + e.getMessage()); |
| } |
| |
| ctxState = STATE_ESTABLISHING; |
| if (!getMutualAuthState()) { |
| ctxState = STATE_ESTABLISHED; |
| } |
| |
| } else if (ctxState == STATE_ESTABLISHING) { |
| verifyServerToken(is, mechTokenSize); |
| outApReq = null; |
| ctxState = STATE_ESTABLISHED; |
| } |
| return ret; |
| } |
| |
| private void setupInitiatorContext(SgtTicket sgt, ApRequest apRequest) throws GSSException { |
| EncKdcRepPart encKdcRepPart = sgt.getEncKdcRepPart(); |
| TicketFlags ticketFlags = encKdcRepPart.getFlags(); |
| setTicketFlags(ticketFlags); |
| |
| setAuthTime(encKdcRepPart.getAuthTime().toString()); |
| |
| Authenticator auth; |
| try { |
| auth = apRequest.getApReq().getAuthenticator(); |
| } catch (KrbException e) { |
| throw new GSSException(GSSException.FAILURE, -1, "ApReq failed in Initiator"); |
| } |
| setMySequenceNumber(auth.getSeqNumber()); |
| |
| EncryptionKey subKey = auth.getSubKey(); |
| if (subKey != null) { |
| setSessionKey(subKey, KerbyContext.INITIATOR_SUBKEY); |
| } else { |
| setSessionKey(sgt.getSessionKey(), KerbyContext.SESSION_KEY); |
| } |
| |
| if (!getMutualAuthState()) { |
| setPeerSequenceNumber(0); |
| } |
| } |
| |
| /** |
| * Verify the AP_REP from server and set context accordingly |
| * @param is |
| * @param mechTokenSize |
| * @return |
| * @throws GSSException |
| * @throws IOException |
| */ |
| private void verifyServerToken(InputStream is, int mechTokenSize) |
| throws GSSException { |
| byte[] token = new byte[mechTokenSize]; |
| ApRep apRep; |
| try { |
| is.read(token); |
| apRep = new ApRep(); |
| apRep.decode(token); |
| } catch (IOException e) { |
| throw new GSSException(GSSException.FAILURE, -1, "Invalid ApRep " + e.getMessage()); |
| } |
| |
| try { |
| ApResponse.validate(getSessionKey(), apRep, outApReq); |
| } catch (KrbException e) { |
| throw new GSSException(GSSException.UNAUTHORIZED, -1, "ApRep verification failed"); |
| } |
| |
| EncryptionKey key = apRep.getEncRepPart().getSubkey(); |
| if (key != null) { |
| setSessionKey(key, ACCEPTOR_SUBKEY); |
| } |
| |
| int seqNum = apRep.getEncRepPart().getSeqNumber(); |
| setPeerSequenceNumber(seqNum == -1 ? 0 : seqNum); |
| } |
| |
| public byte[] acceptSecContext(InputStream is, int mechTokenSize) |
| throws GSSException { |
| byte[] ret = null; |
| |
| if (isInitiator()) { |
| throw new GSSException(GSSException.FAILURE, -1, "acceptSecContext called on initiator"); |
| } |
| |
| if (ctxState == STATE_NONE) { |
| ctxState = STATE_ESTABLISHING; |
| if (!myCred.isAcceptorCredential()) { |
| throw new GSSException(GSSException.FAILURE, -1, "No acceptor credential available"); |
| } |
| |
| KerbyAcceptCred acceptCred = (KerbyAcceptCred) myCred; |
| CredUtils.checkPrincipalPermission( |
| ((KerbyNameElement) acceptCred.getName()).getPrincipalName().getName(), "accept"); |
| |
| if (getMutualAuthState()) { |
| ret = verifyClientToken(acceptCred, is, mechTokenSize); |
| } |
| |
| myCred = null; |
| ctxState = STATE_ESTABLISHED; |
| } |
| |
| return ret; |
| } |
| |
| private byte[] verifyClientToken(KerbyAcceptCred acceptCred, InputStream is, int mechTokenSize) |
| throws GSSException { |
| byte[] token = new byte[mechTokenSize]; |
| ApReq apReq; |
| try { |
| is.read(token); |
| apReq = new ApReq(); |
| apReq.decode(token); |
| } catch (IOException e) { |
| throw new GSSException(GSSException.UNAUTHORIZED, -1, "ApReq invalid" + e.getMessage()); |
| } |
| |
| int kvno = apReq.getTicket().getEncryptedEncPart().getKvno(); |
| int encryptType = apReq.getTicket().getEncryptedEncPart().getEType().getValue(); |
| |
| // Get server key from credential |
| EncryptionKey serverKey = KerbyUtil.getEncryptionKey(acceptCred.getKeys(), encryptType, kvno); |
| if (serverKey == null) { |
| throw new GSSException(GSSException.FAILURE, -1, "Server key not found"); |
| } |
| |
| try { |
| ApRequest.validate(serverKey, apReq, channelBinding.getInitiatorAddress(), 5 * 60 * 1000); |
| } catch (KrbException e) { |
| throw new GSSException(GSSException.UNAUTHORIZED, -1, "ApReq verification failed: " + e.getMessage()); |
| } |
| |
| ApResponse apResponse = new ApResponse(apReq); |
| ApRep apRep; |
| try { |
| apRep = apResponse.getApRep(); |
| } catch (KrbException e) { |
| throw new GSSException(GSSException.UNAUTHORIZED, -1, "Generate ApRep failed"); |
| } |
| |
| EncTicketPart apReqTicketEncPart = apReq.getTicket().getEncPart(); |
| |
| EncryptionKey ssKey = apReqTicketEncPart.getKey(); |
| Authenticator auth = apReq.getAuthenticator(); |
| EncryptionKey subKey = auth.getSubKey(); |
| |
| if (subKey != null) { |
| setSessionKey(subKey, INITIATOR_SUBKEY); |
| } else { |
| setSessionKey(ssKey, SESSION_KEY); |
| } |
| |
| // initial seqNumber |
| int seqNumber = auth.getSeqNumber(); |
| setMySequenceNumber(seqNumber); |
| // initial authtime, tktflags, authdata, |
| setAuthTime(apReqTicketEncPart.getAuthTime().toString()); |
| setTicketFlags(apReqTicketEncPart.getFlags()); |
| setAuthData(apReqTicketEncPart.getAuthorizationData()); |
| |
| byte[] ret = null; |
| try { |
| ret = apRep.encode(); |
| } catch (IOException e) { |
| throw new GSSException(GSSException.FAILURE, -1, "Generate ApRep bytes failed:" + e.getMessage()); |
| } |
| return ret; |
| } |
| |
| public int getWrapSizeLimit(int qop, boolean confReq, int maxTokSize) |
| throws GSSException { |
| return 65536; // TODO: to be implemented |
| } |
| |
| public void wrap(InputStream is, OutputStream os, MessageProp msgProp) |
| throws GSSException { |
| if (ctxState != STATE_ESTABLISHED) { |
| throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for wrap"); |
| } |
| throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported method"); // TODO: to be implemented |
| } |
| |
| public byte[] wrap(byte[] inBuf, int offset, int len, |
| MessageProp msgProp) throws GSSException { |
| if (ctxState != STATE_ESTABLISHED) { |
| throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for wrap"); |
| } |
| return null; // TODO: to be implemented |
| } |
| |
| public void unwrap(InputStream is, OutputStream os, |
| MessageProp msgProp) throws GSSException { |
| throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported method"); // TODO: to be implemented |
| } |
| |
| public byte[] unwrap(byte[] inBuf, int offset, int len, |
| MessageProp msgProp) throws GSSException { |
| if (ctxState != STATE_ESTABLISHED) { |
| throw new GSSException(GSSException.NO_CONTEXT, -1, "Context invalid for unwrap"); |
| } |
| return null; // TODO: to be implemented |
| } |
| |
| public void getMIC(InputStream is, OutputStream os, |
| MessageProp msgProp) |
| throws GSSException { |
| } |
| |
| public byte[] getMIC(byte[] inMsg, int offset, int len, |
| MessageProp msgProp) throws GSSException { |
| return null; // TODO: to be implemented |
| } |
| |
| public void verifyMIC(InputStream is, InputStream msgStr, |
| MessageProp msgProp) throws GSSException { |
| } |
| |
| public void verifyMIC(byte[]inTok, int tokOffset, int tokLen, |
| byte[] inMsg, int msgOffset, int msgLen, |
| MessageProp msgProp) throws GSSException { |
| } |
| |
| public byte[] export() throws GSSException { |
| throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported export method"); |
| } |
| |
| public void dispose() throws GSSException { |
| ctxState = STATE_DESTROYED; |
| setSessionKey(null, 0); |
| peerName = null; |
| myCred = null; |
| myName = null; |
| } |
| |
| |
| private String authTime; |
| private void setAuthTime(String authTime) { |
| this.authTime = authTime; |
| } |
| |
| public Object inquireSecContext(InquireType type) throws GSSException { |
| if (ctxState != STATE_ESTABLISHED) { |
| throw new GSSException(GSSException.NO_CONTEXT, -1, "Invalid context"); |
| } |
| |
| switch (type) { |
| case KRB5_GET_SESSION_KEY: |
| return getSessionKey(); |
| case KRB5_GET_TKT_FLAGS: |
| return KerbyUtil.ticketFlagsToBooleans(ticketFlags); |
| case KRB5_GET_AUTHZ_DATA: |
| if (isInitiator()) { |
| throw new GSSException(GSSException.UNAVAILABLE, -1, |
| "Authorization data not available for initiator"); |
| } else { |
| return KerbyUtil.kerbyAuthorizationDataToJgssAuthorizationDataEntries(authData); |
| } |
| case KRB5_GET_AUTHTIME: |
| return authTime; |
| } |
| throw new GSSException(GSSException.UNAVAILABLE, -1, "Unsupported inquire type"); |
| } |
| |
| |
| // functions not belong to SPI |
| private void setSessionKey(EncryptionKey encryptionKey, int keyComesFrom) { |
| this.sessionKey = encryptionKey; |
| this.keyComesFrom = keyComesFrom; |
| } |
| |
| public int getKeyComesFrom() { |
| return keyComesFrom; |
| } |
| |
| private EncryptionKey getSessionKey() { |
| return sessionKey; |
| } |
| |
| private void setTicketFlags(TicketFlags ticketFlags) { |
| this.ticketFlags = ticketFlags; |
| } |
| |
| private AuthorizationData authData; |
| private void setAuthData(AuthorizationData authData) { |
| this.authData = authData; |
| } |
| |
| |
| private int mySequenceNumber; |
| private int peerSequenceNumber; |
| private Object mySequenceNumberLock; |
| private Object peerSequenceNumberLock; |
| |
| public void setMySequenceNumber(int sequenceNumber) { |
| synchronized (mySequenceNumberLock) { |
| mySequenceNumber = sequenceNumber; |
| } |
| } |
| |
| public int incMySequenceNumber() { |
| synchronized (mySequenceNumberLock) { |
| return mySequenceNumber++; |
| } |
| } |
| |
| public void setPeerSequenceNumber(int sequenceNumber) { |
| synchronized (peerSequenceNumberLock) { |
| peerSequenceNumber = sequenceNumber; |
| } |
| } |
| |
| public int incPeerSequenceNumber() { |
| synchronized (peerSequenceNumberLock) { |
| return peerSequenceNumber++; |
| } |
| } |
| } |