/** 
 * Copyright (c) 1998, 2021, Oracle and/or its affiliates. All rights reserved.
 * 
 */

package hwb1;

import javacard.framework.*;
import javacard.security.AESKey;
import javacard.security.CryptoException;
import javacard.security.KeyBuilder;
import javacard.security.Signature;
import javacardx.crypto.Cipher;

/**
 * Applet class
 * 
 * @author <user>
 */
public class SecretApplet extends Applet {
    private AESKey encryptionKey = null;
    private AESKey macKey = null;

    private byte[] cipherIV = new byte[] { 0x18, 0x24, 0x57, (byte)0xD6, (byte)0x97, 0x33, 0x69, (byte)0xED, 0x05, 0x4D, 0x6E, 0x14, (byte)0x93, (byte)0xE4, (byte)0xB7, 0x45 };
    private byte[] macIV = new byte[] { (byte)0xB6, 0x6C, (byte)0xAC, (byte)0xC7, (byte)0xD6, 0x78, (byte)0x88, 0x18, 0x07, (byte)0x88, (byte)0xA4, 0x27, 0x07, 0x55, 0x50, 0x2A };

    private Cipher cipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false);;
    private Signature signature = Signature.getInstance(Signature.ALG_AES_MAC_128_NOPAD, false);

    private short length;
    private byte[] storage = new byte[(short)(64 + 16)];

    /**
     * Installs this applet.
     * 
     * @param bArray  the array containing installation parameters
     * @param bOffset the starting offset in bArray
     * @param bLength the length in bytes of the parameter data in bArray
     */
    public static void install(byte[] bArray, short bOffset, byte bLength) {
        new SecretApplet();
    }

    /**
     * Only this class's install method should create the applet object.
     */
    protected SecretApplet() {
        register();
    }

    /**
     * Gets a key for encrypting and decrypting data.
     *
     * @return
     * A key for encrypting and decrypting data.
     */
    protected AESKey getEncryptionKey() {
        if (encryptionKey == null) {
            encryptionKey = initializeKey(new byte[] { 0x59, (byte)0xA3, (byte)0xFA, 0x76, 0x35, 0x40, 0x01, (byte)0x82, (byte)0xA5, (byte)0xC0, (byte)0xAB, (byte)0xE0, 0x1F, 0x30, (byte)0x89, (byte)0xFA });
        }
        
        return encryptionKey;
    }

    /**
     * Gets a key for signing and verifying messages.
     *
     * @return
     * A key for signing and verifying messages.
     */
    protected AESKey getMACKey() {
        if (macKey == null) {
            macKey = initializeKey(new byte[] { 0x58, (byte)0x9F, 0x61, 0x62, (byte)0xBB, 0x10, (byte)0x89, (byte)0xD8, 0x1A, 0x16, (byte)0xD3, 0x2A, 0x3F, 0x06, 0x27, (byte)0xC6 });
        }
        
        return macKey;
    }

    protected AESKey initializeKey(byte[] keyBytes) {
        AESKey key = (AESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false);
        key.setKey(keyBytes, (short)0);
        return key;
    }

    protected Cipher getEncryptionCipher(byte mode) {
        cipher.init(encryptionKey, mode, cipherIV, (short)0, (short)cipherIV.length);
        return cipher;
    }

    protected Signature getMACSignature(byte mode) {
        signature.init(macKey, mode, macIV, (short)0, (short)macIV.length);
        return signature;
    }

    /**
     * Processes an incoming APDU.
     * 
     * @see APDU
     * @param apdu the incoming APDU
     */
    public void process(APDU apdu) {
        byte[] buffer = apdu.getBuffer();

        if (selectingApplet()) {
            ISOException.throwIt(ISO7816.SW_NO_ERROR);
        } else {
            if (buffer[ISO7816.OFFSET_CLA] != (byte)0x80) {
                ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
            } else {
                byte instruction = buffer[ISO7816.OFFSET_INS];

                try {
                    switch (instruction) {
                        case 0x42:
                            length = apdu.setIncomingAndReceive();
                            
                            if (length > 64) {
                                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
                            } else {
                                Cipher cipher = getEncryptionCipher(Cipher.MODE_ENCRYPT);
                                cipher.doFinal(buffer, ISO7816.OFFSET_CDATA, length, storage, (short)0);
    
                                Signature signature = getMACSignature(Signature.MODE_SIGN);
                                short totalLength = (short)(length + signature.sign(storage, (short)0, length, storage, length));
                                apdu.setOutgoing();
                                apdu.setOutgoingLength(totalLength);
                                apdu.sendBytesLong(storage, (short)0, totalLength);
                            }
                            break;
                        case 0x44:
                            length = apdu.setIncomingAndReceive();
                            
                            if (length > 80) {
                                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
                            } else {
                                Signature signature = getMACSignature(Signature.MODE_VERIFY);
                                short signatureLength = 16;
                                short messageLength = (short)(length - signatureLength);
                                short signatureOffset = (short)(ISO7816.OFFSET_CDATA + messageLength);

                                if (!signature.verify(buffer, ISO7816.OFFSET_CDATA, messageLength, buffer, signatureOffset, signatureLength)) {
                                    ISOException.throwIt(ISO7816.SW_WRONG_DATA);
                                } else {
                                    Cipher cipher = getEncryptionCipher(Cipher.MODE_DECRYPT);
                                    cipher.doFinal(buffer, ISO7816.OFFSET_CDATA, messageLength, storage, (short)0);
                                    
                                    apdu.setOutgoing();
                                    apdu.setOutgoingLength(messageLength);
                                    apdu.sendBytesLong(storage, (short)0, messageLength);
                                }
                            }
                            break;
                        default:
                            ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
                            break;
                    }
                } catch (CryptoException exception) {
                    ISOException.throwIt((short)(ISO7816.SW_WRONG_LENGTH | exception.getReason()));
                }
                finally {
                    Util.arrayFill(storage, (short)0, (short)storage.length, (byte)0);
                }
                
                ISOException.throwIt(ISO7816.SW_NO_ERROR);
            }
        }
    }
}
