From 65f17d74495b2bfdb323dc5bc87ef95f8466347a Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 3 Apr 2016 19:54:24 +0600 Subject: OTG: port old primitives from otg_alt branch --- .../keychain/javacard/BaseJavacardDevice.java | 590 +++++++++++++++++++++ .../javacard/CachingBaseJavacardDevice.java | 46 ++ .../keychain/javacard/CardException.java | 17 + .../keychain/javacard/JavacardDevice.java | 65 +++ .../keychain/javacard/KeyType.java | 48 ++ .../keychain/javacard/NfcTransport.java | 31 ++ .../keychain/javacard/PinException.java | 7 + .../keychain/javacard/PinType.java | 16 + .../keychain/javacard/Transport.java | 11 + .../keychain/javacard/TransportIoException.java | 20 + .../keychain/javacard/UsbTransport.java | 144 +++++ 11 files changed, 995 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java new file mode 100644 index 000000000..323cb9628 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -0,0 +1,590 @@ +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; + +public class BaseJavacardDevice implements JavacardDevice { + 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 final 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(final Transport mTransport) { + this.mTransport = mTransport; + } + + 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())); + } + + nfcPutKey(keyType.getmSlot(), secretKey, passphrase); + nfcPutData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + nfcPutData(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(nfcGetFingerprint(keyType.getIdx()), fingerprint); + } + + 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 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. + */ + public void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException { + final int MAX_PW1_LENGTH_INDEX = 1; + final int MAX_PW3_LENGTH_INDEX = 3; + + byte[] pwStatusBytes = nfcGetPwStatusBytes(); + byte[] oldPin; + + if (pinType == PinType.BASIC) { + if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + oldPin = mPin.toStringUnsafe().getBytes(); + } else { + if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + oldPin = mAdminPin.toStringUnsafe().getBytes(); + } + + // Command APDU for CHANGE REFERENCE DATA command (page 32) + String changeReferenceDataApdu = "00" // CLA + + "24" // INS + + "00" // P1 + + String.format("%02x", pinType.getmMode()) // P2 + + String.format("%02x", oldPin.length + newPin.length) // Lc + + getHex(oldPin) + + getHex(newPin); + String response = nfcCommunicate(changeReferenceDataApdu); // change PIN + if (!response.equals("9000")) { + throw new PinException("Failed to change PIN", parseCardStatus(response)); + } + } + + /** + * Calls to calculate the signature and returns the MPI value + * + * @param encryptedSessionKey the encoded session key + * @return the decoded session key + */ + 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]; + } + + String first = nfcCommunicate(firstApdu + getHex(one)); + String second = nfcCommunicate(secondApdu + getHex(two) + le); + + String decryptedSessionKey = nfcGetDataField(second); + + Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + + 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. + */ + 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"; + + // 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); + String response = nfcCommunicate(login); // login + if (!response.equals(accepted)) { + throw new PinException("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 card. 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 + */ + public void nfcPutData(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 card in the given slot. + * + * @param slot The slot on the card where the key should be stored: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + */ + public void nfcPutKey(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 smart card."); + } + + // 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 card key."); + } + + 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 card. + 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 card failed", parseCardStatus(response)); + } + } + + // Clear array with secret data before we return. + Arrays.fill(dataToSend, (byte) 0); + } + + /** + * Return the key id 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 long key id of the requested key, or null if not found. + */ + public Long nfcGetKeyId(int idx) throws IOException { + byte[] fp = nfcGetFingerprint(idx); + if (fp == null) { + return null; + } + ByteBuffer buf = ByteBuffer.wrap(fp); + // skip first 12 bytes of the fingerprint + buf.position(12); + // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) + return buf.getLong(); + } + + /** + * 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. + */ + 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, "nfc 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 card. This is a simple DO; no TLV decoding needed. + * + * @return Seven bytes in fixed format, plus 0x9000 status word at the end. + */ + public byte[] nfcGetPwStatusBytes() throws IOException { + String data = "00CA00C400"; + return mTransport.sendAndReceive(Hex.decode(data)); + } + + /** + * 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[] nfcGetFingerprint(int idx) throws IOException { + byte[] data = getFingerprints(); + + // 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; + } + + public byte[] getAid() throws IOException { + String info = "00CA004F00"; + return mTransport.sendAndReceive(Hex.decode(info)); + } + + public String getUserId() throws IOException { + String info = "00CA006500"; + return nfcGetHolderName(nfcCommunicate(info)); + } + + /** + * 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 + */ + public byte[] nfcCalculateSignature(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); + + // 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); + } + + public String nfcCommunicate(String apdu) throws IOException, TransportIoException { + return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); + } + + public boolean isConnected() { + return mTransport.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java new file mode 100644 index 000000000..5ad157de1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java @@ -0,0 +1,46 @@ +package org.sufficientlysecure.keychain.javacard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; +import org.sufficientlysecure.keychain.util.Passphrase; + +import java.io.IOException; + +public class CachingBaseJavacardDevice extends BaseJavacardDevice { + private byte[] mFingerprintsCache; + private String mUserIdCache; + private byte[] mAidCache; + + public CachingBaseJavacardDevice(final Transport mTransport) { + super(mTransport); + } + + @Override + public byte[] getFingerprints() throws IOException { + if (mFingerprintsCache == null) { + mFingerprintsCache = super.getFingerprints(); + } + return mFingerprintsCache; + } + + @Override + public String getUserId() throws IOException { + if (mUserIdCache == null) { + mUserIdCache = super.getUserId(); + } + return mUserIdCache; + } + + @Override + public byte[] getAid() throws IOException { + if (mAidCache == null) { + mAidCache = super.getAid(); + } + return mAidCache; + } + + @Override + public void changeKey(final CanonicalizedSecretKey secretKey, final Passphrase passphrase) throws IOException { + super.changeKey(secretKey, passphrase); + mFingerprintsCache = null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java new file mode 100644 index 000000000..3e9e9f2ca --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java @@ -0,0 +1,17 @@ +package org.sufficientlysecure.keychain.javacard; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java new file mode 100644 index 000000000..240fffaf8 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java @@ -0,0 +1,65 @@ +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 nfcModifyPIN(PinType 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[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java new file mode 100644 index 000000000..e0190f8bd --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.javacard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java new file mode 100644 index 000000000..b5af8b374 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java @@ -0,0 +1,31 @@ +package org.sufficientlysecure.keychain.javacard; + +import android.nfc.tech.IsoDep; + +import java.io.IOException; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final IsoDep mIsoDep; + + public NfcTransport(final IsoDep isoDep) throws IOException { + this.mIsoDep = isoDep; + mIsoDep.setTimeout(TIMEOUT); + mIsoDep.connect(); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + return mIsoDep.transceive(data); + } + + @Override + public void release() { + } + + @Override + public boolean isConnected() { + return mIsoDep.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java new file mode 100644 index 000000000..84a34f116 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.javacard; + +public class PinException extends CardException { + public PinException(final String detailMessage, final short responseCode) { + super(detailMessage, responseCode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java new file mode 100644 index 000000000..b6787a9e1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java @@ -0,0 +1,16 @@ +package org.sufficientlysecure.keychain.javacard; + +public enum PinType { + BASIC(0x81), + ADMIN(0x83),; + + private final int mMode; + + PinType(final int mode) { + this.mMode = mode; + } + + public int getmMode() { + return mMode; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java new file mode 100644 index 000000000..2d7dd3309 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.javacard; + +import java.io.IOException; + +public interface Transport { + byte[] sendAndReceive(byte[] data) throws IOException; + + void release(); + + boolean isConnected(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java new file mode 100644 index 000000000..2dfb7df94 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.javacard; + +import java.io.IOException; + +public class TransportIoException extends IOException { + public TransportIoException() { + } + + public TransportIoException(final String detailMessage) { + super(detailMessage); + } + + public TransportIoException(final String message, final Throwable cause) { + super(message, cause); + } + + public TransportIoException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java new file mode 100644 index 000000000..f2af34c39 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java @@ -0,0 +1,144 @@ +package org.sufficientlysecure.keychain.javacard; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; + +public class UsbTransport implements Transport { + private static final int CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 1000; // 1 s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private final UsbInterface mUsbInterface; + private final UsbEndpoint mBulkIn; + private final UsbEndpoint mBulkOut; + private final UsbDeviceConnection mConnection; + private byte counter = 0; + + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // check result + + final byte[] iccPowerOn = { + 0x62, + 0x00, 0x00, 0x00, 0x00, + 0x00, + counter++, + 0x03, + 0x00, 0x00 + }; + sendRaw(iccPowerOn); + receiveRaw(); + // Check result + } + + /** + * Get first class 11 (Chip/Smartcard) interface for the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(final UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + final UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + @Override + public void release() { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + } + + @Override + public boolean isConnected() { + // TODO: redo + return mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException { + send(data); + return receive(); + } + + public void send(final byte[] d) throws TransportIoException { + int l = d.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + counter++, + 0x01, + 0x00, 0x00}, + d); + sendRaw(data); + } + + public byte[] receive() throws TransportIoException { + final byte[] bytes = receiveRaw(); + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + private void sendRaw(final byte[] data) throws TransportIoException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new TransportIoException("USB error, failed to send data " + tr1); + } + } + + private byte[] receiveRaw() throws TransportIoException { + byte[] buffer = new byte[1024]; + + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + + return Arrays.copyOfRange(buffer, 0, res); + } +} -- cgit v1.2.3 From 834440199a97f3b253d85915ce83613f67e33a25 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 3 Apr 2016 22:20:33 +0600 Subject: OTG: update methods --- .../keychain/javacard/BaseJavacardDevice.java | 279 +++++++--- .../keychain/javacard/JavacardDevice.java | 42 +- .../keychain/javacard/NfcTransport.java | 20 +- .../keychain/ui/CreateKeyActivity.java | 6 +- .../ui/CreateSecurityTokenImportResetFragment.java | 6 +- .../ui/SecurityTokenOperationActivity.java | 36 +- .../keychain/ui/ViewKeyActivity.java | 6 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 620 +-------------------- 8 files changed, 308 insertions(+), 707 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java index 323cb9628..46f4c0443 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -15,7 +15,12 @@ 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 final Transport mTransport; @@ -68,9 +73,9 @@ public class BaseJavacardDevice implements JavacardDevice { keyType.toString())); } - nfcPutKey(keyType.getmSlot(), secretKey, passphrase); - nfcPutData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); - nfcPutData(keyType.getTimestampObjectId(), timestampBytes); + putKey(keyType.getmSlot(), secretKey, passphrase); + putData(keyType.getmFingerprintObjectId(), secretKey.getFingerprint()); + putData(keyType.getTimestampObjectId(), timestampBytes); } public boolean containsKey(KeyType keyType) throws IOException { @@ -78,9 +83,10 @@ public class BaseJavacardDevice implements JavacardDevice { } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(nfcGetFingerprint(keyType.getIdx()), fingerprint); + 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 @@ -127,53 +133,61 @@ public class BaseJavacardDevice implements JavacardDevice { /** * 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. + * conformance to the token'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. + * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. + * @param newPin The new PW1 or PW3. */ - public void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException { + // 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(); - byte[] oldPin; - if (pinType == PinType.BASIC) { + if (pw == 0x81) { if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { throw new IOException("Invalid PIN length"); } - oldPin = mPin.toStringUnsafe().getBytes(); - } else { + } else if (pw == 0x83) { if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { throw new IOException("Invalid PIN length"); } - oldPin = mAdminPin.toStringUnsafe().getBytes(); + } 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", pinType.getmMode()) // P2 - + String.format("%02x", oldPin.length + newPin.length) // Lc - + getHex(oldPin) + + 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 PinException("Failed to change PIN", parseCardStatus(response)); + throw new CardException("Failed to change PIN", parseCardStatus(response)); } } /** - * Calls to calculate the signature and returns the MPI value + * 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) + nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } String firstApdu = "102a8086fe"; @@ -189,12 +203,10 @@ public class BaseJavacardDevice implements JavacardDevice { two[i] = encryptedSessionKey[i + one.length + 1]; } - String first = nfcCommunicate(firstApdu + getHex(one)); + nfcCommunicate(firstApdu + getHex(one)); String second = nfcCommunicate(secondApdu + getHex(two) + le); - String decryptedSessionKey = nfcGetDataField(second); - - Log.d(Constants.TAG, "decryptedSessionKey: " + decryptedSessionKey); + String decryptedSessionKey = getDataField(second); return Hex.decode(decryptedSessionKey); } @@ -205,7 +217,8 @@ public class BaseJavacardDevice implements JavacardDevice { * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. * For PW3 (Admin PIN), mode is 0x83. */ - public void nfcVerifyPIN(int mode) throws IOException { + // METHOD UPDATED [OK] + public void nfcVerifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { byte[] pin; @@ -218,18 +231,9 @@ public class BaseJavacardDevice implements JavacardDevice { // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; - - // 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); - String response = nfcCommunicate(login); // login + String response = nfcTryPin(mode, pin); // login if (!response.equals(accepted)) { - throw new PinException("Bad PIN!", parseCardStatus(response)); + throw new CardException("Bad PIN!", parseCardStatus(response)); } if (mode == 0x81) { @@ -243,23 +247,24 @@ public class BaseJavacardDevice implements JavacardDevice { } /** - * Stores a data object on the card. Automatically validates the proper PIN for the operation. + * 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 */ - public void nfcPutData(int dataObject, byte[] data) throws IOException { + // 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) + nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) } } else if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3) + nfcVerifyPin(0x83); // (Verify PW3) } String putDataApdu = "00" // CLA @@ -275,15 +280,17 @@ public class BaseJavacardDevice implements JavacardDevice { } } + /** - * Puts a key on the card in the given slot. + * Puts a key on the token in the given slot. * - * @param slot The slot on the card where the key should be stored: + * @param slot The slot on the token where the key should be stored: * 0xB6: Signature Key * 0xB8: Decipherment Key * 0xA4: Authentication Key */ - public void nfcPutKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) + // 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"); @@ -299,16 +306,16 @@ public class BaseJavacardDevice implements JavacardDevice { // 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 smart card."); + 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 card key."); + throw new IOException("Invalid public exponent for smart Security Token."); } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) } byte[] header = Hex.decode( @@ -360,7 +367,7 @@ public class BaseJavacardDevice implements JavacardDevice { String putKeyCommand = "10DB3FFF"; String lastPutKeyCommand = "00DB3FFF"; - // Now we're ready to communicate with the card. + // Now we're ready to communicate with the token. offset = 0; String response; while (offset < dataToSend.length) { @@ -379,7 +386,7 @@ public class BaseJavacardDevice implements JavacardDevice { } if (!response.endsWith("9000")) { - throw new CardException("Key export to card failed", parseCardStatus(response)); + throw new CardException("Key export to Security Token failed", parseCardStatus(response)); } } @@ -387,6 +394,7 @@ public class BaseJavacardDevice implements JavacardDevice { Arrays.fill(dataToSend, (byte) 0); } + /** * Return the key id from application specific data stored on tag, or null * if it doesn't exist. @@ -395,7 +403,7 @@ public class BaseJavacardDevice implements JavacardDevice { * @return The long key id of the requested key, or null if not found. */ public Long nfcGetKeyId(int idx) throws IOException { - byte[] fp = nfcGetFingerprint(idx); + byte[] fp = getMasterKeyFingerprint(idx); if (fp == null) { return null; } @@ -412,12 +420,13 @@ public class BaseJavacardDevice implements JavacardDevice { * * @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, "nfc tlv data:\n" + tlv.prettyPrint()); + Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); Iso7816TLV fptlv = Iso7816TLV.findRecursive(tlv, 0xc5); if (fptlv == null) { @@ -427,53 +436,38 @@ public class BaseJavacardDevice implements JavacardDevice { } /** - * Return the PW Status Bytes from the card. This is a simple DO; no TLV decoding needed. + * 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)); } - /** - * 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[] nfcGetFingerprint(int idx) throws IOException { - byte[] data = getFingerprints(); - - // 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; - } - + // 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)); } /** - * Calls to calculate the signature and returns the MPI value + * 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 */ - public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException { + // METHOD UPDATED [OK] + public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { if (!mPw1ValidatedForSignature) { - nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing) + nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing) } // dsi, including Lc @@ -580,6 +574,10 @@ public class BaseJavacardDevice implements JavacardDevice { 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))); } @@ -587,4 +585,141 @@ public class BaseJavacardDevice implements JavacardDevice { 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; + } + } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java index 240fffaf8..04c2c0006 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java @@ -30,7 +30,7 @@ public interface JavacardDevice { * @param pinType For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. * @param newPin The new PW1 or PW3. */ - void nfcModifyPIN(PinType pinType, byte[] newPin) throws IOException; + void modifyPin(int pinType, byte[] newPin) throws IOException; /** * Calls to calculate the signature and returns the MPI value @@ -61,5 +61,43 @@ public interface JavacardDevice { * @param hash the hash for signing * @return a big integer representing the MPI for the given hash */ - byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException; + 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; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java index b5af8b374..421b28aa8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java @@ -1,23 +1,23 @@ package org.sufficientlysecure.keychain.javacard; -import android.nfc.tech.IsoDep; - import java.io.IOException; -public class NfcTransport implements Transport { +import nordpol.IsoCard; + +public class NfcTransport implements Transport { // timeout is set to 100 seconds to avoid cancellation during calculation private static final int TIMEOUT = 100 * 1000; - private final IsoDep mIsoDep; + private final IsoCard mIsoCard; - public NfcTransport(final IsoDep isoDep) throws IOException { - this.mIsoDep = isoDep; - mIsoDep.setTimeout(TIMEOUT); - mIsoDep.connect(); + public NfcTransport(final IsoCard isoDep) throws IOException { + this.mIsoCard = isoDep; + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); } @Override public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { - return mIsoDep.transceive(data); + return mIsoCard.transceive(data); } @Override @@ -26,6 +26,6 @@ public class NfcTransport implements Transport { @Override public boolean isConnected() { - return mIsoDep.isConnected(); + return mIsoCard.isConnected(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b1fec3aae..b3f60ba41 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -149,9 +149,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { return; } - mScannedFingerprints = nfcGetFingerprints(); - mNfcAid = nfcGetAid(); - mNfcUserId = nfcGetUserId(); + mScannedFingerprints = mJavacardDevice.getFingerprints(); + mNfcAid = mJavacardDevice.getAid(); + mNfcUserId = mJavacardDevice.getUserId(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index ea57fe558..401db0b98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -250,9 +250,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.nfcGetFingerprints(); - mTokenAid = mCreateKeyActivity.nfcGetAid(); - mTokenUserId = mCreateKeyActivity.nfcGetUserId(); + mTokenFingerprints = mCreateKeyActivity.mJavacardDevice.getFingerprints(); + mTokenAid = mCreateKeyActivity.mJavacardDevice.getAid(); + mTokenUserId = mCreateKeyActivity.mJavacardDevice.getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index d2ecce242..7e1474eb7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -162,7 +162,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity case NFC_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = nfcDecryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mJavacardDevice.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -173,15 +173,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = nfcCalculateSignature(hash, algo); + byte[] signedHash = mJavacardDevice.calculateSignature(hash, algo); mInputParcel.addCryptoData(hash, signedHash); } break; } case NFC_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation - mPin = new Passphrase("123456"); - mAdminPin = new Passphrase("12345678"); + mJavacardDevice.setPin(new Passphrase("123456")); + mJavacardDevice.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -206,7 +206,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long keyGenerationTimestampMillis = key.getCreationTime().getTime(); long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - byte[] tokenSerialNumber = Arrays.copyOf(nfcGetAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mJavacardDevice.getAid(), 16); Passphrase passphrase; try { @@ -218,25 +218,25 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (key.canSign() || key.canCertify()) { if (shouldPutKey(key.getFingerprint(), 0)) { - nfcPutKey(0xB6, key, passphrase); - nfcPutData(0xCE, timestampBytes); - nfcPutData(0xC7, key.getFingerprint()); + mJavacardDevice.putKey(0xB6, key, passphrase); + mJavacardDevice.putData(0xCE, timestampBytes); + mJavacardDevice.putData(0xC7, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new signature key."); } } else if (key.canEncrypt()) { if (shouldPutKey(key.getFingerprint(), 1)) { - nfcPutKey(0xB8, key, passphrase); - nfcPutData(0xCF, timestampBytes); - nfcPutData(0xC8, key.getFingerprint()); + mJavacardDevice.putKey(0xB8, key, passphrase); + mJavacardDevice.putData(0xCF, timestampBytes); + mJavacardDevice.putData(0xC8, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new decryption key."); } } else if (key.canAuthenticate()) { if (shouldPutKey(key.getFingerprint(), 2)) { - nfcPutKey(0xA4, key, passphrase); - nfcPutData(0xD0, timestampBytes); - nfcPutData(0xC9, key.getFingerprint()); + mJavacardDevice.putKey(0xA4, key, passphrase); + mJavacardDevice.putData(0xD0, timestampBytes); + mJavacardDevice.putData(0xC9, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new authentication key."); } @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - nfcModifyPin(0x81, newPin); - nfcModifyPin(0x83, newAdminPin); + mJavacardDevice.modifyPin(0x81, newPin); + mJavacardDevice.modifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - nfcReset(); + mJavacardDevice.resetAndWipeToken(); break; } @@ -330,7 +330,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = nfcGetMasterKeyFingerprint(idx); + byte[] tokenFingerprint = mJavacardDevice.getMasterKeyFingerprint(idx); // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index d81797454..7d6ba5c8f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -649,9 +649,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements @Override protected void doNfcInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); + mNfcFingerprints = mJavacardDevice.getFingerprints(); + mNfcUserId = mJavacardDevice.getUserId(); + mNfcAid = mJavacardDevice.getAid(); } @Override 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 77044cd2b..94d640768 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 @@ -35,16 +35,19 @@ import android.os.AsyncTask; import android.os.Bundle; import nordpol.Apdu; +import nordpol.IsoCard; import nordpol.android.TagDispatcher; import nordpol.android.AndroidCard; import nordpol.android.OnDiscoveredTagListener; -import nordpol.IsoCard; 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.R; +import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice; +import org.sufficientlysecure.keychain.javacard.JavacardDevice; +import org.sufficientlysecure.keychain.javacard.NfcTransport; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -72,22 +75,20 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; - // Fidesmo constants - private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - protected Passphrase mPin; - protected Passphrase mAdminPin; - protected boolean mPw1ValidForMultipleSignatures; - protected boolean mPw1ValidatedForSignature; - protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - protected boolean mPw3Validated; + //protected Passphrase mPin; + //protected Passphrase mAdminPin; + //protected boolean mPw1ValidForMultipleSignatures; + //protected boolean mPw1ValidatedForSignature; + //protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? + //protected boolean mPw3Validated; + + public JavacardDevice mJavacardDevice; protected TagDispatcher mTagDispatcher; - private IsoCard mIsoCard; +// private IsoCard mIsoCard; private boolean mTagHandlingEnabled; - private static final int TIMEOUT = 100000; - private byte[] mNfcFingerprints; private String mNfcUserId; private byte[] mNfcAid; @@ -102,9 +103,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * Override to implement NFC operations (background thread) */ protected void doNfcInBackground() throws IOException { - mNfcFingerprints = nfcGetFingerprints(); - mNfcUserId = nfcGetUserId(); - mNfcAid = nfcGetAid(); + mNfcFingerprints = mJavacardDevice.getFingerprints(); + mNfcUserId = mJavacardDevice.getUserId(); + mNfcAid = mJavacardDevice.getAid(); } /** @@ -316,7 +317,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } // 6A82 app not installed on security token! case 0x6A82: { - if (isFidesmoToken()) { + if (mJavacardDevice.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -363,7 +364,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mPin = passphrase; + mJavacardDevice.setPin(passphrase); return; } @@ -388,7 +389,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mPin = input.getPassphrase(); + mJavacardDevice.setPin(input.getPassphrase()); break; } default: @@ -413,573 +414,19 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen protected void handleTagDiscovered(Tag tag) throws IOException { // Connect to the detected tag, setting a couple of settings - mIsoCard = AndroidCard.get(tag); - if (mIsoCard == null) { + IsoCard isoCard = AndroidCard.get(tag); + if (isoCard == null) { throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mIsoCard.setTimeout(TIMEOUT); // timeout is set to 100 seconds to avoid cancellation during calculation - mIsoCard.connect(); - - // 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; + mJavacardDevice = new BaseJavacardDevice(new NfcTransport(isoCard)); + mJavacardDevice.connectToDevice(); doNfcInBackground(); - } public boolean isNfcConnected() { - return mIsoCard.isConnected(); - } - - /** Return the key id 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 long key id of the requested key, or null if not found. - */ - public Long nfcGetKeyId(int idx) throws IOException { - byte[] fp = nfcGetMasterKeyFingerprint(idx); - if (fp == null) { - return null; - } - ByteBuffer buf = ByteBuffer.wrap(fp); - // skip first 12 bytes of the fingerprint - buf.position(12); - // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) - return buf.getLong(); - } - - /** 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. - */ - public byte[] nfcGetFingerprints() throws IOException { - String data = "00CA006E00"; - byte[] buf = mIsoCard.transceive(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. - */ - public byte[] nfcGetPwStatusBytes() throws IOException { - String data = "00CA00C400"; - return mIsoCard.transceive(Hex.decode(data)); - } - - /** 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[] nfcGetMasterKeyFingerprint(int idx) throws IOException { - byte[] data = nfcGetFingerprints(); - 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; - } - - public byte[] nfcGetAid() throws IOException { - String info = "00CA004F00"; - return mIsoCard.transceive(Hex.decode(info)); - } - - public String nfcGetUserId() throws IOException { - String info = "00CA006500"; - return getHolderName(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 - */ - public byte[] nfcCalculateSignature(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); - - // 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); - } - - /** - * Call DECIPHER command - * - * @param encryptedSessionKey the encoded session key - * @return the decoded session key - */ - public byte[] nfcDecryptSessionKey(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. - */ - 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; - } - } - } - - /** - * 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. - */ - public void nfcReset() 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)); - } - - } - - 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); - } - - /** 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. - */ - public void nfcModifyPin(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)); - } - } - - /** - * 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 - */ - public void nfcPutData(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 - */ - public void nfcPutKey(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); - } - - /** - * 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: nfcPutData(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. - */ - 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); - } - - /** - * Transceive data via NFC encoded as Hex - */ - public String nfcCommunicate(String apdu) throws IOException { - return getHex(mIsoCard.transceive(Hex.decode(apdu))); + return mJavacardDevice.isConnected(); } /** @@ -1020,10 +467,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - public static String getHex(byte[] raw) { return new String(Hex.encode(raw)); } @@ -1050,21 +493,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private boolean isFidesmoToken() { - if (isNfcConnected()) { // 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 = mIsoCard.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); - } catch (IOException e) { - Log.e(Constants.TAG, "Card communication failed!", e); - } - } - return false; - } - /** * Ask user if she wants to install PGP onto her Fidesmo token */ -- cgit v1.2.3 From bd2906a887f22c415f9b194481607660db2216f6 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Mon, 4 Apr 2016 00:33:59 +0600 Subject: OTG: Add usb device manager prototype --- OpenKeychain/src/main/AndroidManifest.xml | 5 + .../javacard/OnDiscoveredUsbDeviceListener.java | 7 ++ .../keychain/javacard/UsbConnectionManager.java | 135 +++++++++++++++++++++ .../ui/base/BaseSecurityTokenNfcActivity.java | 96 ++++++++++----- .../src/main/res/xml/usb_device_filter.xml | 4 + 5 files changed, 219 insertions(+), 28 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java create mode 100644 OpenKeychain/src/main/res/xml/usb_device_filter.xml diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index 74bf936b4..f485e964c 100644 --- a/OpenKeychain/src/main/AndroidManifest.xml +++ b/OpenKeychain/src/main/AndroidManifest.xml @@ -43,6 +43,11 @@ android:name="android.hardware.screen.portrait" android:required="false" /> + + + mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); + + /** + * Receives broadcast when a supported USB device is attached, detached or + * when a permission to communicate to the device has been granted. + */ + 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)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); + + interceptIntent(intent); + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + + private final Thread mWatchThread = new Thread() { + @Override + public void run() { + final UsbManager usbManager = (UsbManager) mActivity.getSystemService(Context.USB_SERVICE); + + while (true) { + mRunning.acquireUninterruptibly(); + mRunning.release(); + + // + + 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); + + usbManager.requestPermission(device, PendingIntent.getBroadcast(mActivity, 0, intent, 0)); + } + + try { + sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }; + + public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + mRunning.acquireUninterruptibly(); + mWatchThread.start(); + } + + public void startListeningForDevices() { + mRunning.release(); + } + + public void stopListeningForDevices() { + mRunning.acquireUninterruptibly(); + } + + 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; + } + } + + private static UsbDevice getDevice(UsbManager manager) { + HashMap deviceList = manager.getDeviceList(); + for (UsbDevice device : deviceList.values()) { + Log.d(LOG_TAG, device.getDeviceName() + " " + device.getDeviceId()); + if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { + Log.d(LOG_TAG, device.getDeviceName() + " OK"); + return device; + } + } + return null; + } +} 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 94d640768..573123daf 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 @@ -20,42 +20,32 @@ package org.sufficientlysecure.keychain.ui.base; -import java.io.IOException; -import java.math.BigInteger; -import java.nio.ByteBuffer; -import java.security.interfaces.RSAPrivateCrtKey; - import android.app.Activity; +import android.app.PendingIntent; import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; import android.os.AsyncTask; import android.os.Bundle; -import nordpol.Apdu; -import nordpol.IsoCard; -import nordpol.android.TagDispatcher; -import nordpol.android.AndroidCard; -import nordpol.android.OnDiscoveredTagListener; - -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.R; import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice; import org.sufficientlysecure.keychain.javacard.JavacardDevice; import org.sufficientlysecure.keychain.javacard.NfcTransport; -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; +import org.sufficientlysecure.keychain.javacard.OnDiscoveredUsbDeviceListener; +import org.sufficientlysecure.keychain.javacard.UsbConnectionManager; +import org.sufficientlysecure.keychain.javacard.UsbTransport; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; -import org.sufficientlysecure.keychain.service.PassphraseCacheService.KeyNotFoundException; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -66,27 +56,27 @@ import org.sufficientlysecure.keychain.ui.dialog.FidesmoPgpInstallDialog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.Notify; import org.sufficientlysecure.keychain.ui.util.Notify.Style; -import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; -public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implements OnDiscoveredTagListener { +import java.io.IOException; + +import nordpol.IsoCard; +import nordpol.android.AndroidCard; +import nordpol.android.OnDiscoveredTagListener; +import nordpol.android.TagDispatcher; + +public abstract class BaseSecurityTokenNfcActivity extends BaseActivity + implements OnDiscoveredTagListener, OnDiscoveredUsbDeviceListener { public static final int REQUEST_CODE_PIN = 1; public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - //protected Passphrase mPin; - //protected Passphrase mAdminPin; - //protected boolean mPw1ValidForMultipleSignatures; - //protected boolean mPw1ValidatedForSignature; - //protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? - //protected boolean mPw3Validated; - public JavacardDevice mJavacardDevice; protected TagDispatcher mTagDispatcher; -// private IsoCard mIsoCard; + protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; private byte[] mNfcFingerprints; @@ -185,6 +175,43 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen }.execute(); } + + public void usbDeviceDiscovered(final UsbDevice device) { + // Actual NFC operations are executed in doInBackground to not block the UI thread + if(!mTagHandlingEnabled) + return; + new AsyncTask() { + @Override + protected void onPreExecute() { + super.onPreExecute(); + onNfcPreExecute(); + } + + @Override + protected IOException doInBackground(Void... params) { + try { + handleUsbDevice(device); + } catch (IOException e) { + return e; + } + + return null; + } + + @Override + protected void onPostExecute(IOException exception) { + super.onPostExecute(exception); + + if (exception != null) { + handleNfcError(exception); + return; + } + + onNfcPostExecute(); + } + }.execute(); + } + protected void pauseTagHandling() { mTagHandlingEnabled = false; } @@ -198,6 +225,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen super.onCreate(savedInstanceState); mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); + mUsbDispatcher = new UsbConnectionManager(this, this); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { @@ -228,7 +256,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen */ @Override public void onNewIntent(final Intent intent) { - mTagDispatcher.interceptIntent(intent); + if (!mTagDispatcher.interceptIntent(intent)) { + mUsbDispatcher.interceptIntent(intent); + } } private void handleNfcError(IOException e) { @@ -346,6 +376,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen Log.d(Constants.TAG, "BaseNfcActivity.onPause"); mTagDispatcher.disableExclusiveNfc(); + mUsbDispatcher.stopListeningForDevices(); } /** @@ -356,6 +387,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen super.onResume(); Log.d(Constants.TAG, "BaseNfcActivity.onResume"); mTagDispatcher.enableExclusiveNfc(); + mUsbDispatcher.startListeningForDevices(); } protected void obtainSecurityTokenPin(RequiredInputParcel requiredInput) { @@ -372,7 +404,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen intent.putExtra(PassphraseDialogActivity.EXTRA_REQUIRED_INPUT, RequiredInputParcel.createRequiredPassphrase(requiredInput)); startActivityForResult(intent, REQUEST_CODE_PIN); - } catch (KeyNotFoundException e) { + } catch (PassphraseCacheService.KeyNotFoundException e) { throw new AssertionError( "tried to find passphrase for non-existing key. this is a programming error!"); } @@ -425,6 +457,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen doNfcInBackground(); } + protected void handleUsbDevice(UsbDevice device) throws IOException { + UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); + mJavacardDevice = new BaseJavacardDevice(new UsbTransport(device, usbManager)); + mJavacardDevice.connectToDevice(); + + doNfcInBackground(); + } + public boolean isNfcConnected() { return mJavacardDevice.isConnected(); } diff --git a/OpenKeychain/src/main/res/xml/usb_device_filter.xml b/OpenKeychain/src/main/res/xml/usb_device_filter.xml new file mode 100644 index 000000000..cc0599009 --- /dev/null +++ b/OpenKeychain/src/main/res/xml/usb_device_filter.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file -- cgit v1.2.3 From 3d538d885e0ed52640ea72d538fe1752a5ffe1f2 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Mon, 4 Apr 2016 01:31:14 +0600 Subject: OTG: Usb device manager fixes --- .../keychain/javacard/BaseJavacardDevice.java | 13 ++- .../javacard/CachingBaseJavacardDevice.java | 46 ----------- .../keychain/javacard/JavacardDevice.java | 2 + .../keychain/javacard/UsbConnectionManager.java | 94 ++++++++++++---------- .../ui/base/BaseSecurityTokenNfcActivity.java | 12 ++- 5 files changed, 74 insertions(+), 93 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java index 46f4c0443..f81d234b3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -22,7 +22,7 @@ public class BaseJavacardDevice implements JavacardDevice { 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 final Transport mTransport; + private Transport mTransport; private Passphrase mPin; private Passphrase mAdminPin; @@ -32,8 +32,7 @@ public class BaseJavacardDevice implements JavacardDevice { private boolean mPw3Validated; private boolean mTagHandlingEnabled; - public BaseJavacardDevice(final Transport mTransport) { - this.mTransport = mTransport; + public BaseJavacardDevice() { } private static String getHex(byte[] raw) { @@ -528,6 +527,9 @@ public class BaseJavacardDevice implements JavacardDevice { 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); @@ -722,4 +724,9 @@ public class BaseJavacardDevice implements JavacardDevice { return fp; } + @Override + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java deleted file mode 100644 index 5ad157de1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CachingBaseJavacardDevice.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; -import org.sufficientlysecure.keychain.util.Passphrase; - -import java.io.IOException; - -public class CachingBaseJavacardDevice extends BaseJavacardDevice { - private byte[] mFingerprintsCache; - private String mUserIdCache; - private byte[] mAidCache; - - public CachingBaseJavacardDevice(final Transport mTransport) { - super(mTransport); - } - - @Override - public byte[] getFingerprints() throws IOException { - if (mFingerprintsCache == null) { - mFingerprintsCache = super.getFingerprints(); - } - return mFingerprintsCache; - } - - @Override - public String getUserId() throws IOException { - if (mUserIdCache == null) { - mUserIdCache = super.getUserId(); - } - return mUserIdCache; - } - - @Override - public byte[] getAid() throws IOException { - if (mAidCache == null) { - mAidCache = super.getAid(); - } - return mAidCache; - } - - @Override - public void changeKey(final CanonicalizedSecretKey secretKey, final Passphrase passphrase) throws IOException { - super.changeKey(secretKey, passphrase); - mFingerprintsCache = null; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java index 04c2c0006..63d4d2ad7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java @@ -100,4 +100,6 @@ public interface JavacardDevice { * @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/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java index d140c771c..9dd3bc028 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java @@ -8,6 +8,8 @@ import android.content.Intent; import android.content.IntentFilter; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; +import android.os.Handler; +import android.os.Looper; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; @@ -17,50 +19,29 @@ 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 Activity mActivity; - private OnDiscoveredUsbDeviceListener mListener; private final Semaphore mRunning = new Semaphore(1); private final Set mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - - /** - * Receives broadcast when a supported USB device is attached, detached or - * when a permission to communicate to the device has been granted. - */ - 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)) { - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - - interceptIntent(intent); - - context.unregisterReceiver(mUsbReceiver); - } - } - }; - + 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 (true) { - mRunning.acquireUninterruptibly(); + 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); @@ -71,6 +52,7 @@ public class UsbConnectionManager { 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)); } @@ -81,6 +63,30 @@ public class UsbConnectionManager { } } }; + 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. + */ + 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)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); + + interceptIntent(intent); + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + private Handler handler = new Handler(Looper.getMainLooper()); public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; @@ -89,6 +95,16 @@ public class UsbConnectionManager { mWatchThread.start(); } + private static UsbDevice getDevice(UsbManager manager) { + HashMap deviceList = manager.getDeviceList(); + for (UsbDevice device : deviceList.values()) { + if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { + return device; + } + } + return null; + } + public void startListeningForDevices() { mRunning.release(); } @@ -100,7 +116,7 @@ public class UsbConnectionManager { public void interceptIntent(final Intent intent) { if (intent == null || intent.getAction() == null) return; switch (intent.getAction()) { - case UsbManager.ACTION_USB_DEVICE_ATTACHED: { + /*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); @@ -109,7 +125,7 @@ public class UsbConnectionManager { 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) @@ -121,15 +137,11 @@ public class UsbConnectionManager { } } - private static UsbDevice getDevice(UsbManager manager) { - HashMap deviceList = manager.getDeviceList(); - for (UsbDevice device : deviceList.values()) { - Log.d(LOG_TAG, device.getDeviceName() + " " + device.getDeviceId()); - if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { - Log.d(LOG_TAG, device.getDeviceName() + " OK"); - return device; - } + public void onDestroy() { + mStopped.set(true); + try { + mActivity.unregisterReceiver(mUsbReceiver); + } catch (IllegalArgumentException ignore) { } - return null; } } 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 573123daf..e3c331b0b 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 @@ -74,7 +74,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public JavacardDevice mJavacardDevice; + public JavacardDevice mJavacardDevice = new BaseJavacardDevice(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -451,7 +451,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mJavacardDevice = new BaseJavacardDevice(new NfcTransport(isoCard)); + mJavacardDevice.setTransport(new NfcTransport(isoCard)); mJavacardDevice.connectToDevice(); doNfcInBackground(); @@ -459,7 +459,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected void handleUsbDevice(UsbDevice device) throws IOException { UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mJavacardDevice = new BaseJavacardDevice(new UsbTransport(device, usbManager)); + mJavacardDevice.setTransport(new UsbTransport(device, usbManager)); mJavacardDevice.connectToDevice(); doNfcInBackground(); @@ -567,4 +567,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } return mAppInstalled; } + + @Override + protected void onDestroy() { + super.onDestroy(); + mUsbDispatcher.onDestroy(); + } } -- cgit v1.2.3 From 79a0918072e2b4a01f328cb8d8d2a0a8761394f6 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Wed, 6 Apr 2016 01:33:01 +0600 Subject: OTG: Fix usb transport --- .../keychain/javacard/BaseJavacardDevice.java | 20 ---- .../keychain/javacard/UsbConnectionManager.java | 9 +- .../keychain/javacard/UsbTransport.java | 122 +++++++++++++++++---- 3 files changed, 108 insertions(+), 43 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java index f81d234b3..796b4e1f3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java @@ -393,26 +393,6 @@ public class BaseJavacardDevice implements JavacardDevice { Arrays.fill(dataToSend, (byte) 0); } - - /** - * Return the key id 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 long key id of the requested key, or null if not found. - */ - public Long nfcGetKeyId(int idx) throws IOException { - byte[] fp = getMasterKeyFingerprint(idx); - if (fp == null) { - return null; - } - ByteBuffer buf = ByteBuffer.wrap(fp); - // skip first 12 bytes of the fingerprint - buf.position(12); - // the last eight bytes are the key id (big endian, which is default order in ByteBuffer) - return buf.getLong(); - } - /** * Return fingerprints of all keys from application specific data stored * on tag, or null if data not available. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java index 9dd3bc028..6b049159a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java @@ -80,13 +80,14 @@ public class UsbConnectionManager { false); Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - interceptIntent(intent); + if (permission) { + interceptIntent(intent); + } context.unregisterReceiver(mUsbReceiver); } } }; - private Handler handler = new Handler(Looper.getMainLooper()); public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; @@ -98,7 +99,7 @@ public class UsbConnectionManager { private static UsbDevice getDevice(UsbManager manager) { HashMap deviceList = manager.getDeviceList(); for (UsbDevice device : deviceList.values()) { - if (device.getVendorId() == 0x1050 && device.getProductId() == 0x0112) { + if (device.getVendorId() == 0x1050 && (device.getProductId() == 0x0112 || device.getProductId() == 0x0115)) { return device; } } @@ -139,9 +140,11 @@ public class UsbConnectionManager { public void onDestroy() { mStopped.set(true); + mRunning.release(); try { mActivity.unregisterReceiver(mUsbReceiver); } catch (IllegalArgumentException ignore) { } + mActivity = null; } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java index f2af34c39..07697f11e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java @@ -11,10 +11,14 @@ import android.support.annotation.Nullable; import android.util.Pair; import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; public class UsbTransport implements Transport { private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 1000; // 1 s + private static final int TIMEOUT = 20 * 1000; // 2 s private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; @@ -22,7 +26,7 @@ public class UsbTransport implements Transport { private final UsbEndpoint mBulkIn; private final UsbEndpoint mBulkOut; private final UsbDeviceConnection mConnection; - private byte counter = 0; + private byte mCounter = 0; public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { mUsbDevice = usbDevice; @@ -40,17 +44,60 @@ public class UsbTransport implements Transport { mConnection.claimInterface(mUsbInterface, true); // check result + powerOn(); + + setTimings(); + } + + private void setTimings() throws TransportIoException { + byte[] data = { + 0x6C, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, 0x00, 0x00 + }; + sendRaw(data); + data = receive(); + + data[0] = 0x61; + data[1] = 0x04; + data[2] = data[3] = data[4] = 0x00; + data[5] = 0x00; + data[6] = mCounter++; + data[7] = 0x00; + data[8] = data[9] = 0x00; + + data[13] = 1; + + sendRaw(data); + receive(); + } + + private void powerOff() throws TransportIoException { + final byte[] iccPowerOff = { + 0x63, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOff); + receive(); + } + + void powerOn() throws TransportIoException { final byte[] iccPowerOn = { 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, - counter++, - 0x03, + mCounter++, + 0x00, 0x00, 0x00 }; sendRaw(iccPowerOn); - receiveRaw(); - // Check result + receive(); } /** @@ -101,27 +148,58 @@ public class UsbTransport implements Transport { } @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException { + public byte[] sendAndReceive(byte[] data) throws TransportIoException { send(data); - return receive(); + byte[] bytes; + do { + bytes = receive(); + } while (isXfrBlockNotReady(bytes)); + + checkXfrBlockResult(bytes); + return Arrays.copyOfRange(bytes, 10, bytes.length); } - public void send(final byte[] d) throws TransportIoException { + public void send(byte[] d) throws TransportIoException { int l = d.length; byte[] data = Arrays.concatenate(new byte[]{ 0x6f, (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), 0x00, - counter++, - 0x01, + mCounter++, + 0x00, 0x00, 0x00}, d); - sendRaw(data); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } } public byte[] receive() throws TransportIoException { - final byte[] bytes = receiveRaw(); - return Arrays.copyOfRange(bytes, 10, bytes.length); + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new TransportIoException("USB error, failed to receive ccid header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; } private void sendRaw(final byte[] data) throws TransportIoException { @@ -131,14 +209,18 @@ public class UsbTransport implements Transport { } } - private byte[] receiveRaw() throws TransportIoException { - byte[] buffer = new byte[1024]; + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); + private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } + } - return Arrays.copyOfRange(buffer, 0, res); + private boolean isXfrBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; } } -- cgit v1.2.3 From 5e18b15775f4c6d9c563d61a71143320620e968e Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Wed, 6 Apr 2016 22:49:52 +0600 Subject: OTG: Rename 'javacard' package, methods, remove JavacardInterface --- .../keychain/javacard/CardException.java | 17 - .../keychain/javacard/KeyType.java | 48 -- .../keychain/javacard/NfcTransport.java | 31 - .../javacard/OnDiscoveredUsbDeviceListener.java | 7 - .../keychain/javacard/PinException.java | 7 - .../keychain/javacard/PinType.java | 16 - .../keychain/javacard/Transport.java | 11 - .../keychain/javacard/TransportIoException.java | 20 - .../keychain/javacard/UsbConnectionManager.java | 150 ----- .../keychain/javacard/UsbTransport.java | 226 ------- .../keychain/smartcard/CardException.java | 17 + .../keychain/smartcard/KeyType.java | 48 ++ .../keychain/smartcard/NfcTransport.java | 31 + .../smartcard/OnDiscoveredUsbDeviceListener.java | 7 + .../keychain/smartcard/PinException.java | 7 + .../keychain/smartcard/PinType.java | 16 + .../keychain/smartcard/SmartcardDevice.java | 707 +++++++++++++++++++++ .../keychain/smartcard/Transport.java | 11 + .../keychain/smartcard/TransportIoException.java | 20 + .../keychain/smartcard/UsbConnectionManager.java | 148 +++++ .../keychain/smartcard/UsbTransport.java | 226 +++++++ .../keychain/ui/CreateKeyActivity.java | 6 +- .../ui/CreateSecurityTokenImportResetFragment.java | 7 +- .../ui/SecurityTokenOperationActivity.java | 36 +- .../keychain/ui/ViewKeyActivity.java | 6 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 53 +- 26 files changed, 1291 insertions(+), 588 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java deleted file mode 100644 index 3e9e9f2ca..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/CardException.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java deleted file mode 100644 index e0190f8bd..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/KeyType.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; - -public enum KeyType { - SIGN(0, 0xB6, 0xCE, 0xC7), - ENCRYPT(1, 0xB8, 0xCF, 0xC8), - AUTH(2, 0xA4, 0xD0, 0xC9),; - - private final int mIdx; - private final int mSlot; - private final int mTimestampObjectId; - private final int mFingerprintObjectId; - - KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { - this.mIdx = idx; - this.mSlot = slot; - this.mTimestampObjectId = timestampObjectId; - this.mFingerprintObjectId = fingerprintObjectId; - } - - public static KeyType from(final CanonicalizedSecretKey key) { - if (key.canSign() || key.canCertify()) { - return SIGN; - } else if (key.canEncrypt()) { - return ENCRYPT; - } else if (key.canAuthenticate()) { - return AUTH; - } - return null; - } - - public int getIdx() { - return mIdx; - } - - public int getmSlot() { - return mSlot; - } - - public int getTimestampObjectId() { - return mTimestampObjectId; - } - - public int getmFingerprintObjectId() { - return mFingerprintObjectId; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java deleted file mode 100644 index 421b28aa8..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/NfcTransport.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -import nordpol.IsoCard; - -public class NfcTransport implements Transport { - // timeout is set to 100 seconds to avoid cancellation during calculation - private static final int TIMEOUT = 100 * 1000; - private final IsoCard mIsoCard; - - public NfcTransport(final IsoCard isoDep) throws IOException { - this.mIsoCard = isoDep; - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); - } - - @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { - return mIsoCard.transceive(data); - } - - @Override - public void release() { - } - - @Override - public boolean isConnected() { - return mIsoCard.isConnected(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java deleted file mode 100644 index 6104985be..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/OnDiscoveredUsbDeviceListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.hardware.usb.UsbDevice; - -public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java deleted file mode 100644 index 84a34f116..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -public class PinException extends CardException { - public PinException(final String detailMessage, final short responseCode) { - super(detailMessage, responseCode); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java deleted file mode 100644 index b6787a9e1..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/PinType.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -public enum PinType { - BASIC(0x81), - ADMIN(0x83),; - - private final int mMode; - - PinType(final int mode) { - this.mMode = mode; - } - - public int getmMode() { - return mMode; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java deleted file mode 100644 index 2d7dd3309..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/Transport.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public interface Transport { - byte[] sendAndReceive(byte[] data) throws IOException; - - void release(); - - boolean isConnected(); -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java deleted file mode 100644 index 2dfb7df94..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/TransportIoException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import java.io.IOException; - -public class TransportIoException extends IOException { - public TransportIoException() { - } - - public TransportIoException(final String detailMessage) { - super(detailMessage); - } - - public TransportIoException(final String message, final Throwable cause) { - super(message, cause); - } - - public TransportIoException(final Throwable cause) { - super(cause); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java deleted file mode 100644 index 6b049159a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbConnectionManager.java +++ /dev/null @@ -1,150 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.os.Handler; -import android.os.Looper; - -import org.sufficientlysecure.keychain.Constants; -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 mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - 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. - */ - 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)) { - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); - - if (permission) { - interceptIntent(intent); - } - - context.unregisterReceiver(mUsbReceiver); - } - } - }; - - 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 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 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; - } - } - - public void onDestroy() { - mStopped.set(true); - mRunning.release(); - try { - mActivity.unregisterReceiver(mUsbReceiver); - } catch (IllegalArgumentException ignore) { - } - mActivity = null; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java deleted file mode 100644 index 07697f11e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/UsbTransport.java +++ /dev/null @@ -1,226 +0,0 @@ -package org.sufficientlysecure.keychain.javacard; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Pair; - -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class UsbTransport implements Transport { - private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 2 s - - private final UsbManager mUsbManager; - private final UsbDevice mUsbDevice; - private final UsbInterface mUsbInterface; - private final UsbEndpoint mBulkIn; - private final UsbEndpoint mBulkOut; - private final UsbDeviceConnection mConnection; - private byte mCounter = 0; - - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { - mUsbDevice = usbDevice; - mUsbManager = usbManager; - - mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - // throw if any endpoint is null - - mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // check result - - powerOn(); - - setTimings(); - } - - private void setTimings() throws TransportIoException { - byte[] data = { - 0x6C, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, 0x00, 0x00 - }; - sendRaw(data); - data = receive(); - - data[0] = 0x61; - data[1] = 0x04; - data[2] = data[3] = data[4] = 0x00; - data[5] = 0x00; - data[6] = mCounter++; - data[7] = 0x00; - data[8] = data[9] = 0x00; - - data[13] = 1; - - sendRaw(data); - receive(); - } - - private void powerOff() throws TransportIoException { - final byte[] iccPowerOff = { - 0x63, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOff); - receive(); - } - - void powerOn() throws TransportIoException { - final byte[] iccPowerOn = { - 0x62, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOn); - receive(); - } - - /** - * Get first class 11 (Chip/Smartcard) interface for the device - * - * @param device {@link UsbDevice} which will be searched - * @return {@link UsbInterface} of smartcard or null if it doesn't exist - */ - @Nullable - private static UsbInterface getSmartCardInterface(final UsbDevice device) { - for (int i = 0; i < device.getInterfaceCount(); i++) { - final UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { - return anInterface; - } - } - return null; - } - - @NonNull - private static Pair getIoEndpoints(final UsbInterface usbInterface) { - UsbEndpoint bulkIn = null, bulkOut = null; - for (int i = 0; i < usbInterface.getEndpointCount(); i++) { - final UsbEndpoint endpoint = usbInterface.getEndpoint(i); - if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { - continue; - } - - if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { - bulkIn = endpoint; - } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { - bulkOut = endpoint; - } - } - return new Pair<>(bulkIn, bulkOut); - } - - @Override - public void release() { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - } - - @Override - public boolean isConnected() { - // TODO: redo - return mUsbManager.getDeviceList().containsValue(mUsbDevice); - } - - @Override - public byte[] sendAndReceive(byte[] data) throws TransportIoException { - send(data); - byte[] bytes; - do { - bytes = receive(); - } while (isXfrBlockNotReady(bytes)); - - checkXfrBlockResult(bytes); - return Arrays.copyOfRange(bytes, 10, bytes.length); - } - - public void send(byte[] d) throws TransportIoException { - int l = d.length; - byte[] data = Arrays.concatenate(new byte[]{ - 0x6f, - (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), - 0x00, - mCounter++, - 0x00, - 0x00, 0x00}, - d); - - int send = 0; - while (send < data.length) { - final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); - sendRaw(Arrays.copyOfRange(data, send, send + len)); - send += len; - } - } - - public byte[] receive() throws TransportIoException { - byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; - byte[] result = null; - int readBytes = 0, totalBytes = 0; - - do { - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); - } - if (result == null) { - if (res < 10) { - throw new TransportIoException("USB error, failed to receive ccid header"); - } - totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; - result = new byte[totalBytes]; - } - System.arraycopy(buffer, 0, result, readBytes, res); - readBytes += res; - } while (readBytes < totalBytes); - - return result; - } - - private void sendRaw(final byte[] data) throws TransportIoException { - final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); - if (tr1 != data.length) { - throw new TransportIoException("USB error, failed to send data " + tr1); - } - } - - private byte getStatus(byte[] bytes) { - return (byte) ((bytes[7] >> 6) & 0x03); - } - - private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { - final byte status = getStatus(bytes); - if (status != 0) { - throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); - } - } - - private boolean isXfrBlockNotReady(byte[] bytes) { - return getStatus(bytes) == 2; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java new file mode 100644 index 000000000..9ea67f711 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java @@ -0,0 +1,17 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java new file mode 100644 index 000000000..625e1e669 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java @@ -0,0 +1,48 @@ +package org.sufficientlysecure.keychain.smartcard; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java new file mode 100644 index 000000000..557c6f37d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -0,0 +1,31 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +import nordpol.IsoCard; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final IsoCard mIsoCard; + + public NfcTransport(final IsoCard isoDep) throws IOException { + this.mIsoCard = isoDep; + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + return mIsoCard.transceive(data); + } + + @Override + public void release() { + } + + @Override + public boolean isConnected() { + return mIsoCard.isConnected(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java new file mode 100644 index 000000000..46b503b42 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.hardware.usb.UsbDevice; + +public interface OnDiscoveredUsbDeviceListener { + void usbDeviceDiscovered(UsbDevice usbDevice); +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java new file mode 100644 index 000000000..58a7a31c9 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java @@ -0,0 +1,7 @@ +package org.sufficientlysecure.keychain.smartcard; + +public class PinException extends CardException { + public PinException(final String detailMessage, final short responseCode) { + super(detailMessage, responseCode); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java new file mode 100644 index 000000000..7601edcf3 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java @@ -0,0 +1,16 @@ +package org.sufficientlysecure.keychain.smartcard; + +public enum PinType { + BASIC(0x81), + ADMIN(0x83),; + + private final int mMode; + + PinType(final int mode) { + this.mMode = mode; + } + + public int getmMode() { + return mMode; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java new file mode 100644 index 000000000..cffc49555 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -0,0 +1,707 @@ +package org.sufficientlysecure.keychain.smartcard; + +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 SmartcardDevice { + // 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 SmartcardDevice() { + } + + 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 = communicate(opening); // activate connection + if (!response.endsWith(accepted)) { + throw new CardException("Initialization failed!", parseCardStatus(response)); + } + + byte[] pwStatusBytes = getPwStatusBytes(); + 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. + */ + 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. + * + * @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 = getPwStatusBytes(); + + if (pw == 0x81) { + if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + } else if (pw == 0x83) { + if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + } else { + throw new IOException("Invalid PW index for modify PIN operation"); + } + + 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 = communicate(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) { + verifyPin(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]; + } + + communicate(firstApdu + getHex(one)); + String second = communicate(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] + private void verifyPin(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 = tryPin(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) { + verifyPin(0x82); // (Verify PW1 for non-signing operations) + } + } else if (!mPw3Validated) { + 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); + + String response = communicate(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) { + verifyPin(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 = 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)); + } + } + + // 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] + private byte[] getPwStatusBytes() 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 getHolderName(communicate(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) { + verifyPin(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 = communicate(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 = 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)); + } + + // 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); + } + + private String getHolderName(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); + } + + /** + * Transceive data via NFC encoded as Hex + */ + // METHOD UPDATED [OK] + private String communicate(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[] generateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = communicate(generateKeyApdu); + String second = communicate(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 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); + } + + /** + * 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 = tryPin(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 = tryPin(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 = communicate(reactivate1); + String response2 = communicate(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; + } + + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java new file mode 100644 index 000000000..e01d7da16 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -0,0 +1,11 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public interface Transport { + byte[] sendAndReceive(byte[] data) throws IOException; + + void release(); + + boolean isConnected(); +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java new file mode 100644 index 000000000..544dd4045 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class TransportIoException extends IOException { + public TransportIoException() { + } + + public TransportIoException(final String detailMessage) { + super(detailMessage); + } + + public TransportIoException(final String message, final Throwable cause) { + super(message, cause); + } + + public TransportIoException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java new file mode 100644 index 000000000..c98d5d43f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -0,0 +1,148 @@ +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; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; + +import org.sufficientlysecure.keychain.Constants; +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 mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); + 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. + */ + 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)) { + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + Log.d(LOG_TAG, "ACTION_USB_PERMISSION: " + permission + " Device: " + deviceName); + + if (permission) { + interceptIntent(intent); + } + + context.unregisterReceiver(mUsbReceiver); + } + } + }; + + 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 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 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; + } + } + + public void onDestroy() { + mStopped.set(true); + mRunning.release(); + try { + mActivity.unregisterReceiver(mUsbReceiver); + } catch (IllegalArgumentException ignore) { + } + mActivity = null; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java new file mode 100644 index 000000000..08f296c25 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -0,0 +1,226 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +public class UsbTransport implements Transport { + private static final int CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 2 s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private final UsbInterface mUsbInterface; + private final UsbEndpoint mBulkIn; + private final UsbEndpoint mBulkOut; + private final UsbDeviceConnection mConnection; + private byte mCounter = 0; + + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // check result + + powerOn(); + + setTimings(); + } + + private void setTimings() throws TransportIoException { + byte[] data = { + 0x6C, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, 0x00, 0x00 + }; + sendRaw(data); + data = receive(); + + data[0] = 0x61; + data[1] = 0x04; + data[2] = data[3] = data[4] = 0x00; + data[5] = 0x00; + data[6] = mCounter++; + data[7] = 0x00; + data[8] = data[9] = 0x00; + + data[13] = 1; + + sendRaw(data); + receive(); + } + + private void powerOff() throws TransportIoException { + final byte[] iccPowerOff = { + 0x63, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOff); + receive(); + } + + void powerOn() throws TransportIoException { + final byte[] iccPowerOn = { + 0x62, + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + sendRaw(iccPowerOn); + receive(); + } + + /** + * Get first class 11 (Chip/Smartcard) interface for the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(final UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + final UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + @Override + public void release() { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + } + + @Override + public boolean isConnected() { + // TODO: redo + return mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + @Override + public byte[] sendAndReceive(byte[] data) throws TransportIoException { + send(data); + byte[] bytes; + do { + bytes = receive(); + } while (isXfrBlockNotReady(bytes)); + + checkXfrBlockResult(bytes); + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + public void send(byte[] d) throws TransportIoException { + int l = d.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + mCounter++, + 0x00, + 0x00, 0x00}, + d); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } + } + + public byte[] receive() throws TransportIoException { + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new TransportIoException("USB error, failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new TransportIoException("USB error, failed to receive ccid header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; + } + + private void sendRaw(final byte[] data) throws TransportIoException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new TransportIoException("USB error, failed to send data " + tr1); + } + } + + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } + + private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + } + } + + private boolean isXfrBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index b3f60ba41..268dbad02 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -149,9 +149,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { return; } - mScannedFingerprints = mJavacardDevice.getFingerprints(); - mNfcAid = mJavacardDevice.getAid(); - mNfcUserId = mJavacardDevice.getUserId(); + mScannedFingerprints = mSmartcardDevice.getFingerprints(); + mNfcAid = mSmartcardDevice.getAid(); + mNfcUserId = mSmartcardDevice.getUserId(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index 401db0b98..a0e93ed85 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import android.app.Activity; import android.content.Intent; import android.os.Bundle; -import android.os.Parcelable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -250,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.mJavacardDevice.getFingerprints(); - mTokenAid = mCreateKeyActivity.mJavacardDevice.getAid(); - mTokenUserId = mCreateKeyActivity.mJavacardDevice.getUserId(); + mTokenFingerprints = mCreateKeyActivity.mSmartcardDevice.getFingerprints(); + mTokenAid = mCreateKeyActivity.mSmartcardDevice.getAid(); + mTokenUserId = mCreateKeyActivity.mSmartcardDevice.getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 7e1474eb7..c68936577 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -162,7 +162,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity case NFC_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = mJavacardDevice.decryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -173,15 +173,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = mJavacardDevice.calculateSignature(hash, algo); + byte[] signedHash = mSmartcardDevice.calculateSignature(hash, algo); mInputParcel.addCryptoData(hash, signedHash); } break; } case NFC_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation - mJavacardDevice.setPin(new Passphrase("123456")); - mJavacardDevice.setAdminPin(new Passphrase("12345678")); + mSmartcardDevice.setPin(new Passphrase("123456")); + mSmartcardDevice.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -206,7 +206,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long keyGenerationTimestampMillis = key.getCreationTime().getTime(); long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); - byte[] tokenSerialNumber = Arrays.copyOf(mJavacardDevice.getAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); Passphrase passphrase; try { @@ -218,25 +218,25 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (key.canSign() || key.canCertify()) { if (shouldPutKey(key.getFingerprint(), 0)) { - mJavacardDevice.putKey(0xB6, key, passphrase); - mJavacardDevice.putData(0xCE, timestampBytes); - mJavacardDevice.putData(0xC7, key.getFingerprint()); + mSmartcardDevice.putKey(0xB6, key, passphrase); + mSmartcardDevice.putData(0xCE, timestampBytes); + mSmartcardDevice.putData(0xC7, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new signature key."); } } else if (key.canEncrypt()) { if (shouldPutKey(key.getFingerprint(), 1)) { - mJavacardDevice.putKey(0xB8, key, passphrase); - mJavacardDevice.putData(0xCF, timestampBytes); - mJavacardDevice.putData(0xC8, key.getFingerprint()); + mSmartcardDevice.putKey(0xB8, key, passphrase); + mSmartcardDevice.putData(0xCF, timestampBytes); + mSmartcardDevice.putData(0xC8, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new decryption key."); } } else if (key.canAuthenticate()) { if (shouldPutKey(key.getFingerprint(), 2)) { - mJavacardDevice.putKey(0xA4, key, passphrase); - mJavacardDevice.putData(0xD0, timestampBytes); - mJavacardDevice.putData(0xC9, key.getFingerprint()); + mSmartcardDevice.putKey(0xA4, key, passphrase); + mSmartcardDevice.putData(0xD0, timestampBytes); + mSmartcardDevice.putData(0xC9, key.getFingerprint()); } else { throw new IOException("Key slot occupied; token must be reset to put new authentication key."); } @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - mJavacardDevice.modifyPin(0x81, newPin); - mJavacardDevice.modifyPin(0x83, newAdminPin); + mSmartcardDevice.modifyPin(0x81, newPin); + mSmartcardDevice.modifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - mJavacardDevice.resetAndWipeToken(); + mSmartcardDevice.resetAndWipeToken(); break; } @@ -330,7 +330,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = mJavacardDevice.getMasterKeyFingerprint(idx); + byte[] tokenFingerprint = mSmartcardDevice.getMasterKeyFingerprint(idx); // Note: special case: This should not happen, but happens with // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 7d6ba5c8f..8ed2db9b7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -649,9 +649,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements @Override protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mJavacardDevice.getFingerprints(); - mNfcUserId = mJavacardDevice.getUserId(); - mNfcAid = mJavacardDevice.getAid(); + mNfcFingerprints = mSmartcardDevice.getFingerprints(); + mNfcUserId = mSmartcardDevice.getUserId(); + mNfcAid = mSmartcardDevice.getAid(); } @Override 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 e3c331b0b..8dde54a1f 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 @@ -21,7 +21,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; -import android.app.PendingIntent; import android.content.Intent; import android.content.pm.PackageManager; import android.hardware.usb.UsbDevice; @@ -35,12 +34,6 @@ import android.os.Bundle; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; -import org.sufficientlysecure.keychain.javacard.BaseJavacardDevice; -import org.sufficientlysecure.keychain.javacard.JavacardDevice; -import org.sufficientlysecure.keychain.javacard.NfcTransport; -import org.sufficientlysecure.keychain.javacard.OnDiscoveredUsbDeviceListener; -import org.sufficientlysecure.keychain.javacard.UsbConnectionManager; -import org.sufficientlysecure.keychain.javacard.UsbTransport; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; @@ -48,6 +41,11 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; +import org.sufficientlysecure.keychain.smartcard.NfcTransport; +import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; +import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; +import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; @@ -74,7 +72,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public JavacardDevice mJavacardDevice = new BaseJavacardDevice(); + public SmartcardDevice mSmartcardDevice = new SmartcardDevice(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -93,9 +91,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * Override to implement NFC operations (background thread) */ protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mJavacardDevice.getFingerprints(); - mNfcUserId = mJavacardDevice.getUserId(); - mNfcAid = mJavacardDevice.getAid(); + mNfcFingerprints = mSmartcardDevice.getFingerprints(); + mNfcUserId = mSmartcardDevice.getUserId(); + mNfcAid = mSmartcardDevice.getAid(); } /** @@ -141,7 +139,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void tagDiscovered(final Tag tag) { // Actual NFC operations are executed in doInBackground to not block the UI thread - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) return; new AsyncTask() { @Override @@ -178,7 +176,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void usbDeviceDiscovered(final UsbDevice device) { // Actual NFC operations are executed in doInBackground to not block the UI thread - if(!mTagHandlingEnabled) + if (!mTagHandlingEnabled) return; new AsyncTask() { @Override @@ -347,7 +345,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } // 6A82 app not installed on security token! case 0x6A82: { - if (mJavacardDevice.isFidesmoToken()) { + if (mSmartcardDevice.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -396,7 +394,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mJavacardDevice.setPin(passphrase); + mSmartcardDevice.setPin(passphrase); return; } @@ -421,7 +419,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mJavacardDevice.setPin(input.getPassphrase()); + mSmartcardDevice.setPin(input.getPassphrase()); break; } default: @@ -429,19 +427,19 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - /** Handle NFC communication and return a result. - * + /** + * Handle NFC communication and return a result. + *

* This method is called by onNewIntent above upon discovery of an NFC tag. * It handles initialization and login to the application, subsequently * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then * finishes the activity with an appropriate result. - * + *

* On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - * + *

* References to pages are generally related to the OpenPGP Application * on ISO SmartCard Systems specification. - * */ protected void handleTagDiscovered(Tag tag) throws IOException { @@ -451,22 +449,22 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); } - mJavacardDevice.setTransport(new NfcTransport(isoCard)); - mJavacardDevice.connectToDevice(); + mSmartcardDevice.setTransport(new NfcTransport(isoCard)); + mSmartcardDevice.connectToDevice(); doNfcInBackground(); } protected void handleUsbDevice(UsbDevice device) throws IOException { UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mJavacardDevice.setTransport(new UsbTransport(device, usbManager)); - mJavacardDevice.connectToDevice(); + mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + mSmartcardDevice.connectToDevice(); doNfcInBackground(); } public boolean isNfcConnected() { - return mJavacardDevice.isConnected(); + return mSmartcardDevice.isConnected(); } /** @@ -535,7 +533,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Ask user if she wants to install PGP onto her Fidesmo token - */ + */ private void promptFidesmoPgpInstall() { FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); @@ -552,6 +550,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Use the package manager to detect if an application is installed on the phone + * * @param uri an URI identifying the application's package * @return 'true' if the app is installed */ -- cgit v1.2.3 From 3798249570e97861793f5d0ebc695d94e8d5ddcd Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Thu, 7 Apr 2016 01:22:24 +0600 Subject: OTG: Implement hidden activity usb detection technique --- OpenKeychain/src/main/AndroidManifest.xml | 18 + .../keychain/javacard/BaseJavacardDevice.java | 712 --------------------- .../keychain/javacard/JavacardDevice.java | 105 --- .../keychain/smartcard/SmartcardDevice.java | 2 +- .../keychain/smartcard/UsbConnectionManager.java | 118 +--- .../keychain/ui/UsbEventReceiverActivity.java | 42 ++ .../ui/base/BaseSecurityTokenNfcActivity.java | 19 +- .../src/main/res/xml/usb_device_filter.xml | 25 +- 8 files changed, 108 insertions(+), 933 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/BaseJavacardDevice.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/javacard/JavacardDevice.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java diff --git a/OpenKeychain/src/main/AndroidManifest.xml b/OpenKeychain/src/main/AndroidManifest.xml index f485e964c..c6795d9f7 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" /> + + + + + + + + + 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 63d4d2ad7..000000000 --- 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 cffc49555..b86c3cf4c 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 c98d5d43f..8a6971fe6 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 mProcessedDevices = Collections.newSetFromMap(new ConcurrentHashMap()); - 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 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 000000000..9df5800b5 --- /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 8dde54a1f..a6f8c0b0f 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 cc0599009..789e4ffb7 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 @@ + + - + + + + + + + + + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3 From b5eb6468fecfc16ea041eb0f4bf48c37ec2e81f2 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 8 Apr 2016 00:41:53 +0600 Subject: OTG: Add support for persistent usb connection No need to reinsert token on each operation --- .../keychain/smartcard/NfcTransport.java | 5 ++ .../keychain/smartcard/SmartcardDevice.java | 49 ++++++++++++---- .../keychain/smartcard/Transport.java | 2 + .../keychain/smartcard/UsbConnectionManager.java | 8 +++ .../keychain/smartcard/UsbTransport.java | 39 ++++--------- .../ui/CreateSecurityTokenImportResetFragment.java | 6 +- .../ui/SecurityTokenOperationActivity.java | 67 ++++++++++++++++------ .../ui/base/BaseSecurityTokenNfcActivity.java | 67 ++++++---------------- 8 files changed, 132 insertions(+), 111 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index 557c6f37d..d56f5b5bf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -28,4 +28,9 @@ public class NfcTransport implements Transport { public boolean isConnected() { return mIsoCard.isConnected(); } + + @Override + public boolean allowPersistentConnection() { + return false; + } } 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 b86c3cf4c..4420c0c88 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -32,7 +32,32 @@ public class SmartcardDevice { private boolean mPw3Validated; private boolean mTagHandlingEnabled; - public SmartcardDevice() { + protected SmartcardDevice() { + } + + public static SmartcardDevice getInstance() { + return LazyHolder.mSmartcardDevice; + } + + // METHOD UPDATED [OK] + private String getHolderName(String 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; + } catch (IndexOutOfBoundsException e) { + // try-catch for https://github.com/FluffyKaon/OpenPGP-Card + // Note: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! + + Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); + return ""; + } } private static String getHex(byte[] raw) { @@ -541,15 +566,8 @@ public class SmartcardDevice { return Hex.decode(signature); } - private String getHolderName(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); + public boolean isConnected() { + return mTransport != null && mTransport.isConnected(); } /** @@ -560,8 +578,8 @@ public class SmartcardDevice { return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); } - public boolean isConnected() { - return mTransport.isConnected(); + public Transport getTransport() { + return mTransport; } // NEW METHOD [OK] @@ -702,6 +720,13 @@ public class SmartcardDevice { public void setTransport(Transport mTransport) { this.mTransport = mTransport; + } + + public boolean allowPersistentConnection() { + return mTransport != null && mTransport.allowPersistentConnection(); + } + private static class LazyHolder { + private static final SmartcardDevice mSmartcardDevice = new SmartcardDevice(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index e01d7da16..9b0ad2998 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -8,4 +8,6 @@ public interface Transport { void release(); boolean isConnected(); + + boolean allowPersistentConnection(); } 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 8a6971fe6..e6fb5b04d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -51,4 +51,12 @@ public class UsbConnectionManager { public void onStop() { mActivity.unregisterReceiver(mUsbReceiver); } + + public void rescanDevices() { + final SmartcardDevice smartcardDevice = SmartcardDevice.getInstance(); + if (smartcardDevice.isConnected() + && (smartcardDevice.getTransport() instanceof UsbTransport)) { + mListener.usbDeviceDiscovered(((UsbTransport) smartcardDevice.getTransport()).getUsbDevice()); + } + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 08f296c25..2d435ccbe 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -12,6 +12,8 @@ import android.util.Pair; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -45,33 +47,7 @@ public class UsbTransport implements Transport { // check result powerOn(); - - setTimings(); - } - - private void setTimings() throws TransportIoException { - byte[] data = { - 0x6C, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, 0x00, 0x00 - }; - sendRaw(data); - data = receive(); - - data[0] = 0x61; - data[1] = 0x04; - data[2] = data[3] = data[4] = 0x00; - data[5] = 0x00; - data[6] = mCounter++; - data[7] = 0x00; - data[8] = data[9] = 0x00; - - data[13] = 1; - - sendRaw(data); - receive(); + Log.d(Constants.TAG, "Usb transport connected"); } private void powerOff() throws TransportIoException { @@ -147,6 +123,11 @@ public class UsbTransport implements Transport { return mUsbManager.getDeviceList().containsValue(mUsbDevice); } + @Override + public boolean allowPersistentConnection() { + return true; + } + @Override public byte[] sendAndReceive(byte[] data) throws TransportIoException { send(data); @@ -223,4 +204,8 @@ public class UsbTransport implements Transport { private boolean isXfrBlockNotReady(byte[] bytes) { return getStatus(bytes) == 2; } + + public UsbDevice getUsbDevice() { + return mUsbDevice; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index a0e93ed85..aba06ac47 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -249,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.mSmartcardDevice.getFingerprints(); - mTokenAid = mCreateKeyActivity.mSmartcardDevice.getAid(); - mTokenUserId = mCreateKeyActivity.mSmartcardDevice.getUserId(); + mTokenFingerprints = mCreateKeyActivity.getSmartcardDevice().getFingerprints(); + mTokenAid = mCreateKeyActivity.getSmartcardDevice().getAid(); + mTokenUserId = mCreateKeyActivity.getSmartcardDevice().getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index c68936577..5f0093678 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -140,6 +140,32 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); + checkPinAvailability(); + } else { + // No need for pin, rescan USB devices + mUsbDispatcher.rescanDevices(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (REQUEST_CODE_PIN == requestCode) { + checkPinAvailability(); + } + } + + private void checkPinAvailability() { + try { + Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, + mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); + if (passphrase != null) { + // Rescan USB devices + mUsbDispatcher.rescanDevices(); + } + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new AssertionError( + "tried to find passphrase for non-existing key. this is a programming error!"); } } @@ -275,28 +301,33 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - // check all 200ms if Security Token has been taken away - while (true) { - if (isNfcConnected()) { - try { - Thread.sleep(200); - } catch (InterruptedException ignored) { + if (mSmartcardDevice.allowPersistentConnection()) { + // Just close + finish(); + } else { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + // check all 200ms if Security Token has been taken away + while (true) { + if (isNfcConnected()) { + try { + Thread.sleep(200); + } catch (InterruptedException ignored) { + } + } else { + return null; } - } else { - return null; } } - } - @Override - protected void onPostExecute(Void result) { - super.onPostExecute(result); - finish(); - } - }.execute(); + @Override + protected void onPostExecute(Void result) { + super.onPostExecute(result); + finish(); + } + }.execute(); + } } /** 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 a6f8c0b0f..ecec98aaf 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 @@ -31,7 +31,6 @@ import android.nfc.TagLostException; import android.os.AsyncTask; import android.os.Bundle; -import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException; @@ -41,9 +40,9 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.NfcTransport; import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; +import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -72,7 +71,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - public SmartcardDevice mSmartcardDevice = new SmartcardDevice(); + protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance(); protected TagDispatcher mTagDispatcher; protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; @@ -175,7 +174,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public void usbDeviceDiscovered(final UsbDevice device) { - // Actual NFC operations are executed in doInBackground to not block the UI thread + // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; new AsyncTask() { @@ -372,7 +371,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Log.d(Constants.TAG, "BaseNfcActivity.onPause"); mTagDispatcher.disableExclusiveNfc(); -// mUsbDispatcher.stopListeningForDevices(); } /** @@ -453,10 +451,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } protected void handleUsbDevice(UsbDevice device) throws IOException { - UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); - mSmartcardDevice.connectToDevice(); - + // Don't reconnect if device was already connected + if (!mSmartcardDevice.isConnected() + || !(mSmartcardDevice.getTransport() instanceof UsbTransport) + || !((UsbTransport) mSmartcardDevice.getTransport()).getUsbDevice().equals(device)) { + UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); + + mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + mSmartcardDevice.connectToDevice(); + } doNfcInBackground(); } @@ -464,48 +467,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return mSmartcardDevice.isConnected(); } - /** - * Parses out the status word from a JavaCard response string. - * - * @param response A hex string with the response from the token - * @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; - } - } - - public String getHolderName(String 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; - } catch (IndexOutOfBoundsException e) { - // try-catch for https://github.com/FluffyKaon/OpenPGP-Card - // Note: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! - - Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); - return ""; - } - } - - public static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - public class IsoDepNotSupportedException extends IOException { public IsoDepNotSupportedException(String detailMessage) { @@ -575,4 +536,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onStart(); mUsbDispatcher.onStart(); } + + public SmartcardDevice getSmartcardDevice() { + return mSmartcardDevice; + } } -- cgit v1.2.3 From 4d9ce8e95b4604f753aa5f49fe2c243dc73a13a9 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 13:13:02 +0600 Subject: OTG: Refactor persistent connections, naming --- .../keychain/smartcard/NfcTransport.java | 57 ++++++++++-- .../keychain/smartcard/SmartcardDevice.java | 4 +- .../keychain/smartcard/Transport.java | 2 + .../keychain/smartcard/UsbConnectionManager.java | 8 -- .../keychain/smartcard/UsbTransport.java | 61 +++++++----- .../keychain/ui/CreateKeyActivity.java | 2 +- .../ui/SecurityTokenOperationActivity.java | 12 +-- .../keychain/ui/ViewKeyActivity.java | 2 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 102 ++++++--------------- 9 files changed, 132 insertions(+), 118 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index d56f5b5bf..e47ba5360 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -1,18 +1,22 @@ package org.sufficientlysecure.keychain.smartcard; +import android.nfc.Tag; + +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; + import java.io.IOException; import nordpol.IsoCard; +import nordpol.android.AndroidCard; public class NfcTransport implements Transport { // timeout is set to 100 seconds to avoid cancellation during calculation private static final int TIMEOUT = 100 * 1000; - private final IsoCard mIsoCard; + private final Tag mTag; + private IsoCard mIsoCard; - public NfcTransport(final IsoCard isoDep) throws IOException { - this.mIsoCard = isoDep; - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); + public NfcTransport(Tag tag) { + this.mTag = tag; } @Override @@ -26,11 +30,52 @@ public class NfcTransport implements Transport { @Override public boolean isConnected() { - return mIsoCard.isConnected(); + return mIsoCard != null && mIsoCard.isConnected(); } @Override public boolean allowPersistentConnection() { return false; } + + /** + * Handle NFC communication and return a result. + *

+ * On general communication, see also + * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx + *

+ * References to pages are generally related to the OpenPGP Application + * on ISO SmartCard Systems specification. + */ + @Override + public void connect() throws IOException { + mIsoCard = AndroidCard.get(mTag); + if (mIsoCard == null) { + throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + } + + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final NfcTransport that = (NfcTransport) o; + + if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; + if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = mTag != null ? mTag.hashCode() : 0; + result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); + return result; + } } 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 4420c0c88..286a38d1f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -112,6 +112,8 @@ public class SmartcardDevice { // METHOD UPDATED OK public void connectToDevice() throws IOException { + mTransport.connect(); + // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; @@ -722,7 +724,7 @@ public class SmartcardDevice { this.mTransport = mTransport; } - public boolean allowPersistentConnection() { + public boolean isPersistentConnectionAllowed() { return mTransport != null && mTransport.allowPersistentConnection(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index 9b0ad2998..5252a95d0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -10,4 +10,6 @@ public interface Transport { boolean isConnected(); boolean allowPersistentConnection(); + + void connect() 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 e6fb5b04d..8a6971fe6 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java @@ -51,12 +51,4 @@ public class UsbConnectionManager { public void onStop() { mActivity.unregisterReceiver(mUsbReceiver); } - - public void rescanDevices() { - final SmartcardDevice smartcardDevice = SmartcardDevice.getInstance(); - if (smartcardDevice.isConnected() - && (smartcardDevice.getTransport() instanceof UsbTransport)) { - mListener.usbDeviceDiscovered(((UsbTransport) smartcardDevice.getTransport()).getUsbDevice()); - } - } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 2d435ccbe..2a21c10dd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -15,6 +15,7 @@ import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.util.Log; +import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -24,30 +25,15 @@ public class UsbTransport implements Transport { private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; - private final UsbInterface mUsbInterface; - private final UsbEndpoint mBulkIn; - private final UsbEndpoint mBulkOut; - private final UsbDeviceConnection mConnection; + private UsbInterface mUsbInterface; + private UsbEndpoint mBulkIn; + private UsbEndpoint mBulkOut; + private UsbDeviceConnection mConnection; private byte mCounter = 0; - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) throws TransportIoException { + public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) { mUsbDevice = usbDevice; mUsbManager = usbManager; - - mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - // throw if any endpoint is null - - mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // check result - - powerOn(); - Log.d(Constants.TAG, "Usb transport connected"); } private void powerOff() throws TransportIoException { @@ -120,7 +106,7 @@ public class UsbTransport implements Transport { @Override public boolean isConnected() { // TODO: redo - return mUsbManager.getDeviceList().containsValue(mUsbDevice); + return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); } @Override @@ -128,6 +114,24 @@ public class UsbTransport implements Transport { return true; } + @Override + public void connect() throws IOException { + mUsbInterface = getSmartCardInterface(mUsbDevice); + // throw if mUsbInterface == null + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + // throw if any endpoint is null + + mConnection = mUsbManager.openDevice(mUsbDevice); + // throw if connection is null + mConnection.claimInterface(mUsbInterface, true); + // check result + + powerOn(); + Log.d(Constants.TAG, "Usb transport connected"); + } + @Override public byte[] sendAndReceive(byte[] data) throws TransportIoException { send(data); @@ -208,4 +212,19 @@ public class UsbTransport implements Transport { public UsbDevice getUsbDevice() { return mUsbDevice; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final UsbTransport that = (UsbTransport) o; + + return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null; + } + + @Override + public int hashCode() { + return mUsbDevice != null ? mUsbDevice.hashCode() : 0; + } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 268dbad02..07d5be821 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -155,7 +155,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void onNfcPostExecute() { + protected void onSmartcardPostExecute() { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 5f0093678..884f33365 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -142,8 +142,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { - // No need for pin, rescan USB devices - mUsbDispatcher.rescanDevices(); + checkDeviceConnection(); } } @@ -160,8 +159,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); if (passphrase != null) { - // Rescan USB devices - mUsbDispatcher.rescanDevices(); + checkDeviceConnection(); } } catch (PassphraseCacheService.KeyNotFoundException e) { throw new AssertionError( @@ -175,7 +173,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onNfcPreExecute() { + public void onSmartcardPreExecute() { // start with indeterminate progress vAnimator.setDisplayedChild(1); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); @@ -293,7 +291,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected final void onNfcPostExecute() { + protected final void onSmartcardPostExecute() { handleResult(mInputParcel); // show finish @@ -301,7 +299,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - if (mSmartcardDevice.allowPersistentConnection()) { + if (mSmartcardDevice.isPersistentConnectionAllowed()) { // Just close finish(); } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 8ed2db9b7..5bf81f1aa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -655,7 +655,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void onNfcPostExecute() { + protected void onSmartcardPostExecute() { long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); 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 ecec98aaf..5e1592346 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 @@ -43,6 +43,7 @@ import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.smartcard.NfcTransport; import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; +import org.sufficientlysecure.keychain.smartcard.Transport; import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; @@ -83,7 +84,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to change UI before NFC handling (UI thread) */ - protected void onNfcPreExecute() { + protected void onSmartcardPreExecute() { } /** @@ -98,7 +99,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to handle result of NFC operations (UI thread) */ - protected void onNfcPostExecute() { + protected void onSmartcardPostExecute() { final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); @@ -140,54 +141,34 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity // Actual NFC operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; - new AsyncTask() { - @Override - protected void onPreExecute() { - super.onPreExecute(); - onNfcPreExecute(); - } - - @Override - protected IOException doInBackground(Void... params) { - try { - handleTagDiscovered(tag); - } catch (IOException e) { - return e; - } - - return null; - } - - @Override - protected void onPostExecute(IOException exception) { - super.onPostExecute(exception); - - if (exception != null) { - handleNfcError(exception); - return; - } - onNfcPostExecute(); - } - }.execute(); + smartcardDiscovered(new NfcTransport(tag)); } - public void usbDeviceDiscovered(final UsbDevice device) { // Actual USB operations are executed in doInBackground to not block the UI thread + if (!mTagHandlingEnabled) + return; + + UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); + smartcardDiscovered(new UsbTransport(device, usbManager)); + } + + public void smartcardDiscovered(final Transport transport) { + // Actual Smartcard operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; new AsyncTask() { @Override protected void onPreExecute() { super.onPreExecute(); - onNfcPreExecute(); + onSmartcardPreExecute(); } @Override protected IOException doInBackground(Void... params) { try { - handleUsbDevice(device); + handleSmartcard(transport); } catch (IOException e) { return e; } @@ -200,11 +181,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onPostExecute(exception); if (exception != null) { - handleNfcError(exception); + handleSmartcardError(exception); return; } - onNfcPostExecute(); + onSmartcardPostExecute(); } }.execute(); } @@ -256,7 +237,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mTagDispatcher.interceptIntent(intent); } - private void handleNfcError(IOException e) { + private void handleSmartcardError(IOException e) { if (e instanceof TagLostException) { onNfcError(getString(R.string.security_token_error_tag_lost)); @@ -422,42 +403,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - /** - * Handle NFC communication and return a result. - *

- * This method is called by onNewIntent above upon discovery of an NFC tag. - * It handles initialization and login to the application, subsequently - * calls either nfcCalculateSignature() or nfcDecryptSessionKey(), then - * finishes the activity with an appropriate result. - *

- * On general communication, see also - * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - *

- * References to pages are generally related to the OpenPGP Application - * on ISO SmartCard Systems specification. - */ - protected void handleTagDiscovered(Tag tag) throws IOException { - - // Connect to the detected tag, setting a couple of settings - IsoCard isoCard = AndroidCard.get(tag); - if (isoCard == null) { - throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); - } - - mSmartcardDevice.setTransport(new NfcTransport(isoCard)); - mSmartcardDevice.connectToDevice(); - - doNfcInBackground(); - } - - protected void handleUsbDevice(UsbDevice device) throws IOException { + protected void handleSmartcard(Transport transport) throws IOException { // Don't reconnect if device was already connected - if (!mSmartcardDevice.isConnected() - || !(mSmartcardDevice.getTransport() instanceof UsbTransport) - || !((UsbTransport) mSmartcardDevice.getTransport()).getUsbDevice().equals(device)) { - UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - - mSmartcardDevice.setTransport(new UsbTransport(device, usbManager)); + if (!(mSmartcardDevice.isConnected() + && mSmartcardDevice.getTransport().equals(transport))) { + mSmartcardDevice.setTransport(transport); mSmartcardDevice.connectToDevice(); } doNfcInBackground(); @@ -467,7 +417,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return mSmartcardDevice.isConnected(); } - public class IsoDepNotSupportedException extends IOException { + public static class IsoDepNotSupportedException extends IOException { public IsoDepNotSupportedException(String detailMessage) { super(detailMessage); @@ -540,4 +490,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity public SmartcardDevice getSmartcardDevice() { return mSmartcardDevice; } + + protected void checkDeviceConnection() { + if (mSmartcardDevice.isConnected() && mSmartcardDevice.isPersistentConnectionAllowed()) { + this.smartcardDiscovered(mSmartcardDevice.getTransport()); + } + } } -- cgit v1.2.3 From 38a1c2d3ab5a9fe0fa74acbd8301d671eab35d59 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 13:23:29 +0600 Subject: OTG: refactor, change nfc prefix to smartcard --- .../keychain/remote/ApiPendingIntentFactory.java | 6 +- .../service/input/RequiredInputParcel.java | 18 ++--- .../keychain/ui/CreateKeyActivity.java | 2 +- .../ui/SecurityTokenOperationActivity.java | 22 +++--- .../keychain/ui/ViewKeyActivity.java | 2 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 78 +++++++++++----------- .../keychain/ui/base/CryptoOperationHelper.java | 6 +- .../keychain/pgp/PgpKeyOperationTest.java | 11 ++- 8 files changed, 73 insertions(+), 72 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 690a4d1a2..03789f118 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -50,9 +50,9 @@ public class ApiPendingIntentFactory { CryptoInputParcel cryptoInput) { switch (requiredInput.mType) { - case NFC_MOVE_KEY_TO_CARD: - case NFC_DECRYPT: - case NFC_SIGN: { + case SMARTCARD_MOVE_KEY_TO_CARD: + case SMARTCARD_DECRYPT: + case SMARTCARD_SIGN: { return createNfcOperationPendingIntent(data, requiredInput, cryptoInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 429d7a7e5..24aa6f118 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -14,8 +14,8 @@ import java.util.Date; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, NFC_SIGN, NFC_DECRYPT, - NFC_MOVE_KEY_TO_CARD, NFC_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, + PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SMARTCARD_SIGN, SMARTCARD_DECRYPT, + SMARTCARD_MOVE_KEY_TO_CARD, SMARTCARD_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, } public Date mSignatureTime; @@ -92,19 +92,19 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createNfcSignOperation( long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { - return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcDecryptOperation( long masterKeyId, long subKeyId, byte[] encryptedSessionKey) { - return new RequiredInputParcel(RequiredInputType.NFC_DECRYPT, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_DECRYPT, new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcReset() { - return new RequiredInputParcel(RequiredInputType.NFC_RESET_CARD, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_RESET_CARD, null, null, null, null, null); } @@ -209,7 +209,7 @@ public class RequiredInputParcel implements Parcelable { signAlgos[i] = mSignAlgos.get(i); } - return new RequiredInputParcel(RequiredInputType.NFC_SIGN, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId); } @@ -222,7 +222,7 @@ public class RequiredInputParcel implements Parcelable { if (!mSignatureTime.equals(input.mSignatureTime)) { throw new AssertionError("input times must match, this is a programming error!"); } - if (input.mType != RequiredInputType.NFC_SIGN) { + if (input.mType != RequiredInputType.SMARTCARD_SIGN) { throw new AssertionError("operation types must match, this is a progrmming error!"); } @@ -264,7 +264,7 @@ public class RequiredInputParcel implements Parcelable { ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); // We need to pass in a subkey here... - return new RequiredInputParcel(RequiredInputType.NFC_MOVE_KEY_TO_CARD, + return new RequiredInputParcel(RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD, inputData, null, null, mMasterKeyId, buf.getLong()); } @@ -287,7 +287,7 @@ public class RequiredInputParcel implements Parcelable { if (!mMasterKeyId.equals(input.mMasterKeyId)) { throw new AssertionError("Master keys must match, this is a programming error!"); } - if (input.mType != RequiredInputType.NFC_MOVE_KEY_TO_CARD) { + if (input.mType != RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD) { throw new AssertionError("Operation types must match, this is a programming error!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 07d5be821..a9d259b00 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -143,7 +143,7 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void doNfcInBackground() throws IOException { + protected void doSmartcardInBackground() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 884f33365..ed6e3faf3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -137,8 +137,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private void obtainPassphraseIfRequired() { // obtain passphrase for this subkey - if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_MOVE_KEY_TO_CARD - && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.NFC_RESET_CARD) { + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD + && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { @@ -180,10 +180,10 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void doNfcInBackground() throws IOException { + protected void doSmartcardInBackground() throws IOException { switch (mRequiredInput.mType) { - case NFC_DECRYPT: { + case SMARTCARD_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); @@ -191,7 +191,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case NFC_SIGN: { + case SMARTCARD_SIGN: { mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { @@ -202,7 +202,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case NFC_MOVE_KEY_TO_CARD: { + case SMARTCARD_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation mSmartcardDevice.setPin(new Passphrase("123456")); mSmartcardDevice.setAdminPin(new Passphrase("12345678")); @@ -278,7 +278,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity break; } - case NFC_RESET_CARD: { + case SMARTCARD_RESET_CARD: { mSmartcardDevice.resetAndWipeToken(); break; @@ -308,7 +308,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity protected Void doInBackground(Void... params) { // check all 200ms if Security Token has been taken away while (true) { - if (isNfcConnected()) { + if (isSmartcardConnected()) { try { Thread.sleep(200); } catch (InterruptedException ignored) { @@ -340,7 +340,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void onNfcError(String error) { + protected void onSmartcardError(String error) { pauseTagHandling(); vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); @@ -350,8 +350,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onNfcPinError(String error) { - onNfcError(error); + public void onSmartcardPinError(String error) { + onSmartcardError(error); // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 5bf81f1aa..dd753a431 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -647,7 +647,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void doNfcInBackground() throws IOException { + protected void doSmartcardInBackground() throws IOException { mNfcFingerprints = mSmartcardDevice.getFingerprints(); mNfcUserId = mSmartcardDevice.getUserId(); 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 5e1592346..e138af895 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 @@ -59,8 +59,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import java.io.IOException; -import nordpol.IsoCard; -import nordpol.android.AndroidCard; import nordpol.android.OnDiscoveredTagListener; import nordpol.android.TagDispatcher; @@ -77,9 +75,9 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected UsbConnectionManager mUsbDispatcher; private boolean mTagHandlingEnabled; - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; + private byte[] mSmartcardFingerprints; + private String mSmartcardUserId; + private byte[] mSmartcardAid; /** * Override to change UI before NFC handling (UI thread) @@ -90,10 +88,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to implement NFC operations (background thread) */ - protected void doNfcInBackground() throws IOException { - mNfcFingerprints = mSmartcardDevice.getFingerprints(); - mNfcUserId = mSmartcardDevice.getUserId(); - mNfcAid = mSmartcardDevice.getAid(); + protected void doSmartcardInBackground() throws IOException { + mSmartcardFingerprints = mSmartcardDevice.getFingerprints(); + mSmartcardUserId = mSmartcardDevice.getUserId(); + mSmartcardAid = mSmartcardDevice.getAid(); } /** @@ -101,7 +99,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity */ protected void onSmartcardPostExecute() { - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSmartcardFingerprints); try { CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( @@ -110,15 +108,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSmartcardAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSmartcardUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSmartcardFingerprints); startActivity(intent); } catch (PgpKeyNotFoundException e) { Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mNfcAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mNfcUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mSmartcardAid); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mSmartcardUserId); + intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mSmartcardFingerprints); startActivity(intent); } } @@ -126,15 +124,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to use something different than Notify (UI thread) */ - protected void onNfcError(String error) { + protected void onSmartcardError(String error) { Notify.create(this, error, Style.WARN).show(); } /** * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) */ - protected void onNfcPinError(String error) { - onNfcError(error); + protected void onSmartcardPinError(String error) { + onSmartcardError(error); } public void tagDiscovered(final Tag tag) { @@ -240,12 +238,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private void handleSmartcardError(IOException e) { if (e instanceof TagLostException) { - onNfcError(getString(R.string.security_token_error_tag_lost)); + onSmartcardError(getString(R.string.security_token_error_tag_lost)); return; } if (e instanceof IsoDepNotSupportedException) { - onNfcError(getString(R.string.security_token_error_iso_dep_not_supported)); + onSmartcardError(getString(R.string.security_token_error_iso_dep_not_supported)); return; } @@ -260,7 +258,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if ((status & (short) 0xFFF0) == 0x63C0) { int tries = status & 0x000F; // hook to do something different when PIN is wrong - onNfcPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); + onSmartcardPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); return; } @@ -269,56 +267,56 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity // These errors should not occur in everyday use; if they are returned, it means we // made a mistake sending data to the token, or the token is misbehaving. case 0x6A80: { - onNfcError(getString(R.string.security_token_error_bad_data)); + onSmartcardError(getString(R.string.security_token_error_bad_data)); break; } case 0x6883: { - onNfcError(getString(R.string.security_token_error_chaining_error)); + onSmartcardError(getString(R.string.security_token_error_chaining_error)); break; } case 0x6B00: { - onNfcError(getString(R.string.security_token_error_header, "P1/P2")); + onSmartcardError(getString(R.string.security_token_error_header, "P1/P2")); break; } case 0x6D00: { - onNfcError(getString(R.string.security_token_error_header, "INS")); + onSmartcardError(getString(R.string.security_token_error_header, "INS")); break; } case 0x6E00: { - onNfcError(getString(R.string.security_token_error_header, "CLA")); + onSmartcardError(getString(R.string.security_token_error_header, "CLA")); break; } // These error conditions are more likely to be experienced by an end user. case 0x6285: { - onNfcError(getString(R.string.security_token_error_terminated)); + onSmartcardError(getString(R.string.security_token_error_terminated)); break; } case 0x6700: { - onNfcPinError(getString(R.string.security_token_error_wrong_length)); + onSmartcardPinError(getString(R.string.security_token_error_wrong_length)); break; } case 0x6982: { - onNfcError(getString(R.string.security_token_error_security_not_satisfied)); + onSmartcardError(getString(R.string.security_token_error_security_not_satisfied)); break; } case 0x6983: { - onNfcError(getString(R.string.security_token_error_authentication_blocked)); + onSmartcardError(getString(R.string.security_token_error_authentication_blocked)); break; } case 0x6985: { - onNfcError(getString(R.string.security_token_error_conditions_not_satisfied)); + onSmartcardError(getString(R.string.security_token_error_conditions_not_satisfied)); break; } // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { - onNfcError(getString(R.string.security_token_error_data_not_found)); + onSmartcardError(getString(R.string.security_token_error_data_not_found)); break; } // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an // unhandled exception on the security token. case 0x6F00: { - onNfcError(getString(R.string.security_token_error_unknown)); + onSmartcardError(getString(R.string.security_token_error_unknown)); break; } // 6A82 app not installed on security token! @@ -331,12 +329,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity promptFidesmoAppInstall(); } } else { // Other (possibly) compatible hardware - onNfcError(getString(R.string.security_token_error_pgp_app_not_installed)); + onSmartcardError(getString(R.string.security_token_error_pgp_app_not_installed)); } break; } default: { - onNfcError(getString(R.string.security_token_error, e.getMessage())); + onSmartcardError(getString(R.string.security_token_error, e.getMessage())); break; } } @@ -410,10 +408,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mSmartcardDevice.setTransport(transport); mSmartcardDevice.connectToDevice(); } - doNfcInBackground(); + doSmartcardInBackground(); } - public boolean isNfcConnected() { + public boolean isSmartcardConnected() { return mSmartcardDevice.isConnected(); } @@ -491,6 +489,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return mSmartcardDevice; } + /** + * Run smartcard routines if last used token is connected and supports + * persistent connections + */ protected void checkDeviceConnection() { if (mSmartcardDevice.isConnected() && mSmartcardDevice.isPersistentConnectionAllowed()) { this.smartcardDiscovered(mSmartcardDevice.getTransport()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 451065d6b..29200ac2c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -130,9 +130,9 @@ public class CryptoOperationHelper Date: Sat, 9 Apr 2016 16:03:31 +0600 Subject: OTG: Add/update javadoc; rename methods, exceptions --- .../keychain/smartcard/NfcTransport.java | 21 +++- .../keychain/smartcard/SmartcardDevice.java | 49 ++++---- .../keychain/smartcard/Transport.java | 30 ++++- .../keychain/smartcard/TransportIoException.java | 20 ---- .../smartcard/UsbConnectionDispatcher.java | 54 +++++++++ .../keychain/smartcard/UsbConnectionManager.java | 54 --------- .../keychain/smartcard/UsbTransport.java | 123 ++++++++++++++------- .../keychain/smartcard/UsbTransportException.java | 20 ++++ .../ui/base/BaseSecurityTokenNfcActivity.java | 21 +--- 9 files changed, 225 insertions(+), 167 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index e47ba5360..e3c6d12da 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -19,13 +19,23 @@ public class NfcTransport implements Transport { this.mTag = tag; } + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ @Override - public byte[] sendAndReceive(final byte[] data) throws TransportIoException, IOException { + public byte[] transceive(final byte[] data) throws IOException { return mIsoCard.transceive(data); } + /** + * Disconnect and release connection + */ @Override public void release() { + // Not supported } @Override @@ -33,13 +43,18 @@ public class NfcTransport implements Transport { return mIsoCard != null && mIsoCard.isConnected(); } + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ @Override - public boolean allowPersistentConnection() { + public boolean isPersistentConnectionAllowed() { return false; } /** - * Handle NFC communication and return a result. + * Connect to NFC device. *

* On general communication, see also * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx 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 286a38d1f..7bc53a10a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -39,7 +39,6 @@ public class SmartcardDevice { return LazyHolder.mSmartcardDevice; } - // METHOD UPDATED [OK] private String getHolderName(String name) { try { String slength; @@ -80,6 +79,7 @@ public class SmartcardDevice { this.mAdminPin = adminPin; } + // NEW MY METHOD public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); @@ -102,7 +102,7 @@ public class SmartcardDevice { putData(keyType.getTimestampObjectId(), timestampBytes); } - public boolean containsKey(KeyType keyType) throws IOException { + private boolean containsKey(KeyType keyType) throws IOException { return !keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } @@ -110,10 +110,17 @@ public class SmartcardDevice { return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); } - // METHOD UPDATED OK + /** + * Connect to device and select pgp applet + * + * @throws IOException + */ public void connectToDevice() throws IOException { + // Connect on transport layer 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"; @@ -164,7 +171,6 @@ public class SmartcardDevice { * @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; @@ -210,7 +216,6 @@ public class SmartcardDevice { * @param encryptedSessionKey the encoded session key * @return the decoded session key */ - // METHOD UPDATED [OK] public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { if (!mPw1ValidatedForDecrypt) { verifyPin(0x82); // (Verify PW1 with mode 82 for decryption) @@ -280,7 +285,6 @@ public class SmartcardDevice { * @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"); @@ -315,7 +319,6 @@ public class SmartcardDevice { * 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) { @@ -426,10 +429,9 @@ public class SmartcardDevice { * * @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)); + byte[] buf = mTransport.transceive(Hex.decode(data)); Iso7816TLV tlv = Iso7816TLV.readSingle(buf, true); Log.d(Constants.TAG, "nfcGetFingerprints() Iso7816TLV tlv data:\n" + tlv.prettyPrint()); @@ -446,19 +448,16 @@ public class SmartcardDevice { * * @return Seven bytes in fixed format, plus 0x9000 status word at the end. */ - // METHOD UPDATED [OK] private byte[] getPwStatusBytes() throws IOException { String data = "00CA00C400"; - return mTransport.sendAndReceive(Hex.decode(data)); + return mTransport.transceive(Hex.decode(data)); } - // METHOD UPDATED [OK] public byte[] getAid() throws IOException { String info = "00CA004F00"; - return mTransport.sendAndReceive(Hex.decode(info)); + return mTransport.transceive(Hex.decode(info)); } - // METHOD UPDATED [OK] public String getUserId() throws IOException { String info = "00CA006500"; return getHolderName(communicate(info)); @@ -470,7 +469,6 @@ public class SmartcardDevice { * @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) { verifyPin(0x81); // (Verify PW1 with mode 81 for signing) @@ -568,29 +566,23 @@ public class SmartcardDevice { return Hex.decode(signature); } - public boolean isConnected() { - return mTransport != null && mTransport.isConnected(); - } - /** * Transceive data via NFC encoded as Hex */ - // METHOD UPDATED [OK] - private String communicate(String apdu) throws IOException, TransportIoException { - return getHex(mTransport.sendAndReceive(Hex.decode(apdu))); + private String communicate(String apdu) throws IOException { + return getHex(mTransport.transceive(Hex.decode(apdu))); } public Transport getTransport() { return mTransport; } - // 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)); + 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); } catch (IOException e) { @@ -614,7 +606,6 @@ public class SmartcardDevice { * @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[] generateKey(int slot) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { throw new IOException("Invalid key slot"); @@ -641,7 +632,6 @@ public class SmartcardDevice { return Hex.decode(publicKeyData); } - // NEW METHOD [OK][OK] private String getDataField(String output) { return output.substring(0, output.length() - 4); } @@ -665,7 +655,6 @@ public class SmartcardDevice { * 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"; @@ -725,7 +714,11 @@ public class SmartcardDevice { } public boolean isPersistentConnectionAllowed() { - return mTransport != null && mTransport.allowPersistentConnection(); + return mTransport != null && mTransport.isPersistentConnectionAllowed(); + } + + public boolean isConnected() { + return mTransport != null && mTransport.isConnected(); } private static class LazyHolder { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index 5252a95d0..fa8b0695a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -2,14 +2,40 @@ package org.sufficientlysecure.keychain.smartcard; import java.io.IOException; +/** + * Abstraction for transmitting APDU commands + */ public interface Transport { - byte[] sendAndReceive(byte[] data) throws IOException; + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ + byte[] transceive(byte[] data) throws IOException; + /** + * Disconnect and release connection + */ void release(); + /** + * Check if device is was connected to and still is connected + * @return connection status + */ boolean isConnected(); - boolean allowPersistentConnection(); + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + boolean isPersistentConnectionAllowed(); + + /** + * Connect to device + * @throws IOException + */ void connect() throws IOException; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java deleted file mode 100644 index 544dd4045..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/TransportIoException.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import java.io.IOException; - -public class TransportIoException extends IOException { - public TransportIoException() { - } - - public TransportIoException(final String detailMessage) { - super(detailMessage); - } - - public TransportIoException(final String message, final Throwable cause) { - super(message, cause); - } - - public TransportIoException(final Throwable cause) { - super(cause); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java new file mode 100644 index 000000000..c3068916e --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java @@ -0,0 +1,54 @@ +package org.sufficientlysecure.keychain.smartcard; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +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; + +public class UsbConnectionDispatcher { + private Activity mActivity; + + private OnDiscoveredUsbDeviceListener mListener; + /** + * 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(); + + if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + if (permission) { + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + mListener.usbDeviceDiscovered(usbDevice); + } + } + } + }; + + public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + } + + public void onStart() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); + + mActivity.registerReceiver(mUsbReceiver, intentFilter); + } + + public void onStop() { + mActivity.unregisterReceiver(mUsbReceiver); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java deleted file mode 100644 index 8a6971fe6..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -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; - -public class UsbConnectionManager { - private Activity mActivity; - - private OnDiscoveredUsbDeviceListener mListener; - /** - * 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(); - - if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - if (permission) { - Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); - } - } - } - }; - - public UsbConnectionManager(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { - this.mActivity = activity; - this.mListener = listener; - } - - public void onStart() { - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - - mActivity.registerReceiver(mUsbReceiver, intentFilter); - } - - public void onStop() { - mActivity.unregisterReceiver(mUsbReceiver); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 2a21c10dd..86f4a687d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -19,9 +19,14 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; +/** + * Based on USB CCID Specification rev. 1.1 + * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf + * Implements small subset of these features + */ public class UsbTransport implements Transport { - private static final int CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 2 s + private static final int USB_CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 20s private final UsbManager mUsbManager; private final UsbDevice mUsbDevice; @@ -29,29 +34,24 @@ public class UsbTransport implements Transport { private UsbEndpoint mBulkIn; private UsbEndpoint mBulkOut; private UsbDeviceConnection mConnection; - private byte mCounter = 0; + private byte mCounter; - public UsbTransport(final UsbDevice usbDevice, final UsbManager usbManager) { + public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { mUsbDevice = usbDevice; mUsbManager = usbManager; } - private void powerOff() throws TransportIoException { - final byte[] iccPowerOff = { - 0x63, - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - sendRaw(iccPowerOff); - receive(); - } - void powerOn() throws TransportIoException { + /** + * Manage ICC power, Yubikey requires to power on ICC + * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff + * + * @param on true to turn ICC on, false to turn it off + * @throws UsbTransportException + */ + private void iccPowerSet(boolean on) throws UsbTransportException { final byte[] iccPowerOn = { - 0x62, + (byte) (on ? 0x62 : 0x63), 0x00, 0x00, 0x00, 0x00, 0x00, mCounter++, @@ -63,22 +63,28 @@ public class UsbTransport implements Transport { } /** - * Get first class 11 (Chip/Smartcard) interface for the device + * Get first class 11 (Chip/Smartcard) interface of the device * * @param device {@link UsbDevice} which will be searched * @return {@link UsbInterface} of smartcard or null if it doesn't exist */ @Nullable - private static UsbInterface getSmartCardInterface(final UsbDevice device) { + private static UsbInterface getSmartCardInterface(UsbDevice device) { for (int i = 0; i < device.getInterfaceCount(); i++) { - final UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == CLASS_SMARTCARD) { + UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) { return anInterface; } } return null; } + /** + * Get device's bulk-in and bulk-out endpoints + * + * @param usbInterface usb device interface + * @return pair of builk-in and bulk-out endpoints respectively + */ @NonNull private static Pair getIoEndpoints(final UsbInterface usbInterface) { UsbEndpoint bulkIn = null, bulkOut = null; @@ -97,43 +103,77 @@ public class UsbTransport implements Transport { return new Pair<>(bulkIn, bulkOut); } + /** + * Release interface and disconnect + */ @Override public void release() { mConnection.releaseInterface(mUsbInterface); mConnection.close(); + mConnection = null; } + /** + * Check if device is was connected to and still is connected + * @return true if device is connected + */ @Override public boolean isConnected() { - // TODO: redo return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); } + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ @Override - public boolean allowPersistentConnection() { + public boolean isPersistentConnectionAllowed() { return true; } + /** + * Connect to OTG device + * @throws IOException + */ @Override public void connect() throws IOException { + mCounter = 0; mUsbInterface = getSmartCardInterface(mUsbDevice); - // throw if mUsbInterface == null + if (mUsbInterface == null) { + // Shouldn't happen as we whitelist only class 11 devices + throw new UsbTransportException("USB error: device doesn't have class 11 interface"); + } + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); mBulkIn = ioEndpoints.first; mBulkOut = ioEndpoints.second; - // throw if any endpoint is null + + if (mBulkIn == null || mBulkOut == null) { + throw new UsbTransportException("USB error: invalid class 11 interface"); + } mConnection = mUsbManager.openDevice(mUsbDevice); - // throw if connection is null - mConnection.claimInterface(mUsbInterface, true); - // check result + if (mConnection == null) { + throw new UsbTransportException("USB error: failed to connect to device"); + } + + if (!mConnection.claimInterface(mUsbInterface, true)) { + throw new UsbTransportException("USB error: failed to claim interface"); + } - powerOn(); + iccPowerSet(true); Log.d(Constants.TAG, "Usb transport connected"); } + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws UsbTransportException + */ @Override - public byte[] sendAndReceive(byte[] data) throws TransportIoException { + public byte[] transceive(byte[] data) throws UsbTransportException { send(data); byte[] bytes; do { @@ -141,10 +181,11 @@ public class UsbTransport implements Transport { } while (isXfrBlockNotReady(bytes)); checkXfrBlockResult(bytes); + // Discard header return Arrays.copyOfRange(bytes, 10, bytes.length); } - public void send(byte[] d) throws TransportIoException { + private void send(byte[] d) throws UsbTransportException { int l = d.length; byte[] data = Arrays.concatenate(new byte[]{ 0x6f, @@ -163,7 +204,7 @@ public class UsbTransport implements Transport { } } - public byte[] receive() throws TransportIoException { + private byte[] receive() throws UsbTransportException { byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; byte[] result = null; int readBytes = 0, totalBytes = 0; @@ -171,11 +212,11 @@ public class UsbTransport implements Transport { do { int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); if (res < 0) { - throw new TransportIoException("USB error, failed to receive response " + res); + throw new UsbTransportException("USB error: failed to receive response " + res); } if (result == null) { if (res < 10) { - throw new TransportIoException("USB error, failed to receive ccid header"); + throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); } totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; result = new byte[totalBytes]; @@ -187,10 +228,10 @@ public class UsbTransport implements Transport { return result; } - private void sendRaw(final byte[] data) throws TransportIoException { + private void sendRaw(final byte[] data) throws UsbTransportException { final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); if (tr1 != data.length) { - throw new TransportIoException("USB error, failed to send data " + tr1); + throw new UsbTransportException("USB error: failed to transmit data " + tr1); } } @@ -198,10 +239,10 @@ public class UsbTransport implements Transport { return (byte) ((bytes[7] >> 6) & 0x03); } - private void checkXfrBlockResult(byte[] bytes) throws TransportIoException { + private void checkXfrBlockResult(byte[] bytes) throws UsbTransportException { final byte status = getStatus(bytes); if (status != 0) { - throw new TransportIoException("CCID error, status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } } @@ -209,10 +250,6 @@ public class UsbTransport implements Transport { return getStatus(bytes) == 2; } - public UsbDevice getUsbDevice() { - return mUsbDevice; - } - @Override public boolean equals(final Object o) { if (this == o) return true; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java new file mode 100644 index 000000000..27635137f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java @@ -0,0 +1,20 @@ +package org.sufficientlysecure.keychain.smartcard; + +import java.io.IOException; + +public class UsbTransportException extends IOException { + public UsbTransportException() { + } + + public UsbTransportException(final String detailMessage) { + super(detailMessage); + } + + public UsbTransportException(final String message, final Throwable cause) { + super(message, cause); + } + + public UsbTransportException(final Throwable cause) { + super(cause); + } +} 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 e138af895..f3c3cbe75 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 @@ -40,11 +40,12 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.smartcard.CardException; import org.sufficientlysecure.keychain.smartcard.NfcTransport; import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.Transport; -import org.sufficientlysecure.keychain.smartcard.UsbConnectionManager; +import org.sufficientlysecure.keychain.smartcard.UsbConnectionDispatcher; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; @@ -72,7 +73,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance(); protected TagDispatcher mTagDispatcher; - protected UsbConnectionManager mUsbDispatcher; + protected UsbConnectionDispatcher mUsbDispatcher; private boolean mTagHandlingEnabled; private byte[] mSmartcardFingerprints; @@ -201,7 +202,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onCreate(savedInstanceState); mTagDispatcher = TagDispatcher.get(this, this, false, false, true, false); - mUsbDispatcher = new UsbConnectionManager(this, this); + mUsbDispatcher = new UsbConnectionDispatcher(this, this); // Check whether we're recreating a previously destroyed instance if (savedInstanceState != null) { @@ -423,20 +424,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } - public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - - } - /** * Ask user if she wants to install PGP onto her Fidesmo token */ -- cgit v1.2.3 From 409c38ba2ab79cac97a914d89f9cb9a14ebe08c1 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 17:28:44 +0600 Subject: OTG: UsbTransport minor refactoring --- .../keychain/smartcard/UsbTransport.java | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 86f4a687d..4b4f2c35c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -50,7 +50,7 @@ public class UsbTransport implements Transport { * @throws UsbTransportException */ private void iccPowerSet(boolean on) throws UsbTransportException { - final byte[] iccPowerOn = { + final byte[] iccPowerCommand = { (byte) (on ? 0x62 : 0x63), 0x00, 0x00, 0x00, 0x00, 0x00, @@ -58,8 +58,13 @@ public class UsbTransport implements Transport { 0x00, 0x00, 0x00 }; - sendRaw(iccPowerOn); - receive(); + + sendRaw(iccPowerCommand); + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + checkDataBlockResponse(receive()); } /** @@ -174,19 +179,25 @@ public class UsbTransport implements Transport { */ @Override public byte[] transceive(byte[] data) throws UsbTransportException { - send(data); + sendXfrBlock(data); byte[] bytes; do { bytes = receive(); - } while (isXfrBlockNotReady(bytes)); + } while (isDataBlockNotReady(bytes)); - checkXfrBlockResult(bytes); + checkDataBlockResponse(bytes); // Discard header return Arrays.copyOfRange(bytes, 10, bytes.length); } - private void send(byte[] d) throws UsbTransportException { - int l = d.length; + /** + * Transmits XfrBlock + * 6.1.4 PC_to_RDR_XfrBlock + * @param payload payload to transmit + * @throws UsbTransportException + */ + private void sendXfrBlock(byte[] payload) throws UsbTransportException { + int l = payload.length; byte[] data = Arrays.concatenate(new byte[]{ 0x6f, (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), @@ -194,7 +205,7 @@ public class UsbTransport implements Transport { mCounter++, 0x00, 0x00, 0x00}, - d); + payload); int send = 0; while (send < data.length) { @@ -239,14 +250,14 @@ public class UsbTransport implements Transport { return (byte) ((bytes[7] >> 6) & 0x03); } - private void checkXfrBlockResult(byte[] bytes) throws UsbTransportException { + private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { final byte status = getStatus(bytes); if (status != 0) { throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } } - private boolean isXfrBlockNotReady(byte[] bytes) { + private boolean isDataBlockNotReady(byte[] bytes) { return getStatus(bytes) == 2; } -- cgit v1.2.3 From b37b171908339e0a2a79c5811aa092fc47ed79b7 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 17:45:17 +0600 Subject: OTG: move UsbConnectionDispatcher to utls package --- .../smartcard/OnDiscoveredUsbDeviceListener.java | 7 --- .../smartcard/UsbConnectionDispatcher.java | 54 -------------------- .../ui/base/BaseSecurityTokenNfcActivity.java | 5 +- .../keychain/util/UsbConnectionDispatcher.java | 58 ++++++++++++++++++++++ 4 files changed, 60 insertions(+), 64 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java deleted file mode 100644 index 46b503b42..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/OnDiscoveredUsbDeviceListener.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import android.hardware.usb.UsbDevice; - -public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java deleted file mode 100644 index c3068916e..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbConnectionDispatcher.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -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; - -public class UsbConnectionDispatcher { - private Activity mActivity; - - private OnDiscoveredUsbDeviceListener mListener; - /** - * 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(); - - if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - if (permission) { - Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); - } - } - } - }; - - public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { - this.mActivity = activity; - this.mListener = listener; - } - - public void onStart() { - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - - mActivity.registerReceiver(mUsbReceiver, intentFilter); - } - - public void onStop() { - mActivity.unregisterReceiver(mUsbReceiver); - } -} 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 f3c3cbe75..57da687c7 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 @@ -42,10 +42,9 @@ import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.smartcard.CardException; import org.sufficientlysecure.keychain.smartcard.NfcTransport; -import org.sufficientlysecure.keychain.smartcard.OnDiscoveredUsbDeviceListener; import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; import org.sufficientlysecure.keychain.smartcard.Transport; -import org.sufficientlysecure.keychain.smartcard.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; @@ -64,7 +63,7 @@ import nordpol.android.OnDiscoveredTagListener; import nordpol.android.TagDispatcher; public abstract class BaseSecurityTokenNfcActivity extends BaseActivity - implements OnDiscoveredTagListener, OnDiscoveredUsbDeviceListener { + implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { public static final int REQUEST_CODE_PIN = 1; public static final String EXTRA_TAG_HANDLING_ENABLED = "tag_handling_enabled"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java new file mode 100644 index 000000000..e48ba7be0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -0,0 +1,58 @@ +package org.sufficientlysecure.keychain.util; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +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; + +public class UsbConnectionDispatcher { + private Activity mActivity; + + private OnDiscoveredUsbDeviceListener mListener; + /** + * 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(); + + if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + if (permission) { + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + mListener.usbDeviceDiscovered(usbDevice); + } + } + } + }; + + public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { + this.mActivity = activity; + this.mListener = listener; + } + + public void onStart() { + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); + + mActivity.registerReceiver(mUsbReceiver, intentFilter); + } + + public void onStop() { + mActivity.unregisterReceiver(mUsbReceiver); + } + + public interface OnDiscoveredUsbDeviceListener { + void usbDeviceDiscovered(UsbDevice usbDevice); + } +} -- cgit v1.2.3 From 0c9ead62a0f6c7cb59811891059eeb8b5330ee79 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 18:24:31 +0600 Subject: OTG: Remove obsolete Pin classes, reenable changeKey method --- .../keychain/smartcard/PinException.java | 7 --- .../keychain/smartcard/PinType.java | 16 ------- .../keychain/smartcard/SmartcardDevice.java | 40 +++++++++------- .../ui/SecurityTokenOperationActivity.java | 54 +--------------------- 4 files changed, 24 insertions(+), 93 deletions(-) delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java deleted file mode 100644 index 58a7a31c9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -public class PinException extends CardException { - public PinException(final String detailMessage, final short responseCode) { - super(detailMessage, responseCode); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java deleted file mode 100644 index 7601edcf3..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/PinType.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sufficientlysecure.keychain.smartcard; - -public enum PinType { - BASIC(0x81), - ADMIN(0x83),; - - private final int mMode; - - PinType(final int mode) { - this.mMode = mode; - } - - public int getmMode() { - return mMode; - } -} 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 7bc53a10a..a9358ee84 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -17,6 +17,11 @@ import java.security.interfaces.RSAPrivateCrtKey; import nordpol.Apdu; +/** + * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant + * NFC devices. + * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + */ public class SmartcardDevice { // Fidesmo constants private static final String FIDESMO_APPS_AID_PREFIX = "A000000617"; @@ -30,7 +35,6 @@ public class SmartcardDevice { private boolean mPw1ValidatedForSignature; private boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming? private boolean mPw3Validated; - private boolean mTagHandlingEnabled; protected SmartcardDevice() { } @@ -39,6 +43,10 @@ public class SmartcardDevice { return LazyHolder.mSmartcardDevice; } + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + private String getHolderName(String name) { try { String slength; @@ -59,10 +67,6 @@ public class SmartcardDevice { } } - private static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - public Passphrase getPin() { return mPin; } @@ -79,7 +83,6 @@ public class SmartcardDevice { this.mAdminPin = adminPin; } - // NEW MY METHOD public void changeKey(CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { long keyGenerationTimestamp = secretKey.getCreationTime().getTime() / 1000; byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); @@ -90,8 +93,9 @@ public class SmartcardDevice { } // Slot is empty, or contains this key already. PUT KEY operation is safe - boolean canPutKey = !containsKey(keyType) + boolean canPutKey = isSlotEmpty(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())); @@ -102,8 +106,12 @@ public class SmartcardDevice { putData(keyType.getTimestampObjectId(), timestampBytes); } - private boolean containsKey(KeyType keyType) throws IOException { - return !keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + private boolean isSlotEmpty(KeyType keyType) throws IOException { + // Note: special case: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true + if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; + + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); } public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { @@ -248,7 +256,6 @@ public class SmartcardDevice { * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. * For PW3 (Admin PIN), mode is 0x83. */ - // METHOD UPDATED [OK] private void verifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { @@ -285,7 +292,7 @@ public class SmartcardDevice { * @param dataObject The data object to be stored. * @param data The data to store in the object */ - public void putData(int dataObject, byte[] data) throws IOException { + private void putData(int dataObject, byte[] data) throws IOException { if (data.length > 254) { throw new IOException("Cannot PUT DATA with length > 254"); } @@ -319,7 +326,7 @@ public class SmartcardDevice { * 0xB8: Decipherment Key * 0xA4: Authentication Key */ - public void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) + private void putKey(int slot, CanonicalizedSecretKey secretKey, Passphrase passphrase) throws IOException { if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { throw new IOException("Invalid key slot"); @@ -577,6 +584,10 @@ public class SmartcardDevice { return mTransport; } + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + } + public boolean isFidesmoToken() { if (isConnected()) { // Check if we can still talk to the card try { @@ -636,7 +647,6 @@ public class SmartcardDevice { return output.substring(0, output.length() - 4); } - // NEW METHOD [OK] private String tryPin(int mode, byte[] pin) throws IOException { // Command APDU for VERIFY command (page 32) String login = @@ -709,10 +719,6 @@ public class SmartcardDevice { return fp; } - public void setTransport(Transport mTransport) { - this.mTransport = mTransport; - } - public boolean isPersistentConnectionAllowed() { return mTransport != null && mTransport.isPersistentConnectionAllowed(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ed6e3faf3..12843cbcd 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -69,8 +69,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private RequiredInputParcel mRequiredInput; - 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 CryptoInputParcel mInputParcel; @Override @@ -226,10 +224,6 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long subkeyId = buf.getLong(); CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); - - long keyGenerationTimestampMillis = key.getCreationTime().getTime(); - long keyGenerationTimestamp = keyGenerationTimestampMillis / 1000; - byte[] timestampBytes = ByteBuffer.allocate(4).putInt((int) keyGenerationTimestamp).array(); byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); Passphrase passphrase; @@ -240,33 +234,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity throw new IOException("Unable to get cached passphrase!"); } - if (key.canSign() || key.canCertify()) { - if (shouldPutKey(key.getFingerprint(), 0)) { - mSmartcardDevice.putKey(0xB6, key, passphrase); - mSmartcardDevice.putData(0xCE, timestampBytes); - mSmartcardDevice.putData(0xC7, key.getFingerprint()); - } else { - throw new IOException("Key slot occupied; token must be reset to put new signature key."); - } - } else if (key.canEncrypt()) { - if (shouldPutKey(key.getFingerprint(), 1)) { - mSmartcardDevice.putKey(0xB8, key, passphrase); - mSmartcardDevice.putData(0xCF, timestampBytes); - mSmartcardDevice.putData(0xC8, key.getFingerprint()); - } else { - throw new IOException("Key slot occupied; token must be reset to put new decryption key."); - } - } else if (key.canAuthenticate()) { - if (shouldPutKey(key.getFingerprint(), 2)) { - mSmartcardDevice.putKey(0xA4, key, passphrase); - mSmartcardDevice.putData(0xD0, timestampBytes); - mSmartcardDevice.putData(0xC9, key.getFingerprint()); - } else { - throw new IOException("Key slot occupied; token must be reset to put new authentication key."); - } - } else { - throw new IOException("Inappropriate key flags for Security Token key."); - } + mSmartcardDevice.changeKey(key, passphrase); // TODO: Is this really used anywhere? mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); @@ -357,24 +325,4 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity PassphraseCacheService.clearCachedPassphrase( this, mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId()); } - - private boolean shouldPutKey(byte[] fingerprint, int idx) throws IOException { - byte[] tokenFingerprint = mSmartcardDevice.getMasterKeyFingerprint(idx); - - // Note: special case: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (tokenFingerprint == null) { - return true; - } - - // Slot is empty, or contains this key already. PUT KEY operation is safe - if (Arrays.equals(tokenFingerprint, BLANK_FINGERPRINT) || - Arrays.equals(tokenFingerprint, fingerprint)) { - return true; - } - - // Slot already contains a different key; don't overwrite it. - return false; - } - } -- cgit v1.2.3 From 0077f3a3b8a3a62578a48ec8b096cbb85c189246 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sat, 9 Apr 2016 18:35:32 +0600 Subject: OTG: Add/Update copyrights --- .../keychain/smartcard/CardException.java | 17 +++++++++++++++++ .../keychain/smartcard/KeyType.java | 17 +++++++++++++++++ .../keychain/smartcard/NfcTransport.java | 17 +++++++++++++++++ .../keychain/smartcard/SmartcardDevice.java | 22 ++++++++++++++++++++++ .../keychain/smartcard/Transport.java | 17 +++++++++++++++++ .../keychain/smartcard/UsbTransport.java | 17 +++++++++++++++++ .../keychain/smartcard/UsbTransportException.java | 17 +++++++++++++++++ .../ui/SecurityTokenOperationActivity.java | 1 + .../ui/base/BaseSecurityTokenNfcActivity.java | 1 + .../keychain/util/UsbConnectionDispatcher.java | 17 +++++++++++++++++ 10 files changed, 143 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java index 9ea67f711..06445e988 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.smartcard; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java index 625e1e669..29c7fce96 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.smartcard; import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java index e3c6d12da..a743c753a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.smartcard; import android.nfc.Tag; 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 a9358ee84..58cf9b51a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java @@ -1,3 +1,25 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * Copyright (C) 2013-2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * Copyright (C) 2013-2014 Signe Rüsch + * Copyright (C) 2013-2014 Philipp Jakubeit + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + package org.sufficientlysecure.keychain.smartcard; import org.bouncycastle.bcpg.HashAlgorithmTags; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java index fa8b0695a..5b942ee5c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.smartcard; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 4b4f2c35c..b08908aa5 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.smartcard; import android.hardware.usb.UsbConstants; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java index 27635137f..7bf713bf9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.smartcard; import java.io.IOException; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 12843cbcd..ac12c2a4a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -3,6 +3,7 @@ * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by 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 57da687c7..c8022a776 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 @@ -3,6 +3,7 @@ * Copyright (C) 2015 Vincent Breitmoser * Copyright (C) 2013-2014 Signe Rüsch * Copyright (C) 2013-2014 Philipp Jakubeit + * Copyright (C) 2016 Nikita Mikhailov * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index e48ba7be0..697db0811 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.util; import android.app.Activity; -- cgit v1.2.3 From 7caceee92f2b73c51923bec20545a1609b466716 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 10 Apr 2016 22:29:45 +0600 Subject: OTG: fix icc powering method --- .../java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index b08908aa5..43496b31c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -81,7 +81,7 @@ public class UsbTransport implements Transport { do { bytes = receive(); } while (isDataBlockNotReady(bytes)); - checkDataBlockResponse(receive()); + checkDataBlockResponse(bytes); } /** -- cgit v1.2.3 From 8cb94c446bf1d490978cfe49adcefb29d2fbca5b Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 10 Apr 2016 23:08:52 +0600 Subject: OTG: Add copyright --- .../keychain/ui/UsbEventReceiverActivity.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java index 9df5800b5..05b30b1ae 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/UsbEventReceiverActivity.java @@ -1,3 +1,20 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package org.sufficientlysecure.keychain.ui; import android.app.Activity; -- cgit v1.2.3 From 4e543e5368ae37afd474ebf0f04bd869d12be755 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Sun, 10 Apr 2016 23:44:52 +0600 Subject: OTG: rescan devices before next operation --- .../keychain/smartcard/UsbTransport.java | 14 ++++-- .../ui/base/BaseSecurityTokenNfcActivity.java | 10 ++-- .../keychain/util/UsbConnectionDispatcher.java | 53 ++++++++++++++++++---- 3 files changed, 58 insertions(+), 19 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java index 43496b31c..8d661b9fa 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java @@ -130,9 +130,13 @@ public class UsbTransport implements Transport { */ @Override public void release() { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - mConnection = null; + if (mConnection != null) { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + mConnection = null; + } + + Log.d(Constants.TAG, "Usb transport disconnected"); } /** @@ -292,4 +296,8 @@ public class UsbTransport implements Transport { public int hashCode() { return mUsbDevice != null ? mUsbDevice.hashCode() : 0; } + + public UsbDevice getUsbDevice() { + return mUsbDevice; + } } 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 c8022a776..5ad542526 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 @@ -24,7 +24,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; @@ -144,13 +143,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity smartcardDiscovered(new NfcTransport(tag)); } - public void usbDeviceDiscovered(final UsbDevice device) { + public void usbDeviceDiscovered(final UsbTransport transport) { // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; - UsbManager usbManager = (UsbManager) getSystemService(USB_SERVICE); - smartcardDiscovered(new UsbTransport(device, usbManager)); + smartcardDiscovered(transport); } public void smartcardDiscovered(final Transport transport) { @@ -481,8 +479,6 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * persistent connections */ protected void checkDeviceConnection() { - if (mSmartcardDevice.isConnected() && mSmartcardDevice.isPersistentConnectionAllowed()) { - this.smartcardDiscovered(mSmartcardDevice.getTransport()); - } + mUsbDispatcher.rescanDevices(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 697db0811..09b029523 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -26,13 +26,15 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.smartcard.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; -import org.sufficientlysecure.keychain.util.Log; public class UsbConnectionDispatcher { private Activity mActivity; private OnDiscoveredUsbDeviceListener mListener; + private UsbTransport mLastUsedUsbTransport; + private UsbManager mUsbManager; /** * Receives broadcast when a supported USB device get permission. */ @@ -41,13 +43,27 @@ public class UsbConnectionDispatcher { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - if (UsbEventReceiverActivity.ACTION_USB_PERMISSION.equals(action)) { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, - false); - if (permission) { - Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); + switch (action) { + case UsbEventReceiverActivity.ACTION_USB_PERMISSION: { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, + false); + if (permission) { + Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); + + mLastUsedUsbTransport = new UsbTransport(usbDevice, mUsbManager); + mListener.usbDeviceDiscovered(mLastUsedUsbTransport); + } + break; + } + case UsbManager.ACTION_USB_DEVICE_DETACHED: { + UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + + if (mLastUsedUsbTransport != null && mLastUsedUsbTransport.getUsbDevice().equals(usbDevice)) { + mLastUsedUsbTransport.release(); + mLastUsedUsbTransport = null; + } + break; } } } @@ -56,11 +72,13 @@ public class UsbConnectionDispatcher { public UsbConnectionDispatcher(final Activity activity, final OnDiscoveredUsbDeviceListener listener) { this.mActivity = activity; this.mListener = listener; + this.mUsbManager = (UsbManager) activity.getSystemService(Context.USB_SERVICE); } public void onStart() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); + intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mActivity.registerReceiver(mUsbReceiver, intentFilter); } @@ -69,7 +87,24 @@ public class UsbConnectionDispatcher { mActivity.unregisterReceiver(mUsbReceiver); } + /** + * Rescans devices and triggers {@link OnDiscoveredUsbDeviceListener} + */ + public void rescanDevices() { + // Note: we don't check devices VID/PID because + // we check for permisssion instead. + // We should have permission only for matching devices + for (UsbDevice device : mUsbManager.getDeviceList().values()) { + if (mUsbManager.hasPermission(device)) { + if (mListener != null) { + mListener.usbDeviceDiscovered(new UsbTransport(device, mUsbManager)); + } + break; + } + } + } + public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); + void usbDeviceDiscovered(UsbTransport usbTransport); } } -- cgit v1.2.3 From df57ecde4755b6d2bf39c486cc7bb8d521d3268b Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Tue, 12 Apr 2016 00:02:59 +0600 Subject: OTG: rename Smartcard -> SecurityToken --- .../keychain/securitytoken/CardException.java | 34 + .../keychain/securitytoken/KeyType.java | 65 ++ .../keychain/securitytoken/NfcTransport.java | 113 +++ .../securitytoken/SecurityTokenHelper.java | 755 +++++++++++++++++++++ .../keychain/securitytoken/Transport.java | 58 ++ .../keychain/securitytoken/UsbTransport.java | 303 +++++++++ .../securitytoken/UsbTransportException.java | 37 + .../keychain/smartcard/CardException.java | 34 - .../keychain/smartcard/KeyType.java | 65 -- .../keychain/smartcard/NfcTransport.java | 113 --- .../keychain/smartcard/SmartcardDevice.java | 755 --------------------- .../keychain/smartcard/Transport.java | 58 -- .../keychain/smartcard/UsbTransport.java | 303 --------- .../keychain/smartcard/UsbTransportException.java | 37 - .../keychain/ui/CreateKeyActivity.java | 34 +- .../ui/CreateSecurityTokenImportResetFragment.java | 6 +- .../ui/SecurityTokenOperationActivity.java | 32 +- .../keychain/ui/ViewKeyActivity.java | 33 +- .../ui/base/BaseSecurityTokenNfcActivity.java | 123 ++-- .../keychain/util/UsbConnectionDispatcher.java | 2 +- 20 files changed, 1479 insertions(+), 1481 deletions(-) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java delete mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java new file mode 100644 index 000000000..905deca90 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardException.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import java.io.IOException; + +public class CardException extends IOException { + private short mResponseCode; + + public CardException(String detailMessage, short responseCode) { + super(detailMessage); + mResponseCode = responseCode; + } + + public short getResponseCode() { + return mResponseCode; + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java new file mode 100644 index 000000000..0e28f022b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/KeyType.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; + +public enum KeyType { + SIGN(0, 0xB6, 0xCE, 0xC7), + ENCRYPT(1, 0xB8, 0xCF, 0xC8), + AUTH(2, 0xA4, 0xD0, 0xC9),; + + private final int mIdx; + private final int mSlot; + private final int mTimestampObjectId; + private final int mFingerprintObjectId; + + KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { + this.mIdx = idx; + this.mSlot = slot; + this.mTimestampObjectId = timestampObjectId; + this.mFingerprintObjectId = fingerprintObjectId; + } + + public static KeyType from(final CanonicalizedSecretKey key) { + if (key.canSign() || key.canCertify()) { + return SIGN; + } else if (key.canEncrypt()) { + return ENCRYPT; + } else if (key.canAuthenticate()) { + return AUTH; + } + return null; + } + + public int getIdx() { + return mIdx; + } + + public int getmSlot() { + return mSlot; + } + + public int getTimestampObjectId() { + return mTimestampObjectId; + } + + public int getmFingerprintObjectId() { + return mFingerprintObjectId; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java new file mode 100644 index 000000000..3b2dd838d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/NfcTransport.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import android.nfc.Tag; + +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; + +import java.io.IOException; + +import nordpol.IsoCard; +import nordpol.android.AndroidCard; + +public class NfcTransport implements Transport { + // timeout is set to 100 seconds to avoid cancellation during calculation + private static final int TIMEOUT = 100 * 1000; + private final Tag mTag; + private IsoCard mIsoCard; + + public NfcTransport(Tag tag) { + this.mTag = tag; + } + + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ + @Override + public byte[] transceive(final byte[] data) throws IOException { + return mIsoCard.transceive(data); + } + + /** + * Disconnect and release connection + */ + @Override + public void release() { + // Not supported + } + + @Override + public boolean isConnected() { + return mIsoCard != null && mIsoCard.isConnected(); + } + + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + @Override + public boolean isPersistentConnectionAllowed() { + return false; + } + + /** + * Connect to NFC device. + *

+ * On general communication, see also + * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx + *

+ * References to pages are generally related to the OpenPGP Application + * on ISO SmartCard Systems specification. + */ + @Override + public void connect() throws IOException { + mIsoCard = AndroidCard.get(mTag); + if (mIsoCard == null) { + throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); + } + + mIsoCard.setTimeout(TIMEOUT); + mIsoCard.connect(); + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final NfcTransport that = (NfcTransport) o; + + if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; + if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = mTag != null ? mTag.hashCode() : 0; + result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); + return result; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java new file mode 100644 index 000000000..e3f280e18 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -0,0 +1,755 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * Copyright (C) 2013-2015 Dominik Schürmann + * Copyright (C) 2015 Vincent Breitmoser + * Copyright (C) 2013-2014 Signe Rüsch + * Copyright (C) 2013-2014 Philipp Jakubeit + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +package org.sufficientlysecure.keychain.securitytoken; + +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; + +/** + * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant + * devices. + * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf + */ +public class SecurityTokenHelper { + // 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; + + protected SecurityTokenHelper() { + } + + public static SecurityTokenHelper getInstance() { + return LazyHolder.SECURITY_TOKEN_HELPER; + } + + private static String getHex(byte[] raw) { + return new String(Hex.encode(raw)); + } + + private String getHolderName(String 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; + } catch (IndexOutOfBoundsException e) { + // try-catch for https://github.com/FluffyKaon/OpenPGP-Card + // Note: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! + + Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); + return ""; + } + } + + 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 = isSlotEmpty(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); + } + + private boolean isSlotEmpty(KeyType keyType) throws IOException { + // Note: special case: This should not happen, but happens with + // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true + if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; + + return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); + } + + public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { + return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); + } + + /** + * Connect to device and select pgp applet + * + * @throws IOException + */ + public void connectToDevice() throws IOException { + // Connect on transport layer + 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)); + } + + byte[] pwStatusBytes = getPwStatusBytes(); + 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. + */ + 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. + * + * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. + * @param newPin The new PW1 or PW3. + */ + 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 = getPwStatusBytes(); + + if (pw == 0x81) { + if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + } else if (pw == 0x83) { + if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { + throw new IOException("Invalid PIN length"); + } + } else { + throw new IOException("Invalid PW index for modify PIN operation"); + } + + 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 = communicate(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 + */ + public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { + if (!mPw1ValidatedForDecrypt) { + verifyPin(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]; + } + + communicate(firstApdu + getHex(one)); + String second = communicate(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. + */ + private void verifyPin(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 = tryPin(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 + */ + private 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) { + verifyPin(0x82); // (Verify PW1 for non-signing operations) + } + } else if (!mPw3Validated) { + 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); + + String response = communicate(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 + */ + private 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) { + verifyPin(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 = 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)); + } + } + + // 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. + */ + public byte[] getFingerprints() throws IOException { + String data = "00CA006E00"; + byte[] buf = mTransport.transceive(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. + */ + private byte[] getPwStatusBytes() throws IOException { + String data = "00CA00C400"; + return mTransport.transceive(Hex.decode(data)); + } + + public byte[] getAid() throws IOException { + String info = "00CA004F00"; + return mTransport.transceive(Hex.decode(info)); + } + + public String getUserId() throws IOException { + String info = "00CA006500"; + return getHolderName(communicate(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 + */ + public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { + if (!mPw1ValidatedForSignature) { + verifyPin(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 = communicate(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 = 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)); + } + + // 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); + } + + /** + * Transceive data via NFC encoded as Hex + */ + private String communicate(String apdu) throws IOException { + return getHex(mTransport.transceive(Hex.decode(apdu))); + } + + public Transport getTransport() { + return mTransport; + } + + public void setTransport(Transport mTransport) { + this.mTransport = mTransport; + } + + 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.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); + } 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. + */ + public byte[] generateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + verifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = communicate(generateKeyApdu); + String second = communicate(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); + } + + private String getDataField(String output) { + return output.substring(0, output.length() - 4); + } + + private String 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); + } + + /** + * 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. + */ + 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 = tryPin(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 = tryPin(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 = communicate(reactivate1); + String response2 = communicate(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; + } + + public boolean isPersistentConnectionAllowed() { + return mTransport != null && mTransport.isPersistentConnectionAllowed(); + } + + public boolean isConnected() { + return mTransport != null && mTransport.isConnected(); + } + + private static class LazyHolder { + private static final SecurityTokenHelper SECURITY_TOKEN_HELPER = new SecurityTokenHelper(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java new file mode 100644 index 000000000..294eaa9ee --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/Transport.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import java.io.IOException; + +/** + * Abstraction for transmitting APDU commands + */ +public interface Transport { + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws IOException + */ + byte[] transceive(byte[] data) throws IOException; + + /** + * Disconnect and release connection + */ + void release(); + + /** + * Check if device is was connected to and still is connected + * @return connection status + */ + boolean isConnected(); + + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + boolean isPersistentConnectionAllowed(); + + + /** + * Connect to device + * @throws IOException + */ + void connect() throws IOException; +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java new file mode 100644 index 000000000..016117feb --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import android.hardware.usb.UsbConstants; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbEndpoint; +import android.hardware.usb.UsbInterface; +import android.hardware.usb.UsbManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Pair; + +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Based on USB CCID Specification rev. 1.1 + * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf + * Implements small subset of these features + */ +public class UsbTransport implements Transport { + private static final int USB_CLASS_SMARTCARD = 11; + private static final int TIMEOUT = 20 * 1000; // 20s + + private final UsbManager mUsbManager; + private final UsbDevice mUsbDevice; + private UsbInterface mUsbInterface; + private UsbEndpoint mBulkIn; + private UsbEndpoint mBulkOut; + private UsbDeviceConnection mConnection; + private byte mCounter; + + public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { + mUsbDevice = usbDevice; + mUsbManager = usbManager; + } + + + /** + * Manage ICC power, Yubikey requires to power on ICC + * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff + * + * @param on true to turn ICC on, false to turn it off + * @throws UsbTransportException + */ + private void iccPowerSet(boolean on) throws UsbTransportException { + final byte[] iccPowerCommand = { + (byte) (on ? 0x62 : 0x63), + 0x00, 0x00, 0x00, 0x00, + 0x00, + mCounter++, + 0x00, + 0x00, 0x00 + }; + + sendRaw(iccPowerCommand); + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + checkDataBlockResponse(bytes); + } + + /** + * Get first class 11 (Chip/Smartcard) interface of the device + * + * @param device {@link UsbDevice} which will be searched + * @return {@link UsbInterface} of smartcard or null if it doesn't exist + */ + @Nullable + private static UsbInterface getSmartCardInterface(UsbDevice device) { + for (int i = 0; i < device.getInterfaceCount(); i++) { + UsbInterface anInterface = device.getInterface(i); + if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) { + return anInterface; + } + } + return null; + } + + /** + * Get device's bulk-in and bulk-out endpoints + * + * @param usbInterface usb device interface + * @return pair of builk-in and bulk-out endpoints respectively + */ + @NonNull + private static Pair getIoEndpoints(final UsbInterface usbInterface) { + UsbEndpoint bulkIn = null, bulkOut = null; + for (int i = 0; i < usbInterface.getEndpointCount(); i++) { + final UsbEndpoint endpoint = usbInterface.getEndpoint(i); + if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { + continue; + } + + if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { + bulkIn = endpoint; + } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { + bulkOut = endpoint; + } + } + return new Pair<>(bulkIn, bulkOut); + } + + /** + * Release interface and disconnect + */ + @Override + public void release() { + if (mConnection != null) { + mConnection.releaseInterface(mUsbInterface); + mConnection.close(); + mConnection = null; + } + + Log.d(Constants.TAG, "Usb transport disconnected"); + } + + /** + * Check if device is was connected to and still is connected + * @return true if device is connected + */ + @Override + public boolean isConnected() { + return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); + } + + /** + * Check if Transport supports persistent connections e.g connections which can + * handle multiple operations in one session + * @return true if transport supports persistent connections + */ + @Override + public boolean isPersistentConnectionAllowed() { + return true; + } + + /** + * Connect to OTG device + * @throws IOException + */ + @Override + public void connect() throws IOException { + mCounter = 0; + mUsbInterface = getSmartCardInterface(mUsbDevice); + if (mUsbInterface == null) { + // Shouldn't happen as we whitelist only class 11 devices + throw new UsbTransportException("USB error: device doesn't have class 11 interface"); + } + + final Pair ioEndpoints = getIoEndpoints(mUsbInterface); + mBulkIn = ioEndpoints.first; + mBulkOut = ioEndpoints.second; + + if (mBulkIn == null || mBulkOut == null) { + throw new UsbTransportException("USB error: invalid class 11 interface"); + } + + mConnection = mUsbManager.openDevice(mUsbDevice); + if (mConnection == null) { + throw new UsbTransportException("USB error: failed to connect to device"); + } + + if (!mConnection.claimInterface(mUsbInterface, true)) { + throw new UsbTransportException("USB error: failed to claim interface"); + } + + iccPowerSet(true); + Log.d(Constants.TAG, "Usb transport connected"); + } + + /** + * Transmit and receive data + * @param data data to transmit + * @return received data + * @throws UsbTransportException + */ + @Override + public byte[] transceive(byte[] data) throws UsbTransportException { + sendXfrBlock(data); + byte[] bytes; + do { + bytes = receive(); + } while (isDataBlockNotReady(bytes)); + + checkDataBlockResponse(bytes); + // Discard header + return Arrays.copyOfRange(bytes, 10, bytes.length); + } + + /** + * Transmits XfrBlock + * 6.1.4 PC_to_RDR_XfrBlock + * @param payload payload to transmit + * @throws UsbTransportException + */ + private void sendXfrBlock(byte[] payload) throws UsbTransportException { + int l = payload.length; + byte[] data = Arrays.concatenate(new byte[]{ + 0x6f, + (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), + 0x00, + mCounter++, + 0x00, + 0x00, 0x00}, + payload); + + int send = 0; + while (send < data.length) { + final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); + sendRaw(Arrays.copyOfRange(data, send, send + len)); + send += len; + } + } + + private byte[] receive() throws UsbTransportException { + byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; + byte[] result = null; + int readBytes = 0, totalBytes = 0; + + do { + int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); + if (res < 0) { + throw new UsbTransportException("USB error: failed to receive response " + res); + } + if (result == null) { + if (res < 10) { + throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); + } + totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; + result = new byte[totalBytes]; + } + System.arraycopy(buffer, 0, result, readBytes, res); + readBytes += res; + } while (readBytes < totalBytes); + + return result; + } + + private void sendRaw(final byte[] data) throws UsbTransportException { + final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); + if (tr1 != data.length) { + throw new UsbTransportException("USB error: failed to transmit data " + tr1); + } + } + + private byte getStatus(byte[] bytes) { + return (byte) ((bytes[7] >> 6) & 0x03); + } + + private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { + final byte status = getStatus(bytes); + if (status != 0) { + throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + } + } + + private boolean isDataBlockNotReady(byte[] bytes) { + return getStatus(bytes) == 2; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + final UsbTransport that = (UsbTransport) o; + + return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null; + } + + @Override + public int hashCode() { + return mUsbDevice != null ? mUsbDevice.hashCode() : 0; + } + + public UsbDevice getUsbDevice() { + return mUsbDevice; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java new file mode 100644 index 000000000..6d9212d9f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransportException.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.securitytoken; + +import java.io.IOException; + +public class UsbTransportException extends IOException { + public UsbTransportException() { + } + + public UsbTransportException(final String detailMessage) { + super(detailMessage); + } + + public UsbTransportException(final String message, final Throwable cause) { + super(message, cause); + } + + public UsbTransportException(final Throwable cause) { + super(cause); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java deleted file mode 100644 index 06445e988..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/CardException.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.smartcard; - -import java.io.IOException; - -public class CardException extends IOException { - private short mResponseCode; - - public CardException(String detailMessage, short responseCode) { - super(detailMessage); - mResponseCode = responseCode; - } - - public short getResponseCode() { - return mResponseCode; - } - -} \ No newline at end of file diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java deleted file mode 100644 index 29c7fce96..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/KeyType.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.smartcard; - -import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; - -public enum KeyType { - SIGN(0, 0xB6, 0xCE, 0xC7), - ENCRYPT(1, 0xB8, 0xCF, 0xC8), - AUTH(2, 0xA4, 0xD0, 0xC9),; - - private final int mIdx; - private final int mSlot; - private final int mTimestampObjectId; - private final int mFingerprintObjectId; - - KeyType(final int idx, final int slot, final int timestampObjectId, final int fingerprintObjectId) { - this.mIdx = idx; - this.mSlot = slot; - this.mTimestampObjectId = timestampObjectId; - this.mFingerprintObjectId = fingerprintObjectId; - } - - public static KeyType from(final CanonicalizedSecretKey key) { - if (key.canSign() || key.canCertify()) { - return SIGN; - } else if (key.canEncrypt()) { - return ENCRYPT; - } else if (key.canAuthenticate()) { - return AUTH; - } - return null; - } - - public int getIdx() { - return mIdx; - } - - public int getmSlot() { - return mSlot; - } - - public int getTimestampObjectId() { - return mTimestampObjectId; - } - - public int getmFingerprintObjectId() { - return mFingerprintObjectId; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java deleted file mode 100644 index a743c753a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/NfcTransport.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.smartcard; - -import android.nfc.Tag; - -import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; - -import java.io.IOException; - -import nordpol.IsoCard; -import nordpol.android.AndroidCard; - -public class NfcTransport implements Transport { - // timeout is set to 100 seconds to avoid cancellation during calculation - private static final int TIMEOUT = 100 * 1000; - private final Tag mTag; - private IsoCard mIsoCard; - - public NfcTransport(Tag tag) { - this.mTag = tag; - } - - /** - * Transmit and receive data - * @param data data to transmit - * @return received data - * @throws IOException - */ - @Override - public byte[] transceive(final byte[] data) throws IOException { - return mIsoCard.transceive(data); - } - - /** - * Disconnect and release connection - */ - @Override - public void release() { - // Not supported - } - - @Override - public boolean isConnected() { - return mIsoCard != null && mIsoCard.isConnected(); - } - - /** - * Check if Transport supports persistent connections e.g connections which can - * handle multiple operations in one session - * @return true if transport supports persistent connections - */ - @Override - public boolean isPersistentConnectionAllowed() { - return false; - } - - /** - * Connect to NFC device. - *

- * On general communication, see also - * http://www.cardwerk.com/smartcards/smartcard_standard_ISO7816-4_annex-a.aspx - *

- * References to pages are generally related to the OpenPGP Application - * on ISO SmartCard Systems specification. - */ - @Override - public void connect() throws IOException { - mIsoCard = AndroidCard.get(mTag); - if (mIsoCard == null) { - throw new BaseSecurityTokenNfcActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)"); - } - - mIsoCard.setTimeout(TIMEOUT); - mIsoCard.connect(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final NfcTransport that = (NfcTransport) o; - - if (mTag != null ? !mTag.equals(that.mTag) : that.mTag != null) return false; - if (mIsoCard != null ? !mIsoCard.equals(that.mIsoCard) : that.mIsoCard != null) - return false; - - return true; - } - - @Override - public int hashCode() { - int result = mTag != null ? mTag.hashCode() : 0; - result = 31 * result + (mIsoCard != null ? mIsoCard.hashCode() : 0); - return result; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java deleted file mode 100644 index 58cf9b51a..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/SmartcardDevice.java +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * Copyright (C) 2013-2015 Dominik Schürmann - * Copyright (C) 2015 Vincent Breitmoser - * Copyright (C) 2013-2014 Signe Rüsch - * Copyright (C) 2013-2014 Philipp Jakubeit - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - - -package org.sufficientlysecure.keychain.smartcard; - -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; - -/** - * This class provides a communication interface to OpenPGP applications on ISO SmartCard compliant - * NFC devices. - * For the full specs, see http://g10code.com/docs/openpgp-card-2.0.pdf - */ -public class SmartcardDevice { - // 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; - - protected SmartcardDevice() { - } - - public static SmartcardDevice getInstance() { - return LazyHolder.mSmartcardDevice; - } - - private static String getHex(byte[] raw) { - return new String(Hex.encode(raw)); - } - - private String getHolderName(String 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; - } catch (IndexOutOfBoundsException e) { - // try-catch for https://github.com/FluffyKaon/OpenPGP-Card - // Note: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus return an empty string for now! - - Log.e(Constants.TAG, "Couldn't get holder name, returning empty string!", e); - return ""; - } - } - - 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 = isSlotEmpty(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); - } - - private boolean isSlotEmpty(KeyType keyType) throws IOException { - // Note: special case: This should not happen, but happens with - // https://github.com/FluffyKaon/OpenPGP-Card, thus for now assume true - if (getMasterKeyFingerprint(keyType.getIdx()) == null) return true; - - return keyMatchesFingerPrint(keyType, BLANK_FINGERPRINT); - } - - public boolean keyMatchesFingerPrint(KeyType keyType, byte[] fingerprint) throws IOException { - return java.util.Arrays.equals(getMasterKeyFingerprint(keyType.getIdx()), fingerprint); - } - - /** - * Connect to device and select pgp applet - * - * @throws IOException - */ - public void connectToDevice() throws IOException { - // Connect on transport layer - 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)); - } - - byte[] pwStatusBytes = getPwStatusBytes(); - 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. - */ - 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. - * - * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. - * @param newPin The new PW1 or PW3. - */ - 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 = getPwStatusBytes(); - - if (pw == 0x81) { - if (newPin.length < 6 || newPin.length > pwStatusBytes[MAX_PW1_LENGTH_INDEX]) { - throw new IOException("Invalid PIN length"); - } - } else if (pw == 0x83) { - if (newPin.length < 8 || newPin.length > pwStatusBytes[MAX_PW3_LENGTH_INDEX]) { - throw new IOException("Invalid PIN length"); - } - } else { - throw new IOException("Invalid PW index for modify PIN operation"); - } - - 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 = communicate(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 - */ - public byte[] decryptSessionKey(byte[] encryptedSessionKey) throws IOException { - if (!mPw1ValidatedForDecrypt) { - verifyPin(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]; - } - - communicate(firstApdu + getHex(one)); - String second = communicate(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. - */ - private void verifyPin(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 = tryPin(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 - */ - private 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) { - verifyPin(0x82); // (Verify PW1 for non-signing operations) - } - } else if (!mPw3Validated) { - 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); - - String response = communicate(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 - */ - private 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) { - verifyPin(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 = 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)); - } - } - - // 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. - */ - public byte[] getFingerprints() throws IOException { - String data = "00CA006E00"; - byte[] buf = mTransport.transceive(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. - */ - private byte[] getPwStatusBytes() throws IOException { - String data = "00CA00C400"; - return mTransport.transceive(Hex.decode(data)); - } - - public byte[] getAid() throws IOException { - String info = "00CA004F00"; - return mTransport.transceive(Hex.decode(info)); - } - - public String getUserId() throws IOException { - String info = "00CA006500"; - return getHolderName(communicate(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 - */ - public byte[] calculateSignature(byte[] hash, int hashAlgo) throws IOException { - if (!mPw1ValidatedForSignature) { - verifyPin(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 = communicate(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 = 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)); - } - - // 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); - } - - /** - * Transceive data via NFC encoded as Hex - */ - private String communicate(String apdu) throws IOException { - return getHex(mTransport.transceive(Hex.decode(apdu))); - } - - public Transport getTransport() { - return mTransport; - } - - public void setTransport(Transport mTransport) { - this.mTransport = mTransport; - } - - 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.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); - } 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. - */ - public byte[] generateKey(int slot) throws IOException { - if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { - throw new IOException("Invalid key slot"); - } - - if (!mPw3Validated) { - verifyPin(0x83); // (Verify PW3 with mode 83) - } - - String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; - String getResponseApdu = "00C00000"; - - String first = communicate(generateKeyApdu); - String second = communicate(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); - } - - private String getDataField(String output) { - return output.substring(0, output.length() - 4); - } - - private String 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); - } - - /** - * 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. - */ - 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 = tryPin(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 = tryPin(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 = communicate(reactivate1); - String response2 = communicate(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; - } - - public boolean isPersistentConnectionAllowed() { - return mTransport != null && mTransport.isPersistentConnectionAllowed(); - } - - public boolean isConnected() { - return mTransport != null && mTransport.isConnected(); - } - - private static class LazyHolder { - private static final SmartcardDevice mSmartcardDevice = new SmartcardDevice(); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java deleted file mode 100644 index 5b942ee5c..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/Transport.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.smartcard; - -import java.io.IOException; - -/** - * Abstraction for transmitting APDU commands - */ -public interface Transport { - /** - * Transmit and receive data - * @param data data to transmit - * @return received data - * @throws IOException - */ - byte[] transceive(byte[] data) throws IOException; - - /** - * Disconnect and release connection - */ - void release(); - - /** - * Check if device is was connected to and still is connected - * @return connection status - */ - boolean isConnected(); - - /** - * Check if Transport supports persistent connections e.g connections which can - * handle multiple operations in one session - * @return true if transport supports persistent connections - */ - boolean isPersistentConnectionAllowed(); - - - /** - * Connect to device - * @throws IOException - */ - void connect() throws IOException; -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java deleted file mode 100644 index 8d661b9fa..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransport.java +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.smartcard; - -import android.hardware.usb.UsbConstants; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbEndpoint; -import android.hardware.usb.UsbInterface; -import android.hardware.usb.UsbManager; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.util.Pair; - -import org.bouncycastle.util.Arrays; -import org.bouncycastle.util.encoders.Hex; -import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.util.Log; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -/** - * Based on USB CCID Specification rev. 1.1 - * http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf - * Implements small subset of these features - */ -public class UsbTransport implements Transport { - private static final int USB_CLASS_SMARTCARD = 11; - private static final int TIMEOUT = 20 * 1000; // 20s - - private final UsbManager mUsbManager; - private final UsbDevice mUsbDevice; - private UsbInterface mUsbInterface; - private UsbEndpoint mBulkIn; - private UsbEndpoint mBulkOut; - private UsbDeviceConnection mConnection; - private byte mCounter; - - public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { - mUsbDevice = usbDevice; - mUsbManager = usbManager; - } - - - /** - * Manage ICC power, Yubikey requires to power on ICC - * Spec: 6.1.1 PC_to_RDR_IccPowerOn; 6.1.2 PC_to_RDR_IccPowerOff - * - * @param on true to turn ICC on, false to turn it off - * @throws UsbTransportException - */ - private void iccPowerSet(boolean on) throws UsbTransportException { - final byte[] iccPowerCommand = { - (byte) (on ? 0x62 : 0x63), - 0x00, 0x00, 0x00, 0x00, - 0x00, - mCounter++, - 0x00, - 0x00, 0x00 - }; - - sendRaw(iccPowerCommand); - byte[] bytes; - do { - bytes = receive(); - } while (isDataBlockNotReady(bytes)); - checkDataBlockResponse(bytes); - } - - /** - * Get first class 11 (Chip/Smartcard) interface of the device - * - * @param device {@link UsbDevice} which will be searched - * @return {@link UsbInterface} of smartcard or null if it doesn't exist - */ - @Nullable - private static UsbInterface getSmartCardInterface(UsbDevice device) { - for (int i = 0; i < device.getInterfaceCount(); i++) { - UsbInterface anInterface = device.getInterface(i); - if (anInterface.getInterfaceClass() == USB_CLASS_SMARTCARD) { - return anInterface; - } - } - return null; - } - - /** - * Get device's bulk-in and bulk-out endpoints - * - * @param usbInterface usb device interface - * @return pair of builk-in and bulk-out endpoints respectively - */ - @NonNull - private static Pair getIoEndpoints(final UsbInterface usbInterface) { - UsbEndpoint bulkIn = null, bulkOut = null; - for (int i = 0; i < usbInterface.getEndpointCount(); i++) { - final UsbEndpoint endpoint = usbInterface.getEndpoint(i); - if (endpoint.getType() != UsbConstants.USB_ENDPOINT_XFER_BULK) { - continue; - } - - if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) { - bulkIn = endpoint; - } else if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) { - bulkOut = endpoint; - } - } - return new Pair<>(bulkIn, bulkOut); - } - - /** - * Release interface and disconnect - */ - @Override - public void release() { - if (mConnection != null) { - mConnection.releaseInterface(mUsbInterface); - mConnection.close(); - mConnection = null; - } - - Log.d(Constants.TAG, "Usb transport disconnected"); - } - - /** - * Check if device is was connected to and still is connected - * @return true if device is connected - */ - @Override - public boolean isConnected() { - return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); - } - - /** - * Check if Transport supports persistent connections e.g connections which can - * handle multiple operations in one session - * @return true if transport supports persistent connections - */ - @Override - public boolean isPersistentConnectionAllowed() { - return true; - } - - /** - * Connect to OTG device - * @throws IOException - */ - @Override - public void connect() throws IOException { - mCounter = 0; - mUsbInterface = getSmartCardInterface(mUsbDevice); - if (mUsbInterface == null) { - // Shouldn't happen as we whitelist only class 11 devices - throw new UsbTransportException("USB error: device doesn't have class 11 interface"); - } - - final Pair ioEndpoints = getIoEndpoints(mUsbInterface); - mBulkIn = ioEndpoints.first; - mBulkOut = ioEndpoints.second; - - if (mBulkIn == null || mBulkOut == null) { - throw new UsbTransportException("USB error: invalid class 11 interface"); - } - - mConnection = mUsbManager.openDevice(mUsbDevice); - if (mConnection == null) { - throw new UsbTransportException("USB error: failed to connect to device"); - } - - if (!mConnection.claimInterface(mUsbInterface, true)) { - throw new UsbTransportException("USB error: failed to claim interface"); - } - - iccPowerSet(true); - Log.d(Constants.TAG, "Usb transport connected"); - } - - /** - * Transmit and receive data - * @param data data to transmit - * @return received data - * @throws UsbTransportException - */ - @Override - public byte[] transceive(byte[] data) throws UsbTransportException { - sendXfrBlock(data); - byte[] bytes; - do { - bytes = receive(); - } while (isDataBlockNotReady(bytes)); - - checkDataBlockResponse(bytes); - // Discard header - return Arrays.copyOfRange(bytes, 10, bytes.length); - } - - /** - * Transmits XfrBlock - * 6.1.4 PC_to_RDR_XfrBlock - * @param payload payload to transmit - * @throws UsbTransportException - */ - private void sendXfrBlock(byte[] payload) throws UsbTransportException { - int l = payload.length; - byte[] data = Arrays.concatenate(new byte[]{ - 0x6f, - (byte) l, (byte) (l >> 8), (byte) (l >> 16), (byte) (l >> 24), - 0x00, - mCounter++, - 0x00, - 0x00, 0x00}, - payload); - - int send = 0; - while (send < data.length) { - final int len = Math.min(mBulkIn.getMaxPacketSize(), data.length - send); - sendRaw(Arrays.copyOfRange(data, send, send + len)); - send += len; - } - } - - private byte[] receive() throws UsbTransportException { - byte[] buffer = new byte[mBulkIn.getMaxPacketSize()]; - byte[] result = null; - int readBytes = 0, totalBytes = 0; - - do { - int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); - if (res < 0) { - throw new UsbTransportException("USB error: failed to receive response " + res); - } - if (result == null) { - if (res < 10) { - throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); - } - totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; - result = new byte[totalBytes]; - } - System.arraycopy(buffer, 0, result, readBytes, res); - readBytes += res; - } while (readBytes < totalBytes); - - return result; - } - - private void sendRaw(final byte[] data) throws UsbTransportException { - final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); - if (tr1 != data.length) { - throw new UsbTransportException("USB error: failed to transmit data " + tr1); - } - } - - private byte getStatus(byte[] bytes) { - return (byte) ((bytes[7] >> 6) & 0x03); - } - - private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { - final byte status = getStatus(bytes); - if (status != 0) { - throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); - } - } - - private boolean isDataBlockNotReady(byte[] bytes) { - return getStatus(bytes) == 2; - } - - @Override - public boolean equals(final Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - final UsbTransport that = (UsbTransport) o; - - return mUsbDevice != null ? mUsbDevice.equals(that.mUsbDevice) : that.mUsbDevice == null; - } - - @Override - public int hashCode() { - return mUsbDevice != null ? mUsbDevice.hashCode() : 0; - } - - public UsbDevice getUsbDevice() { - return mUsbDevice; - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java deleted file mode 100644 index 7bf713bf9..000000000 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/smartcard/UsbTransportException.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2016 Nikita Mikhailov - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package org.sufficientlysecure.keychain.smartcard; - -import java.io.IOException; - -public class UsbTransportException extends IOException { - public UsbTransportException() { - } - - public UsbTransportException(final String detailMessage) { - super(detailMessage); - } - - public UsbTransportException(final String message, final Throwable cause) { - super(message, cause); - } - - public UsbTransportException(final Throwable cause) { - super(cause); - } -} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index a9d259b00..83beccb2a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -47,9 +47,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { public static final String EXTRA_SECURITY_TOKEN_PIN = "yubi_key_pin"; public static final String EXTRA_SECURITY_TOKEN_ADMIN_PIN = "yubi_key_admin_pin"; - public static final String EXTRA_NFC_USER_ID = "nfc_user_id"; - public static final String EXTRA_NFC_AID = "nfc_aid"; - public static final String EXTRA_NFC_FINGERPRINTS = "nfc_fingerprints"; + public static final String EXTRA_SECURITY_TOKEN_USER_ID = "nfc_user_id"; + public static final String EXTRA_SECURITY_TOKEN_AID = "nfc_aid"; + public static final String EXTRA_SECURITY_FINGERPRINTS = "nfc_fingerprints"; public static final String FRAGMENT_TAG = "currentFragment"; @@ -66,8 +66,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { byte[] mScannedFingerprints; - byte[] mNfcAid; - String mNfcUserId; + byte[] mSecurityTokenAid; + String mSecurityTokenUserId; @Override public void onCreate(Bundle savedInstanceState) { @@ -107,10 +107,10 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { mFirstTime = intent.getBooleanExtra(EXTRA_FIRST_TIME, false); mCreateSecurityToken = intent.getBooleanExtra(EXTRA_CREATE_SECURITY_TOKEN, false); - if (intent.hasExtra(EXTRA_NFC_FINGERPRINTS)) { - byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_NFC_FINGERPRINTS); - String nfcUserId = intent.getStringExtra(EXTRA_NFC_USER_ID); - byte[] nfcAid = intent.getByteArrayExtra(EXTRA_NFC_AID); + if (intent.hasExtra(EXTRA_SECURITY_FINGERPRINTS)) { + byte[] nfcFingerprints = intent.getByteArrayExtra(EXTRA_SECURITY_FINGERPRINTS); + String nfcUserId = intent.getStringExtra(EXTRA_SECURITY_TOKEN_USER_ID); + byte[] nfcAid = intent.getByteArrayExtra(EXTRA_SECURITY_TOKEN_AID); if (containsKeys(nfcFingerprints)) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( @@ -143,19 +143,19 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } @Override - protected void doSmartcardInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); return; } - mScannedFingerprints = mSmartcardDevice.getFingerprints(); - mNfcAid = mSmartcardDevice.getAid(); - mNfcUserId = mSmartcardDevice.getUserId(); + mScannedFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); } @Override - protected void onSmartcardPostExecute() { + protected void onSecurityTokenPostExecute() { if (mCurrentFragment instanceof NfcListenerFragment) { ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); return; @@ -169,15 +169,15 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mScannedFingerprints); startActivity(intent); finish(); } catch (PgpKeyNotFoundException e) { Fragment frag = CreateSecurityTokenImportResetFragment.newInstance( - mScannedFingerprints, mNfcAid, mNfcUserId); + mScannedFingerprints, mSecurityTokenAid, mSecurityTokenUserId); loadFragment(frag, FragAction.TO_RIGHT); } } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index aba06ac47..7a27e4acc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -249,9 +249,9 @@ public class CreateSecurityTokenImportResetFragment @Override public void doNfcInBackground() throws IOException { - mTokenFingerprints = mCreateKeyActivity.getSmartcardDevice().getFingerprints(); - mTokenAid = mCreateKeyActivity.getSmartcardDevice().getAid(); - mTokenUserId = mCreateKeyActivity.getSmartcardDevice().getUserId(); + mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); + mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); + mTokenUserId = mCreateKeyActivity.getSecurityTokenHelper().getUserId(); byte[] fp = new byte[20]; ByteBuffer.wrap(fp).put(mTokenFingerprints, 0, 20); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ac12c2a4a..ddc1a7ca0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -172,20 +172,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onSmartcardPreExecute() { + public void onSecurityTokenPreExecute() { // start with indeterminate progress vAnimator.setDisplayedChild(1); nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.TRANSFERRING); } @Override - protected void doSmartcardInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { switch (mRequiredInput.mType) { case SMARTCARD_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; - byte[] decryptedSessionKey = mSmartcardDevice.decryptSessionKey(encryptedSessionKey); + byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); mInputParcel.addCryptoData(encryptedSessionKey, decryptedSessionKey); } break; @@ -196,15 +196,15 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] hash = mRequiredInput.mInputData[i]; int algo = mRequiredInput.mSignAlgos[i]; - byte[] signedHash = mSmartcardDevice.calculateSignature(hash, algo); + byte[] signedHash = mSecurityTokenHelper.calculateSignature(hash, algo); mInputParcel.addCryptoData(hash, signedHash); } break; } case SMARTCARD_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation - mSmartcardDevice.setPin(new Passphrase("123456")); - mSmartcardDevice.setAdminPin(new Passphrase("12345678")); + mSecurityTokenHelper.setPin(new Passphrase("123456")); + mSecurityTokenHelper.setAdminPin(new Passphrase("12345678")); ProviderHelper providerHelper = new ProviderHelper(this); CanonicalizedSecretKeyRing secretKeyRing; @@ -225,7 +225,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity long subkeyId = buf.getLong(); CanonicalizedSecretKey key = secretKeyRing.getSecretKey(subkeyId); - byte[] tokenSerialNumber = Arrays.copyOf(mSmartcardDevice.getAid(), 16); + byte[] tokenSerialNumber = Arrays.copyOf(mSecurityTokenHelper.getAid(), 16); Passphrase passphrase; try { @@ -235,20 +235,20 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity throw new IOException("Unable to get cached passphrase!"); } - mSmartcardDevice.changeKey(key, passphrase); + mSecurityTokenHelper.changeKey(key, passphrase); // TODO: Is this really used anywhere? mInputParcel.addCryptoData(subkeyBytes, tokenSerialNumber); } // change PINs afterwards - mSmartcardDevice.modifyPin(0x81, newPin); - mSmartcardDevice.modifyPin(0x83, newAdminPin); + mSecurityTokenHelper.modifyPin(0x81, newPin); + mSecurityTokenHelper.modifyPin(0x83, newAdminPin); break; } case SMARTCARD_RESET_CARD: { - mSmartcardDevice.resetAndWipeToken(); + mSecurityTokenHelper.resetAndWipeToken(); break; } @@ -260,7 +260,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected final void onSmartcardPostExecute() { + protected final void onSecurityTokenPostExecute() { handleResult(mInputParcel); // show finish @@ -268,7 +268,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity nfcGuideView.setCurrentStatus(NfcGuideView.NfcGuideViewStatus.DONE); - if (mSmartcardDevice.isPersistentConnectionAllowed()) { + if (mSecurityTokenHelper.isPersistentConnectionAllowed()) { // Just close finish(); } else { @@ -309,7 +309,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - protected void onSmartcardError(String error) { + protected void onSecurityTokenError(String error) { pauseTagHandling(); vErrorText.setText(error + "\n\n" + getString(R.string.security_token_nfc_try_again_text)); @@ -319,8 +319,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } @Override - public void onSmartcardPinError(String error) { - onSmartcardError(error); + public void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); // clear (invalid) passphrase PassphraseCacheService.clearCachedPassphrase( diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index dd753a431..e47ca1db9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -32,7 +32,6 @@ import android.app.ActivityOptions; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.PorterDuff; import android.net.Uri; import android.nfc.NfcAdapter; @@ -172,9 +171,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements private byte[] mFingerprint; private String mFingerprintString; - private byte[] mNfcFingerprints; - private String mNfcUserId; - private byte[] mNfcAid; + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; @SuppressLint("InflateParams") @Override @@ -647,17 +646,17 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements } @Override - protected void doSmartcardInBackground() throws IOException { + protected void doSecurityTokenInBackground() throws IOException { - mNfcFingerprints = mSmartcardDevice.getFingerprints(); - mNfcUserId = mSmartcardDevice.getUserId(); - mNfcAid = mSmartcardDevice.getAid(); + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); } @Override - protected void onSmartcardPostExecute() { + protected void onSecurityTokenPostExecute() { - long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mNfcFingerprints); + long tokenId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { @@ -668,7 +667,7 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements // if the master key of that key matches this one, just show the token dialog if (KeyFormattingUtils.convertFingerprintToHex(candidateFp).equals(mFingerprintString)) { - showSecurityTokenFragment(mNfcFingerprints, mNfcUserId, mNfcAid); + showSecurityTokenFragment(mSecurityTokenFingerprints, mSecurityTokenUserId, mSecurityTokenAid); return; } @@ -681,9 +680,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements Intent intent = new Intent( ViewKeyActivity.this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } @@ -696,9 +695,9 @@ public class ViewKeyActivity extends BaseSecurityTokenNfcActivity implements public void onAction() { Intent intent = new Intent( ViewKeyActivity.this, CreateKeyActivity.class); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mNfcAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mNfcUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mNfcFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); finish(); } 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 5ad542526..4aaf2aba0 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 @@ -24,7 +24,6 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; @@ -40,12 +39,12 @@ import org.sufficientlysecure.keychain.provider.ProviderHelper; import org.sufficientlysecure.keychain.service.PassphraseCacheService; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; -import org.sufficientlysecure.keychain.smartcard.CardException; -import org.sufficientlysecure.keychain.smartcard.NfcTransport; -import org.sufficientlysecure.keychain.smartcard.SmartcardDevice; -import org.sufficientlysecure.keychain.smartcard.Transport; +import org.sufficientlysecure.keychain.securitytoken.CardException; +import org.sufficientlysecure.keychain.securitytoken.NfcTransport; +import org.sufficientlysecure.keychain.securitytoken.SecurityTokenHelper; +import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.util.UsbConnectionDispatcher; -import org.sufficientlysecure.keychain.smartcard.UsbTransport; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.CreateKeyActivity; import org.sufficientlysecure.keychain.ui.PassphraseDialogActivity; import org.sufficientlysecure.keychain.ui.ViewKeyActivity; @@ -70,36 +69,36 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private static final String FIDESMO_APP_PACKAGE = "com.fidesmo.sec.android"; - protected SmartcardDevice mSmartcardDevice = SmartcardDevice.getInstance(); + protected SecurityTokenHelper mSecurityTokenHelper = SecurityTokenHelper.getInstance(); protected TagDispatcher mTagDispatcher; protected UsbConnectionDispatcher mUsbDispatcher; private boolean mTagHandlingEnabled; - private byte[] mSmartcardFingerprints; - private String mSmartcardUserId; - private byte[] mSmartcardAid; + private byte[] mSecurityTokenFingerprints; + private String mSecurityTokenUserId; + private byte[] mSecurityTokenAid; /** - * Override to change UI before NFC handling (UI thread) + * Override to change UI before SecurityToken handling (UI thread) */ - protected void onSmartcardPreExecute() { + protected void onSecurityTokenPreExecute() { } /** - * Override to implement NFC operations (background thread) + * Override to implement SecurityToken operations (background thread) */ - protected void doSmartcardInBackground() throws IOException { - mSmartcardFingerprints = mSmartcardDevice.getFingerprints(); - mSmartcardUserId = mSmartcardDevice.getUserId(); - mSmartcardAid = mSmartcardDevice.getAid(); + protected void doSecurityTokenInBackground() throws IOException { + mSecurityTokenFingerprints = mSecurityTokenHelper.getFingerprints(); + mSecurityTokenUserId = mSecurityTokenHelper.getUserId(); + mSecurityTokenAid = mSecurityTokenHelper.getAid(); } /** - * Override to handle result of NFC operations (UI thread) + * Override to handle result of SecurityToken operations (UI thread) */ - protected void onSmartcardPostExecute() { + protected void onSecurityTokenPostExecute() { - final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSmartcardFingerprints); + final long subKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mSecurityTokenFingerprints); try { CachedPublicKeyRing ring = new ProviderHelper(this).getCachedPublicKeyRing( @@ -108,15 +107,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Intent intent = new Intent(this, ViewKeyActivity.class); intent.setData(KeyRings.buildGenericKeyRingUri(masterKeyId)); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSmartcardAid); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSmartcardUserId); - intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSmartcardFingerprints); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(ViewKeyActivity.EXTRA_SECURITY_TOKEN_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); } catch (PgpKeyNotFoundException e) { Intent intent = new Intent(this, CreateKeyActivity.class); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_AID, mSmartcardAid); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_USER_ID, mSmartcardUserId); - intent.putExtra(CreateKeyActivity.EXTRA_NFC_FINGERPRINTS, mSmartcardFingerprints); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_AID, mSecurityTokenAid); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_TOKEN_USER_ID, mSecurityTokenUserId); + intent.putExtra(CreateKeyActivity.EXTRA_SECURITY_FINGERPRINTS, mSecurityTokenFingerprints); startActivity(intent); } } @@ -124,15 +123,15 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity /** * Override to use something different than Notify (UI thread) */ - protected void onSmartcardError(String error) { + protected void onSecurityTokenError(String error) { Notify.create(this, error, Style.WARN).show(); } /** * Override to do something when PIN is wrong, e.g., clear passphrases (UI thread) */ - protected void onSmartcardPinError(String error) { - onSmartcardError(error); + protected void onSecurityTokenPinError(String error) { + onSecurityTokenError(error); } public void tagDiscovered(final Tag tag) { @@ -140,7 +139,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if (!mTagHandlingEnabled) return; - smartcardDiscovered(new NfcTransport(tag)); + securityTokenDiscovered(new NfcTransport(tag)); } public void usbDeviceDiscovered(final UsbTransport transport) { @@ -148,10 +147,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if (!mTagHandlingEnabled) return; - smartcardDiscovered(transport); + securityTokenDiscovered(transport); } - public void smartcardDiscovered(final Transport transport) { + public void securityTokenDiscovered(final Transport transport) { // Actual Smartcard operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; @@ -159,7 +158,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity @Override protected void onPreExecute() { super.onPreExecute(); - onSmartcardPreExecute(); + onSecurityTokenPreExecute(); } @Override @@ -182,7 +181,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } - onSmartcardPostExecute(); + onSecurityTokenPostExecute(); } }.execute(); } @@ -237,12 +236,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity private void handleSmartcardError(IOException e) { if (e instanceof TagLostException) { - onSmartcardError(getString(R.string.security_token_error_tag_lost)); + onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); return; } if (e instanceof IsoDepNotSupportedException) { - onSmartcardError(getString(R.string.security_token_error_iso_dep_not_supported)); + onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported)); return; } @@ -257,7 +256,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity if ((status & (short) 0xFFF0) == 0x63C0) { int tries = status & 0x000F; // hook to do something different when PIN is wrong - onSmartcardPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); + onSecurityTokenPinError(getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries)); return; } @@ -266,61 +265,61 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity // These errors should not occur in everyday use; if they are returned, it means we // made a mistake sending data to the token, or the token is misbehaving. case 0x6A80: { - onSmartcardError(getString(R.string.security_token_error_bad_data)); + onSecurityTokenError(getString(R.string.security_token_error_bad_data)); break; } case 0x6883: { - onSmartcardError(getString(R.string.security_token_error_chaining_error)); + onSecurityTokenError(getString(R.string.security_token_error_chaining_error)); break; } case 0x6B00: { - onSmartcardError(getString(R.string.security_token_error_header, "P1/P2")); + onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2")); break; } case 0x6D00: { - onSmartcardError(getString(R.string.security_token_error_header, "INS")); + onSecurityTokenError(getString(R.string.security_token_error_header, "INS")); break; } case 0x6E00: { - onSmartcardError(getString(R.string.security_token_error_header, "CLA")); + onSecurityTokenError(getString(R.string.security_token_error_header, "CLA")); break; } // These error conditions are more likely to be experienced by an end user. case 0x6285: { - onSmartcardError(getString(R.string.security_token_error_terminated)); + onSecurityTokenError(getString(R.string.security_token_error_terminated)); break; } case 0x6700: { - onSmartcardPinError(getString(R.string.security_token_error_wrong_length)); + onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length)); break; } case 0x6982: { - onSmartcardError(getString(R.string.security_token_error_security_not_satisfied)); + onSecurityTokenError(getString(R.string.security_token_error_security_not_satisfied)); break; } case 0x6983: { - onSmartcardError(getString(R.string.security_token_error_authentication_blocked)); + onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked)); break; } case 0x6985: { - onSmartcardError(getString(R.string.security_token_error_conditions_not_satisfied)); + onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied)); break; } // 6A88 is "Not Found" in the spec, but Yubikey also returns 6A83 for this in some cases. case 0x6A88: case 0x6A83: { - onSmartcardError(getString(R.string.security_token_error_data_not_found)); + onSecurityTokenError(getString(R.string.security_token_error_data_not_found)); break; } // 6F00 is a JavaCard proprietary status code, SW_UNKNOWN, and usually represents an // unhandled exception on the security token. case 0x6F00: { - onSmartcardError(getString(R.string.security_token_error_unknown)); + onSecurityTokenError(getString(R.string.security_token_error_unknown)); break; } // 6A82 app not installed on security token! case 0x6A82: { - if (mSmartcardDevice.isFidesmoToken()) { + if (mSecurityTokenHelper.isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -328,12 +327,12 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity promptFidesmoAppInstall(); } } else { // Other (possibly) compatible hardware - onSmartcardError(getString(R.string.security_token_error_pgp_app_not_installed)); + onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed)); } break; } default: { - onSmartcardError(getString(R.string.security_token_error, e.getMessage())); + onSecurityTokenError(getString(R.string.security_token_error, e.getMessage())); break; } } @@ -367,7 +366,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity Passphrase passphrase = PassphraseCacheService.getCachedPassphrase(this, requiredInput.getMasterKeyId(), requiredInput.getSubKeyId()); if (passphrase != null) { - mSmartcardDevice.setPin(passphrase); + mSecurityTokenHelper.setPin(passphrase); return; } @@ -392,7 +391,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity return; } CryptoInputParcel input = data.getParcelableExtra(PassphraseDialogActivity.RESULT_CRYPTO_INPUT); - mSmartcardDevice.setPin(input.getPassphrase()); + mSecurityTokenHelper.setPin(input.getPassphrase()); break; } default: @@ -402,16 +401,16 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected void handleSmartcard(Transport transport) throws IOException { // Don't reconnect if device was already connected - if (!(mSmartcardDevice.isConnected() - && mSmartcardDevice.getTransport().equals(transport))) { - mSmartcardDevice.setTransport(transport); - mSmartcardDevice.connectToDevice(); + if (!(mSecurityTokenHelper.isConnected() + && mSecurityTokenHelper.getTransport().equals(transport))) { + mSecurityTokenHelper.setTransport(transport); + mSecurityTokenHelper.connectToDevice(); } - doSmartcardInBackground(); + doSecurityTokenInBackground(); } public boolean isSmartcardConnected() { - return mSmartcardDevice.isConnected(); + return mSecurityTokenHelper.isConnected(); } public static class IsoDepNotSupportedException extends IOException { @@ -470,8 +469,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mUsbDispatcher.onStart(); } - public SmartcardDevice getSmartcardDevice() { - return mSmartcardDevice; + public SecurityTokenHelper getSecurityTokenHelper() { + return mSecurityTokenHelper; } /** diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 09b029523..7055b2633 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -26,7 +26,7 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; -import org.sufficientlysecure.keychain.smartcard.UsbTransport; +import org.sufficientlysecure.keychain.securitytoken.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; public class UsbConnectionDispatcher { -- cgit v1.2.3 From 784bf2322cc37befb4857f0c440f889d43f89a48 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Tue, 12 Apr 2016 00:06:21 +0600 Subject: OTG: Replace colons with dashes in usb error messages --- .../keychain/securitytoken/UsbTransport.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java index 016117feb..10043e675 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java @@ -168,7 +168,7 @@ public class UsbTransport implements Transport { mUsbInterface = getSmartCardInterface(mUsbDevice); if (mUsbInterface == null) { // Shouldn't happen as we whitelist only class 11 devices - throw new UsbTransportException("USB error: device doesn't have class 11 interface"); + throw new UsbTransportException("USB error - device doesn't have class 11 interface"); } final Pair ioEndpoints = getIoEndpoints(mUsbInterface); @@ -176,16 +176,16 @@ public class UsbTransport implements Transport { mBulkOut = ioEndpoints.second; if (mBulkIn == null || mBulkOut == null) { - throw new UsbTransportException("USB error: invalid class 11 interface"); + throw new UsbTransportException("USB error - invalid class 11 interface"); } mConnection = mUsbManager.openDevice(mUsbDevice); if (mConnection == null) { - throw new UsbTransportException("USB error: failed to connect to device"); + throw new UsbTransportException("USB error - failed to connect to device"); } if (!mConnection.claimInterface(mUsbInterface, true)) { - throw new UsbTransportException("USB error: failed to claim interface"); + throw new UsbTransportException("USB error - failed to claim interface"); } iccPowerSet(true); @@ -244,11 +244,11 @@ public class UsbTransport implements Transport { do { int res = mConnection.bulkTransfer(mBulkIn, buffer, buffer.length, TIMEOUT); if (res < 0) { - throw new UsbTransportException("USB error: failed to receive response " + res); + throw new UsbTransportException("USB error - failed to receive response " + res); } if (result == null) { if (res < 10) { - throw new UsbTransportException("USB-CCID error: failed to receive CCID header"); + throw new UsbTransportException("USB-CCID error - failed to receive CCID header"); } totalBytes = ByteBuffer.wrap(buffer, 1, 4).order(ByteOrder.LITTLE_ENDIAN).asIntBuffer().get() + 10; result = new byte[totalBytes]; @@ -263,7 +263,7 @@ public class UsbTransport implements Transport { private void sendRaw(final byte[] data) throws UsbTransportException { final int tr1 = mConnection.bulkTransfer(mBulkOut, data, data.length, TIMEOUT); if (tr1 != data.length) { - throw new UsbTransportException("USB error: failed to transmit data " + tr1); + throw new UsbTransportException("USB error - failed to transmit data " + tr1); } } @@ -274,7 +274,7 @@ public class UsbTransport implements Transport { private void checkDataBlockResponse(byte[] bytes) throws UsbTransportException { final byte status = getStatus(bytes); if (status != 0) { - throw new UsbTransportException("USB-CCID error: status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); + throw new UsbTransportException("USB-CCID error - status " + status + " error code: " + Hex.toHexString(bytes, 8, 1)); } } -- cgit v1.2.3 From 263799ec965669ab027db6b1ad62a26fd6af3bac Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 15 Apr 2016 00:13:33 +0600 Subject: OTG: Fix connection issues --- .../keychain/securitytoken/UsbTransport.java | 7 ++++--- .../ui/base/BaseSecurityTokenNfcActivity.java | 13 +++++++++---- .../keychain/util/UsbConnectionDispatcher.java | 21 ++++----------------- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java index 10043e675..dfe91427e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbTransport.java @@ -66,7 +66,7 @@ public class UsbTransport implements Transport { * @param on true to turn ICC on, false to turn it off * @throws UsbTransportException */ - private void iccPowerSet(boolean on) throws UsbTransportException { + private void setIccPower(boolean on) throws UsbTransportException { final byte[] iccPowerCommand = { (byte) (on ? 0x62 : 0x63), 0x00, 0x00, 0x00, 0x00, @@ -145,7 +145,8 @@ public class UsbTransport implements Transport { */ @Override public boolean isConnected() { - return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice); + return mConnection != null && mUsbManager.getDeviceList().containsValue(mUsbDevice) && + mConnection.getSerial() != null; } /** @@ -188,7 +189,7 @@ public class UsbTransport implements Transport { throw new UsbTransportException("USB error - failed to claim interface"); } - iccPowerSet(true); + setIccPower(true); Log.d(Constants.TAG, "Usb transport connected"); } 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 4aaf2aba0..dbb234977 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 @@ -22,8 +22,11 @@ package org.sufficientlysecure.keychain.ui.base; import android.app.Activity; +import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; @@ -142,12 +145,13 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity securityTokenDiscovered(new NfcTransport(tag)); } - public void usbDeviceDiscovered(final UsbTransport transport) { + public void usbDeviceDiscovered(final UsbDevice usbDevice) { // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; - securityTokenDiscovered(transport); + UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); + securityTokenDiscovered(new UsbTransport(usbDevice, usbManager)); } public void securityTokenDiscovered(final Transport transport) { @@ -401,7 +405,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity protected void handleSmartcard(Transport transport) throws IOException { // Don't reconnect if device was already connected - if (!(mSecurityTokenHelper.isConnected() + if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() + && mSecurityTokenHelper.isConnected() && mSecurityTokenHelper.getTransport().equals(transport))) { mSecurityTokenHelper.setTransport(transport); mSecurityTokenHelper.connectToDevice(); @@ -477,7 +482,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity * Run smartcard routines if last used token is connected and supports * persistent connections */ - protected void checkDeviceConnection() { + public void checkDeviceConnection() { mUsbDispatcher.rescanDevices(); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java index 7055b2633..60fc84dba 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/UsbConnectionDispatcher.java @@ -33,7 +33,6 @@ public class UsbConnectionDispatcher { private Activity mActivity; private OnDiscoveredUsbDeviceListener mListener; - private UsbTransport mLastUsedUsbTransport; private UsbManager mUsbManager; /** * Receives broadcast when a supported USB device get permission. @@ -45,23 +44,12 @@ public class UsbConnectionDispatcher { switch (action) { case UsbEventReceiverActivity.ACTION_USB_PERMISSION: { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); + android.hardware.usb.UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false); if (permission) { Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - - mLastUsedUsbTransport = new UsbTransport(usbDevice, mUsbManager); - mListener.usbDeviceDiscovered(mLastUsedUsbTransport); - } - break; - } - case UsbManager.ACTION_USB_DEVICE_DETACHED: { - UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); - - if (mLastUsedUsbTransport != null && mLastUsedUsbTransport.getUsbDevice().equals(usbDevice)) { - mLastUsedUsbTransport.release(); - mLastUsedUsbTransport = null; + mListener.usbDeviceDiscovered(usbDevice); } break; } @@ -78,7 +66,6 @@ public class UsbConnectionDispatcher { public void onStart() { final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(UsbEventReceiverActivity.ACTION_USB_PERMISSION); - intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED); mActivity.registerReceiver(mUsbReceiver, intentFilter); } @@ -97,7 +84,7 @@ public class UsbConnectionDispatcher { for (UsbDevice device : mUsbManager.getDeviceList().values()) { if (mUsbManager.hasPermission(device)) { if (mListener != null) { - mListener.usbDeviceDiscovered(new UsbTransport(device, mUsbManager)); + mListener.usbDeviceDiscovered(device); } break; } @@ -105,6 +92,6 @@ public class UsbConnectionDispatcher { } public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbTransport usbTransport); + void usbDeviceDiscovered(UsbDevice usbDevice); } } -- cgit v1.2.3 From db57cf3e7ecaae25e9b8c1b468822c28801af129 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 15 Apr 2016 00:37:55 +0600 Subject: OTG: Skip TokenWaitFragmen when otg device is connected --- .../keychain/ui/CreateKeyActivity.java | 19 ++++++++++++------- .../ui/CreateSecurityTokenImportResetFragment.java | 8 ++++---- .../keychain/ui/CreateSecurityTokenWaitFragment.java | 11 +++++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java index 83beccb2a..44b185c52 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateKeyActivity.java @@ -144,8 +144,8 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { @Override protected void doSecurityTokenInBackground() throws IOException { - if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).doNfcInBackground(); + if (mCurrentFragment instanceof SecurityTokenListenerFragment) { + ((SecurityTokenListenerFragment) mCurrentFragment).doSecurityTokenInBackground(); return; } @@ -156,11 +156,16 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { @Override protected void onSecurityTokenPostExecute() { - if (mCurrentFragment instanceof NfcListenerFragment) { - ((NfcListenerFragment) mCurrentFragment).onNfcPostExecute(); + if (mCurrentFragment instanceof SecurityTokenListenerFragment) { + ((SecurityTokenListenerFragment) mCurrentFragment).onSecurityTokenPostExecute(); return; } + // We don't want get back to wait activity mainly because it looks weird with otg token + if (mCurrentFragment instanceof CreateSecurityTokenWaitFragment) { + getSupportFragmentManager().popBackStackImmediate(); + } + if (containsKeys(mScannedFingerprints)) { try { long masterKeyId = KeyFormattingUtils.getKeyIdFromFingerprint(mScannedFingerprints); @@ -255,9 +260,9 @@ public class CreateKeyActivity extends BaseSecurityTokenNfcActivity { } - interface NfcListenerFragment { - void doNfcInBackground() throws IOException; - void onNfcPostExecute(); + interface SecurityTokenListenerFragment { + void doSecurityTokenInBackground() throws IOException; + void onSecurityTokenPostExecute(); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java index 7a27e4acc..a716cb20d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenImportResetFragment.java @@ -42,7 +42,7 @@ import org.sufficientlysecure.keychain.service.ImportKeyringParcel; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; -import org.sufficientlysecure.keychain.ui.CreateKeyActivity.NfcListenerFragment; +import org.sufficientlysecure.keychain.ui.CreateKeyActivity.SecurityTokenListenerFragment; import org.sufficientlysecure.keychain.ui.base.QueueingCryptoOperationFragment; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Preferences; @@ -50,7 +50,7 @@ import org.sufficientlysecure.keychain.util.Preferences; public class CreateSecurityTokenImportResetFragment extends QueueingCryptoOperationFragment - implements NfcListenerFragment { + implements SecurityTokenListenerFragment { private static final int REQUEST_CODE_RESET = 0x00005001; @@ -247,7 +247,7 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void doNfcInBackground() throws IOException { + public void doSecurityTokenInBackground() throws IOException { mTokenFingerprints = mCreateKeyActivity.getSecurityTokenHelper().getFingerprints(); mTokenAid = mCreateKeyActivity.getSecurityTokenHelper().getAid(); @@ -259,7 +259,7 @@ public class CreateSecurityTokenImportResetFragment } @Override - public void onNfcPostExecute() { + public void onSecurityTokenPostExecute() { setData(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java index a3ea38e40..5dc2c478b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CreateSecurityTokenWaitFragment.java @@ -19,6 +19,7 @@ package org.sufficientlysecure.keychain.ui; import android.app.Activity; import android.os.Bundle; +import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -26,6 +27,7 @@ import android.view.ViewGroup; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.CreateKeyActivity.FragAction; +import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenNfcActivity; public class CreateSecurityTokenWaitFragment extends Fragment { @@ -33,6 +35,15 @@ public class CreateSecurityTokenWaitFragment extends Fragment { CreateKeyActivity mCreateKeyActivity; View mBackButton; + @Override + public void onCreate(@Nullable final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (this.getActivity() instanceof BaseSecurityTokenNfcActivity) { + ((BaseSecurityTokenNfcActivity) this.getActivity()).checkDeviceConnection(); + } + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.create_security_token_wait_fragment, container, false); -- cgit v1.2.3 From 28a352a288701a6419602e4c2fff3d5da7172cc0 Mon Sep 17 00:00:00 2001 From: Nikita Mikhailov Date: Fri, 15 Apr 2016 02:02:11 +0600 Subject: OTG: Rename smartcard -> security token --- .../keychain/remote/ApiPendingIntentFactory.java | 6 +++--- .../keychain/service/input/RequiredInputParcel.java | 18 +++++++++--------- .../keychain/ui/SecurityTokenOperationActivity.java | 14 +++++++------- .../keychain/ui/base/BaseSecurityTokenNfcActivity.java | 14 +++++++------- .../keychain/ui/base/CryptoOperationHelper.java | 6 +++--- .../keychain/pgp/PgpKeyOperationTest.java | 10 +++++----- 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java index 03789f118..46bade950 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ApiPendingIntentFactory.java @@ -50,9 +50,9 @@ public class ApiPendingIntentFactory { CryptoInputParcel cryptoInput) { switch (requiredInput.mType) { - case SMARTCARD_MOVE_KEY_TO_CARD: - case SMARTCARD_DECRYPT: - case SMARTCARD_SIGN: { + case SECURITY_TOKEN_MOVE_KEY_TO_CARD: + case SECURITY_TOKEN_DECRYPT: + case SECURITY_TOKEN_SIGN: { return createNfcOperationPendingIntent(data, requiredInput, cryptoInput); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 24aa6f118..efc574124 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -14,8 +14,8 @@ import java.util.Date; public class RequiredInputParcel implements Parcelable { public enum RequiredInputType { - PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SMARTCARD_SIGN, SMARTCARD_DECRYPT, - SMARTCARD_MOVE_KEY_TO_CARD, SMARTCARD_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, + PASSPHRASE, PASSPHRASE_SYMMETRIC, BACKUP_CODE, SECURITY_TOKEN_SIGN, SECURITY_TOKEN_DECRYPT, + SECURITY_TOKEN_MOVE_KEY_TO_CARD, SECURITY_TOKEN_RESET_CARD, ENABLE_ORBOT, UPLOAD_FAIL_RETRY, } public Date mSignatureTime; @@ -92,19 +92,19 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createNfcSignOperation( long masterKeyId, long subKeyId, byte[] inputHash, int signAlgo, Date signatureTime) { - return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN, new byte[][] { inputHash }, new int[] { signAlgo }, signatureTime, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcDecryptOperation( long masterKeyId, long subKeyId, byte[] encryptedSessionKey) { - return new RequiredInputParcel(RequiredInputType.SMARTCARD_DECRYPT, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_DECRYPT, new byte[][] { encryptedSessionKey }, null, null, masterKeyId, subKeyId); } public static RequiredInputParcel createNfcReset() { - return new RequiredInputParcel(RequiredInputType.SMARTCARD_RESET_CARD, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD, null, null, null, null, null); } @@ -209,7 +209,7 @@ public class RequiredInputParcel implements Parcelable { signAlgos[i] = mSignAlgos.get(i); } - return new RequiredInputParcel(RequiredInputType.SMARTCARD_SIGN, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_SIGN, inputHashes, signAlgos, mSignatureTime, mMasterKeyId, mSubKeyId); } @@ -222,7 +222,7 @@ public class RequiredInputParcel implements Parcelable { if (!mSignatureTime.equals(input.mSignatureTime)) { throw new AssertionError("input times must match, this is a programming error!"); } - if (input.mType != RequiredInputType.SMARTCARD_SIGN) { + if (input.mType != RequiredInputType.SECURITY_TOKEN_SIGN) { throw new AssertionError("operation types must match, this is a progrmming error!"); } @@ -264,7 +264,7 @@ public class RequiredInputParcel implements Parcelable { ByteBuffer buf = ByteBuffer.wrap(mSubkeysToExport.get(0)); // We need to pass in a subkey here... - return new RequiredInputParcel(RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD, + return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD, inputData, null, null, mMasterKeyId, buf.getLong()); } @@ -287,7 +287,7 @@ public class RequiredInputParcel implements Parcelable { if (!mMasterKeyId.equals(input.mMasterKeyId)) { throw new AssertionError("Master keys must match, this is a programming error!"); } - if (input.mType != RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD) { + if (input.mType != RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD) { throw new AssertionError("Operation types must match, this is a programming error!"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index ddc1a7ca0..39cd74fd2 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -136,8 +136,8 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity private void obtainPassphraseIfRequired() { // obtain passphrase for this subkey - if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_MOVE_KEY_TO_CARD - && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SMARTCARD_RESET_CARD) { + if (mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD + && mRequiredInput.mType != RequiredInputParcel.RequiredInputType.SECURITY_TOKEN_RESET_CARD) { obtainSecurityTokenPin(mRequiredInput); checkPinAvailability(); } else { @@ -182,7 +182,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity protected void doSecurityTokenInBackground() throws IOException { switch (mRequiredInput.mType) { - case SMARTCARD_DECRYPT: { + case SECURITY_TOKEN_DECRYPT: { for (int i = 0; i < mRequiredInput.mInputData.length; i++) { byte[] encryptedSessionKey = mRequiredInput.mInputData[i]; byte[] decryptedSessionKey = mSecurityTokenHelper.decryptSessionKey(encryptedSessionKey); @@ -190,7 +190,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case SMARTCARD_SIGN: { + case SECURITY_TOKEN_SIGN: { mInputParcel.addSignatureTime(mRequiredInput.mSignatureTime); for (int i = 0; i < mRequiredInput.mInputData.length; i++) { @@ -201,7 +201,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } break; } - case SMARTCARD_MOVE_KEY_TO_CARD: { + case SECURITY_TOKEN_MOVE_KEY_TO_CARD: { // TODO: assume PIN and Admin PIN to be default for this operation mSecurityTokenHelper.setPin(new Passphrase("123456")); mSecurityTokenHelper.setAdminPin(new Passphrase("12345678")); @@ -247,7 +247,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity break; } - case SMARTCARD_RESET_CARD: { + case SECURITY_TOKEN_RESET_CARD: { mSecurityTokenHelper.resetAndWipeToken(); break; @@ -277,7 +277,7 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity protected Void doInBackground(Void... params) { // check all 200ms if Security Token has been taken away while (true) { - if (isSmartcardConnected()) { + if (isSecurityTokenConnected()) { try { Thread.sleep(200); } catch (InterruptedException ignored) { 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 dbb234977..f4c0a9365 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 @@ -155,7 +155,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } public void securityTokenDiscovered(final Transport transport) { - // Actual Smartcard operations are executed in doInBackground to not block the UI thread + // Actual Security Token operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) return; new AsyncTask() { @@ -168,7 +168,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity @Override protected IOException doInBackground(Void... params) { try { - handleSmartcard(transport); + handleSecurityToken(transport); } catch (IOException e) { return e; } @@ -181,7 +181,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity super.onPostExecute(exception); if (exception != null) { - handleSmartcardError(exception); + handleSecurityTokenError(exception); return; } @@ -237,7 +237,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity mTagDispatcher.interceptIntent(intent); } - private void handleSmartcardError(IOException e) { + private void handleSecurityTokenError(IOException e) { if (e instanceof TagLostException) { onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); @@ -403,7 +403,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } } - protected void handleSmartcard(Transport transport) throws IOException { + protected void handleSecurityToken(Transport transport) throws IOException { // Don't reconnect if device was already connected if (!(mSecurityTokenHelper.isPersistentConnectionAllowed() && mSecurityTokenHelper.isConnected() @@ -414,7 +414,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity doSecurityTokenInBackground(); } - public boolean isSmartcardConnected() { + public boolean isSecurityTokenConnected() { return mSecurityTokenHelper.isConnected(); } @@ -479,7 +479,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity } /** - * Run smartcard routines if last used token is connected and supports + * Run Security Token routines if last used token is connected and supports * persistent connections */ public void checkDeviceConnection() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java index 29200ac2c..ad15c8f68 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/CryptoOperationHelper.java @@ -130,9 +130,9 @@ public class CryptoOperationHelper