diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 7b3844e1d..da5e7afa1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -143,6 +143,7 @@ public final class Constants { public static final String EXPERIMENTAL_ENABLE_WORD_CONFIRM = "experimentalEnableWordConfirm"; public static final String EXPERIMENTAL_ENABLE_LINKED_IDENTITIES = "experimentalEnableLinkedIdentities"; public static final String EXPERIMENTAL_ENABLE_KEYBASE = "experimentalEnableKeybase"; + public static final String EXPERIMENTAL_USB_ALLOW_UNTESTED = "experimentalUsbAllowUntested"; public static final String EXPERIMENTAL_SMARTPGP_VERIFY_AUTHORITY = "smartpgp_authorities_pref"; public static final String EXPERIMENTAL_SMARTPGP_AUTHORITIES = "smartpgp_authorities"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java index 1556cb259..0c6631561 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenConnection.java @@ -189,10 +189,11 @@ public class SecurityTokenConnection { @VisibleForTesting void connectToDevice(Context context) throws IOException { // Connect on transport layer - mCardCapabilities = new CardCapabilities(); - mTransport.connect(); + // dummy instance for initial communicate() calls + mCardCapabilities = new CardCapabilities(); + determineTokenType(); CommandApdu select = commandFactory.createSelectFileOpenPgpCommand(); @@ -546,7 +547,7 @@ public class SecurityTokenConnection { } // Now we're ready to communicate with the token. - byte[] keyBytes = null; + byte[] keyBytes; try { secretKey.unlock(passphrase); @@ -1015,10 +1016,9 @@ public class SecurityTokenConnection { TransportType transportType = mTransport.getTransportType(); - SecurityTokenInfo info = SecurityTokenInfo - .create(transportType, tokenType, fingerprints, aid, userId, url, pwInfo[4], pwInfo[6], hasLifeCycleManagement); - - return info; + return SecurityTokenInfo + .create(transportType, tokenType, fingerprints, aid, userId, url, pwInfo[4], pwInfo[6], + hasLifeCycleManagement); } public static double parseOpenPgpVersion(final byte[] aid) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java index aa85ad3ae..a54eabb6a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenInfo.java @@ -3,8 +3,10 @@ package org.sufficientlysecure.keychain.securitytoken; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -100,7 +102,7 @@ public abstract class SecurityTokenInfo implements Parcelable { NITROKEY_START_1_25_AND_NEWER, GNUK_OLD, GNUK_1_25_AND_NEWER, LEDGER_NANO_S, UNKNOWN } - private static final HashSet SUPPORTED_USB_TOKENS = new HashSet<>(Arrays.asList( + public static final Set SUPPORTED_USB_TOKENS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( TokenType.YUBIKEY_NEO, TokenType.YUBIKEY_4, TokenType.NITROKEY_PRO, @@ -109,23 +111,16 @@ public abstract class SecurityTokenInfo implements Parcelable { TokenType.NITROKEY_START_1_25_AND_NEWER, TokenType.GNUK_OLD, TokenType.GNUK_1_25_AND_NEWER - )); + ))); - private static final HashSet SUPPORTED_USB_SETUP = new HashSet<>(Arrays.asList( + private static final Set SUPPORTED_USB_SETUP = Collections.unmodifiableSet(new HashSet<>(Arrays.asList( TokenType.YUBIKEY_NEO, TokenType.YUBIKEY_4, TokenType.NITROKEY_PRO, TokenType.NITROKEY_STORAGE, TokenType.NITROKEY_START_1_25_AND_NEWER, TokenType.GNUK_1_25_AND_NEWER - )); - - public boolean isSecurityTokenSupported() { - boolean isKnownSupported = SUPPORTED_USB_TOKENS.contains(getTokenType()); - boolean isNfcTransport = getTransportType() == TransportType.NFC; - - return isKnownSupported || isNfcTransport; - } + ))); public boolean isPutKeySupported() { boolean isKnownSupported = SUPPORTED_USB_SETUP.contains(getTokenType()); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java index 9db35c790..e43cf03ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/UsbConnectionDispatcher.java @@ -26,6 +26,7 @@ import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport; import org.sufficientlysecure.keychain.ui.UsbEventReceiverActivity; import org.sufficientlysecure.keychain.util.Log; @@ -50,7 +51,7 @@ public class UsbConnectionDispatcher { false); if (permission) { Log.d(Constants.TAG, "Got permission for " + usbDevice.getDeviceName()); - mListener.usbDeviceDiscovered(usbDevice); + sendUsbTransportDiscovered(usbDevice); } break; } @@ -82,17 +83,24 @@ public class UsbConnectionDispatcher { // Note: we don't check devices VID/PID because // we check for permission instead. // We should have permission only for matching devices - for (UsbDevice device : mUsbManager.getDeviceList().values()) { - if (mUsbManager.hasPermission(device)) { - if (mListener != null) { - mListener.usbDeviceDiscovered(device); - } + for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) { + if (mUsbManager.hasPermission(usbDevice)) { + sendUsbTransportDiscovered(usbDevice); break; } } } + private void sendUsbTransportDiscovered(UsbDevice usbDevice) { + if (mListener == null) { + return; + } + + UsbTransport usbTransport = UsbTransport.createUsbTransport(mActivity.getBaseContext(), usbDevice); + mListener.usbTransportDiscovered(usbTransport); + } + public interface OnDiscoveredUsbDeviceListener { - void usbDeviceDiscovered(UsbDevice usbDevice); + void usbTransportDiscovered(UsbTransport usbTransport); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UnsupportedUsbTokenException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UnsupportedUsbTokenException.java new file mode 100644 index 000000000..19f34c81b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UnsupportedUsbTokenException.java @@ -0,0 +1,8 @@ +package org.sufficientlysecure.keychain.securitytoken.usb; + + +public class UnsupportedUsbTokenException extends UsbTransportException { + UnsupportedUsbTokenException() { + super("This USB token is not supported!"); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java index 3f363923e..152b3a540 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransport.java @@ -20,6 +20,7 @@ package org.sufficientlysecure.keychain.securitytoken.usb; import java.io.IOException; +import android.content.Context; import android.hardware.usb.UsbConstants; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbDeviceConnection; @@ -39,6 +40,8 @@ import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType; import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.util.Log; +import org.sufficientlysecure.keychain.util.Preferences; + /** * Based on USB CCID Specification rev. 1.1 @@ -72,10 +75,19 @@ public class UsbTransport implements Transport { private UsbDeviceConnection usbConnection; private UsbInterface usbInterface; private CcidTransportProtocol ccidTransportProtocol; + private boolean allowUntestedUsbTokens; - public UsbTransport(UsbDevice usbDevice, UsbManager usbManager) { + public static UsbTransport createUsbTransport(Context context, UsbDevice usbDevice) { + UsbManager usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE); + boolean allowUntestedUsbTokens = Preferences.getPreferences(context).getExperimentalUsbAllowUntested(); + + return new UsbTransport(usbDevice, usbManager, allowUntestedUsbTokens); + } + + private UsbTransport(UsbDevice usbDevice, UsbManager usbManager, boolean allowUntestedUsbTokens) { this.usbDevice = usbDevice; this.usbManager = usbManager; + this.allowUntestedUsbTokens = allowUntestedUsbTokens; } @Override @@ -134,6 +146,13 @@ public class UsbTransport implements Transport { throw new UsbTransportException("USB error: failed to connect to device"); } + boolean tokenTypeSupported = SecurityTokenInfo.SUPPORTED_USB_TOKENS.contains(getTokenTypeIfAvailable()); + if (!allowUntestedUsbTokens && !tokenTypeSupported) { + usbConnection.close(); + usbConnection = null; + throw new UnsupportedUsbTokenException(); + } + if (!usbConnection.claimInterface(usbInterface, true)) { throw new UsbTransportException("USB error: failed to claim interface"); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java index 0818f1570..394b31318 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenActivity.java @@ -25,11 +25,8 @@ package org.sufficientlysecure.keychain.ui.base; import java.io.IOException; 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; @@ -48,6 +45,7 @@ import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo; import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType; import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.securitytoken.UsbConnectionDispatcher; +import org.sufficientlysecure.keychain.securitytoken.usb.UnsupportedUsbTokenException; import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport; import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; import org.sufficientlysecure.keychain.service.PassphraseCacheService; @@ -62,6 +60,7 @@ import org.sufficientlysecure.keychain.ui.util.Notify.Style; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; + public abstract class BaseSecurityTokenActivity extends BaseActivity implements OnDiscoveredTagListener, UsbConnectionDispatcher.OnDiscoveredUsbDeviceListener { public static final int REQUEST_CODE_PIN = 1; @@ -114,6 +113,7 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity onSecurityTokenError(error); } + @Override public void tagDiscovered(Tag tag) { // Actual NFC operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) { @@ -124,15 +124,13 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity securityTokenDiscovered(nfcTransport); } - public void usbDeviceDiscovered(UsbDevice usbDevice) { + @Override + public void usbTransportDiscovered(UsbTransport usbTransport) { // Actual USB operations are executed in doInBackground to not block the UI thread if (!mTagHandlingEnabled) { return; } - UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - - UsbTransport usbTransport = new UsbTransport(usbDevice, usbManager); securityTokenDiscovered(usbTransport); } @@ -235,6 +233,11 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity private void handleSecurityTokenError(SecurityTokenConnection stConnection, IOException e) { Log.d(Constants.TAG, "Exception in handleSecurityTokenError", e); + if (e instanceof UnsupportedUsbTokenException) { + onSecurityTokenError(getString(R.string.security_token_not_supported)); + return; + } + if (e instanceof TagLostException) { onSecurityTokenError(getString(R.string.security_token_error_tag_lost)); return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 263e95029..09539ab98 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -402,6 +402,10 @@ public class Preferences { return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_ENABLE_KEYBASE, false); } + public boolean getExperimentalUsbAllowUntested() { + return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_USB_ALLOW_UNTESTED, false); + } + public boolean getExperimentalSmartPGPAuthoritiesEnable() { return mSharedPreferences.getBoolean(Pref.EXPERIMENTAL_SMARTPGP_VERIFY_AUTHORITY, false); } diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 4e124c3dd..062f4d968 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1597,6 +1597,7 @@ "Security Token does not support the required communication standard (ISO-DEP, ISO 14443-4)" "Communication error. Most probably, the Security Token has been taken off too early." "Try again" + "This Security Token is not yet supported by OpenKeychain" Delete original file "Filenames are encrypted." @@ -1982,5 +1983,7 @@ "Where is my NFC reader?" + If enabled, USB Smartcard readers can be used that have not been properly tested. + Allow untested USB Devices diff --git a/OpenKeychain/src/main/res/xml/experimental_preferences.xml b/OpenKeychain/src/main/res/xml/experimental_preferences.xml index 8544487d2..1e922063e 100644 --- a/OpenKeychain/src/main/res/xml/experimental_preferences.xml +++ b/OpenKeychain/src/main/res/xml/experimental_preferences.xml @@ -35,6 +35,14 @@ android:persistent="true" android:title="@string/label_theme" /> + + +