/**
 * 
 */
package fr.cryptis.crypto;

import javacard.framework.Applet;
import javacard.framework.ISOException;
import javacard.framework.ISO7816;
import javacard.framework.APDU;
import javacard.security.DESKey;
import javacard.security.KeyBuilder;
import javacard.security.KeyPair;
import javacard.security.RSAPrivateCrtKey;
import javacard.security.RSAPublicKey;
import javacard.security.RandomData;
import javacard.security.Signature;
import javacardx.crypto.Cipher;

/**
 * @author sauveron
 *
 */
public class Crypt extends Applet implements ISO7816 {

	public final static byte SET_KEY = (byte)0x10;	
	public final static byte GET_CHALLENGE = (byte) 0x20;
	public final static byte SET_CHALLENGE = (byte)0x30;
	public final static byte VERIFY_CRYPTO = (byte)0x40;	
	
	public final static short RNG_SIZE = (short)8;
	public final static short DES_KEY_SIZE = (short) 24; 

	private DESKey k;
	private RandomData rng;
	private KeyPair kp;
	private RSAPublicKey pub;
	private RSAPrivateCrtKey priv;
	private Signature sgn;	
	private byte [] challenge;
	private RSAPublicKey pubFromOther;
	private Cipher cipher;
	
	public Crypt() {
		k = (DESKey)  KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false);
		rng = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
		kp = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512);
		kp.genKeyPair();
		pub = (RSAPublicKey) kp.getPublic();
		priv = (RSAPrivateCrtKey) kp.getPrivate();
		sgn = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false);
		challenge = new byte[RNG_SIZE];
		pubFromOther = (RSAPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC, KeyBuilder.LENGTH_RSA_512, false);
		cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);

	}

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// GP-compliant JavaCard applet registration
		new Crypt().register(bArray, (short) (bOffset + 1), bArray[bOffset]);
	}

	public void process(APDU apdu) {
		// Good practice: Return 9000 on SELECT
		if (selectingApplet()) {
			return;
		}

		byte[] buf = apdu.getBuffer();
		short offset = 0;
		short lc;
		
		switch (buf[OFFSET_INS]) {
		case (byte) SET_KEY:
			lc = apdu.setIncomingAndReceive();
			if (lc != DES_KEY_SIZE) ISOException.throwIt(SW_WRONG_LENGTH);			
			k.setKey(buf, OFFSET_CDATA);
			break;
		case GET_CHALLENGE:
			offset = OFFSET_CLA;
			rng.generateData(buf, offset, RNG_SIZE);
			offset += RNG_SIZE;
			// Sauvegarde du RNG généré
			Util.arrayCopy(buf, OFFSET_CLA, challenge, (short)0, RNG_SIZE) ;

			offset += pub.getExponent(buf, offset);
			offset += pub.getModulus(buf, offset);	
			
			sgn.init(priv, Signature.MODE_SIGN);
			offset += sgn.sign(buf, OFFSET_CLA, offset, buf, offset);
			
			apdu.setOutgoingAndSend(OFFSET_CLA, offset);
			break;

		case (byte) SET_CHALLENGE:
			lc = apdu.setIncomingAndReceive();
			offset = OFFSET_CDATA; 
			offset+=RNG_SIZE;

			pubFromOther.setExponent(buf, offset, (short)3);
			offset+=(short)3;
			pubFromOther.setModulus(buf, offset, (short)0x40);
			offset+=(short)0x40;

			sgn.init(pubFromOther, Signature.MODE_VERIFY);
			if(!sgn.verify(buf, OFFSET_CDATA, (short)(offset-OFFSET_CDATA), buf, offset,(short)(lc-offset+OFFSET_CDATA)))
				ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);			
			//... WE ARE HERE!!!
			
			break;	
		default:
			// good practice: If you don't know the INStruction, say so:
			ISOException.throwIt(SW_INS_NOT_SUPPORTED);
		}
	}
}