Compare commits

..
Author SHA1 Message Date
Michele Balistreri 9366028aaf add factory reset 2023-06-06 16:42:49 +02:00
Michele Balistreri 0d27ac445c treat malformed response as ioexception 2023-02-22 12:16:44 +01:00
Michele Balistreri ccb353ca82 handle securityexceptions 2023-02-06 13:01:59 +01:00
Michele Balistreri 78c6dfb6d6 support raw signature format 2022-12-09 12:31:56 +01:00
Michele Balistreri 15a61e16e7 Add init with alt PIN (#29)
* init with alt pin

* chain code in pubkeys
2022-11-21 08:43:55 +01:00
5 changed files with 71 additions and 9 deletions
@@ -24,14 +24,25 @@ public class NFCCardChannel implements CardChannel {
public APDUResponse send(APDUCommand cmd) throws IOException {
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));
byte[] resp = this.isoDep.transceive(apdu);
APDUResponse response = new APDUResponse(resp);
Log.d(TAG, String.format("RESPONSE LEN: %02X, SW: %04X %n-----------------------", response.getData().length, response.getSw()));
return response;
try {
byte[] resp = this.isoDep.transceive(apdu);
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
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
*/
public boolean isConnected() {
return isoDep != null && isoDep.isConnected();
try {
return isoDep != null && isoDep.isConnected();
} catch (SecurityException e) {
return false;
}
}
@Override
@@ -58,7 +62,7 @@ public class NFCCardManager extends Thread implements NfcAdapter.ReaderCallback
isoDep = IsoDep.get(tag);
isoDep.connect();
isoDep.setTimeout(120000);
} catch (IOException e) {
} catch (IOException | SecurityException e) {
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_CREDENTIALS_MANAGEMENT = (byte) 0x04;
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.
@@ -191,4 +192,13 @@ public class ApplicationInfo {
public boolean hasNDEFCapability() {
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;
}
}
@@ -18,6 +18,7 @@ import java.util.Arrays;
*/
public class KeycardCommandSet {
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_SET_NDEF = (byte) 0xF3;
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_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;
private final CardChannel apduChannel;
@@ -554,7 +558,7 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
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);
}
@@ -887,4 +891,15 @@ public class KeycardCommandSet {
APDUCommand init = new APDUCommand(0x80, INS_INIT, 0, 0, secureChannel.oneShotEncrypt(initData));
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;
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;
private static final X9ECParameters CURVE_PARAMS = CustomNamedCurves.getByName("secp256k1");
@@ -41,6 +42,19 @@ public class RecoverableSignature {
*/
public RecoverableSignature(byte[] hash, byte[] 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);
this.publicKey = tlv.readPrimitive(ApplicationInfo.TLV_PUB_KEY);
tlv.enterConstructed(TLV_ECDSA_TEMPLATE);
@@ -51,6 +65,14 @@ public class RecoverableSignature {
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) {
this.publicKey = publicKey;
this.r = r;