|
|
|
|
@@ -30,6 +30,8 @@ import org.bouncycastle.util.encoders.Hex;
|
|
|
|
|
import org.sufficientlysecure.keychain.Constants;
|
|
|
|
|
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
|
|
|
|
|
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.Log;
|
|
|
|
|
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
|
|
|
|
|
*/
|
|
|
|
|
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
|
|
|
|
|
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
|
|
|
|
|
|
|
|
|
|
@@ -72,16 +81,9 @@ public class SecurityTokenHelper {
|
|
|
|
|
return new String(Hex.encode(raw));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String getHolderName(String name) {
|
|
|
|
|
private String getHolderName(byte[] name) {
|
|
|
|
|
try {
|
|
|
|
|
String slength;
|
|
|
|
|
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;
|
|
|
|
|
return (new String(name, 4, name[3])).replace('<', ' ');
|
|
|
|
|
} catch (IndexOutOfBoundsException e) {
|
|
|
|
|
// try-catch for https://github.com/FluffyKaon/OpenPGP-Card
|
|
|
|
|
// Note: This should not happen, but happens with
|
|
|
|
|
@@ -153,23 +155,12 @@ public class SecurityTokenHelper {
|
|
|
|
|
mTransport.connect();
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
String opening =
|
|
|
|
|
"00" // CLA
|
|
|
|
|
+ "A4" // INS
|
|
|
|
|
+ "04" // P1
|
|
|
|
|
+ "00" // P2
|
|
|
|
|
+ "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));
|
|
|
|
|
CommandAPDU select = new CommandAPDU(0x00, 0xA4, 0x04, 0x00, Hex.decode("D27600012401"));
|
|
|
|
|
ResponseAPDU response = communicate(select); // activate connection
|
|
|
|
|
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Initialization failed!", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
byte[] pwStatusBytes = getPwStatusBytes();
|
|
|
|
|
@@ -179,24 +170,6 @@ public class SecurityTokenHelper {
|
|
|
|
|
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
|
|
|
|
|
* 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)
|
|
|
|
|
String changeReferenceDataApdu = "00" // CLA
|
|
|
|
|
+ "24" // INS
|
|
|
|
|
+ "00" // P1
|
|
|
|
|
+ String.format("%02x", pw) // P2
|
|
|
|
|
+ String.format("%02x", pin.length + newPin.length) // Lc
|
|
|
|
|
+ getHex(pin)
|
|
|
|
|
+ getHex(newPin);
|
|
|
|
|
String response = communicate(changeReferenceDataApdu); // change PIN
|
|
|
|
|
if (!response.equals("9000")) {
|
|
|
|
|
throw new CardException("Failed to change PIN", parseCardStatus(response));
|
|
|
|
|
CommandAPDU changePin = new CommandAPDU(0x00, 0x24, 0x00, pw, Arrays.concatenate(pin, newPin));
|
|
|
|
|
ResponseAPDU response = communicate(changePin);
|
|
|
|
|
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Failed to change PIN", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -254,43 +222,20 @@ public class SecurityTokenHelper {
|
|
|
|
|
verifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int offset = 2; // Skip first byte TODO: why?
|
|
|
|
|
String response = "", status = "";
|
|
|
|
|
boolean shouldPad = true;
|
|
|
|
|
|
|
|
|
|
// Transmit
|
|
|
|
|
while (offset < encryptedSessionKey.length) {
|
|
|
|
|
boolean isLastCommand = MAX_APDU_DATAFIELD_SIZE >= encryptedSessionKey.length - offset;
|
|
|
|
|
String cla = isLastCommand ? "00" : "10";
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
byte[] data = Arrays.copyOfRange(encryptedSessionKey, 2, encryptedSessionKey.length);
|
|
|
|
|
if (data[0] != 0) {
|
|
|
|
|
data = Arrays.prepend(data, (byte) 0x00);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Receive
|
|
|
|
|
String result = getDataField(response);
|
|
|
|
|
while (response.endsWith("61")) {
|
|
|
|
|
response = communicate("00C00000" + status.substring(2));
|
|
|
|
|
status = response.substring(response.length() - 4);
|
|
|
|
|
result += getDataField(response);
|
|
|
|
|
}
|
|
|
|
|
if (!status.equals("9000")) {
|
|
|
|
|
throw new CardException("Deciphering with Security token failed on receive", parseCardStatus(response));
|
|
|
|
|
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x80, 0x86, data, MAX_APDU_NE_EXT);
|
|
|
|
|
ResponseAPDU response = communicate(command);
|
|
|
|
|
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Deciphering with Security token failed on receive", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Hex.decode(result);
|
|
|
|
|
return response.getData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -309,12 +254,9 @@ public class SecurityTokenHelper {
|
|
|
|
|
pin = mPin.toStringUnsafe().getBytes();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SW1/2 0x9000 is the generic "ok" response, which we expect most of the time.
|
|
|
|
|
// See specification, page 51
|
|
|
|
|
String accepted = "9000";
|
|
|
|
|
String response = tryPin(mode, pin); // login
|
|
|
|
|
if (!response.equals(accepted)) {
|
|
|
|
|
throw new CardException("Bad PIN!", parseCardStatus(response));
|
|
|
|
|
ResponseAPDU response = tryPin(mode, pin);// login
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Bad PIN!", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (mode == 0x81) {
|
|
|
|
|
@@ -347,16 +289,11 @@ public class SecurityTokenHelper {
|
|
|
|
|
verifyPin(0x83); // (Verify PW3)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String putDataApdu = "00" // CLA
|
|
|
|
|
+ "DA" // INS
|
|
|
|
|
+ String.format("%02x", (dataObject & 0xFF00) >> 8) // P1
|
|
|
|
|
+ String.format("%02x", dataObject & 0xFF) // P2
|
|
|
|
|
+ String.format("%02x", data.length) // Lc
|
|
|
|
|
+ getHex(data);
|
|
|
|
|
CommandAPDU command = new CommandAPDU(0x00, 0xDA, (dataObject & 0xFF00) >> 8, dataObject & 0xFF, data);
|
|
|
|
|
ResponseAPDU response = communicate(command); // put data
|
|
|
|
|
|
|
|
|
|
String response = communicate(putDataApdu); // put data
|
|
|
|
|
if (!response.equals("9000")) {
|
|
|
|
|
throw new CardException("Failed to put data.", parseCardStatus(response));
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Failed to put data.", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -442,34 +379,18 @@ public class SecurityTokenHelper {
|
|
|
|
|
currentKeyObject = crtSecretKey.getModulus().toByteArray();
|
|
|
|
|
System.arraycopy(currentKeyObject, currentKeyObject.length - 256, dataToSend, offset, 256);
|
|
|
|
|
|
|
|
|
|
String putKeyCommand = "10DB3FFF";
|
|
|
|
|
String lastPutKeyCommand = "00DB3FFF";
|
|
|
|
|
|
|
|
|
|
// 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")) {
|
|
|
|
|
throw new CardException("Key export to Security Token failed", parseCardStatus(response));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CommandAPDU apdu = new CommandAPDU(0x00, 0xDB, 0x3F, 0xFF, dataToSend);
|
|
|
|
|
|
|
|
|
|
// Clear array with secret data before we return.
|
|
|
|
|
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.
|
|
|
|
|
*/
|
|
|
|
|
public byte[] getFingerprints() throws IOException {
|
|
|
|
|
String data = "00CA006E00";
|
|
|
|
|
byte[] buf = mTransport.transceive(Hex.decode(data));
|
|
|
|
|
CommandAPDU apdu = new CommandAPDU(0x00, 0xCA, 0x00, 0x6E);
|
|
|
|
|
ResponseAPDU response = communicate(apdu);
|
|
|
|
|
|
|
|
|
|
Iso7816TLV[] tlvs = Iso7816TLV.readList(buf, true);
|
|
|
|
|
Iso7816TLV fptlv = null;
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Failed to get fingerprints", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < tlvs.length; i++) {
|
|
|
|
|
Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlvs[i].prettyPrint());
|
|
|
|
|
Iso7816TLV[] tlvList = Iso7816TLV.readList(response.getData(), true);
|
|
|
|
|
Iso7816TLV fingerPrintTlv = null;
|
|
|
|
|
|
|
|
|
|
Iso7816TLV tlv = Iso7816TLV.findRecursive(tlvs[i], 0xc5);
|
|
|
|
|
if (tlv != null) {
|
|
|
|
|
fptlv = tlv;
|
|
|
|
|
for (Iso7816TLV tlv : tlvList) {
|
|
|
|
|
Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
|
|
|
|
|
|
|
|
|
|
Iso7816TLV matchingTlv = Iso7816TLV.findRecursive(tlv, 0xc5);
|
|
|
|
|
if (matchingTlv != null) {
|
|
|
|
|
fingerPrintTlv = matchingTlv;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fptlv == null) {
|
|
|
|
|
if (fingerPrintTlv == 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.
|
|
|
|
|
*/
|
|
|
|
|
private byte[] getPwStatusBytes() throws IOException {
|
|
|
|
|
String data = "00CA00C400";
|
|
|
|
|
return mTransport.transceive(Hex.decode(data));
|
|
|
|
|
return getData(0x00, 0xC4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] getAid() throws IOException {
|
|
|
|
|
String info = "00CA004F00";
|
|
|
|
|
return mTransport.transceive(Hex.decode(info));
|
|
|
|
|
return getData(0x00, 0x4F);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getUserId() throws IOException {
|
|
|
|
|
String info = "00CA006500";
|
|
|
|
|
return getHolderName(communicate(info));
|
|
|
|
|
return getHolderName(getData(0x00, 0x65));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
String dsi;
|
|
|
|
|
byte[] dsi;
|
|
|
|
|
|
|
|
|
|
Log.i(Constants.TAG, "Hash: " + hashAlgo);
|
|
|
|
|
switch (hashAlgo) {
|
|
|
|
|
@@ -540,86 +470,65 @@ public class SecurityTokenHelper {
|
|
|
|
|
if (hash.length != 20) {
|
|
|
|
|
throw new IOException("Bad hash length (" + hash.length + ", expected 10!");
|
|
|
|
|
}
|
|
|
|
|
dsi = "23" // Lc
|
|
|
|
|
+ "3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
|
|
|
|
|
dsi = Arrays.concatenate(Hex.decode(
|
|
|
|
|
"3021" // Tag/Length of Sequence, the 0x21 includes all following 33 bytes
|
|
|
|
|
+ "3009" // Tag/Length of Sequence, the 0x09 are the following header bytes
|
|
|
|
|
+ "0605" + "2B0E03021A" // OID of SHA1
|
|
|
|
|
+ "0500" // TLV coding of ZERO
|
|
|
|
|
+ "0414" + getHex(hash); // 0x14 are 20 hash bytes
|
|
|
|
|
+ "0414"), hash); // 0x14 are 20 hash bytes
|
|
|
|
|
break;
|
|
|
|
|
case HashAlgorithmTags.RIPEMD160:
|
|
|
|
|
if (hash.length != 20) {
|
|
|
|
|
throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
|
|
|
|
|
}
|
|
|
|
|
dsi = "233021300906052B2403020105000414" + getHex(hash);
|
|
|
|
|
dsi = Arrays.concatenate(Hex.decode("3021300906052B2403020105000414"), hash);
|
|
|
|
|
break;
|
|
|
|
|
case HashAlgorithmTags.SHA224:
|
|
|
|
|
if (hash.length != 28) {
|
|
|
|
|
throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
|
|
|
|
|
}
|
|
|
|
|
dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
|
|
|
|
|
dsi = Arrays.concatenate(Hex.decode("302D300D06096086480165030402040500041C"), hash);
|
|
|
|
|
break;
|
|
|
|
|
case HashAlgorithmTags.SHA256:
|
|
|
|
|
if (hash.length != 32) {
|
|
|
|
|
throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
|
|
|
|
|
}
|
|
|
|
|
dsi = "333031300D060960864801650304020105000420" + getHex(hash);
|
|
|
|
|
dsi = Arrays.concatenate(Hex.decode("3031300D060960864801650304020105000420"), hash);
|
|
|
|
|
break;
|
|
|
|
|
case HashAlgorithmTags.SHA384:
|
|
|
|
|
if (hash.length != 48) {
|
|
|
|
|
throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
|
|
|
|
|
}
|
|
|
|
|
dsi = "433041300D060960864801650304020205000430" + getHex(hash);
|
|
|
|
|
dsi = Arrays.concatenate(Hex.decode("3041300D060960864801650304020205000430"), hash);
|
|
|
|
|
break;
|
|
|
|
|
case HashAlgorithmTags.SHA512:
|
|
|
|
|
if (hash.length != 64) {
|
|
|
|
|
throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
|
|
|
|
|
}
|
|
|
|
|
dsi = "533051300D060960864801650304020305000440" + getHex(hash);
|
|
|
|
|
dsi = Arrays.concatenate(Hex.decode("3051300D060960864801650304020305000440"), hash);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
throw new IOException("Not supported hash algo!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Command APDU for PERFORM SECURITY OPERATION: COMPUTE DIGITAL SIGNATURE (page 37)
|
|
|
|
|
String apdu =
|
|
|
|
|
"002A9E9A" // CLA, INS, P1, P2
|
|
|
|
|
+ "0000"
|
|
|
|
|
+ dsi // digital signature input
|
|
|
|
|
+ "0000"; // Le
|
|
|
|
|
CommandAPDU command = new CommandAPDU(0x00, 0x2A, 0x9E, 0x9A, dsi, MAX_APDU_NE_EXT);
|
|
|
|
|
ResponseAPDU response = communicate(command);
|
|
|
|
|
|
|
|
|
|
String response = communicate(apdu);
|
|
|
|
|
|
|
|
|
|
if (response.length() < 4) {
|
|
|
|
|
throw new CardException("Bad response", (short) 0);
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new CardException("Failed to sign", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
// 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) {
|
|
|
|
|
mPw1ValidatedForSignature = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!"9000".equals(status)) {
|
|
|
|
|
throw new CardException("Bad NFC response code: " + status, parseCardStatus(response));
|
|
|
|
|
}
|
|
|
|
|
byte[] signature = response.getData();
|
|
|
|
|
|
|
|
|
|
// Make sure the signature we received is actually the expected number of bytes long!
|
|
|
|
|
if (signature.length() != 256 && signature.length() != 512
|
|
|
|
|
&& signature.length() != 768 && signature.length() != 1024) {
|
|
|
|
|
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length() / 2);
|
|
|
|
|
if (signature.length != 128 && signature.length != 256
|
|
|
|
|
&& signature.length != 384 && signature.length != 512) {
|
|
|
|
|
throw new IOException("Bad signature length! Expected 128/256/384/512 bytes, got " + signature.length);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Hex.decode(signature);
|
|
|
|
|
@@ -628,8 +537,8 @@ public class SecurityTokenHelper {
|
|
|
|
|
/**
|
|
|
|
|
* Transceive data via NFC encoded as Hex
|
|
|
|
|
*/
|
|
|
|
|
private String communicate(String apdu) throws IOException {
|
|
|
|
|
return getHex(mTransport.transceive(Hex.decode(apdu)));
|
|
|
|
|
private ResponseAPDU communicate(CommandAPDU apdu) throws IOException {
|
|
|
|
|
return mTransport.transceive(apdu);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Transport getTransport() {
|
|
|
|
|
@@ -645,9 +554,8 @@ public class SecurityTokenHelper {
|
|
|
|
|
try {
|
|
|
|
|
// By trying to select any apps that have the Fidesmo AID prefix we can
|
|
|
|
|
// see if it is a Fidesmo device or not
|
|
|
|
|
byte[] mSelectResponse = mTransport.transceive(Apdu.select(FIDESMO_APPS_AID_PREFIX));
|
|
|
|
|
// Compare the status returned by our select with the OK status code
|
|
|
|
|
return Apdu.hasStatus(mSelectResponse, Apdu.OK_APDU);
|
|
|
|
|
CommandAPDU apdu = new CommandAPDU(0x00, 0xA4, 0x04, 0x00, Hex.decode(FIDESMO_APPS_AID_PREFIX));
|
|
|
|
|
return communicate(apdu).getSW() == APDU_SW_SUCCESS;
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
Log.e(Constants.TAG, "Card communication failed!", e);
|
|
|
|
|
}
|
|
|
|
|
@@ -678,38 +586,19 @@ public class SecurityTokenHelper {
|
|
|
|
|
verifyPin(0x83); // (Verify PW3 with mode 83)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000";
|
|
|
|
|
String getResponseApdu = "00C00000";
|
|
|
|
|
CommandAPDU apdu = new CommandAPDU(0x00, 0x47, 0x80, 0x00, new byte[]{(byte) slot, 0x00}, MAX_APDU_NE_EXT);
|
|
|
|
|
ResponseAPDU response = communicate(apdu);
|
|
|
|
|
|
|
|
|
|
String first = communicate(generateKeyApdu);
|
|
|
|
|
String second = communicate(getResponseApdu);
|
|
|
|
|
|
|
|
|
|
if (!second.endsWith("9000")) {
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) {
|
|
|
|
|
throw new IOException("On-card key generation failed");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String publicKeyData = getDataField(first) + getDataField(second);
|
|
|
|
|
|
|
|
|
|
Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData);
|
|
|
|
|
|
|
|
|
|
return Hex.decode(publicKeyData);
|
|
|
|
|
return response.getData();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String getDataField(String output) {
|
|
|
|
|
return output.substring(0, output.length() - 4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private String tryPin(int mode, byte[] pin) throws IOException {
|
|
|
|
|
private ResponseAPDU tryPin(int mode, byte[] pin) throws IOException {
|
|
|
|
|
// Command APDU for VERIFY command (page 32)
|
|
|
|
|
String login =
|
|
|
|
|
"00" // CLA
|
|
|
|
|
+ "20" // INS
|
|
|
|
|
+ "00" // P1
|
|
|
|
|
+ String.format("%02x", mode) // P2
|
|
|
|
|
+ String.format("%02x", pin.length) // Lc
|
|
|
|
|
+ Hex.toHexString(pin);
|
|
|
|
|
|
|
|
|
|
return communicate(login);
|
|
|
|
|
return communicate(new CommandAPDU(0x00, 0x20, 0x00, mode, pin));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -723,30 +612,33 @@ public class SecurityTokenHelper {
|
|
|
|
|
// try wrong PIN 4 times until counter goes to C0
|
|
|
|
|
byte[] pin = "XXXXXX".getBytes();
|
|
|
|
|
for (int i = 0; i <= 4; i++) {
|
|
|
|
|
String response = tryPin(0x81, pin);
|
|
|
|
|
if (response.equals(accepted)) { // Should NOT accept!
|
|
|
|
|
throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response));
|
|
|
|
|
ResponseAPDU response = tryPin(0x81, pin);
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) { // Should NOT accept!
|
|
|
|
|
throw new CardException("Should never happen, XXXXXX has been accepted!", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// try wrong Admin PIN 4 times until counter goes to C0
|
|
|
|
|
byte[] adminPin = "XXXXXXXX".getBytes();
|
|
|
|
|
for (int i = 0; i <= 4; i++) {
|
|
|
|
|
String response = tryPin(0x83, adminPin);
|
|
|
|
|
if (response.equals(accepted)) { // Should NOT accept!
|
|
|
|
|
throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response));
|
|
|
|
|
ResponseAPDU response = tryPin(0x83, adminPin);
|
|
|
|
|
if (response.getSW() != APDU_SW_SUCCESS) { // Should NOT accept!
|
|
|
|
|
throw new CardException("Should never happen, XXXXXXXX has been accepted", response.getSW());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|