Updated on 2026-08-14

This commit is contained in:
Tangem 2019-02-28 11:35:37 +03:00
parent 383a95bfc8
commit adfc3345cc
134 changed files with 11791 additions and 0 deletions

View file

@ -0,0 +1,289 @@
package com.tangem.card_common.reader;
import com.tangem.card_common.util.Log;
import com.tangem.card_common.util.PBKDF2;
import com.tangem.card_common.util.Util;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
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.MessageDigest;
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);
Security.addProvider(new EdDSASecurityProvider());
}
public enum Curve {secp256k1, ed25519}
public static PublicKey LoadPublicKey(Curve curve, byte[] publicKeyArray) throws Exception {
if (publicKeyArray == null) throw new Exception("Public key not specified!");
switch (curve) {
case secp256k1: {
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);
}
case ed25519: {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(publicKeyArray, spec);
return new EdDSAPublicKey(pubKey);
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception {
return LoadPublicKey(Curve.secp256k1, publicKeyArray);
}
public static boolean VerifySignature(Curve curve, byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
switch (curve) {
case secp256k1: {
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);
}
case ed25519: {
data = Util.calculateSHA512(data);
PublicKey publicKey = LoadPublicKey(curve, publicKeyArray);
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
Signature signatureInstance = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
signatureInstance.initVerify(publicKey);
signatureInstance.update(data);
return signatureInstance.verify(signature);
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static boolean VerifySignature(byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
return VerifySignature(Curve.secp256k1, publicKeyArray, data, signature);
}
public static boolean VerifySignature(String curveID, byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
Curve curve;
try {
curve = Curve.valueOf(curveID);
} catch (Exception e) {
throw new Exception("Card EC curve (" + curveID + ") isn't supported!");
}
return VerifySignature(curve, publicKeyArray, data, signature);
}
public static byte[] Signature(Curve curve, byte[] privateKeyArray, byte[] data) throws Exception {
switch (curve) {
case secp256k1: {
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;
}
case ed25519: {
data = Util.calculateSHA512(data);
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
//Signature sgr = Signature.getInstance("EdDSA", "I2P");
Signature signatureInstance = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPrivateKeySpec privateKeySpec = new EdDSAPrivateKeySpec(privateKeyArray, spec);
PrivateKey privateKey = new EdDSAPrivateKey(privateKeySpec);
signatureInstance.initSign(privateKey);
signatureInstance.update(data);
return signatureInstance.sign();
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static byte[] Signature(byte[] privateKeyArray, byte[] data) throws Exception {
return Signature(Curve.secp256k1, privateKeyArray, data);
}
public static byte[] GeneratePublicKey(Curve curve, byte[] privateKeyArray) throws Exception {
switch (curve) {
case secp256k1: {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1, privateKeyArray)).getEncoded(false);
return publicKeyArray;
}
case ed25519: {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
EdDSAPrivateKeySpec privateKeySpec = new EdDSAPrivateKeySpec(privateKeyArray, spec);
EdDSAPublicKeySpec publicKeySpec = new EdDSAPublicKeySpec(privateKeySpec.getA(), spec);
EdDSAPublicKey publicKey = new EdDSAPublicKey(publicKeySpec);
return publicKey.getAbyte();
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws Exception {
return GeneratePublicKey(Curve.secp256k1, privateKeyArray);
}
/**
* 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, boolean UsePKCS7) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
if (UsePKCS7) {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "SC");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] mEncryptedData = cipher.doFinal(data);
return mEncryptedData;
} else {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING", "SC");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] mEncryptedData = cipher.doFinal(data);
return mEncryptedData;
}
}
public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return Encrypt(key, data, true);
}
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

View file

@ -0,0 +1,276 @@
package com.tangem.card_common.reader;
import com.tangem.card_common.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;
}
}

View file

@ -0,0 +1,35 @@
package com.tangem.card_common.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;
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.card_common.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();
}

View file

@ -0,0 +1,137 @@
package com.tangem.card_common.reader;
import com.tangem.card_common.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());
}
}

View file

@ -0,0 +1,43 @@
package com.tangem.card_common.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 "???";
}
}

View file

@ -0,0 +1,67 @@
package com.tangem.card_common.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();
}
}

View file

@ -0,0 +1,237 @@
package com.tangem.card_common.reader;
import com.tangem.card_common.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());
}
}
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.card_common.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);
}
}

View file

@ -0,0 +1,72 @@
package com.tangem.card_common.reader;
/**
* Created by dvol on 23.06.2017.
*/
import com.tangem.card_common.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;
}
}