// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb4.lib;

import java.io.*;
import krb4.lib.crypto.des;

public class Krb4APReq {
	public byte pvno;
	public byte msgType;
	boolean littleEndian;
	public byte keyVersion;
	public String srealm;
	public byte[] encTicket;
	public byte[] encAuthenticator;
	public Krb4Ticket ticket;
	public Krb4Authenticator authenticator;
	boolean _mutualAuthRequired;
	int checksum;

	public Krb4APReq(
		Krb4Creds creds,
		String cname,
		String cinst,
		String crealm,
		int new_checksum,
		boolean mutualAuthRequired
	) throws Krb4Exception, IOException {
		pvno = (byte)Krb4.KRB_PROT_VERSION;
		littleEndian = true;
		keyVersion = (byte)creds.keyVersion;
		srealm = creds.srealm;
		encTicket = creds.encTicket;
		checksum = new_checksum;
		Krb4Authenticator authenticator = new Krb4Authenticator(
			cname,
			cinst,
			crealm,
			checksum
		);
		encAuthenticator = authenticator.encode(creds.sessionKey);
		_mutualAuthRequired = mutualAuthRequired;
		if (_mutualAuthRequired)
			msgType = (byte)Krb4.AUTH_MSG_APPL_REQUEST_MUTUAL;
		else
			msgType = (byte)Krb4.AUTH_MSG_APPL_REQUEST;
	}

	public Krb4APReq(
		Krb4Creds creds,
		String cname,
		String cinst,
		String crealm,
		boolean mutualAuthRequired
	) throws Krb4Exception, IOException {
		this(
			creds,
			cname,
			cinst,
			crealm,
			0,
			mutualAuthRequired
		);
	}

	public Krb4APReq(
		int new_keyVersion,
		String new_srealm,
		byte[] new_encTicket,
		byte[] new_encAuthenticator,
		boolean mutualAuthRequired
	) {
		pvno = (byte)Krb4.KRB_PROT_VERSION;
		littleEndian = true;
		keyVersion = (byte)new_keyVersion;
		srealm = new_srealm;
		encTicket = new_encTicket;
		encAuthenticator = new_encAuthenticator;
		_mutualAuthRequired = mutualAuthRequired;
		if (_mutualAuthRequired)
			msgType = (byte)Krb4.AUTH_MSG_APPL_REQUEST_MUTUAL;
		else
			msgType = (byte)Krb4.AUTH_MSG_APPL_REQUEST;
	}

	public byte[] encode() throws Krb4Exception, IOException {
		ByteArrayOutputStream o = new ByteArrayOutputStream();
		o.write(pvno);
		if (_mutualAuthRequired)
			msgType = (byte)Krb4.AUTH_MSG_APPL_REQUEST_MUTUAL;
		else
			msgType = (byte)Krb4.AUTH_MSG_APPL_REQUEST;
		byte temp = (byte)((msgType << 1) +
			(littleEndian ? 1 : 0));
		o.write(temp);
		o.write(keyVersion);
		o.write(Krb4Encode.toBytes(srealm));
		o.write((byte)encTicket.length);
		o.write((byte)encAuthenticator.length);
		o.write(encTicket);
		o.write(encAuthenticator);
		return o.toByteArray();
	}

	public Krb4APReq(byte[] data) throws Krb4Exception {
		Krb4Encode ref = new Krb4Encode(data);
		pvno = ref.getByte();
		if (pvno != Krb4.KRB_PROT_VERSION)
			throw new Krb4Exception(Krb4.RD_AP_VERSION);
		byte temp = ref.getByte();
		msgType = (byte)(temp >>> 1);
		if (msgType != Krb4.AUTH_MSG_APPL_REQUEST &&
			msgType != Krb4.AUTH_MSG_APPL_REQUEST_MUTUAL)
			throw new Krb4Exception(Krb4.RD_AP_MSG_TYPE);
		_mutualAuthRequired = msgType == Krb4.AUTH_MSG_APPL_REQUEST_MUTUAL;
		littleEndian = (temp & 1) == 1;
		ref.setByteOrder(littleEndian);
		keyVersion = ref.getByte();
		srealm = ref.getString();
		int encTicketLength = ref.getUnsignedByte();
		int encAuthenLength = ref.getUnsignedByte();
		encTicket = ref.getBytes(encTicketLength);
		encAuthenticator = ref.getBytes(encAuthenLength);
	}

	public void authenticate(byte[] key) throws Krb4Exception {
		authenticate(key, null, null, null, null);
	}

	public void authenticate(
		byte[] key,
		String sname,
		String sinst,
		String realm,
		byte[] caddr
	) throws Krb4Exception {
		byte[] ticketBytes = new byte[encTicket.length];
		long[] key_sched = new long[16];
		des.des_set_key(key, key_sched);
		byte[] ivec = new byte[key.length];
		System.arraycopy(key, 0, ivec, 0, key.length);
		des.pcbc_encrypt(encTicket, ticketBytes, key_sched,
			ivec, false);
		try {
			ticket = new Krb4Ticket(ticketBytes);
		} catch (Krb4Exception e) {
			ticket = null;
			if (e.returnCode() == Krb4.REP_OVERRUN)
				throw new Krb4Exception( Krb4.RD_AP_UNDEC);
			else
				throw e;
		}
		long now = new java.util.Date().getTime();
		if (now < ticket.timestamp) {
			if (ticket.timestamp - now > (long)Krb4.CLOCK_SKEW * 1000L)
				throw new Krb4Exception(Krb4.RD_AP_NYV);
		}
		else
			if (now - ticket.timestamp > ticket.lifetime)
				throw new Krb4Exception(Krb4.RD_AP_EXP);
		if (sname != null)
			if (!sname.equals(ticket.sname))
				throw new Krb4Exception(Krb4.RD_AP_NOT_US);
		if (sinst != null)
			if (!sinst.equals(ticket.sinst))
				throw new Krb4Exception(Krb4.RD_AP_NOT_US);
		if (realm != null)
			if (!realm.equals(srealm))
				throw new Krb4Exception(Krb4.RD_AP_NOT_US);
		if (caddr != null) {
			if (ticket.caddr.length != caddr.length)
				throw new Krb4Exception(Krb4.RD_AP_BADD);
			for (int i = 0; i < caddr.length; i++)
				if (ticket.caddr[i] != caddr[i])
					throw new Krb4Exception(Krb4.RD_AP_BADD);
		}
		byte[] authenticatorBytes = new byte[encAuthenticator.length];
		des.des_set_key(ticket.sessionKey, key_sched);
		ivec = new byte[ticket.sessionKey.length];
		System.arraycopy(ticket.sessionKey, 0, ivec, 0, ticket.sessionKey.length);
		des.pcbc_encrypt(encAuthenticator, authenticatorBytes, key_sched,
			ivec, false);
		try {
			authenticator = new Krb4Authenticator(authenticatorBytes,
				littleEndian);
		} catch (Krb4Exception e) {
			authenticator = null;
			if (e.returnCode() == Krb4.REP_OVERRUN)
				throw new Krb4Exception(Krb4.RD_AP_UNDEC);
			else
				throw e;
		}
		//XXX tbd: check for repeated request (RD_AP_REPEAT)
		if (!authenticator.cname.equals(ticket.cname) ||
			!authenticator.cinst.equals(ticket.cinst) ||
			!authenticator.crealm.equals(ticket.crealm))
			throw new Krb4Exception(Krb4.RD_AP_INCON);
		if (java.lang.Math.abs(authenticator.timestamp +
			authenticator.timestamp_5ms - now) >
			(long)Krb4.CLOCK_SKEW * 1000L)
			throw new Krb4Exception(Krb4.RD_AP_TIME);
	}

	public String getClientName() {
		if (ticket != null)
			return ticket.cname;
		else
			return null;
	}

	public String getClientInstance() {
		if (ticket != null)
			return ticket.cinst;
		else
			return null;
	}

	public String getClientRealm() {
		if (ticket != null)
			return ticket.crealm;
		else
			return null;
	}

	public String getServerName() {
		if (ticket != null)
			return ticket.sname;
		else
			return null;
	}

	public String getServerInstance() {
		if (ticket != null)
			return ticket.sinst;
		else
			return null;
	}

	public String getServerRealm() {
		return srealm;
	}

	public int getServerKeyVersion() {
		return (int)keyVersion;
	}

	public boolean mutualAuthRequired() {
		return _mutualAuthRequired;
	}

	public int getChecksum() {
		return checksum;
	}

	public byte[] getSessionKey() {
		if (ticket != null)
			return ticket.sessionKey;
		else
			return null;
	}
}
