Merge pull request #26 from status-im/backup-restore

implement backup/restore
This commit is contained in:
Bitgamma 2018-11-22 18:04:26 +03:00 committed by GitHub
commit 2ff8de6e80
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 298 additions and 31 deletions

View File

@ -325,6 +325,48 @@ command until a new LOAD KEY command is performed.
Generates and stores keys completely on card. The state of the card after execution is the same as if a LOAD KEY command
had been performed.
### DUPLICATE KEY
* CLA = 0x80
* INS = 0xD5
* P1 = subcommand
* P2 = depends on subcommand
* Data = depends on phase
* Response SW = 0x9000 on success.
* Response Data = depends on subcommand
* Preconditions: depends on subcommand
P1:
* 0x00: START DUPLICATE
* 0x01: ADD ENTROPY
* 0x02: EXPORT DUPLICATE
* 0x03: IMPORT DUPLICATE
#### START DUPLICATE
This is the first step to start duplication. Requires an open secure channel and user PIN must be verified. Aborts any
on-going duplication session. P2 is the number of entropy pieces to expect in total (including this command). The data
contain the first piece of entropy. Returns no data. Must be performed with exactly the same parameters and data on all
cards taking part in the duplication.
#### ADD ENTROPY
This command uses the same one-shot secure channel scheme as defined in the INIT command. P2 is 00. Requires an ongoing
duplicate session started with the START DUPLICATE subcommand. Must be performed once per device taking part in the
duplication process, for a total number of devices equaling the P2 parameter of the START DUPLICATE subcommand (counting
the device which sent the START DUPLICATE command as the first device). The data is a random 256-bit number. The same
data must be sent to all the cards taking part in the duplication process.
#### EXPORT DUPLICATE
This command must be sent to the card which you wish to duplicate. Requires an open secure channel and authenticated
PIN. Works only if a duplication session is active and ADD ENTROPY has been performed the required number of times.
Returns the encrypted duplicate of the master key and terminates the duplication session for this card. The format is
exactly the same as the one defined in the LOAD KEY (TLV) command with omitted public key. It is however prepended by a
16-bytes IV and the entire TLV structure is encrypted.
#### IMPORT DUPLICATE
This command must be sent to all the cards which are a target for duplication. The Data field must contain the output
from the EXPORT DUPLICATE command performed on the source card. Returns the key UID. It follows exactly the same rules
as the EXPORT DUPLICATE subcommand.
### SIGN
* CLA = 0x80

View File

@ -42,7 +42,7 @@ dependencies {
testCompile('org.web3j:core:2.3.1')
testCompile('org.bitcoinj:bitcoinj-core:0.14.5')
testCompile("org.bouncycastle:bcprov-jdk15on:1.58")
testCompile("com.github.status-im:hardwallet-lite-sdk:7e3787f")
testCompile("com.github.status-im:hardwallet-lite-sdk:f64cefd")
testCompile("org.junit.jupiter:junit-jupiter-api:5.1.1")
testRuntime("org.junit.jupiter:junit-jupiter-engine:5.1.1")
}

View File

@ -3,12 +3,15 @@ package im.status.wallet;
import javacard.framework.JCSystem;
import javacard.framework.Util;
import javacard.security.*;
import javacardx.crypto.Cipher;
/**
* Crypto utilities, mostly BIP32 related. The init method must be called during application installation. This class
* is not meant to be instantiated.
*/
public class Crypto {
final static public short AES_BLOCK_SIZE = 16;
final static private short KEY_SECRET_SIZE = 32;
final static private short KEY_DERIVATION_INPUT_SIZE = 37;
final static private short HMAC_OUT_SIZE = 64;
@ -23,15 +26,18 @@ public class Crypto {
final static private byte[] KEY_BITCOIN_SEED = {'B', 'i', 't', 'c', 'o', 'i', 'n', ' ', 's', 'e', 'e', 'd'};
// The below 4 objects can be accessed anywhere from the entire applet
// The below 5 objects can be accessed anywhere from the entire applet
RandomData random;
KeyAgreement ecdh;
MessageDigest sha256;
MessageDigest sha512;
Cipher aesCbcIso9797m2;
private Signature hmacSHA512;
private HMACKey hmacKey;
private AESKey tmpAES256;
private byte[] tmp;
Crypto() {
@ -39,6 +45,9 @@ public class Crypto {
sha256 = MessageDigest.getInstance(MessageDigest.ALG_SHA_256, false);
ecdh = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false);
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);
short blockSize;
@ -54,6 +63,12 @@ public class Crypto {
tmp = JCSystem.makeTransientByteArray((short) (HMAC_BLOCK_OFFSET + blockSize), JCSystem.CLEAR_ON_RESET);
}
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), sLen, dst, dOff);
}
/**
* Derives a private key according to the algorithm defined in BIP32. The BIP32 specifications define some checks
* to be performed on the derived keys. In the very unlikely event that these checks fail this key is not considered

View File

@ -11,7 +11,7 @@ public class SecureChannel {
public static final short SC_KEY_LENGTH = 256;
public static final short SC_SECRET_LENGTH = 32;
public static final short PAIRING_KEY_LENGTH = SC_SECRET_LENGTH + 1;
public static final short SC_BLOCK_SIZE = 16;
public static final short SC_BLOCK_SIZE = Crypto.AES_BLOCK_SIZE;
public static final short SC_OUT_OFFSET = ISO7816.OFFSET_CDATA + (SC_BLOCK_SIZE * 2);
public static final short SC_COUNTER_MAX = 100;
@ -28,7 +28,6 @@ public class SecureChannel {
private AESKey scEncKey;
private AESKey scMacKey;
private Cipher scCipher;
private Signature scMac;
private KeyPair scKeypair;
private byte[] secret;
@ -55,8 +54,6 @@ public class SecureChannel {
public SecureChannel(byte pairingLimit, Crypto crypto, SECP256k1 secp256k1) {
this.crypto = crypto;
scCipher = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false);
scMac = Signature.getInstance(Signature.ALG_AES_MAC_128_NOPAD, false);
scEncKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES_TRANSIENT_DESELECT, KeyBuilder.LENGTH_AES_256, false);
@ -88,12 +85,10 @@ public class SecureChannel {
}
/**
* Decrypts the content of the APDU by generating an AES key using EC-DH. Only usable in pre-initialization state.
* Decrypts the content of the APDU by generating an AES key using EC-DH. Usable only with specific commands.
* @param apduBuffer the APDU buffer
*/
public void oneShotDecrypt(byte[] apduBuffer) {
if (pairingSecret != null) return;
crypto.ecdh.init(scKeypair.getPrivate());
short off = (short)(ISO7816.OFFSET_CDATA + 1);
@ -106,10 +101,10 @@ public class SecureChannel {
}
scEncKey.setKey(secret, (short) 0);
scCipher.init(scEncKey, Cipher.MODE_DECRYPT, apduBuffer, off, SC_BLOCK_SIZE);
crypto.aesCbcIso9797m2.init(scEncKey, Cipher.MODE_DECRYPT, apduBuffer, off, SC_BLOCK_SIZE);
off = (short)(off + SC_BLOCK_SIZE);
apduBuffer[ISO7816.OFFSET_LC] = (byte) scCipher.doFinal(apduBuffer, off, (short)((short)(apduBuffer[ISO7816.OFFSET_LC] & 0xff) - off + ISO7816.OFFSET_CDATA), apduBuffer, ISO7816.OFFSET_CDATA);
apduBuffer[ISO7816.OFFSET_LC] = (byte) crypto.aesCbcIso9797m2.doFinal(apduBuffer, off, (short)((short)(apduBuffer[ISO7816.OFFSET_LC] & 0xff) - off + ISO7816.OFFSET_CDATA), apduBuffer, ISO7816.OFFSET_CDATA);
}
/**
@ -304,9 +299,9 @@ public class SecureChannel {
ISOException.throwIt(ISO7816.SW_SECURITY_STATUS_NOT_SATISFIED);
}
scCipher.init(scEncKey, Cipher.MODE_DECRYPT, secret, (short) 0, SC_BLOCK_SIZE);
crypto.aesCbcIso9797m2.init(scEncKey, Cipher.MODE_DECRYPT, secret, (short) 0, SC_BLOCK_SIZE);
Util.arrayCopyNonAtomic(apduBuffer, ISO7816.OFFSET_CDATA, secret, (short) 0, SC_BLOCK_SIZE);
short len = scCipher.doFinal(apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), (short) (apduLen - SC_BLOCK_SIZE), apduBuffer, ISO7816.OFFSET_CDATA);
short len = crypto.aesCbcIso9797m2.doFinal(apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE), (short) (apduLen - SC_BLOCK_SIZE), apduBuffer, ISO7816.OFFSET_CDATA);
apduBuffer[ISO7816.OFFSET_LC] = (byte) len;
@ -342,8 +337,8 @@ public class SecureChannel {
Util.setShort(apduBuffer, (short) (SC_OUT_OFFSET + len), sw);
len += 2;
scCipher.init(scEncKey, Cipher.MODE_ENCRYPT, secret, (short) 0, SC_BLOCK_SIZE);
len = scCipher.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE));
crypto.aesCbcIso9797m2.init(scEncKey, Cipher.MODE_ENCRYPT, secret, (short) 0, SC_BLOCK_SIZE);
len = crypto.aesCbcIso9797m2.doFinal(apduBuffer, SC_OUT_OFFSET, len, apduBuffer, (short)(ISO7816.OFFSET_CDATA + SC_BLOCK_SIZE));
apduBuffer[0] = (byte) (len + SC_BLOCK_SIZE);

View File

@ -2,6 +2,7 @@ package im.status.wallet;
import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.Cipher;
/**
* The applet's main class. All incoming commands a processed by this class.
@ -20,6 +21,7 @@ public class WalletApplet 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;
@ -56,6 +58,11 @@ public class WalletApplet 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 EXPORT_KEY_P1_ANY = 0x00;
static final byte EXPORT_KEY_P1_HIGH = 0x01;
@ -111,6 +118,9 @@ public class WalletApplet extends Applet {
private Crypto crypto;
private SECP256k1 secp256k1;
private byte[] duplicationEncKey;
private short expectedEntropy;
/**
* Invoked during applet installation. Creates an instance of this class. The installation parameters are passed in
* the given buffer.
@ -163,6 +173,9 @@ public class WalletApplet extends Applet {
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
secureChannel = new SecureChannel(PAIRING_MAX_CLIENT_COUNT, crypto, secp256k1);
duplicationEncKey = new byte[(short)(KeyBuilder.LENGTH_AES_256/8)];
expectedEntropy = -1;
register(bArray, (short) (bOffset + 1), bArray[bOffset]);
}
@ -233,6 +246,9 @@ public class WalletApplet extends Applet {
case INS_GENERATE_KEY:
generateKey(apdu);
break;
case INS_DUPLICATE_KEY:
duplicateKey(apdu);
break;
case INS_SIGN:
sign(apdu);
break;
@ -578,13 +594,10 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
boolean newExtended = false;
switch (apduBuffer[ISO7816.OFFSET_P1]) {
case LOAD_KEY_P1_EXT_EC:
newExtended = true;
case LOAD_KEY_P1_EC:
loadKeyPair(apduBuffer, newExtended);
case LOAD_KEY_P1_EXT_EC:
loadKeyPair(apduBuffer);
break;
case LOAD_KEY_P1_SEED:
loadSeed(apduBuffer);
@ -620,14 +633,12 @@ public class WalletApplet extends Applet {
}
/**
* Called internally by the loadKey method to load a key in the TLV format. The presence of the public key is optional
* if public key derivation is supported on card, otherwise it is mandatory. The presence of a chain code is indicated
* explicitly through the newExtended argument (which is set depending on the P1 parameter of the command).
* Called internally by the loadKey method to load a key in the TLV format. The presence of the public key is optional.
* The presence of the chain code determines whether the key is extended or not.
*
* @param apduBuffer the APDU buffer
* @param newExtended whether the key to load contains a chain code or not
*/
private void loadKeyPair(byte[] apduBuffer, boolean newExtended) {
private void loadKeyPair(byte[] apduBuffer) {
short pubOffset = (short)(ISO7816.OFFSET_CDATA + (apduBuffer[(short) (ISO7816.OFFSET_CDATA + 1)] == (byte) 0x81 ? 3 : 2));
short privOffset = (short)(pubOffset + apduBuffer[(short)(pubOffset + 1)] + 2);
short chainOffset = (short)(privOffset + apduBuffer[(short)(privOffset + 1)] + 2);
@ -638,14 +649,14 @@ public class WalletApplet extends Applet {
pubOffset = -1;
}
if (!((apduBuffer[ISO7816.OFFSET_CDATA] == TLV_KEY_TEMPLATE) && (apduBuffer[privOffset] == TLV_PRIV_KEY) && (!newExtended || apduBuffer[chainOffset] == TLV_CHAIN_CODE))) {
if (!((apduBuffer[ISO7816.OFFSET_CDATA] == TLV_KEY_TEMPLATE) && (apduBuffer[privOffset] == TLV_PRIV_KEY))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
JCSystem.beginTransaction();
try {
isExtended = newExtended;
isExtended = (apduBuffer[chainOffset] == TLV_CHAIN_CODE);
masterPrivate.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
privateKey.setS(apduBuffer, (short) (privOffset + 2), apduBuffer[(short) (privOffset + 1)]);
@ -952,12 +963,117 @@ public class WalletApplet 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);
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 off = (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE);
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, (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE), off, apduBuffer, (short) (SecureChannel.SC_OUT_OFFSET + Crypto.AES_BLOCK_SIZE), 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 data using SHA-256 with possible segmentation over
* multiple APDUs as well as signing a precomputed 32-bytes hash. The latter option is the actual use case at the
* moment, since Ethereum signatures actually require Keccak-256 hashes, which are not supported by any version of
* JavaCard (including 3.0.5 which supports SHA-3 but not Keccak-256 which is slightly different). The signature is
* path to be the current key path. This command supports signing a precomputed 32-bytes hash. The signature is
* generated using the current keys, so if no keys are loaded the command does not work. The result of the execution
* is not the plain signature, but a TLV object containing the public key which must be used to verify the signature
* and the signature itself. The client should use this to calculate 'v' and format the signature according to the

View File

@ -1017,6 +1017,105 @@ public class WalletAppletTest {
assertEquals(0x6985, response.getSW());
}
@Test
@DisplayName("DUPLICATE KEY command")
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
ResponseAPDU 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("Sign actual Ethereum transaction")
@Tag("manual")