Compare commits

...
Author SHA1 Message Date
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
Michele Balistreri 7d968cf969 support export chain code (#28) 2022-11-10 08:11:03 +01:00
Michele Balistreri 9fac06b19d Add IDENTIFY CARD (#24)
* ident applet support

* add identify card command

* fix certificate class

* make sure private key is 32 bytes

* don't remove TLV header from signature

* fix typo

* use secure channel if open
2022-11-04 12:33:06 +01:00
Audrius Molis 6c965f726a test adding exception info to the RuntimeException in RuntimeException (#26) 2022-09-09 15:31:58 +02:00
12 changed files with 441 additions and 46 deletions
@@ -24,14 +24,23 @@ 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);
}
}
@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");
}
}
@@ -66,13 +66,13 @@ public class BIP32KeyPair {
tlv.unreadLastTag();
privKey = tlv.readPrimitive(TLV_PRIV_KEY);
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);
}
@@ -32,6 +32,18 @@ public class CashCommandSet {
return apduChannel.send(selectApplet);
}
/**
* Sends an IDENTIFY CARD APDU. The challenge is sent as APDU data as-is. It must be 32 bytes long
*
* @param challenge the data of the APDU
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse identifyCard(byte[] challenge) throws IOException {
APDUCommand identifyCard = new APDUCommand(0x80, KeycardCommandSet.INS_IDENTIFY_CARD, 0, 0, challenge);
return apduChannel.send(identifyCard);
}
/**
* Sends a SIGN APDU.
*
@@ -0,0 +1,141 @@
package im.status.keycard.applet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.interfaces.ECPublicKey;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.interfaces.ECPrivateKey;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.util.Arrays;
public class Certificate extends RecoverableSignature {
public static final byte TLV_CERT = (byte) 0x8A;
private byte[] identPriv;
private byte[] identPub;
public Certificate(byte[] publicKey, boolean compressed, byte[] r, byte[] s, int recId) {
super(publicKey, compressed,r, s, recId);
}
public static KeyPair generateIdentKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDSA", "BC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256k1");
keyPairGenerator.initialize(spec, new SecureRandom());
return keyPairGenerator.generateKeyPair();
} catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?");
}
}
public static Certificate createCertificate(KeyPair caPair, KeyPair identKeys) {
try {
byte[] pub = ((ECPublicKey) identKeys.getPublic()).getQ().getEncoded(true);
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] hash = md.digest(pub);
Signature signer = Signature.getInstance("NONEwithECDSA", "BC");
signer.initSign(caPair.getPrivate());
signer.update(hash);
byte[] sig = signer.sign();
TinyBERTLV tlv = new TinyBERTLV(sig);
tlv.enterConstructed(TLV_ECDSA_TEMPLATE);
byte[] r = toUInt(tlv.readPrimitive(TinyBERTLV.TLV_INT));
byte[] s = toUInt(tlv.readPrimitive(TinyBERTLV.TLV_INT));
Certificate cert = new Certificate(((ECPublicKey)caPair.getPublic()).getQ().getEncoded(true), true, r, s, -1);
cert.calculateRecID(hash);
cert.identPriv = toUInt(((ECPrivateKey) identKeys.getPrivate()).getD().toByteArray());
cert.identPub = pub;
return cert;
} catch(IllegalArgumentException e) {
throw e;
} catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?");
}
}
public static Certificate generateNewCertificate(KeyPair caPair) {
return createCertificate(caPair, generateIdentKeyPair());
}
public static Certificate fromTLV(byte[] certData) {
try {
byte[] pub = Arrays.copyOfRange(certData, 0, 33);
byte[] r = Arrays.copyOfRange(certData, 33, 65);
byte[] s = Arrays.copyOfRange(certData, 65, 97);
int recId = certData[97];
MessageDigest md = MessageDigest.getInstance("SHA256", "BC");
byte[] hash = md.digest(pub);
byte[] caPub = recoverFromSignature(recId, hash, r, s, true);
Certificate cert = new Certificate(caPub, true, r, s, recId);
cert.identPub = pub;
return cert;
} catch(IllegalArgumentException e) {
throw e;
} catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?");
}
}
public static byte[] verifyIdentity(byte[] hash, byte[] tlvData) {
try {
TinyBERTLV tlv = new TinyBERTLV(tlvData);
tlv.enterConstructed(TLV_SIGNATURE_TEMPLATE);
byte[] certData = tlv.readPrimitive(TLV_CERT);
Certificate cert = fromTLV(certData);
byte[] signature = tlv.peekUnread();
Signature verifier = Signature.getInstance("NONEWithECDSA", "BC");
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPublicKeySpec cardKeySpec = new ECPublicKeySpec(ecSpec.getCurve().decodePoint(cert.identPub), ecSpec);
ECPublicKey cardKey = (ECPublicKey) KeyFactory.getInstance("ECDSA", "BC").generatePublic(cardKeySpec);
verifier.initVerify(cardKey);
verifier.update(hash);
if (!verifier.verify(signature)) {
return null;
}
return cert.getPublicKey();
} catch(Exception e) {
throw new RuntimeException("Is BouncyCastle in the classpath?");
}
}
public byte[] toStoreData() {
if (identPriv == null) {
throw new IllegalStateException("The private key must be set.");
}
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
os.write(this.identPub);
os.write(this.getR());
os.write(this.getS());
os.write(this.getRecId());
os.write(this.identPriv);
} catch(IOException e) {
throw new RuntimeException(e);
}
return os.toByteArray();
}
}
@@ -0,0 +1,46 @@
package im.status.keycard.applet;
import im.status.keycard.io.APDUCommand;
import im.status.keycard.io.APDUResponse;
import im.status.keycard.io.CardChannel;
import java.io.IOException;
/**
* Command set for the Ident applet.
*/
public class IdentCommandSet {
private final CardChannel apduChannel;
/**
* Creates a IdentCommandSet using the given APDU Channel
* @param apduChannel APDU channel
*/
public IdentCommandSet(CardChannel apduChannel) {
this.apduChannel = apduChannel;
}
/**
* Selects a Cash instance. 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 {
APDUCommand selectApplet = new APDUCommand(0x00, 0xA4, 4, 0, Identifiers.IDENT_INSTANCE_AID);
return apduChannel.send(selectApplet);
}
/**
* Sends a STORE DATA APDU.
*
* @param data the data to sign
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse storeData(byte[] data) throws IOException {
APDUCommand sign = new APDUCommand(0x80, KeycardCommandSet.INS_STORE_DATA, 0x00, 0x00, data);
return apduChannel.send(sign);
}
}
@@ -16,6 +16,9 @@ public class Identifiers {
public static final byte[] CASH_AID = Hex.decode("A000000804000103");
public static final byte[] CASH_INSTANCE_AID = Hex.decode("A00000080400010301");
public static final byte[] IDENT_AID = Hex.decode("A000000804000104");
public static final byte[] IDENT_INSTANCE_AID = Hex.decode("A00000080400010401");
/**
* Gets the instance AID of the default instance of the Keycard applet.
*
@@ -20,6 +20,7 @@ public class KeycardCommandSet {
static final byte INS_INIT = (byte) 0xFE;
static final byte INS_GET_STATUS = (byte) 0xF2;
static final byte INS_SET_NDEF = (byte) 0xF3;
static final byte INS_IDENTIFY_CARD = (byte) 0x14;
static final byte INS_VERIFY_PIN = (byte) 0x20;
static final byte INS_CHANGE_PIN = (byte) 0x21;
static final byte INS_UNBLOCK_PIN = (byte) 0x22;
@@ -76,8 +77,9 @@ public class KeycardCommandSet {
static final byte EXPORT_KEY_P1_DERIVE = 0x01;
static final byte EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT = 0x02;
static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
public static final byte EXPORT_KEY_P2_PRIVATE_AND_PUBLIC = 0x00;
public static final byte EXPORT_KEY_P2_PUBLIC_ONLY = 0x01;
public static final byte EXPORT_KEY_P2_EXTENDED_PUBLIC = 0x02;
static final byte TLV_APPLICATION_INFO_TEMPLATE = (byte) 0xA4;
@@ -269,6 +271,18 @@ public class KeycardCommandSet {
secureChannel.unpairOthers(apduChannel);
}
/**
* Sends an IDENTIFY CARD APDU. The challenge is sent as APDU data as-is. It must be 32 bytes long
*
* @param challenge the data of the APDU
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse identifyCard(byte[] challenge) throws IOException {
APDUCommand identifyCard = secureChannel.protectedCommand(0x80, INS_IDENTIFY_CARD, 0, 0, challenge);
return secureChannel.transmit(apduChannel, identifyCard);
}
/**
* Sends a GET STATUS APDU. The info byte is the P1 parameter of the command, valid constants are defined in the applet
* class itself.
@@ -540,7 +554,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);
}
@@ -620,6 +634,10 @@ public class KeycardCommandSet {
return secureChannel.transmit(apduChannel, setPinlessPath);
}
private byte poToP2(boolean publicOnly) {
return publicOnly ? EXPORT_KEY_P2_PUBLIC_ONLY : EXPORT_KEY_P2_PRIVATE_AND_PUBLIC;
}
/**
* Sends an EXPORT KEY APDU to export the current key.
*
@@ -628,9 +646,20 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
public APDUResponse exportCurrentKey(boolean publicOnly) throws IOException {
return exportKey(EXPORT_KEY_P1_CURRENT, publicOnly, new byte[0]);
return exportCurrentKey(poToP2(publicOnly));
}
/**
* Sends an EXPORT KEY APDU to export the current key.
*
* @param p2 the p2 parameter
* @return the raw card reponse
* @throws IOException communication error
*/
public APDUResponse exportCurrentKey(byte p2) throws IOException {
return exportKey(EXPORT_KEY_P1_CURRENT, p2, new byte[0]);
}
/**
* Sends an EXPORT KEY APDU. Performs derivation of the given keypath and optionally makes it the current key.
*
@@ -641,10 +670,23 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
public APDUResponse exportKey(String keyPath, boolean makeCurrent, boolean publicOnly) throws IOException {
KeyPath path = new KeyPath(keyPath);
return exportKey(path.getData(), path.getSource(), makeCurrent, publicOnly);
return exportKey(keyPath, makeCurrent, poToP2(publicOnly));
}
/**
* Sends an EXPORT KEY APDU. Performs derivation of the given keypath and optionally makes it the current key.
*
* @param keyPath the keypath to export
* @param makeCurrent if the key should be made current or not
* @param p2 the P2 parameter
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse exportKey(String keyPath, boolean makeCurrent, byte p2) throws IOException {
KeyPath path = new KeyPath(keyPath);
return exportKey(path.getData(), path.getSource(), makeCurrent, p2);
}
/**
* Sends an EXPORT KEY APDU. Performs derivation of the given keypath and optionally makes it the current key.
*
@@ -655,10 +697,23 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
public APDUResponse exportKey(byte[] keyPath, int source, boolean makeCurrent, boolean publicOnly) throws IOException {
int p1 = source | (makeCurrent ? EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT : EXPORT_KEY_P1_DERIVE);
return exportKey(p1, publicOnly, keyPath);
return exportKey(keyPath, source, makeCurrent, poToP2(publicOnly));
}
/**
* Sends an EXPORT KEY APDU. Performs derivation of the given keypath and optionally makes it the current key.
*
* @param keyPath the keypath to export
* @param makeCurrent if the key should be made current or not
* @param p2 the P2 parameter
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse exportKey(byte[] keyPath, int source, boolean makeCurrent, byte p2) throws IOException {
int p1 = source | (makeCurrent ? EXPORT_KEY_P1_DERIVE_AND_MAKE_CURRENT : EXPORT_KEY_P1_DERIVE);
return exportKey(p1, p2, keyPath);
}
/**
* Sends an EXPORT KEY APDU. The parameters are sent as-is.
*
@@ -669,10 +724,22 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
public APDUResponse exportKey(int derivationOptions, boolean publicOnly, byte[] keypath) throws IOException {
byte p2 = publicOnly ? EXPORT_KEY_P2_PUBLIC_ONLY : EXPORT_KEY_P2_PRIVATE_AND_PUBLIC;
return exportKey(derivationOptions, poToP2(publicOnly), keypath);
}
/**
* Sends an EXPORT KEY APDU. The parameters are sent as-is.
*
* @param derivationOptions the P1 parameter
* @param p2 the P2 parameter
* @param keypath the data parameter
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse exportKey(int derivationOptions, byte p2, byte[] keypath) throws IOException {
APDUCommand exportKey = secureChannel.protectedCommand(0x80, INS_EXPORT_KEY, derivationOptions, p2, keypath);
return secureChannel.transmit(apduChannel, exportKey);
}
}
/**
* Sends a GET DATA APDU.
@@ -748,9 +815,25 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
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.
*
@@ -761,13 +844,14 @@ public class KeycardCommandSet {
* @throws IOException communication error
*/
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.
*
* @param pin the PIN
* @param pin the alternative
* @param puk the PUK
* @param sharedSecret the shared secret for pairing
* @param pinRetries the number of allowed PIN retries
@@ -775,15 +859,29 @@ public class KeycardCommandSet {
* @return the raw card response
* @throws IOException communication error
*/
public APDUResponse init(String pin, String puk, byte[] sharedSecret, byte pinRetries, byte pukRetries) throws IOException {
boolean addRetries = !((pinRetries == 0) || (pukRetries == 0));
byte[] initData = Arrays.copyOf(pin.getBytes(), pin.length() + puk.length() + sharedSecret.length + (addRetries ? 2 : 0));
public APDUResponse init(String pin, String altPin, String puk, byte[] sharedSecret, byte pinRetries, byte pukRetries) throws IOException {
int baselen = pin.length() + puk.length() + sharedSecret.length;
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(sharedSecret, 0, initData, pin.length() + puk.length(), sharedSecret.length);
if (addRetries) {
initData[initData.length - 2] = pinRetries;
initData[initData.length - 1] = pukRetries;
if (extlen > 0) {
initData[baselen] = pinRetries;
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));
@@ -124,7 +124,7 @@ public class Mnemonic {
PBEKeySpec spec = new PBEKeySpec(mnemonicPhrase.toCharArray(), ("mnemonic" + password).getBytes(), 2048, 512);
key = skf.generateSecret(spec);
} catch (Exception e) {
throw new RuntimeException("Is Bouncycastle correctly initialized?");
throw new RuntimeException("Is Bouncycastle correctly initialized?", e);
}
return key.getEncoded();
@@ -20,8 +20,10 @@ public class RecoverableSignature {
private int recId;
private byte[] r;
private byte[] s;
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");
@@ -40,16 +42,50 @@ public class RecoverableSignature {
*/
public RecoverableSignature(byte[] hash, byte[] tlvData) {
TinyBERTLV tlv = new TinyBERTLV(tlvData);
tlv.enterConstructed(TLV_SIGNATURE_TEMPLATE);
publicKey = tlv.readPrimitive(ApplicationInfo.TLV_PUB_KEY);
tlv.enterConstructed(TLV_ECDSA_TEMPLATE);
r = toUInt(tlv.readPrimitive(TinyBERTLV.TLV_INT));
s = toUInt(tlv.readPrimitive(TinyBERTLV.TLV_INT));
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);
this.r = toUInt(tlv.readPrimitive(TinyBERTLV.TLV_INT));
this.s = toUInt(tlv.readPrimitive(TinyBERTLV.TLV_INT));
this.compressed = false;
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;
this.s = s;
this.compressed = compressed;
this.recId = recId;
}
void calculateRecID(byte[] hash) {
recId = -1;
for (int i = 0; i < 4; i++) {
byte[] candidate = recoverFromSignature(i, new BigInteger(1, hash), new BigInteger(1, r), new BigInteger(1, s));
byte[] candidate = recoverFromSignature(i, hash, r, s, compressed);
if (Arrays.equals(candidate, publicKey)) {
recId = i;
@@ -62,7 +98,7 @@ public class RecoverableSignature {
}
}
private byte[] toUInt(byte[] signedInt) {
static byte[] toUInt(byte[] signedInt) {
if (signedInt[0] == 0) {
return Arrays.copyOfRange(signedInt, 1, signedInt.length);
} else {
@@ -114,7 +150,15 @@ public class RecoverableSignature {
return Ethereum.toEthereumAddress(publicKey);
}
private static byte[] recoverFromSignature(int recId, BigInteger e, BigInteger r, BigInteger s) {
static byte[] recoverFromSignature(int recId, byte[] hash, byte[] r, byte[] s, boolean compressed) {
BigInteger h = new BigInteger(1, hash);
BigInteger br = new BigInteger(1, r);
BigInteger bs = new BigInteger(1, s);
return recoverFromSignature(recId, h, br, bs, compressed);
}
static byte[] recoverFromSignature(int recId, BigInteger e, BigInteger r, BigInteger s, boolean compressed) {
BigInteger n = CURVE.getN();
BigInteger i = BigInteger.valueOf((long) recId / 2);
BigInteger x = r.add(i.multiply(n));
@@ -135,7 +179,7 @@ public class RecoverableSignature {
BigInteger srInv = rInv.multiply(s).mod(n);
BigInteger eInvrInv = rInv.multiply(eInv).mod(n);
ECPoint q = ECAlgorithms.sumOfTwoMultiplies(CURVE.getG(), eInvrInv, R, srInv);
return q.getEncoded(false);
return q.getEncoded(compressed);
}
private static ECPoint decompressKey(BigInteger xBN, boolean yBit) {
@@ -119,6 +119,15 @@ public class TinyBERTLV {
return TinyBERTLV.readVal(val, 0, val.length);
}
/**
* Returns all unread bytes in the TLV.
*
* @return all unread bytes
*/
byte[] peekUnread() {
return Arrays.copyOfRange(buffer, pos, buffer.length);
}
/**
* Low-level method to unread the last read tag. Only valid if the previous call was readTag(). Does nothing if the
* end of the TLV has been reached.
@@ -250,6 +250,16 @@ public class GlobalPlatformCommandSet {
return delete(Identifiers.NDEF_INSTANCE_AID);
}
/**
* Deletes the Ident applet instance.
*
* @return the card response
* @throws IOException communication error
*/
public APDUResponse deleteIdentInstance() throws IOException {
return delete(Identifiers.IDENT_INSTANCE_AID);
}
/**
* Deletes the Keycard package.
*
@@ -268,10 +278,7 @@ public class GlobalPlatformCommandSet {
* @throws IOException communication error
*/
public void deleteKeycardInstancesAndPackage() throws IOException, APDUException {
deleteNDEFInstance().checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
deleteKeycardInstance().checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
deleteCashInstance().checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
deleteKeycardPackage().checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
delete(Identifiers.PACKAGE_AID, (byte) 0x80).checkSW(APDUResponse.SW_OK, APDUResponse.SW_REFERENCED_DATA_NOT_FOUND);
}
/**
@@ -282,15 +289,27 @@ public class GlobalPlatformCommandSet {
* @throws IOException communication error.
*/
public APDUResponse delete(byte[] aid) throws IOException {
return delete(aid, (byte) 0);
}
/**
* Sends a DELETE APDU with the given AID
* @param aid the AID to the delete
* @param p2 the P2 value
* @return the raw card response
*
* @throws IOException communication error.
*/
public APDUResponse delete(byte[] aid, byte p2) throws IOException {
byte[] data = new byte[aid.length + 2];
data[0] = 0x4F;
data[1] = (byte) aid.length;
System.arraycopy(aid, 0, data, 2, aid.length);
APDUCommand cmd = new APDUCommand(0x80, INS_DELETE, 0, 0, data);
APDUCommand cmd = new APDUCommand(0x80, INS_DELETE, 0, p2, data);
return this.secureChannel.send(cmd);
}
}
/**
* Loads the Keycard package.
@@ -448,4 +467,14 @@ public class GlobalPlatformCommandSet {
public APDUResponse installCashApplet() throws IOException {
return installCashApplet(new byte[0]);
}
/**
* Installs the Ident applet.
*
* @return the card response
* @throws IOException communication error.
*/
public APDUResponse installIdentApplet() throws IOException {
return installForInstall(Identifiers.PACKAGE_AID, Identifiers.IDENT_AID, Identifiers.IDENT_INSTANCE_AID, new byte[0]);
}
}