diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.java new file mode 100644 index 000000000..8133f985d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/CardCapabilities.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.securitytoken.usb.UsbTransportException; + +import java.nio.ByteBuffer; + +public class CardCapabilities { + private static final int MASK_CHAINING = 1 << 7; + private static final int MASK_EXTENDED = 1 << 6; + + private byte[] mCapabilityBytes; + + public CardCapabilities(byte[] historicalBytes) throws UsbTransportException { + if (historicalBytes[0] != 0x00) { + throw new UsbTransportException("Invalid historical bytes category indicator byte"); + } + + mCapabilityBytes = getCapabilitiesBytes(historicalBytes); + } + + public CardCapabilities() { + mCapabilityBytes = null; + } + + private static byte[] getCapabilitiesBytes(byte[] historicalBytes) { + // Compact TLV + ByteBuffer byteBuffer = ByteBuffer.wrap(historicalBytes, 1, historicalBytes.length - 2); + while (byteBuffer.hasRemaining()) { + byte tl = byteBuffer.get(); + if (tl == 0x73) { // Capabilities TL + byte[] val = new byte[3]; + byteBuffer.get(val); + return val; + } + byteBuffer.position(byteBuffer.position() + (tl & 0xF)); + } + + return null; + } + + public boolean hasChaining() { + return mCapabilityBytes != null && (mCapabilityBytes[2] & MASK_CHAINING) != 0; + } + + public boolean hasExtended() { + return mCapabilityBytes != null && (mCapabilityBytes[2] & MASK_EXTENDED) != 0; + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java index 0232c36a8..034d4b1ac 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/securitytoken/SecurityTokenHelper.java @@ -32,10 +32,12 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey; import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException; import org.sufficientlysecure.keychain.securitytoken.smartcardio.CommandAPDU; import org.sufficientlysecure.keychain.securitytoken.smartcardio.ResponseAPDU; +import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; import org.sufficientlysecure.keychain.util.Iso7816TLV; import org.sufficientlysecure.keychain.util.Log; import org.sufficientlysecure.keychain.util.Passphrase; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; import java.nio.ByteBuffer; @@ -57,11 +59,14 @@ public class SecurityTokenHelper { private static final int APDU_SW_SUCCESS = 0x9000; + private static final int MASK_CLA_CHAINING = 1 << 4; + // 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 CardCapabilities mCardCapabilities; private Passphrase mPin; private Passphrase mAdminPin; @@ -152,6 +157,8 @@ public class SecurityTokenHelper { */ public void connectToDevice() throws IOException { // Connect on transport layer + mCardCapabilities = null; + mTransport.connect(); // Connect on smartcard layer @@ -163,6 +170,8 @@ public class SecurityTokenHelper { throw new CardException("Initialization failed!", response.getSW()); } + mCardCapabilities = new CardCapabilities(getHistoricalBytes()); + byte[] pwStatusBytes = getPwStatusBytes(); mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1); mPw1ValidatedForSignature = false; @@ -442,6 +451,10 @@ public class SecurityTokenHelper { return getHolderName(getData(0x00, 0x65)); } + private byte[] getHistoricalBytes() throws IOException { + return getData(0x5F, 0x52); + } + private byte[] getData(int p1, int p2) throws IOException { ResponseAPDU response = communicate(new CommandAPDU(0x00, 0xCA, p1, p2)); if (response.getSW() != APDU_SW_SUCCESS) { @@ -538,7 +551,48 @@ public class SecurityTokenHelper { * Transceive data via NFC encoded as Hex */ private ResponseAPDU communicate(CommandAPDU apdu) throws IOException { - return mTransport.transceive(apdu); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + + ResponseAPDU lastResponse = null; + // Transmit + if (mCardCapabilities.hasExtended()) { + lastResponse = mTransport.transceive(apdu); + } else if (apdu.getData().length <= MAX_APDU_NC) { + lastResponse = mTransport.transceive(new CommandAPDU(apdu.getCLA(), apdu.getINS(), + apdu.getP1(), apdu.getP2(), apdu.getData(), MAX_APDU_NE)); + } else if (apdu.getData().length > MAX_APDU_NC && mCardCapabilities.hasChaining()) { + int offset = 0; + byte[] data = apdu.getData(); + while (offset < data.length) { + int curLen = Math.min(MAX_APDU_NC, data.length - offset); + boolean last = offset + curLen >= data.length; + int cla = apdu.getCLA() + (last ? 0 : MASK_CLA_CHAINING); + + lastResponse = mTransport.transceive(new CommandAPDU(cla, apdu.getINS(), apdu.getP1(), + apdu.getP2(), apdu.getData(), MAX_APDU_NE)); + + if (!last && lastResponse.getSW() != APDU_SW_SUCCESS) { + throw new UsbTransportException("Failed to chain apdu"); + } + } + } + if (lastResponse == null) { + throw new UsbTransportException("Can't transmit command"); + } + + result.write(lastResponse.getData()); + + // Receive + while (lastResponse.getSW1() == 0x61) { + CommandAPDU getResponse = new CommandAPDU(0x00, 0xC0, 0x00, 0x00, lastResponse.getSW2()); + lastResponse = mTransport.transceive(getResponse); + result.write(lastResponse.getData()); + } + + result.write(lastResponse.getSW1()); + result.write(lastResponse.getSW2()); + + return new ResponseAPDU(result.toByteArray()); } public Transport getTransport() {