// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb4.lib;

import java.io.*;
import krb4.lib.crypto.des;

public class Krb4KRBPriv {
	public byte pvno;
	public byte msgType;
	boolean littleEndian;
	public byte[] encData;
	public byte[] data;
	public long timestamp_5ms;
	public byte[] saddr;
	public boolean directionToLower;
	public long timestamp;

	public Krb4KRBPriv(
		Krb4Creds creds,
		byte[] new_data,
		byte[] new_saddr,
		int sport,
		byte[] raddr,
		int rport
	) throws Krb4Exception, IOException {
		long new_timestamp = new java.util.Date().getTime();
		long new_timestamp_5ms = new_timestamp % 1000L;
		init(
			creds.sessionKey,
			new_data,
			new_timestamp_5ms,
			new_saddr,
			sport,
			raddr,
			rport,
			new_timestamp
		);
	}

	public Krb4KRBPriv(
		Krb4APReq ap_req,
		byte[] new_data,
		byte[] new_saddr,
		int sport,
		byte[] raddr,
		int rport
	) throws Krb4Exception, IOException {
		long new_timestamp = new java.util.Date().getTime();
		long new_timestamp_5ms = new_timestamp % 1000L;
		if (ap_req == null || ap_req.ticket == null)
			throw new Krb4Exception(Krb4.RD_AP_UNDEC);
		init(
			ap_req.ticket.sessionKey,
			new_data,
			new_timestamp_5ms,
			new_saddr,
			sport,
			raddr,
			rport,
			new_timestamp
		);
	}

	public Krb4KRBPriv(
		byte[] key,
		byte[] new_data,
		byte[] new_saddr,
		int sport,
		byte[] raddr,
		int rport
	) throws Krb4Exception, IOException {
		long new_timestamp = new java.util.Date().getTime();
		long new_timestamp_5ms = new_timestamp % 1000L;
		init(
			key,
			new_data,
			new_timestamp_5ms,
			new_saddr,
			sport,
			raddr,
			rport,
			new_timestamp
		);
	}

	public Krb4KRBPriv(
		byte[] key,
		byte[] new_data,
		long new_timestamp_5ms,
		byte[] new_saddr,
		int sport,
		byte[] raddr,
		int rport,
		long new_timestamp
	) throws Krb4Exception, IOException {
		init(
			key,
			new_data,
			new_timestamp_5ms,
			new_saddr,
			sport,
			raddr,
			rport,
			new_timestamp
		);
	}

	public void init(
		byte[] key,
		byte[] new_data,
		long new_timestamp_5ms,
		byte[] new_saddr,
		int sport,
		byte[] raddr,
		int rport,
		long new_timestamp
	) throws Krb4Exception, IOException {
		pvno = (byte)Krb4.KRB_PROT_VERSION;
		msgType = (byte)Krb4.AUTH_MSG_PRIVATE;
		littleEndian = true;
		data = new_data;
		timestamp_5ms = new_timestamp_5ms;
		saddr = new_saddr;
		timestamp = new_timestamp;

		ByteArrayOutputStream o = new ByteArrayOutputStream();
		o.write(Krb4Encode.toBytes(data.length));
		o.write(data);
		o.write((byte)(timestamp_5ms / 5L));
		o.write(saddr);
		directionToLower = isDirectionToLower(saddr, sport, raddr, rport);
		int temp = (int)(((directionToLower ? 1L : 0L) << 31)
			| ((timestamp / 1000L) & 0x7fffffffL));
		o.write(Krb4Encode.toBytes(temp));
		byte[] unencData = o.toByteArray();
		encData = new byte[unencData.length +
			(unencData.length % 8 != 0 ? 8 - unencData.length % 8 : 0)];
		long[] key_schedule = new long[16];
		des.des_set_key(key, key_schedule);
		byte[] ivec = new byte[key.length];
		System.arraycopy(key, 0, ivec, 0, key.length);
		des.pcbc_encrypt(unencData, encData,
			key_schedule, ivec, true);
	}

	static boolean isDirectionToLower(
		byte[] saddr,
		int sport,
		byte[] raddr,
		int rport
	) {
		boolean _directionToLower = false;
		//XXX this is probably wrong...
		int s = Krb4Encode.quad2int(saddr, 0, false);
		int r = Krb4Encode.quad2int(raddr, 0, false);
		if (s > r)
			_directionToLower = true;
		else
			if (s < r)
				_directionToLower = false;
			else
				if (sport > rport)
					_directionToLower = true;
		return _directionToLower;
	}

	public byte[] encode() throws Krb4Exception, IOException {
		ByteArrayOutputStream o = new ByteArrayOutputStream();
		o.write(pvno);
		byte temp = (byte)((msgType << 1) +
			(littleEndian ? 1 : 0));
		o.write(temp);
		o.write(Krb4Encode.toBytes(encData.length));
		o.write(encData);
		return o.toByteArray();
	}

	public Krb4KRBPriv(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_PRIVATE)
			throw new Krb4Exception(Krb4.RD_AP_MSG_TYPE);
		littleEndian = (temp & 1) == 1;
		ref.setByteOrder(littleEndian);
		//XXX limited to signed int max
		int encDataLength = ref.getInt();
		encData = ref.getBytes(encDataLength);
	}

	public byte[] decrypt(
		byte[] key,
		byte[] new_saddr,
		int sport,
		byte[] raddr,
		int rport
	) throws Krb4Exception, IOException {
		byte[] unencData = new byte[encData.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(encData, unencData, key_sched,
			ivec, false);
		//XXX limited to signed int max
		Krb4Encode ref = new Krb4Encode(unencData);
		ref.setByteOrder(littleEndian);
		int dataLength = ref.getInt();
		data = ref.getBytes(dataLength);
		timestamp_5ms = (long)ref.getUnsignedByte() * 5L;
		saddr = ref.getBytes(4);
		int temp = ref.getInt();
		directionToLower = (temp >>> 31) == 1;
		timestamp = ((((long)temp) & 0x7fffffffL) * 1000L);
		if (new_saddr != null && raddr != null) {
			if (directionToLower != isDirectionToLower(
				new_saddr, sport, raddr, rport))
				//XXX may be wrong exception
				throw new Krb4Exception(Krb4.RD_AP_ORDER);
		}
		return data;
	}

	public byte[] decrypt(byte[] key) throws Krb4Exception, IOException {
		return decrypt(key, null, 0, null, 0);
	}

	public byte[] decrypt(Krb4Creds creds) throws Krb4Exception,
		IOException {
		return decrypt(creds.sessionKey);
	}

	public byte[] decrypt(
		Krb4Creds creds,
		byte[] saddr,
		int sport,
		byte[] raddr,
		int rport
	) throws Krb4Exception,
		IOException {
		return decrypt(
			creds.sessionKey,
			saddr,
			sport,
			raddr,
			rport
		);
	}

	public byte[] decrypt(Krb4APReq ap_req) throws Krb4Exception,
		IOException {
		if (ap_req == null || ap_req.ticket == null)
			throw new Krb4Exception(Krb4.RD_AP_UNDEC);
		return decrypt(ap_req.ticket.sessionKey);
	}

	public byte[] decrypt(
		Krb4APReq ap_req,
		byte[] saddr,
		int sport,
		byte[] raddr,
		int rport
	) throws Krb4Exception,
		IOException {
		if (ap_req == null || ap_req.ticket == null)
			//XXX may be wrong exception
			throw new Krb4Exception(Krb4.RD_AP_UNDEC);
		return decrypt(
			ap_req.ticket.sessionKey,
			saddr,
			sport,
			raddr,
			rport
		);
	}
}
