aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikita Mikhailov <nikita.s.mikhailov@gmail.com>2016-04-07 01:22:24 +0600
committerNikita Mikhailov <nikita.s.mikhailov@gmail.com>2016-04-14 22:48:01 +0600
commit3798249570e97861793f5d0ebc695d94e8d5ddcd (patch)
treeda42628bb2045450efc6218822c2c7ea2827e304
parent5e18b15775f4c6d9c563d61a71143320620e968e (diff)
downloadopen-keychain-3798249570e97861793f5d0ebc695d94e8d5ddcd.zip
open-keychain-3798249570e97861793f5d0ebc695d94e8d5ddcd.tar.gz
open-keychain-3798249570e97861793f5d0ebc695d94e8d5ddcd.tar.bz2
OTG: Implement hidden activity usb detection technique
-rw-r--r--OpenKeychain/src/main/AndroidManifest.xml18
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java712
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java105
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java118
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java42
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java19
-rw-r--r--OpenKeychain/src/main/res/xml/usb_device_filter.xml25
8 files changed, 108 insertions, 933 deletions
diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml
index f485e96..c6795d9 100644
--- a/OpenKeychain/src/main/AndroidManifest.xml
+++ b/OpenKeychain/src/main/AndroidManifest.xml
@@ -877,6 +877,24 @@
android:configChanges="orientation|screenSize|keyboardHidden|keyboard"
android:label="@string/title_import_keys" />
+ <!-- Usb interceptor activity -->
+ <activity
+ android:name=".ui.UsbEventReceiverActivity"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.Keychain.Transparent"
+ android:noHistory="true"
+ android:excludeFromRecents="true"
+ android:taskAffinity="com.example.taskAffinityUsbEventReceiver"
+ android:process=":UsbEventReceiverActivityProcess"
+ android:exported="false">
+
+ <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
+ android:resource="@xml/usb_device_filter" />
+ <intent-filter>
+ <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
+ </intent-filter>
+ </activity>
+
<!-- DEPRECATED service,
using this service may lead to truncated data being returned to the caller -->
<service
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java
deleted file mode 100644
index 796b4e1..0000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java
+++ /dev/null
@@ -1,712 +0,0 @@
-package org.sufficientlysecure.keychain.javacard;
-
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.util.Arrays;
-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.util.Iso7816TLV;
-import org.sufficientlysecure.keychain.util.Log;
-import org.sufficientlysecure.keychain.util.Passphrase;
-
-import java.io.IOException;
-import java.math.BigInteger;
-import java.nio.ByteBuffer;
-import java.security.interfaces.RSAPrivateCrtKey;
-
-import nordpol.Apdu;
-
-public class BaseJavacardDevice implements JavacardDevice {
- // Fidesmo constants
- private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
-
- private static final byte[] BLANK_FINGERPRINT = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
- private Transport mTransport;
-
- private Passphrase mPin;
- private Passphrase mAdminPin;
- private boolean mPw1ValidForMultipleSignatures;
- private boolean mPw1ValidatedForSignature;
- private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
- private boolean mPw3Validated;
- private boolean mTagHandlingEnabled;
-
- public BaseJavacardDevice() {
- }
-
- private static String getHex(byte[] raw) {
- return new String(Hex.encode(raw));
- }
-
- public Passphrase getPin() {
- return mPin;
- }
-
- public void setPin(final Passphrase pin) {
- this.mPin = pin;
- }
-
- public Passphrase getAdminPin() {
- return mAdminPin;
- }
-
- public void setAdminPin(final Passphrase adminPin) {
- this.mAdminPin = adminPin;
- }
-
- public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException {
- long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000;
- byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array();
- KeyType keyType = KeyType.from(secretKey);
-
- if (keyType == null) {
- throw new IOException("Inappropriate key flags for smart card key.");
- }
-
- // Slot is empty, or contains this key already. PUT KEY operation is safe
- boolean canPutKey = !containsKey(keyType)
- || keyMatchesFingerPrint(keyType, secretKey.getFingerprint());
- if (!canPutKey) {
- throw new IOException(String.format("Key slot occupied; card must be reset to put new %s key.",
- keyType.toString()));
- }
-
- putKey(keyType.getmSlot(), secretKey, passphrase);
- putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint());
- putData(keyType.getTimestampObjectId(), timestampBytes);
- }
-
- public boolean containsKey(KeyType keyType) throws IOException {
- return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
- }
-
- public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
- return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint);
- }
-
- // METHOD UPDATED OK
- public void connectToDevice() throws IOException {
- // 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 = nfcCommunicate(opening); // activate connection
- if (!response.endsWith(accepted)) {
- throw new CardException("Initialization failed!", parseCardStatus(response));
- }
-
- byte[] pwStatusBytes = nfcGetPwStatusBytes();
- mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
- mPw1ValidatedForSignature = false;
- mPw1ValidatedForDecrypt = 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.
- */
- 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.
- *
- * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
- * @param newPin The new PW1 or PW3.
- */
- // METHOD UPDATED[OK]
- public void modifyPin(int pw, byte[] newPin) throws IOException {
- final int MAX_PW1_LENGTH_INDEX = 1;
- final int MAX_PW3_LENGTH_INDEX = 3;
-
- byte[] pwStatusBytes = nfcGetPwStatusBytes();
-
- 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");
- }
-
- byte[] pin;
- if (pw == 0x83) {
- pin = mAdminPin.toStringUnsafe().getBytes();
- } else {
- pin = mPin.toStringUnsafe().getBytes();
- }
-
- // 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 = nfcCommunicate(changeReferenceDataApdu); // change PIN
- if (!response.equals("9000")) {
- throw new CardException("Failed to change PIN", parseCardStatus(response));
- }
- }
-
- /**
- * Call DECIPHER command
- *
- * @param encryptedSessionKey the encoded session key
- * @return the decoded session key
- */
- // METHOD UPDATED [OK]
- public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException {
- if (!mPw1ValidatedForDecrypt) {
- nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption)
- }
-
- String firstApdu = "102a8086fe";
- String secondApdu = "002a808603";
- String le = "00";
-
- byte[] one = new byte[254];
- // leave out first byte:
- System.arraycopy(encryptedSessionKey, 1, one, 0, one.length);
-
- byte[] two = new byte[encryptedSessionKey.length - 1 - one.length];
- for (int i = 0; i < two.length; i++) {
- two[i] = encryptedSessionKey[i + one.length + 1];
- }
-
- nfcCommunicate(firstApdu + getHex(one));
- String second = nfcCommunicate(secondApdu + getHex(two) + le);
-
- String decryptedSessionKey = getDataField(second);
-
- return Hex.decode(decryptedSessionKey);
- }
-
- /**
- * 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.
- */
- // METHOD UPDATED [OK]
- public void nfcVerifyPin(int mode) throws IOException {
- if (mPin != null || mode == 0x83) {
-
- byte[] pin;
- if (mode == 0x83) {
- pin = mAdminPin.toStringUnsafe().getBytes();
- } else {
- 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 = nfcTryPin(mode, pin); // login
- if (!response.equals(accepted)) {
- throw new CardException("Bad PIN!", parseCardStatus(response));
- }
-
- if (mode == 0x81) {
- mPw1ValidatedForSignature = true;
- } else if (mode == 0x82) {
- mPw1ValidatedForDecrypt = true;
- } else if (mode == 0x83) {
- mPw3Validated = true;
- }
- }
- }
-
- /**
- * Stores a data object on the token. Automatically validates the proper PIN for the operation.
- * Supported for all data objects < 255 bytes in length. Only the cardholder certificate
- * (0x7F21) can exceed this length.
- *
- * @param dataObject The data object to be stored.
- * @param data The data to store in the object
- */
- // METHOD UPDATED [OK]
- public void putData(int dataObject, byte[] data) throws IOException {
- if (data.length > 254) {
- throw new IOException("Cannot PUT DATA with length > 254");
- }
- if (dataObject == 0x0101 || dataObject == 0x0103) {
- if (!mPw1ValidatedForDecrypt) {
- nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations)
- }
- } else if (!mPw3Validated) {
- nfcVerifyPin(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);
-
- String response = nfcCommunicate(putDataApdu); // put data
- if (!response.equals("9000")) {
- throw new CardException("Failed to put data.", parseCardStatus(response));
- }
- }
-
-
- /**
- * Puts a key on the token in the given slot.
- *
- * @param slot The slot on the token where the key should be stored:
- * 0xB6: Signature Key
- * 0xB8: Decipherment Key
- * 0xA4: Authentication Key
- */
- // METHOD UPDATED [OK]
- public void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase)
- throws IOException {
- if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
- throw new IOException("Invalid key slot");
- }
-
- RSAPrivateCrtKey crtSecretKey;
- try {
- secretKey.unlock(passphrase);
- crtSecretKey = secretKey.getCrtSecretKey();
- } catch (PgpGeneralException e) {
- throw new IOException(e.getMessage());
- }
-
- // Shouldn't happen; the UI should block the user from getting an incompatible key this far.
- if (crtSecretKey.getModulus().bitLength() > 2048) {
- throw new IOException("Key too large to export to Security Token.");
- }
-
- // Should happen only rarely; all GnuPG keys since 2006 use public exponent 65537.
- if (!crtSecretKey.getPublicExponent().equals(new BigInteger("65537"))) {
- throw new IOException("Invalid public exponent for smart Security Token.");
- }
-
- if (!mPw3Validated) {
- nfcVerifyPin(0x83); // (Verify PW3 with mode 83)
- }
-
- byte[] header = Hex.decode(
- "4D82" + "03A2" // Extended header list 4D82, length of 930 bytes. (page 23)
- + String.format("%02x", slot) + "00" // CRT to indicate targeted key, no length
- + "7F48" + "15" // Private key template 0x7F48, length 21 (decimal, 0x15 hex)
- + "9103" // Public modulus, length 3
- + "928180" // Prime P, length 128
- + "938180" // Prime Q, length 128
- + "948180" // Coefficient (1/q mod p), length 128
- + "958180" // Prime exponent P (d mod (p - 1)), length 128
- + "968180" // Prime exponent Q (d mod (1 - 1)), length 128
- + "97820100" // Modulus, length 256, last item in private key template
- + "5F48" + "820383");// DO 5F48; 899 bytes of concatenated key data will follow
- byte[] dataToSend = new byte[934];
- byte[] currentKeyObject;
- int offset = 0;
-
- System.arraycopy(header, 0, dataToSend, offset, header.length);
- offset += header.length;
- currentKeyObject = crtSecretKey.getPublicExponent().toByteArray();
- System.arraycopy(currentKeyObject, 0, dataToSend, offset, 3);
- offset += 3;
- // NOTE: For a 2048-bit key, these lengths are fixed. However, bigint includes a leading 0
- // in the array to represent sign, so we take care to set the offset to 1 if necessary.
- currentKeyObject = crtSecretKey.getPrimeP().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte) 0);
- offset += 128;
- currentKeyObject = crtSecretKey.getPrimeQ().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte) 0);
- offset += 128;
- currentKeyObject = crtSecretKey.getCrtCoefficient().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte) 0);
- offset += 128;
- currentKeyObject = crtSecretKey.getPrimeExponentP().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte) 0);
- offset += 128;
- currentKeyObject = crtSecretKey.getPrimeExponentQ().toByteArray();
- System.arraycopy(currentKeyObject, currentKeyObject.length - 128, dataToSend, offset, 128);
- Arrays.fill(currentKeyObject, (byte) 0);
- offset += 128;
- 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 = nfcCommunicate(
- putKeyCommand + "FE" + Hex.toHexString(dataToSend, offset, 254)
- );
- offset += 254;
- } else {
- int length = dataToSend.length - offset;
- response = nfcCommunicate(
- 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));
- }
- }
-
- // Clear array with secret data before we return.
- Arrays.fill(dataToSend, (byte) 0);
- }
-
- /**
- * Return fingerprints of all keys from application specific data stored
- * on tag, or null if data not available.
- *
- * @return The fingerprints of all subkeys in a contiguous byte array.
- */
- // METHOD UPDATED [OK]
- public byte[] getFingerprints() throws IOException {
- String data = "00CA006E00";
- byte[] buf = mTransport.sendAndReceive(Hex.decode(data));
-
- Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true);
- Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint());
-
- Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5);
- if (fptlv == null) {
- return null;
- }
- return fptlv.mV;
- }
-
- /**
- * Return the PW Status Bytes from the token. This is a simple DO; no TLV decoding needed.
- *
- * @return Seven bytes in fixed format, plus 0x9000 status word at the end.
- */
- // METHOD UPDATED [OK]
- public byte[] nfcGetPwStatusBytes() throws IOException {
- String data = "00CA00C400";
- return mTransport.sendAndReceive(Hex.decode(data));
- }
-
- // METHOD UPDATED [OK]
- public byte[] getAid() throws IOException {
- String info = "00CA004F00";
- return mTransport.sendAndReceive(Hex.decode(info));
- }
-
- // METHOD UPDATED [OK]
- public String getUserId() throws IOException {
- String info = "00CA006500";
- return nfcGetHolderName(nfcCommunicate(info));
- }
-
- /**
- * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value
- *
- * @param hash the hash for signing
- * @return a big integer representing the MPI for the given hash
- */
- // METHOD UPDATED [OK]
- public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException {
- if (!mPw1ValidatedForSignature) {
- nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing)
- }
-
- // dsi, including Lc
- String dsi;
-
- Log.i(Constants.TAG, "Hash: " + hashAlgo);
- switch (hashAlgo) {
- case HashAlgorithmTags.SHA1:
- 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
- + "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
- break;
- case HashAlgorithmTags.RIPEMD160:
- if (hash.length != 20) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 20!");
- }
- dsi = "233021300906052B2403020105000414" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA224:
- if (hash.length != 28) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 28!");
- }
- dsi = "2F302D300D06096086480165030402040500041C" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA256:
- if (hash.length != 32) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 32!");
- }
- dsi = "333031300D060960864801650304020105000420" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA384:
- if (hash.length != 48) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 48!");
- }
- dsi = "433041300D060960864801650304020205000430" + getHex(hash);
- break;
- case HashAlgorithmTags.SHA512:
- if (hash.length != 64) {
- throw new IOException("Bad hash length (" + hash.length + ", expected 64!");
- }
- dsi = "533051300D060960864801650304020305000440" + getHex(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
- + dsi // digital signature input
- + "00"; // Le
-
- String response = nfcCommunicate(apdu);
-
- 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 = nfcCommunicate("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));
- }
-
- // Make sure the signature we received is actually the expected number of bytes long!
- if (signature.length() != 256 && signature.length() != 512) {
- throw new IOException("Bad signature length! Expected 128 or 256 bytes, got " + signature.length() / 2);
- }
-
- return Hex.decode(signature);
- }
-
- public String nfcGetHolderName(String name) {
- 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);
- }
-
- private String nfcGetDataField(String output) {
- return output.substring(0, output.length() - 4);
- }
-
- /**
- * Transceive data via NFC encoded as Hex
- */
- // METHOD UPDATED [OK]
- public String nfcCommunicate(String apdu) throws IOException, TransportIoException {
- return getHex(mTransport.sendAndReceive(Hex.decode(apdu)));
- }
-
- public boolean isConnected() {
- return mTransport.isConnected();
- }
-
- // NEW METHOD [OK]
- public boolean isFidesmoToken() {
- if (isConnected()) { // Check if we can still talk to the card
- 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.sendAndReceive(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);
- } catch (IOException e) {
- Log.e(Constants.TAG, "Card communication failed!", e);
- }
- }
- return false;
- }
-
- /**
- * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key),
- * this command also has the effect of resetting the digital signature counter.
- * NOTE: This does not set the key fingerprint data object! After calling this command, you
- * must construct a public key packet using the returned public key data objects, compute the
- * key fingerprint, and store it on the card using: putData(0xC8, key.getFingerprint())
- *
- * @param slot The slot on the card where the key should be generated:
- * 0xB6: Signature Key
- * 0xB8: Decipherment Key
- * 0xA4: Authentication Key
- * @return the public key data objects, in TLV format. For RSA this will be the public modulus
- * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required.
- */
- // NEW METHOD [OK]
- public byte[] nfcGenerateKey(int slot) throws IOException {
- if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) {
- throw new IOException("Invalid key slot");
- }
-
- if (!mPw3Validated) {
- nfcVerifyPin(0x83); // (Verify PW3 with mode 83)
- }
-
- String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000";
- String getResponseApdu = "00C00000";
-
- String first = nfcCommunicate(generateKeyApdu);
- String second = nfcCommunicate(getResponseApdu);
-
- if (!second.endsWith("9000")) {
- 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);
- }
-
- // NEW METHOD [OK][OK]
- private String getDataField(String output) {
- return output.substring(0, output.length() - 4);
- }
-
- // NEW METHOD [OK]
- private String nfcTryPin(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 nfcCommunicate(login);
- }
-
- /**
- * Resets security token, which deletes all keys and data objects.
- * This works by entering a wrong PIN and then Admin PIN 4 times respectively.
- * Afterwards, the token is reactivated.
- */
- // NEW METHOD [OK]
- public void resetAndWipeToken() throws IOException {
- String accepted = "9000";
-
- // try wrong PIN 4 times until counter goes to C0
- byte[] pin = "XXXXXX".getBytes();
- for (int i = 0; i <= 4; i++) {
- String response = nfcTryPin(0x81, pin);
- if (response.equals(accepted)) { // Should NOT accept!
- throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response));
- }
- }
-
- // try wrong Admin PIN 4 times until counter goes to C0
- byte[] adminPin = "XXXXXXXX".getBytes();
- for (int i = 0; i <= 4; i++) {
- String response = nfcTryPin(0x83, adminPin);
- if (response.equals(accepted)) { // Should NOT accept!
- throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response));
- }
- }
-
- // reactivate token!
- String reactivate1 = "00" + "e6" + "00" + "00";
- String reactivate2 = "00" + "44" + "00" + "00";
- String response1 = nfcCommunicate(reactivate1);
- String response2 = nfcCommunicate(reactivate2);
- if (!response1.equals(accepted) || !response2.equals(accepted)) {
- throw new CardException("Reactivating failed!", parseCardStatus(response1));
- }
-
- }
-
- /**
- * Return the fingerprint from application specific data stored on tag, or
- * null if it doesn't exist.
- *
- * @param idx Index of the key to return the fingerprint from.
- * @return The fingerprint of the requested key, or null if not found.
- */
- public byte[] getMasterKeyFingerprint(int idx) throws IOException {
- byte[] data = getFingerprints();
- if (data == null) {
- return null;
- }
-
- // return the master key fingerprint
- ByteBuffer fpbuf = ByteBuffer.wrap(data);
- byte[] fp = new byte[20];
- fpbuf.position(idx * 20);
- fpbuf.get(fp, 0, 20);
-
- return fp;
- }
-
- @Override
- public void setTransport(Transport mTransport) {
- this.mTransport = mTransport;
-
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java
deleted file mode 100644
index 63d4d2a..0000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package org.sufficientlysecure.keychain.javacard;
-
-import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey;
-import org.sufficientlysecure.keychain.util.Passphrase;
-
-import java.io.IOException;
-
-public interface JavacardDevice {
-
- Passphrase getPin();
-
- void setPin(final Passphrase pin);
-
- Passphrase getAdminPin();
-
- void setAdminPin(final Passphrase adminPin);
-
- void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException;
-
- boolean containsKey(KeyType keyType) throws IOException;
-
- boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException;
-
- void connectToDevice() throws IOException;
-
- /**
- * Modifies the user's PW1 or PW3. Before sending, the new PIN will be validated for
- * conformance to the card's requirements for key length.
- *
- * @param pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83.
- * @param newPin The new PW1 or PW3.
- */
- void modifyPin(int pinType, byte[] newPin) throws IOException;
-
- /**
- * Calls to calculate the signature and returns the MPI value
- *
- * @param encryptedSessionKey the encoded session key
- * @return the decoded session key
- */
- byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException;
-
- /**
- * Return fingerprints of all keys from application specific data stored
- * on tag, or null if data not available.
- *
- * @return The fingerprints of all subkeys in a contiguous byte array.
- */
- byte[] getFingerprints() throws IOException;
-
-
- byte[] getAid() throws IOException;
-
- String getUserId() throws IOException;
-
- boolean isConnected();
-
- /**
- * Calls to calculate the signature and returns the MPI value
- *
- * @param hash the hash for signing
- * @return a big integer representing the MPI for the given hash
- */
- byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException;
-
- boolean isFidesmoToken();
-
- /**
- * Return the fingerprint from application specific data stored on tag, or
- * null if it doesn't exist.
- *
- * @param idx Index of the key to return the fingerprint from.
- * @return The fingerprint of the requested key, or null if not found.
- */
- byte[] getMasterKeyFingerprint(int idx) throws IOException;
-
- /**
- * Resets security token, which deletes all keys and data objects.
- * This works by entering a wrong PIN and then Admin PIN 4 times respectively.
- * Afterwards, the token is reactivated.
- */
- void resetAndWipeToken() throws IOException;
-
- /**
- * Puts a key on the token in the given slot.
- *
- * @param slot The slot on the token where the key should be stored:
- * 0xB6: Signature Key
- * 0xB8: Decipherment Key
- * 0xA4: Authentication Key
- */
- void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException;
-
- /**
- * Stores a data object on the token. Automatically validates the proper PIN for the operation.
- * Supported for all data objects < 255 bytes in length. Only the cardholder certificate
- * (0x7F21) can exceed this length.
- *
- * @param dataObject The data object to be stored.
- * @param data The data to store in the object
- */
- void putData(int dataObject, byte[] data) throws IOException;
-
- void setTransport(Transport mTransport);
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java
index cffc495..b86c3cf 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java
@@ -78,7 +78,7 @@ public class SmartcardDevice {
}
public boolean containsKey(KeyType keyType) throws IOException {
- return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
+ return !keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT);
}
public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java
index c98d5d4..8a6971f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java
@@ -1,7 +1,6 @@
package org.sufficientlysecure.keychain.smartcard;
import android.app.Activity;
-import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -10,79 +9,29 @@ import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbManager;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity;
import org.sufficientlysecure.keychain.util.Log;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Semaphore;
-import java.util.concurrent.atomic.AtomicBoolean;
-
public class UsbConnectionManager {
- private static final String LOG_TAG = UsbConnectionManager.class.getName();
- private static final String ACTION_USB_PERMISSION = Constants.PACKAGE_NAME + ".USB_PERMITSSION";
- private final Semaphore mRunning = new Semaphore(1);
- private final Set<UsbDevice> mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap<UsbDevice, Boolean>());
- private final AtomicBoolean mStopped = new AtomicBoolean(false);
private Activity mActivity;
- private final Thread mWatchThread = new Thread() {
- @Override
- public void run() {
- final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE);
-
- while (!mStopped.get()) {
- try {
- mRunning.acquire();
- } catch (InterruptedException e) {
- }
- mRunning.release();
- if (mStopped.get()) return;
-
- //
- final UsbDevice device = getDevice(usbManager);
- if (device != null && !mProcessedDevices.contains(device)) {
- mProcessedDevices.add(device);
-
- final Intent intent = new Intent(ACTION_USB_PERMISSION);
-
- IntentFilter filter = new IntentFilter();
- filter.addAction(ACTION_USB_PERMISSION);
- mActivity.registerReceiver(mUsbReceiver, filter);
- Log.d(LOG_TAG, "Requesting permission for " + device.getDeviceName());
- usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0));
- }
-
- try {
- sleep(1000);
- } catch (InterruptedException ignored) {
- }
- }
- }
- };
private OnDiscoveredUsbDeviceListener mListener;
/**
- * Receives broadcast when a supported USB device is attached, detached or
- * when a permission to communicate to the device has been granted.
+ * Receives broadcast when a supported USB device get permission.
*/
private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- UsbDevice usbDevice = (UsbDevice) intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- String deviceName = usbDevice.getDeviceName();
- if (ACTION_USB_PERMISSION.equals(action)) {
+ if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
false);
- Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName);
-
if (permission) {
- interceptIntent(intent);
+ Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName());
+ mListener.usbDeviceDiscovered(usbDevice);
}
-
- context.unregisterReceiver(mUsbReceiver);
}
}
};
@@ -90,59 +39,16 @@ public class UsbConnectionManager {
public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) {
this.mActivity = activity;
this.mListener = listener;
- mRunning.acquireUninterruptibly();
- mWatchThread.start();
- }
-
- private static UsbDevice getDevice(UsbManager manager) {
- HashMap<String, UsbDevice> deviceList = manager.getDeviceList();
- for (UsbDevice device : deviceList.values()) {
- if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) {
- return device;
- }
- }
- return null;
}
- public void startListeningForDevices() {
- mRunning.release();
- }
-
- public void stopListeningForDevices() {
- mRunning.acquireUninterruptibly();
- }
+ public void onStart() {
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION);
- public void interceptIntent(final Intent intent) {
- if (intent == null || intent.getAction() == null) return;
- switch (intent.getAction()) {
- /*case UsbManager.ACTION_USB_DEVICE_ATTACHED: {
- final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE);
- final UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- Intent usbI = new Intent(mActivity, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- usbI.setAction(ACTION_USB_PERMISSION);
- usbI.putExtra(UsbManager.EXTRA_DEVICE, device);
- PendingIntent pi = PendingIntent.getActivity(mActivity, 0, usbI, PendingIntent.FLAG_CANCEL_CURRENT);
- usbManager.requestPermission(device, pi);
- break;
- }*/
- case ACTION_USB_PERMISSION: {
- UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
- if (device != null)
- mListener.usbDeviceDiscovered(device);
- break;
- }
- default:
- break;
- }
+ mActivity.registerReceiver(mUsbReceiver, intentFilter);
}
- public void onDestroy() {
- mStopped.set(true);
- mRunning.release();
- try {
- mActivity.unregisterReceiver(mUsbReceiver);
- } catch (IllegalArgumentException ignore) {
- }
- mActivity = null;
+ public void onStop() {
+ mActivity.unregisterReceiver(mUsbReceiver);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java
new file mode 100644
index 0000000..9df5800
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java
@@ -0,0 +1,42 @@
+package org.sufficientlysecure.keychain.ui;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbManager;
+import android.os.Bundle;
+
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class UsbEventReceiverActivity extends Activity {
+ public static final String ACTION_USB_PERMISSION =
+ "org.sufficientlysecure.keychain.ui.USB_PERMISSION";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
+
+ Intent intent = getIntent();
+ if (intent != null) {
+ if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(intent.getAction())) {
+ UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
+
+ Log.d(Constants.TAG, "Requesting permission for " + usbDevice.getDeviceName());
+ usbManager.requestPermission(usbDevice,
+ PendingIntent.getBroadcast(this, 0, new Intent(ACTION_USB_PERMISSION), 0));
+ }
+ }
+
+ // Close the activity
+ finish();
+ }
+} \ No newline at end of file
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
index 8dde54a..a6f8c0b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java
@@ -254,9 +254,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
*/
@Override
public void onNewIntent(final Intent intent) {
- if (!mTagDispatcher.interceptIntent(intent)) {
- mUsbDispatcher.interceptIntent(intent);
- }
+ mTagDispatcher.interceptIntent(intent);
}
private void handleNfcError(IOException e) {
@@ -374,7 +372,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
Log.d(Constants.TAG, "BaseNfcActivity.onPause");
mTagDispatcher.disableExclusiveNfc();
- mUsbDispatcher.stopListeningForDevices();
+// mUsbDispatcher.stopListeningForDevices();
}
/**
@@ -385,7 +383,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
super.onResume();
Log.d(Constants.TAG, "BaseNfcActivity.onResume");
mTagDispatcher.enableExclusiveNfc();
- mUsbDispatcher.startListeningForDevices();
}
protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) {
@@ -568,8 +565,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity
}
@Override
- protected void onDestroy() {
- super.onDestroy();
- mUsbDispatcher.onDestroy();
+ protected void onStop() {
+ super.onStop();
+ mUsbDispatcher.onStop();
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mUsbDispatcher.onStart();
}
}
diff --git a/OpenKeychain/src/main/res/xml/usb_device_filter.xml b/OpenKeychain/src/main/res/xml/usb_device_filter.xml
index cc05990..789e4ff 100644
--- a/OpenKeychain/src/main/res/xml/usb_device_filter.xml
+++ b/OpenKeychain/src/main/res/xml/usb_device_filter.xml
@@ -1,4 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ Based on https://github.com/Yubico/yubikey-personalization/blob/master/ykcore/ykdef.h
+ Note that values are decimal.
+-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
- <usb-device class="11"/>
+ <!-- Yubikey NEO OTP + CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="273"/>
+ <!-- Yubikey NEO CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="274"/>
+ <!-- Yubikey NEO U2F + CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="277"/>
+ <!-- Yubikey NEO OTP + U2F + CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="278"/>
+
+
+ <!-- Yubikey 4 CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="1028"/>
+ <!-- Yubikey 4 OTP + CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="1029"/>
+ <!-- Yubikey 4 U2F + CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="1030"/>
+ <!-- Yubikey 4 OTP + U2F + CCID-->
+ <usb-device class="11" vendor-id="4176" product-id="1031"/>
+
</resources> \ No newline at end of file