| /* |
| * 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.directory.server.kerberos.kdc.ticketgrant; |
| |
| |
| import java.net.InetAddress; |
| import java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| import javax.security.auth.kerberos.KerberosPrincipal; |
| |
| import org.apache.directory.api.asn1.EncoderException; |
| import org.apache.directory.api.ldap.model.constants.Loggers; |
| import org.apache.directory.server.i18n.I18n; |
| import org.apache.directory.server.kerberos.KerberosConfig; |
| import org.apache.directory.server.kerberos.kdc.KdcContext; |
| import org.apache.directory.server.kerberos.protocol.codec.KerberosDecoder; |
| import org.apache.directory.server.kerberos.shared.crypto.checksum.ChecksumHandler; |
| import org.apache.directory.server.kerberos.shared.crypto.encryption.CipherTextHandler; |
| import org.apache.directory.server.kerberos.shared.crypto.encryption.KeyUsage; |
| import org.apache.directory.server.kerberos.shared.crypto.encryption.RandomKeyFactory; |
| import org.apache.directory.server.kerberos.shared.replay.ReplayCache; |
| import org.apache.directory.server.kerberos.shared.store.PrincipalStore; |
| import org.apache.directory.server.kerberos.shared.store.PrincipalStoreEntry; |
| import org.apache.directory.shared.kerberos.KerberosConstants; |
| import org.apache.directory.shared.kerberos.KerberosTime; |
| import org.apache.directory.shared.kerberos.KerberosUtils; |
| import org.apache.directory.shared.kerberos.codec.options.KdcOptions; |
| import org.apache.directory.shared.kerberos.codec.types.EncryptionType; |
| import org.apache.directory.shared.kerberos.codec.types.LastReqType; |
| import org.apache.directory.shared.kerberos.codec.types.PaDataType; |
| import org.apache.directory.shared.kerberos.components.AuthorizationData; |
| import org.apache.directory.shared.kerberos.components.Checksum; |
| import org.apache.directory.shared.kerberos.components.EncKdcRepPart; |
| import org.apache.directory.shared.kerberos.components.EncTicketPart; |
| import org.apache.directory.shared.kerberos.components.EncryptedData; |
| import org.apache.directory.shared.kerberos.components.EncryptionKey; |
| import org.apache.directory.shared.kerberos.components.HostAddress; |
| import org.apache.directory.shared.kerberos.components.HostAddresses; |
| import org.apache.directory.shared.kerberos.components.KdcReq; |
| import org.apache.directory.shared.kerberos.components.KdcReqBody; |
| import org.apache.directory.shared.kerberos.components.LastReq; |
| import org.apache.directory.shared.kerberos.components.LastReqEntry; |
| import org.apache.directory.shared.kerberos.components.PaData; |
| import org.apache.directory.shared.kerberos.components.PrincipalName; |
| import org.apache.directory.shared.kerberos.crypto.checksum.ChecksumType; |
| import org.apache.directory.shared.kerberos.exceptions.ErrorType; |
| import org.apache.directory.shared.kerberos.exceptions.InvalidTicketException; |
| import org.apache.directory.shared.kerberos.exceptions.KerberosException; |
| import org.apache.directory.shared.kerberos.flags.TicketFlag; |
| import org.apache.directory.shared.kerberos.messages.ApReq; |
| import org.apache.directory.shared.kerberos.messages.Authenticator; |
| import org.apache.directory.shared.kerberos.messages.EncTgsRepPart; |
| import org.apache.directory.shared.kerberos.messages.TgsRep; |
| import org.apache.directory.shared.kerberos.messages.Ticket; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class TicketGrantingService |
| { |
| |
| /** the log for this class */ |
| private static final Logger LOG_KRB = LoggerFactory.getLogger( Loggers.KERBEROS_LOG.getName() ); |
| |
| private static final CipherTextHandler cipherTextHandler = new CipherTextHandler(); |
| |
| private static final String SERVICE_NAME = "Ticket-Granting Service (TGS)"; |
| |
| private static final ChecksumHandler checksumHandler = new ChecksumHandler(); |
| |
| |
| public static void execute( TicketGrantingContext tgsContext ) throws Exception |
| { |
| if ( LOG_KRB.isDebugEnabled() ) |
| { |
| monitorRequest( tgsContext ); |
| } |
| |
| configureTicketGranting( tgsContext ); |
| selectEncryptionType( tgsContext ); |
| getAuthHeader( tgsContext ); |
| // commenting to allow cross-realm auth |
| //verifyTgt( tgsContext ); |
| getTicketPrincipalEntry( tgsContext ); |
| verifyTgtAuthHeader( tgsContext ); |
| verifyBodyChecksum( tgsContext ); |
| getRequestPrincipalEntry( tgsContext ); |
| generateTicket( tgsContext ); |
| buildReply( tgsContext ); |
| } |
| |
| |
| private static void configureTicketGranting( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| tgsContext.setCipherTextHandler( cipherTextHandler ); |
| |
| if ( tgsContext.getRequest().getProtocolVersionNumber() != KerberosConstants.KERBEROS_V5 ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BAD_PVNO ); |
| } |
| } |
| |
| |
| private static void monitorRequest( KdcContext kdcContext ) throws Exception |
| { |
| KdcReq request = kdcContext.getRequest(); |
| |
| try |
| { |
| String clientAddress = kdcContext.getClientAddress().getHostAddress(); |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| sb.append( "Received " + SERVICE_NAME + " request:" ); |
| sb.append( "\n\t" + "messageType: " + request.getMessageType() ); |
| sb.append( "\n\t" + "protocolVersionNumber: " + request.getProtocolVersionNumber() ); |
| sb.append( "\n\t" + "clientAddress: " + clientAddress ); |
| sb.append( "\n\t" + "nonce: " + request.getKdcReqBody().getNonce() ); |
| sb.append( "\n\t" + "kdcOptions: " + request.getKdcReqBody().getKdcOptions() ); |
| sb.append( "\n\t" + "clientPrincipal: " + request.getKdcReqBody().getCName() ); |
| sb.append( "\n\t" + "serverPrincipal: " + request.getKdcReqBody().getSName() ); |
| sb.append( "\n\t" + "encryptionType: " |
| + KerberosUtils.getEncryptionTypesString( request.getKdcReqBody().getEType() ) ); |
| sb.append( "\n\t" + "realm: " + request.getKdcReqBody().getRealm() ); |
| sb.append( "\n\t" + "from time: " + request.getKdcReqBody().getFrom() ); |
| sb.append( "\n\t" + "till time: " + request.getKdcReqBody().getTill() ); |
| sb.append( "\n\t" + "renew-till time: " + request.getKdcReqBody().getRTime() ); |
| sb.append( "\n\t" + "hostAddresses: " + request.getKdcReqBody().getAddresses() ); |
| |
| LOG_KRB.debug( sb.toString() ); |
| } |
| catch ( Exception e ) |
| { |
| // This is a monitor. No exceptions should bubble up. |
| LOG_KRB.error( I18n.err( I18n.ERR_153 ), e ); |
| } |
| } |
| |
| |
| private static void selectEncryptionType( TicketGrantingContext tgsContext ) throws Exception |
| { |
| KdcContext kdcContext = tgsContext; |
| KerberosConfig config = kdcContext.getConfig(); |
| |
| Set<EncryptionType> requestedTypes = kdcContext.getRequest().getKdcReqBody().getEType(); |
| |
| EncryptionType bestType = KerberosUtils.getBestEncryptionType( requestedTypes, config.getEncryptionTypes() ); |
| |
| LOG_KRB.debug( "Session will use encryption type {}.", bestType ); |
| |
| if ( bestType == null ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_ETYPE_NOSUPP ); |
| } |
| |
| kdcContext.setEncryptionType( bestType ); |
| } |
| |
| |
| private static void getAuthHeader( TicketGrantingContext tgsContext ) throws Exception |
| { |
| KdcReq request = tgsContext.getRequest(); |
| |
| if ( ( request.getPaData() == null ) || ( request.getPaData().size() < 1 ) ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP ); |
| } |
| |
| byte[] undecodedAuthHeader = null; |
| |
| for ( PaData paData : request.getPaData() ) |
| { |
| if ( paData.getPaDataType() == PaDataType.PA_TGS_REQ ) |
| { |
| undecodedAuthHeader = paData.getPaDataValue(); |
| } |
| } |
| |
| if ( undecodedAuthHeader == null ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_PADATA_TYPE_NOSUPP ); |
| } |
| |
| ApReq authHeader = KerberosDecoder.decodeApReq( undecodedAuthHeader ); |
| |
| Ticket tgt = authHeader.getTicket(); |
| |
| tgsContext.setAuthHeader( authHeader ); |
| tgsContext.setTgt( tgt ); |
| } |
| |
| |
| public static void verifyTgt( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| KerberosConfig config = tgsContext.getConfig(); |
| Ticket tgt = tgsContext.getTgt(); |
| |
| // Check primary realm. |
| if ( !tgt.getRealm().equals( config.getPrimaryRealm() ) ) |
| { |
| throw new KerberosException( ErrorType.KRB_AP_ERR_NOT_US ); |
| } |
| |
| String tgtServerName = KerberosUtils.getKerberosPrincipal( tgt.getSName(), tgt.getRealm() ).getName(); |
| String requestServerName = KerberosUtils.getKerberosPrincipal( |
| tgsContext.getRequest().getKdcReqBody().getSName(), tgsContext.getRequest().getKdcReqBody().getRealm() ) |
| .getName(); |
| |
| /* |
| * if (tgt.sname is not a TGT for local realm and is not req.sname) |
| * then error_out(KRB_AP_ERR_NOT_US); |
| */ |
| if ( !tgtServerName.equals( config.getServicePrincipal().getName() ) |
| && !tgtServerName.equals( requestServerName ) ) |
| { |
| throw new KerberosException( ErrorType.KRB_AP_ERR_NOT_US ); |
| } |
| } |
| |
| |
| private static void getTicketPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| PrincipalName principal = tgsContext.getTgt().getSName(); |
| PrincipalStore store = tgsContext.getStore(); |
| |
| KerberosPrincipal principalWithRealm = KerberosUtils.getKerberosPrincipal( principal, tgsContext.getTgt() |
| .getRealm() ); |
| PrincipalStoreEntry entry = getEntry( principalWithRealm, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN ); |
| tgsContext.setTicketPrincipalEntry( entry ); |
| } |
| |
| |
| private static void verifyTgtAuthHeader( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| ApReq authHeader = tgsContext.getAuthHeader(); |
| Ticket tgt = tgsContext.getTgt(); |
| |
| KdcOptions kdcOptions = tgsContext.getRequest().getKdcReqBody().getKdcOptions(); |
| boolean isValidate = kdcOptions.get( KdcOptions.VALIDATE ); |
| |
| EncryptionType encryptionType = tgt.getEncPart().getEType(); |
| EncryptionKey serverKey = tgsContext.getTicketPrincipalEntry().getKeyMap().get( encryptionType ); |
| |
| long clockSkew = tgsContext.getConfig().getAllowableClockSkew(); |
| ReplayCache replayCache = tgsContext.getReplayCache(); |
| boolean emptyAddressesAllowed = tgsContext.getConfig().isEmptyAddressesAllowed(); |
| InetAddress clientAddress = tgsContext.getClientAddress(); |
| CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler(); |
| |
| Authenticator authenticator = KerberosUtils.verifyAuthHeader( authHeader, tgt, serverKey, clockSkew, |
| replayCache, |
| emptyAddressesAllowed, clientAddress, cipherTextHandler, |
| KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_TGS_SESS_KEY, isValidate ); |
| |
| tgsContext.setAuthenticator( authenticator ); |
| } |
| |
| |
| /** |
| * RFC4120 |
| * <li>Section 3.3.2. Receipt of KRB_TGS_REQ Message -> 2nd paragraph |
| * <li>Section 5.5.1. KRB_AP_REQ Definition -> Authenticator -> cksum |
| */ |
| private static void verifyBodyChecksum( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| KerberosConfig config = tgsContext.getConfig(); |
| |
| if ( config.isBodyChecksumVerified() ) |
| { |
| KdcReqBody body = tgsContext.getRequest().getKdcReqBody(); |
| // FIXME how this byte[] is computed?? |
| // is it full ASN.1 encoded bytes OR just the bytes of all the values alone? |
| // for now am using the ASN.1 encoded value |
| ByteBuffer buf = ByteBuffer.allocate( body.computeLength() ); |
| try |
| { |
| body.encode( buf ); |
| } |
| catch ( EncoderException e ) |
| { |
| throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM ); |
| } |
| |
| byte[] bodyBytes = buf.array(); |
| Checksum authenticatorChecksum = tgsContext.getAuthenticator().getCksum(); |
| |
| if ( authenticatorChecksum != null ) |
| { |
| // we need the session key |
| Ticket tgt = tgsContext.getTgt(); |
| EncTicketPart encTicketPart = tgt.getEncTicketPart(); |
| EncryptionKey sessionKey = encTicketPart.getKey(); |
| |
| if ( authenticatorChecksum == null || authenticatorChecksum.getChecksumType() == null |
| || authenticatorChecksum.getChecksumValue() == null || bodyBytes == null ) |
| { |
| throw new KerberosException( ErrorType.KRB_AP_ERR_INAPP_CKSUM ); |
| } |
| |
| LOG_KRB.debug( "Verifying body checksum type '{}'.", authenticatorChecksum.getChecksumType() ); |
| |
| checksumHandler.verifyChecksum( authenticatorChecksum, bodyBytes, sessionKey.getKeyValue(), |
| KeyUsage.TGS_REQ_PA_TGS_REQ_PADATA_AP_REQ_AUTHNT_CKSUM_TGS_SESS_KEY ); |
| } |
| } |
| } |
| |
| |
| public static void getRequestPrincipalEntry( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| KerberosPrincipal principal = KerberosUtils.getKerberosPrincipal( |
| tgsContext.getRequest().getKdcReqBody().getSName(), tgsContext.getRequest().getKdcReqBody().getRealm() ); |
| PrincipalStore store = tgsContext.getStore(); |
| |
| PrincipalStoreEntry entry = getEntry( principal, store, ErrorType.KDC_ERR_S_PRINCIPAL_UNKNOWN ); |
| tgsContext.setRequestPrincipalEntry( entry ); |
| } |
| |
| |
| private static void generateTicket( TicketGrantingContext tgsContext ) throws KerberosException, |
| InvalidTicketException |
| { |
| KdcReq request = tgsContext.getRequest(); |
| Ticket tgt = tgsContext.getTgt(); |
| Authenticator authenticator = tgsContext.getAuthenticator(); |
| CipherTextHandler cipherTextHandler = tgsContext.getCipherTextHandler(); |
| KerberosPrincipal ticketPrincipal = KerberosUtils.getKerberosPrincipal( |
| request.getKdcReqBody().getSName(), request.getKdcReqBody().getRealm() ); |
| |
| EncryptionType encryptionType = tgsContext.getEncryptionType(); |
| EncryptionKey serverKey = tgsContext.getRequestPrincipalEntry().getKeyMap().get( encryptionType ); |
| |
| KerberosConfig config = tgsContext.getConfig(); |
| |
| tgsContext.getRequest().getKdcReqBody().getAdditionalTickets(); |
| |
| EncTicketPart newTicketPart = new EncTicketPart(); |
| |
| newTicketPart.setClientAddresses( tgt.getEncTicketPart().getClientAddresses() ); |
| |
| processFlags( config, request, tgt, newTicketPart ); |
| |
| EncryptionKey sessionKey = RandomKeyFactory.getRandomKey( tgsContext.getEncryptionType() ); |
| newTicketPart.setKey( sessionKey ); |
| |
| newTicketPart.setCName( tgt.getEncTicketPart().getCName() ); |
| newTicketPart.setCRealm( tgt.getEncTicketPart().getCRealm() ); |
| |
| if ( request.getKdcReqBody().getEncAuthorizationData() != null ) |
| { |
| byte[] authorizationData = cipherTextHandler.decrypt( authenticator.getSubKey(), request.getKdcReqBody() |
| .getEncAuthorizationData(), KeyUsage.TGS_REQ_KDC_REQ_BODY_AUTHZ_DATA_ENC_WITH_TGS_SESS_KEY ); |
| AuthorizationData authData = KerberosDecoder.decodeAuthorizationData( authorizationData ); |
| authData.addEntry( tgt.getEncTicketPart().getAuthorizationData().getCurrentAD() ); |
| newTicketPart.setAuthorizationData( authData ); |
| } |
| |
| processTransited( newTicketPart, tgt ); |
| |
| processTimes( config, request, newTicketPart, tgt ); |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ENC_TKT_IN_SKEY ) ) |
| { |
| Ticket[] additionalTkts = tgsContext.getRequest().getKdcReqBody().getAdditionalTickets(); |
| |
| if ( additionalTkts == null || additionalTkts.length == 0 ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| Ticket additionalTgt = additionalTkts[0]; |
| // reject if it is not a TGT |
| if ( !additionalTgt.getEncTicketPart().getFlags().isInitial() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| serverKey = additionalTgt.getEncTicketPart().getKey(); |
| /* |
| * if (server not specified) then |
| * server = req.second_ticket.client; |
| * endif |
| * |
| * if ((req.second_ticket is not a TGT) or |
| * (req.second_ticket.client != server)) then |
| * error_out(KDC_ERR_POLICY); |
| * endif |
| * |
| * new_tkt.enc-part := encrypt OCTET STRING using etype_for_key(second-ticket.key), second-ticket.key; |
| */ |
| //throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| EncryptedData encryptedData = cipherTextHandler.seal( serverKey, newTicketPart, |
| KeyUsage.AS_OR_TGS_REP_TICKET_WITH_SRVKEY ); |
| |
| Ticket newTicket = new Ticket( request.getKdcReqBody().getSName(), encryptedData ); |
| newTicket.setEncTicketPart( newTicketPart ); |
| newTicket.setRealm( request.getKdcReqBody().getRealm() ); |
| |
| tgsContext.setNewTicket( newTicket ); |
| } |
| |
| |
| private static void buildReply( TicketGrantingContext tgsContext ) throws KerberosException |
| { |
| KdcReq request = tgsContext.getRequest(); |
| Ticket tgt = tgsContext.getTgt(); |
| Ticket newTicket = tgsContext.getNewTicket(); |
| |
| TgsRep reply = new TgsRep(); |
| |
| reply.setCName( tgt.getEncTicketPart().getCName() ); |
| reply.setCRealm( tgt.getEncTicketPart().getCRealm() ); |
| reply.setTicket( newTicket ); |
| |
| EncKdcRepPart encKdcRepPart = new EncKdcRepPart(); |
| |
| encKdcRepPart.setKey( newTicket.getEncTicketPart().getKey() ); |
| encKdcRepPart.setNonce( request.getKdcReqBody().getNonce() ); |
| // TODO - resp.last-req := fetch_last_request_info(client); requires store |
| // FIXME temporary fix, IMO we should create some new ATs to store this info in DIT |
| LastReq lastReq = new LastReq(); |
| lastReq.addEntry( new LastReqEntry( LastReqType.TIME_OF_INITIAL_REQ, new KerberosTime() ) ); |
| encKdcRepPart.setLastReq( lastReq ); |
| |
| encKdcRepPart.setFlags( newTicket.getEncTicketPart().getFlags() ); |
| encKdcRepPart.setClientAddresses( newTicket.getEncTicketPart().getClientAddresses() ); |
| encKdcRepPart.setAuthTime( newTicket.getEncTicketPart().getAuthTime() ); |
| encKdcRepPart.setStartTime( newTicket.getEncTicketPart().getStartTime() ); |
| encKdcRepPart.setEndTime( newTicket.getEncTicketPart().getEndTime() ); |
| encKdcRepPart.setSName( newTicket.getSName() ); |
| encKdcRepPart.setSRealm( newTicket.getRealm() ); |
| |
| if ( newTicket.getEncTicketPart().getFlags().isRenewable() ) |
| { |
| encKdcRepPart.setRenewTill( newTicket.getEncTicketPart().getRenewTill() ); |
| } |
| |
| if ( LOG_KRB.isDebugEnabled() ) |
| { |
| monitorContext( tgsContext ); |
| monitorReply( reply, encKdcRepPart ); |
| } |
| |
| EncTgsRepPart encTgsRepPart = new EncTgsRepPart(); |
| encTgsRepPart.setEncKdcRepPart( encKdcRepPart ); |
| |
| Authenticator authenticator = tgsContext.getAuthenticator(); |
| |
| EncryptedData encryptedData; |
| |
| if ( authenticator.getSubKey() != null ) |
| { |
| encryptedData = cipherTextHandler.seal( authenticator.getSubKey(), encTgsRepPart, |
| KeyUsage.TGS_REP_ENC_PART_TGS_AUTHNT_SUB_KEY ); |
| } |
| else |
| { |
| encryptedData = cipherTextHandler.seal( tgt.getEncTicketPart().getKey(), encTgsRepPart, |
| KeyUsage.TGS_REP_ENC_PART_TGS_SESS_KEY ); |
| } |
| |
| reply.setEncPart( encryptedData ); |
| reply.setEncKdcRepPart( encKdcRepPart ); |
| |
| tgsContext.setReply( reply ); |
| } |
| |
| |
| private static void monitorContext( TicketGrantingContext tgsContext ) |
| { |
| try |
| { |
| Ticket tgt = tgsContext.getTgt(); |
| long clockSkew = tgsContext.getConfig().getAllowableClockSkew(); |
| |
| Checksum cksum = tgsContext.getAuthenticator().getCksum(); |
| |
| ChecksumType checksumType = null; |
| if ( cksum != null ) |
| { |
| checksumType = cksum.getChecksumType(); |
| } |
| |
| InetAddress clientAddress = tgsContext.getClientAddress(); |
| HostAddresses clientAddresses = tgt.getEncTicketPart().getClientAddresses(); |
| |
| boolean caddrContainsSender = false; |
| if ( tgt.getEncTicketPart().getClientAddresses() != null ) |
| { |
| caddrContainsSender = tgt.getEncTicketPart().getClientAddresses() |
| .contains( new HostAddress( clientAddress ) ); |
| } |
| |
| StringBuffer sb = new StringBuffer(); |
| |
| sb.append( "Monitoring " + SERVICE_NAME + " context:" ); |
| |
| sb.append( "\n\t" + "clockSkew " + clockSkew ); |
| sb.append( "\n\t" + "checksumType " + checksumType ); |
| sb.append( "\n\t" + "clientAddress " + clientAddress ); |
| sb.append( "\n\t" + "clientAddresses " + clientAddresses ); |
| sb.append( "\n\t" + "caddr contains sender " + caddrContainsSender ); |
| |
| PrincipalName requestServerPrincipal = tgsContext.getRequest().getKdcReqBody().getSName(); |
| PrincipalStoreEntry requestPrincipal = tgsContext.getRequestPrincipalEntry(); |
| |
| sb.append( "\n\t" + "principal " + requestServerPrincipal ); |
| sb.append( "\n\t" + "cn " + requestPrincipal.getCommonName() ); |
| sb.append( "\n\t" + "realm " + requestPrincipal.getRealmName() ); |
| sb.append( "\n\t" + "principal " + requestPrincipal.getPrincipal() ); |
| sb.append( "\n\t" + "SAM type " + requestPrincipal.getSamType() ); |
| |
| PrincipalName ticketServerPrincipal = tgsContext.getTgt().getSName(); |
| PrincipalStoreEntry ticketPrincipal = tgsContext.getTicketPrincipalEntry(); |
| |
| sb.append( "\n\t" + "principal " + ticketServerPrincipal ); |
| sb.append( "\n\t" + "cn " + ticketPrincipal.getCommonName() ); |
| sb.append( "\n\t" + "realm " + ticketPrincipal.getRealmName() ); |
| sb.append( "\n\t" + "principal " + ticketPrincipal.getPrincipal() ); |
| sb.append( "\n\t" + "SAM type " + ticketPrincipal.getSamType() ); |
| |
| EncryptionType encryptionType = tgsContext.getTgt().getEncPart().getEType(); |
| int keyVersion = ticketPrincipal.getKeyMap().get( encryptionType ).getKeyVersion(); |
| sb.append( "\n\t" + "Ticket key type " + encryptionType ); |
| sb.append( "\n\t" + "Service key version " + keyVersion ); |
| |
| LOG_KRB.debug( sb.toString() ); |
| } |
| catch ( Exception e ) |
| { |
| // This is a monitor. No exceptions should bubble up. |
| LOG_KRB.error( I18n.err( I18n.ERR_154 ), e ); |
| } |
| } |
| |
| |
| private static void monitorReply( TgsRep success, EncKdcRepPart part ) |
| { |
| try |
| { |
| StringBuffer sb = new StringBuffer(); |
| |
| sb.append( "Responding with " + SERVICE_NAME + " reply:" ); |
| sb.append( "\n\t" + "messageType: " + success.getMessageType() ); |
| sb.append( "\n\t" + "protocolVersionNumber: " + success.getProtocolVersionNumber() ); |
| sb.append( "\n\t" + "nonce: " + part.getNonce() ); |
| sb.append( "\n\t" + "clientPrincipal: " + success.getCName() ); |
| sb.append( "\n\t" + "client realm: " + success.getCRealm() ); |
| sb.append( "\n\t" + "serverPrincipal: " + part.getSName() ); |
| sb.append( "\n\t" + "server realm: " + part.getSRealm() ); |
| sb.append( "\n\t" + "auth time: " + part.getAuthTime() ); |
| sb.append( "\n\t" + "start time: " + part.getStartTime() ); |
| sb.append( "\n\t" + "end time: " + part.getEndTime() ); |
| sb.append( "\n\t" + "renew-till time: " + part.getRenewTill() ); |
| sb.append( "\n\t" + "hostAddresses: " + part.getClientAddresses() ); |
| |
| LOG_KRB.debug( sb.toString() ); |
| } |
| catch ( Exception e ) |
| { |
| // This is a monitor. No exceptions should bubble up. |
| LOG_KRB.error( I18n.err( I18n.ERR_155 ), e ); |
| } |
| } |
| |
| |
| private static void processFlags( KerberosConfig config, KdcReq request, Ticket tgt, |
| EncTicketPart newTicketPart ) throws KerberosException |
| { |
| if ( tgt.getEncTicketPart().getFlags().isPreAuth() ) |
| { |
| newTicketPart.setFlag( TicketFlag.PRE_AUTHENT ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDABLE ) ) |
| { |
| if ( !config.isForwardableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isForwardable() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| newTicketPart.setFlag( TicketFlag.FORWARDABLE ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.FORWARDED ) ) |
| { |
| if ( !config.isForwardableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isForwardable() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| if ( request.getKdcReqBody().getAddresses() != null |
| && request.getKdcReqBody().getAddresses().getAddresses() != null |
| && request.getKdcReqBody().getAddresses().getAddresses().length > 0 ) |
| { |
| newTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() ); |
| } |
| else |
| { |
| if ( !config.isEmptyAddressesAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| } |
| |
| newTicketPart.setFlag( TicketFlag.FORWARDED ); |
| } |
| |
| if ( tgt.getEncTicketPart().getFlags().isForwarded() ) |
| { |
| newTicketPart.setFlag( TicketFlag.FORWARDED ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXIABLE ) ) |
| { |
| if ( !config.isProxiableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isProxiable() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| newTicketPart.setFlag( TicketFlag.PROXIABLE ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.PROXY ) ) |
| { |
| if ( !config.isProxiableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isProxiable() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| if ( request.getKdcReqBody().getAddresses() != null |
| && request.getKdcReqBody().getAddresses().getAddresses() != null |
| && request.getKdcReqBody().getAddresses().getAddresses().length > 0 ) |
| { |
| newTicketPart.setClientAddresses( request.getKdcReqBody().getAddresses() ); |
| } |
| else |
| { |
| if ( !config.isEmptyAddressesAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| } |
| |
| newTicketPart.setFlag( TicketFlag.PROXY ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.ALLOW_POSTDATE ) ) |
| { |
| if ( !config.isPostdatedAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| newTicketPart.setFlag( TicketFlag.MAY_POSTDATE ); |
| } |
| |
| /* |
| * "Otherwise, if the TGT has the MAY-POSTDATE flag set, then the resulting |
| * ticket will be postdated, and the requested starttime is checked against |
| * the policy of the local realm. If acceptable, the ticket's starttime is |
| * set as requested, and the INVALID flag is set. The postdated ticket MUST |
| * be validated before use by presenting it to the KDC after the starttime |
| * has been reached. However, in no case may the starttime, endtime, or |
| * renew-till time of a newly-issued postdated ticket extend beyond the |
| * renew-till time of the TGT." |
| */ |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) ) |
| { |
| if ( !config.isPostdatedAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isMayPosdate() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| newTicketPart.setFlag( TicketFlag.POSTDATED ); |
| newTicketPart.setFlag( TicketFlag.INVALID ); |
| |
| newTicketPart.setStartTime( request.getKdcReqBody().getFrom() ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.VALIDATE ) ) |
| { |
| if ( !config.isPostdatedAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isInvalid() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| KerberosTime startTime = ( tgt.getEncTicketPart().getStartTime() != null ) ? |
| tgt.getEncTicketPart().getStartTime() : |
| tgt.getEncTicketPart().getAuthTime(); |
| |
| if ( startTime.greaterThan( new KerberosTime() ) ) |
| { |
| throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_NYV ); |
| } |
| |
| echoTicket( newTicketPart, tgt ); |
| newTicketPart.getFlags().clearFlag( TicketFlag.INVALID ); |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_0 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_7 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_9 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_10 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_11 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_12 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_13 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_14 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_15 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_16 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_17 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_18 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_19 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_20 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_21 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_22 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_23 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_24 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_25 ) || |
| request.getKdcReqBody().getKdcOptions().get( KdcOptions.RESERVED_29 ) ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| } |
| |
| |
| private static void processTimes( KerberosConfig config, KdcReq request, EncTicketPart newTicketPart, |
| Ticket tgt ) throws KerberosException |
| { |
| KerberosTime now = new KerberosTime(); |
| |
| newTicketPart.setAuthTime( tgt.getEncTicketPart().getAuthTime() ); |
| |
| KerberosTime startTime = request.getKdcReqBody().getFrom(); |
| |
| /* |
| * "If the requested starttime is absent, indicates a time in the past, |
| * or is within the window of acceptable clock skew for the KDC and the |
| * POSTDATE option has not been specified, then the starttime of the |
| * ticket is set to the authentication server's current time." |
| */ |
| if ( startTime == null || startTime.lessThan( now ) || startTime.isInClockSkew( config.getAllowableClockSkew() ) |
| && !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) ) |
| { |
| startTime = now; |
| } |
| |
| /* |
| * "If it indicates a time in the future beyond the acceptable clock skew, |
| * but the POSTDATED option has not been specified or the MAY-POSTDATE flag |
| * is not set in the TGT, then the error KDC_ERR_CANNOT_POSTDATE is |
| * returned." |
| */ |
| if ( startTime != null |
| && startTime.greaterThan( now ) |
| && !startTime.isInClockSkew( config.getAllowableClockSkew() ) |
| && ( !request.getKdcReqBody().getKdcOptions().get( KdcOptions.POSTDATED ) || !tgt.getEncTicketPart() |
| .getFlags().isMayPosdate() ) ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_CANNOT_POSTDATE ); |
| } |
| |
| KerberosTime renewalTime = null; |
| KerberosTime kerberosEndTime = null; |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEW ) ) |
| { |
| if ( !config.isRenewableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| if ( !tgt.getEncTicketPart().getFlags().isRenewable() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_BADOPTION ); |
| } |
| |
| if ( tgt.getEncTicketPart().getRenewTill().lessThan( now ) ) |
| { |
| throw new KerberosException( ErrorType.KRB_AP_ERR_TKT_EXPIRED ); |
| } |
| |
| echoTicket( newTicketPart, tgt ); |
| |
| newTicketPart.setStartTime( now ); |
| |
| KerberosTime tgtStartTime = ( tgt.getEncTicketPart().getStartTime() != null ) ? |
| tgt.getEncTicketPart().getStartTime() : |
| tgt.getEncTicketPart().getAuthTime(); |
| |
| long oldLife = tgt.getEncTicketPart().getEndTime().getTime() - tgtStartTime.getTime(); |
| |
| kerberosEndTime = new KerberosTime( Math.min( tgt.getEncTicketPart().getRenewTill().getTime(), |
| now.getTime() + oldLife ) ); |
| newTicketPart.setEndTime( kerberosEndTime ); |
| } |
| else |
| { |
| if ( newTicketPart.getStartTime() == null ) |
| { |
| newTicketPart.setStartTime( now ); |
| } |
| |
| KerberosTime till; |
| if ( request.getKdcReqBody().getTill().isZero() ) |
| { |
| till = KerberosTime.INFINITY; |
| } |
| else |
| { |
| till = request.getKdcReqBody().getTill(); |
| } |
| |
| /* |
| * The end time is the minimum of (a) the requested till time or (b) |
| * the start time plus maximum lifetime as configured in policy or (c) |
| * the end time of the TGT. |
| */ |
| List<KerberosTime> minimizer = new ArrayList<KerberosTime>(); |
| minimizer.add( till ); |
| minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumTicketLifetime() ) ); |
| minimizer.add( tgt.getEncTicketPart().getEndTime() ); |
| kerberosEndTime = Collections.min( minimizer ); |
| |
| newTicketPart.setEndTime( kerberosEndTime ); |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE_OK ) |
| && kerberosEndTime.lessThan( request.getKdcReqBody().getTill() ) |
| && tgt.getEncTicketPart().getFlags().isRenewable() ) |
| { |
| if ( !config.isRenewableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| // We set the RENEWABLE option for later processing. |
| request.getKdcReqBody().getKdcOptions().set( KdcOptions.RENEWABLE ); |
| long rtime = Math.min( request.getKdcReqBody().getTill().getTime(), tgt.getEncTicketPart() |
| .getRenewTill().getTime() ); |
| renewalTime = new KerberosTime( rtime ); |
| } |
| } |
| |
| if ( renewalTime == null ) |
| { |
| renewalTime = request.getKdcReqBody().getRTime(); |
| } |
| |
| KerberosTime rtime; |
| if ( renewalTime != null && renewalTime.isZero() ) |
| { |
| rtime = KerberosTime.INFINITY; |
| } |
| else |
| { |
| rtime = renewalTime; |
| } |
| |
| if ( request.getKdcReqBody().getKdcOptions().get( KdcOptions.RENEWABLE ) |
| && tgt.getEncTicketPart().getFlags().isRenewable() ) |
| { |
| if ( !config.isRenewableAllowed() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_POLICY ); |
| } |
| |
| newTicketPart.setFlag( TicketFlag.RENEWABLE ); |
| |
| /* |
| * The renew-till time is the minimum of (a) the requested renew-till |
| * time or (b) the start time plus maximum renewable lifetime as |
| * configured in policy or (c) the renew-till time of the TGT. |
| */ |
| List<KerberosTime> minimizer = new ArrayList<KerberosTime>(); |
| |
| /* |
| * 'rtime' KerberosTime is OPTIONAL |
| */ |
| if ( rtime != null ) |
| { |
| minimizer.add( rtime ); |
| } |
| |
| minimizer.add( new KerberosTime( startTime.getTime() + config.getMaximumRenewableLifetime() ) ); |
| minimizer.add( tgt.getEncTicketPart().getRenewTill() ); |
| newTicketPart.setRenewTill( Collections.min( minimizer ) ); |
| } |
| |
| /* |
| * "If the requested expiration time minus the starttime (as determined |
| * above) is less than a site-determined minimum lifetime, an error |
| * message with code KDC_ERR_NEVER_VALID is returned." |
| */ |
| if ( kerberosEndTime.lessThan( startTime ) ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID ); |
| } |
| |
| long ticketLifeTime = Math.abs( startTime.getTime() - kerberosEndTime.getTime() ); |
| if ( ticketLifeTime < config.getAllowableClockSkew() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_NEVER_VALID ); |
| } |
| } |
| |
| |
| /* |
| * if (realm_tgt_is_for(tgt) := tgt.realm) then |
| * // tgt issued by local realm |
| * new_tkt.transited := tgt.transited; |
| * else |
| * // was issued for this realm by some other realm |
| * if (tgt.transited.tr-type not supported) then |
| * error_out(KDC_ERR_TRTYPE_NOSUPP); |
| * endif |
| * |
| * new_tkt.transited := compress_transited(tgt.transited + tgt.realm) |
| * endif |
| */ |
| private static void processTransited( EncTicketPart newTicketPart, Ticket tgt ) |
| { |
| // TODO - currently no transited support other than local |
| newTicketPart.setTransited( tgt.getEncTicketPart().getTransited() ); |
| } |
| |
| |
| private static void echoTicket( EncTicketPart newTicketPart, Ticket tgt ) |
| { |
| EncTicketPart encTicketpart = tgt.getEncTicketPart(); |
| newTicketPart.setAuthorizationData( encTicketpart.getAuthorizationData() ); |
| newTicketPart.setAuthTime( encTicketpart.getAuthTime() ); |
| newTicketPart.setClientAddresses( encTicketpart.getClientAddresses() ); |
| newTicketPart.setCName( encTicketpart.getCName() ); |
| newTicketPart.setEndTime( encTicketpart.getEndTime() ); |
| newTicketPart.setFlags( encTicketpart.getFlags() ); |
| newTicketPart.setRenewTill( encTicketpart.getRenewTill() ); |
| newTicketPart.setKey( encTicketpart.getKey() ); |
| newTicketPart.setTransited( encTicketpart.getTransited() ); |
| } |
| |
| |
| /** |
| * Get a PrincipalStoreEntry given a principal. The ErrorType is used to indicate |
| * whether any resulting error pertains to a server or client. |
| * |
| * @param principal |
| * @param store |
| * @param errorType |
| * @return The PrincipalStoreEntry |
| * @throws Exception |
| */ |
| public static PrincipalStoreEntry getEntry( KerberosPrincipal principal, PrincipalStore store, ErrorType errorType ) |
| throws KerberosException |
| { |
| PrincipalStoreEntry entry = null; |
| |
| try |
| { |
| entry = store.getPrincipal( principal ); |
| } |
| catch ( Exception e ) |
| { |
| throw new KerberosException( errorType, e ); |
| } |
| |
| if ( entry == null ) |
| { |
| throw new KerberosException( errorType ); |
| } |
| |
| if ( entry.getKeyMap() == null || entry.getKeyMap().isEmpty() ) |
| { |
| throw new KerberosException( ErrorType.KDC_ERR_NULL_KEY ); |
| } |
| |
| return entry; |
| } |
| |
| } |