Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9366028aaf | ||
|
|
0d27ac445c | ||
|
|
ccb353ca82 | ||
|
|
78c6dfb6d6 | ||
|
|
15a61e16e7 | ||
|
|
7d968cf969 |
@@ -24,14 +24,25 @@ public class NFCCardChannel implements CardChannel {
|
|||||||
public APDUResponse send(APDUCommand cmd) throws IOException {
|
public APDUResponse send(APDUCommand cmd) throws IOException {
|
||||||
byte[] apdu = cmd.serialize();
|
byte[] apdu = cmd.serialize();
|
||||||
Log.d(TAG, String.format("COMMAND CLA: %02X INS: %02X P1: %02X P2: %02X LC: %02X", cmd.getCla(), cmd.getIns(), cmd.getP1(), cmd.getP2(), cmd.getData().length));
|
Log.d(TAG, String.format("COMMAND CLA: %02X INS: %02X P1: %02X P2: %02X LC: %02X", cmd.getCla(), cmd.getIns(), cmd.getP1(), cmd.getP2(), cmd.getData().length));
|
||||||
byte[] resp = this.isoDep.transceive(apdu);
|
|
||||||
APDUResponse response = new APDUResponse(resp);
|
try {
|
||||||
Log.d(TAG, String.format("RESPONSE LEN: %02X, SW: %04X %n-----------------------", response.getData().length, response.getSw()));
|
byte[] resp = this.isoDep.transceive(apdu);
|
||||||
return response;
|
APDUResponse response = new APDUResponse(resp);
|
||||||
|
Log.d(TAG, String.format("RESPONSE LEN: %02X, SW: %04X %n-----------------------", response.getData().length, response.getSw()));
|
||||||
|
return response;
|
||||||
|
} catch(SecurityException e) {
|
||||||
|
throw new IOException("Tag disconnected", e);
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
throw new IOException("Malformed card response", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return this.isoDep.isConnected();
|
try {
|
||||||
|
return this.isoDep.isConnected();
|
||||||
|
} catch(SecurityException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,11 @@ public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback
|
|||||||
* @return if connected, false otherwise
|
* @return if connected, false otherwise
|
||||||
*/
|
*/
|
||||||
public boolean isConnected() {
|
public boolean isConnected() {
|
||||||
return isoDep != null && isoDep.isConnected();
|
try {
|
||||||
|
return isoDep != null && isoDep.isConnected();
|
||||||
|
} catch (SecurityException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -58,7 +62,7 @@ public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback
|
|||||||
isoDep = IsoDep.get(tag);
|
isoDep = IsoDep.get(tag);
|
||||||
isoDep.connect();
|
isoDep.connect();
|
||||||
isoDep.setTimeout(120000);
|
isoDep.setTimeout(120000);
|
||||||
} catch (IOException e) {
|
} catch (IOException | SecurityException e) {
|
||||||
Log.e(TAG, "error connecting to tag");
|
Log.e(TAG, "error connecting to tag");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,9 @@ public class ApplicationInfo {
|
|||||||
static final byte CAPABILITY_KEY_MANAGEMENT = (byte) 0x02;
|
static final byte CAPABILITY_KEY_MANAGEMENT = (byte) 0x02;
|
||||||
static final byte CAPABILITY_CREDENTIALS_MANAGEMENT = (byte) 0x04;
|
static final byte CAPABILITY_CREDENTIALS_MANAGEMENT = (byte) 0x04;
|
||||||
static final byte CAPABILITY_NDEF = (byte) 0x08;
|
static final byte CAPABILITY_NDEF = (byte) 0x08;
|
||||||
|
static final byte CAPABILITY_FACTORY_RESET = (byte) 0x10;
|
||||||
|
|
||||||
static final byte CAPABILITIES_ALL = CAPABILITY_SECURE_CHANNEL | CAPABILITY_KEY_MANAGEMENT | CAPABILITY_CREDENTIALS_MANAGEMENT | CAPABILITY_NDEF;
|
static final byte CAPABILITIES_ALL = CAPABILITY_SECURE_CHANNEL | CAPABILITY_KEY_MANAGEMENT | CAPABILITY_CREDENTIALS_MANAGEMENT | CAPABILITY_NDEF | CAPABILITY_FACTORY_RESET;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an object by parsing the TLV data.
|
* Constructs an object by parsing the TLV data.
|
||||||
@@ -191,4 +192,13 @@ public class ApplicationInfo {
|
|||||||
public boolean hasNDEFCapability() {
|
public boolean hasNDEFCapability() {
|
||||||
return (capabilities & CAPABILITY_NDEF) == CAPABILITY_NDEF;
|
return (capabilities & CAPABILITY_NDEF) == CAPABILITY_NDEF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the device supports the Factory Reset capability.
|
||||||
|
*
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
public boolean hasFactoryResetCapability() {
|
||||||
|
return (capabilities & CAPABILITY_FACTORY_RESET) == CAPABILITY_FACTORY_RESET;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,13 +66,13 @@ public class BIP32KeyPair {
|
|||||||
tlv.unreadLastTag();
|
tlv.unreadLastTag();
|
||||||
privKey = tlv.readPrimitive(TLV_PRIV_KEY);
|
privKey = tlv.readPrimitive(TLV_PRIV_KEY);
|
||||||
tag = tlv.readTag();
|
tag = tlv.readTag();
|
||||||
|
|
||||||
if (tag == TLV_CHAIN_CODE) {
|
|
||||||
tlv.unreadLastTag();
|
|
||||||
chainCode = tlv.readPrimitive(TLV_CHAIN_CODE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tag == TLV_CHAIN_CODE) {
|
||||||
|
tlv.unreadLastTag();
|
||||||
|
chainCode = tlv.readPrimitive(TLV_CHAIN_CODE);
|
||||||
|
}
|
||||||
|
|
||||||
return new BIP32KeyPair(privKey, chainCode, pubKey);
|
return new BIP32KeyPair(privKey, chainCode, pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public class KeycardCommandSet {
|
public class KeycardCommandSet {
|
||||||
static final byte INS_INIT = (byte) 0xFE;
|
static final byte INS_INIT = (byte) 0xFE;
|
||||||
|
static final byte INS_FACTORY_RESET = (byte) 0xFD;
|
||||||
static final byte INS_GET_STATUS = (byte) 0xF2;
|
static final byte INS_GET_STATUS = (byte) 0xF2;
|
||||||
static final byte INS_SET_NDEF = (byte) 0xF3;
|
static final byte INS_SET_NDEF = (byte) 0xF3;
|
||||||
static final byte INS_IDENTIFY_CARD = (byte) 0x14;
|
static final byte INS_IDENTIFY_CARD = (byte) 0x14;
|
||||||
@@ -81,6 +82,9 @@ public class KeycardCommandSet {
|
|||||||
public static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
|
public static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
|
||||||
public static final byte EXPORT_KEY_P2_EXTENDED_PUBLIC = 0x02;
|
public static final byte EXPORT_KEY_P2_EXTENDED_PUBLIC = 0x02;
|
||||||
|
|
||||||
|
static final byte FACTORY_RESET_P1_MAGIC = (byte) 0xAA;
|
||||||
|
static final byte FACTORY_RESET_P2_MAGIC = 0x55;
|
||||||
|
|
||||||
static final byte TLV_APPLICATION_INFO_TEMPLATE = (byte) 0xA4;
|
static final byte TLV_APPLICATION_INFO_TEMPLATE = (byte) 0xA4;
|
||||||
|
|
||||||
private final CardChannel apduChannel;
|
private final CardChannel apduChannel;
|
||||||
@@ -554,7 +558,7 @@ public class KeycardCommandSet {
|
|||||||
* @throws IOException communication error
|
* @throws IOException communication error
|
||||||
*/
|
*/
|
||||||
public APDUResponse sign(byte[] data, int p1) throws IOException {
|
public APDUResponse sign(byte[] data, int p1) throws IOException {
|
||||||
APDUCommand sign = secureChannel.protectedCommand(0x80, INS_SIGN, p1, 0x00, data);
|
APDUCommand sign = secureChannel.protectedCommand(0x80, INS_SIGN, p1, 0x01, data);
|
||||||
return secureChannel.transmit(apduChannel, sign);
|
return secureChannel.transmit(apduChannel, sign);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -815,9 +819,25 @@ public class KeycardCommandSet {
|
|||||||
* @throws IOException communication error
|
* @throws IOException communication error
|
||||||
*/
|
*/
|
||||||
public APDUResponse init(String pin, String puk, String pairingPassword, byte pinRetries, byte pukRetries) throws IOException {
|
public APDUResponse init(String pin, String puk, String pairingPassword, byte pinRetries, byte pukRetries) throws IOException {
|
||||||
return this.init(pin, puk, pairingPasswordToSecret(pairingPassword), pinRetries, pukRetries);
|
return this.init(pin, null, puk, pairingPasswordToSecret(pairingPassword), pinRetries, pukRetries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the INIT command to the card.
|
||||||
|
*
|
||||||
|
* @param pin the PIN
|
||||||
|
* @param altPin the alternative PIN
|
||||||
|
* @param puk the PUK
|
||||||
|
* @param pairingPassword pairing password
|
||||||
|
* @param pinRetries the number of allowed PIN retries
|
||||||
|
* @param pukRetries the number of allowed PUK retries
|
||||||
|
* @return the raw card response
|
||||||
|
* @throws IOException communication error
|
||||||
|
*/
|
||||||
|
public APDUResponse init(String pin, String altPin, String puk, String pairingPassword, byte pinRetries, byte pukRetries) throws IOException {
|
||||||
|
return this.init(pin, altPin, puk, pairingPasswordToSecret(pairingPassword), pinRetries, pukRetries);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the INIT command to the card.
|
* Sends the INIT command to the card.
|
||||||
*
|
*
|
||||||
@@ -828,13 +848,14 @@ public class KeycardCommandSet {
|
|||||||
* @throws IOException communication error
|
* @throws IOException communication error
|
||||||
*/
|
*/
|
||||||
public APDUResponse init(String pin, String puk, byte[] sharedSecret) throws IOException {
|
public APDUResponse init(String pin, String puk, byte[] sharedSecret) throws IOException {
|
||||||
return init(pin, puk, sharedSecret, (byte) 0, (byte) 0);
|
return init(pin, null, puk, sharedSecret, (byte) 0, (byte) 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends the INIT command to the card. If either pinRetries or pukRetries is zero, neither will be sent.
|
* Sends the INIT command to the card. If either pinRetries or pukRetries is zero, neither will be sent.
|
||||||
*
|
*
|
||||||
* @param pin the PIN
|
* @param pin the PIN
|
||||||
|
* @param pin the alternative
|
||||||
* @param puk the PUK
|
* @param puk the PUK
|
||||||
* @param sharedSecret the shared secret for pairing
|
* @param sharedSecret the shared secret for pairing
|
||||||
* @param pinRetries the number of allowed PIN retries
|
* @param pinRetries the number of allowed PIN retries
|
||||||
@@ -842,18 +863,43 @@ public class KeycardCommandSet {
|
|||||||
* @return the raw card response
|
* @return the raw card response
|
||||||
* @throws IOException communication error
|
* @throws IOException communication error
|
||||||
*/
|
*/
|
||||||
public APDUResponse init(String pin, String puk, byte[] sharedSecret, byte pinRetries, byte pukRetries) throws IOException {
|
public APDUResponse init(String pin, String altPin, String puk, byte[] sharedSecret, byte pinRetries, byte pukRetries) throws IOException {
|
||||||
boolean addRetries = !((pinRetries == 0) || (pukRetries == 0));
|
int baselen = pin.length() + puk.length() + sharedSecret.length;
|
||||||
byte[] initData = Arrays.copyOf(pin.getBytes(), pin.length() + puk.length() + sharedSecret.length + (addRetries ? 2 : 0));
|
int extlen;
|
||||||
|
|
||||||
|
if (altPin != null) {
|
||||||
|
extlen = 2 + altPin.length();
|
||||||
|
} else if ((pinRetries != 0) || (pukRetries != 0)) {
|
||||||
|
extlen = 2;
|
||||||
|
} else {
|
||||||
|
extlen = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] initData = Arrays.copyOf(pin.getBytes(), baselen + extlen);
|
||||||
System.arraycopy(puk.getBytes(), 0, initData, pin.length(), puk.length());
|
System.arraycopy(puk.getBytes(), 0, initData, pin.length(), puk.length());
|
||||||
System.arraycopy(sharedSecret, 0, initData, pin.length() + puk.length(), sharedSecret.length);
|
System.arraycopy(sharedSecret, 0, initData, pin.length() + puk.length(), sharedSecret.length);
|
||||||
|
|
||||||
if (addRetries) {
|
if (extlen > 0) {
|
||||||
initData[initData.length - 2] = pinRetries;
|
initData[baselen] = pinRetries;
|
||||||
initData[initData.length - 1] = pukRetries;
|
initData[baselen + 1] = pukRetries;
|
||||||
|
|
||||||
|
if (extlen > 2) {
|
||||||
|
System.arraycopy(altPin.getBytes(), 0, initData, baselen + 2, altPin.length());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
APDUCommand init = new APDUCommand(0x80, INS_INIT, 0, 0, secureChannel.oneShotEncrypt(initData));
|
APDUCommand init = new APDUCommand(0x80, INS_INIT, 0, 0, secureChannel.oneShotEncrypt(initData));
|
||||||
return apduChannel.send(init);
|
return apduChannel.send(init);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends the FACTORY RESET command to the card.
|
||||||
|
*
|
||||||
|
* @return the raw card response
|
||||||
|
* @throws IOException communication error
|
||||||
|
*/
|
||||||
|
public APDUResponse factoryReset() throws IOException {
|
||||||
|
APDUCommand factoryReset = new APDUCommand(0x80, INS_FACTORY_RESET, FACTORY_RESET_P1_MAGIC, FACTORY_RESET_P2_MAGIC, new byte[0]);
|
||||||
|
return apduChannel.send(factoryReset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ public class RecoverableSignature {
|
|||||||
private boolean compressed;
|
private boolean compressed;
|
||||||
|
|
||||||
public static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
|
public static final byte TLV_SIGNATURE_TEMPLATE = (byte) 0xA0;
|
||||||
|
public static final byte TLV_RAW_SIGNATURE = (byte) 0x80;
|
||||||
public static final byte TLV_ECDSA_TEMPLATE = (byte) 0x30;
|
public static final byte TLV_ECDSA_TEMPLATE = (byte) 0x30;
|
||||||
|
|
||||||
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
|
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
|
||||||
@@ -41,6 +42,19 @@ public class RecoverableSignature {
|
|||||||
*/
|
*/
|
||||||
public RecoverableSignature(byte[] hash, byte[] tlvData) {
|
public RecoverableSignature(byte[] hash, byte[] tlvData) {
|
||||||
TinyBERTLV tlv = new TinyBERTLV(tlvData);
|
TinyBERTLV tlv = new TinyBERTLV(tlvData);
|
||||||
|
int tag = tlv.readTag();
|
||||||
|
tlv.unreadLastTag();
|
||||||
|
|
||||||
|
if (tag == TLV_RAW_SIGNATURE) {
|
||||||
|
initFromRawSignature(hash, tlv.readPrimitive(tag));
|
||||||
|
} else if (tag == TLV_SIGNATURE_TEMPLATE) {
|
||||||
|
initFromLegacy(hash, tlv);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("invalid tlv");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initFromLegacy(byte[] hash, TinyBERTLV tlv) {
|
||||||
tlv.enterConstructed(TLV_SIGNATURE_TEMPLATE);
|
tlv.enterConstructed(TLV_SIGNATURE_TEMPLATE);
|
||||||
this.publicKey = tlv.readPrimitive(ApplicationInfo.TLV_PUB_KEY);
|
this.publicKey = tlv.readPrimitive(ApplicationInfo.TLV_PUB_KEY);
|
||||||
tlv.enterConstructed(TLV_ECDSA_TEMPLATE);
|
tlv.enterConstructed(TLV_ECDSA_TEMPLATE);
|
||||||
@@ -51,6 +65,14 @@ public class RecoverableSignature {
|
|||||||
calculateRecID(hash);
|
calculateRecID(hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initFromRawSignature(byte[] hash, byte[] signature) {
|
||||||
|
this.r = Arrays.copyOfRange(signature, 0, 32);
|
||||||
|
this.s = Arrays.copyOfRange(signature, 32, 64);
|
||||||
|
this.recId = signature[64];
|
||||||
|
this.compressed = false;
|
||||||
|
this.publicKey = recoverFromSignature(this.recId, hash, this.r, this.s, this.compressed);
|
||||||
|
}
|
||||||
|
|
||||||
public RecoverableSignature(byte[] publicKey, boolean compressed, byte[] r, byte[] s, int recId) {
|
public RecoverableSignature(byte[] publicKey, boolean compressed, byte[] r, byte[] s, int recId) {
|
||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.r = r;
|
this.r = r;
|
||||||
|
|||||||
Reference in New Issue
Block a user