replace magic constants in APDU factory

This commit is contained in:
Vincent Breitmoser
2017-10-13 15:58:21 +02:00
parent 8e9a62070d
commit b7723c1a4a
4 changed files with 200 additions and 104 deletions

View File

@@ -1,7 +1,6 @@
package org.sufficientlysecure.keychain.securitytoken;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
@@ -19,91 +18,178 @@ class CommandAPDUFactory {
private static final int MAX_APDU_NE = 256;
private static final int MAX_APDU_NE_EXT = 65536;
final int CLA = 0x00;
private static final int CLA = 0x00;
private static final int MASK_CLA_CHAINING = 1 << 4;
final int MASK_CLA_CHAINING = 1 << 4;
private static final int INS_SELECT_FILE = 0xA4;
private static final int P1_SELECT_FILE = 0x04;
private static final byte[] AID_SELECT_FILE_OPENPGP = Hex.decode("D27600012401");
private static final int INS_ACTIVATE_FILE = 0x44;
private static final int INS_TERMINATE_DF = 0xE6;
private static final int INS_GET_RESPONSE = 0xC0;
private static final int INS_INTERNAL_AUTHENTICATE = 0x88;
private static final int P1_INTERNAL_AUTH_SECURE_MESSAGING = 0x01;
private static final int INS_VERIFY = 0x20;
private static final int P2_VERIFY_PW1_SIGN = 0x81;
private static final int P2_VERIFY_PW1_OTHER = 0x82;
private static final int P2_VERIFY_PW3 = 0x83;
private static final int INS_CHANGE_REFERENCE_DATA = 0x24;
private static final int P2_CHANGE_REFERENCE_DATA_PW1 = 0x81;
private static final int P2_CHANGE_REFERENCE_DATA_PW3 = 0x83;
private static final int INS_RESET_RETRY_COUNTER = 0x2C;
private static final int P1_RESET_RETRY_COUNTER_NEW_PW = 0x02;
private static final int P2_RESET_RETRY_COUNTER = 0x81;
private static final int INS_PERFORM_SECURITY_OPERATION = 0x2A;
private static final int P1_PSO_DECIPHER = 0x80;
private static final int P1_PSO_COMPUTE_DIGITAL_SIGNATURE = 0x9E;
private static final int P2_PSO_DECIPHER = 0x86;
private static final int P2_PSO_COMPUTE_DIGITAL_SIGNATURE = 0x9A;
private static final int INS_SELECT_DATA = 0xA5;
private static final int P1_SELECT_DATA_FOURTH = 0x03;
private static final int P2_SELECT_DATA = 0x04;
private static final byte[] CP_SELECT_DATA_CARD_HOLDER_CERT = Hex.decode("60045C027F21");
private static final int INS_GET_DATA = 0xCA;
private static final int P1_GET_DATA_CARD_HOLDER_CERT = 0x7F;
private static final int P2_GET_DATA_CARD_HOLDER_CERT = 0x21;
private static final int INS_PUT_DATA = 0xDA;
private static final int INS_PUT_DATA_ODD = 0xDB;
private static final int P1_PUT_DATA_ODD_KEY = 0x3F;
private static final int P2_PUT_DATA_ODD_KEY = 0xFF;
private static final int INS_GENERATE_ASYMMETRIC_KEY_PAIR = 0x47;
private static final int P1_GAKP_GENERATE = 0x80;
private static final int P1_GAKP_READ_PUBKEY_TEMPLATE = 0x81;
private static final byte[] CRT_GAKP_SECURE_MESSAGING = Hex.decode("A600");
private static final int P1_EMPTY = 0x00;
private static final int P2_EMPTY = 0x00;
@NonNull
CommandAPDU createPutDataCommand(int dataObject, byte[] data) {
return new CommandAPDU(CLA, INS_PUT_DATA, (dataObject & 0xFF00) >> 8, dataObject & 0xFF, data);
}
@NonNull
CommandAPDU createPutKeyCommand(byte[] keyBytes) {
return new CommandAPDU(CLA, 0xDB, 0x3F, 0xFF, keyBytes);
// the odd PUT DATA INS is for compliance with ISO 7816-8. This is used only to put key data on the card
return new CommandAPDU(CLA, INS_PUT_DATA_ODD, P1_PUT_DATA_ODD_KEY, P2_PUT_DATA_ODD_KEY, keyBytes);
}
@NonNull
CommandAPDU createComputeDigitalSignatureCommand(byte[] data) {
return new CommandAPDU(CLA, 0x2A, 0x9E, 0x9A, data, MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createPutDataCommand(int dataObject, byte[] data) {
return new CommandAPDU(CLA, 0xDA, (dataObject & 0xFF00) >> 8, dataObject & 0xFF, data);
return new CommandAPDU(CLA, INS_PERFORM_SECURITY_OPERATION, P1_PSO_COMPUTE_DIGITAL_SIGNATURE,
P2_PSO_COMPUTE_DIGITAL_SIGNATURE, data, MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createDecipherCommand(byte[] data) {
return new CommandAPDU(CLA, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT);
return new CommandAPDU(CLA, INS_PERFORM_SECURITY_OPERATION, P1_PSO_DECIPHER, P2_PSO_DECIPHER, data,
MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createChangeReferenceDataCommand(int pw, byte[] newPin, byte[] pin) {
return new CommandAPDU(CLA, 0x24, 0x00, pw, Arrays.concatenate(pin, newPin));
CommandAPDU createChangePw1Command(byte[] pin, byte[] newPin) {
return new CommandAPDU(CLA, INS_CHANGE_REFERENCE_DATA, P1_EMPTY,
P2_CHANGE_REFERENCE_DATA_PW1, Arrays.concatenate(pin, newPin));
}
@NonNull
CommandAPDU createResetRetryCounter(byte[] newPin) {
return new CommandAPDU(CLA, 0x2C, 0x02, 0x81, newPin);
CommandAPDU createChangePw3Command(byte[] adminPin, byte[] newAdminPin) {
return new CommandAPDU(CLA, INS_CHANGE_REFERENCE_DATA, P1_EMPTY,
P2_CHANGE_REFERENCE_DATA_PW3, Arrays.concatenate(adminPin, newAdminPin));
}
@NonNull
CommandAPDU createResetPw1Command(byte[] newPin) {
return new CommandAPDU(CLA, INS_RESET_RETRY_COUNTER, P1_RESET_RETRY_COUNTER_NEW_PW,
P2_RESET_RETRY_COUNTER, newPin);
}
@NonNull
CommandAPDU createGetDataCommand(int p1, int p2) {
return new CommandAPDU(CLA, 0xCA, p1, p2, MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createGenerateKeyCommand(int slot) {
return new CommandAPDU(CLA, 0x47, 0x80, 0x00, new byte[] { (byte) slot, 0x00 }, MAX_APDU_NE_EXT);
return new CommandAPDU(CLA, INS_GET_DATA, p1, p2, MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createGetResponseCommand(int lastResponseSw2) {
return new CommandAPDU(CLA, 0xC0, 0x00, 0x00, lastResponseSw2);
return new CommandAPDU(CLA, INS_GET_RESPONSE, P1_EMPTY, P2_EMPTY, lastResponseSw2);
}
@NonNull
CommandAPDU createVerifyPw1ForSignatureCommand(byte[] pin) {
return new CommandAPDU(CLA, INS_VERIFY, P1_EMPTY, P2_VERIFY_PW1_SIGN, pin);
}
@NonNull
CommandAPDU createVerifyCommand(int mode, byte[] pin) {
return new CommandAPDU(CLA, 0x20, 0x00, mode, pin);
CommandAPDU createVerifyPw1ForOtherCommand(byte[] pin) {
return new CommandAPDU(CLA, INS_VERIFY, P1_EMPTY, P2_VERIFY_PW1_OTHER, pin);
}
@NonNull
CommandAPDU createVerifyPw3Command(byte[] pin) {
return new CommandAPDU(CLA, INS_VERIFY, P1_EMPTY, P2_VERIFY_PW3, pin);
}
@NonNull
CommandAPDU createSelectFileOpenPgpCommand() {
return new CommandAPDU(CLA, INS_SELECT_FILE, P1_SELECT_FILE, P2_EMPTY, AID_SELECT_FILE_OPENPGP);
}
@NonNull
CommandAPDU createSelectFileCommand(String fileAid) {
return new CommandAPDU(CLA, 0xA4, 0x04, 0x00, Hex.decode(fileAid));
return new CommandAPDU(CLA, INS_SELECT_FILE, P1_SELECT_FILE, P2_EMPTY, Hex.decode(fileAid));
}
@NonNull
CommandAPDU createReactivate2Command() {
return new CommandAPDU(CLA, 0x44, 0x00, 0x00);
return new CommandAPDU(CLA, INS_ACTIVATE_FILE, P1_EMPTY, P2_EMPTY);
}
@NonNull
CommandAPDU createReactivate1Command() {
return new CommandAPDU(CLA, 0xE6, 0x00, 0x00);
return new CommandAPDU(CLA, INS_TERMINATE_DF, P1_EMPTY, P2_EMPTY);
}
@NonNull
CommandAPDU createInternalAuthenticateCommand(ByteArrayOutputStream pkout) {
return new CommandAPDU(0, 0x88, 0x01, 0x0, pkout.toByteArray(), MAX_APDU_NE_EXT);
CommandAPDU createInternalAuthForSecureMessagingCommand(byte[] authData) {
return new CommandAPDU(CLA, INS_INTERNAL_AUTHENTICATE, P1_INTERNAL_AUTH_SECURE_MESSAGING, P2_EMPTY, authData,
MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createRetrievePublicKeyCommand(byte[] msgCert) {
return new CommandAPDU(0, 0x47, 0x81, 0x00, msgCert, MAX_APDU_NE_EXT);
CommandAPDU createGenerateKeyCommand(int slot) {
return new CommandAPDU(CLA, INS_GENERATE_ASYMMETRIC_KEY_PAIR,
P1_GAKP_GENERATE, P2_EMPTY, new byte[] { (byte) slot, 0x00 }, MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createRetrieveCertificateCommand() {
return new CommandAPDU(0, 0xA5, 0x03, 0x04,
new byte[]{0x60, 0x04, 0x5C, 0x02, 0x7F, 0x21});
CommandAPDU createRetrieveSecureMessagingPublicKeyCommand() {
// see https://github.com/ANSSI-FR/SmartPGP/blob/master/secure_messaging/smartpgp_sm.pdf
return new CommandAPDU(CLA, INS_GENERATE_ASYMMETRIC_KEY_PAIR, P1_GAKP_READ_PUBKEY_TEMPLATE, P2_EMPTY,
CRT_GAKP_SECURE_MESSAGING, MAX_APDU_NE_EXT);
}
@NonNull
CommandAPDU createSelectSecureMessagingCertificateCommand() {
// see https://github.com/ANSSI-FR/SmartPGP/blob/master/secure_messaging/smartpgp_sm.pdf
// this command selects the fourth occurence of data tag 7F21
return new CommandAPDU(CLA, INS_SELECT_DATA, P1_SELECT_DATA_FOURTH, P2_SELECT_DATA,
CP_SELECT_DATA_CARD_HOLDER_CERT);
}
@NonNull
CommandAPDU createGetDataCardHolderCertCommand() {
return createGetDataCommand(P1_GET_DATA_CARD_HOLDER_CERT, P2_GET_DATA_CARD_HOLDER_CERT);
}
@NonNull

View File

@@ -17,17 +17,6 @@
package org.sufficientlysecure.keychain.securitytoken;
import android.content.Context;
import android.support.annotation.NonNull;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity;
import org.sufficientlysecure.keychain.util.Preferences;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -65,6 +54,9 @@ import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.ArrayList;
import android.content.Context;
import android.support.annotation.NonNull;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
@@ -76,12 +68,19 @@ import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import org.bouncycastle.asn1.nist.NISTNamedCurves;
import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.Arrays;
import org.sufficientlysecure.keychain.ui.SettingsSmartPGPAuthoritiesActivity;
import org.sufficientlysecure.keychain.util.Preferences;
class SCP11bSecureMessaging implements SecureMessaging {
private static final byte OPENPGP_SECURE_MESSAGING_CLA_MASK = (byte)0x04;
private static final byte[] OPENPGP_SECURE_MESSAGING_KEY_CRT = new byte[] { (byte)0xA6, (byte)0 };
private static final byte OPENPGP_SECURE_MESSAGING_KEY_ATTRIBUTES_TAG = (byte)0xD4;
private static final int AES_BLOCK_SIZE = 128 / 8;
@@ -316,12 +315,12 @@ class SCP11bSecureMessaging implements SecureMessaging {
if (prefs != null && prefs.getExperimentalSmartPGPAuthoritiesEnable()) {
// retrieving certificate
cmd = commandFactory.createRetrieveCertificateCommand();
cmd = commandFactory.createSelectSecureMessagingCertificateCommand();
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenConnection.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to select secure messaging certificate");
}
cmd = commandFactory.createGetDataCommand(0x7F, 0x21);
cmd = commandFactory.createGetDataCardHolderCertCommand();
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenConnection.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to retrieve secure messaging certificate");
@@ -330,8 +329,7 @@ class SCP11bSecureMessaging implements SecureMessaging {
pkcard = verifyCertificate(ctx, eckf, resp.getData());
} else {
// retrieving public key
cmd = commandFactory.createRetrievePublicKeyCommand(OPENPGP_SECURE_MESSAGING_KEY_CRT);
cmd = commandFactory.createRetrieveSecureMessagingPublicKeyCommand();
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenConnection.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to retrieve secure messaging public key");
@@ -391,8 +389,7 @@ class SCP11bSecureMessaging implements SecureMessaging {
pkout.writeTo(bout);
pkout = bout;
// internal authenticate
cmd = commandFactory.createInternalAuthenticateCommand(pkout);
cmd = commandFactory.createInternalAuthForSecureMessagingCommand(pkout.toByteArray());
resp = t.communicate(cmd);
if (resp.getSW() != SecurityTokenConnection.APDU_SW_SUCCESS) {
throw new SecureMessagingException("failed to initiate internal authenticate");

View File

@@ -183,7 +183,7 @@ public class SecurityTokenConnection {
// Connect on smartcard layer
// Command APDU (page 51) for SELECT FILE command (page 29)
CommandAPDU select = commandFactory.createSelectFileCommand("D27600012401");
CommandAPDU select = commandFactory.createSelectFileOpenPgpCommand();
ResponseAPDU response = communicate(select); // activate connection
if (response.getSW() != APDU_SW_SUCCESS) {
@@ -222,7 +222,7 @@ public class SecurityTokenConnection {
}
// Command APDU for RESET RETRY COUNTER command (page 33)
CommandAPDU changePin = commandFactory.createResetRetryCounter(newPin);
CommandAPDU changePin = commandFactory.createResetPw1Command(newPin);
ResponseAPDU response = communicate(changePin);
if (response.getSW() != APDU_SW_SUCCESS) {
@@ -231,42 +231,48 @@ public class SecurityTokenConnection {
}
/**
* Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
* Modifies the user's PW3. Before sending, the new PIN will be validated for
* conformance to the token's requirements for key length.
*
* @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
* @param newPin The new PW1 or PW3.
* @param newAdminPin The new PW3.
*/
public void modifyPin(int pw, byte[] newPin, Passphrase adminPin) throws IOException {
final int MAX_PW1_LENGTH_INDEX = 1;
public void modifyPw3Pin(byte[] newAdminPin, Passphrase adminPin) throws IOException {
final int MAX_PW3_LENGTH_INDEX = 3;
byte[] pwStatusBytes = getPwStatusBytes();
if (pw == 0x81) {
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
throw new IOException("Invalid PIN length");
}
} else if (pw == 0x83) {
if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
throw new IOException("Invalid PIN length");
}
} else {
throw new IOException("Invalid PW index for modify PIN operation");
if (newAdminPin.length < 8 || newAdminPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) {
throw new IOException("Invalid PIN length");
}
byte[] pin;
if (pw == 0x83) {
if (adminPin == null) {
throw new IllegalArgumentException("Changing the admin pin requires admin pin argument!");
}
pin = adminPin.toStringUnsafe().getBytes();
} else {
pin = mPin.toStringUnsafe().getBytes();
byte[] pin = adminPin.toStringUnsafe().getBytes();
CommandAPDU changePin = commandFactory.createChangePw3Command(pin, newAdminPin);
ResponseAPDU response = communicate(changePin);
if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Failed to change PIN", response.getSW());
}
}
/**
* Modifies the user's PW1. Before sending, the new PIN will be validated for
* conformance to the token's requirements for key length.
*
* @param newPin The new PW1.
*/
public void modifyPw1Pin(byte[] newPin) throws IOException {
final int MAX_PW1_LENGTH_INDEX = 1;
byte[] pwStatusBytes = getPwStatusBytes();
if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) {
throw new IOException("Invalid PIN length");
}
// Command APDU for CHANGE REFERENCE DATA command (page 32)
CommandAPDU changePin = commandFactory.createChangeReferenceDataCommand(pw, newPin, pin);
byte[] pin = mPin.toStringUnsafe().getBytes();
CommandAPDU changePin = commandFactory.createChangePw1Command(pin, newPin);
ResponseAPDU response = communicate(changePin);
if (response.getSW() != APDU_SW_SUCCESS) {
@@ -287,7 +293,7 @@ public class SecurityTokenConnection {
final KeyFormat kf = mOpenPgpCapabilities.getFormatForKeyType(KeyType.ENCRYPT);
if (!mPw1ValidatedForDecrypt) {
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
verifyPinForOther();
}
byte[] data;
@@ -410,31 +416,41 @@ public class SecurityTokenConnection {
}
/**
* Verifies the user's PW1 or PW3 with the appropriate mode.
*
* @param mode For PW1, this is 0x81 for signing, 0x82 for everything else.
* For PW3 (Admin PIN), mode is 0x83.
* Verifies the user's PW1 with the appropriate mode.
*/
private void verifyPin(int mode) throws IOException {
private void verifyPinForSignature() throws IOException {
byte[] pin = mPin.toStringUnsafe().getBytes();
ResponseAPDU response = tryPin(mode, pin);// login
ResponseAPDU response = communicate(commandFactory.createVerifyPw1ForSignatureCommand(pin));
if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Bad PIN!", response.getSW());
}
if (mode == 0x81) {
mPw1ValidatedForSignature = true;
} else if (mode == 0x82) {
mPw1ValidatedForDecrypt = true;
mPw1ValidatedForSignature = true;
}
/**
* Verifies the user's PW1 with the appropriate mode.
*/
private void verifyPinForOther() throws IOException {
byte[] pin = mPin.toStringUnsafe().getBytes();
// Command APDU for VERIFY command (page 32)
ResponseAPDU response = communicate(commandFactory.createVerifyPw1ForOtherCommand(pin));
if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Bad PIN!", response.getSW());
}
mPw1ValidatedForDecrypt = true;
}
/**
* Verifies the user's PW1 or PW3 with the appropriate mode.
*/
private void verifyAdminPin(Passphrase adminPin) throws IOException {
ResponseAPDU response = tryPin(0x83, adminPin.toStringUnsafe().getBytes());
// Command APDU for VERIFY command (page 32)
ResponseAPDU response =
communicate(commandFactory.createVerifyPw3Command(adminPin.toStringUnsafe().getBytes()));
if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Bad PIN!", response.getSW());
}
@@ -457,7 +473,7 @@ public class SecurityTokenConnection {
// TODO use admin pin regardless, if we have it?
if (dataObject == 0x0101 || dataObject == 0x0103) {
if (!mPw1ValidatedForDecrypt) {
verifyPin(0x82); // (Verify PW1 for non-signing operations)
verifyPinForOther();
}
} else if (!mPw3Validated) {
verifyAdminPin(adminPin);
@@ -640,7 +656,7 @@ public class SecurityTokenConnection {
*/
public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException {
if (!mPw1ValidatedForSignature) {
verifyPin(0x81); // (Verify PW1 with mode 81 for signing)
verifyPinForSignature();
}
byte[] dsi;
@@ -869,11 +885,6 @@ public class SecurityTokenConnection {
return response.getData();
}
private ResponseAPDU tryPin(int mode, byte[] pin) throws IOException {
// Command APDU for VERIFY command (page 32)
return communicate(commandFactory.createVerifyCommand(mode, pin));
}
/**
* Resets security token, which deletes all keys and data objects.
* This works by entering a wrong PIN and then Admin PIN 4 times respectively.
@@ -883,8 +894,9 @@ public class SecurityTokenConnection {
// try wrong PIN 4 times until counter goes to C0
byte[] pin = "XXXXXX".getBytes();
for (int i = 0; i <= 4; i++) {
ResponseAPDU response = tryPin(0x81, pin);
if (response.getSW() == APDU_SW_SUCCESS) { // Should NOT accept!
// Command APDU for VERIFY command (page 32)
ResponseAPDU response = communicate(commandFactory.createVerifyPw1ForSignatureCommand(pin));
if (response.getSW() == APDU_SW_SUCCESS) {
throw new CardException("Should never happen, XXXXXX has been accepted!", response.getSW());
}
}
@@ -892,7 +904,8 @@ public class SecurityTokenConnection {
// try wrong Admin PIN 4 times until counter goes to C0
byte[] adminPin = "XXXXXXXX".getBytes();
for (int i = 0; i <= 4; i++) {
ResponseAPDU response = tryPin(0x83, adminPin);
// Command APDU for VERIFY command (page 32)
ResponseAPDU response = communicate(commandFactory.createVerifyPw3Command(adminPin));
if (response.getSW() == APDU_SW_SUCCESS) { // Should NOT accept!
throw new CardException("Should never happen, XXXXXXXX has been accepted", response.getSW());
}

View File

@@ -273,8 +273,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenActivity {
}
// change PINs afterwards
stConnection.modifyPin(0x81, newPin, null);
stConnection.modifyPin(0x83, newAdminPin, adminPin);
stConnection.modifyPw1Pin(newPin);
stConnection.modifyPw3Pin(newAdminPin, adminPin);
break;
}