SecurityToken: use CommandAPDU instead of raw strings

This commit is contained in:
Nikita Mikhailov
2016-05-14 15:35:36 +06:00
parent 131bf94b9b
commit c2ca440c88
5 changed files with 135 additions and 228 deletions

View File

@@ -27,6 +27,15 @@ public class CardException extends IOException {
mResponseCode = responseCode; mResponseCode = responseCode;
} }
public CardException(String detailMessage, int responseCode) {
super(detailMessage);
mResponseCode = (short) responseCode;
}
public CardException(String s) {
this(s, -1);
}
public short getResponseCode() { public short getResponseCode() {
return mResponseCode; return mResponseCode;
} }

View File

@@ -19,6 +19,8 @@ package org.sufficientlysecure.keychain.securitytoken;
import android.nfc.Tag; import android.nfc.Tag;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.CommandAPDU;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.ResponseAPDU;
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity; import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
import java.io.IOException; import java.io.IOException;
@@ -43,8 +45,8 @@ public class NfcTransport implements Transport {
* @throws IOException * @throws IOException
*/ */
@Override @Override
public byte[] transceive(final byte[] data) throws IOException { public ResponseAPDU transceive(final CommandAPDU data) throws IOException {
return mIsoCard.transceive(data); return new ResponseAPDU(mIsoCard.transceive(data.getBytes()));
} }
/** /**

View File

@@ -30,6 +30,8 @@ import org.bouncycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.CommandAPDU;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.ResponseAPDU;
import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase; import org.sufficientlysecure.keychain.util.Passphrase;
@@ -47,7 +49,14 @@ import nordpol.Apdu;
* For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf
*/ */
public class SecurityTokenHelper { public class SecurityTokenHelper {
private static final int MAX_APDU_DATAFIELD_SIZE = 254 * 500; private static final int MAX_APDU_NC = 255;
private static final int MAX_APDU_NC_EXT = 65535;
private static final int MAX_APDU_NE = 256;
private static final int MAX_APDU_NE_EXT = 65536;
private static final int APDU_SW_SUCCESS = 0x9000;
// Fidesmo constants // Fidesmo constants
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
@@ -72,16 +81,9 @@ public class SecurityTokenHelper {
return new String(Hex.encode(raw)); return new String(Hex.encode(raw));
} }
private String getHolderName(String name) { private String getHolderName(byte[] name) {
try { try {
String slength; return (new String(name, 4, name[3])).replace('<', ' ');
int ilength;
name = name.substring(6);
slength = name.substring(0, 2);
ilength = Integer.parseInt(slength, 16) * 2;
name = name.substring(2, ilength + 2);
name = (new String(Hex.decode(name))).replace('<', ' ');
return name;
} catch (IndexOutOfBoundsException e) { } catch (IndexOutOfBoundsException e) {
// try-catch for https://github.com/FluffyKaon/OpenPGP-Card // try-catch for https://github.com/FluffyKaon/OpenPGP-Card
// Note: This should not happen, but happens with // Note: This should not happen, but happens with
@@ -153,23 +155,12 @@ public class SecurityTokenHelper {
mTransport.connect(); mTransport.connect();
// Connect on smartcard layer // Connect on smartcard layer
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
// See specification, page 51
String accepted = "9000";
// Command APDU (page 51) for SELECT FILE command (page 29) // Command APDU (page 51) for SELECT FILE command (page 29)
String opening = CommandAPDU select = new CommandAPDU(0x00, 0xA4, 0x04, 0x00, Hex.decode("D27600012401"));
"00" // CLA ResponseAPDU response = communicate(select); // activate connection
+ "A4" // INS
+ "04" // P1 if (response.getSW() != APDU_SW_SUCCESS) {
+ "00" // P2 throw new CardException("Initialization failed!", response.getSW());
+ "06" // Lc (number of bytes)
+ "D27600012401" // Data (6 bytes)
+ "00"; // Le
String response = communicate(opening); // activate connection
if (!response.endsWith(accepted)) {
throw new CardException("Initialization failed!", parseCardStatus(response));
} }
byte[] pwStatusBytes = getPwStatusBytes(); byte[] pwStatusBytes = getPwStatusBytes();
@@ -179,24 +170,6 @@ public class SecurityTokenHelper {
mPw3Validated = false; mPw3Validated = false;
} }
/**
* Parses out the status word from a JavaCard response string.
*
* @param response A hex string with the response from the card
* @return A short indicating the SW1/SW2, or 0 if a status could not be determined.
*/
private short parseCardStatus(String response) {
if (response.length() < 4) {
return 0; // invalid input
}
try {
return Short.parseShort(response.substring(response.length() - 4), 16);
} catch (NumberFormatException e) {
return 0;
}
}
/** /**
* Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for * Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
* conformance to the token's requirements for key length. * conformance to the token's requirements for key length.
@@ -230,16 +203,11 @@ public class SecurityTokenHelper {
} }
// Command APDU for CHANGE REFERENCE DATA command (page 32) // Command APDU for CHANGE REFERENCE DATA command (page 32)
String changeReferenceDataApdu = "00" // CLA CommandAPDU changePin = new CommandAPDU(0x00, 0x24, 0x00, pw, Arrays.concatenate(pin, newPin));
+ "24" // INS ResponseAPDU response = communicate(changePin);
+ "00" // P1
+ String.format("%02x", pw) // P2 if (response.getSW() != APDU_SW_SUCCESS) {
+ String.format("%02x", pin.length + newPin.length) // Lc throw new CardException("Failed to change PIN", response.getSW());
+ getHex(pin)
+ getHex(newPin);
String response = communicate(changeReferenceDataApdu); // change PIN
if (!response.equals("9000")) {
throw new CardException("Failed to change PIN", parseCardStatus(response));
} }
} }
@@ -254,43 +222,20 @@ public class SecurityTokenHelper {
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption) verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
} }
int offset = 2; // Skip first byte TODO: why?
String response = "", status = "";
boolean shouldPad = true;
// Transmit // Transmit
while (offset < encryptedSessionKey.length) { byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
boolean isLastCommand = MAX_APDU_DATAFIELD_SIZE >= encryptedSessionKey.length - offset; if (data[0] != 0) {
String cla = isLastCommand ? "00" : "10"; data = Arrays.prepend(data, (byte) 0x00);
int len = Math.min(MAX_APDU_DATAFIELD_SIZE, encryptedSessionKey.length - offset + (shouldPad ? 1 : 0));
String command = cla + "2a8086"
+ Hex.toHexString(new byte[]{(byte) ((len >> 16) & 0xFF), (byte) ((len >> 8) & 0xFF), (byte) (len & 0xFF)})
+ (shouldPad ? "00": "")
+ Hex.toHexString(encryptedSessionKey, offset, len - (shouldPad ? 1 : 0)) + "0000";
shouldPad = false;
response = communicate(command);
status = response.substring(response.length() - 4);
if (!isLastCommand && !response.endsWith("9000")) {
throw new CardException("Deciphering with Security token failed on transmit", parseCardStatus(response));
}
offset += MAX_APDU_DATAFIELD_SIZE;
} }
// Receive CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT);
String result = getDataField(response); ResponseAPDU response = communicate(command);
while (response.endsWith("61")) {
response = communicate("00C00000" + status.substring(2)); if (response.getSW() != APDU_SW_SUCCESS) {
status = response.substring(response.length() - 4); throw new CardException("Deciphering with Security token failed on receive", response.getSW());
result += getDataField(response);
}
if (!status.equals("9000")) {
throw new CardException("Deciphering with Security token failed on receive", parseCardStatus(response));
} }
return Hex.decode(result); return response.getData();
} }
/** /**
@@ -309,12 +254,9 @@ public class SecurityTokenHelper {
pin = mPin.toStringUnsafe().getBytes(); pin = mPin.toStringUnsafe().getBytes();
} }
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. ResponseAPDU response = tryPin(mode, pin);// login
// See specification, page 51 if (response.getSW() != APDU_SW_SUCCESS) {
String accepted = "9000"; throw new CardException("Bad PIN!", response.getSW());
String response = tryPin(mode, pin); // login
if (!response.equals(accepted)) {
throw new CardException("Bad PIN!", parseCardStatus(response));
} }
if (mode == 0x81) { if (mode == 0x81) {
@@ -347,16 +289,11 @@ public class SecurityTokenHelper {
verifyPin(0x83); // (Verify PW3) verifyPin(0x83); // (Verify PW3)
} }
String putDataApdu = "00" // CLA CommandAPDU command = new CommandAPDU(0x00, 0xDA, (dataObject & 0xFF00) >> 8, dataObject & 0xFF, data);
+ "DA" // INS ResponseAPDU response = communicate(command); // put data
+ String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
+ String.format("%02x", dataObject & 0xFF) // P2
+ String.format("%02x", data.length) // Lc
+ getHex(data);
String response = communicate(putDataApdu); // put data if (response.getSW() != APDU_SW_SUCCESS) {
if (!response.equals("9000")) { throw new CardException("Failed to put data.", response.getSW());
throw new CardException("Failed to put data.", parseCardStatus(response));
} }
} }
@@ -442,34 +379,18 @@ public class SecurityTokenHelper {
currentKeyObject = crtSecretKey.getModulus().toByteArray(); currentKeyObject = crtSecretKey.getModulus().toByteArray();
System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256); System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
String putKeyCommand = "10DB3FFF";
String lastPutKeyCommand = "00DB3FFF";
// Now we're ready to communicate with the token. // Now we're ready to communicate with the token.
offset = 0;
String response;
while (offset < dataToSend.length) {
int dataRemaining = dataToSend.length - offset;
if (dataRemaining > 254) {
response = communicate(
putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
);
offset += 254;
} else {
int length = dataToSend.length - offset;
response = communicate(
lastPutKeyCommand + String.format("%02x", length)
+ Hex.toHexString(dataToSend, offset, length));
offset += length;
}
if (!response.endsWith("9000")) { CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, dataToSend);
throw new CardException("Key export to Security Token failed", parseCardStatus(response));
}
}
// Clear array with secret data before we return. // Clear array with secret data before we return.
Arrays.fill(dataToSend, (byte) 0); Arrays.fill(dataToSend, (byte) 0);
ResponseAPDU response = communicate(apdu);
if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Key export to Security Token failed", response.getSW());
}
} }
/** /**
@@ -479,25 +400,29 @@ public class SecurityTokenHelper {
* @return The fingerprints of all subkeys in a contiguous byte array. * @return The fingerprints of all subkeys in a contiguous byte array.
*/ */
public byte[] getFingerprints() throws IOException { public byte[] getFingerprints() throws IOException {
String data = "00CA006E00"; CommandAPDU apdu = new CommandAPDU(0x00, 0xCA, 0x00, 0x6E);
byte[] buf = mTransport.transceive(Hex.decode(data)); ResponseAPDU response = communicate(apdu);
Iso7816TLV[] tlvs = Iso7816TLV.readList(buf, true); if (response.getSW() != APDU_SW_SUCCESS) {
Iso7816TLV fptlv = null; throw new CardException("Failed to get fingerprints", response.getSW());
}
for (int i = 0; i < tlvs.length; i++) { Iso7816TLV[] tlvList = Iso7816TLV.readList(response.getData(), true);
Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlvs[i].prettyPrint()); Iso7816TLV fingerPrintTlv = null;
Iso7816TLV tlv = Iso7816TLV.findRecursive(tlvs[i], 0xc5); for (Iso7816TLV tlv : tlvList) {
if (tlv != null) { Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
fptlv = tlv;
Iso7816TLV matchingTlv = Iso7816TLV.findRecursive(tlv, 0xc5);
if (matchingTlv != null) {
fingerPrintTlv = matchingTlv;
} }
} }
if (fptlv == null) { if (fingerPrintTlv == null) {
return null; return null;
} }
return fptlv.mV; return fingerPrintTlv.mV;
} }
/** /**
@@ -506,18 +431,23 @@ public class SecurityTokenHelper {
* @return Seven bytes in fixed format, plus 0x9000 status word at the end. * @return Seven bytes in fixed format, plus 0x9000 status word at the end.
*/ */
private byte[] getPwStatusBytes() throws IOException { private byte[] getPwStatusBytes() throws IOException {
String data = "00CA00C400"; return getData(0x00, 0xC4);
return mTransport.transceive(Hex.decode(data));
} }
public byte[] getAid() throws IOException { public byte[] getAid() throws IOException {
String info = "00CA004F00"; return getData(0x00, 0x4F);
return mTransport.transceive(Hex.decode(info));
} }
public String getUserId() throws IOException { public String getUserId() throws IOException {
String info = "00CA006500"; return getHolderName(getData(0x00, 0x65));
return getHolderName(communicate(info)); }
private byte[] getData(int p1, int p2) throws IOException {
ResponseAPDU response = communicate(new CommandAPDU(0x00, 0xCA, p1, p2));
if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Failed to get pw status bytes", response.getSW());
}
return response.getData();
} }
/** /**
@@ -532,7 +462,7 @@ public class SecurityTokenHelper {
} }
// dsi, including Lc // dsi, including Lc
String dsi; byte[] dsi;
Log.i(Constants.TAG, "Hash: " + hashAlgo); Log.i(Constants.TAG, "Hash: " + hashAlgo);
switch (hashAlgo) { switch (hashAlgo) {
@@ -540,86 +470,65 @@ public class SecurityTokenHelper {
if (hash.length != 20) { if (hash.length != 20) {
throw new IOException("Bad hash length (" + hash.length + ", expected 10!"); throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
} }
dsi = "23" // Lc dsi = Arrays.concatenate(Hex.decode(
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
+ "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes + "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
+ "0605" + "2B0E03021A" // OID of SHA1 + "0605" + "2B0E03021A" // OID of SHA1
+ "0500" // TLV coding of ZERO + "0500" // TLV coding of ZERO
+ "0414" + getHex(hash); // 0x14 are 20 hash bytes + "0414"), hash); // 0x14 are 20 hash bytes
break; break;
case HashAlgorithmTags.RIPEMD160: case HashAlgorithmTags.RIPEMD160:
if (hash.length != 20) { if (hash.length != 20) {
throw new IOException("Bad hash length (" + hash.length + ", expected 20!"); throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
} }
dsi = "233021300906052B2403020105000414" + getHex(hash); dsi = Arrays.concatenate(Hex.decode("3021300906052B2403020105000414"), hash);
break; break;
case HashAlgorithmTags.SHA224: case HashAlgorithmTags.SHA224:
if (hash.length != 28) { if (hash.length != 28) {
throw new IOException("Bad hash length (" + hash.length + ", expected 28!"); throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
} }
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash); dsi = Arrays.concatenate(Hex.decode("302D300D06096086480165030402040500041C"), hash);
break; break;
case HashAlgorithmTags.SHA256: case HashAlgorithmTags.SHA256:
if (hash.length != 32) { if (hash.length != 32) {
throw new IOException("Bad hash length (" + hash.length + ", expected 32!"); throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
} }
dsi = "333031300D060960864801650304020105000420" + getHex(hash); dsi = Arrays.concatenate(Hex.decode("3031300D060960864801650304020105000420"), hash);
break; break;
case HashAlgorithmTags.SHA384: case HashAlgorithmTags.SHA384:
if (hash.length != 48) { if (hash.length != 48) {
throw new IOException("Bad hash length (" + hash.length + ", expected 48!"); throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
} }
dsi = "433041300D060960864801650304020205000430" + getHex(hash); dsi = Arrays.concatenate(Hex.decode("3041300D060960864801650304020205000430"), hash);
break; break;
case HashAlgorithmTags.SHA512: case HashAlgorithmTags.SHA512:
if (hash.length != 64) { if (hash.length != 64) {
throw new IOException("Bad hash length (" + hash.length + ", expected 64!"); throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
} }
dsi = "533051300D060960864801650304020305000440" + getHex(hash); dsi = Arrays.concatenate(Hex.decode("3051300D060960864801650304020305000440"), hash);
break; break;
default: default:
throw new IOException("Not supported hash algo!"); throw new IOException("Not supported hash algo!");
} }
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37) // Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
String apdu = CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, dsi, MAX_APDU_NE_EXT);
"002A9E9A" // CLA, INS, P1, P2 ResponseAPDU response = communicate(command);
+ "0000"
+ dsi // digital signature input
+ "0000"; // Le
String response = communicate(apdu); if (response.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Failed to sign", response.getSW());
if (response.length() < 4) {
throw new CardException("Bad response", (short) 0);
} }
// split up response into signature and status
String status = response.substring(response.length() - 4);
String signature = response.substring(0, response.length() - 4);
// while we are getting 0x61 status codes, retrieve more data
while (status.substring(0, 2).equals("61")) {
Log.d(Constants.TAG, "requesting more data, status " + status);
// Send GET RESPONSE command
response = communicate("00C00000" + status.substring(2));
status = response.substring(response.length() - 4);
signature += response.substring(0, response.length() - 4);
}
Log.d(Constants.TAG, "final response:" + status);
if (!mPw1ValidForMultipleSignatures) { if (!mPw1ValidForMultipleSignatures) {
mPw1ValidatedForSignature = false; mPw1ValidatedForSignature = false;
} }
if (!"9000".equals(status)) { byte[] signature = response.getData();
throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
}
// Make sure the signature we received is actually the expected number of bytes long! // Make sure the signature we received is actually the expected number of bytes long!
if (signature.length() != 256 && signature.length() != 512 if (signature.length != 128 && signature.length != 256
&& signature.length() != 768 && signature.length() != 1024) { && signature.length != 384 && signature.length != 512) {
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length() / 2); throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
} }
return Hex.decode(signature); return Hex.decode(signature);
@@ -628,8 +537,8 @@ public class SecurityTokenHelper {
/** /**
* Transceive data via NFC encoded as Hex * Transceive data via NFC encoded as Hex
*/ */
private String communicate(String apdu) throws IOException { private ResponseAPDU communicate(CommandAPDU apdu) throws IOException {
return getHex(mTransport.transceive(Hex.decode(apdu))); return mTransport.transceive(apdu);
} }
public Transport getTransport() { public Transport getTransport() {
@@ -645,9 +554,8 @@ public class SecurityTokenHelper {
try { try {
// By trying to select any apps that have the Fidesmo AID prefix we can // By trying to select any apps that have the Fidesmo AID prefix we can
// see if it is a Fidesmo device or not // see if it is a Fidesmo device or not
byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX)); CommandAPDU apdu = new CommandAPDU(0x00, 0xA4, 0x04, 0x00, Hex.decode(FIDESMO_APPS_AID_PREFIX));
// Compare the status returned by our select with the OK status code return communicate(apdu).getSW() == APDU_SW_SUCCESS;
return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU);
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "Card communication failed!", e); Log.e(Constants.TAG, "Card communication failed!", e);
} }
@@ -678,38 +586,19 @@ public class SecurityTokenHelper {
verifyPin(0x83); // (Verify PW3 with mode 83) verifyPin(0x83); // (Verify PW3 with mode 83)
} }
String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; CommandAPDU apdu = new CommandAPDU(0x00, 0x47, 0x80, 0x00, new byte[]{(byte) slot, 0x00}, MAX_APDU_NE_EXT);
String getResponseApdu = "00C00000"; ResponseAPDU response = communicate(apdu);
String first = communicate(generateKeyApdu); if (response.getSW() != APDU_SW_SUCCESS) {
String second = communicate(getResponseApdu);
if (!second.endsWith("9000")) {
throw new IOException("On-card key generation failed"); throw new IOException("On-card key generation failed");
} }
String publicKeyData = getDataField(first) + getDataField(second); return response.getData();
Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData);
return Hex.decode(publicKeyData);
} }
private String getDataField(String output) { private ResponseAPDU tryPin(int mode, byte[] pin) throws IOException {
return output.substring(0, output.length() - 4);
}
private String tryPin(int mode, byte[] pin) throws IOException {
// Command APDU for VERIFY command (page 32) // Command APDU for VERIFY command (page 32)
String login = return communicate(new CommandAPDU(0x00, 0x20, 0x00, mode, pin));
"00" // CLA
+ "20" // INS
+ "00" // P1
+ String.format("%02x", mode) // P2
+ String.format("%02x", pin.length) // Lc
+ Hex.toHexString(pin);
return communicate(login);
} }
/** /**
@@ -723,30 +612,33 @@ public class SecurityTokenHelper {
// try wrong PIN 4 times until counter goes to C0 // try wrong PIN 4 times until counter goes to C0
byte[] pin = "XXXXXX".getBytes(); byte[] pin = "XXXXXX".getBytes();
for (int i = 0; i <= 4; i++) { for (int i = 0; i <= 4; i++) {
String response = tryPin(0x81, pin); ResponseAPDU response = tryPin(0x81, pin);
if (response.equals(accepted)) { // Should NOT accept! if (response.getSW() != APDU_SW_SUCCESS) { // Should NOT accept!
throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); throw new CardException("Should never happen, XXXXXX has been accepted!", response.getSW());
} }
} }
// try wrong Admin PIN 4 times until counter goes to C0 // try wrong Admin PIN 4 times until counter goes to C0
byte[] adminPin = "XXXXXXXX".getBytes(); byte[] adminPin = "XXXXXXXX".getBytes();
for (int i = 0; i <= 4; i++) { for (int i = 0; i <= 4; i++) {
String response = tryPin(0x83, adminPin); ResponseAPDU response = tryPin(0x83, adminPin);
if (response.equals(accepted)) { // Should NOT accept! if (response.getSW() != APDU_SW_SUCCESS) { // Should NOT accept!
throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); throw new CardException("Should never happen, XXXXXXXX has been accepted", response.getSW());
} }
} }
// reactivate token! // reactivate token!
String reactivate1 = "00" + "e6" + "00" + "00";
String reactivate2 = "00" + "44" + "00" + "00";
String response1 = communicate(reactivate1);
String response2 = communicate(reactivate2);
if (!response1.equals(accepted) || !response2.equals(accepted)) {
throw new CardException("Reactivating failed!", parseCardStatus(response1));
}
CommandAPDU reactivate1 = new CommandAPDU(0x00, 0xE6, 0x00, 0x00);
CommandAPDU reactivate2 = new CommandAPDU(0x00, 0x44, 0x00, 0x00);
ResponseAPDU response1 = communicate(reactivate1);
ResponseAPDU response2 = communicate(reactivate2);
if (response1.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Reactivating failed!", response1.getSW());
}
if (response2.getSW() != APDU_SW_SUCCESS) {
throw new CardException("Reactivating failed!", response2.getSW());
}
} }
/** /**

View File

@@ -17,6 +17,9 @@
package org.sufficientlysecure.keychain.securitytoken; package org.sufficientlysecure.keychain.securitytoken;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.CommandAPDU;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.ResponseAPDU;
import java.io.IOException; import java.io.IOException;
/** /**
@@ -29,7 +32,7 @@ public interface Transport {
* @return received data * @return received data
* @throws IOException * @throws IOException
*/ */
byte[] transceive(byte[] data) throws IOException; ResponseAPDU transceive(CommandAPDU data) throws IOException;
/** /**
* Disconnect and release connection * Disconnect and release connection

View File

@@ -29,8 +29,9 @@ import android.util.Pair;
import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.securitytoken.Transport;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.CommandAPDU;
import org.sufficientlysecure.keychain.securitytoken.smartcardio.ResponseAPDU;
import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1TpduProtocol; import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1TpduProtocol;
import org.sufficientlysecure.keychain.util.Iso7816TLV;
import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException; import java.io.IOException;
@@ -230,8 +231,8 @@ public class UsbTransport implements Transport {
* @throws UsbTransportException * @throws UsbTransportException
*/ */
@Override @Override
public byte[] transceive(byte[] data) throws UsbTransportException { public ResponseAPDU transceive(CommandAPDU data) throws UsbTransportException {
return mProtocol.transceive(data); return new ResponseAPDU(mProtocol.transceive(data.getBytes()));
} }
@Override @Override