From a4ff736d6e820d0f4baea7358c0520caba118dca Mon Sep 17 00:00:00 2001 From: Bitgamma Date: Thu, 4 Apr 2019 10:20:38 +0300 Subject: [PATCH] Keycard v2.2 (#15) * add methods for the extended SIGN command * add resetPinlessPath --- README.md | 4 +- .../keycard/applet/KeycardCommandSet.java | 84 ++++++++++++++++++- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 67f1d8f..2cdbbcb 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can import the SDK in your Gradle or Maven project using [Jitpack.io](https: ```groovy dependencies { - implementation 'com.github.status-im.status-keycard-java:android:2.1.1' + implementation 'com.github.status-im.status-keycard-java:android:2.2.0' } ``` @@ -23,6 +23,6 @@ dependencies { ```groovy dependencies { - implementation 'com.github.status-im.status-keycard-java:desktop:2.1.1' + implementation 'com.github.status-im.status-keycard-java:desktop:2.2.0' } ``` \ No newline at end of file diff --git a/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java b/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java index 04e74b0..b421080 100644 --- a/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java +++ b/lib/src/main/java/im/status/keycard/applet/KeycardCommandSet.java @@ -53,6 +53,11 @@ public class KeycardCommandSet { static final byte DUPLICATE_KEY_P1_EXPORT = 0x02; static final byte DUPLICATE_KEY_P1_IMPORT = 0x03; + static final byte SIGN_P1_CURRENT_KEY = 0x00; + static final byte SIGN_P1_DERIVE = 0x01; + static final byte SIGN_P1_DERIVE_AND_MAKE_CURRENT = 0x02; + static final byte SIGN_P1_PINLESS = 0x03; + public static final int GENERATE_MNEMONIC_12_WORDS = 0x04; public static final int GENERATE_MNEMONIC_15_WORDS = 0x05; public static final int GENERATE_MNEMONIC_18_WORDS = 0x06; @@ -533,14 +538,58 @@ public class KeycardCommandSet { } /** - * Sends a SIGN APDU. This signs a precomputed hash so the input must be exactly 32-bytes long. + * Sends a SIGN APDU. This signs a precomputed hash that must be exactly 32-bytes long. * + * @param hash the hash to sign + * @return the raw card response + * @throws IOException communication error + */ + public APDUResponse sign(byte[] hash) throws IOException { + return sign(hash, SIGN_P1_CURRENT_KEY); + } + + /** + * Sends a SIGN APDU. This signs a precomputed hash that must be exactly 32-bytes long. The key used to sign is given + * as a parameter. + * + * @param hash the hash to sign + * @params path the path of the key to use + * @param makeCurrent ture if the key used to sign should become the current key, false otherwise + * @return the raw card response + * @throws IOException communication error + */ + public APDUResponse signWithPath(byte[] hash, String path, boolean makeCurrent) throws IOException { + KeyPath keyPath = new KeyPath(path); + byte[] pathData = keyPath.getData(); + byte[] data = Arrays.copyOf(hash, hash.length + pathData.length); + System.arraycopy(pathData, 0, data, hash.length, pathData.length); + return sign(data, keyPath.getSource() | (makeCurrent ? SIGN_P1_DERIVE_AND_MAKE_CURRENT : SIGN_P1_DERIVE)); + } + + /** + * Sends a SIGN APDU. This signs a precomputed hash that must be exactly 32-bytes long. The pinless path will be used + * to sign. This command is the only variant of SIGN which can also be executed without a Secure Channel. + * + * @param hash the hash to sign + * @return the raw card response + * @throws IOException communication error + */ + public APDUResponse signPinless(byte[] hash) throws IOException { + return sign(hash, SIGN_P1_PINLESS); + } + + + /** + * Sends a SIGN APDU. This signs a precomputed hash so the input must be exactly 32-bytes long, eventually followed by + * a derivation path. + * + * @param p1 the p1 parameter * @param data the data to sign * @return the raw card response * @throws IOException communication error */ - public APDUResponse sign(byte[] data) throws IOException { - APDUCommand sign = secureChannel.protectedCommand(0x80, INS_SIGN, 0x00, 0x00, data); + public APDUResponse sign(byte[] data, int p1) throws IOException { + APDUCommand sign = secureChannel.protectedCommand(0x80, INS_SIGN, p1, 0x00, data); return secureChannel.transmit(apduChannel, sign); } @@ -581,6 +630,33 @@ public class KeycardCommandSet { return secureChannel.transmit(apduChannel, deriveKey); } + /** + * Sends a SET PINLESS PATH APDU. The path must be absolute, that is starting from the master key. + + * @param path the path. Must be an absolute path (i.e: starting from the master key) + * @return the raw card response + * @throws IOException communication error + */ + public APDUResponse setPinlessPath(String path) throws IOException { + KeyPath keyPath = new KeyPath(path); + if (keyPath.getSource() != DERIVE_P1_SOURCE_MASTER) { + throw new IllegalArgumentException("Only absolute paths can be set as PINLESS path"); + } + + return setPinlessPath(keyPath.getData()); + } + + /** + * Sends an empty SET PINLESS PATH APDU, resetting it. After this command the card does not have a PINless path until + * a new one is set. + * + * @return the raw card response + * @throws IOException communication error + */ + public APDUResponse resetPinlessPath() throws IOException { + return setPinlessPath(new byte[]{}); + } + /** * Sends a SET PINLESS PATH APDU. The data is encrypted and sent as-is. * @@ -588,7 +664,7 @@ public class KeycardCommandSet { * @return the raw card response * @throws IOException communication error */ - public APDUResponse setPinlessPath(byte [] data) throws IOException { + public APDUResponse setPinlessPath(byte[] data) throws IOException { APDUCommand setPinlessPath = secureChannel.protectedCommand(0x80, INS_SET_PINLESS_PATH, 0x00, 0x00, data); return secureChannel.transmit(apduChannel, setPinlessPath); }