diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidDescription.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidDescription.java new file mode 100644 index 000000000..5a30c830f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidDescription.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2016 Nikita Mikhailov + * Copyright (C) 2017 Vincent Breitmoser + * + * 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.usb; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.ArrayList; + +import android.support.annotation.NonNull; + +import com.google.auto.value.AutoValue; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol; +import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1TpduProtocol; + + +@AutoValue +abstract class CcidDescription { + private static final int DESCRIPTOR_LENGTH = 0x36; + private static final int DESCRIPTOR_TYPE = 0x21; + + // dwFeatures Masks + private static final int FEATURE_AUTOMATIC_VOLTAGE = 0x00008; + private static final int FEATURE_EXCHANGE_LEVEL_TPDU = 0x10000; + private static final int FEATURE_EXCHAGE_LEVEL_SHORT_APDU = 0x20000; + private static final int FEATURE_EXCHAGE_LEVEL_EXTENDED_APDU = 0x40000; + + // bVoltageSupport Masks + private static final byte VOLTAGE_5V = 1; + private static final byte VOLTAGE_3V = 2; + private static final byte VOLTAGE_1_8V = 4; + + private static final int SLOT_OFFSET = 4; + private static final int FEATURES_OFFSET = 40; + private static final short MASK_T1_PROTO = 2; + + public abstract byte getMaxSlotIndex(); + public abstract byte getVoltageSupport(); + public abstract int getProtocols(); + public abstract int getFeatures(); + + @NonNull + static CcidDescription fromRawDescriptors(byte[] desc) throws UsbTransportException { + int dwProtocols = 0, dwFeatures = 0; + byte bMaxSlotIndex = 0, bVoltageSupport = 0; + + boolean hasCcidDescriptor = false; + + ByteBuffer byteBuffer = ByteBuffer.wrap(desc).order(ByteOrder.LITTLE_ENDIAN); + + while (byteBuffer.hasRemaining()) { + byteBuffer.mark(); + byte len = byteBuffer.get(), type = byteBuffer.get(); + + if (type == DESCRIPTOR_TYPE && len == DESCRIPTOR_LENGTH) { + byteBuffer.reset(); + + byteBuffer.position(byteBuffer.position() + SLOT_OFFSET); + bMaxSlotIndex = byteBuffer.get(); + bVoltageSupport = byteBuffer.get(); + dwProtocols = byteBuffer.getInt(); + + byteBuffer.reset(); + + byteBuffer.position(byteBuffer.position() + FEATURES_OFFSET); + dwFeatures = byteBuffer.getInt(); + hasCcidDescriptor = true; + break; + } else { + byteBuffer.position(byteBuffer.position() + len - 2); + } + } + + if (!hasCcidDescriptor) { + throw new UsbTransportException("CCID descriptor not found"); + } + + return new AutoValue_CcidDescription(bMaxSlotIndex, bVoltageSupport, dwProtocols, dwFeatures); + } + + Voltage[] getVoltages() { + ArrayList voltages = new ArrayList<>(); + + if (hasFeature(FEATURE_AUTOMATIC_VOLTAGE)) { + voltages.add(Voltage.AUTO); + } else { + for (Voltage v : Voltage.values()) { + if ((v.mask & getVoltageSupport()) != 0) { + voltages.add(v); + } + } + } + + return voltages.toArray(new Voltage[voltages.size()]); + } + + CcidTransportProtocol getSuitableTransportProtocol() throws UsbTransportException { + boolean hasT1Protocol = (getProtocols() & MASK_T1_PROTO) != 0; + if (!hasT1Protocol) { + throw new UsbTransportException("T=0 protocol is not supported!"); + } + + if (hasFeature(CcidDescription.FEATURE_EXCHANGE_LEVEL_TPDU)) { + return new T1TpduProtocol(); + } else if (hasFeature(CcidDescription.FEATURE_EXCHAGE_LEVEL_SHORT_APDU) || + hasFeature(CcidDescription.FEATURE_EXCHAGE_LEVEL_EXTENDED_APDU)) { + return new T1ShortApduProtocol(); + } else { + throw new UsbTransportException("Character level exchange is not supported"); + } + } + + private boolean hasFeature(int feature) { + return (getFeatures() & feature) != 0; + } + + enum Voltage { + AUTO(0, 0), _5V(1, VOLTAGE_5V), _3V(2, VOLTAGE_3V), _1_8V(3, VOLTAGE_1_8V); + + final byte mask; + final byte powerOnValue; + + Voltage(int powerOnValue, int mask) { + this.powerOnValue = (byte) powerOnValue; + this.mask = (byte) mask; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java index 471cefbbf..18912da4a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/CcidTransceiver.java @@ -34,6 +34,7 @@ import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; + public class CcidTransceiver { private static final int CCID_HEADER_LENGTH = 10; @@ -55,15 +56,18 @@ public class CcidTransceiver { private final UsbDeviceConnection usbConnection; private final UsbEndpoint usbBulkIn; private final UsbEndpoint usbBulkOut; + private final CcidDescription usbCcidDescription; private final byte[] inputBuffer; private byte currentSequenceNumber; - CcidTransceiver(UsbDeviceConnection connection, UsbEndpoint bulkIn, UsbEndpoint bulkOut) { + CcidTransceiver(UsbDeviceConnection connection, UsbEndpoint bulkIn, UsbEndpoint bulkOut, + CcidDescription ccidDescription) { usbConnection = connection; usbBulkIn = bulkIn; usbBulkOut = bulkOut; + usbCcidDescription = ccidDescription; inputBuffer = new byte[usbBulkIn.getMaxPacketSize()]; } @@ -79,25 +83,58 @@ public class CcidTransceiver { skipAvailableInput(); + CcidDataBlock response = null; + for (CcidDescription.Voltage v : usbCcidDescription.getVoltages()) { + Log.v(Constants.TAG, "CCID: attempting to power on with voltage " + v.toString()); + response = iccPowerOnVoltage(v.powerOnValue); + + if (response.getStatus() == 1 && response.getError() == 7) { // Power select error + Log.v(Constants.TAG, "CCID: failed to power on with voltage " + v.toString()); + iccPowerOff(); + Log.v(Constants.TAG, "CCID: powered off"); + } else { + break; + } + } + if (response == null) { + throw new UsbTransportException("Couldn't power up ICC2"); + } + + long elapsedTime = SystemClock.elapsedRealtime() - startTime; + + Log.d(Constants.TAG, "Usb transport connected, took " + elapsedTime + "ms, ATR=" + + Hex.toHexString(response.getData())); + + return response; + } + + private CcidDataBlock iccPowerOnVoltage(byte voltage) throws UsbTransportException { byte sequenceNumber = currentSequenceNumber++; final byte[] iccPowerCommand = { MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON, 0x00, 0x00, 0x00, 0x00, SLOT_NUMBER, sequenceNumber, - 0x00, // voltage select = auto + voltage, 0x00, 0x00 // reserved for future use }; sendRaw(iccPowerCommand, 0, iccPowerCommand.length); - CcidDataBlock response = receiveDataBlock(sequenceNumber); - long elapsedTime = SystemClock.elapsedRealtime() - startTime; + return receiveDataBlock(sequenceNumber); + } - Log.d(Constants.TAG, "Usb transport connected T1/TPDU, took " + elapsedTime + "ms, ATR=" + - Hex.toHexString(response.getData())); + private void iccPowerOff() throws UsbTransportException { + byte sequenceNumber = currentSequenceNumber++; + final byte[] iccPowerCommand = { + 0x63, + 0x00, 0x00, 0x00, 0x00, + 0x00, + sequenceNumber, + 0x00 + }; - return response; + sendRaw(iccPowerCommand, 0, iccPowerCommand.length); } /** 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 707c6a076..0ff4e9f02 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 @@ -33,13 +33,9 @@ import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.Transport import org.sufficientlysecure.keychain.securitytoken.Transport; import org.sufficientlysecure.keychain.securitytoken.CommandApdu; import org.sufficientlysecure.keychain.securitytoken.ResponseApdu; -import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol; -import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1TpduProtocol; 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 @@ -47,15 +43,6 @@ import java.nio.ByteOrder; * Implements small subset of these features */ public class UsbTransport implements Transport { - private static final int PROTOCOLS_OFFSET = 6; - private static final int FEATURES_OFFSET = 40; - private static final short MASK_T1_PROTO = 2; - - // dwFeatures Masks - private static final int MASK_TPDU = 0x10000; - 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; @@ -148,57 +135,13 @@ public class UsbTransport implements Transport { throw new UsbTransportException("USB error: failed to claim interface"); } - byte[] rawDescriptors = usbConnection.getRawDescriptors(); - ccidTransportProtocol = getCcidTransportProtocolForRawDescriptors(rawDescriptors); + CcidDescription ccidDescription = CcidDescription.fromRawDescriptors(usbConnection.getRawDescriptors()); + CcidTransceiver transceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, ccidDescription); - CcidTransceiver transceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut); + ccidTransportProtocol = ccidDescription.getSuitableTransportProtocol(); ccidTransportProtocol.connect(transceiver); } - private CcidTransportProtocol getCcidTransportProtocolForRawDescriptors(byte[] desc) throws UsbTransportException { - int dwProtocols = 0, dwFeatures = 0; - boolean hasCcidDescriptor = false; - - ByteBuffer byteBuffer = ByteBuffer.wrap(desc).order(ByteOrder.LITTLE_ENDIAN); - - while (byteBuffer.hasRemaining()) { - byteBuffer.mark(); - byte len = byteBuffer.get(), type = byteBuffer.get(); - - if (type == 0x21 && len == 0x36) { - byteBuffer.reset(); - - byteBuffer.position(byteBuffer.position() + PROTOCOLS_OFFSET); - dwProtocols = byteBuffer.getInt(); - - byteBuffer.reset(); - - byteBuffer.position(byteBuffer.position() + FEATURES_OFFSET); - dwFeatures = byteBuffer.getInt(); - hasCcidDescriptor = true; - break; - } else { - byteBuffer.position(byteBuffer.position() + len - 2); - } - } - - if (!hasCcidDescriptor) { - throw new UsbTransportException("CCID descriptor not found"); - } - - if ((dwProtocols & MASK_T1_PROTO) == 0) { - throw new UsbTransportException("T=0 protocol is not supported"); - } - - if ((dwFeatures & MASK_TPDU) != 0) { - return new T1TpduProtocol(); - } else if (((dwFeatures & MASK_SHORT_APDU) != 0) || ((dwFeatures & MASK_EXTENDED_APDU) != 0)) { - return new T1ShortApduProtocol(); - } else { - throw new UsbTransportException("Character level exchange is not supported"); - } - } - /** * Transmit and receive data * diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java index 13e86c723..8c9d618b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/usb/UsbTransportException.java @@ -20,18 +20,15 @@ package org.sufficientlysecure.keychain.securitytoken.usb; import java.io.IOException; public class UsbTransportException extends IOException { - public UsbTransportException() { - } - - public UsbTransportException(final String detailMessage) { + public UsbTransportException(String detailMessage) { super(detailMessage); } - public UsbTransportException(final String message, final Throwable cause) { + public UsbTransportException(String message, final Throwable cause) { super(message, cause); } - public UsbTransportException(final Throwable cause) { + public UsbTransportException(Throwable cause) { super(cause); } }