// Copyright 1997 The Open Group Research Institute.  All rights reserved.

package krb5.lib;

import krb5.lib.asn1.*;
import krb5.lib.crypto.*;

/**
 * Implementation of encrypted data
 */
public class EncryptedData implements Cloneable {
	/**
	 * Encryption type
	 * @see krb5.lib.Krb5
	 */      
	public int eType;
	
	/**
	 * Kerberos version
	 * @see krb5.lib.Krb5
	 */  
	public Integer kvno; //optional
	
	/**
	 * Cipher text
	 */
	public byte[] cipher;
	
	/**
	 * Plaintext
	 */		
	public byte[] plain; //not part of ASN.1 encoding

	/**
	 * Class constructor
	 */	
	private EncryptedData() {
	}

	/**
	 * Clones a copy of the object
	 *
	 * @return Object is a result
	 * @see java.lang.Object
	 */
	public Object clone() {
		EncryptedData new_encryptedData = new EncryptedData();
		new_encryptedData.eType = eType;
		if (kvno != null) {
			new_encryptedData.kvno = new Integer(kvno.intValue());
		}
		if (cipher != null) {
			new_encryptedData.cipher = new byte[cipher.length];
			System.arraycopy(cipher, 0, new_encryptedData.cipher,
				0, cipher.length);
		}
		if (plain != null) {
			new_encryptedData.plain = new byte[plain.length];
			System.arraycopy(plain, 0, new_encryptedData.plain,
				0, plain.length);
		}
		return new_encryptedData;
	}

	/**
	 * Class constructor specifying encryption type, version and the cipher text
	 *
	 * @param new_eType is of type int
	 * @param new_kvno is of type Integer
	 * @param new_cipher is of type byte[]
	 * @see krb5.lib.Krb5
	 */
	public EncryptedData(
		int new_eType,
		Integer new_kvno,
		byte[] new_cipher
	) {
		eType = new_eType;
		kvno = new_kvno;
		cipher = new_cipher;
	}

	/**
	 * Class constructor specifying encryption key and the plaintext
	 *
	 * @param key is of type EncryptionKey
	 * @param plaintext is of type byte[]
	 * @exception KdcErrException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KdcErrException
	 */
	public EncryptedData(
		EncryptionKey key,
		byte[] plaintext
	) throws KdcErrException {
		EType etypeEngine = EType.getInstance(key.eType());
		cipher = etypeEngine.encrypt(plaintext, key.keyValue);
		eType = key.eType();
		kvno = key.kvno;
	}

	/**
	 * Class constructor specifying encryption key, initial vector and plaintext
	 *
	 * @param key is of type EncryptionKey
	 * @param ivec is of type byte[]
	 * @param plaintext is of type byte[]
	 * @exception KdcErrException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KdcErrException
	 */
	public EncryptedData(
		EncryptionKey key,
		byte[] ivec,
		byte[] plaintext
	) throws KdcErrException {
		EType etypeEngine = EType.getInstance(key.eType());
		cipher = etypeEngine.encrypt(plaintext, key.keyValue, ivec);
		eType = key.eType();
		kvno = key.kvno;
	}

	/**
	 * Class constructor specifying password and plaintext
	 *
	 * @param password is of type String
	 * @param plaintext is of type byte[]
	 * @exception KdcErrException an exception
	 * @see java.lang.String
	 * @exception KdcErrException an exception
	 */
	public EncryptedData(
		String password,
		byte[] plaintext
	) throws KdcErrException {
		EncryptionKey key = new EncryptionKey(password);
		EType etypeEngine = EType.getInstance(key.eType());
		cipher = etypeEngine.encrypt(plaintext, key.keyValue);
		eType = key.eType();
		kvno = key.kvno;
	}

	//XXX currently destructive on cipher
	/**
	 * Decrypts the cypher text for a given encryption key. It is destructive on cipher.
	 *
	 * @return byte[] is a result
	 * @param key is of type EncryptionKey
	 * @exception KdcErrException an exception
	 * @exception KrbApErrException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KrbApErrException
     * @see krb5.lib.KdcErrException
	 */	
	public byte[] decrypt(
		EncryptionKey key
	) throws KdcErrException, KrbApErrException {
		//XXX should check for matching eType and kvno here
		EType etypeEngine = EType.getInstance(eType);
		etypeEngine.decrypt(cipher, key.keyValue);
		plain = cipher;
		cipher = null;
		return etypeEngine.decryptedData(plain);
	}

	//XXX currently destructive on cipher
	/**
	 * Decrypts the cypher with the key and the vector. It is destructive on cipher.
	 *
	 * @return byte[] is a result
	 * @param key is of type EncryptionKey
	 * @param ivec is of type byte[]
	 * @exception KdcErrException an exception
	 * @exception KrbApErrException an exception
	 * @see krb5.lib.EncryptionKey
	 * @see krb5.lib.KrbApErrException
     * @see krb5.lib.KdcErrException
	 */	
	public byte[] decrypt(
		EncryptionKey key,
		byte[] ivec
	) throws KdcErrException, KrbApErrException {
		//XXX should check for matching eType and kvno here
		EType etypeEngine = EType.getInstance(eType);
		etypeEngine.decrypt(cipher, key.keyValue, ivec);
		plain = cipher;
		cipher = null;
		return etypeEngine.decryptedData(plain);
	}

	//XXX currently destructive on cipher
	/**
	 * Decrypts the cipher text with given password. It is currently destructive on cipher text.
	 *
	 * @return byte[] is a result
	 * @param password is of type String
	 * @exception KdcErrException an exception
	 * @exception KrbApErrException an exception
	 * @see java.lang.String
	 * @see krb5.lib.KrbApErrException
     * @see krb5.lib.KdcErrException
	 */	
	public byte[] decrypt(
		String password
	) throws KdcErrException, KrbApErrException {
		EncryptionKey key = new EncryptionKey(password);
		//XXX should check for matching eType here
		EType etypeEngine = EType.getInstance(eType);
		etypeEngine.decrypt(cipher, key.keyValue);
		plain = cipher;
		cipher = null;
		return etypeEngine.decryptedData(plain);
	}

	/**
	 * Returns the decrypted data
	 *
	 * @return byte[] is a result
	 * @exception KdcErrException an exception
	 * @see krb5.lib.KdcErrException
	 */
	public byte[] decryptedData() throws KdcErrException {
		if (plain != null) {
			EType etypeEngine = EType.getInstance(eType);
			return etypeEngine.decryptedData(plain);
		}
		return null;
	}

	/**
	 * Class constructor specifying encoding reference
	 *
	 * @param ref is of type EncodeRef
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.asn1.EncodeRef
	 * @see krb5.lib.Asn1Exception
	 */
	public EncryptedData(EncodeRef ref) throws Asn1Exception {
		EncodeRef subRef = decode.Sequence(ref);
		if (decode.Tag(subRef) == 0) {
			eType = decode.Integer(subRef.startOfData());
			//XXX need to validate that the type is recognized
			subRef.next();
		}
		else
			throw new Asn1Exception(Krb5.ASN1_BAD_ID);
		if (decode.Tag(subRef) == 1) {
			kvno = new Integer(decode.Integer(subRef.startOfData()));
			//XXX need to validate kvno length is right for eType
			subRef.next();
		}
		if (decode.Tag(subRef) == 2) {
			cipher = decode.OctetString(subRef.startOfData());
			subRef.next();
		}
		else
			throw new Asn1Exception(Krb5.ASN1_BAD_ID);
		if (subRef.isMoreInSequence())
			throw new Asn1Exception(Krb5.ASN1_BAD_ID);
	}

	/**
	 * Encodes the data in asn1
	 *
	 * @return byte[] is a result
	 * @exception Asn1Exception an exception
	 * @see krb5.lib.Asn1Exception
	 */
	public byte[] asn1Encode() throws Asn1Exception {
		if (kvno != null) {
			byte[][] encrypted_data = {
				encode.prependExplicitTag(0, encode.Integer(eType)),
				encode.prependExplicitTag(1, encode.Integer(kvno.intValue())),
				encode.prependExplicitTag(2, encode.OctetString(cipher))
			};
			return encode.Sequence(encrypted_data);
		}
		else {
			byte[][] encrypted_data = {
				encode.prependExplicitTag(0, encode.Integer(eType)),
				encode.prependExplicitTag(2, encode.OctetString(cipher))
			};
			return encode.Sequence(encrypted_data);
		}
	}

}
