Merge pull request #2195 from open-keychain/ccid-check
Change USB filtering
This commit is contained in:
@@ -18,10 +18,13 @@
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import android.nfc.Tag;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseSecurityTokenActivity;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -35,6 +38,14 @@ public class NfcTransport implements Transport {
|
||||
private final Tag mTag;
|
||||
private IsoCard mIsoCard;
|
||||
|
||||
public static class IsoDepNotSupportedException extends IOException {
|
||||
|
||||
IsoDepNotSupportedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public NfcTransport(Tag tag) {
|
||||
this.mTag = tag;
|
||||
}
|
||||
@@ -96,13 +107,25 @@ public class NfcTransport implements Transport {
|
||||
public void connect() throws IOException {
|
||||
mIsoCard = AndroidCard.get(mTag);
|
||||
if (mIsoCard == null) {
|
||||
throw new BaseSecurityTokenActivity.IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)");
|
||||
throw new IsoDepNotSupportedException("Tag does not support ISO-DEP (ISO 14443-4)");
|
||||
}
|
||||
|
||||
mIsoCard.setTimeout(TIMEOUT);
|
||||
mIsoCard.connect();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportType getTransportType() {
|
||||
return TransportType.NFC;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TokenType getTokenTypeIfAvailable() {
|
||||
// Sadly, the NFC transport has no direct information about the token type.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (this == o) return true;
|
||||
|
||||
@@ -48,6 +48,8 @@ import javax.crypto.Cipher;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
@@ -74,8 +76,7 @@ import java.util.List;
|
||||
public class SecurityTokenConnection {
|
||||
private static final int APDU_SW1_RESPONSE_AVAILABLE = 0x61;
|
||||
|
||||
// Fidesmo constants
|
||||
private static final String FIDESMO_APPS_AID_PREFIX = "A000000617";
|
||||
private static final String AID_PREFIX_FIDESMO = "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};
|
||||
|
||||
@@ -89,6 +90,7 @@ public class SecurityTokenConnection {
|
||||
private final Passphrase mPin;
|
||||
private final OpenPgpCommandApduFactory commandFactory;
|
||||
|
||||
private TokenType tokenType;
|
||||
private CardCapabilities mCardCapabilities;
|
||||
private OpenPgpCapabilities mOpenPgpCapabilities;
|
||||
private SecureMessaging mSecureMessaging;
|
||||
@@ -182,8 +184,8 @@ public class SecurityTokenConnection {
|
||||
|
||||
mTransport.connect();
|
||||
|
||||
// Connect on smartcard layer
|
||||
// Command APDU (page 51) for SELECT FILE command (page 29)
|
||||
determineTokenType();
|
||||
|
||||
CommandApdu select = commandFactory.createSelectFileOpenPgpCommand();
|
||||
ResponseApdu response = communicate(select); // activate connection
|
||||
|
||||
@@ -208,6 +210,31 @@ public class SecurityTokenConnection {
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void determineTokenType() throws IOException {
|
||||
tokenType = mTransport.getTokenTypeIfAvailable();
|
||||
if (tokenType != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CommandApdu selectFidesmoApdu = commandFactory.createSelectFileCommand(AID_PREFIX_FIDESMO);
|
||||
if (communicate(selectFidesmoApdu).isSuccess()) {
|
||||
tokenType = TokenType.FIDESMO;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We could determine if this is a yubikey here. The info isn't used at the moment, so we save the roundtrip
|
||||
// AID from https://github.com/Yubico/ykneo-oath/blob/master/build.xml#L16
|
||||
CommandApdu selectYubicoApdu = commandFactory.createSelectFileCommand("A000000527200101");
|
||||
if (communicate(selectYubicoApdu).isSuccess()) {
|
||||
tokenType = TokenType.YUBIKEY_UNKNOWN;
|
||||
return;
|
||||
}
|
||||
*/
|
||||
|
||||
tokenType = TokenType.UNKNOWN;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void setConnectionCapabilities(OpenPgpCapabilities openPgpCapabilities) throws IOException {
|
||||
this.mOpenPgpCapabilities = openPgpCapabilities;
|
||||
@@ -795,20 +822,6 @@ public class SecurityTokenConnection {
|
||||
return lastResponse;
|
||||
}
|
||||
|
||||
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
|
||||
CommandApdu apdu = commandFactory.createSelectFileCommand(FIDESMO_APPS_AID_PREFIX);
|
||||
return communicate(apdu).isSuccess();
|
||||
} 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.
|
||||
@@ -917,6 +930,10 @@ public class SecurityTokenConnection {
|
||||
return mTransport.isConnected();
|
||||
}
|
||||
|
||||
public TokenType getTokenType() {
|
||||
return tokenType;
|
||||
}
|
||||
|
||||
public void clearSecureMessaging() {
|
||||
if (mSecureMessaging != null) {
|
||||
mSecureMessaging.clearSession();
|
||||
@@ -929,10 +946,6 @@ public class SecurityTokenConnection {
|
||||
mSecureMessaging = sm;
|
||||
}
|
||||
|
||||
OpenPgpCapabilities getOpenPgpCapabilities() {
|
||||
return mOpenPgpCapabilities;
|
||||
}
|
||||
|
||||
public SecurityTokenInfo getTokenInfo() throws IOException {
|
||||
byte[] rawFingerprints = getFingerprints();
|
||||
|
||||
@@ -948,7 +961,16 @@ public class SecurityTokenConnection {
|
||||
String url = getUrl();
|
||||
byte[] pwInfo = getPwStatusBytes();
|
||||
|
||||
return SecurityTokenInfo.create(fingerprints, aid, userId, url, pwInfo[4], pwInfo[6]);
|
||||
TransportType transportType = mTransport.getTransportType();
|
||||
|
||||
SecurityTokenInfo info = SecurityTokenInfo
|
||||
.create(transportType, tokenType, fingerprints, aid, userId, url, pwInfo[4], pwInfo[6]);
|
||||
|
||||
if (! info.isSecurityTokenSupported()) {
|
||||
throw new UnsupportedSecurityTokenException();
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public static double parseOpenPgpVersion(final byte[] aid) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import android.os.Parcelable;
|
||||
@@ -18,6 +19,9 @@ import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
|
||||
public abstract class SecurityTokenInfo implements Parcelable {
|
||||
private static final byte[] EMPTY_ARRAY = new byte[20];
|
||||
|
||||
public abstract TransportType getTransportType();
|
||||
public abstract TokenType getTokenType();
|
||||
|
||||
public abstract List<byte[]> getFingerprints();
|
||||
@Nullable
|
||||
public abstract byte[] getAid();
|
||||
@@ -32,7 +36,8 @@ public abstract class SecurityTokenInfo implements Parcelable {
|
||||
return getFingerprints().isEmpty();
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo create(byte[][] fingerprints, byte[] aid, String userId, String url,
|
||||
public static SecurityTokenInfo create(TransportType transportType, TokenType tokenType, byte[][] fingerprints,
|
||||
byte[] aid, String userId, String url,
|
||||
int verifyRetries, int verifyAdminRetries) {
|
||||
ArrayList<byte[]> fingerprintList = new ArrayList<>(fingerprints.length);
|
||||
for (byte[] fingerprint : fingerprints) {
|
||||
@@ -40,14 +45,15 @@ public abstract class SecurityTokenInfo implements Parcelable {
|
||||
fingerprintList.add(fingerprint);
|
||||
}
|
||||
}
|
||||
return new AutoValue_SecurityTokenInfo(fingerprintList, aid, userId, url, verifyRetries, verifyAdminRetries);
|
||||
return new AutoValue_SecurityTokenInfo(
|
||||
transportType, tokenType, fingerprintList, aid, userId, url, verifyRetries, verifyAdminRetries);
|
||||
}
|
||||
|
||||
public static SecurityTokenInfo newInstanceDebugKeyserver() {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("1efdb4845ca242ca6977fddb1f788094fd3b430a") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", null, 3, 3);
|
||||
}
|
||||
@@ -56,7 +62,7 @@ public abstract class SecurityTokenInfo implements Parcelable {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 3, 3);
|
||||
}
|
||||
@@ -65,7 +71,7 @@ public abstract class SecurityTokenInfo implements Parcelable {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 0, 3);
|
||||
}
|
||||
@@ -74,9 +80,43 @@ public abstract class SecurityTokenInfo implements Parcelable {
|
||||
if (!BuildConfig.DEBUG) {
|
||||
throw new UnsupportedOperationException("This operation is only available in debug builds!");
|
||||
}
|
||||
return SecurityTokenInfo.create(
|
||||
return SecurityTokenInfo.create(TransportType.NFC, TokenType.UNKNOWN,
|
||||
new byte[][] { KeyFormattingUtils.convertFingerprintHexFingerprint("4700BA1AC417ABEF3CC7765AD686905837779C3E") },
|
||||
Hex.decode("010203040506"), "yubinu2@mugenguild.com", "http://valodim.stratum0.net/mryubinu2.asc", 0, 0);
|
||||
}
|
||||
|
||||
public enum TransportType {
|
||||
NFC, USB
|
||||
}
|
||||
|
||||
public enum TokenType {
|
||||
YUBIKEY_NEO, YUBIKEY_4, FIDESMO, NITROKEY_PRO, NITROKEY_STORAGE, NITROKEY_START, GNUK, LEDGER_NANO_S, UNKNOWN
|
||||
}
|
||||
|
||||
private static final HashSet<TokenType> SUPPORTED_USB_TOKENS = new HashSet<>(Arrays.asList(
|
||||
TokenType.YUBIKEY_NEO,
|
||||
TokenType.YUBIKEY_4,
|
||||
TokenType.NITROKEY_PRO,
|
||||
TokenType.NITROKEY_STORAGE
|
||||
));
|
||||
|
||||
private static final HashSet<TokenType> SUPPORTED_USB_PUT_KEY = new HashSet<>(Arrays.asList(
|
||||
TokenType.YUBIKEY_NEO,
|
||||
TokenType.YUBIKEY_4 // Not clear, will be tested: https://github.com/open-keychain/open-keychain/issues/2069
|
||||
));
|
||||
|
||||
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_PUT_KEY.contains(getTokenType());
|
||||
boolean isNfcTransport = getTransportType() == TransportType.NFC;
|
||||
|
||||
return isKnownSupported || isNfcTransport;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,8 +17,14 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* Abstraction for transmitting APDU commands
|
||||
*/
|
||||
@@ -55,4 +61,8 @@ public interface Transport {
|
||||
* @throws IOException
|
||||
*/
|
||||
void connect() throws IOException;
|
||||
|
||||
TransportType getTransportType();
|
||||
@Nullable
|
||||
TokenType getTokenTypeIfAvailable();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.sufficientlysecure.keychain.securitytoken;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class UnsupportedSecurityTokenException extends IOException {
|
||||
|
||||
UnsupportedSecurityTokenException() {
|
||||
super();
|
||||
}
|
||||
|
||||
UnsupportedSecurityTokenException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,8 @@ import android.support.annotation.Nullable;
|
||||
import android.util.Pair;
|
||||
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
||||
import org.sufficientlysecure.keychain.securitytoken.CommandApdu;
|
||||
import org.sufficientlysecure.keychain.securitytoken.ResponseApdu;
|
||||
@@ -54,6 +56,25 @@ public class UsbTransport implements Transport {
|
||||
private static final int MASK_SHORT_APDU = 0x20000;
|
||||
private static final int MASK_EXTENDED_APDU = 0x40000;
|
||||
|
||||
// https://github.com/Yubico/yubikey-personalization/blob/master/ykcore/ykdef.h
|
||||
private static final int VENDOR_YUBICO = 4176;
|
||||
private static final int PRODUCT_YUBIKEY_NEO_OTP_CCID = 273;
|
||||
private static final int PRODUCT_YUBIKEY_NEO_CCID = 274;
|
||||
private static final int PRODUCT_YUBIKEY_NEO_U2F_CCID = 277;
|
||||
private static final int PRODUCT_YUBIKEY_NEO_OTP_U2F_CCID = 278;
|
||||
private static final int PRODUCT_YUBIKEY_4_CCID = 1028;
|
||||
private static final int PRODUCT_YUBIKEY_4_OTP_CCID = 1029;
|
||||
private static final int PRODUCT_YUBIKEY_4_U2F_CCID = 1030;
|
||||
private static final int PRODUCT_YUBIKEY_4_OTP_U2F_CCID = 1031;
|
||||
|
||||
// https://www.nitrokey.com/de/documentation/installation#p:nitrokey-pro&os:linux
|
||||
private static final int VENDOR_NITROKEY = 8352;
|
||||
private static final int PRODUCT_NITROKEY_PRO = 16648;
|
||||
private static final int PRODUCT_NITROKEY_START = 16913;
|
||||
private static final int PRODUCT_NITROKEY_STORAGE = 16649;
|
||||
|
||||
private static final int VENDOR_FSIJ = 9035;
|
||||
private static final int VENDOR_LEDGER = 11415;
|
||||
|
||||
private final UsbDevice usbDevice;
|
||||
private final UsbManager usbManager;
|
||||
@@ -80,6 +101,7 @@ public class UsbTransport implements Transport {
|
||||
|
||||
/**
|
||||
* Check if device is was connected to and still is connected
|
||||
*
|
||||
* @return true if device is connected
|
||||
*/
|
||||
@Override
|
||||
@@ -91,6 +113,7 @@ public class UsbTransport implements Transport {
|
||||
/**
|
||||
* 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
|
||||
@@ -105,8 +128,7 @@ public class UsbTransport implements Transport {
|
||||
public void connect() throws IOException {
|
||||
usbInterface = getSmartCardInterface(usbDevice);
|
||||
if (usbInterface == 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: CCID mode must be enabled (no class 11 interface)");
|
||||
}
|
||||
|
||||
final Pair<UsbEndpoint, UsbEndpoint> ioEndpoints = getIoEndpoints(usbInterface);
|
||||
@@ -114,16 +136,16 @@ public class UsbTransport implements Transport {
|
||||
UsbEndpoint usbBulkOut = ioEndpoints.second;
|
||||
|
||||
if (usbBulkIn == null || usbBulkOut == null) {
|
||||
throw new UsbTransportException("USB error - invalid class 11 interface");
|
||||
throw new UsbTransportException("USB error: invalid class 11 interface");
|
||||
}
|
||||
|
||||
usbConnection = usbManager.openDevice(usbDevice);
|
||||
if (usbConnection == null) {
|
||||
throw new UsbTransportException("USB error - failed to connect to device");
|
||||
throw new UsbTransportException("USB error: failed to connect to device");
|
||||
}
|
||||
|
||||
if (!usbConnection.claimInterface(usbInterface, true)) {
|
||||
throw new UsbTransportException("USB error - failed to claim interface");
|
||||
throw new UsbTransportException("USB error: failed to claim interface");
|
||||
}
|
||||
|
||||
byte[] rawDescriptors = usbConnection.getRawDescriptors();
|
||||
@@ -179,6 +201,7 @@ public class UsbTransport implements Transport {
|
||||
|
||||
/**
|
||||
* Transmit and receive data
|
||||
*
|
||||
* @param data data to transmit
|
||||
* @return received data
|
||||
*/
|
||||
@@ -202,6 +225,53 @@ public class UsbTransport implements Transport {
|
||||
return usbDevice != null ? usbDevice.hashCode() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportType getTransportType() {
|
||||
return TransportType.USB;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TokenType getTokenTypeIfAvailable() {
|
||||
switch (usbDevice.getVendorId()) {
|
||||
case VENDOR_YUBICO: {
|
||||
switch (usbDevice.getProductId()) {
|
||||
case PRODUCT_YUBIKEY_NEO_OTP_CCID:
|
||||
case PRODUCT_YUBIKEY_NEO_CCID:
|
||||
case PRODUCT_YUBIKEY_NEO_U2F_CCID:
|
||||
case PRODUCT_YUBIKEY_NEO_OTP_U2F_CCID:
|
||||
return TokenType.YUBIKEY_NEO;
|
||||
case PRODUCT_YUBIKEY_4_CCID:
|
||||
case PRODUCT_YUBIKEY_4_OTP_CCID:
|
||||
case PRODUCT_YUBIKEY_4_U2F_CCID:
|
||||
case PRODUCT_YUBIKEY_4_OTP_U2F_CCID:
|
||||
return TokenType.YUBIKEY_4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VENDOR_NITROKEY: {
|
||||
switch (usbDevice.getProductId()) {
|
||||
case PRODUCT_NITROKEY_PRO:
|
||||
return TokenType.NITROKEY_PRO;
|
||||
case PRODUCT_NITROKEY_START:
|
||||
return TokenType.NITROKEY_START;
|
||||
case PRODUCT_NITROKEY_STORAGE:
|
||||
return TokenType.NITROKEY_STORAGE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VENDOR_FSIJ: {
|
||||
return TokenType.GNUK;
|
||||
}
|
||||
case VENDOR_LEDGER: {
|
||||
return TokenType.LEDGER_NANO_S;
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "Unknown USB token. Vendor ID: " + usbDevice.getVendorId() + ", Product ID: " +
|
||||
usbDevice.getProductId());
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get first class 11 (Chip/Smartcard) interface of the device
|
||||
|
||||
@@ -45,9 +45,12 @@ import org.sufficientlysecure.keychain.securitytoken.CardException;
|
||||
import org.sufficientlysecure.keychain.securitytoken.NfcTransport;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenConnection;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
||||
import org.sufficientlysecure.keychain.securitytoken.UnsupportedSecurityTokenException;
|
||||
import org.sufficientlysecure.keychain.securitytoken.UsbConnectionDispatcher;
|
||||
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransport;
|
||||
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException;
|
||||
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
@@ -136,8 +139,9 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
|
||||
public void securityTokenDiscovered(final Transport transport) {
|
||||
// Actual Security Token operations are executed in doInBackground to not block the UI thread
|
||||
if (!mTagHandlingEnabled)
|
||||
if (!mTagHandlingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SecurityTokenConnection stConnection =
|
||||
SecurityTokenConnection.getInstanceForTransport(transport, mCachedPin);
|
||||
@@ -230,151 +234,158 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
}
|
||||
|
||||
private void handleSecurityTokenError(SecurityTokenConnection stConnection, IOException e) {
|
||||
Log.d(Constants.TAG, "Exception in handleSecurityTokenError", e);
|
||||
|
||||
if (e instanceof TagLostException) {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_tag_lost));
|
||||
return;
|
||||
}
|
||||
|
||||
if (e instanceof IsoDepNotSupportedException) {
|
||||
if (e instanceof NfcTransport.IsoDepNotSupportedException) {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_iso_dep_not_supported));
|
||||
return;
|
||||
}
|
||||
|
||||
short status;
|
||||
if (e instanceof CardException) {
|
||||
status = ((CardException) e).getResponseCode();
|
||||
} else {
|
||||
status = -1;
|
||||
}
|
||||
|
||||
// Wrong PIN, a status of 63CX indicates X attempts remaining.
|
||||
// NOTE: Used in ykneo-openpgp version < 1.0.10, changed to 0x6982 in 1.0.11
|
||||
// https://github.com/Yubico/ykneo-openpgp/commit/90c2b91e86fb0e43ee234dd258834e75e3416410
|
||||
if ((status & (short) 0xFFF0) == 0x63C0) {
|
||||
int tries = status & 0x000F;
|
||||
|
||||
SecurityTokenInfo tokeninfo = null;
|
||||
try {
|
||||
tokeninfo = stConnection.getTokenInfo();
|
||||
} catch (IOException e2) {
|
||||
// don't care
|
||||
}
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(
|
||||
getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries), tokeninfo);
|
||||
if (e instanceof UsbTransportException) {
|
||||
onSecurityTokenError(getString(R.string.security_token_error, e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d(Constants.TAG, "security token exception", e);
|
||||
if (e instanceof UnsupportedSecurityTokenException) {
|
||||
onSecurityTokenError(getString(R.string.security_token_not_supported));
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, all status codes are fixed values.
|
||||
switch (status) {
|
||||
if (e instanceof CardException) {
|
||||
short status = ((CardException) e).getResponseCode();
|
||||
|
||||
// These error conditions are likely to be experienced by an end user.
|
||||
// Wrong PIN, a status of 63CX indicates X attempts remaining.
|
||||
// NOTE: Used in ykneo-openpgp version < 1.0.10, changed to 0x6982 in 1.0.11
|
||||
// https://github.com/Yubico/ykneo-openpgp/commit/90c2b91e86fb0e43ee234dd258834e75e3416410
|
||||
if ((status & (short) 0xFFF0) == 0x63C0) {
|
||||
int tries = status & 0x000F;
|
||||
|
||||
/* OpenPGP Card Spec: Security status not satisfied, PW wrong,
|
||||
PW not checked (command not allowed), Secure messaging incorrect (checksum and/or cryptogram) */
|
||||
// NOTE: Used in ykneo-openpgp >= 1.0.11 for wrong PIN
|
||||
case 0x6982: {
|
||||
SecurityTokenInfo tokeninfo = null;
|
||||
try {
|
||||
tokeninfo = stConnection.getTokenInfo();
|
||||
} catch (IOException e2) {
|
||||
// don't care
|
||||
}
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(
|
||||
getResources().getQuantityString(R.plurals.security_token_error_pin, tries, tries), tokeninfo);
|
||||
return;
|
||||
}
|
||||
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied), tokeninfo);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Selected file in termination state */
|
||||
case 0x6285: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_terminated));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Wrong length (Lc and/or Le) */
|
||||
// NOTE: Used in ykneo-openpgp < 1.0.10 for too short PIN, changed in 1.0.11 to 0x6A80 for too short PIN
|
||||
// https://github.com/Yubico/ykneo-openpgp/commit/b49ce8241917e7c087a4dab7b2c755420ff4500f
|
||||
case 0x6700: {
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length), null);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Incorrect parameters in the data field */
|
||||
// NOTE: Used in ykneo-openpgp >= 1.0.11 for too short PIN
|
||||
case 0x6A80: {
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data), null);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Authentication method blocked, PW blocked (error counter zero) */
|
||||
case 0x6983: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Condition of use not satisfied */
|
||||
case 0x6985: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: SM data objects incorrect (e.g. wrong TLV-structure in command data) */
|
||||
// NOTE: 6A88 is "Not Found" in the spec, but ykneo-openpgp also returns 6A83 for this in some cases.
|
||||
case 0x6A88:
|
||||
case 0x6A83: {
|
||||
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: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_unknown));
|
||||
break;
|
||||
}
|
||||
// 6A82 app not installed on security token!
|
||||
case 0x6A82: {
|
||||
if (stConnection.isFidesmoToken()) {
|
||||
// Check if the Fidesmo app is installed
|
||||
if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) {
|
||||
promptFidesmoPgpInstall();
|
||||
} else {
|
||||
promptFidesmoAppInstall();
|
||||
// Otherwise, all status codes are fixed values.
|
||||
switch (status) {
|
||||
/* OpenPGP Card Spec: Security status not satisfied, PW wrong,
|
||||
PW not checked (command not allowed), Secure messaging incorrect (checksum and/or cryptogram) */
|
||||
// NOTE: Used in ykneo-openpgp >= 1.0.11 for wrong PIN
|
||||
case 0x6982: {
|
||||
SecurityTokenInfo tokeninfo = null;
|
||||
try {
|
||||
tokeninfo = stConnection.getTokenInfo();
|
||||
} catch (IOException e2) {
|
||||
// don't care
|
||||
}
|
||||
} else { // Other (possibly) compatible hardware
|
||||
onSecurityTokenError(getString(R.string.security_token_error_pgp_app_not_installed));
|
||||
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_security_not_satisfied), tokeninfo);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Selected file in termination state */
|
||||
case 0x6285: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_terminated));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Wrong length (Lc and/or Le) */
|
||||
// NOTE: Used in ykneo-openpgp < 1.0.10 for too short PIN, changed in 1.0.11 to 0x6A80 for too short PIN
|
||||
// https://github.com/Yubico/ykneo-openpgp/commit/b49ce8241917e7c087a4dab7b2c755420ff4500f
|
||||
case 0x6700: {
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_wrong_length), null);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Incorrect parameters in the data field */
|
||||
// NOTE: Used in ykneo-openpgp >= 1.0.11 for too short PIN
|
||||
case 0x6A80: {
|
||||
// hook to do something different when PIN is wrong
|
||||
onSecurityTokenPinError(getString(R.string.security_token_error_bad_data), null);
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Authentication method blocked, PW blocked (error counter zero) */
|
||||
case 0x6983: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_authentication_blocked));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Condition of use not satisfied */
|
||||
case 0x6985: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_conditions_not_satisfied));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: SM data objects incorrect (e.g. wrong TLV-structure in command data) */
|
||||
// NOTE: 6A88 is "Not Found" in the spec, but ykneo-openpgp also returns 6A83 for this in some cases.
|
||||
case 0x6A88:
|
||||
case 0x6A83: {
|
||||
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: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_unknown));
|
||||
break;
|
||||
}
|
||||
// 6A82 app not installed on security token!
|
||||
case 0x6A82: {
|
||||
if (stConnection.getTokenType() == TokenType.FIDESMO) {
|
||||
// Check if the Fidesmo app is installed
|
||||
if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) {
|
||||
promptFidesmoPgpInstall();
|
||||
} else {
|
||||
promptFidesmoAppInstall();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 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.
|
||||
// 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.
|
||||
|
||||
/* OpenPGP Card Spec: Last command of the chain expected */
|
||||
case 0x6883: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_chaining_error));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Wrong parameters P1-P2 */
|
||||
case 0x6B00: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2"));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Instruction (INS) not supported */
|
||||
case 0x6D00: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_header, "INS"));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Class (CLA) not supported */
|
||||
case 0x6E00: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_header, "CLA"));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error, e.getMessage()));
|
||||
break;
|
||||
/* OpenPGP Card Spec: Last command of the chain expected */
|
||||
case 0x6883: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_chaining_error));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Wrong parameters P1-P2 */
|
||||
case 0x6B00: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_header, "P1/P2"));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Instruction (INS) not supported */
|
||||
case 0x6D00: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_header, "INS"));
|
||||
break;
|
||||
}
|
||||
/* OpenPGP Card Spec: Class (CLA) not supported */
|
||||
case 0x6E00: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_header, "CLA"));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
onSecurityTokenError(getString(R.string.security_token_error, e.getMessage()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallback for generic IOException
|
||||
if (e.getMessage() != null) {
|
||||
onSecurityTokenError(getString(R.string.security_token_error, e.getMessage()));
|
||||
} else {
|
||||
onSecurityTokenError(getString(R.string.security_token_error_generic));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -440,14 +451,6 @@ public abstract class BaseSecurityTokenActivity extends BaseActivity
|
||||
doSecurityTokenInBackground(stConnection);
|
||||
}
|
||||
|
||||
public static class IsoDepNotSupportedException extends IOException {
|
||||
|
||||
public IsoDepNotSupportedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask user if she wants to install PGP onto her Fidesmo token
|
||||
*/
|
||||
|
||||
@@ -79,6 +79,7 @@ class ManageSecurityTokenContract {
|
||||
void showActionRetryOrFromFile();
|
||||
void showActionLocked(int unlockAttempts);
|
||||
void showActionEmptyToken();
|
||||
void showActionUnsupportedToken();
|
||||
void hideAction();
|
||||
|
||||
void operationImportKey(byte[] importKeyData);
|
||||
|
||||
@@ -263,6 +263,11 @@ public class ManageSecurityTokenFragment extends Fragment implements ManageSecur
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_empty);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showActionUnsupportedToken() {
|
||||
actionAnimator.setDisplayedChildId(R.id.token_layout_unsupported);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideAction() {
|
||||
actionAnimator.setDisplayedChild(0);
|
||||
|
||||
@@ -164,6 +164,14 @@ class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
|
||||
|
||||
private void performKeyCheck() {
|
||||
boolean keyIsEmpty = tokenInfo.isEmpty();
|
||||
boolean putKeyIsSupported = tokenInfo.isPutKeySupported();
|
||||
|
||||
if (keyIsEmpty && !putKeyIsSupported) {
|
||||
view.statusLineOk();
|
||||
view.showActionUnsupportedToken();
|
||||
return;
|
||||
}
|
||||
|
||||
if (keyIsEmpty) {
|
||||
boolean tokenIsAdminLocked = tokenInfo.getVerifyAdminRetries() == 0;
|
||||
if (tokenIsAdminLocked) {
|
||||
|
||||
@@ -370,6 +370,31 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/token_layout_unsupported">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/token_result_empty"
|
||||
style="?android:textAppearanceLarge"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:text="@string/token_put_key_unsupported"
|
||||
style="?android:textAppearanceMedium"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1580,6 +1580,8 @@
|
||||
<string name="security_token_error_header">"Security Token reported invalid %s byte."</string>
|
||||
<string name="security_token_error_tag_lost">"Security Token has been taken off too early. Keep the Security Token at the back until the operation finishes."</string>
|
||||
<string name="security_token_error_iso_dep_not_supported">"Security Token does not support the required communication standard (ISO-DEP, ISO 14443-4)"</string>
|
||||
<string name="security_token_error_generic">"Generic Error: Most probably, the Security Token has been taken off too early."</string>
|
||||
<string name="security_token_not_supported">"This Security Token is not supported by OpenKeychain."</string>
|
||||
<string name="security_token_error_try_again">"Try again"</string>
|
||||
<string name="btn_delete_original">Delete original file</string>
|
||||
|
||||
@@ -1895,6 +1897,7 @@
|
||||
<string name="token_result_key_found">Key found!</string>
|
||||
<string name="token_result_token_ok">Ready for use!</string>
|
||||
<string name="token_result_empty">Token is empty</string>
|
||||
<string name="token_put_key_unsupported">"Please set up this Security Token using GnuPG. OpenKeychain currently only supports singing/decrypting with this Security Token."</string>
|
||||
<string name="token_action_retry">Retry Search</string>
|
||||
<string name="token_action_load_from_file">Load from File</string>
|
||||
<string name="token_action_reset">Reset Security Token</string>
|
||||
|
||||
@@ -1,42 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Based on
|
||||
https://github.com/Yubico/yubikey-personalization/blob/master/ykcore/ykdef.h
|
||||
https://www.nitrokey.com/de/documentation/installation#p:nitrokey-pro&os:linux
|
||||
<!--
|
||||
Route all smart card devices (class 11) to OpenKeychain.
|
||||
Internally, we check against a whitelist of working devices.
|
||||
-->
|
||||
<usb-device class="11" />
|
||||
|
||||
Note that values are decimal.
|
||||
-->
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Yubikey NEO OTP + CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="273"/>
|
||||
<!-- Yubikey NEO CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="274"/>
|
||||
<!-- Yubikey NEO U2F + CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="277"/>
|
||||
<!-- Yubikey NEO OTP + U2F + CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="278"/>
|
||||
<!--
|
||||
Route some HID devices (class 3) to OpenKeychain.
|
||||
These tokens are generally supported but need to have CCID enabled.
|
||||
Thus, we show a notification inside OpenKeychain to inform the user.
|
||||
|
||||
<!-- Nitrokey Pro -->
|
||||
<usb-device class="11" vendor-id="8352" product-id="16648"/>
|
||||
Based on
|
||||
https://github.com/Yubico/yubikey-personalization/blob/master/ykcore/ykdef.h
|
||||
Note that values are decimal.
|
||||
-->
|
||||
<!-- Yubikey NEO - OTP only -->
|
||||
<usb-device class="3" vendor-id="4176" product-id="272"/>
|
||||
<!-- Yubikey NEO - U2F only -->
|
||||
<usb-device class="3" vendor-id="4176" product-id="275"/>
|
||||
<!-- Yubikey NEO - OTP and U2F -->
|
||||
<usb-device class="3" vendor-id="4176" product-id="276"/>
|
||||
|
||||
<!-- Yubikey 4 CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="1028"/>
|
||||
<!-- Yubikey 4 OTP + CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="1029"/>
|
||||
<!-- Yubikey 4 U2F + CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="1030"/>
|
||||
<!-- Yubikey 4 OTP + U2F + CCID -->
|
||||
<usb-device class="11" vendor-id="4176" product-id="1031"/>
|
||||
<!-- Yubikey 4 - OTP only -->
|
||||
<usb-device class="3" vendor-id="4176" product-id="1025"/>
|
||||
<!-- Yubikey 4 - U2F only -->
|
||||
<usb-device class="3" vendor-id="4176" product-id="1026"/>
|
||||
<!-- Yubikey 4 - OTP and U2F -->
|
||||
<usb-device class="3" vendor-id="4176" product-id="1027"/>
|
||||
|
||||
<!-- Nitrokey Storage -->
|
||||
<!--<usb-device class="11" vendor-id="8352" product-id="16649"/>-->
|
||||
|
||||
<!-- Nitrokey Start -->
|
||||
<!--<usb-device class="11" vendor-id="8352" product-id="16913"/>-->
|
||||
<!-- Default GNUK vid/pid -->
|
||||
<!--<usb-device class="11" vendor-id="9035" product-id="0"/>-->
|
||||
|
||||
<!-- Ledger Nano S -->
|
||||
<!--<usb-device class="11" vendor-id="11415" product-id="1"/>-->
|
||||
</resources>
|
||||
|
||||
@@ -8,9 +8,12 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.internal.verification.VerificationModeFactory;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.shadows.ShadowLog;
|
||||
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
||||
import org.sufficientlysecure.keychain.util.Passphrase;
|
||||
|
||||
import static org.mockito.Matchers.eq;
|
||||
@@ -23,19 +26,25 @@ import static org.mockito.Mockito.when;
|
||||
@RunWith(KeychainTestRunner.class)
|
||||
public class SecurityTokenConnectionTest {
|
||||
|
||||
private Transport transport;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
ShadowLog.stream = System.out;
|
||||
|
||||
transport = mock(Transport.class);
|
||||
when(transport.getTransportType()).thenReturn(TransportType.USB);
|
||||
when(transport.getTokenTypeIfAvailable()).thenReturn(TokenType.YUBIKEY_NEO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test_connectToDevice() throws Exception {
|
||||
Transport transport = mock(Transport.class);
|
||||
SecurityTokenConnection securityTokenConnection =
|
||||
new SecurityTokenConnection(transport, new Passphrase("123456"), new OpenPgpCommandApduFactory());
|
||||
|
||||
String[] dialog = { "00a4040006d27600012401", "9000",
|
||||
"00ca006e00",
|
||||
String[] dialog = {
|
||||
"00a4040006d27600012401", // select openpgp applet
|
||||
"9000",
|
||||
"00ca006e00", // get application related data
|
||||
"6e81de4f10d27600012401020000060364311500005f520f0073000080000000000000000000007381b7c00af" +
|
||||
"00000ff04c000ff00ffc106010800001103c206010800001103c306010800001103c407007f7f7f03030" +
|
||||
"3c53c4ec5fee25c4e89654d58cad8492510a89d3c3d8468da7b24e15bfc624c6a792794f15b7599915f7" +
|
||||
@@ -55,7 +64,6 @@ public class SecurityTokenConnectionTest {
|
||||
|
||||
@Test
|
||||
public void test_getTokenInfo() throws Exception {
|
||||
Transport transport = mock(Transport.class);
|
||||
SecurityTokenConnection securityTokenConnection =
|
||||
new SecurityTokenConnection(transport, new Passphrase("123456"), new OpenPgpCommandApduFactory());
|
||||
OpenPgpCapabilities openPgpCapabilities = new OpenPgpCapabilities(
|
||||
@@ -68,6 +76,7 @@ public class SecurityTokenConnectionTest {
|
||||
"000000000cd0c59cd0f2a59cd0af059cd0c95"
|
||||
));
|
||||
securityTokenConnection.setConnectionCapabilities(openPgpCapabilities);
|
||||
securityTokenConnection.determineTokenType();
|
||||
|
||||
String[] dialog = {
|
||||
"00ca006500",
|
||||
@@ -98,6 +107,5 @@ public class SecurityTokenConnectionTest {
|
||||
CommandApdu command = CommandApdu.fromBytes(Hex.decode(dialog[i]));
|
||||
inOrder.verify(transport).transceive(eq(command));
|
||||
}
|
||||
inOrder.verifyNoMoreInteractions();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user