add ability to change PUK and pairing secret

This commit is contained in:
Michele Balistreri 2018-10-10 12:42:34 +02:00
parent 796449023d
commit 625f75302b
5 changed files with 172 additions and 27 deletions

View File

@ -91,6 +91,13 @@ generate an AES key. It must then generate a random IV and encrypt the payload u
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).
@ -157,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

View File

@ -89,7 +89,7 @@ public class SecureChannel {
if (pairingSecret != null) return;
pairingSecret = new byte[SC_SECRET_LENGTH];
Util.arrayCopyNonAtomic(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
Util.arrayCopy(aPairingSecret, off, pairingSecret, (short) 0, SC_SECRET_LENGTH);
scKeypair.genKeyPair();
}
@ -439,6 +439,15 @@ public class SecureChannel {
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

View File

@ -38,6 +38,10 @@ public class WalletApplet extends Applet {
static final byte GET_STATUS_P1_APPLICATION = 0x00;
static final byte GET_STATUS_P1_KEY_PATH = 0x01;
static final byte CHANGE_PIN_P1_USER_PIN = 0x00;
static final byte CHANGE_PIN_P1_PUK = 0x01;
static final byte CHANGE_PIN_P1_PAIRING_SECRET = 0x02;
static final byte LOAD_KEY_P1_EC = 0x01;
static final byte LOAD_KEY_P1_EXT_EC = 0x02;
static final byte LOAD_KEY_P1_SEED = 0x03;
@ -454,9 +458,8 @@ public class WalletApplet extends Applet {
}
/**
* Processes the CHANGE PIN command. Requires a secure channel to be already open and the PIN to be verified. Since
* the PIN is fixed to a 6-digits format, longer or shorter PINs or PINs containing non-numeric characters will be
* refused.
* Processes the CHANGE PIN command. Requires a secure channel to be already open and the user PIN to be verified. All
* PINs have a fixed format which is verified by this method.
*
* @param apdu the JCRE-owned APDU object.
*/
@ -468,6 +471,28 @@ public class WalletApplet extends Applet {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}
switch(apduBuffer[ISO7816.OFFSET_P1]) {
case CHANGE_PIN_P1_USER_PIN:
changeUserPIN(apduBuffer, len);
break;
case CHANGE_PIN_P1_PUK:
changePUK(apduBuffer, len);
break;
case CHANGE_PIN_P1_PAIRING_SECRET:
changePairingSecret(apduBuffer, len);
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
break;
}
}
/**
* Changes the user PIN. Called internally by CHANGE PIN
* @param apduBuffer the APDU buffer
* @param len the data length
*/
private void changeUserPIN(byte[] apduBuffer, byte len) {
if (!(len == PIN_LENGTH && allDigits(apduBuffer, ISO7816.OFFSET_CDATA, len))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
@ -476,6 +501,32 @@ public class WalletApplet extends Applet {
pin.check(apduBuffer, ISO7816.OFFSET_CDATA, len);
}
/**
* Changes the PUK. Called internally by CHANGE PIN
* @param apduBuffer the APDU buffer
* @param len the data length
*/
private void changePUK(byte[] apduBuffer, byte len) {
if (!(len == PUK_LENGTH && allDigits(apduBuffer, ISO7816.OFFSET_CDATA, len))) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
puk.update(apduBuffer, ISO7816.OFFSET_CDATA, len);
}
/**
* Changes the pairing secret. Called internally by CHANGE PIN
* @param apduBuffer the APDU buffer
* @param len the data length
*/
private void changePairingSecret(byte[] apduBuffer, byte len) {
if (len != SecureChannel.SC_SECRET_LENGTH) {
ISOException.throwIt(ISO7816.SW_WRONG_DATA);
}
secureChannel.updatePairingSecret(apduBuffer, ISO7816.OFFSET_CDATA);
}
/**
* Processes the UNBLOCK PIN command. Requires a secure channel to be already open and the PIN to be blocked. The PUK
* and the new PIN are sent in the same APDU with no separator. This is possible because the PUK is exactly 12 digits

View File

@ -170,12 +170,26 @@ public class WalletAppletCommandSet {
* Sends a CHANGE PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU
* data.
*
* @param pinType the PIN type
* @param pin the new PIN
* @return the raw card response
* @throws CardException communication error
*/
public ResponseAPDU changePIN(String pin) throws CardException {
CommandAPDU changePIN = secureChannel.protectedCommand(0x80, WalletApplet.INS_CHANGE_PIN, 0, 0, pin.getBytes());
public ResponseAPDU changePIN(int pinType, String pin) throws CardException {
return changePIN(pinType, pin.getBytes());
}
/**
* Sends a CHANGE PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU
* data.
*
* @param pinType the PIN type
* @param pin the new PIN
* @return the raw card response
* @throws CardException communication error
*/
public ResponseAPDU changePIN(int pinType, byte[] pin) throws CardException {
CommandAPDU changePIN = secureChannel.protectedCommand(0x80, WalletApplet.INS_CHANGE_PIN, pinType, 0, pin);
return secureChannel.transmit(apduChannel, changePIN);
}

View File

@ -390,23 +390,54 @@ public class WalletAppletTest {
@DisplayName("CHANGE PIN command")
void changePinTest() throws CardException {
// Security condition violation: SecureChannel not open
ResponseAPDU response = cmdSet.changePIN("123456");
ResponseAPDU response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x6985, response.getSW());
cmdSet.autoOpenSecureChannel();
// Security condition violation: PIN n ot verified
response = cmdSet.changePIN("123456");
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x6985, response.getSW());
// Change PIN correctly, check that after PIN change the PIN remains validated
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN("123456");
// Wrong P1
response = cmdSet.changePIN(0x03, "123456");
assertEquals(0x6a86, response.getSW());
// Test wrong PIN formats (non-digits, too short, too long)
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "654a21");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "54321");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "7654321");
assertEquals(0x6A80, response.getSW());
// Test wrong PUK formats
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "210987654a21");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "10987654321");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "3210987654321");
assertEquals(0x6A80, response.getSW());
// Test wrong pairing secret format (too long, too short)
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz123456789012");
assertEquals(0x6A80, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz1234567890");
assertEquals(0x6A80, response.getSW());
// Change PIN correctly, check that after PIN change the PIN remains validated
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "123456");
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN("654321");
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "654321");
assertEquals(0x9000, response.getSW());
// Reset card and verify that the new PIN has really been set
@ -415,18 +446,46 @@ public class WalletAppletTest {
response = cmdSet.verifyPIN("654321");
assertEquals(0x9000, response.getSW());
// Test wrong PIN formats (non-digits, too short, too long)
response = cmdSet.changePIN("654a21");
assertEquals(0x6A80, response.getSW());
// Change PUK
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "210987654321");
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN("54321");
assertEquals(0x6A80, response.getSW());
resetAndSelectAndOpenSC();
response = cmdSet.changePIN("7654321");
assertEquals(0x6A80, response.getSW());
response = cmdSet.verifyPIN("000000");
assertEquals(0x63C2, response.getSW());
response = cmdSet.verifyPIN("000000");
assertEquals(0x63C1, response.getSW());
response = cmdSet.verifyPIN("000000");
assertEquals(0x63C0, response.getSW());
// Reset the PIN to make further tests possible
response = cmdSet.changePIN("000000");
// Reset the PIN with the new PUK
response = cmdSet.unblockPIN("210987654321", "000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
// Reset PUK
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PUK, "123456789012");
assertEquals(0x9000, response.getSW());
// Change the pairing secret
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, "abcdefghilmnopqrstuvz12345678901");
assertEquals(0x9000, response.getSW());
cmdSet.autoUnpair();
reset();
response = cmdSet.select();
assertEquals(0x9000, response.getSW());
cmdSet.autoPair("abcdefghilmnopqrstuvz12345678901".getBytes());
// Reset pairing secret
cmdSet.autoOpenSecureChannel();
response = cmdSet.verifyPIN("000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_PAIRING_SECRET, SHARED_SECRET);
assertEquals(0x9000, response.getSW());
}
@ -475,7 +534,7 @@ public class WalletAppletTest {
assertEquals(0x9000, response.getSW());
// Reset the PIN to make further tests possible
response = cmdSet.changePIN("000000");
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "000000");
assertEquals(0x9000, response.getSW());
}
@ -1072,7 +1131,7 @@ public class WalletAppletTest {
// Signing session can be resumed if other commands are sent
response = cmdSet.sign(data, WalletApplet.SIGN_P1_DATA,true, false);
assertEquals(0x9000, response.getSW());
response = cmdSet.changePIN("000000");
response = cmdSet.changePIN(WalletApplet.CHANGE_PIN_P1_USER_PIN, "000000");
assertEquals(0x9000, response.getSW());
response = cmdSet.sign(smallData, WalletApplet.SIGN_P1_DATA,false, true);
assertEquals(0x9000, response.getSW());