decouple pairing secret from PUK

This commit is contained in:
Michele Balistreri 2018-06-25 11:13:06 +02:00
parent eb233757fc
commit 527760d7df
6 changed files with 50 additions and 14 deletions

View File

@ -55,14 +55,14 @@ Response Data format:
- Tag 0x8F = Instance UID (16 bytes)
- Tag 0x80 = ECC public Key
- Tag 0x02 = Application Version (2 bytes)
- Tag 0x02 = PUK retry count (1 byte)
- Tag 0x02 = Number of remaining pairing slots (1 byte)
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
which contains the instance UID (which can be used by the client to keep track of multiple cards) and the public key
which must be used by the client to establish the Secure Channel. Additionally it contains the version number of the
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 PUK retry count is also included in the response.
(e.g: version 1.1 is formatted as 0x0101). The number of remaining pairing slots is also included in the response.
### OPEN SECURE CHANNEL
@ -74,8 +74,14 @@ The MUTUALLY AUTHENTICATE command is as specified in the [SECURE_CHANNEL.MD](SEC
### PAIR
The PAIR command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD). The shared secret is the SHA-256 of the
PUK.
The PAIR command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD). The shared secret is 32 bytes long. The
way the secret is generated is not within the scope of this document because the applet accepts the already generated
secret as an installation parameter. However the card issuer and clients must agree on the way it is generated for
interoperability reasons.
If the code is meant to be input manually, a random, variable length alphanumeric password to be used in conjuction with
an algorithm such as scrypt or PBKDF2 is suggested. If another input mechanism is foreseen (such as QR-code scanning),
then the sequence should be composed of random bytes from a secure source of randomness.
### UNPAIR

View File

@ -61,7 +61,9 @@ task install(type: Exec) {
select -AID ${project.properties['im.status.gradle.gpshell.isd']}
open_sc -security 1 -keyind 0 -keyver ${project.properties['im.status.gradle.gpshell.kvn']} -mac_key ${project.properties['im.status.gradle.gpshell.mac_key']} -enc_key ${project.properties['im.status.gradle.gpshell.enc_key']} -kek_key ${project.properties['im.status.gradle.gpshell.kek_key']}
send_apdu_nostop -sc 1 -APDU 80E400800E4F0C53746174757357616C6C6574
install -file build/javacard/im/status/wallet/javacard/wallet.cap -AID 53746174757357616C6C6574417070 -instAID 53746174757357616C6C6574417070 -instParam 313233343536373839303132
install_for_load -pkgAID 53746174757357616C6C6574
load -file build/javacard/im/status/wallet/javacard/wallet.cap
send_apdu -sc 1 -APDU 80E60C005F0C53746174757357616C6C65740F53746174757357616C6C65744170700F53746174757357616C6C657441707001002EC92C313233343536373839303132178381C5E8D324BED4033D14E1E1FDCAAADB74803869bee9f7a10b1b7108ed5300
card_disconnect
release_context
"""

View File

@ -21,6 +21,8 @@ public class Crypto {
final static private short HMAC_BLOCK_SIZE = (short) 128;
final static private short HMAC_BLOCK_OFFSET = (short) KEY_DERIVATION_INPUT_SIZE + HMAC_OUT_SIZE;
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
static RandomData random;
static KeyAgreement ecdh;
@ -102,6 +104,19 @@ public class Crypto {
return true;
}
/**
* Applies the algorithm for master key derivation defined by BIP32 to the binary seed provided as input.
*
* @param seed the binary seed
* @param seedOff the offset of the binary seed
* @param seedSize the size of the binary seed
* @param masterKey the output buffer
* @param keyOff the offset in the output buffer
*/
static void bip32MasterFromSeed(byte[] seed, short seedOff, short seedSize, byte[] masterKey, short keyOff) {
Crypto.hmacSHA512(KEY_BITCOIN_SEED, (short) 0, (short) KEY_BITCOIN_SEED.length, seed, seedOff, seedSize, masterKey, keyOff);
}
/**
* Fixes the S value of the signature as described in BIP-62 to avoid malleable signatures. It also fixes the all
* internal TLV length fields. Returns the number of bytes by which the overall signature length changed (0 or -1).

View File

@ -41,6 +41,7 @@ public class SecureChannel {
private byte[] pairingKeys;
private short preassignedPairingOffset = -1;
private byte remainingSlots;
private boolean mutuallyAuthenticated = false;
/**
@ -68,6 +69,7 @@ public class SecureChannel {
secret = JCSystem.makeTransientByteArray((short)(SC_SECRET_LENGTH * 2), JCSystem.CLEAR_ON_DESELECT);
pairingSecret = new byte[SC_SECRET_LENGTH];
pairingKeys = new byte[(short)(PAIRING_KEY_LENGTH * pairingLimit)];
remainingSlots = pairingLimit;
Util.arrayCopyNonAtomic(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
}
@ -222,6 +224,7 @@ public class SecureChannel {
Crypto.sha256.update(pairingSecret, (short) 0, SC_SECRET_LENGTH);
Crypto.sha256.doFinal(apduBuffer, (short) 1, SC_SECRET_LENGTH, pairingKeys, (short) (preassignedPairingOffset + 1));
pairingKeys[preassignedPairingOffset] = 1;
remainingSlots--;
apduBuffer[0] = (byte) (preassignedPairingOffset / PAIRING_KEY_LENGTH);
preassignedPairingOffset = -1;
@ -237,7 +240,10 @@ public class SecureChannel {
*/
public void unpair(byte[] apduBuffer) {
short off = checkPairingIndexAndGetOffset(apduBuffer[ISO7816.OFFSET_P1]);
Util.arrayFillNonAtomic(pairingKeys, off, PAIRING_KEY_LENGTH, (byte) 0);
if (pairingKeys[off] == 1) {
Util.arrayFillNonAtomic(pairingKeys, off, PAIRING_KEY_LENGTH, (byte) 0);
remainingSlots++;
}
}
/**
@ -361,6 +367,13 @@ public class SecureChannel {
return scEncKey.isInitialized() && scMacKey.isInitialized() && mutuallyAuthenticated;
}
/**
* Returns the number of still available pairing slots.
*/
public byte getRemainingPairingSlots() {
return remainingSlots;
}
/**
* Resets the Secure Channel, invalidating the current session. If no session is opened, this does nothing.
*/

View File

@ -30,7 +30,7 @@ public class WalletApplet extends Applet {
static final short EC_KEY_SIZE = 256;
static final short CHAIN_CODE_SIZE = 32;
static final short SEED_SIZE = CHAIN_CODE_SIZE * 2;
static final short BIP39_SEED_SIZE = CHAIN_CODE_SIZE * 2;
static final byte GET_STATUS_P1_APPLICATION = 0x00;
static final byte GET_STATUS_P1_KEY_PATH = 0x01;
@ -157,7 +157,6 @@ public class WalletApplet extends Applet {
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
SECP256k1.setCurveParameters(masterPublic);
SECP256k1.setCurveParameters(masterPrivate);
@ -173,9 +172,7 @@ public class WalletApplet extends Applet {
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
c9Off += (short)(bArray[c9Off] + 2); // Skip Privileges and parameter length
Crypto.sha256.doFinal(bArray, c9Off, PUK_LENGTH, chainCode, (short) 0);
secureChannel = new SecureChannel(PAIRING_MAX_CLIENT_COUNT, chainCode, (short) 0);
Util.arrayFillNonAtomic(chainCode, (short) 0, CHAIN_CODE_SIZE, (byte) 0);
secureChannel = new SecureChannel(PAIRING_MAX_CLIENT_COUNT, bArray, (short) (c9Off + PUK_LENGTH));
puk = new OwnerPIN(PUK_MAX_RETRIES, PUK_LENGTH);
puk.update(bArray, c9Off, PUK_LENGTH);
@ -312,7 +309,7 @@ public class WalletApplet extends Applet {
Util.setShort(apduBuffer, (short)(UID_LENGTH + keyLength + 8), APPLICATION_VERSION);
apduBuffer[(short)(UID_LENGTH + keyLength + 10)] = TLV_INT;
apduBuffer[(short)(UID_LENGTH + keyLength + 11)] = 1;
apduBuffer[(short)(UID_LENGTH + keyLength + 12)] = puk.getTriesRemaining();
apduBuffer[(short)(UID_LENGTH + keyLength + 12)] = secureChannel.getRemainingPairingSlots();
apduBuffer[1] = (byte)(keyLength + UID_LENGTH + 11);
apdu.setOutgoingAndSend((short) 0, (short)(apduBuffer[1] + 2));
}
@ -574,10 +571,12 @@ public class WalletApplet extends Applet {
private void loadSeed(byte[] apduBuffer) {
SECP256k1.assertECPointMultiplicationSupport();
if (apduBuffer[ISO7816.OFFSET_LC] != SEED_SIZE) {
if (apduBuffer[ISO7816.OFFSET_LC] != BIP39_SEED_SIZE) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
Crypto.bip32MasterFromSeed(apduBuffer, (short) ISO7816.OFFSET_CDATA, BIP39_SEED_SIZE, apduBuffer, (short) ISO7816.OFFSET_CDATA);
JCSystem.beginTransaction();
isExtended = true;

View File

@ -42,7 +42,8 @@ import static org.junit.jupiter.api.Assertions.*;
@DisplayName("Test the Wallet Applet")
public class WalletAppletTest {
public static final byte[] SHARED_SECRET = sha256("123456789012".getBytes());
// Generated on https://www.browserling.com/tools/scrypt using Password abc123, Salt WVpJqW4u, Output Size 32, N 16384, r 8, p 1
public static final byte[] SHARED_SECRET = new byte[] { (byte) 0x17, (byte) 0x83, (byte) 0x81, (byte) 0xc5, (byte) 0xe8, (byte) 0xd3, (byte) 0x24, (byte) 0xbe, (byte) 0xd4, (byte) 0x03, (byte) 0x3d, (byte) 0x14, (byte) 0xe1, (byte) 0xe1, (byte) 0xfd, (byte) 0xca, (byte) 0xaa, (byte) 0xdb, (byte) 0x74, (byte) 0x80, (byte) 0x38, (byte) 0x69, (byte) 0xbe, (byte) 0xe9, (byte) 0xf7, (byte) 0xa1, (byte) 0x0b, (byte) 0x1b, (byte) 0x71, (byte) 0x08, (byte) 0xed, (byte) 0x53 };
private static CardTerminal cardTerminal;
private static CardChannel apduChannel;
private static CardSimulator simulator;