/* * Copyright (C) 2016 Nikita Mikhailov <nikita.s.mikhailov@gmail.com> * * 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 <http://www.gnu.org/licenses/>. */ package org.sufficientlysecure.keychain.securitytoken.usb.tpdu; import android.support.annotation.NonNull; import org.bouncycastle.util.Arrays; import org.bouncycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver; import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException; import org.sufficientlysecure.keychain.securitytoken.usb.CcidTransportProtocol; import org.sufficientlysecure.keychain.util.Log; public class T1TpduProtocol implements CcidTransportProtocol { private final static int MAX_FRAME_LEN = 254; private byte mCounter = 0; private CcidTransceiver mTransceiver; private BlockChecksumType mChecksumType; public T1TpduProtocol(final CcidTransceiver transceiver) throws UsbTransportException { mTransceiver = transceiver; // Connect byte[] atr = mTransceiver.iccPowerOn(); Log.d(Constants.TAG, "Usb transport connected T1/TPDU, ATR=" + Hex.toHexString(atr)); // TODO: set checksum from atr mChecksumType = BlockChecksumType.LRC; // PPS all auto pps(); } protected void pps() throws UsbTransportException { byte[] pps = new byte[]{(byte) 0xFF, 1, (byte) (0xFF ^ 1)}; mTransceiver.sendXfrBlock(pps); byte[] ppsResponse = mTransceiver.receiveRaw(); Log.d(Constants.TAG, "PPS response " + Hex.toHexString(ppsResponse)); } public byte[] transceive(@NonNull byte[] apdu) throws UsbTransportException { int start = 0; if (apdu.length == 0) { throw new UsbTransportException("Cant transcive zero-length apdu(tpdu)"); } Block responseBlock = null; while (apdu.length - start > 0) { boolean hasMore = start + MAX_FRAME_LEN < apdu.length; int len = Math.min(MAX_FRAME_LEN, apdu.length - start); // Send next frame Block block = newIBlock(mCounter++, hasMore, Arrays.copyOfRange(apdu, start, start + len)); mTransceiver.sendXfrBlock(block.getRawData()); // Receive I or R block responseBlock = getBlockFromResponse(mTransceiver.receiveRaw()); start += len; if (responseBlock instanceof SBlock) { Log.d(Constants.TAG, "S-Block received " + responseBlock.toString()); // just ignore } else if (responseBlock instanceof RBlock) { Log.d(Constants.TAG, "R-Block received " + responseBlock.toString()); if (((RBlock) responseBlock).getError() != RBlock.RError.NO_ERROR) { throw new UsbTransportException("R-Block reports error " + ((RBlock) responseBlock).getError()); } } else { // I block if (start != apdu.length) { throw new UsbTransportException("T1 frame response underflow"); } break; } } // Receive if (responseBlock == null || !(responseBlock instanceof IBlock)) throw new UsbTransportException("Invalid tpdu sequence state"); byte[] responseApdu = responseBlock.getApdu(); while (((IBlock) responseBlock).getChaining()) { Block ackBlock = newRBlock((byte) (((IBlock) responseBlock).getSequence() + 1)); mTransceiver.sendXfrBlock(ackBlock.getRawData()); responseBlock = getBlockFromResponse(mTransceiver.receiveRaw()); if (responseBlock instanceof IBlock) { responseApdu = Arrays.concatenate(responseApdu, responseBlock.getApdu()); } else { Log.d(Constants.TAG, "Response block received " + responseBlock.toString()); throw new UsbTransportException("Response: invalid state - invalid block received"); } } return responseApdu; } // Factory methods public Block getBlockFromResponse(byte[] data) throws UsbTransportException { final Block baseBlock = new Block(mChecksumType, data); if ((baseBlock.getPcb() & IBlock.MASK_RBLOCK) == IBlock.MASK_VALUE_RBLOCK) { return new IBlock(baseBlock); } else if ((baseBlock.getPcb() & SBlock.MASK_SBLOCK) == SBlock.MASK_VALUE_SBLOCK) { return new SBlock(baseBlock); } else if ((baseBlock.getPcb() & RBlock.MASK_RBLOCK) == RBlock.MASK_VALUE_RBLOCK) { return new RBlock(baseBlock); } throw new UsbTransportException("TPDU Unknown block type"); } public IBlock newIBlock(byte sequence, boolean chaining, byte[] apdu) throws UsbTransportException { return new IBlock(mChecksumType, (byte) 0, sequence, chaining, apdu); } public RBlock newRBlock(byte sequence) throws UsbTransportException { return new RBlock(mChecksumType, (byte) 0, sequence); } }