From 8678b76612fb5ee9c6b6a059dbf6a479e09dff05 Mon Sep 17 00:00:00 2001 From: Andrea Franz Date: Mon, 10 Sep 2018 12:48:20 +0200 Subject: [PATCH] add Dele command and test --- .../apducommands/ExternalAuthenticate.java | 39 -- .../appletinstaller/apducommands/GetData.java | 8 - .../apducommands/InitializeUpdate.java | 78 --- .../apducommands/InstallForInstall.java | 52 -- .../apducommands/InstallForLoad.java | 36 -- .../appletinstaller/apducommands/Load.java | 135 ------ .../apducommands/SecureChannelSession.java | 444 ------------------ .../appletinstaller/apducommands/Select.java | 20 - .../appletinstaller/apducommands/Status.java | 26 - .../apducommands/WalletAppletCommandSet.java | 441 ----------------- .../ExternalAuthenticateTest.java | 38 -- .../apducommands/InitializeUpdateTest.java | 62 --- .../apducommands/InstallForInstallTest.java | 26 - .../apducommands/InstallForLoadTest.java | 24 - .../apducommands/LoadTest.java | 53 --- .../apducommands/SelectTest.java | 20 - .../apducommands/StatusTest.java | 21 - 17 files changed, 1523 deletions(-) delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/GetData.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java delete mode 100644 app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticateTest.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java delete mode 100644 app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java deleted file mode 100644 index 1e4858c..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticate.java +++ /dev/null @@ -1,39 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUResponse; -import im.status.applet_installer_test.appletinstaller.Crypto; - -public class ExternalAuthenticate { - public static int CLA = 0x84; - public static int INS = 0x82; - public static int P1 = 0x01; - public static int P2 = 0x00; - - private byte[] encKeyData; - private byte[] cardChallenge; - private byte[] hostChallenge; - - public ExternalAuthenticate(byte[] encKeyData, byte[] cardChallenge, byte[] hostChallenge) { - this.encKeyData = encKeyData; - this.cardChallenge = cardChallenge; - this.hostChallenge = hostChallenge; - } - - public APDUCommand getCommand() { - return new APDUCommand(CLA, INS, P1, P2, this.getHostCryptogram()); - } - - public byte[] getHostCryptogram() { - byte[] data = new byte[this.cardChallenge.length + this.hostChallenge.length]; - System.arraycopy(cardChallenge, 0, data, 0, cardChallenge.length); - System.arraycopy(hostChallenge, 0, data, cardChallenge.length, hostChallenge.length); - byte[] paddedData = Crypto.appendDESPadding(data); - - return Crypto.mac3des(this.encKeyData, paddedData, Crypto.NullBytes8); - } - - public boolean checkResponse(APDUResponse resp) { - return resp.getSw() == APDUResponse.SW_OK; - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/GetData.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/GetData.java deleted file mode 100644 index 6c52272..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/GetData.java +++ /dev/null @@ -1,8 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -public class GetData { - public static final int CLA = 0x80; - public static final int INS = 0x50; - public static final int P1 = 0; - public static final int P2 = 0; -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java deleted file mode 100644 index 5f66f37..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdate.java +++ /dev/null @@ -1,78 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import java.security.SecureRandom; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUException; -import im.status.applet_installer_test.appletinstaller.APDUResponse; -import im.status.applet_installer_test.appletinstaller.Crypto; -import im.status.applet_installer_test.appletinstaller.HexUtils; -import im.status.applet_installer_test.appletinstaller.Keys; -import im.status.applet_installer_test.appletinstaller.Logger; -import im.status.applet_installer_test.appletinstaller.Session; - -public class InitializeUpdate { - public static final int CLA = 0x80; - public static final int INS = 0x50; - public static final int P1 = 0; - public static final int P2 = 0; - - public static byte[] DERIVATION_PURPOSE_ENC = new byte[]{(byte) 0x01, (byte) 0x82}; - public static byte[] DERIVATION_PURPOSE_MAC = new byte[]{(byte) 0x01, (byte) 0x01}; - public static byte[] DERIVATION_PURPOSE_DEK = new byte[]{(byte) 0x01, (byte) 0x81}; - - private byte[] hostChallenge; - - public InitializeUpdate(byte[] challenge) { - this.hostChallenge = challenge; - } - - public APDUCommand getCommand() { - return new APDUCommand(CLA, INS, P1, P2, this.hostChallenge, true); - } - - public static byte[] generateChallenge() { - SecureRandom random = new SecureRandom(); - byte challenge[] = new byte[8]; - random.nextBytes(challenge); - - return challenge; - } - - public Session verifyResponse(Keys cardKeys, APDUResponse resp) throws APDUException { - if (resp.getSw() == APDUResponse.SW_SECURITY_CONDITION_NOT_SATISFIED) { - throw new APDUException(resp.getSw(), "security confition not satisfied"); - } - - if (resp.getSw() == APDUResponse.SW_AUTHENTICATION_METHOD_BLOCKED) { - throw new APDUException(resp.getSw(), "authentication method blocked"); - } - - byte[] data = resp.getData(); - - if (data.length != 28) { - throw new APDUException(resp.getSw(), String.format("bad data length, expected 28, got %d", data.length)); - } - - byte[] cardChallenge = new byte[8]; - System.arraycopy(data, 12, cardChallenge, 0, 8); - - byte[] cardCryptogram = new byte[8]; - System.arraycopy(data, 20, cardCryptogram, 0, 8); - - byte[] seq = new byte[2]; - System.arraycopy(data, 12, seq, 0, 2); - - byte[] sessionEncKey = Crypto.deriveKey(cardKeys.getEncKeyData(), seq, DERIVATION_PURPOSE_ENC); - byte[] sessionMacKey = Crypto.deriveKey(cardKeys.getMacKeyData(), seq, DERIVATION_PURPOSE_MAC); - - Keys sessionKeys = new Keys(sessionEncKey, sessionMacKey); - - boolean verified = Crypto.verifyCryptogram(sessionKeys.getEncKeyData(), this.hostChallenge, cardChallenge, cardCryptogram); - if (!verified) { - throw new APDUException("error verifying card cryptogram."); - } - - return new Session(sessionKeys, cardChallenge); - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java deleted file mode 100644 index f0c0cd6..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstall.java +++ /dev/null @@ -1,52 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; - -public class InstallForInstall { - public static final int CLA = 0x80; - public static final int INS = 0xE6; - public static final int P1 = 0x0C; - public static final int P2 = 0; - - private byte[] packageAID; - private byte[] appletAID; - private byte[] instanceAID; - private byte[] params; - - public InstallForInstall(byte[] packageAID, byte[] appletAID, byte[] instanceAID, byte[] params) { - this.packageAID = packageAID; - this.appletAID = appletAID; - this.instanceAID = instanceAID; - this.params = params; - } - - public APDUCommand getCommand() throws IOException { - ByteArrayOutputStream data = new ByteArrayOutputStream(); - data.write(this.packageAID.length); - data.write(this.packageAID); - data.write(this.appletAID.length); - data.write(this.appletAID); - data.write(this.instanceAID.length); - data.write(this.instanceAID); - - byte[] privileges = new byte[]{0x00}; - data.write(privileges.length); - data.write(privileges); - - byte[] fullParams = new byte[2 + params.length]; - fullParams[0] = (byte) 0xC9; - fullParams[1] = (byte) params.length; - System.arraycopy(params, 0, fullParams, 2, params.length); - - data.write(fullParams.length); - data.write(fullParams); - - // empty install token - data.write(0x00); - - return new APDUCommand(CLA, INS, P1, P2, data.toByteArray() ); - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java deleted file mode 100644 index 7e13622..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoad.java +++ /dev/null @@ -1,36 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; - -public class InstallForLoad { - public static final int CLA = 0x80; - public static final int INS = 0xE6; - public static final int P1 = 0x02; - public static final int P2 = 0; - - private byte[] aid; - private byte[] sdaid; - - public InstallForLoad(byte[] aid, byte[] sdaid) { - this.aid = aid; - this.sdaid = sdaid; - } - - public APDUCommand getCommand() throws IOException { - ByteArrayOutputStream data = new ByteArrayOutputStream(); - data.write(this.aid.length); - data.write(this.aid); - data.write(this.sdaid.length); - data.write(this.sdaid); - - // empty hash length and hash - data.write(0x00); - data.write(0x00); - data.write(0x00); - - return new APDUCommand(CLA, INS, P1, P2, data.toByteArray()); - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java deleted file mode 100644 index a14d85e..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Load.java +++ /dev/null @@ -1,135 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.HexUtils; - -public class Load { - public static final int CLA = 0x80; - public static final int INS = 0xE8; - - private static String[] fileNames = {"Header", "Directory", "Import", "Applet", - "Class", "Method", "StaticField", "Export", "ConstantPool", "RefLocation"}; - - private String path; - private int offset; - private int count; - private byte[] fullData; - - public Load(InputStream in) throws FileNotFoundException, IOException { - this.path = path; - this.offset = 0; - this.count = 0; - Map files = this.loadFiles(in); - in.close(); - this.fullData = this.getCode(files); - } - - public Map loadFiles(InputStream in) throws IOException { - Map files = new LinkedHashMap<>(); - ZipInputStream zip = new ZipInputStream(in); - ZipEntry entry = zip.getNextEntry(); - - while(entry != null) { - ByteArrayOutputStream data = new ByteArrayOutputStream(); - byte[] buf = new byte[1024]; - int count; - while ((count = zip.read(buf)) != -1) { - data.write(buf, 0, count); - } - String name = baseName(entry.getName()); - files.put(name, data.toByteArray()); - entry = zip.getNextEntry(); - } - - return files; - } - - private String baseName(String path) { - String[] parts = path.split("[/.]"); - return parts[parts.length - 2]; - } - - public APDUCommand getCommand() { - int blockSize = 247; // 255 - 8 bytes for MAC - if (this.offset >= this.fullData.length) { - return null; - } - - int rangeEnd = this.offset + blockSize; - if (rangeEnd >= this.fullData.length) { - rangeEnd = this.fullData.length; - } - - int size = rangeEnd - offset; - byte[] data = new byte[size]; - System.arraycopy(this.fullData, this.offset, data, 0, size); - - boolean isLast = this.offset + size >= this.fullData.length; - int p1 = isLast ? 0x80 : 0; - APDUCommand cmd = new APDUCommand(CLA, INS, p1, this.count, data); - - this.offset += size; - this.count++; - - return cmd; - } - - private byte[] encodeFullLength(int length) { - if (length < 0x80) { - return new byte[]{(byte) length}; - } else if (length < 0xFF) { - return new byte[]{(byte) 0x81, (byte) length}; - } else if (length < 0xFFFF) { - return new byte[]{ - (byte) 0x82, - (byte) ((length & 0xFF00) >> 8), - (byte) (length & 0xFF), - }; - } else { - return new byte[]{ - (byte) 0x83, - (byte) ((length & 0xFF0000) >> 16), - (byte) ((length & 0xFF00) >> 8), - (byte) (length & 0xFF), - }; - } - } - - public byte[] getCode(Map files) throws IOException { - ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); - - for (String name : fileNames) { - byte[] fileData = files.get(name); - if (fileData == null) { - continue; - } - - dataStream.write(fileData); - } - - byte[] data = dataStream.toByteArray(); - byte[] encodedFullLength = encodeFullLength(data.length); - byte[] fullData = new byte[1 + encodedFullLength.length + data.length]; - - fullData[0] = (byte) 0xC4; - System.arraycopy(encodedFullLength, 0, fullData, 1, encodedFullLength.length); - System.arraycopy(data, 0, fullData, 1 + encodedFullLength.length, data.length); - - return fullData; - } - - public int getCount() { - return count; - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java deleted file mode 100644 index 0cf52ec..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/SecureChannelSession.java +++ /dev/null @@ -1,444 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.*; -import org.spongycastle.crypto.engines.AESEngine; -import org.spongycastle.crypto.macs.CBCBlockCipherMac; -import org.spongycastle.crypto.params.KeyParameter; -import org.spongycastle.jce.ECNamedCurveTable; -import org.spongycastle.jce.interfaces.ECPublicKey; -import org.spongycastle.jce.spec.ECParameterSpec; -import org.spongycastle.jce.spec.ECPublicKeySpec; - -import javax.crypto.Cipher; -import javax.crypto.KeyAgreement; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -import java.io.IOException; -import java.security.*; -import java.util.Arrays; - -/** - * Handles a SecureChannel session with the card. - */ -public class SecureChannelSession { - public static final short SC_SECRET_LENGTH = 32; - public static final short SC_BLOCK_SIZE = 16; - - public static final byte INS_OPEN_SECURE_CHANNEL = 0x10; - public static final byte INS_MUTUALLY_AUTHENTICATE = 0x11; - public static final byte INS_PAIR = 0x12; - public static final byte INS_UNPAIR = 0x13; - - public static final byte PAIR_P1_FIRST_STEP = 0x00; - public static final byte PAIR_P1_LAST_STEP = 0x01; - - public static final int PAYLOAD_MAX_SIZE = 223; - - static final byte PAIRING_MAX_CLIENT_COUNT = 5; - - - private byte[] secret; - private byte[] publicKey; - private byte[] pairingKey; - private byte[] iv; - private byte pairingIndex; - private Cipher sessionCipher; - private CBCBlockCipherMac sessionMac; - private SecretKeySpec sessionEncKey; - private KeyParameter sessionMacKey; - private SecureRandom random; - private boolean open; - - /** - * Constructs a SecureChannel session on the client. The client should generate a fresh key pair for each session. - * The public key of the card is used as input for the EC-DH algorithm. The output is stored as the secret. - * - * @param keyData the public key returned by the applet as response to the SELECT command - */ - public SecureChannelSession(byte[] keyData) { - try { - random = new SecureRandom(); - ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1"); - KeyPairGenerator g = KeyPairGenerator.getInstance("ECDH"); - g.initialize(ecSpec, random); - - KeyPair keyPair = g.generateKeyPair(); - - publicKey = ((ECPublicKey) keyPair.getPublic()).getQ().getEncoded(false); - KeyAgreement keyAgreement = KeyAgreement.getInstance("ECDH"); - keyAgreement.init(keyPair.getPrivate()); - - ECPublicKeySpec cardKeySpec = new ECPublicKeySpec(ecSpec.getCurve().decodePoint(keyData), ecSpec); - ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA").generatePublic(cardKeySpec); - - keyAgreement.doPhase(cardKey, true); - secret = keyAgreement.generateSecret(); - - open = false; - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Returns the public key - * @return the public key - */ - public byte[] getPublicKey() { - return publicKey; - } - - /** - * Returns the pairing index - * @return the pairing index - */ - public byte getPairingIndex() { - return pairingIndex; - } - - /** - * Establishes a Secure Channel with the card. The command parameters are the public key generated in the first step. - * Follows the specifications from the SECURE_CHANNEL.md document. - * - * @param apduChannel the apdu channel - * @return the card response - * @throws IOException communication error - */ - public void autoOpenSecureChannel(CardChannel apduChannel) throws IOException { - APDUResponse response = openSecureChannel(apduChannel, pairingIndex, publicKey); - - if (response.getSw() != 0x9000) { - throw new IOException("OPEN SECURE CHANNEL failed"); - } - - processOpenSecureChannelResponse(response); - - response = mutuallyAuthenticate(apduChannel); - - if (response.getSw() != 0x9000) { - throw new IOException("MUTUALLY AUTHENTICATE failed"); - } - - if(!verifyMutuallyAuthenticateResponse(response)) { - throw new IOException("Invalid authentication data from the card"); - } - } - - /** - * Processes the response from OPEN SECURE CHANNEL. This initialize the session keys, Cipher and MAC internally. - * - * @param response the card response - */ - public void processOpenSecureChannelResponse(APDUResponse response) { - try { - MessageDigest md = MessageDigest.getInstance("SHA512"); - md.update(secret); - md.update(pairingKey); - byte[] data = response.getData(); - byte[] keyData = md.digest(Arrays.copyOf(data, SC_SECRET_LENGTH)); - iv = Arrays.copyOfRange(data, SC_SECRET_LENGTH, data.length); - - sessionEncKey = new SecretKeySpec(Arrays.copyOf(keyData, SC_SECRET_LENGTH), "AES"); - sessionMacKey = new KeyParameter(keyData, SC_SECRET_LENGTH, SC_SECRET_LENGTH); - sessionCipher = Cipher.getInstance("AES/CBC/ISO7816-4Padding"); - sessionMac = new CBCBlockCipherMac(new AESEngine(), 128, null); - open = true; - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Verify that the response from MUTUALLY AUTHENTICATE is correct. - * - * @param response the card response - * @return true if response is correct, false otherwise - */ - public boolean verifyMutuallyAuthenticateResponse(APDUResponse response) { - return response.getData().length == SC_SECRET_LENGTH; - } - - /** - * Handles the entire pairing procedure in order to be able to use the secure channel - * - * @param apduChannel the apdu channel - * @throws IOException communication error - */ - public void autoPair(CardChannel apduChannel, byte[] sharedSecret) throws IOException { - byte[] challenge = new byte[32]; - random.nextBytes(challenge); - APDUResponse resp = pair(apduChannel, PAIR_P1_FIRST_STEP, challenge); - - if (resp.getSw() != 0x9000) { - throw new IOException("Pairing failed on step 1"); - } - - byte[] respData = resp.getData(); - byte[] cardCryptogram = Arrays.copyOf(respData, 32); - byte[] cardChallenge = Arrays.copyOfRange(respData, 32, respData.length); - byte[] checkCryptogram; - - MessageDigest md; - - try { - md = MessageDigest.getInstance("SHA256"); - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - - md.update(sharedSecret); - checkCryptogram = md.digest(challenge); - - if (!Arrays.equals(checkCryptogram, cardCryptogram)) { - throw new IOException("Invalid card cryptogram"); - } - - md.update(sharedSecret); - checkCryptogram = md.digest(cardChallenge); - - resp = pair(apduChannel, PAIR_P1_LAST_STEP, checkCryptogram); - - if (resp.getSw() != 0x9000) { - throw new IOException("Pairing failed on step 2"); - } - - respData = resp.getData(); - md.update(sharedSecret); - pairingKey = md.digest(Arrays.copyOfRange(respData, 1, respData.length)); - pairingIndex = respData[0]; - } - - /** - * Unpairs the current paired key - * - * @param apduChannel the apdu channel - * @throws IOException communication error - */ - public void autoUnpair(CardChannel apduChannel) throws IOException { - APDUResponse resp = unpair(apduChannel, pairingIndex); - - if (resp.getSw() != 0x9000) { - throw new IOException("Unpairing failed"); - } - } - - /** - * Sends a OPEN SECURE CHANNEL APDU. - * - * @param apduChannel the apdu channel - * @param index the P1 parameter - * @param data the data - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse openSecureChannel(CardChannel apduChannel, byte index, byte[] data) throws IOException { - open = false; - APDUCommand openSecureChannel = new APDUCommand(0x80, INS_OPEN_SECURE_CHANNEL, index, 0, data); - return apduChannel.send(openSecureChannel); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. The data is generated automatically - * - * @param apduChannel the apdu channel - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse mutuallyAuthenticate(CardChannel apduChannel) throws IOException { - byte[] data = new byte[SC_SECRET_LENGTH]; - random.nextBytes(data); - - return mutuallyAuthenticate(apduChannel, data); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. - * - * @param apduChannel the apdu channel - * @param data the data - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse mutuallyAuthenticate(CardChannel apduChannel, byte[] data) throws IOException { - APDUCommand mutuallyAuthenticate = protectedCommand(0x80, INS_MUTUALLY_AUTHENTICATE, 0, 0, data); - return transmit(apduChannel, mutuallyAuthenticate); - } - - /** - * Sends a PAIR APDU. - * - * @param apduChannel the apdu channel - * @param p1 the P1 parameter - * @param data the data - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse pair(CardChannel apduChannel, byte p1, byte[] data) throws IOException { - APDUCommand openSecureChannel = new APDUCommand(0x80, INS_PAIR, p1, 0, data); - return transmit(apduChannel, openSecureChannel); - } - - /** - * Sends a UNPAIR APDU. - * - * @param apduChannel the apdu channel - * @param p1 the P1 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse unpair(CardChannel apduChannel, byte p1) throws IOException { - APDUCommand openSecureChannel = protectedCommand(0x80, INS_UNPAIR, p1, 0, new byte[0]); - return transmit(apduChannel, openSecureChannel); - } - - /** - * Unpair all other clients - * - * @param apduChannel the apdu channel - * @return the raw card response - * @throws IOException communication error - */ - public void unpairOthers(CardChannel apduChannel) throws IOException, APDUException { - for (int i = 0; i < PAIRING_MAX_CLIENT_COUNT; i++) { - if (i != pairingIndex) { - APDUCommand openSecureChannel = protectedCommand(0x80, INS_UNPAIR, i, 0, new byte[0]); - transmit(apduChannel, openSecureChannel).checkOK(); - } - } - } - - /** - * Encrypts the plaintext data using the session key. The maximum plaintext size is 223 bytes. The returned ciphertext - * already includes the IV and padding and can be sent as-is in the APDU payload. If the input is an empty byte array - * the returned data will still contain the IV and padding. - * - * @param data the plaintext data - * @return the encrypted data - */ - private byte[] encryptAPDU(byte[] data) { - assert data.length <= PAYLOAD_MAX_SIZE; - - try { - IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); - - sessionCipher.init(Cipher.ENCRYPT_MODE, sessionEncKey, ivParameterSpec); - return sessionCipher.doFinal(data); - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Decrypts the response from the card using the session key. The returned data is already stripped from IV and padding - * and can be potentially empty. - * - * @param data the ciphetext - * @return the plaintext - */ - private byte[] decryptAPDU(byte[] data) { - try { - IvParameterSpec ivParameterSpec = new IvParameterSpec(iv); - sessionCipher.init(Cipher.DECRYPT_MODE, sessionEncKey, ivParameterSpec); - return sessionCipher.doFinal(data); - } catch(Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } - - /** - * Returns a command APDU with MAC and encrypted data. - * - * @param cla the CLA byte - * @param ins the INS byte - * @param p1 the P1 byte - * @param p2 the P2 byte - * @param data the data, can be an empty array but not null - * @return the command APDU - */ - public APDUCommand protectedCommand(int cla, int ins, int p1, int p2, byte[] data) { - byte[] finalData; - - if (open) { - data = encryptAPDU(data); - byte[] meta = new byte[]{(byte) cla, (byte) ins, (byte) p1, (byte) p2, (byte) (data.length + SC_BLOCK_SIZE), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - updateIV(meta, data); - - finalData = Arrays.copyOf(iv, iv.length + data.length); - System.arraycopy(data, 0, finalData, iv.length, data.length); - } else { - finalData = data; - } - - return new APDUCommand(cla, ins, p1, p2, finalData); - } - - /** - * Transmits a protected command APDU and unwraps the response data. The MAC is verified, the data decrypted and the - * SW read from the payload. - * - * @param apduChannel the APDU channel - * @param apdu the APDU to send - * @return the unwrapped response APDU - * @throws IOException transmission error - */ - public APDUResponse transmit(CardChannel apduChannel, APDUCommand apdu) throws IOException { - APDUResponse resp = apduChannel.send(apdu); - - if (resp.getSw() == 0x6982) { - open = false; - } - - if (open) { - byte[] data = resp.getData(); - byte[] meta = new byte[]{(byte) data.length, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - byte[] mac = Arrays.copyOf(data, iv.length); - data = Arrays.copyOfRange(data, iv.length, data.length); - - byte[] plainData = decryptAPDU(data); - - updateIV(meta, data); - - if (!Arrays.equals(iv, mac)) { - throw new IOException("Invalid MAC"); - } - - return new APDUResponse(plainData); - } else { - return resp; - } - } - - /** - * Marks the SecureChannel as closed - */ - public void reset() { - open = false; - } - - /** - * Marks the SecureChannel as open. Only to be used when writing tests for the SecureChannel, in normal operation this - * would only make things wrong. - * - */ - void setOpen() { - open = true; - } - - /** - * Calculates a CMAC from the metadata and data provided and sets it as the IV for the next message. - * - * @param meta metadata - * @param data data - */ - private void updateIV(byte[] meta, byte[] data) { - try { - sessionMac.init(sessionMacKey); - sessionMac.update(meta, 0, meta.length); - sessionMac.update(data, 0, data.length); - sessionMac.doFinal(iv, 0); - } catch (Exception e) { - throw new RuntimeException("Is BouncyCastle in the classpath?", e); - } - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java deleted file mode 100644 index f977d04..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Select.java +++ /dev/null @@ -1,20 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; - -public class Select { - private static final int CLA = 0x00; - private static final int INS = 0xA4; - private static final int P1 = 0x04; - private static final int P2 = 0x00; // first occurrence - - private byte[] aid; - - public Select(byte[] aid) { - this.aid = aid; - } - - public APDUCommand getCommand() { - return new APDUCommand(CLA, INS, P1, P2, this.aid); - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java deleted file mode 100644 index d2e59dc..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/Status.java +++ /dev/null @@ -1,26 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; - -public class Status { - private static final int CLA = 0x80; - private static final int INS = 0xF2; - - public static final int P1_ISSUER_SECURITY_DOMAIN = 0x80; - public static final int P1_APPLICATIONS = 0x40; - public static final int P1_EXECUTABLE_LOAD_FILES = 0x20; - public static final int P1_EXECUTABLE_LOAD_FILES_AND_MODULES = 0x10; - - private static final int P2 = 0x02; - - private int p1; - - public Status(int p1) { - this.p1 = p1; - } - - public APDUCommand getCommand() { - byte[] data = new byte[]{0x4F, 0x00}; - return new APDUCommand(CLA, INS, this.p1, P2, data, true); - } -} diff --git a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java b/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java deleted file mode 100644 index a345b4b..0000000 --- a/app/src/main/java/im/status/applet_installer_test/appletinstaller/apducommands/WalletAppletCommandSet.java +++ /dev/null @@ -1,441 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUException; -import im.status.applet_installer_test.appletinstaller.APDUResponse; -import im.status.applet_installer_test.appletinstaller.CardChannel; -import org.spongycastle.jce.interfaces.ECPrivateKey; -import org.spongycastle.jce.interfaces.ECPublicKey; -import org.spongycastle.util.encoders.Hex; - -import java.io.IOException; -import java.security.KeyPair; -import java.security.PrivateKey; - -/** - * This class is used to send APDU to the applet. Each method corresponds to an APDU as defined in the APPLICATION.md - * file. Some APDUs map to multiple methods for the sake of convenience since their payload or response require some - * pre/post processing. - */ -public class WalletAppletCommandSet { - static final byte INS_GET_STATUS = (byte) 0xF2; - static final byte INS_VERIFY_PIN = (byte) 0x20; - static final byte INS_CHANGE_PIN = (byte) 0x21; - static final byte INS_UNBLOCK_PIN = (byte) 0x22; - static final byte INS_LOAD_KEY = (byte) 0xD0; - static final byte INS_DERIVE_KEY = (byte) 0xD1; - static final byte INS_GENERATE_MNEMONIC = (byte) 0xD2; - static final byte INS_SIGN = (byte) 0xC0; - static final byte INS_SET_PINLESS_PATH = (byte) 0xC1; - static final byte INS_EXPORT_KEY = (byte) 0xC2; - - static final byte GET_STATUS_P1_APPLICATION = 0x00; - - static final byte LOAD_KEY_P1_EC = 0x01; - static final byte LOAD_KEY_P1_EXT_EC = 0x02; - static final byte LOAD_KEY_P1_SEED = 0x03; - - static final byte DERIVE_P1_ASSISTED_MASK = 0x01; - static final byte DERIVE_P1_SOURCE_MASTER = (byte) 0x00; - - static final byte DERIVE_P2_KEY_PATH = 0x00; - static final byte DERIVE_P2_PUBLIC_KEY = 0x01; - - static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00; - static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01; - - static final byte TLV_PUB_KEY = (byte) 0x80; - static final byte TLV_PRIV_KEY = (byte) 0x81; - static final byte TLV_CHAIN_CODE = (byte) 0x82; - - public static final String APPLET_AID = "53746174757357616C6C6574417070"; - public static final byte[] APPLET_AID_BYTES = Hex.decode(APPLET_AID); - - private final CardChannel apduChannel; - private SecureChannelSession secureChannel; - - public WalletAppletCommandSet(CardChannel apduChannel) { - this.apduChannel = apduChannel; - } - - public void setSecureChannel(SecureChannelSession secureChannel) { - this.secureChannel = secureChannel; - } - - /** - * Selects the applet. The applet is assumed to have been installed with its default AID. The returned data is a - * public key which must be used to initialize the secure channel. - * - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse select() throws IOException { - if (secureChannel != null) { - secureChannel.reset(); - } - - APDUCommand selectApplet = new APDUCommand(0x00, 0xA4, 4, 0, APPLET_AID_BYTES); - return apduChannel.send(selectApplet); - } - - /** - * Opens the secure channel. Calls the corresponding method of the SecureChannel class. - * - * @return the raw card response - * @throws IOException communication error - */ - public void autoOpenSecureChannel() throws IOException { - secureChannel.autoOpenSecureChannel(apduChannel); - } - - /** - * Automatically pairs. Calls the corresponding method of the SecureChannel class. - * - * @throws IOException communication error - */ - public void autoPair(byte[] sharedSecret) throws IOException { - secureChannel.autoPair(apduChannel, sharedSecret); - } - - /** - * Automatically unpairs. Calls the corresponding method of the SecureChannel class. - * - * @throws IOException communication error - */ - public void autoUnpair() throws IOException { - secureChannel.autoUnpair(apduChannel); - } - - /** - * Sends a OPEN SECURE CHANNEL APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse openSecureChannel(byte index, byte[] data) throws IOException { - return secureChannel.openSecureChannel(apduChannel, index, data); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse mutuallyAuthenticate() throws IOException { - return secureChannel.mutuallyAuthenticate(apduChannel); - } - - /** - * Sends a MUTUALLY AUTHENTICATE APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse mutuallyAuthenticate(byte[] data) throws IOException { - return secureChannel.mutuallyAuthenticate(apduChannel, data); - } - - /** - * Sends a PAIR APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse pair(byte p1, byte[] data) throws IOException { - return secureChannel.pair(apduChannel, p1, data); - } - - /** - * Sends a UNPAIR APDU. Calls the corresponding method of the SecureChannel class. - */ - public APDUResponse unpair(byte p1) throws IOException { - return secureChannel.unpair(apduChannel, p1); - } - - /** - * Unpair all other clients. - */ - public void unpairOthers() throws IOException, APDUException { - secureChannel.unpairOthers(apduChannel); - } - - /** - * Sends a GET STATUS APDU. The info byte is the P1 parameter of the command, valid constants are defined in the applet - * class itself. - * - * @param info the P1 of the APDU - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse getStatus(byte info) throws IOException { - APDUCommand getStatus = secureChannel.protectedCommand(0x80, INS_GET_STATUS, info, 0, new byte[0]); - return secureChannel.transmit(apduChannel, getStatus); - } - - /** - * Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating public key - * derivation support. - * - * @return whether public key derivation is supported or not - * @throws IOException communication error - */ - public boolean getPublicKeyDerivationSupport() throws IOException { - APDUResponse resp = getStatus(GET_STATUS_P1_APPLICATION); - byte[] data = resp.getData(); - return data[data.length - 1] != 0x00; - } - - /** - * Sends a VERIFY PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU - * data. - * - * @param pin the pin - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse verifyPIN(String pin) throws IOException { - APDUCommand verifyPIN = secureChannel.protectedCommand(0x80, INS_VERIFY_PIN, 0, 0, pin.getBytes()); - return secureChannel.transmit(apduChannel, verifyPIN); - } - - /** - * Sends a CHANGE PIN APDU. The raw bytes of the given string are encrypted using the secure channel and used as APDU - * data. - * - * @param pin the new PIN - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse changePIN(String pin) throws IOException { - APDUCommand changePIN = secureChannel.protectedCommand(0x80, INS_CHANGE_PIN, 0, 0, pin.getBytes()); - return secureChannel.transmit(apduChannel, changePIN); - } - - /** - * Sends an UNBLOCK PIN APDU. The PUK and PIN are concatenated and the raw bytes are encrypted using the secure - * channel and used as APDU data. - * - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse unblockPIN(String puk, String newPin) throws IOException { - APDUCommand unblockPIN = secureChannel.protectedCommand(0x80, INS_UNBLOCK_PIN, 0, 0, (puk + newPin).getBytes()); - return secureChannel.transmit(apduChannel, unblockPIN); - } - - /** - * Sends a LOAD KEY APDU. The given private key and chain code are formatted as a raw binary seed and the P1 of - * the command is set to LOAD_KEY_P1_SEED (0x03). This works on cards which support public key derivation. - * The loaded keyset is extended and support further key derivation. - * - * @param aPrivate a private key - * @param chainCode the chain code - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(PrivateKey aPrivate, byte[] chainCode) throws IOException { - byte[] privateKey = ((ECPrivateKey) aPrivate).getD().toByteArray(); - - int privLen = privateKey.length; - int privOff = 0; - - if(privateKey[0] == 0x00) { - privOff++; - privLen--; - } - - byte[] data = new byte[chainCode.length + privLen]; - System.arraycopy(privateKey, privOff, data, 0, privLen); - System.arraycopy(chainCode, 0, data, privLen, chainCode.length); - - return loadKey(data, LOAD_KEY_P1_SEED); - } - - /** - * Sends a LOAD KEY APDU. The key is sent in TLV format, includes the public key and no chain code, meaning that - * the card will not be able to do further key derivation. - * - * @param ecKeyPair a key pair - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(KeyPair ecKeyPair) throws IOException { - return loadKey(ecKeyPair, false, null); - } - - /** - * Sends a LOAD KEY APDU. The key is sent in TLV format. The public key is included or not depending on the value - * of the omitPublicKey parameter. The chain code is included if the chainCode is not null. P1 is set automatically - * to either LOAD_KEY_P1_EC or LOAD_KEY_P1_EXT_EC depending on the presence of the chainCode. - * - * @param keyPair a key pair - * @param omitPublicKey whether the public key is sent or not - * @param chainCode the chain code - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(KeyPair keyPair, boolean omitPublicKey, byte[] chainCode) throws IOException { - byte[] publicKey = omitPublicKey ? null : ((ECPublicKey) keyPair.getPublic()).getQ().getEncoded(false); - byte[] privateKey = ((ECPrivateKey) keyPair.getPrivate()).getD().toByteArray(); - - return loadKey(publicKey, privateKey, chainCode); - } - - /** - * Sends a LOAD KEY APDU. The key is sent in TLV format. The public key is included if not null. The chain code is - * included if not null. P1 is set automatically to either LOAD_KEY_P1_EC or - * LOAD_KEY_P1_EXT_EC depending on the presence of the chainCode. - * - * @param publicKey a raw public key - * @param privateKey a raw private key - * @param chainCode the chain code - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(byte[] publicKey, byte[] privateKey, byte[] chainCode) throws IOException { - int privLen = privateKey.length; - int privOff = 0; - - if(privateKey[0] == 0x00) { - privOff++; - privLen--; - } - - int off = 0; - int totalLength = publicKey == null ? 0 : (publicKey.length + 2); - totalLength += (privLen + 2); - totalLength += chainCode == null ? 0 : (chainCode.length + 2); - - if (totalLength > 127) { - totalLength += 3; - } else { - totalLength += 2; - } - - byte[] data = new byte[totalLength]; - data[off++] = (byte) 0xA1; - - if (totalLength > 127) { - data[off++] = (byte) 0x81; - data[off++] = (byte) (totalLength - 3); - } else { - data[off++] = (byte) (totalLength - 2); - } - - if (publicKey != null) { - data[off++] = TLV_PUB_KEY; - data[off++] = (byte) publicKey.length; - System.arraycopy(publicKey, 0, data, off, publicKey.length); - off += publicKey.length; - } - - data[off++] = TLV_PRIV_KEY; - data[off++] = (byte) privLen; - System.arraycopy(privateKey, privOff, data, off, privLen); - off += privLen; - - byte p1; - - if (chainCode != null) { - p1 = LOAD_KEY_P1_EXT_EC; - data[off++] = (byte) TLV_CHAIN_CODE; - data[off++] = (byte) chainCode.length; - System.arraycopy(chainCode, 0, data, off, chainCode.length); - } else { - p1 = LOAD_KEY_P1_EC; - } - - return loadKey(data, p1); - } - - /** - * Sends a LOAD KEY APDU. The data is encrypted and sent as-is. The keyType parameter is used as P1. - * - * @param data key data - * @param keyType the P1 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse loadKey(byte[] data, byte keyType) throws IOException { - APDUCommand loadKey = secureChannel.protectedCommand(0x80, INS_LOAD_KEY, keyType, 0, data); - return secureChannel.transmit(apduChannel, loadKey); - } - - /** - * Sends a GENERATE MNEMONIC APDU. The cs parameter is the length of the checksum and is used as P1. - * - * @param cs the P1 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse generateMnemonic(int cs) throws IOException { - APDUCommand generateMnemonic = secureChannel.protectedCommand(0x80, INS_GENERATE_MNEMONIC, cs, 0, new byte[0]); - return secureChannel.transmit(apduChannel, generateMnemonic); - } - - /** - * Sends a SIGN APDU. The dataType is P1 as defined in the applet. The isFirst and isLast arguments are used to form - * the P2 parameter. The data is the data to sign, or part of it. Only when sending the last block a signature is - * generated and thus returned. When signing a precomputed hash it must be done in a single block, so isFirst and - * isLast will always be true at the same time. - * - * @param data the data to sign - * @param dataType the P1 parameter - * @param isFirst whether this is the first block of the command or not - * @param isLast whether this is the last block of the command or not - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse sign(byte[] data, byte dataType, boolean isFirst, boolean isLast) throws IOException { - byte p2 = (byte) ((isFirst ? 0x01 : 0x00) | (isLast ? 0x80 : 0x00)); - APDUCommand sign = secureChannel.protectedCommand(0x80, INS_SIGN, dataType, p2, data); - return secureChannel.transmit(apduChannel, sign); - } - - /** - * Sends a DERIVE KEY APDU. The data is encrypted and sent as-is. The P1 and P2 parameters are forced to 0, meaning - * that the derivation starts from the master key and is non-assisted. - * - * @param data the raw key path - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse deriveKey(byte[] data) throws IOException { - return deriveKey(data, DERIVE_P1_SOURCE_MASTER, false, false); - } - - /** - * Sends a DERIVE KEY APDU. The data is encrypted and sent as-is. The reset and assisted parameters are combined to - * form P1. The isPublicKey parameter is used for P2. - * - * @param data the raw key path or a public key - * @param source the source to start derivation - * @param assisted whether we are doing assisted derivation or not - * @param isPublicKey whether we are sending a public key or a key path (only make sense during assisted derivation) - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse deriveKey(byte[] data, int source, boolean assisted, boolean isPublicKey) throws IOException { - byte p1 = assisted ? DERIVE_P1_ASSISTED_MASK : 0; - p1 |= source; - byte p2 = isPublicKey ? DERIVE_P2_PUBLIC_KEY : DERIVE_P2_KEY_PATH; - - APDUCommand deriveKey = secureChannel.protectedCommand(0x80, INS_DERIVE_KEY, p1, p2, data); - return secureChannel.transmit(apduChannel, deriveKey); - } - - /** - * Sends a SET PINLESS PATH APDU. The data is encrypted and sent as-is. - * - * @param data the raw key path - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse setPinlessPath(byte [] data) throws IOException { - APDUCommand setPinlessPath = secureChannel.protectedCommand(0x80, INS_SET_PINLESS_PATH, 0x00, 0x00, data); - return secureChannel.transmit(apduChannel, setPinlessPath); - } - - /** - * Sends an EXPORT KEY APDU. The keyPathIndex is used as P1. Valid values are defined in the applet itself - * - * @param keyPathIndex the P1 parameter - * @param publicOnly the P2 parameter - * @return the raw card response - * @throws IOException communication error - */ - public APDUResponse exportKey(byte keyPathIndex, boolean publicOnly) throws IOException { - byte p2 = publicOnly ? EXPORT_KEY_P2_PUBLIC_ONLY : EXPORT_KEY_P2_PRIVATE_AND_PUBLIC; - APDUCommand exportKey = secureChannel.protectedCommand(0x80, INS_EXPORT_KEY, keyPathIndex, p2, new byte[0]); - return secureChannel.transmit(apduChannel, exportKey); - } -} diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticateTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticateTest.java deleted file mode 100644 index bc0919c..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/ExternalAuthenticateTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.HexUtils; - -import static org.junit.Assert.*; - -public class ExternalAuthenticateTest { - @Test - public void getHostCryptogram() { - byte[] encKeyData = HexUtils.hexStringToByteArray("0EF72A1065236DD6CAC718D5E3F379A4"); - byte[] cardChallenge = HexUtils.hexStringToByteArray("0076a6c0d55e9535"); - byte[] hostChallenge = HexUtils.hexStringToByteArray("266195e638da1b95"); - - ExternalAuthenticate auth = new ExternalAuthenticate(encKeyData, cardChallenge, hostChallenge); - - String expectedHostCryptogram = "45A5F48DAE68203C"; - byte[] hostCryptogram = auth.getHostCryptogram(); - assertEquals(expectedHostCryptogram, HexUtils.byteArrayToHexString(hostCryptogram)); - } - - @Test - public void getCommand() throws IOException { - byte[] encKeyData = HexUtils.hexStringToByteArray("8D289AFE0AB9C45B1C76DEEA182966F4"); - byte[] cardChallenge = HexUtils.hexStringToByteArray("000f3fd65d4d6e45"); - byte[] hostChallenge = HexUtils.hexStringToByteArray("cf307b6719bf224d"); - - ExternalAuthenticate auth = new ExternalAuthenticate(encKeyData, cardChallenge, hostChallenge); - - String expectedAPDU = "84820100087702AC6CE46A47F0"; - byte[] apdu = auth.getCommand().serialize(); - assertEquals(expectedAPDU, HexUtils.byteArrayToHexString(apdu)); - } -} - diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java deleted file mode 100644 index 8bf37d6..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InitializeUpdateTest.java +++ /dev/null @@ -1,62 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUException; -import im.status.applet_installer_test.appletinstaller.APDUResponse; -import im.status.applet_installer_test.appletinstaller.HexUtils; -import im.status.applet_installer_test.appletinstaller.Keys; - -import static org.junit.Assert.*; - -public class InitializeUpdateTest { - @Test - public void getCommand() throws IOException { - byte[] challenge = HexUtils.hexStringToByteArray("2d315d5ffc616d10"); - InitializeUpdate init = new InitializeUpdate(challenge); - APDUCommand cmd = init.getCommand(); - - assertEquals(0x80, cmd.getCla()); - assertEquals(0x50, cmd.getIns()); - assertEquals(0, cmd.getP1()); - assertEquals(0, cmd.getP2()); - assertEquals(challenge, cmd.getData()); - - String expectedAPDU = "80500000082D315D5FFC616D1000"; - byte[] apdu = cmd.serialize(); - assertEquals(expectedAPDU, HexUtils.byteArrayToHexString(apdu)); - } - - @Test - public void validateResponse_BadResponse() throws APDUException { - byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002003b5e508f751c0af3016e3fbc23d3a66982"); - APDUResponse resp = new APDUResponse(apdu); - - byte[] challenge = InitializeUpdate.generateChallenge(); - InitializeUpdate init = new InitializeUpdate(challenge); - - try { - init.verifyResponse(new Keys(new byte[]{}, new byte[]{}), resp); - fail("expected APDUException to be thrown"); - } catch (APDUException e) { - assertEquals(0x6982, e.sw); - } - } - - //TODO: reimplement test - //@Test - //public void validateResponse_GoodResponse() throws APDUException { - // byte[] encKey = HexUtils.hexStringToByteArray("16B5867FF50BE7239C2BF1245B83A362"); - - // byte[] challenge = HexUtils.hexStringToByteArray("f0467f908e5ca23f"); - // InitializeUpdate init = new InitializeUpdate(challenge); - - // byte[] apdu = HexUtils.hexStringToByteArray("000002650183039536622002000de9c62ba1c4c8e55fcb91b6654ce49000"); - // APDUResponse resp = new APDUResponse(apdu); - - // init.verifyResponse(new Keys(), resp); - //} -} \ No newline at end of file diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java deleted file mode 100644 index 47b0f02..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForInstallTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUCommandTest; -import im.status.applet_installer_test.appletinstaller.HexUtils; -import static org.junit.Assert.*; - -public class InstallForInstallTest { - @Test - public void forInstall() throws IOException { - byte[] packageAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574"); - byte[] appletAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070"); - byte[] instanceAID = HexUtils.hexStringToByteArray("53746174757357616C6C6574417070"); - byte[] params = HexUtils.hexStringToByteArray("AABBCC"); - InstallForInstall install = new InstallForInstall(packageAID, appletAID, instanceAID, params); - APDUCommand cmd = install.getCommand(); - byte[] apdu = cmd.serialize(); - - String expected = "80E60C00360C53746174757357616C6C65740F53746174757357616C6C65744170700F53746174757357616C6C6574417070010005C903AABBCC00"; - assertEquals(expected, HexUtils.byteArrayToHexString(apdu)); - } -} \ No newline at end of file diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java deleted file mode 100644 index 46751a9..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/InstallForLoadTest.java +++ /dev/null @@ -1,24 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.HexUtils; - -import static org.junit.Assert.*; - -public class InstallForLoadTest { - @Test - public void forLoad() throws IOException { - byte[] aid = HexUtils.hexStringToByteArray("53746174757357616C6C6574"); - byte[] sdaid = HexUtils.hexStringToByteArray("A000000151000000"); - InstallForLoad install = new InstallForLoad(aid, sdaid); - APDUCommand cmd = install.getCommand(); - byte[] apdu = cmd.serialize(); - - String expected = "80E60200190C53746174757357616C6C657408A000000151000000000000"; - assertEquals(expected, HexUtils.byteArrayToHexString(apdu)); - } -} \ No newline at end of file diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java deleted file mode 100644 index 507ff0f..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/LoadTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; -import java.net.URL; -import java.util.ArrayList; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.HexUtils; - -import static org.junit.Assert.*; - -public class LoadTest { - - @Test - public void getCommand() throws IOException { - URL url = this.getClass().getClassLoader().getResource("wallet.cap"); - Load load = new Load(url.getPath()); - - ArrayList commands = new ArrayList(); - APDUCommand cmd; - while((cmd = load.getCommand()) != null) { - commands.add(cmd); - } - - assertEquals(31, commands.size()); - - // Command 1 - cmd = commands.get(0); - assertEquals(0, cmd.getP1()); - assertEquals(0, cmd.getP2()); - String expectedData = "C4821D74010027DECAFFED02020401010C53746174757357616C6C657410696D2F7374617475732F77616C6C6574020021002700210013002902B600401581015D0301000006EB372C0024000A013504010004002904000107A0000000620001050107A0000000620102050107A0000000620101050107A0000000620201030013010F53746174757357616C6C65744170700937060040000000800000FF000100000000800000FF00010000000080000D000A010A000004EB05A005F307140739080C08D608EB0908090D008203160010070100000A7B07158106005A801A0076003003808009038B00300457800904620030051F8010"; - byte[] data = cmd.getData(); - assertEquals(expectedData, HexUtils.byteArrayToHexString(data)); - - // Command 2 - cmd = commands.get(1); - assertEquals(0, cmd.getP1()); - assertEquals(1, cmd.getP2()); - expectedData = "053100440A9380AB0B4000750EBF80930F5400300110188C002F7A0302058D00327F003307038D00347F003506038D00377F00381006038D00347F003B032F101B038D00407F004110141020038D0042940000437F005E700B2C017F00411100802F10651C41048D005F7F00607A0861032906181D2510805310806B1E7B00601606590601033816061A7B006016068E03006113412906702B1B7B0060038E03006213044329067B0060037B00601606250453600506700305381606054704412906181D7B00601606078D006329061504160510207B00600316067B006016068D00647B006016067B0065038D0066620403781A7B0060"; - data = cmd.getData(); - assertEquals(expectedData, HexUtils.byteArrayToHexString(data)); - - // Last command - cmd = commands.get(30); - assertEquals(0x80, cmd.getP1()); - assertEquals(30, cmd.getP2()); - expectedData = "080707080C08090A04050A0A06070706081A07085029031512201209190C0B0B0503060D09030E0B0C0C080A0A0603070706104622070914240A1611130D080B0F0A0D10110905040B2E271205030E0D0D0D0D0807140808030E1E10050321032C2A070606080D140C2723180B081D0A0707060811030D0407070608201B07091408252C2E39"; - data = cmd.getData(); - assertEquals(expectedData, HexUtils.byteArrayToHexString(data)); - } -} \ No newline at end of file diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java deleted file mode 100644 index 479a053..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/SelectTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.HexUtils; - -import static org.junit.Assert.*; - -public class SelectTest { - @Test - public void getCode() throws IOException { - Select s = new Select(new byte[0]); - byte[] apdu = s.getCommand().serialize(); - String expected = "00A4040000"; - assertEquals(expected, HexUtils.byteArrayToHexString(apdu)); - } -} \ No newline at end of file diff --git a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java b/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java deleted file mode 100644 index d402123..0000000 --- a/app/src/test/java/im/status/applet_installer_test/appletinstaller/apducommands/StatusTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package im.status.applet_installer_test.appletinstaller.apducommands; - -import org.junit.Test; - -import java.io.IOException; - -import im.status.applet_installer_test.appletinstaller.APDUCommand; -import im.status.applet_installer_test.appletinstaller.APDUWrapper; -import im.status.applet_installer_test.appletinstaller.HexUtils; - -import static org.junit.Assert.*; - -public class StatusTest { - @Test - public void getCommand() throws IOException { - Status status = new Status(Status.P1_ISSUER_SECURITY_DOMAIN); - String expectedAPDU = "80F28002024F0000"; - byte[] apdu = status.getCommand().serialize(); - assertEquals(expectedAPDU, HexUtils.byteArrayToHexString(apdu)); - } -} \ No newline at end of file