/**
 * 
 */
package fr.cryptis.cryptoonjc;

import javacard.framework.Applet;
import javacard.framework.ISOException;
import javacard.framework.ISO7816;
import javacard.framework.APDU;
import javacard.framework.Util;
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 CryptoOnJC 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) 0x08;
	public final static short DES_KEY_SIZE = (short) 24; 
	
	private RandomData rng;
	private DESKey k;
	private KeyPair kp;
	private RSAPublicKey pub;
	private RSAPrivateCrtKey priv;
	private RSAPublicKey pubFromOther;
	private Signature sgn;
	private Cipher cipher;
	private byte[] challenge;
	
	public CryptoOnJC() {
		rng = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
		k = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false) ;
		kp = new KeyPair(KeyPair.ALG_RSA_CRT, KeyBuilder.LENGTH_RSA_512);
		kp.genKeyPair();
		pub = (RSAPublicKey) kp.getPublic();
		priv = (RSAPrivateCrtKey) kp.getPrivate();
		pubFromOther = (RSAPublicKey)KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PUBLIC,KeyBuilder.LENGTH_RSA_512,false);
		sgn = Signature.getInstance(Signature.ALG_RSA_SHA_PKCS1, false);
		cipher = Cipher.getInstance(Cipher.ALG_DES_CBC_NOPAD, false);
		challenge = new byte[RNG_SIZE];
	}

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// GP-compliant JavaCard applet registration
		new CryptoOnJC().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 lc;
		short bOff;
		
		switch (buf[ISO7816.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 (byte) GET_CHALLENGE:
			bOff = OFFSET_CLA; 
			rng.generateData(buf, bOff, RNG_SIZE);
			Util.arrayCopyNonAtomic(buf, bOff, challenge, (short)0, RNG_SIZE); // copy for latter
			bOff+=RNG_SIZE;
			bOff+=pub.getExponent(buf, bOff);
			bOff+=pub.getModulus(buf, bOff);
			sgn.init(priv, Signature.MODE_SIGN);
			bOff += sgn.sign(buf, OFFSET_CLA, bOff, buf, bOff);
			apdu.setOutgoingAndSend(OFFSET_CLA, bOff);
			break;
		case (byte) SET_CHALLENGE:
			lc = apdu.setIncomingAndReceive();
			bOff = OFFSET_CDATA; 
			bOff+=RNG_SIZE;
			
			pubFromOther.setExponent(buf, bOff, (short)3);
			bOff+=(short)3;
			pubFromOther.setModulus(buf, bOff, (short)0x40);
			bOff+=(short)0x40;
			sgn.init(pubFromOther, Signature.MODE_VERIFY);
			if(!sgn.verify(buf, OFFSET_CDATA, (short)(bOff-OFFSET_CDATA), buf, bOff,(short)(lc-bOff+OFFSET_CDATA)))
				ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
			cipher.init(k, Cipher.MODE_ENCRYPT);
			bOff = OFFSET_CLA; 
			bOff += cipher.doFinal(buf, OFFSET_CDATA, RNG_SIZE, buf, bOff);
			sgn.init(priv, Signature.MODE_SIGN);
			bOff += sgn.sign(buf, OFFSET_CLA, bOff, buf, bOff);
			apdu.setOutgoingAndSend(OFFSET_CLA, bOff);
			break;
		case (byte) VERIFY_CRYPTO:
			lc = apdu.setIncomingAndReceive();
			bOff = OFFSET_CDATA;			
			bOff+=RNG_SIZE;
			
			sgn.init(pubFromOther, Signature.MODE_VERIFY);
			if(!sgn.verify(buf, OFFSET_CDATA, (short)(bOff-OFFSET_CDATA), buf, bOff,(short)(lc-bOff+OFFSET_CDATA)))
				ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
			
			bOff = OFFSET_CDATA;	
			//challenge = new byte[] {(byte)0xDD, (byte)0x79, (byte)0xB3, (byte)0x47, (byte)0x1C, (byte)0xFA, (byte)0x8F, (byte)0x3F};
			cipher.init(k, Cipher.MODE_DECRYPT);
			cipher.doFinal(buf, OFFSET_CDATA, RNG_SIZE, buf, bOff);
			if(Util.arrayCompare(buf, OFFSET_CDATA, challenge, (short)0, RNG_SIZE)!=0)
				ISOException.throwIt(SW_CONDITIONS_NOT_SATISFIED);
			break;			
		default:
			// good practice: If you don't know the INStruction, say so:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}
	}
}