diff --git a/src/main/java/im/status/keycard/Crypto.java b/src/main/java/im/status/keycard/Crypto.java index c93cffd..d374241 100644 --- a/src/main/java/im/status/keycard/Crypto.java +++ b/src/main/java/im/status/keycard/Crypto.java @@ -36,8 +36,6 @@ public class Crypto { private Signature hmacSHA512; private HMACKey hmacKey; - private AESKey tmpAES256; - private byte[] hmacBlock; Crypto() { @@ -47,8 +45,6 @@ public class Crypto { sha512 = MessageDigest.getInstance(MessageDigest.ALG_SHA_512, false); aesCbcIso9797m2 = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false); - tmpAES256 = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false); - try { hmacSHA512 = Signature.getInstance(Signature.ALG_HMAC_SHA_512, false); hmacKey = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false); @@ -59,12 +55,6 @@ public class Crypto { } - public short oneShotAES(byte mode, byte[] src, short sOff, short sLen, byte[] dst, short dOff, byte[] key, short keyOff) { - tmpAES256.setKey(key, keyOff); - aesCbcIso9797m2.init(tmpAES256, mode, src, sOff, AES_BLOCK_SIZE); - return aesCbcIso9797m2.doFinal(src, (short) (sOff + AES_BLOCK_SIZE), (short) (sLen - AES_BLOCK_SIZE), dst, dOff); - } - boolean bip32IsHardened(byte[] i, short iOff) { return (i[iOff] & (byte) 0x80) == (byte) 0x80; } diff --git a/src/main/java/im/status/keycard/KeycardApplet.java b/src/main/java/im/status/keycard/KeycardApplet.java index f7770a5..5f11d03 100644 --- a/src/main/java/im/status/keycard/KeycardApplet.java +++ b/src/main/java/im/status/keycard/KeycardApplet.java @@ -21,7 +21,6 @@ public class KeycardApplet extends Applet { static final byte INS_GENERATE_MNEMONIC = (byte) 0xD2; static final byte INS_REMOVE_KEY = (byte) 0xD3; static final byte INS_GENERATE_KEY = (byte) 0xD4; - static final byte INS_DUPLICATE_KEY = (byte) 0xD5; static final byte INS_SIGN = (byte) 0xC0; static final byte INS_SET_PINLESS_PATH = (byte) 0xC1; static final byte INS_EXPORT_KEY = (byte) 0xC2; @@ -63,11 +62,6 @@ public class KeycardApplet extends Applet { static final byte GENERATE_MNEMONIC_P1_CS_MAX = 8; static final byte GENERATE_MNEMONIC_TMP_OFF = SecureChannel.SC_OUT_OFFSET + ((((GENERATE_MNEMONIC_P1_CS_MAX * 32) + GENERATE_MNEMONIC_P1_CS_MAX) / 11) * 2); - static final byte DUPLICATE_KEY_P1_START = 0x00; - static final byte DUPLICATE_KEY_P1_ADD_ENTROPY = 0x01; - static final byte DUPLICATE_KEY_P1_EXPORT = 0x02; - static final byte DUPLICATE_KEY_P1_IMPORT = 0x03; - static final byte SIGN_P1_CURRENT_KEY = 0x00; static final byte SIGN_P1_DERIVE = 0x01; static final byte SIGN_P1_DERIVE_AND_MAKE_CURRENT = 0x02; @@ -139,9 +133,6 @@ public class KeycardApplet extends Applet { private Crypto crypto; private SECP256k1 secp256k1; - private byte[] duplicationEncKey; - private short expectedEntropy; - private byte[] derivationOutput; private byte[] data; @@ -201,9 +192,6 @@ public class KeycardApplet extends Applet { signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); - duplicationEncKey = new byte[(short)(KeyBuilder.LENGTH_AES_256/8)]; - expectedEntropy = -1; - derivationOutput = JCSystem.makeTransientByteArray((short) (Crypto.KEY_SECRET_SIZE + CHAIN_CODE_SIZE), JCSystem.CLEAR_ON_RESET); data = new byte[MAX_DATA_LENGTH + 1]; @@ -278,9 +266,6 @@ public class KeycardApplet extends Applet { case INS_GENERATE_KEY: generateKey(apdu); break; - case INS_DUPLICATE_KEY: - duplicateKey(apdu); - break; case INS_SIGN: sign(apdu); break; @@ -1101,116 +1086,6 @@ public class KeycardApplet extends Applet { generateKeyUIDAndRespond(apdu, apduBuffer); } - /** - * Processes the DUPLICATE KEY command. The actual processing depends on the subcommand. - * - * @param apdu the JCRE-owned APDU object. - */ - private void duplicateKey(APDU apdu) { - byte[] apduBuffer = apdu.getBuffer(); - - if (apduBuffer[ISO7816.OFFSET_P1] == DUPLICATE_KEY_P1_ADD_ENTROPY) { - if (expectedEntropy <= 0) { - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - - secureChannel.oneShotDecrypt(apduBuffer); - addEntropy(apduBuffer); - return; - } else { - secureChannel.preprocessAPDU(apduBuffer); - - if (!pin.isValidated()) { - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - } - - switch(apduBuffer[ISO7816.OFFSET_P1]) { - case DUPLICATE_KEY_P1_START: - startDuplication(apduBuffer); - break; - case DUPLICATE_KEY_P1_EXPORT: - short len = exportDuplicate(apduBuffer); - secureChannel.respond(apdu, len, ISO7816.SW_NO_ERROR); - break; - case DUPLICATE_KEY_P1_IMPORT: - importDuplicate(apduBuffer); - pinlessPathLen = 0; - generateKeyUIDAndRespond(apdu, apduBuffer); - break; - default: - ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); - break; - } - } - - private void startDuplication(byte[] apduBuffer) { - if (apduBuffer[ISO7816.OFFSET_LC] != (short) duplicationEncKey.length) { - ISOException.throwIt(ISO7816.SW_WRONG_DATA); - } - - JCSystem.beginTransaction(); - Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, duplicationEncKey, (short) 0, (short) duplicationEncKey.length); - expectedEntropy = (short) (apduBuffer[ISO7816.OFFSET_P2] - 1); - JCSystem.commitTransaction(); - } - - private void addEntropy(byte[] apduBuffer) { - if (apduBuffer[ISO7816.OFFSET_LC] != (short) duplicationEncKey.length) { - ISOException.throwIt(ISO7816.SW_WRONG_DATA); - } - - JCSystem.beginTransaction(); - for (short i = 0; i < (short) duplicationEncKey.length; i++) { - duplicationEncKey[i] ^= apduBuffer[(short) (ISO7816.OFFSET_CDATA + i)]; - } - - expectedEntropy--; - JCSystem.commitTransaction(); - } - - private void finalizeDuplicationKey() { - if (expectedEntropy != 0) { - ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); - } - - expectedEntropy = -1; - } - - private short exportDuplicate(byte[] apduBuffer) { - finalizeDuplicationKey(); - crypto.random.generateData(apduBuffer, SecureChannel.SC_OUT_OFFSET, Crypto.AES_BLOCK_SIZE); - short sOff = (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE); - short off = sOff; - Util.arrayCopyNonAtomic(apduBuffer, SecureChannel.SC_OUT_OFFSET, apduBuffer, off, Crypto.AES_BLOCK_SIZE); - off += Crypto.AES_BLOCK_SIZE; - - apduBuffer[off++] = TLV_KEY_TEMPLATE; - short keyTemplateLenOff = off++; - - apduBuffer[off++] = TLV_PRIV_KEY; - apduBuffer[off] = (byte) masterPrivate.getS(apduBuffer, (short) (off + 1)); - apduBuffer[keyTemplateLenOff] = (byte) (apduBuffer[off] + 2); - off += (short) (apduBuffer[off] + 1); - - if (isExtended) { - apduBuffer[off++] = TLV_CHAIN_CODE; - apduBuffer[off++] = CHAIN_CODE_SIZE; - Util.arrayCopyNonAtomic(masterChainCode, (short) 0, apduBuffer, off, CHAIN_CODE_SIZE); - apduBuffer[keyTemplateLenOff] += (byte) (CHAIN_CODE_SIZE + 2); - off += CHAIN_CODE_SIZE; - } - - return (short) (Crypto.AES_BLOCK_SIZE + crypto.oneShotAES(Cipher.MODE_ENCRYPT, apduBuffer, sOff, (short)(off - sOff), apduBuffer, sOff, duplicationEncKey, (short) 0)); - } - - private void importDuplicate(byte[] apduBuffer) { - finalizeDuplicationKey(); - short len = crypto.oneShotAES(Cipher.MODE_DECRYPT, apduBuffer, ISO7816.OFFSET_CDATA, (short) (apduBuffer[ISO7816.OFFSET_LC] & 0xff), apduBuffer, ISO7816.OFFSET_CDATA, duplicationEncKey, (short) 0); - apduBuffer[ISO7816.OFFSET_LC] = (byte) len; - loadKeyPair(apduBuffer); - } - /** * Processes the SIGN command. Requires a secure channel to open and either the PIN to be verified or the PIN-less key * path to be the current key path. This command supports signing a precomputed 32-bytes hash. The signature is diff --git a/src/test/java/im/status/keycard/KeycardTest.java b/src/test/java/im/status/keycard/KeycardTest.java index 846c275..7a74cc8 100644 --- a/src/test/java/im/status/keycard/KeycardTest.java +++ b/src/test/java/im/status/keycard/KeycardTest.java @@ -1360,106 +1360,6 @@ public class KeycardTest { assertArrayEquals(data, response.getData()); } - @Test - @DisplayName("DUPLICATE KEY command") - @Capabilities("keyManagement") - void duplicateTest() throws Exception { - int secretCount = 5; - Random random = new Random(); - byte[][] secrets = new byte[secretCount][32]; - for (int i = 0; i < secretCount; i++) { - random.nextBytes(secrets[i]); - } - - // Security condition violation: SecureChannel not open - APDUResponse response = cmdSet.duplicateKeyStart(secretCount, secrets[0]); - assertEquals(0x6985, response.getSw()); - - cmdSet.autoOpenSecureChannel(); - - // Security condition violation: PIN not verified - response = cmdSet.duplicateKeyStart(secretCount, secrets[0]); - assertEquals(0x6985, response.getSw()); - - response = cmdSet.verifyPIN("000000"); - assertEquals(0x9000, response.getSw()); - response = cmdSet.generateKey(); - assertEquals(0x9000, response.getSw()); - byte[] keyUID = response.getData(); - - // Init duplication - response = cmdSet.duplicateKeyStart(secretCount, secrets[0]); - assertEquals(0x9000, response.getSw()); - - // Adding key entropy must work without secure channel and PIN authentication - reset(); - response = cmdSet.select(); - assertEquals(0x9000, response.getSw()); - - // Put all except the last piece of entropy - for (int i = 1; i < (secretCount - 1); i++) { - response = cmdSet.duplicateKeyAddEntropy(secrets[i]); - assertEquals(0x9000, response.getSw()); - } - - cmdSet.autoOpenSecureChannel(); - response = cmdSet.verifyPIN("000000"); - assertEquals(0x9000, response.getSw()); - - // Try to backup before enough entropy has been set - response = cmdSet.duplicateKeyExport(); - assertEquals(0x6985, response.getSw()); - - reset(); - response = cmdSet.select(); - assertEquals(0x9000, response.getSw()); - - // Put last piece of entropy - response = cmdSet.duplicateKeyAddEntropy(secrets[(secretCount - 1)]); - assertEquals(0x9000, response.getSw()); - - // Try putting more entropy (failure expected) - response = cmdSet.duplicateKeyAddEntropy(secrets[(secretCount - 1)]); - assertEquals(0x6985, response.getSw()); - - cmdSet.autoOpenSecureChannel(); - response = cmdSet.verifyPIN("000000"); - assertEquals(0x9000, response.getSw()); - - // Backup - response = cmdSet.duplicateKeyExport(); - assertEquals(0x9000, response.getSw()); - byte[] backup = response.getData(); - - // Try to restore the backup (failure expected, session is over) - response = cmdSet.duplicateKeyImport(backup); - assertEquals(0x6985, response.getSw()); - - // Now try to restore the backup and check that the key UID matches, but first change the keys to random ones - response = cmdSet.generateKey(); - assertEquals(0x9000, response.getSw()); - - response = cmdSet.duplicateKeyStart(secretCount, secrets[0]); - assertEquals(0x9000, response.getSw()); - - reset(); - response = cmdSet.select(); - assertEquals(0x9000, response.getSw()); - - for (int i = 1; i < secretCount; i++) { - response = cmdSet.duplicateKeyAddEntropy(secrets[i]); - assertEquals(0x9000, response.getSw()); - } - - cmdSet.autoOpenSecureChannel(); - response = cmdSet.verifyPIN("000000"); - assertEquals(0x9000, response.getSw()); - - response = cmdSet.duplicateKeyImport(backup); - assertEquals(0x9000, response.getSw()); - assertArrayEquals(keyUID, response.getData()); - } - @Test @DisplayName("Test the Cash applet") @Tag("manual")