package io.github.darconeous.gausskeycard; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.security.AESKey; import javacard.security.CryptoException; import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; import javacard.security.KeyAgreement; import javacard.security.KeyBuilder; import javacard.security.KeyPair; import javacard.security.RandomData; import javacardx.crypto.Cipher; public class GaussKeyCard extends Applet { /* Card commands we support. */ private static final byte INS_GET_PUBLIC_KEY = (byte)0x04; private static final byte INS_AUTHENTICATE = (byte)0x11; private static final byte INS_GET_CARD_INFO = (byte)0x14; private static final short OFFSET_CHALLENGE = (short)(ISO7816.OFFSET_CDATA + 65); // Constants from JavaCard 3.x. This way we can still install on // JC 2.2.2 cards and fall back to the traditional behavior. private static final byte TYPE_AES_TRANSIENT_DESELECT = 14; private static final byte TYPE_AES_TRANSIENT_RESET = 13; private final KeyPair key1; private final KeyAgreement ecdh; private final Cipher aes_ecb; private final AESKey aes_key; private final RandomData rng; public static void install(byte[] info, short off, byte len) { final GaussKeyCard applet = new GaussKeyCard(); applet.register(); } protected GaussKeyCard() { key1 = ECP256.newKeyPair(false); key1.genKeyPair(); ecdh = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH, false); ecdh.init(key1.getPrivate()); aes_ecb = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, false); AESKey key = null; try { // Put the AES key in RAM if we can. key = (AESKey)KeyBuilder.buildKey(TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_128, false); } catch (CryptoException e) { try { // This will use a bit more RAM, but // at least it isn't using flash. key = (AESKey)KeyBuilder.buildKey(TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); } catch (CryptoException x) { // Uggh. This will wear out the flash // eventually, but we don't have a better option. key = (AESKey)KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); } } aes_key = key; // We shouldn't require high-strength random numbers // for calculating the challenge salt. rng = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); } public void process(APDU apdu) { final byte[] buffer = apdu.getBuffer(); if (selectingApplet()) { return; } // We only support the proprietary class. if ((buffer[ISO7816.OFFSET_CLA] & (byte)0x80) != (byte)0x80) { ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); return; } switch (buffer[ISO7816.OFFSET_INS]) { case INS_GET_PUBLIC_KEY: processGetPublicKey(apdu); break; case INS_AUTHENTICATE: processAuthenticate(apdu); break; case INS_GET_CARD_INFO: processGetCardInfo(apdu); break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } } private void processGetCardInfo(APDU apdu) { final byte[] buffer = apdu.getBuffer(); final short le = apdu.setOutgoing(); short len = 0; buffer[len++] = 0x00; buffer[len++] = 0x01; len = le > 0 ? (le > len ? len : le) : len; apdu.setOutgoingLength(len); apdu.sendBytes((short)0, len); } private void processGetPublicKey(APDU apdu) { final byte[] buffer = apdu.getBuffer(); final short le = apdu.setOutgoing(); final ECPublicKey epubk = (ECPublicKey)key1.getPublic(); short len = epubk.getW(buffer, (short)0); len = le > 0 ? (le > len ? len : le) : len; apdu.setOutgoingLength(len); apdu.sendBytes((short)0, len); } private void processAuthenticate(APDU apdu) { final byte[] buffer = apdu.getBuffer(); final short incomingLength = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF); if (incomingLength < (short)0x51) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } ecdh.generateSecret(buffer, ISO7816.OFFSET_CDATA, (short)65, buffer, (short)16); aes_key.setKey(buffer, (short)16); aes_ecb.init(aes_key, Cipher.MODE_ENCRYPT); // Generate the random salt. rng.generateData(buffer, OFFSET_CHALLENGE, (short)4); short len = aes_ecb.doFinal(buffer, OFFSET_CHALLENGE, (short)16, buffer, (short)0); final short le = apdu.setOutgoing(); len = le > 0 ? (le > len ? len : le) : len; apdu.setOutgoingLength(len); apdu.sendBytes((short)0, len); } }