Updated on 2026-08-14
This commit is contained in:
parent
d4e76c4a00
commit
7737b890bb
124 changed files with 1067 additions and 706 deletions
|
|
@ -0,0 +1,176 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
import com.tangem.tangemcard.util.Log;
|
||||
import com.tangem.tangemcard.util.PBKDF2;
|
||||
import com.tangem.tangemcard.util.Util;
|
||||
|
||||
import org.spongycastle.asn1.ASN1EncodableVector;
|
||||
import org.spongycastle.asn1.ASN1Integer;
|
||||
import org.spongycastle.asn1.DERSequence;
|
||||
import org.spongycastle.jce.ECNamedCurveTable;
|
||||
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
|
||||
import org.spongycastle.jce.spec.ECPrivateKeySpec;
|
||||
import org.spongycastle.jce.spec.ECPublicKeySpec;
|
||||
import org.spongycastle.math.ec.ECPoint;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Created by dvol on 14.11.2017.
|
||||
*/
|
||||
|
||||
public class CardCrypto {
|
||||
static {
|
||||
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
|
||||
}
|
||||
|
||||
public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception {
|
||||
if( publicKeyArray==null ) throw new Exception("Public key not specified!");
|
||||
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
|
||||
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
|
||||
|
||||
ECPoint p1 = spec.getCurve().decodePoint(publicKeyArray);
|
||||
ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec);
|
||||
|
||||
return factory.generatePublic(keySpec);
|
||||
}
|
||||
|
||||
public static boolean VerifySignature(byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
|
||||
Signature signatureInstance = Signature.getInstance("SHA256withECDSA");
|
||||
PublicKey publicKey = LoadPublicKey(publicKeyArray);
|
||||
signatureInstance.initVerify(publicKey);
|
||||
signatureInstance.update(data);
|
||||
|
||||
ASN1EncodableVector v = new ASN1EncodableVector();
|
||||
int size = signature.length / 2;
|
||||
v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, 0, size))));
|
||||
v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, size, size * 2))));
|
||||
byte[] sigDer = new DERSequence(v).getEncoded();
|
||||
|
||||
return signatureInstance.verify(sigDer);
|
||||
}
|
||||
|
||||
|
||||
public static byte[] Signature(byte[] privateKeyArray, byte[] data) throws Exception {
|
||||
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
|
||||
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
|
||||
|
||||
ECPrivateKeySpec keySpecP = new ECPrivateKeySpec(new BigInteger(1,privateKeyArray), spec);
|
||||
|
||||
Signature signature = Signature.getInstance("SHA256withECDSA");
|
||||
|
||||
PrivateKey privateKey = factory.generatePrivate(keySpecP);
|
||||
signature.initSign(privateKey);
|
||||
signature.update(data);
|
||||
byte[] enc = signature.sign();
|
||||
|
||||
if (enc[0] != 0x30) throw new Exception("bad encoding 1");
|
||||
if ((enc[1] & 0x80) != 0) throw new Exception("unsupported length encoding 1");
|
||||
if (enc[2] != 0x02) throw new Exception("bad encoding 2");
|
||||
if ((enc[3] & 0x80) != 0) throw new Exception("unsupported length encoding 2");
|
||||
int rLength = enc[3];
|
||||
|
||||
if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3");
|
||||
if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3");
|
||||
int sLength = enc[5 + rLength];
|
||||
|
||||
|
||||
int sPos = 6 + rLength;
|
||||
byte[] res = new byte[64];
|
||||
if (rLength <= 32) {
|
||||
System.arraycopy(enc, 4, res, 32-rLength, rLength);
|
||||
rLength=32;
|
||||
} else if (rLength == 33 && enc[4] == 0) {
|
||||
rLength--;
|
||||
System.arraycopy(enc, 5, res, 0, rLength);
|
||||
} else {
|
||||
Log.e("cardCrypto","r-length:" + String.valueOf(rLength));
|
||||
Log.e("cardCrypto","s-length:" + String.valueOf(sLength));
|
||||
Log.e("cardCrypto","enc:" + Util.bytesToHex(enc));
|
||||
throw new Exception("unsupported r-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc));
|
||||
}
|
||||
if (sLength <= 32) {
|
||||
System.arraycopy(enc, sPos, res, rLength+32-sLength, sLength);
|
||||
sLength=32;
|
||||
} else if (sLength == 33 && enc[sPos] == 0) {
|
||||
System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1);
|
||||
} else {
|
||||
Log.e("cardCrypto","s-length:" + String.valueOf(sLength));
|
||||
Log.e("cardCrypto","r-length:" + String.valueOf(rLength));
|
||||
Log.e("cardCrypto","enc:" +Util.bytesToHex(enc));
|
||||
throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc));
|
||||
}
|
||||
|
||||
if(!VerifySignature(GeneratePublicKey(privateKeyArray), data, res))
|
||||
{
|
||||
throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength)+",s-length:" + String.valueOf(sLength)+",enc:" +Util.bytesToHex(enc)+",res:"+Util.bytesToHex(res));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws NoSuchProviderException, NoSuchAlgorithmException {
|
||||
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
|
||||
|
||||
|
||||
byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1,privateKeyArray)).getEncoded(false);
|
||||
|
||||
return publicKeyArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the PBKDF2 hash of a password.
|
||||
*
|
||||
* @param password the password to hash.
|
||||
* @param salt the salt
|
||||
* @param iterations the iteration count (slowness factor)
|
||||
* @return the PBDKF2 hash of the password
|
||||
*/
|
||||
public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations)
|
||||
throws InvalidKeyException {
|
||||
return PBKDF2.deriveKey(password, salt, iterations);
|
||||
}
|
||||
|
||||
public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
|
||||
{
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
|
||||
byte[] mEncryptedData = cipher.doFinal(data);
|
||||
return mEncryptedData;
|
||||
}
|
||||
|
||||
public static byte[] Decrypt(byte[] key, byte[] data, boolean UsePKCS7)
|
||||
throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, NoSuchProviderException {
|
||||
if (UsePKCS7) {
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
|
||||
byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length));
|
||||
return decryptedData;
|
||||
} else {
|
||||
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING");
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING");
|
||||
cipher.init(Cipher.DECRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
|
||||
byte[] decryptedData = cipher.doFinal(Arrays.copyOfRange(data, 0, data.length));
|
||||
return decryptedData;
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,276 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
import com.tangem.tangemcard.util.Util;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidAlgorithmParameterException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
|
||||
public class CommandApdu {
|
||||
|
||||
public static final byte ISO_CLA = (byte) 0x00;
|
||||
|
||||
protected String mCmdName;
|
||||
protected int mCla = 0x00;
|
||||
protected int mIns = 0x00;
|
||||
protected int mP1 = 0x00;
|
||||
protected int mP2 = 0x00;
|
||||
protected int mLc = 0x00;
|
||||
|
||||
protected byte[] mData = new byte[0];
|
||||
|
||||
protected int mLe = 0x00;
|
||||
protected boolean mLeUsed = false;
|
||||
protected TLVList tlvList = new TLVList();
|
||||
|
||||
public CommandApdu() {
|
||||
}
|
||||
|
||||
public CommandApdu(int cla, int ins, int p1, int p2) {
|
||||
setCommandName(ins);
|
||||
mCla = cla;
|
||||
mIns = ins;
|
||||
mP1 = p1;
|
||||
mP2 = p2;
|
||||
}
|
||||
|
||||
public CommandApdu(int cla, int ins, int p1, int p2, byte[] data) {
|
||||
setCommandName(ins);
|
||||
mCla = cla;
|
||||
mIns = ins;
|
||||
mLc = data.length;
|
||||
mP1 = p1;
|
||||
mP2 = p2;
|
||||
mData = data;
|
||||
}
|
||||
|
||||
public CommandApdu(INS ins) {
|
||||
setCommandName(ins.name());
|
||||
mCla = ISO_CLA;
|
||||
mIns = ins.Code;
|
||||
mP1 = 0;
|
||||
mP2 = 0;
|
||||
}
|
||||
|
||||
public CommandApdu(int cla, int ins, int p1, int p2, byte[] data, int le) {
|
||||
setCommandName(ins);
|
||||
mCla = cla;
|
||||
mIns = ins;
|
||||
mLc = data.length;
|
||||
mP1 = p1;
|
||||
mP2 = p2;
|
||||
mData = data;
|
||||
mLe = le;
|
||||
mLeUsed = true;
|
||||
}
|
||||
|
||||
public CommandApdu(int cla, int ins, int p1, int p2, int le) {
|
||||
setCommandName(ins);
|
||||
mCla = cla;
|
||||
mIns = ins;
|
||||
mP1 = p1;
|
||||
mP2 = p2;
|
||||
mLe = le;
|
||||
mLeUsed = true;
|
||||
}
|
||||
|
||||
public void setCommandName(String cmdName) {
|
||||
mCmdName = cmdName;
|
||||
}
|
||||
|
||||
private void setCommandName(int ins) {
|
||||
INS ins1 = INS.ByCode(ins);
|
||||
if (ins1 != null) {
|
||||
mCmdName = ins1.toString();
|
||||
} else {
|
||||
mCmdName = String.format("INS[%2X]", ins);
|
||||
}
|
||||
}
|
||||
|
||||
public String getCommandName() {
|
||||
return mCmdName;
|
||||
}
|
||||
|
||||
public void setP1(int p1) {
|
||||
mP1 = p1;
|
||||
}
|
||||
|
||||
public void setP2(int p2) {
|
||||
mP2 = p2;
|
||||
}
|
||||
|
||||
public void setData(byte[] data) {
|
||||
mLc = data.length;
|
||||
mData = data;
|
||||
}
|
||||
|
||||
public void addTLV(TLV.Tag tag, byte[] value) {
|
||||
tlvList.add(new TLV(tag, value));
|
||||
}
|
||||
|
||||
public void addTLV_U8(TLV.Tag tag, int U8) {
|
||||
addTLV(tag, new byte[]{(byte) U8});
|
||||
}
|
||||
|
||||
public void addTLV_U16(TLV.Tag tag, int U16) {
|
||||
addTLV(tag, Util.intToByteArray2(U16));
|
||||
}
|
||||
|
||||
public void addTLV_U32(TLV.Tag tag, int U32) {
|
||||
addTLV(tag, Util.intToByteArray4(U32));
|
||||
}
|
||||
|
||||
public void setLe(int le) {
|
||||
mLe = le;
|
||||
mLeUsed = true;
|
||||
}
|
||||
|
||||
public int getP1() {
|
||||
return mP1;
|
||||
}
|
||||
|
||||
public int getP2() {
|
||||
return mP2;
|
||||
}
|
||||
|
||||
public int getLc() {
|
||||
return mLc;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
public TLVList getTLVs() {
|
||||
return tlvList;
|
||||
}
|
||||
|
||||
public int getLe() {
|
||||
return mLe;
|
||||
}
|
||||
|
||||
public static String toString(byte[] cmdApdu, int Lc) {
|
||||
String cmd = Util.bytesToHex(cmdApdu);
|
||||
if (Lc == 0) return cmd;
|
||||
return cmd.substring(0, 8) + " " + cmd.substring(8, 10) + " " +
|
||||
cmd.substring(10, 10 + Lc * 2) + " " + cmd.substring(10 + Lc * 2, cmd.length());
|
||||
}
|
||||
|
||||
|
||||
public void Crypt(byte[] key) throws IllegalBlockSizeException, InvalidKeyException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, InvalidAlgorithmParameterException, NoSuchProviderException {
|
||||
if (tlvList.size() != 0) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
for (TLV tlv : tlvList) {
|
||||
try {
|
||||
tlv.WriteToStream(stream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mData = stream.toByteArray();
|
||||
byte[] crc = Util.calculateCRC16(mData);
|
||||
stream = new ByteArrayOutputStream();
|
||||
stream.write(Util.intToByteArray2(mData.length));
|
||||
stream.write(crc);
|
||||
stream.write(mData);
|
||||
mData = stream.toByteArray();
|
||||
|
||||
byte[] mEncryptedData = CardCrypto.Encrypt(key, mData);
|
||||
|
||||
mData = mEncryptedData;
|
||||
mLc = mData.length;
|
||||
|
||||
tlvList.clear();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
int length = 4; // CLA, INS, P1, P2
|
||||
|
||||
if (tlvList.size() != 0) {
|
||||
mData = tlvList.toBytes();
|
||||
mLc = mData.length;
|
||||
}
|
||||
|
||||
if (mData.length != 0) {
|
||||
length += 1; // LC
|
||||
if (mLc >= 256)
|
||||
length += 2;
|
||||
length += mData.length; // DATA
|
||||
}
|
||||
if (mLeUsed) {
|
||||
length += 1; // LE
|
||||
if (mLc >= 256)
|
||||
length += 2;
|
||||
}
|
||||
|
||||
byte[] apdu = new byte[length];
|
||||
|
||||
int index = 0;
|
||||
apdu[index] = (byte) mCla;
|
||||
index++;
|
||||
apdu[index] = (byte) mIns;
|
||||
index++;
|
||||
apdu[index] = (byte) mP1;
|
||||
index++;
|
||||
apdu[index] = (byte) mP2;
|
||||
index++;
|
||||
if (mLc != 0) {
|
||||
if (mLc < 256) {
|
||||
apdu[index] = (byte) mLc;
|
||||
index++;
|
||||
} else {
|
||||
apdu[index] = 0;
|
||||
index++;
|
||||
apdu[index] = (byte) (mLc >> 8);
|
||||
index++;
|
||||
apdu[index] = (byte) (mLc & 0xFF);
|
||||
index++;
|
||||
}
|
||||
|
||||
System.arraycopy(mData, 0, apdu, index, mData.length);
|
||||
index += mData.length;
|
||||
}
|
||||
if (mLeUsed) {
|
||||
if (mLc < 256) {
|
||||
apdu[index] += (byte) mLe; // LE
|
||||
} else {
|
||||
apdu[index] = 0;
|
||||
index++;
|
||||
apdu[index] = (byte) (mLe >> 8);
|
||||
index++;
|
||||
apdu[index] = (byte) (mLe & 0xFF);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
return apdu;
|
||||
}
|
||||
|
||||
public CommandApdu clone() {
|
||||
CommandApdu apdu = new CommandApdu();
|
||||
apdu.setCommandName(mCmdName);
|
||||
apdu.mCla = mCla;
|
||||
apdu.mIns = mIns;
|
||||
apdu.mP1 = mP1;
|
||||
apdu.mP2 = mP2;
|
||||
apdu.mLc = mLc;
|
||||
apdu.mData = new byte[mData.length];
|
||||
System.arraycopy(mData, 0, apdu.mData, 0, mData.length);
|
||||
apdu.mLe = mLe;
|
||||
apdu.mLeUsed = mLeUsed;
|
||||
apdu.tlvList = new TLVList(tlvList);
|
||||
return apdu;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
/**
|
||||
* Created by dvol on 07.03.2018.
|
||||
*/
|
||||
public enum INS {
|
||||
Unknown(0x00),
|
||||
Read(0xF2),
|
||||
VerifyCard(0xF3),
|
||||
ValidateCard(0xF4),
|
||||
VerifyCode(0xF5),
|
||||
WriteIssuerData(0xF6),
|
||||
GetIssuerData(0xF7),
|
||||
CreateWallet(0xF8),
|
||||
CheckWallet(0xF9),
|
||||
SwapPIN(0xFA),
|
||||
Sign(0xFB),
|
||||
PurgeWallet(0xFC),
|
||||
Activate(0xFE),
|
||||
OpenSession(0xFF);
|
||||
|
||||
INS(int Code) {
|
||||
this.Code = Code;
|
||||
}
|
||||
|
||||
public int Code;
|
||||
|
||||
public static INS ByCode(int Code) {
|
||||
INS[] allINS = INS.values();
|
||||
for (INS i : allINS) {
|
||||
if (i.Code == Code) return i;
|
||||
}
|
||||
return Unknown;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface NfcReader {
|
||||
byte[] getId();
|
||||
|
||||
void setTimeout(int timeout)throws IOException;
|
||||
|
||||
int getTimeout();
|
||||
|
||||
byte[] transceive(byte[] data) throws IOException;
|
||||
|
||||
void ignoreTag() throws IOException;
|
||||
|
||||
void notifyReadResult(boolean success);
|
||||
|
||||
void connect();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
import com.tangem.tangemcard.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ResponseApdu {
|
||||
|
||||
private int mSw1 = 0x00;
|
||||
private int mSw2 = 0x00;
|
||||
|
||||
private byte[] mData = new byte[0];
|
||||
private byte[] mBytes = new byte[0];
|
||||
|
||||
private TLVList tlvList = new TLVList();
|
||||
|
||||
private String parseError = null;
|
||||
|
||||
private ResponseApdu() {
|
||||
}
|
||||
|
||||
ResponseApdu(byte[] respApdu) {
|
||||
if (respApdu.length < 2) {
|
||||
return;
|
||||
}
|
||||
if (respApdu.length > 2) {
|
||||
mData = new byte[respApdu.length - 2];
|
||||
System.arraycopy(respApdu, 0, mData, 0, respApdu.length - 2);
|
||||
|
||||
try {
|
||||
tlvList = TLVList.fromBytes(mData);
|
||||
} catch (TLVException e) {
|
||||
parseError = e.getMessage();
|
||||
}
|
||||
|
||||
}else{
|
||||
tlvList=new TLVList();
|
||||
parseError=null;
|
||||
}
|
||||
mSw1 = 0x00FF & respApdu[respApdu.length - 2];
|
||||
mSw2 = 0x00FF & respApdu[respApdu.length - 1];
|
||||
mBytes = respApdu;
|
||||
}
|
||||
|
||||
public static boolean isStatusWord(byte[] respApdu, int SW)
|
||||
{
|
||||
int mSw1 = 0x00FF & respApdu[respApdu.length - 2];
|
||||
int mSw2 = 0x00FF & respApdu[respApdu.length - 1];
|
||||
return ((mSw1 << 8) | mSw2)==SW;
|
||||
}
|
||||
|
||||
public static ResponseApdu Decrypt(byte[] data, byte[] key) throws Exception{
|
||||
|
||||
if( data.length==2 )
|
||||
{
|
||||
ResponseApdu responseApdu = new ResponseApdu();
|
||||
responseApdu.mSw1 = ((int) data[0] & 0xFF);
|
||||
responseApdu.mSw2 = ((int) data[1] & 0xFF);
|
||||
return responseApdu;
|
||||
}else if( data.length>=18 ){
|
||||
byte[] decryptedData = CardCrypto.Decrypt(key, Arrays.copyOfRange(data, 0, data.length - 2),true);
|
||||
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(decryptedData);
|
||||
byte[] baLength = new byte[2];
|
||||
inputStream.read(baLength);
|
||||
int length = ((int) baLength[0] & 0xFF) * 256 + ((int) baLength[1] & 0xFF);
|
||||
if (length > decryptedData.length - 4)
|
||||
throw new Exception("Can't decrypt - data size invalid");
|
||||
byte[] baCRC = new byte[2];
|
||||
inputStream.read(baCRC);
|
||||
byte[] answerData = new byte[length];
|
||||
inputStream.read(answerData);
|
||||
byte[] crc = Util.calculateCRC16(answerData);
|
||||
if (!Arrays.equals(baCRC, crc)) throw new Exception("Can't decrypt - crc invalid");
|
||||
|
||||
ResponseApdu responseApdu = new ResponseApdu();
|
||||
responseApdu.mSw1 = ((int) data[data.length - 2] & 0xFF);
|
||||
responseApdu.mSw2 = ((int) data[data.length - 1] & 0xFF);
|
||||
responseApdu.mBytes = data;
|
||||
responseApdu.mData = answerData;
|
||||
|
||||
try {
|
||||
responseApdu.tlvList = TLVList.fromBytes(answerData);
|
||||
} catch (TLVException e) {
|
||||
responseApdu.parseError = e.getMessage();
|
||||
}
|
||||
|
||||
return responseApdu;
|
||||
}else{
|
||||
throw new Exception("Can't decrypt - data size to small");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getSW1() {
|
||||
return mSw1;
|
||||
}
|
||||
|
||||
public int getSW2() {
|
||||
return mSw2;
|
||||
}
|
||||
|
||||
public int getSW1SW2() {
|
||||
return (mSw1 << 8) | mSw2;
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return mData;
|
||||
}
|
||||
|
||||
public TLVList getTLVs() {
|
||||
return tlvList;
|
||||
}
|
||||
|
||||
public boolean isParsedWithError() {
|
||||
return parseError != null;
|
||||
}
|
||||
public String getParseErroMessage() {
|
||||
return parseError;
|
||||
}
|
||||
public byte[] toBytes() {
|
||||
return mBytes;
|
||||
}
|
||||
|
||||
public boolean isStatus(int sw1sw2) {
|
||||
if (getSW1SW2() == sw1sw2) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public String getSW1SW2Description() {
|
||||
return SW.getDescription(getSW1SW2());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
/**
|
||||
* Created by dvol on 07.03.2018.
|
||||
*/
|
||||
|
||||
public class SW {
|
||||
public static final int PROCESS_COMPLETED = 0x9000;
|
||||
public static final int INVALID_PARAMS = 0x6A86;
|
||||
public static final int ERROR_PROCESSING_COMMAND = 0x6286;
|
||||
public static final int INVALID_STATE = 0x6985;
|
||||
public static final int PINS_NOT_CHANGED = PROCESS_COMPLETED;
|
||||
public static final int PIN1_CHANGED = PROCESS_COMPLETED + 0x0001;
|
||||
public static final int PIN2_CHANGED = PROCESS_COMPLETED + 0x0002;
|
||||
public static final int PINS_CHANGED = PROCESS_COMPLETED + 0x0003;
|
||||
public static final int INS_NOT_SUPPORTED = 0x6D00;
|
||||
public static final int NEED_ENCRYPTION = 0x6982;
|
||||
public static final int NEED_PAUSE = 0x9789;
|
||||
|
||||
public static String getDescription(int sw) {
|
||||
switch (sw) {
|
||||
case ERROR_PROCESSING_COMMAND:
|
||||
return "SW_ERROR_PROCESSING_COMMAND";
|
||||
case INVALID_PARAMS:
|
||||
return "SW_INVALID_PARAMS";
|
||||
case INVALID_STATE:
|
||||
return "SW_INVALID_STATE";
|
||||
case INS_NOT_SUPPORTED:
|
||||
return "SW_INS_NOT_SUPPORTED";
|
||||
case NEED_ENCRYPTION:
|
||||
return "SW_NEED_ENCRYPTION";
|
||||
case PIN1_CHANGED:
|
||||
return "SW_PIN1_CHANGED";
|
||||
case PIN2_CHANGED:
|
||||
return "SW_PIN2_CHANGED";
|
||||
case PINS_CHANGED:
|
||||
return "SW_PINS_CHANGED";
|
||||
case PROCESS_COMPLETED:
|
||||
return "SW_PROCESS_COMPLETED";
|
||||
}
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
/**
|
||||
* Created by dvol on 07.03.2018.
|
||||
*/
|
||||
|
||||
public class SettingsMask {
|
||||
public static final int IsReusable = 0x0001;
|
||||
public static final int UseActivation = 0x0002;
|
||||
public static final int ForbidPurgeWallet = 0x0004;
|
||||
public static final int UseBlock = 0x0008;
|
||||
|
||||
public static final int AllowSwapPIN = 0x0010;
|
||||
public static final int AllowSwapPIN2 = 0x0020;
|
||||
public static final int UseCVC = 0x0040;
|
||||
public static final int ForbidDefaultPIN = 0x0080;
|
||||
|
||||
public static final int UseOneCommandAtTime = 0x0100;
|
||||
public static final int UseNDEF = 0x0200;
|
||||
public static final int UseDynamicNDEF = 0x0400;
|
||||
public static final int SmartSecurityDelay = 0x0800;
|
||||
|
||||
public static final int Protocol_AllowUnencrypted = 0x1000;
|
||||
public static final int Protocol_AllowStaticEncryption = 0x2000;
|
||||
|
||||
public static final int ProtectIssuerDataAgainstReplay = 0x4000;
|
||||
|
||||
public static final int AllowSelectBlockchain = 0x8000;
|
||||
|
||||
public static final int DisablePrecomputedNDEF = 0x00010000;
|
||||
|
||||
public static String getDescription(int iValue) {
|
||||
StringBuilder sb=new StringBuilder();
|
||||
sb.append("[");
|
||||
if ((iValue & SettingsMask.AllowSwapPIN) != 0) sb.append("AllowSwapPIN, ");
|
||||
if ((iValue & SettingsMask.AllowSwapPIN2) != 0)
|
||||
sb.append("AllowSwapPIN2, ");
|
||||
if ((iValue & SettingsMask.ForbidDefaultPIN) != 0)
|
||||
sb.append("ForbidDefaultPIN, ");
|
||||
if ((iValue & SettingsMask.IsReusable) != 0) sb.append("IsReusable, ");
|
||||
if ((iValue & SettingsMask.Protocol_AllowStaticEncryption) != 0)
|
||||
sb.append("Protocol_AllowStaticEncryption, ");
|
||||
if ((iValue & SettingsMask.Protocol_AllowUnencrypted) != 0)
|
||||
sb.append("Protocol_AllowUnencrypted, ");
|
||||
if ((iValue & SettingsMask.SmartSecurityDelay) != 0)
|
||||
sb.append("SmartSecurityDelay, ");
|
||||
if ((iValue & SettingsMask.UseActivation) != 0)
|
||||
sb.append("UseActivation, ");
|
||||
if ((iValue & SettingsMask.UseBlock) != 0) sb.append("UseBlock, ");
|
||||
if ((iValue & SettingsMask.UseCVC) != 0) sb.append("UseCVC, ");
|
||||
if ((iValue & SettingsMask.UseDynamicNDEF) != 0)
|
||||
sb.append("UseDynamicNDEF, ");
|
||||
if ((iValue & SettingsMask.UseNDEF) != 0) sb.append("UseNDEF, ");
|
||||
if ((iValue & SettingsMask.UseOneCommandAtTime) != 0)
|
||||
sb.append("UseOneCommandAtTime, ");
|
||||
if ((iValue & SettingsMask.ProtectIssuerDataAgainstReplay) != 0)
|
||||
sb.append("ProtectIssuerDataAgainstReplay, ");
|
||||
if ((iValue & SettingsMask.ForbidPurgeWallet) != 0) sb.append("ForbidPurgeWallet, ");
|
||||
if ((iValue & SettingsMask.AllowSelectBlockchain) != 0) sb.append("AllowSelectBlockchain, ");
|
||||
if ((iValue & SettingsMask.DisablePrecomputedNDEF) != 0) sb.append("DisablePrecomputedNDEF, ");
|
||||
|
||||
if (sb.length() > 1) sb.delete(sb.length() - 2, sb.length());
|
||||
sb.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,237 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
import com.tangem.tangemcard.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Created by dvol on 23.06.2017.
|
||||
*/
|
||||
|
||||
public class TLV {
|
||||
public enum Tag {
|
||||
TAG_Unknown(0x00),
|
||||
TAG_CardID(0x01),
|
||||
TAG_Status(0x02),
|
||||
TAG_CardPublicKey(0x03),
|
||||
TAG_CardSignature(0x04),
|
||||
TAG_CurveID(0x05),
|
||||
TAG_HashAlgID(0x06),
|
||||
TAG_SigningMethod(0x07),
|
||||
TAG_MaxSignatures(0x08),
|
||||
TAG_PauseBeforePIN2(0x09),
|
||||
TAG_SettingsMask(0x0A),
|
||||
TAG_CardData(0x0C),
|
||||
TAG_NDEFData(0x0D),
|
||||
TAG_Health(0x0F),
|
||||
|
||||
TAG_PIN(0x10),
|
||||
TAG_PIN2(0x11),
|
||||
TAG_NewPIN(0x12),
|
||||
TAG_NewPIN2(0x13),
|
||||
TAG_NewPIN_Hash(0x14),
|
||||
TAG_NewPIN2_Hash(0x15),
|
||||
TAG_Challenge(0x16),
|
||||
TAG_Salt(0x17),
|
||||
TAG_ValidationCounter(0x18),
|
||||
TAG_CVC(0x19),
|
||||
|
||||
TAG_Session_Key_A(0x1A),
|
||||
TAG_Session_Key_B(0x1B),
|
||||
TAG_Pause(0x1C),
|
||||
|
||||
TAG_Manufacture_ID(0x20),
|
||||
TAG_Manufacturer_Signature(0x21),
|
||||
|
||||
TAG_Issuer_Data_PublicKey(0x30),
|
||||
TAG_Issuer_Transaction_PublicKey(0x31),
|
||||
TAG_Issuer_Data(0x32),
|
||||
TAG_Issuer_Data_Signature(0x33),
|
||||
TAG_Issuer_Transaction_Signature(0x34),
|
||||
TAG_Issuer_Data_Counter(0x35),
|
||||
|
||||
TAG_IsActivated(0x3A),
|
||||
TAG_ActivationSeed(0x3B),
|
||||
TAG_ResetPIN(0x36),
|
||||
|
||||
TAG_CodePageAddress(0x40),
|
||||
TAG_CodePageCount(0x41),
|
||||
TAG_CodeHash(0x42),
|
||||
|
||||
TAG_TrOut_Hash(0x50),
|
||||
TAG_TrOut_HashSize(0x51),
|
||||
TAG_TrOut_Raw(0x52),
|
||||
|
||||
TAG_Wallet_PublicKey(0x60),
|
||||
TAG_Signature(0x61),
|
||||
TAG_RemainingSignatures(0x62),
|
||||
TAG_SignedHashes(0x63),
|
||||
|
||||
TAG_Firmware(0x80),
|
||||
TAG_Batch(0x81),
|
||||
TAG_ManufactureDateTime(0x82),
|
||||
TAG_Issuer_ID(0x83),
|
||||
TAG_Blockchain_ID(0x84),
|
||||
TAG_Manufacturer_PublicKey(0x85),
|
||||
TAG_CardID_Manufacturer_Signature(0x86),
|
||||
|
||||
TAG_Token_Symbol(0xA0),
|
||||
TAG_Token_Contract_Address(0xA1),
|
||||
TAG_Token_Decimal(0xA2),
|
||||
TAG_Denomination(0xC0),
|
||||
TAG_ValidatedBalance(0xC1),
|
||||
TAG_LastSign_Date(0xC2),
|
||||
TAG_DenominationText(0xC3);
|
||||
|
||||
|
||||
Tag(int Code) {
|
||||
this.Code = Code;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return Code;
|
||||
}
|
||||
|
||||
private int Code;
|
||||
|
||||
public static Tag ByCode(int Code) {
|
||||
Tag[] allTags = Tag.values();
|
||||
for (Tag t : allTags) if (t.getCode() == Code) return t;
|
||||
return TAG_Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
private Tag tag;
|
||||
|
||||
public Tag getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public byte[] Value;
|
||||
|
||||
public TLV(Tag tag, byte[] value) {
|
||||
this.tag = tag;
|
||||
this.Value = value;
|
||||
}
|
||||
|
||||
public void WriteToStream(ByteArrayOutputStream stream) throws IOException {
|
||||
stream.write(tag.getCode());
|
||||
if (Value != null) {
|
||||
if (Value.length > 0xFE) {
|
||||
stream.write(0xFF);
|
||||
stream.write((Value.length >> 8) & 0xFF);
|
||||
stream.write(Value.length & 0xFF);
|
||||
} else {
|
||||
stream.write(Value.length & 0xFF);
|
||||
}
|
||||
stream.write(Value);
|
||||
} else {
|
||||
stream.write(0x00);
|
||||
}
|
||||
}
|
||||
|
||||
public static TLV ReadFromStream(ByteArrayInputStream stream) throws IOException {
|
||||
int code = stream.read();
|
||||
if (code == -1) return null;
|
||||
int len = stream.read();
|
||||
if (len == -1)
|
||||
throw new IOException("Can't read TLV");
|
||||
if (len == 0xFF) {
|
||||
int lenH = stream.read();
|
||||
if (lenH == -1)
|
||||
throw new IOException("Can't read TLV");
|
||||
len = stream.read();
|
||||
if (len == -1)
|
||||
throw new IOException("Can't read TLV");
|
||||
len |= (lenH << 8);
|
||||
}
|
||||
byte[] value = new byte[len];
|
||||
if (len > 0) {
|
||||
if (len != stream.read(value)) {
|
||||
throw new IOException("Can't read TLV");
|
||||
}
|
||||
}
|
||||
Tag tag = Tag.ByCode(code);
|
||||
TLV result = new TLV(tag, value);
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getAsInt() {
|
||||
return Util.byteArrayToInt(Value);
|
||||
}
|
||||
|
||||
public String getAsHexString() {
|
||||
return Util.bytesToHex(Value);
|
||||
}
|
||||
|
||||
public String getAsString() {
|
||||
if( Value.length==0 ) return "";
|
||||
|
||||
if (Value[Value.length - 1] == 0) {
|
||||
String s1 = new String(Arrays.copyOfRange(Value, 0, Value.length - 1), Charset.forName("utf-8"));
|
||||
return s1.trim();
|
||||
} else {
|
||||
String s1 = new String(Value, Charset.forName("utf-8"));
|
||||
return s1.trim();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
switch (tag) {
|
||||
case TAG_CardData:
|
||||
case TAG_Issuer_Data: {
|
||||
try {
|
||||
TLVList tlvSub = TLVList.fromBytes(Value);
|
||||
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), tlvSub.toString());
|
||||
} catch (TLVException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (Value != null) {
|
||||
return String.format("%s[%d]: %s (non TLV)", tag.name(), Value.length, Util.bytesToHex(Value));
|
||||
} else {
|
||||
return String.format("%s[]: [[NULL]]", tag.name());
|
||||
}
|
||||
}
|
||||
case TAG_CurveID:
|
||||
case TAG_HashAlgID:
|
||||
case TAG_Blockchain_ID:
|
||||
case TAG_Manufacture_ID:
|
||||
case TAG_Firmware:
|
||||
case TAG_Issuer_ID:
|
||||
case TAG_Token_Symbol:
|
||||
if (Value != null) {
|
||||
return String.format("%s[%d]: %s(%s)", tag.name(), Value.length, Util.bytesToHex(Value), getAsString());
|
||||
} else {
|
||||
return String.format("%s[]: [[NULL]]", tag.name());
|
||||
}
|
||||
case TAG_SettingsMask: {
|
||||
StringBuilder sb=new StringBuilder();
|
||||
if( Value!=null ) {
|
||||
try {
|
||||
int iValue = Util.byteArrayToInt(Value);
|
||||
return String.format("%s[%d]: %s (%s)", tag.name(), Value.length, Util.bytesToHex(Value), SettingsMask.getDescription(iValue));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value));
|
||||
}
|
||||
}else{
|
||||
return String.format("%s[]: [[NULL]]", tag.name());
|
||||
}
|
||||
}
|
||||
default:
|
||||
if (Value != null) {
|
||||
return String.format("%s[%d]: %s", tag.name(), Value.length, Util.bytesToHex(Value));
|
||||
} else {
|
||||
return String.format("%s[]: [[NULL]]", tag.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
public class TLVException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public TLVException(String message){
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TLVException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public TLVException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.tangemcard.reader;
|
||||
|
||||
/**
|
||||
* Created by dvol on 23.06.2017.
|
||||
*/
|
||||
|
||||
import com.tangem.tangemcard.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
public class TLVList extends ArrayList<TLV> {
|
||||
public String getParsedTLVs(String Prefix) {
|
||||
String parsed = "";
|
||||
for (int i = 0; i < size(); i++) {
|
||||
parsed += Prefix + this.get(i).toString() + (i < size() - 1 ? "\n" : "");
|
||||
}
|
||||
return parsed;//.substring(0,parsed.length()-2);
|
||||
}
|
||||
|
||||
public TLVList() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TLVList(Collection<? extends TLV> c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public TLV getTLV(TLV.Tag tag) {
|
||||
for (TLV tlv : this) {
|
||||
if (tlv.getTag() == tag) return tlv;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getTagAsInt(TLV.Tag tag) {
|
||||
TLV tlv = getTLV(tag);
|
||||
return Util.byteArrayToInt(tlv.Value);
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
for (TLV tlv : this) {
|
||||
try {
|
||||
tlv.WriteToStream(stream);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return stream.toByteArray();
|
||||
}
|
||||
|
||||
public static TLVList fromBytes(byte[] mData) throws TLVException {
|
||||
TLVList tlvList = new TLVList();
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(mData);
|
||||
TLV tlv = null;
|
||||
do {
|
||||
try {
|
||||
tlv = TLV.ReadFromStream(stream);
|
||||
if (tlv != null) tlvList.add(tlv);
|
||||
} catch (IOException e) {
|
||||
throw new TLVException("TLVError: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
while (tlv != null);
|
||||
return tlvList;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue