package org.apache.kerberos.kerb.server.request;
import org.apache.kerberos.kerb.KrbErrorCode;
import org.apache.kerberos.kerb.codec.KrbCodec;
import org.apache.kerberos.kerb.common.EncryptionUtil;
import org.apache.kerberos.kerb.crypto.EncryptionHandler;
import org.apache.kerberos.kerb.identity.KrbIdentity;
import org.apache.kerberos.kerb.server.KdcConfig;
import org.apache.kerberos.kerb.server.KdcContext;
import org.apache.kerberos.kerb.server.preauth.FastContext;
import org.apache.kerberos.kerb.server.preauth.PreauthContext;
import org.apache.kerberos.kerb.server.preauth.PreauthHandler;
import org.apache.kerberos.kerb.KrbConstant;
import org.apache.kerberos.kerb.KrbErrorException;
import org.apache.kerberos.kerb.KrbException;
import org.apache.kerberos.kerb.spec.KerberosTime;
import org.apache.kerberos.kerb.spec.common.*;
import org.apache.kerberos.kerb.spec.kdc.KdcOption;
import org.apache.kerberos.kerb.spec.kdc.KdcOptions;
import org.apache.kerberos.kerb.spec.kdc.KdcRep;
import org.apache.kerberos.kerb.spec.kdc.KdcReq;
import org.apache.kerberos.kerb.spec.pa.PaData;
import org.apache.kerberos.kerb.spec.pa.PaDataEntry;
import org.apache.kerberos.kerb.spec.pa.PaDataType;
import org.apache.kerberos.kerb.spec.ticket.EncTicketPart;
import org.apache.kerberos.kerb.spec.ticket.Ticket;
import org.apache.kerberos.kerb.spec.ticket.TicketFlag;
import org.apache.kerberos.kerb.spec.ticket.TicketFlags;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.util.Date;
import java.util.List;
public abstract class KdcRequest {
protected KdcContext kdcContext;
private Ticket ticket;
private boolean isPreAuthenticated;
private KdcReq kdcReq;
private KdcRep reply;
private InetAddress clientAddress;
private boolean isTcp;
private EncryptionType encryptionType;
private EncryptionKey clientKey;
private KrbIdentity clientEntry;
private KrbIdentity serverEntry;
private EncryptionKey serverKey;
private KrbIdentity tgsEntry;
private PreauthContext preauthContext;
private FastContext fastContext;
private PrincipalName serverPrincipal;
public KdcRequest(KdcReq kdcReq, KdcContext kdcContext) {
this.kdcReq = kdcReq;
this.kdcContext = kdcContext;
this.preauthContext = kdcContext.getPreauthHandler()
.preparePreauthContext(this);
this.fastContext = new FastContext();
}
public KdcContext getKdcContext() {
return kdcContext;
}
public PreauthContext getPreauthContext() {
return preauthContext;
}
public void process() throws KrbException {
checkVersion();
checkClient();
checkServer();
preauth();
authenticate();
issueTicket();
makeReply();
}
public KdcReq getKdcReq() {
return kdcReq;
}
public KrbIdentity getTgsEntry() {
return tgsEntry;
}
public void setTgsEntry(KrbIdentity tgsEntry) {
this.tgsEntry = tgsEntry;
}
public boolean isTcp() {
return isTcp;
}
public void isTcp(boolean isTcp) {
this.isTcp = isTcp;
}
public KrbMessage getReply() {
return reply;
}
public void setReply(KdcRep reply) {
this.reply = reply;
}
public InetAddress getClientAddress() {
return clientAddress;
}
public void setClientAddress(InetAddress clientAddress) {
this.clientAddress = clientAddress;
}
public EncryptionType getEncryptionType() {
return encryptionType;
}
public void setEncryptionType(EncryptionType encryptionType) {
this.encryptionType = encryptionType;
}
public Ticket getTicket() {
return ticket;
}
public void setTicket(Ticket ticket) {
this.ticket = ticket;
}
public boolean isPreAuthenticated() {
return isPreAuthenticated;
}
public void setPreAuthenticated(boolean isPreAuthenticated) {
this.isPreAuthenticated = isPreAuthenticated;
}
public KrbIdentity getServerEntry() {
return serverEntry;
}
public void setServerEntry(KrbIdentity serverEntry) {
this.serverEntry = serverEntry;
}
public KrbIdentity getClientEntry() {
return clientEntry;
}
public void setClientEntry(KrbIdentity clientEntry) {
this.clientEntry = clientEntry;
}
public EncryptionKey getClientKey(EncryptionType encType) throws KrbException {
return getClientEntry().getKey(encType);
}
public EncryptionKey getClientKey() {
return clientKey;
}
public void setClientKey(EncryptionKey clientKey) {
this.clientKey = clientKey;
}
public EncryptionKey getServerKey() {
return serverKey;
}
public void setServerKey(EncryptionKey serverKey) {
this.serverKey = serverKey;
}
public PrincipalName getTgsPrincipal() {
PrincipalName result = new PrincipalName(kdcContext.getConfig().getTgsPrincipal());
result.setRealm(kdcContext.getKdcRealm());
return result;
}
protected abstract void makeReply() throws KrbException;
protected void checkVersion() throws KrbException {
KdcReq request = getKdcReq();
int kerberosVersion = request.getPvno();
if (kerberosVersion != KrbConstant.KRB_V5) {
throw new KrbException(KrbErrorCode.KDC_ERR_BAD_PVNO);
}
}
protected void checkPolicy() throws KrbException {
KrbIdentity entry = getClientEntry();
if (entry.isDisabled()) {
throw new KrbException(KrbErrorCode.KDC_ERR_CLIENT_REVOKED);
}
if (entry.isLocked()) {
throw new KrbException(KrbErrorCode.KDC_ERR_CLIENT_REVOKED);
}
if (entry.getExpireTime().lessThan(new Date().getTime())) {
throw new KrbException(KrbErrorCode.KDC_ERR_CLIENT_REVOKED);
}
}
protected void checkClient() throws KrbException {
KdcReq request = getKdcReq();
PrincipalName clientPrincipal = request.getReqBody().getCname();
String clientRealm = request.getReqBody().getRealm();
if (clientRealm == null || clientRealm.isEmpty()) {
clientRealm = kdcContext.getServerRealm();
}
clientPrincipal.setRealm(clientRealm);
KrbIdentity clientEntry = getEntry(clientPrincipal.getName());
setClientEntry(clientEntry);
EncryptionType encType = request.getReqBody().getEtypes().listIterator().next();
EncryptionKey clientKey = clientEntry.getKeys().get(encType);
setClientKey(clientKey);
}
protected void preauth() throws KrbException {
KdcReq request = getKdcReq();
PaData preAuthData = request.getPaData();
if (preauthContext.isPreauthRequired()) {
if (preAuthData == null || preAuthData.isEmpty()) {
KrbError krbError = makePreAuthenticationError(kdcContext);
throw new KrbErrorException(krbError);
} else {
getPreauthHandler().verify(this, preAuthData);
}
}
setPreAuthenticated(true);
}
protected void setPreauthRequired(boolean preauthRequired) {
preauthContext.setPreauthRequired(preauthRequired);
}
protected boolean isPreauthRequired() {
return preauthContext.isPreauthRequired();
}
protected PreauthHandler getPreauthHandler() {
return kdcContext.getPreauthHandler();
}
protected void checkEncryptionType() throws KrbException {
List<EncryptionType> requestedTypes = getKdcReq().getReqBody().getEtypes();
EncryptionType bestType = EncryptionUtil.getBestEncryptionType(requestedTypes,
kdcContext.getConfig().getEncryptionTypes());
if (bestType == null) {
throw new KrbException(KrbErrorCode.KDC_ERR_ETYPE_NOSUPP);
}
setEncryptionType(bestType);
}
protected void authenticate() throws KrbException {
checkEncryptionType();
checkPolicy();
}
protected void issueTicket() throws KrbException {
KdcReq request = getKdcReq();
EncryptionType encryptionType = getEncryptionType();
EncryptionKey serverKey = getServerEntry().getKeys().get(encryptionType);
PrincipalName ticketPrincipal = request.getReqBody().getSname();
EncTicketPart encTicketPart = new EncTicketPart();
KdcConfig config = kdcContext.getConfig();
TicketFlags ticketFlags = new TicketFlags();
encTicketPart.setFlags(ticketFlags);
ticketFlags.setFlag(TicketFlag.INITIAL);
if (isPreAuthenticated()) {
ticketFlags.setFlag(TicketFlag.PRE_AUTH);
}
if (request.getReqBody().getKdcOptions().isFlagSet(KdcOption.FORWARDABLE)) {
if (!config.isForwardableAllowed()) {
throw new KrbException(KrbErrorCode.KDC_ERR_POLICY);
}
ticketFlags.setFlag(TicketFlag.FORWARDABLE);
}
if (request.getReqBody().getKdcOptions().isFlagSet(KdcOption.PROXIABLE)) {
if (!config.isProxiableAllowed()) {
throw new KrbException(KrbErrorCode.KDC_ERR_POLICY);
}
ticketFlags.setFlag(TicketFlag.PROXIABLE);
}
if (request.getReqBody().getKdcOptions().isFlagSet(KdcOption.ALLOW_POSTDATE)) {
if (!config.isPostdatedAllowed()) {
throw new KrbException(KrbErrorCode.KDC_ERR_POLICY);
}
ticketFlags.setFlag(TicketFlag.MAY_POSTDATE);
}
KdcOptions kdcOptions = request.getReqBody().getKdcOptions();
EncryptionKey sessionKey = EncryptionHandler.random2Key(getEncryptionType());
encTicketPart.setKey(sessionKey);
encTicketPart.setCname(request.getReqBody().getCname());
encTicketPart.setCrealm(request.getReqBody().getRealm());
TransitedEncoding transEnc = new TransitedEncoding();
encTicketPart.setTransited(transEnc);
String serverRealm = request.getReqBody().getRealm();
KerberosTime now = KerberosTime.now();
encTicketPart.setAuthTime(now);
KerberosTime krbStartTime = request.getReqBody().getFrom();
if (krbStartTime == null || krbStartTime.lessThan(now) ||
krbStartTime.isInClockSkew(config.getAllowableClockSkew())) {
krbStartTime = now;
}
if (krbStartTime.greaterThan(now)
&& !krbStartTime.isInClockSkew(config.getAllowableClockSkew())
&& !kdcOptions.isFlagSet(KdcOption.POSTDATED)) {
throw new KrbException(KrbErrorCode.KDC_ERR_CANNOT_POSTDATE);
}
if (kdcOptions.isFlagSet(KdcOption.POSTDATED)) {
if (!config.isPostdatedAllowed()) {
throw new KrbException(KrbErrorCode.KDC_ERR_POLICY);
}
ticketFlags.setFlag(TicketFlag.POSTDATED);
encTicketPart.setStartTime(krbStartTime);
}
KerberosTime krbEndTime = request.getReqBody().getTill();
if (krbEndTime == null) {
krbEndTime = krbStartTime.extend(config.getMaximumTicketLifetime() * 1000);
} else if (krbStartTime.greaterThan(krbEndTime)) {
throw new KrbException(KrbErrorCode.KDC_ERR_NEVER_VALID);
}
encTicketPart.setEndTime(krbEndTime);
long ticketLifeTime = Math.abs(krbEndTime.diff(krbStartTime));
if (ticketLifeTime < config.getMinimumTicketLifetime()) {
throw new KrbException(KrbErrorCode.KDC_ERR_NEVER_VALID);
}
KerberosTime krbRtime = request.getReqBody().getRtime();
if (kdcOptions.isFlagSet(KdcOption.RENEWABLE_OK)) {
kdcOptions.setFlag(KdcOption.RENEWABLE);
}
if (kdcOptions.isFlagSet(KdcOption.RENEWABLE)) {
if (!config.isRenewableAllowed()) {
throw new KrbException(KrbErrorCode.KDC_ERR_POLICY);
}
ticketFlags.setFlag(TicketFlag.RENEWABLE);
if (krbRtime == null) {
krbRtime = KerberosTime.NEVER;
}
KerberosTime allowedMaximumRenewableTime = krbStartTime;
allowedMaximumRenewableTime.extend(config.getMaximumRenewableLifetime() * 1000);
if (krbRtime.greaterThan(allowedMaximumRenewableTime)) {
krbRtime = allowedMaximumRenewableTime;
}
encTicketPart.setRenewtill(krbRtime);
}
HostAddresses hostAddresses = request.getReqBody().getAddresses();
if (hostAddresses == null || hostAddresses.isEmpty()) {
if (!config.isEmptyAddressesAllowed()) {
throw new KrbException(KrbErrorCode.KDC_ERR_POLICY);
}
} else {
encTicketPart.setClientAddresses(hostAddresses);
}
EncryptedData encryptedData = EncryptionUtil.seal(encTicketPart,
serverKey, KeyUsage.KDC_REP_TICKET);
Ticket newTicket = new Ticket();
newTicket.setSname(ticketPrincipal);
newTicket.setEncryptedEncPart(encryptedData);
newTicket.setRealm(serverRealm);
newTicket.setEncPart(encTicketPart);
setTicket(newTicket);
}
private void checkServer() throws KrbException {
KdcReq request = getKdcReq();
KrbIdentity tgsEntry = getEntry(getTgsPrincipal().getName());
setTgsEntry(tgsEntry);
PrincipalName principal = request.getReqBody().getSname();
String serverRealm = request.getReqBody().getRealm();
if (serverRealm == null || serverRealm.isEmpty()) {
serverRealm = kdcContext.getServerRealm();
}
principal.setRealm(serverRealm);
KrbIdentity serverEntry = getEntry(principal.getName());
setServerEntry(serverEntry);
EncryptionType encType = request.getReqBody().getEtypes().listIterator().next();
EncryptionKey serverKey = serverEntry.getKeys().get(encType);
setServerKey(serverKey);
}
protected KrbError makePreAuthenticationError(KdcContext kdcContext) throws KrbException {
EncryptionType requestedType = getEncryptionType();
List<EncryptionType> encryptionTypes = kdcContext.getConfig().getEncryptionTypes();
boolean isNewEtype = true;
EtypeInfo2 eTypeInfo2 = new EtypeInfo2();
EtypeInfo eTypeInfo = new EtypeInfo();
for (EncryptionType encryptionType : encryptionTypes) {
if (!isNewEtype) {
EtypeInfoEntry etypeInfoEntry = new EtypeInfoEntry();
etypeInfoEntry.setEtype(encryptionType);
etypeInfoEntry.setSalt(null);
eTypeInfo.add(etypeInfoEntry);
}
EtypeInfo2Entry etypeInfo2Entry = new EtypeInfo2Entry();
etypeInfo2Entry.setEtype(encryptionType);
eTypeInfo2.add(etypeInfo2Entry);
}
byte[] encTypeInfo = null;
byte[] encTypeInfo2 = null;
if (!isNewEtype) {
encTypeInfo = KrbCodec.encode(eTypeInfo);
}
encTypeInfo2 = KrbCodec.encode(eTypeInfo2);
MethodData methodData = new MethodData();
methodData.add(new PaDataEntry(PaDataType.ENC_TIMESTAMP, null));
if (!isNewEtype) {
methodData.add(new PaDataEntry(PaDataType.ETYPE_INFO, encTypeInfo));
}
methodData.add(new PaDataEntry(PaDataType.ETYPE_INFO2, encTypeInfo2));
KrbError krbError = new KrbError();
krbError.setErrorCode(KrbErrorCode.KDC_ERR_PREAUTH_REQUIRED);
byte[] encodedData = KrbCodec.encode(methodData);
krbError.setEdata(encodedData);
return krbError;
}
protected KrbIdentity getEntry(String principal) throws KrbException {
KrbIdentity entry = null;
KrbErrorCode krbErrorCode = KrbErrorCode.KDC_ERR_C_PRINCIPAL_UNKNOWN;
try {
entry = kdcContext.getIdentityService().getIdentity(principal);
} catch (Exception e) {
throw new KrbException(krbErrorCode, e);
}
if (entry == null) {
throw new KrbException(krbErrorCode);
}
return entry;
}
public ByteBuffer getRequestBody() throws KrbException {
return null;
}
public EncryptionKey getArmorKey() throws KrbException {
return fastContext.armorKey;
}
public PrincipalName getServerPrincipal() {
return serverPrincipal;
}
}