Skip to content

Commit

Permalink
Merge pull request #5 from status-im/no-inst-params
Browse files Browse the repository at this point in the history
Move installation parameters to INIT command
  • Loading branch information
bitgamma authored Oct 10, 2018
2 parents f71286e + 8fbba18 commit 643f81b
Show file tree
Hide file tree
Showing 10 changed files with 363 additions and 131 deletions.
60 changes: 48 additions & 12 deletions APPLICATION.MD
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,22 @@ authentication.
Before any application command is processed, a Secure Channel session must be established as specified in the
[SECURE_CHANNEL.MD](SECURE_CHANNEL.MD) document.

## INITIALIZATION

After installation, the applet is not ready to operate and is in a pre-initializaed state. In this state the applet can
only process the SELECT and INIT command. The INIT command is used to personalize the PIN, PUK and pairing secret, which
must be generated off-card.

## PIN

During installation the user's PIN is set to 000000 (six times zero). The PIN length is fixed at 6 digits. After 3
failed authentication attempts the PIN is blocked and authentication is not possible anymore. A blocked PIN can be
replaced and unblocked using a PUK. The PUK is a 12-digit number, unique for each installation and is generated off-card
and passed as an installation parameter to the applet according to the JavaCard specifications. After 5 failed attempts
to unblock the applet using the PUK, the PUK is blocked, meaning the wallet is lost.
The PIN length is fixed at 6 digits. After 3 failed authentication attempts the PIN is blocked and authentication is not
possible anymore. A blocked PIN can be replaced and unblocked using a PUK. The PUK is a 12-digit number. After 5 failed
attempts to unblock the applet using the PUK, the PUK is blocked, meaning the wallet is lost.

After authentication, the user remains authenticated until the application is either deselected or the card is reset.
Authentication with PIN is a requirement for most commands to succeed.

The PIN can be changed by the user after authentication.
The PIN and PUK can be changed by the user after authentication.

## Keys & Signature

Expand All @@ -48,7 +52,7 @@ SW 0x6985 is returned. All tagged data structures are encoded in the [BER-TLV fo
* P1 = 0x04
* P2 = 0x00
* Data = 53746174757357616C6C6574417070 (hex)
* Response = Application Info Template
* Response = Application Info Template or ECC public key.

Response Data format:
- Tag 0xA4 = Application Info Template
Expand All @@ -67,6 +71,33 @@ application, formatted on two bytes. The first byte is the major version and the

The Key UID can be either empty (when no key is loaded on card) or the SHA-256 hash of the master public key.

When the applet is in pre-initializated state, it only returns the ECC public key, BER-TLV encoded with tag 0x80.

### INIT
* CLA = 0x80
* INS = 0xFE
* P1 = 0x00
* P2 = 0x00
* Data = EC public key (LV encoded) | IV | encrypted payload
* Response SW = 0x9000 on success, 0x6D00 if the applet is already initialized

This command is only available when the applet is in pre-initialized state and successful execution brings the applet in
the initialized state. This command is needed to allow securely storing secrets on the applet at a different moment and
place than installation is taking place. Currently these are the PIN, PUK and pairing password.

The client must take the public key received after the SELECT command, generate a random keypair and perform EC-DH to
generate an AES key. It must then generate a random IV and encrypt the payload using AES-CBC with ISO/IEC 9797-1 Method
2 padding.

They payload is the concatenation of the PIN (6 digits/bytes), PUK (12 digits/bytes) and pairing secret (32 bytes).

This scheme guarantees protection against passive MITM attacks. Since the applet has no "owner" before the execution of
this command, protection against active MITM cannot be provided at this stage. However since the communication happens
locally (either through NFC or contacted interface) the realization of such an attack at this point is unrealistic.

After successful execution, this command cannot be executed anymore. The regular SecureChannel (with pairing) is active
and PIN and PUK are initialized.

### OPEN SECURE CHANNEL

The OPEN SECURE CHANNEL command is as specified in the [SECURE_CHANNEL.MD](SECURE_CHANNEL.MD).
Expand Down Expand Up @@ -133,15 +164,20 @@ always returns 0x63C0, even if the PIN is inserted correctly.

* CLA = 0x80
* INS = 0x21
* P1 = 0x00
* P1 = PIN identifier
* P2 = 0x00
* Data = the new PIN
* Response SW = 0x9000 on success, 0x6A80 if the PIN format is invalid
* Response SW = 0x9000 on success, 0x6A80 if the PIN format is invalid, 0x6A86 if P1 is invalid
* Preconditions: Secure Channel must be opened, user PIN must be verified

Used to change the user PIN. The new PIN must be composed of exactly 6 numeric digits. Should this be not the case,
the code 0x6A80 is returned. If the conditions match, the user PIN is updated and authenticated for the rest of
the session. The no-error SW 0x9000 is returned.
Used to change a PIN or secret. In case of invalid format, the code 0x6A80 is returned. If the conditions match, the PIN
or secret is updated. The no-error SW 0x9000 is returned.

P1:
* 0x00: User PIN. Must be 6-digits. The updated PIN is authenticated for the rest of the session.
* 0x01: Applet PUK. Must be 12-digits.
* 0x02: Pairing secret. Must be 32-bytes long. Existing pairings are not broken, but new pairings will need to use the
new secret.

### UNBLOCK PIN

Expand Down
11 changes: 0 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,6 @@ im.status.gradle.gpshell.kvn=0
im.status.wallet.test.simulated=false
```

## Alternative installation method
This method does not require the JavaCard SDK but requires an already compiled CAP file. The cards generated this way
have a random PUK and pairing code so they have better security. However applet installation/removal is not disabled,
because the script is still meant to be used during the development phase.

1. Install GPShell and Python 3
2. Put the wallet.cap file in the same directory as status_hw_perso.py
3. Disconnect all card reader terminals from the system, except the one with the card where you want to install the applet
4. Run the status_hw_perso.py script with no arguments.
5. Take note of the pairing code and PUK output by the script

## Implementation notes

* The applet requires JavaCard 3.0.4 or later.
Expand Down
12 changes: 7 additions & 5 deletions SECURE_CHANNEL.MD
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ authentication for each APDU.
A short description of establishing a session is as follows

1. The client selects the application on card. The application responds with a public EC key.
2. The client sends an OPEN SECURE CHANNEL command with its public key. The EC-DH algorithm is used by both parties to
generate a shared 256-bit secret (more details below).
3. The generated secret is used as an AES key to encrypt all further communication. CBC mode is used with a random IV
generated for each APDU and prepended to the APDU payload. Both command and responses are encrypted.
4. The client sends a MUTUALLY AUTHENTICATE command to verify that the keys are matching and thus the secure channel is
2. The client sends an OPEN SECURE CHANNEL command with its public key and pairing index. The EC-DH algorithm is used by
both parties to generate a shared 256-bit secret (more details below).
3. The generated secret is concatenated with the pairing key and random data and hashed with SHA-512.
4. The first half of the generated value is used as an AES key to encrypt all further communication. CBC mode is used
with a random IV generated for each APDU and prepended to the APDU payload. The second half is used to MAC generation
and verification. Both command and responses are encrypted.
5. The client sends a MUTUALLY AUTHENTICATE command to verify that the keys are matching and thus the secure channel is
successfully established.

The EC keyset used by the card for the EC-DH algorithm is generated on-card on applet installation and is not used
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ task install(type: Exec) {
send_apdu_nostop -sc 1 -APDU 80E400800E4F0C53746174757357616C6C6574
install_for_load -pkgAID 53746174757357616C6C6574
load -file build/javacard/im/status/wallet/javacard/wallet.cap
send_apdu -sc 1 -APDU 80E60C005F0C53746174757357616C6C65740F53746174757357616C6C65744170700F53746174757357616C6C657441707001002EC92C313233343536373839303132e929d425d7f73c2a0a24ffefad87b65e9b2ee96603eab34d64088b5aae2a026f00
install_for_install -AID 53746174757357616C6C6574417070 -pkgAID 53746174757357616C6C6574 -instAID 53746174757357616C6C6574417070
install_for_install -AID 53746174757357616C6C65744e4643 -pkgAID 53746174757357616C6C6574 -instAID D2760000850101
card_disconnect
release_context
Expand Down
58 changes: 51 additions & 7 deletions src/main/java/im/status/wallet/SecureChannel.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,13 @@ public class SecureChannel {
private boolean mutuallyAuthenticated = false;

private Crypto crypto;
private SECP256k1 secp256k1;

/**
* Instantiates a Secure Channel. All memory allocations needed for the secure channel are performed here. The keypair
* used for the EC-DH algorithm is also generated here.
* Instantiates a Secure Channel. All memory allocations (except pairing secret) needed for the secure channel are
* performed here. The keypair used for the EC-DH algorithm is also generated here.
*/
public SecureChannel(byte pairingLimit, byte[] aPairingSecret, short off, Crypto crypto, SECP256k1 secp256k1) {
public SecureChannel(byte pairingLimit, Crypto crypto, SECP256k1 secp256k1) {
this.crypto = crypto;
this.secp256k1 = secp256k1;

scCipher = Cipher.getInstance(Cipher.ALG_AES_CBC_ISO9797_M2,false);

Expand All @@ -76,11 +74,48 @@ public SecureChannel(byte pairingLimit, byte[] aPairingSecret, short off, Crypto
scKeypair.genKeyPair();

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);
}

/**
* Initializes the SecureChannel instance with the pairing secret.
*
* @param aPairingSecret the pairing secret
* @param off the offset in the buffer
*/
public void initSecureChannel(byte[] aPairingSecret, short off) {
if (pairingSecret != null) return;

pairingSecret = new byte[SC_SECRET_LENGTH];
Util.arrayCopy(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
scKeypair.genKeyPair();
}

/**
* Decrypts the content of the APDU by generating an AES key using EC-DH. Only usable in pre-initialization state.
* @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);
try {
crypto.ecdh.generateSecret(apduBuffer, off, apduBuffer[ISO7816.OFFSET_CDATA], secret, (short) 0);
off = (short)(off + apduBuffer[ISO7816.OFFSET_CDATA]);
} catch(Exception e) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
return;
}

scEncKey.setKey(secret, (short) 0);
scCipher.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);
}

/**
Expand Down Expand Up @@ -404,6 +439,15 @@ public void reset() {
mutuallyAuthenticated = false;
}

/**
* Updates the pairing secret. Does not affect existing pairings.
* @param aPairingSecret the buffer
* @param off the offset
*/
public void updatePairingSecret(byte[] aPairingSecret, byte off) {
Util.arrayCopy(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
}

/**
* Returns the offset in the pairingKey byte array of the pairing key with the given index. Throws 0x6A86 if the index
* is invalid
Expand Down
Loading

0 comments on commit 643f81b

Please sign in to comment.