mirror of
https://github.com/status-im/status-keycard.git
synced 2025-03-03 14:20:33 +00:00
implement PIN-less path
This commit is contained in:
parent
5fc82298b8
commit
d778080899
@ -20,7 +20,7 @@ and passed as an installation parameter to the applet according to the JavaCard
|
|||||||
to unblock the applet using the PUK, the PUK is blocked, meaning the the wallet is lost.
|
to unblock the applet using the PUK, the PUK is blocked, meaning the the wallet is lost.
|
||||||
|
|
||||||
After authentication, the user remains authenticated until the application is either deselected or the card is reset.
|
After authentication, the user remains authenticated until the application is either deselected or the card is reset.
|
||||||
Authentication with PIN is a requirement for all further commands to succeed.
|
Authentication with PIN is a requirement for most commands to succeed.
|
||||||
|
|
||||||
The PIN can be changed by the user after authentication.
|
The PIN can be changed by the user after authentication.
|
||||||
|
|
||||||
@ -32,7 +32,9 @@ specifications. This keyset is used to sign transactions. When the applet is fir
|
|||||||
signing will fail. It is necessary to first load the keyset in order for the application to be fully operational.
|
signing will fail. It is necessary to first load the keyset in order for the application to be fully operational.
|
||||||
|
|
||||||
Signing of transactions is done by uploading the data in blocks no larger than 255 bytes (including the overhead caused
|
Signing of transactions is done by uploading the data in blocks no larger than 255 bytes (including the overhead caused
|
||||||
by the Secure Channel). Segmentation must be handled at the application protocol.
|
by the Secure Channel). Segmentation must be handled at the application protocol. Another option is to sign the hash
|
||||||
|
of the transaction, with the hash being calculated off-card. Signing generally requires the PIN to be authenticated,
|
||||||
|
however the user can set a special key path which requires no authentication.
|
||||||
|
|
||||||
## APDUs
|
## APDUs
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ be used by the client to establish the Secure Channel.
|
|||||||
The OPEN SECURE CHANNEL command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD).
|
The OPEN SECURE CHANNEL command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD).
|
||||||
|
|
||||||
### GET STATUS
|
### GET STATUS
|
||||||
|
|
||||||
* CLA = 0x80
|
* CLA = 0x80
|
||||||
* INS = 0xF2
|
* INS = 0xF2
|
||||||
* P1 = 0x00 for application status, 0x01 for key path status
|
* P1 = 0x00 for application status, 0x01 for key path status
|
||||||
@ -165,7 +168,7 @@ signing sessions, if any. Unless a DERIVE KEY is sent, a subsequent SIGN command
|
|||||||
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A81 if public key derivation is not supported and
|
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A81 if public key derivation is not supported and
|
||||||
bit 0 of P1 is set, 0x6A86 if P2 = 0x01 and bit 0 of P1 is not set.
|
bit 0 of P1 is set, 0x6A86 if P2 = 0x01 and bit 0 of P1 is not set.
|
||||||
* Response Data = On assisted derivation and P2 = 0x01 the key derivation template. Empty otherwise.
|
* Response Data = On assisted derivation and P2 = 0x01 the key derivation template. Empty otherwise.
|
||||||
* Preconditions: Secure Channel must be opened, user PIN must be verified, an extended keyset must be loaded
|
* Preconditions: Secure Channel must be opened, user PIN must be verified (if no PIN-less key is defined), an extended keyset must be loaded
|
||||||
|
|
||||||
This command is used before a signing session to generate a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
This command is used before a signing session to generate a private key according to the [BIP32](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki)
|
||||||
specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent
|
specifications. This command always aborts open signing sessions, if any. The generated key is used for all subsequent
|
||||||
@ -181,7 +184,7 @@ P1:
|
|||||||
* bit 7 = if 0 derive from master keys, if 1 derive from current keys
|
* bit 7 = if 0 derive from master keys, if 1 derive from current keys
|
||||||
|
|
||||||
P2:
|
P2:
|
||||||
* 0x00 = data is 32 a sequence of 32-bit integers
|
* 0x00 = data is a sequence of 32-bit integers
|
||||||
* 0x01 = data is a public key
|
* 0x01 = data is a public key
|
||||||
|
|
||||||
Response Data format:
|
Response Data format:
|
||||||
@ -216,14 +219,13 @@ human-readable mnemonic. Each integer can have a value from 0 to 2047.
|
|||||||
* Data = the data to sign
|
* Data = the data to sign
|
||||||
* Response = if P2 indicates last segment, the public key and the signature are returned
|
* Response = if P2 indicates last segment, the public key and the signature are returned
|
||||||
* Response SW = 0x9000 on success, 0x6A86 if P2 is invalid
|
* Response SW = 0x9000 on success, 0x6A86 if P2 is invalid
|
||||||
* Preconditions: Secure Channel must be opened, user PIN must be verified, a valid keypair must be loaded
|
* Preconditions: Secure Channel must be opened, user PIN must be verified (or a PIN-less key must be active), a valid keypair must be loaded
|
||||||
|
|
||||||
P1:
|
P1:
|
||||||
* 0x00 = transaction data
|
* 0x00 = transaction data
|
||||||
* 0x01 = precomputed hash
|
* 0x01 = precomputed hash
|
||||||
|
|
||||||
P2:
|
P2:
|
||||||
|
|
||||||
* bit 0 = if 1 first block, if 0 other block
|
* bit 0 = if 1 first block, if 0 other block
|
||||||
* bit 1-6 = reserved
|
* bit 1-6 = reserved
|
||||||
* bit 7 = if 0 more blocks, if 1 last block
|
* bit 7 = if 0 more blocks, if 1 last block
|
||||||
@ -255,3 +257,16 @@ This segmentation scheme allows resuming signature sessions if other commands mu
|
|||||||
the same time avoid generating signatures over partial data, since both the first and the last block are marked.
|
the same time avoid generating signatures over partial data, since both the first and the last block are marked.
|
||||||
|
|
||||||
On applet selection any pending signing session is aborted.
|
On applet selection any pending signing session is aborted.
|
||||||
|
|
||||||
|
### SET PINLESS PATH
|
||||||
|
|
||||||
|
* CLA = 0x80
|
||||||
|
* INS = 0xC1
|
||||||
|
* P1 = 0x00
|
||||||
|
* P2 = 0x00
|
||||||
|
* Data = a sequence of 32-bit integers
|
||||||
|
* Response SW = 0x9000 on success, 0x6A80 if data is invalid
|
||||||
|
* Preconditions: Secure Channel must be opened, user PIN must be verified
|
||||||
|
|
||||||
|
Sets the given sequence of 32-bit integers as a PIN-less path. When the current derived key matches this path, SIGN
|
||||||
|
will work even if no PIN authentication has been performed. An empty sequence means that no PIN-less path is defined.
|
@ -12,11 +12,13 @@ public class WalletApplet extends Applet {
|
|||||||
static final byte INS_DERIVE_KEY = (byte) 0xD1;
|
static final byte INS_DERIVE_KEY = (byte) 0xD1;
|
||||||
static final byte INS_GENERATE_MNEMONIC = (byte) 0xD2;
|
static final byte INS_GENERATE_MNEMONIC = (byte) 0xD2;
|
||||||
static final byte INS_SIGN = (byte) 0xC0;
|
static final byte INS_SIGN = (byte) 0xC0;
|
||||||
|
static final byte INS_SET_PINLESS_PATH = (byte) 0xC1;
|
||||||
|
|
||||||
static final byte PUK_LENGTH = 12;
|
static final byte PUK_LENGTH = 12;
|
||||||
static final byte PUK_MAX_RETRIES = 5;
|
static final byte PUK_MAX_RETRIES = 5;
|
||||||
static final byte PIN_LENGTH = 6;
|
static final byte PIN_LENGTH = 6;
|
||||||
static final byte PIN_MAX_RETRIES = 3;
|
static final byte PIN_MAX_RETRIES = 3;
|
||||||
|
static final short KEY_PATH_MAX_DEPTH = 10;
|
||||||
|
|
||||||
static final short EC_KEY_SIZE = 256;
|
static final short EC_KEY_SIZE = 256;
|
||||||
static final short CHAIN_CODE_SIZE = 32;
|
static final short CHAIN_CODE_SIZE = 32;
|
||||||
@ -62,7 +64,6 @@ public class WalletApplet extends Applet {
|
|||||||
static final byte TLV_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
|
static final byte TLV_PUBLIC_KEY_DERIVATION = (byte) 0xC3;
|
||||||
|
|
||||||
private static final byte[] ASSISTED_DERIVATION_HASH = { (byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
|
private static final byte[] ASSISTED_DERIVATION_HASH = { (byte) 0xAA, (byte) 0x2D, (byte) 0xA9, (byte) 0x9D, (byte) 0x91, (byte) 0x8C, (byte) 0x7D, (byte) 0x95, (byte) 0xB8, (byte) 0x96, (byte) 0x89, (byte) 0x87, (byte) 0x3E, (byte) 0xAA, (byte) 0x37, (byte) 0x67, (byte) 0x25, (byte) 0x0C, (byte) 0xFF, (byte) 0x50, (byte) 0x13, (byte) 0x9A, (byte) 0x2F, (byte) 0x87, (byte) 0xBB, (byte) 0x4F, (byte) 0xCA, (byte) 0xB4, (byte) 0xAE, (byte) 0xC3, (byte) 0xE8, (byte) 0x90};
|
||||||
private static final short KEY_PATH_MAX_DEPTH = 10;
|
|
||||||
|
|
||||||
private OwnerPIN pin;
|
private OwnerPIN pin;
|
||||||
private OwnerPIN puk;
|
private OwnerPIN puk;
|
||||||
@ -80,6 +81,9 @@ public class WalletApplet extends Applet {
|
|||||||
private byte[] keyPath;
|
private byte[] keyPath;
|
||||||
private short keyPathLen;
|
private short keyPathLen;
|
||||||
|
|
||||||
|
private byte[] pinlessPath;
|
||||||
|
private short pinlessPathLen;
|
||||||
|
|
||||||
private Signature signature;
|
private Signature signature;
|
||||||
private boolean signInProgress;
|
private boolean signInProgress;
|
||||||
private boolean expectPublicKey;
|
private boolean expectPublicKey;
|
||||||
@ -109,6 +113,7 @@ public class WalletApplet extends Applet {
|
|||||||
masterChainCode = new byte[32];
|
masterChainCode = new byte[32];
|
||||||
chainCode = new byte[32];
|
chainCode = new byte[32];
|
||||||
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
||||||
|
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
||||||
|
|
||||||
publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, EC_KEY_SIZE, false);
|
publicKey = (ECPublicKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PUBLIC, EC_KEY_SIZE, false);
|
||||||
privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, EC_KEY_SIZE, false);
|
privateKey = (ECPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, EC_KEY_SIZE, false);
|
||||||
@ -160,6 +165,9 @@ public class WalletApplet extends Applet {
|
|||||||
case INS_SIGN:
|
case INS_SIGN:
|
||||||
sign(apdu);
|
sign(apdu);
|
||||||
break;
|
break;
|
||||||
|
case INS_SET_PINLESS_PATH:
|
||||||
|
setPinlessPath(apdu);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
|
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
|
||||||
break;
|
break;
|
||||||
@ -391,7 +399,7 @@ public class WalletApplet extends Applet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void deriveKey(APDU apdu) {
|
private void deriveKey(APDU apdu) {
|
||||||
if (!(secureChannel.isOpen() && pin.isValidated() && isExtended)) {
|
if (!(secureChannel.isOpen() && (pin.isValidated() || (pinlessPathLen > 0)) && isExtended)) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -539,7 +547,7 @@ public class WalletApplet extends Applet {
|
|||||||
private void sign(APDU apdu) {
|
private void sign(APDU apdu) {
|
||||||
apdu.setIncomingAndReceive();
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
if (!(secureChannel.isOpen() && pin.isValidated() && privateKey.isInitialized() && !expectPublicKey)) {
|
if (!(secureChannel.isOpen() && (pin.isValidated() || isPinless()) && privateKey.isInitialized() && !expectPublicKey)) {
|
||||||
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -581,6 +589,26 @@ public class WalletApplet extends Applet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setPinlessPath(APDU apdu) {
|
||||||
|
apdu.setIncomingAndReceive();
|
||||||
|
|
||||||
|
if (!(secureChannel.isOpen() && pin.isValidated())) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] apduBuffer = apdu.getBuffer();
|
||||||
|
short len = secureChannel.decryptAPDU(apduBuffer);
|
||||||
|
|
||||||
|
if (((short) (len % 4) != 0) || (len > pinlessPath.length)) {
|
||||||
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
|
}
|
||||||
|
|
||||||
|
JCSystem.beginTransaction();
|
||||||
|
pinlessPathLen = len;
|
||||||
|
Util.arrayCopy(apduBuffer, ISO7816.OFFSET_CDATA, pinlessPath, (short) 0, len);
|
||||||
|
JCSystem.commitTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
private boolean allDigits(byte[] buffer, short off, short len) {
|
private boolean allDigits(byte[] buffer, short off, short len) {
|
||||||
while(len > 0) {
|
while(len > 0) {
|
||||||
len--;
|
len--;
|
||||||
@ -594,4 +622,8 @@ public class WalletApplet extends Applet {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isPinless() {
|
||||||
|
return (pinlessPathLen > 0) && (pinlessPathLen == keyPathLen) && (Util.arrayCompare(keyPath, (short) 0, pinlessPath, (short) 0, keyPathLen) == 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,4 +195,9 @@ public class WalletAppletCommandSet {
|
|||||||
CommandAPDU deriveKey = new CommandAPDU(0x80, WalletApplet.INS_DERIVE_KEY, p1, p2, secureChannel.encryptAPDU(data));
|
CommandAPDU deriveKey = new CommandAPDU(0x80, WalletApplet.INS_DERIVE_KEY, p1, p2, secureChannel.encryptAPDU(data));
|
||||||
return apduChannel.transmit(deriveKey);
|
return apduChannel.transmit(deriveKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ResponseAPDU setPinlessPath(byte [] data) throws CardException {
|
||||||
|
CommandAPDU setPinlessPath = new CommandAPDU(0x80, WalletApplet.INS_SET_PINLESS_PATH, 0x00, 0x00, secureChannel.encryptAPDU(data));
|
||||||
|
return apduChannel.transmit(setPinlessPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -538,6 +538,95 @@ public class WalletAppletTest {
|
|||||||
assertTrue(signature.verify(sig));
|
assertTrue(signature.verify(sig));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("SET PINLESS PATH command")
|
||||||
|
void setPinlessPathTest() throws Exception {
|
||||||
|
byte[] data = "some data to be hashed".getBytes();
|
||||||
|
byte[] hash = sha256(data);
|
||||||
|
|
||||||
|
KeyPairGenerator g = keypairGenerator();
|
||||||
|
KeyPair keyPair = g.generateKeyPair();
|
||||||
|
byte[] chainCode = new byte[32];
|
||||||
|
new Random().nextBytes(chainCode);
|
||||||
|
|
||||||
|
// Security condition violation: SecureChannel not open
|
||||||
|
ResponseAPDU response = cmdSet.setPinlessPath(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02});
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
|
||||||
|
cmdSet.openSecureChannel();
|
||||||
|
|
||||||
|
// Security condition violation: PIN not verified
|
||||||
|
response = cmdSet.setPinlessPath(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02});
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.loadKey(keyPair, false, chainCode);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
// Wrong data
|
||||||
|
response = cmdSet.setPinlessPath(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00});
|
||||||
|
assertEquals(0x6a80, response.getSW());
|
||||||
|
response = cmdSet.setPinlessPath(new byte[(WalletApplet.KEY_PATH_MAX_DEPTH + 1)* 4]);
|
||||||
|
assertEquals(0x6a80, response.getSW());
|
||||||
|
|
||||||
|
// Correct
|
||||||
|
response = cmdSet.setPinlessPath(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02});
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
// Verify that only PINless path can be used without PIN
|
||||||
|
resetAndSelectAndOpenSC();
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, false, true, false);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
// Verify changing path
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.setPinlessPath(new byte[] {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01});
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
resetAndSelectAndOpenSC();
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x01}, false, true, false);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(derivePublicKey(secureChannel.decryptAPDU(response.getData())), false, true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
response = cmdSet.verifyPIN("000000");
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
response = cmdSet.setPinlessPath(new byte[] {});
|
||||||
|
assertEquals(0x9000, response.getSW());
|
||||||
|
resetAndSelectAndOpenSC();
|
||||||
|
response = cmdSet.sign(hash, WalletApplet.SIGN_P1_PRECOMPUTED_HASH,true, true);
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
response = cmdSet.deriveKey(new byte[] {0x00, 0x00, 0x00, 0x02}, true, true, false);
|
||||||
|
assertEquals(0x6985, response.getSW());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@DisplayName("SIGN data (unused for the current scenario)")
|
@DisplayName("SIGN data (unused for the current scenario)")
|
||||||
@Tag("manual")
|
@Tag("manual")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user