mirror of
https://github.com/status-im/status-keycard.git
synced 2025-01-20 18:49:14 +00:00
decouple pairing secret from PUK
This commit is contained in:
parent
eb233757fc
commit
527760d7df
@ -55,14 +55,14 @@ Response Data format:
|
|||||||
- Tag 0x8F = Instance UID (16 bytes)
|
- Tag 0x8F = Instance UID (16 bytes)
|
||||||
- Tag 0x80 = ECC public Key
|
- Tag 0x80 = ECC public Key
|
||||||
- Tag 0x02 = Application Version (2 bytes)
|
- 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,
|
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
|
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 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
|
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
|
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
|
### OPEN SECURE CHANNEL
|
||||||
|
|
||||||
@ -74,8 +74,14 @@ The MUTUALLY AUTHENTICATE command is as specified in the [SECURE_CHANNEL.MD](SEC
|
|||||||
|
|
||||||
### PAIR
|
### PAIR
|
||||||
|
|
||||||
The PAIR command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD). The shared secret is the SHA-256 of the
|
The PAIR command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD). The shared secret is 32 bytes long. The
|
||||||
PUK.
|
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
|
### UNPAIR
|
||||||
|
|
||||||
|
@ -61,7 +61,9 @@ task install(type: Exec) {
|
|||||||
select -AID ${project.properties['im.status.gradle.gpshell.isd']}
|
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']}
|
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
|
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
|
card_disconnect
|
||||||
release_context
|
release_context
|
||||||
"""
|
"""
|
||||||
|
@ -21,6 +21,8 @@ public class Crypto {
|
|||||||
final static private short HMAC_BLOCK_SIZE = (short) 128;
|
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 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
|
// The below 4 objects can be accessed anywhere from the entire applet
|
||||||
static RandomData random;
|
static RandomData random;
|
||||||
static KeyAgreement ecdh;
|
static KeyAgreement ecdh;
|
||||||
@ -102,6 +104,19 @@ public class Crypto {
|
|||||||
return true;
|
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
|
* 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).
|
* internal TLV length fields. Returns the number of bytes by which the overall signature length changed (0 or -1).
|
||||||
|
@ -41,6 +41,7 @@ public class SecureChannel {
|
|||||||
private byte[] pairingKeys;
|
private byte[] pairingKeys;
|
||||||
|
|
||||||
private short preassignedPairingOffset = -1;
|
private short preassignedPairingOffset = -1;
|
||||||
|
private byte remainingSlots;
|
||||||
private boolean mutuallyAuthenticated = false;
|
private boolean mutuallyAuthenticated = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,6 +69,7 @@ public class SecureChannel {
|
|||||||
secret = JCSystem.makeTransientByteArray((short)(SC_SECRET_LENGTH * 2), JCSystem.CLEAR_ON_DESELECT);
|
secret = JCSystem.makeTransientByteArray((short)(SC_SECRET_LENGTH * 2), JCSystem.CLEAR_ON_DESELECT);
|
||||||
pairingSecret = new byte[SC_SECRET_LENGTH];
|
pairingSecret = new byte[SC_SECRET_LENGTH];
|
||||||
pairingKeys = new byte[(short)(PAIRING_KEY_LENGTH * pairingLimit)];
|
pairingKeys = new byte[(short)(PAIRING_KEY_LENGTH * pairingLimit)];
|
||||||
|
remainingSlots = pairingLimit;
|
||||||
|
|
||||||
Util.arrayCopyNonAtomic(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
|
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.update(pairingSecret, (short) 0, SC_SECRET_LENGTH);
|
||||||
Crypto.sha256.doFinal(apduBuffer, (short) 1, SC_SECRET_LENGTH, pairingKeys, (short) (preassignedPairingOffset + 1));
|
Crypto.sha256.doFinal(apduBuffer, (short) 1, SC_SECRET_LENGTH, pairingKeys, (short) (preassignedPairingOffset + 1));
|
||||||
pairingKeys[preassignedPairingOffset] = 1;
|
pairingKeys[preassignedPairingOffset] = 1;
|
||||||
|
remainingSlots--;
|
||||||
apduBuffer[0] = (byte) (preassignedPairingOffset / PAIRING_KEY_LENGTH);
|
apduBuffer[0] = (byte) (preassignedPairingOffset / PAIRING_KEY_LENGTH);
|
||||||
|
|
||||||
preassignedPairingOffset = -1;
|
preassignedPairingOffset = -1;
|
||||||
@ -237,7 +240,10 @@ public class SecureChannel {
|
|||||||
*/
|
*/
|
||||||
public void unpair(byte[] apduBuffer) {
|
public void unpair(byte[] apduBuffer) {
|
||||||
short off = checkPairingIndexAndGetOffset(apduBuffer[ISO7816.OFFSET_P1]);
|
short off = checkPairingIndexAndGetOffset(apduBuffer[ISO7816.OFFSET_P1]);
|
||||||
|
if (pairingKeys[off] == 1) {
|
||||||
Util.arrayFillNonAtomic(pairingKeys, off, PAIRING_KEY_LENGTH, (byte) 0);
|
Util.arrayFillNonAtomic(pairingKeys, off, PAIRING_KEY_LENGTH, (byte) 0);
|
||||||
|
remainingSlots++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -361,6 +367,13 @@ public class SecureChannel {
|
|||||||
return scEncKey.isInitialized() && scMacKey.isInitialized() && mutuallyAuthenticated;
|
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.
|
* Resets the Secure Channel, invalidating the current session. If no session is opened, this does nothing.
|
||||||
*/
|
*/
|
||||||
|
@ -30,7 +30,7 @@ public class WalletApplet extends Applet {
|
|||||||
|
|
||||||
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;
|
||||||
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_APPLICATION = 0x00;
|
||||||
static final byte GET_STATUS_P1_KEY_PATH = 0x01;
|
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];
|
keyPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
||||||
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
pinlessPath = new byte[KEY_PATH_MAX_DEPTH * 4];
|
||||||
|
|
||||||
|
|
||||||
SECP256k1.setCurveParameters(masterPublic);
|
SECP256k1.setCurveParameters(masterPublic);
|
||||||
SECP256k1.setCurveParameters(masterPrivate);
|
SECP256k1.setCurveParameters(masterPrivate);
|
||||||
|
|
||||||
@ -173,9 +172,7 @@ public class WalletApplet extends Applet {
|
|||||||
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
short c9Off = (short)(bOffset + bArray[bOffset] + 1); // Skip AID
|
||||||
c9Off += (short)(bArray[c9Off] + 2); // Skip Privileges and parameter length
|
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, bArray, (short) (c9Off + PUK_LENGTH));
|
||||||
secureChannel = new SecureChannel(PAIRING_MAX_CLIENT_COUNT, chainCode, (short) 0);
|
|
||||||
Util.arrayFillNonAtomic(chainCode, (short) 0, CHAIN_CODE_SIZE, (byte) 0);
|
|
||||||
|
|
||||||
puk = new OwnerPIN(PUK_MAX_RETRIES, PUK_LENGTH);
|
puk = new OwnerPIN(PUK_MAX_RETRIES, PUK_LENGTH);
|
||||||
puk.update(bArray, c9Off, 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);
|
Util.setShort(apduBuffer, (short)(UID_LENGTH + keyLength + 8), APPLICATION_VERSION);
|
||||||
apduBuffer[(short)(UID_LENGTH + keyLength + 10)] = TLV_INT;
|
apduBuffer[(short)(UID_LENGTH + keyLength + 10)] = TLV_INT;
|
||||||
apduBuffer[(short)(UID_LENGTH + keyLength + 11)] = 1;
|
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);
|
apduBuffer[1] = (byte)(keyLength + UID_LENGTH + 11);
|
||||||
apdu.setOutgoingAndSend((short) 0, (short)(apduBuffer[1] + 2));
|
apdu.setOutgoingAndSend((short) 0, (short)(apduBuffer[1] + 2));
|
||||||
}
|
}
|
||||||
@ -574,10 +571,12 @@ public class WalletApplet extends Applet {
|
|||||||
private void loadSeed(byte[] apduBuffer) {
|
private void loadSeed(byte[] apduBuffer) {
|
||||||
SECP256k1.assertECPointMultiplicationSupport();
|
SECP256k1.assertECPointMultiplicationSupport();
|
||||||
|
|
||||||
if (apduBuffer[ISO7816.OFFSET_LC] != SEED_SIZE) {
|
if (apduBuffer[ISO7816.OFFSET_LC] != BIP39_SEED_SIZE) {
|
||||||
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Crypto.bip32MasterFromSeed(apduBuffer, (short) ISO7816.OFFSET_CDATA, BIP39_SEED_SIZE, apduBuffer, (short) ISO7816.OFFSET_CDATA);
|
||||||
|
|
||||||
JCSystem.beginTransaction();
|
JCSystem.beginTransaction();
|
||||||
isExtended = true;
|
isExtended = true;
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ import static org.junit.jupiter.api.Assertions.*;
|
|||||||
|
|
||||||
@DisplayName("Test the Wallet Applet")
|
@DisplayName("Test the Wallet Applet")
|
||||||
public class WalletAppletTest {
|
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 CardTerminal cardTerminal;
|
||||||
private static CardChannel apduChannel;
|
private static CardChannel apduChannel;
|
||||||
private static CardSimulator simulator;
|
private static CardSimulator simulator;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user