add key uid

This commit is contained in:
Michele Balistreri 2018-10-01 15:41:19 +02:00
parent 6a0ca5e260
commit 582002b29e
4 changed files with 57 additions and 3 deletions

View File

@ -56,6 +56,7 @@ Response Data format:
- Tag 0x80 = ECC public Key
- Tag 0x02 = Application Version (2 bytes)
- Tag 0x02 = Number of remaining pairing slots (1 byte)
- Tag 0x8E = Key UID (0 or 32 bytes)
The SELECT command is documented in the ISO 7816-4 specifications and is used to select the application on the card,
making it the active one. The data field is the AID of the application. The response is the Application Info template
@ -64,6 +65,8 @@ which must be used by the client to establish the Secure Channel. Additionally i
application, formatted on two bytes. The first byte is the major version and the second is the minor version
(e.g: version 1.1 is formatted as 0x0101). The number of remaining pairing slots is also included in the response.
The Key UID can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key.
### OPEN SECURE CHANNEL
The OPEN SECURE CHANNEL command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD).
@ -166,6 +169,7 @@ always returns 0x63C0, even if the PUK is inserted correctly. In this case the w
* Data = the key data
* Response SW = 0x9000 on success, 0x6A80 if the format is invalid, 0x6A86 if P1 is invalid, 0x6A81 if public key
derivation is not supported and the public key is omitted
* Response Data = the key UID, defined as the SHA-256 of the public key
* Preconditions: Secure Channel must be opened, user PIN must be verified
P1:

View File

@ -23,7 +23,7 @@ javacard {
aid = '0x53:0x74:0x61:0x74:0x75:0x73:0x57:0x61:0x6c:0x6c:0x65:0x74:0x41:0x70:0x70'
className = 'WalletApplet'
}
version = '1.1'
version = '1.2'
}
}

View File

@ -7,7 +7,7 @@ import javacard.security.*;
* The applet's main class. All incoming commands a processed by this class.
*/
public class WalletApplet extends Applet {
static final short APPLICATION_VERSION = (short) 0x0101;
static final short APPLICATION_VERSION = (short) 0x0102;
static final byte INS_GET_STATUS = (byte) 0xF2;
static final byte INS_VERIFY_PIN = (byte) 0x20;
@ -31,6 +31,7 @@ public class WalletApplet extends Applet {
static final short EC_KEY_SIZE = 256;
static final short CHAIN_CODE_SIZE = 32;
static final short KEY_UID_LENGTH = 32;
static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2;
static final byte GET_STATUS_P1_APPLICATION = 0x00;
@ -81,6 +82,7 @@ public class WalletApplet extends Applet {
static final byte TLV_APPLICATION_INFO_TEMPLATE = (byte) 0xA4;
static final byte TLV_UID = (byte) 0x8F;
static final byte TLV_KEY_UID = (byte) 0x8E;
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 EXPORT_KEY_HIGH_MASK = (byte) 0xc0;
@ -114,6 +116,8 @@ public class WalletApplet extends Applet {
private boolean signInProgress;
private boolean expectPublicKey;
private byte[] keyUID;
private Crypto crypto;
private SECP256k1 secp256k1;
@ -161,6 +165,8 @@ public class WalletApplet extends Applet {
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
keyUID = new byte[KEY_UID_LENGTH];
resetCurveParameters();
signature = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false);
@ -309,7 +315,17 @@ public class WalletApplet extends Applet {
apduBuffer[(short)(UID_LENGTH + keyLength + 10)] = TLV_INT;
apduBuffer[(short)(UID_LENGTH + keyLength + 11)] = 1;
apduBuffer[(short)(UID_LENGTH + keyLength + 12)] = secureChannel.getRemainingPairingSlots();
apduBuffer[1] = (byte)(keyLength + UID_LENGTH + 11);
apduBuffer[(short)(UID_LENGTH + keyLength + 13)] = TLV_KEY_UID;
if (privateKey.isInitialized()) {
apduBuffer[(short)(UID_LENGTH + keyLength + 14)] = KEY_UID_LENGTH;
Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, (short)(UID_LENGTH + keyLength + 15), KEY_UID_LENGTH);
keyLength += KEY_UID_LENGTH;
} else {
apduBuffer[(short)(UID_LENGTH + keyLength + 14)] = 0;
}
apduBuffer[1] = (byte)(keyLength + UID_LENGTH + 13);
apdu.setOutgoingAndSend((short) 0, (short)(apduBuffer[1] + 2));
}
@ -485,6 +501,11 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
break;
}
short pubLen = masterPublic.getW(apduBuffer, (short) 0);
crypto.sha256.doFinal(apduBuffer, (short) 0, pubLen, keyUID, (short) 0);
Util.arrayCopyNonAtomic(keyUID, (short) 0, apduBuffer, SecureChannel.SC_OUT_OFFSET, KEY_UID_LENGTH);
secureChannel.respond(apdu, KEY_UID_LENGTH, ISO7816.SW_NO_ERROR);
}
/**

View File

@ -34,6 +34,7 @@ import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Security;
import java.security.Signature;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import java.util.Arrays;
import java.util.Random;
@ -117,6 +118,7 @@ public class WalletAppletTest {
assertEquals(WalletApplet.APPLICATION_VERSION >> 8, data[24 + data[21]]);
assertEquals(WalletApplet.APPLICATION_VERSION & 0xFF, data[25 + data[21]]);
assertEquals(WalletApplet.TLV_INT, data[26 + data[21]]);
assertEquals(WalletApplet.TLV_KEY_UID, data[29 + data[21]]);
}
@Test
@ -495,18 +497,22 @@ public class WalletAppletTest {
// Correct LOAD KEY
response = cmdSet.loadKey(keyPair);
assertEquals(0x9000, response.getSW());
verifyKeyUID(response.getData(), ((ECPublicKey) keyPair.getPublic()));
keyPair = g.generateKeyPair();
// Check extended key
response = cmdSet.loadKey(keyPair, false, chainCode);
assertEquals(0x9000, response.getSW());
verifyKeyUID(response.getData(), ((ECPublicKey) keyPair.getPublic()));
// Check omitted public key
response = cmdSet.loadKey(keyPair, true, null);
assertEquals(publicKeyDerivationSW, response.getSW());
verifyKeyUID(response.getData(), ((ECPublicKey) keyPair.getPublic()));
response = cmdSet.loadKey(keyPair, true, chainCode);
assertEquals(publicKeyDerivationSW, response.getSW());
verifyKeyUID(response.getData(), ((ECPublicKey) keyPair.getPublic()));
// Check seed load
response = cmdSet.loadKey(keyPair.getPrivate(), chainCode);
@ -571,6 +577,16 @@ public class WalletAppletTest {
response = cmdSet.loadKey(keyPair);
assertEquals(0x9000, response.getSW());
response = cmdSet.select();
assertEquals(0x9000, response.getSW());
byte[] data = response.getData();
assertEquals(32, data[30 + data[21]]);
verifyKeyUID(Arrays.copyOfRange(data, (31 + data[21]), (63 + data[21])), (ECPublicKey) keyPair.getPublic());
cmdSet.autoOpenSecureChannel();
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
assertTrue(cmdSet.getKeyInitializationStatus());
// Good case
@ -578,6 +594,11 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW());
assertFalse(cmdSet.getKeyInitializationStatus());
response = cmdSet.select();
assertEquals(0x9000, response.getSW());
data = response.getData();
assertEquals(0, data[30 + data[21]]);
}
@Test
@ -1524,4 +1545,12 @@ public class WalletAppletTest {
return new Sign.SignatureData(v, rB, sB);
}
private void verifyKeyUID(byte[] keyUID, ECPublicKey pubKey) {
verifyKeyUID(keyUID, pubKey.getQ().getEncoded(false));
}
private void verifyKeyUID(byte[] keyUID, byte[] pubKey) {
assertArrayEquals(sha256(pubKey), keyUID);
}
}