/* * Copyright (C) 2017 Schürmann & Breitmoser GbR * * 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 java.util.Arrays; import com.google.auto.value.AutoValue; /** * A command APDU following the structure defined in ISO/IEC 7816-4. * It consists of a four byte header and a conditional body of variable length. */ @AutoValue public abstract class CommandApdu { public abstract int getCLA(); public abstract int getINS(); public abstract int getP1(); public abstract int getP2(); @SuppressWarnings("mutable") public abstract byte[] getData(); public abstract int getNe(); public static CommandApdu create(byte[] apdu, int apduOffset, int apduLength) { return fromBytes(Arrays.copyOfRange(apdu, apduOffset, apduOffset + apduLength)); } public static CommandApdu create(int cla, int ins, int p1, int p2) { return create(cla, ins, p1, p2, null, 0, 0, 0); } public static CommandApdu create(int cla, int ins, int p1, int p2, int ne) { return create(cla, ins, p1, p2, null, 0, 0, ne); } public static CommandApdu create(int cla, int ins, int p1, int p2, byte[] data) { return create(cla, ins, p1, p2, data, 0, data.length, 0); } public static CommandApdu create(int cla, int ins, int p1, int p2, byte[] data, int dataOffset, int dataLength) { return create(cla, ins, p1, p2, data, dataOffset, dataLength,0); } public static CommandApdu create(int cla, int ins, int p1, int p2, byte[] data, int ne) { return create(cla, ins, p1, p2, data, 0, data.length, ne); } public static CommandApdu create( int cla, int ins, int p1, int p2, byte[] data, int dataOffset, int dataLength, int ne) { if (ne < 0) { throw new IllegalArgumentException("ne must not be negative"); } if (ne > 65536) { throw new IllegalArgumentException("ne is too large"); } if (data != null) { data = Arrays.copyOfRange(data, dataOffset, dataOffset + dataLength); } else { data = new byte[0]; } return new AutoValue_CommandApdu(cla, ins, p1, p2, data, ne); } public static CommandApdu fromBytes(byte[] apdu, int offset, int length) { return fromBytes(Arrays.copyOfRange(apdu, offset, offset + length)); } /** * Command APDU encoding options: *

* case 1: |CLA|INS|P1 |P2 | len = 4 * case 2s: |CLA|INS|P1 |P2 |LE | len = 5 * case 3s: |CLA|INS|P1 |P2 |LC |...BODY...| len = 6..260 * case 4s: |CLA|INS|P1 |P2 |LC |...BODY...|LE | len = 7..261 * case 2e: |CLA|INS|P1 |P2 |00 |LE1|LE2| len = 7 * case 3e: |CLA|INS|P1 |P2 |00 |LC1|LC2|...BODY...| len = 8..65542 * case 4e: |CLA|INS|P1 |P2 |00 |LC1|LC2|...BODY...|LE1|LE2| len =10..65544 *

* LE, LE1, LE2 may be 0x00. * LC must not be 0x00 and LC1|LC2 must not be 0x00|0x00 */ public static CommandApdu fromBytes(byte[] apdu) { if (apdu.length < 4) { throw new IllegalArgumentException("apdu must be at least 4 bytes long"); } int cla = apdu[0] & 0xff; int ins = apdu[1] & 0xff; int p1 = apdu[2] & 0xff; int p2 = apdu[3] & 0xff; final Integer dataOffset; final Integer dataLength; final int ne; if (apdu.length == 4) { // case 1 dataOffset = null; dataLength = null; ne = 0; } else if (apdu.length == 5) { // case 2s dataOffset = null; dataLength = null; ne = (apdu[4] == 0) ? 256 : (apdu[4] & 0xff); } else if (apdu[4] != 0) { dataOffset = 5; dataLength = apdu[4] & 0xff; if (apdu.length == 4 + 1 + dataLength) { // case 3s ne = 0; } else { // case 4s int l2 = apdu[apdu.length - 1] & 0xff; ne = (l2 == 0) ? 256 : l2; } } else { int l2 = ((apdu[5] & 0xff) << 8) | (apdu[6] & 0xff); if (apdu.length == 7) { // case 2e dataOffset = null; dataLength = null; ne = (l2 == 0) ? 65536 : l2; } else { dataOffset = 7; dataLength = l2; if (apdu.length == 4 + 3 + l2) { // case 3e ne = 0; } else { // case 4e int leOfs = apdu.length - 2; int le = ((apdu[leOfs] & 0xff) << 8) | (apdu[leOfs + 1] & 0xff); ne = (le == 0) ? 65536 : le; } } } byte[] data; if (dataOffset != null) { data = Arrays.copyOfRange(apdu, dataOffset, dataOffset + dataLength); } else { data = new byte[0]; } return new AutoValue_CommandApdu(cla, ins, p1, p2, data, ne); } public byte[] toBytes() { final byte[] apdu; byte[] data = getData(); int ne = getNe(); if (data.length == 0) { if (ne == 0) { // case 1 apdu = new byte[4]; } else { // case 2s or 2e if (ne <= 256) { // case 2s apdu = new byte[5]; apdu[4] = (ne != 256) ? (byte) ne : 0; } else { // case 2e apdu = new byte[7]; if (ne != 65536) { apdu[5] = (byte) (ne >> 8); apdu[6] = (byte) ne; } else { apdu[5] = 0; apdu[6] = 0; } } } } else { if (ne == 0) { // case 3s or 3e if (data.length <= 255) { // case 3s apdu = new byte[4 + 1 + data.length]; apdu[4] = (byte) data.length; System.arraycopy(data, 0, apdu, 5, data.length); } else { // case 3e apdu = new byte[4 + 3 + data.length]; apdu[4] = 0; apdu[5] = (byte) (data.length >> 8); apdu[6] = (byte) data.length; System.arraycopy(data, 0, apdu, 7, data.length); } } else { if (data.length <= 255 && ne <= 256) { // case 4s apdu = new byte[4 + 2 + data.length]; apdu[4] = (byte) data.length; System.arraycopy(data, 0, apdu, 5, data.length); apdu[apdu.length - 1] = (ne != 256) ? (byte) ne : 0; } else { // case 4e apdu = new byte[4 + 5 + data.length]; apdu[4] = 0; apdu[5] = (byte) (data.length >> 8); apdu[6] = (byte) data.length; System.arraycopy(data, 0, apdu, 7, data.length); if (ne != 65536) { apdu[apdu.length - 2] = (byte) (ne >> 8); apdu[apdu.length - 1] = (byte) ne; } else { apdu[apdu.length - 2] = 0; apdu[apdu.length - 1] = 0; } } } } apdu[0] = (byte) getCLA(); apdu[1] = (byte) getINS(); apdu[2] = (byte) getP1(); apdu[3] = (byte) getP2(); return apdu; } }