mirror of
https://github.com/status-im/status-keycard.git
synced 2025-01-12 14:54:54 +00:00
remove DUPLICATE KEY
This commit is contained in:
parent
b9fa61282e
commit
1b716790f0
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
Loading…
x
Reference in New Issue
Block a user