add ability to change PUK and pairing secret
This commit is contained in:
parent
796449023d
commit
625f75302b
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Reference in New Issue