Skip to content

Commit

Permalink
allow export of extended public key
Browse files Browse the repository at this point in the history
  • Loading branch information
bitgamma committed Nov 3, 2022
1 parent e6dc3b8 commit f3e8342
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 9 deletions.
22 changes: 21 additions & 1 deletion src/main/java/im/status/keycard/KeycardApplet.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ public class KeycardApplet extends Applet {

static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
static final byte EXPORT_KEY_P2_EXTENDED_PUBLIC = 0x02;

static final byte STORE_DATA_P1_PUBLIC = 0x00;
static final byte STORE_DATA_P1_NDEF = 0x01;
Expand Down Expand Up @@ -1140,14 +1141,21 @@ private void exportKey(APDU apdu) {
}

boolean publicOnly;
boolean extendedPublic;

switch (apduBuffer[ISO7816.OFFSET_P2]) {
case EXPORT_KEY_P2_PRIVATE_AND_PUBLIC:
publicOnly = false;
extendedPublic = false;
break;
case EXPORT_KEY_P2_PUBLIC_ONLY:
publicOnly = true;
extendedPublic = false;
break;
case EXPORT_KEY_P2_EXTENDED_PUBLIC:
publicOnly = true;
extendedPublic = true;
break;
default:
ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
return;
Expand All @@ -1172,7 +1180,9 @@ private void exportKey(APDU apdu) {

updateDerivationPath(apduBuffer, (short) 0, dataLen, derivationSource);

if (!publicOnly && ((tmpPathLen < (short)(((short) EIP_1581_PREFIX.length) + 8)) || (Util.arrayCompare(EIP_1581_PREFIX, (short) 0, tmpPath, (short) 0, (short) EIP_1581_PREFIX.length) != 0))) {
boolean eip1581 = isEIP1581();

if (!(publicOnly || eip1581) || (extendedPublic && eip1581)) {
ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
}

Expand All @@ -1189,6 +1199,12 @@ private void exportKey(APDU apdu) {
apduBuffer[off++] = TLV_PUB_KEY;
off++;
len = secp256k1.derivePublicKey(derivationOutput, (short) 0, apduBuffer, off);

if (extendedPublic) {
Util.arrayCopyNonAtomic(derivationOutput, Crypto.KEY_SECRET_SIZE, apduBuffer, (short) (off + len), CHAIN_CODE_SIZE);
len += CHAIN_CODE_SIZE;
}

apduBuffer[(short) (off - 1)] = (byte) len;
off += len;
} else {
Expand Down Expand Up @@ -1322,6 +1338,10 @@ private boolean isPinless() {
return (pinlessPathLen > 0) && (pinlessPathLen == tmpPathLen) && (Util.arrayCompare(tmpPath, (short) 0, pinlessPath, (short) 0, tmpPathLen) == 0);
}

private boolean isEIP1581() {
return (tmpPathLen >= (short)(((short) EIP_1581_PREFIX.length) + 8)) && (Util.arrayCompare(EIP_1581_PREFIX, (short) 0, tmpPath, (short) 0, (short) EIP_1581_PREFIX.length) == 0);
}

/**
* Set curve parameters to cleared keys
*/
Expand Down
35 changes: 27 additions & 8 deletions src/test/java/im/status/keycard/KeycardTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1281,19 +1281,19 @@ void exportKey() throws Exception {
response = cmdSet.exportCurrentKey(true);
assertEquals(0x9000, response.getSw());
byte[] keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000 }, true);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000 }, true, false);

// Derive & Make current
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER,true,false);
response = cmdSet.exportKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER, true, false);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);

// Derive without making current
response = cmdSet.exportKey(new byte[] {(byte) 0x00, 0x00, 0x00, 0x01}, KeycardApplet.DERIVE_P1_SOURCE_PARENT, false,false);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000001 }, false, false);
response = cmdSet.getStatus(KeycardApplet.GET_STATUS_P1_KEY_PATH);
assertEquals(0x9000, response.getSw());
assertArrayEquals(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, response.getData());
Expand All @@ -1302,7 +1302,16 @@ void exportKey() throws Exception {
response = cmdSet.exportCurrentKey(false);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false);
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062d, 0x00000000, 0x00000000 }, false, false);

// Export extended public
response = cmdSet.exportExtendedPublicKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2D, (byte) 0x00, 0x00, 0x00, 0x00, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x6985, response.getSw());

response = cmdSet.exportExtendedPublicKey(new byte[] {(byte) 0x80, 0x00, 0x00, 0x2B, (byte) 0x80, 0x00, 0x00, 0x3C, (byte) 0x80, 0x00, 0x06, 0x2c, (byte) 0x00, 0x00, 0x00, 0x00}, KeycardApplet.DERIVE_P1_SOURCE_MASTER);
assertEquals(0x9000, response.getSw());
keyTemplate = response.getData();
verifyExportedKey(keyTemplate, keyPair, chainCode, new int[] { 0x8000002b, 0x8000003c, 0x8000062c, 0x00000000 }, true, true);

// Reset
response = cmdSet.deriveKey(new byte[0], KeycardApplet.DERIVE_P1_SOURCE_MASTER);
Expand Down Expand Up @@ -1673,19 +1682,29 @@ private void verifyKeyDerivation(KeyPair keyPair, byte[] chainCode, int[] path)
}
}

private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly) {
private void verifyExportedKey(byte[] keyTemplate, KeyPair keyPair, byte[] chainCode, int[] path, boolean publicOnly, boolean extendedPublic) {
if (!cmdSet.getApplicationInfo().hasKeyManagementCapability()) {
return;
}

ECKey key = deriveKey(keyPair, chainCode, path).decompress();
DeterministicKey dk = deriveKey(keyPair, chainCode, path);
ECKey key = dk.decompress();
assertEquals(KeycardApplet.TLV_KEY_TEMPLATE, keyTemplate[0]);
int pubKeyLen = 0;

if (publicOnly) {
assertEquals(KeycardApplet.TLV_PUB_KEY, keyTemplate[2]);
byte[] pubKey = Arrays.copyOfRange(keyTemplate, 4, 4 + keyTemplate[3]);
assertArrayEquals(key.getPubKey(), pubKey);
byte[] correctPub = key.getPubKey();

if (extendedPublic) {
byte[] chain = dk.getChainCode();
int len = correctPub.length;
correctPub = Arrays.copyOf(correctPub, len + chain.length);
System.arraycopy(chain, 0, correctPub, len, chain.length);
}

assertArrayEquals(correctPub, pubKey);
pubKeyLen = 2 + pubKey.length;
assertEquals(pubKeyLen, keyTemplate[1]);
assertEquals(pubKeyLen + 2, keyTemplate.length);
Expand Down
11 changes: 11 additions & 0 deletions src/test/java/im/status/keycard/TestKeycardCommandSet.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import im.status.keycard.applet.ApplicationStatus;
import im.status.keycard.applet.KeycardCommandSet;
import im.status.keycard.io.APDUCommand;
import im.status.keycard.io.APDUResponse;
import im.status.keycard.io.CardChannel;
import org.web3j.crypto.ECKeyPair;
Expand All @@ -12,12 +13,17 @@


public class TestKeycardCommandSet extends KeycardCommandSet {
private CardChannel ac;
private TestSecureChannelSession sc;

public TestKeycardCommandSet(CardChannel apduChannel) {
super(apduChannel);
ac = apduChannel;
}

public void setSecureChannel(TestSecureChannelSession secureChannel) {
super.setSecureChannel(secureChannel);
sc = secureChannel;
}

/**
Expand Down Expand Up @@ -77,6 +83,11 @@ public APDUResponse loadKey(PrivateKey aPrivate, byte[] chainCode) throws IOExce
return loadKey(data, LOAD_KEY_P1_SEED);
}

public APDUResponse exportExtendedPublicKey(byte[] keyPath, byte source) throws IOException {
APDUCommand exportKey = sc.protectedCommand(0x80, 0xc2, (source | 0x01), 2, keyPath);
return sc.transmit(ac, exportKey);
}

/**
* Sends a GET STATUS APDU to retrieve the APPLICATION STATUS template and reads the byte indicating key initialization
* status
Expand Down

0 comments on commit f3e8342

Please sign in to comment.