Updated on 2026-08-14

This commit is contained in:
Tangem 2018-05-28 13:56:12 +03:00
commit 5e7300987c
243 changed files with 28116 additions and 0 deletions

View file

@ -0,0 +1,243 @@
package com.tangem.cardReader;
import android.util.Log;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
import org.spongycastle.crypto.params.ECDomainParameters;
import org.spongycastle.crypto.params.ECPrivateKeyParameters;
import org.spongycastle.crypto.signers.ECDSASigner;
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.Signature;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
/**
* Created by dvol on 14.11.2017.
*/
public class CardCrypto {
public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception {
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 boolean isCanonical(BigInteger s) {
//
// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
//
// BigInteger HALF_CURVE_ORDER = spec.getN().shiftRight(1);
// return s.compareTo(HALF_CURVE_ORDER) <= 0;
// }
//
// public static BigInteger toCanonicalised(BigInteger s) {
//
// // The order of the curve is the number of valid points that exist on that curve. If S is in the upper
// // half of the number of valid points, then bring it back to the lower half. Otherwise, imagine that
// // N = 10
// // s = 8, so (-8 % 10 == 2) thus both (r, 8) and (r, 2) are valid solutions.
// // 10 - 8 == 2, giving us always the latter solution, which is canonical.
// ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
// if(!isCanonical(s)) {
// BigInteger canon = spec.getN().subtract(s);
// //Log.e("TX_SIGN", "non Canonical S");
// return canon;
// }
//
// return s;
//
// }
//
// public static BigInteger[] calcSign2(byte[] priv, byte[] hash)
// {
// ECDSASigner signer = new ECDSASigner();
// BigInteger d = new BigInteger(priv);
//
// ECNamedCurveParameterSpec CURVE_PARAMS = ECNamedCurveTable.getParameterSpec("secp256k1");
// ECDomainParameters CURVE = new ECDomainParameters(CURVE_PARAMS.getCurve(), CURVE_PARAMS.getG(), CURVE_PARAMS.getN(),
// CURVE_PARAMS.getH());
// ECPrivateKeyParameters params = new ECPrivateKeyParameters(d, CURVE);
// //ECPublicKeyParameters params = new ECPublicKeyParameters(CURVE.getCurve().decodePoint(pub), CURVE);
// signer.init(true, params);
// BigInteger[] rs = signer.generateSignature(hash);
// return rs;
//
// }
//
// public static byte[] Signature3(byte[] privateKeyArray, byte[] data) throws Exception
// {
// byte[] hash=Util.calculateSHA256(data);
// BigInteger[] signBI=calcSign2(privateKeyArray, hash);
// //signBI[0]=toCanonicalised(signBI[0]);
// signBI[1]=toCanonicalised(signBI[1]);
// byte[] r = signBI[0].toByteArray();
// byte[] s = signBI[1].toByteArray();
//
// byte[] res = new byte[64];
// if( r.length==32 ) {
// System.arraycopy(r, 0, res, 0, r.length);
// }else if( r.length==33 && r[0]==0 ){
// System.arraycopy(r, 1, res, 0, r.length-1);
// }else {
// throw new Exception("unsupported r-length");
// }
// if( s.length==32 ) {
// System.arraycopy(s, 0, res, 32, 32);
// }else{
// throw new Exception("unsupported s-length");
// }
// return res;
// }
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 NoSuchAlgorithmException, InvalidKeySpecException, 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) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
try {
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;
}
catch (Exception e)
{
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 ));
Log.e("decrypt",Util.bytesToHex(decryptedData));
throw e;
}
}
}

View file

@ -0,0 +1,954 @@
package com.tangem.cardReader;
import android.content.Context;
import android.nfc.Tag;
import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
import android.util.Log;
import com.tangem.wallet.CoinEngine;
import com.tangem.wallet.CoinEngineFactory;
import com.tangem.wallet.Issuer;
import com.tangem.wallet.Tangem_Card;
import com.tangem.wallet.Manufacturer;
import org.spongycastle.jce.ECNamedCurveTable;
import org.spongycastle.jce.interfaces.ECPublicKey;
import org.spongycastle.jce.spec.ECNamedCurveParameterSpec;
import org.spongycastle.math.ec.ECPoint;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.util.Calendar;
import javax.crypto.KeyAgreement;
/**
* Created by dvol on 14.07.2017.
*/
public class CardProtocol {
public interface Notifications {
void OnReadStart(CardProtocol cardProtocol);
void OnReadProgress(CardProtocol cardProtocol, int progress);
void OnReadFinish(CardProtocol cardProtocol);
void OnReadCancel();
void OnReadWait(int msec);
void OnReadBeforeRequest(int timeout);
void OnReadAfterRequest();
}
public void setError(Exception error) {
mError = error;
}
public Tangem_Card getCard() {
return mCard;
}
public Exception getError() {
return mError;
}
private static final String logTag = "CardProtocol";
public static final int SW_PIN_ERROR = SW.INVALID_PARAMS;
public static final String DefaultPIN = "000000";
public static final String DefaultPIN2 = "000";
protected IsoDep mIsoDep;
public Tag getTag() {
return mIsoDep.getTag();
}
protected String mPIN;
protected Notifications mNotifications;
public void setPIN(String PIN) {
mPIN = PIN;
if (mCard != null) {
mCard.setPIN(PIN);
}
}
protected Tangem_Card mCard;
protected Exception mError;
protected Context mContext;
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
public CardProtocol(Context context, IsoDep isoDep, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mPIN = null;
mCard = new Tangem_Card(Util.byteArrayToHexString(mIsoDep.getTag().getId()));
}
public CardProtocol(Context context, IsoDep isoDep, Tangem_Card card, Notifications notifications) {
mContext = context;
mIsoDep = isoDep;
mNotifications = notifications;
mPIN = card.getPIN();
mCard = card;
}
public static class TangemException extends Exception {
public TangemException(String message) {
super(message);
}
}
public static class TangemException_InvalidPIN extends TangemException {
public TangemException_InvalidPIN(String message) {
super(message);
}
}
public static class TangemException_NeedPause extends TangemException {
public TangemException_NeedPause(String message) {
super(message);
}
}
public static class TangemException_ExtendedLengthNotSupported extends TangemException {
public TangemException_ExtendedLengthNotSupported(String message) {
super(message);
}
}
public byte[] GetUID() {
if (mIsoDep == null || mIsoDep.getTag() == null) return null;
return mIsoDep.getTag().getId();
}
public int getTimeout() {
if (mIsoDep == null || mIsoDep.getTag() == null) return 60000;
return mIsoDep.getTimeout();
}
protected byte[] protocolKey;
public void resetProtocolKey() {
protocolKey = null;
}
public void CreateProtocolKey() throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {
protocolKey = CardCrypto.pbkdf2(Util.calculateSHA256(mPIN), mIsoDep.getTag().getId(), 50);
//Log.e("Reader", String.format("PIN: %s, Protocol key: %s", mPIN, Util.bytesToHex(protocolKey)));
if (sessionKey != null) {
sessionKey = null;
}
}
byte[] sessionKey = null;
public void run_OpenSession(Tangem_Card.EncryptionMode encryptionMode) throws Exception {
sessionKey = null;
try {
CommandApdu cmdApdu = new CommandApdu(CommandApdu.ISO_CLA, INS.OpenSession.Code, 0, encryptionMode.getP());
switch (encryptionMode) {
case Fast: {
byte[] baMyChallenge = Util.generateRandomBytes(16);
cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyChallenge);
Log.i(logTag, cmdApdu.getCommandName());
ResponseApdu rspApdu = null;
try {
if (mIsoDep == null) {
throw new TagLostException();
}
byte[] cmdBytes = cmdApdu.toBytes();
String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc());
Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr));
byte[] rsp = mIsoDep.transceive(cmdBytes);
rspApdu = new ResponseApdu(rsp);
Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp)));
if (rspApdu.isParsedWithError()) {
throw new Exception("Can't parse answer");
}
} catch (Exception E) {
sessionKey = null;
throw E;
}
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
byte[] baTheirsChallenge = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value;
if (protocolKey == null) CreateProtocolKey();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(baMyChallenge);
outputStream.write(baTheirsChallenge);
outputStream.write(protocolKey);
sessionKey = Util.calculateSHA256(outputStream.toByteArray());
//Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey)));
} else {
Log.e(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description()));
throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2()));
}
}
break;
case Strong: {
KeyPairGenerator kpgen = KeyPairGenerator.getInstance("ECDH", "SC");
kpgen.initialize(new ECGenParameterSpec("secp256k1"), new SecureRandom());
KeyPair KP = kpgen.generateKeyPair();
KeyAgreement ka = KeyAgreement.getInstance("ECDH", "SC");
ka.init(KP.getPrivate());
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
//return spec.getG().multiply(new BigInteger((ECPrivateKey) )).getEncoded(false);
ECPublicKey eckey = (ECPublicKey) KP.getPublic();
byte[] baMyPublicKey = eckey.getQ().getEncoded(false);
cmdApdu.addTLV(TLV.Tag.TAG_Session_Key_A, baMyPublicKey);
Log.i(logTag, cmdApdu.getCommandName());
ResponseApdu rspApdu = null;
try {
if (mIsoDep == null) {
throw new TagLostException();
}
//mIsoDep.setTimeout(msTimeout);
byte[] cmdBytes = cmdApdu.toBytes();
String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc());
Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr));
byte[] rsp = mIsoDep.transceive(cmdBytes);
rspApdu = new ResponseApdu(rsp);
Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp)));
if (rspApdu.isParsedWithError()) {
throw new Exception("Can't parse answer");
}
} catch (Exception E) {
sessionKey = null;
throw E;
}
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
byte[] baTheirsPublicKey = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Session_Key_B).Value;
ka.doPhase(CardCrypto.LoadPublicKey(baTheirsPublicKey), true);
if (protocolKey == null) CreateProtocolKey();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(ka.generateSecret());
outputStream.write(protocolKey);
sessionKey = Util.calculateSHA256(outputStream.toByteArray());
// Log.i(logTag, String.format("Session key: %s", Util.bytesToHex(sessionKey)));
} else {
Log.i(logTag, String.format("Failed: %04X - %s", rspApdu.getSW1SW2(), rspApdu.getSW1SW2Description()));
throw new Exception(String.format("Can't open session: SW - %04X", rspApdu.getSW1SW2()));
}
}
break;
default:
throw new Exception("Unknown encryption mode");
}
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, String.format("Exception: %s", e.getMessage()));
throw new Exception("Can't open session: " + e.getMessage());
}
}
// send command APDU, get response APDU, and display HEX data to user
private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception {
if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) {
if (sessionKey == null) {
run_OpenSession(mCard.encryptionMode);
}
cmdApdu.Crypt(sessionKey);
}
cmdApdu.setP1(mCard.encryptionMode.getP());
byte[] cmdBytes = cmdApdu.toBytes();
String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc());
Log.v("NFC", String.format("<< [%s]: %s", cmdApdu.getCommandName(), cmdStr));
byte[] rsp;
ResponseApdu rspApdu;
try {
do {
try {
mNotifications.OnReadBeforeRequest(mIsoDep.getTimeout());
try {
rsp = mIsoDep.transceive(cmdBytes);
} finally {
mNotifications.OnReadAfterRequest();
}
} catch (IOException e) {
if (e.getMessage().contains("length exceeds supported maximum") && mIsoDep.isExtendedLengthApduSupported()) {
throw new TangemException_ExtendedLengthNotSupported(e.getMessage());
}
throw e;
}
if (mCard.encryptionMode != Tangem_Card.EncryptionMode.None) {
rspApdu = ResponseApdu.Decrypt(rsp, sessionKey);
} else {
rspApdu = new ResponseApdu(rsp);
}
if (rspApdu.isParsedWithError()) {
Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp)));
throw new TangemException(rspApdu.getParseErroMessage());
} else if (rspApdu.getSW1SW2() == SW.NEED_PAUSE && mNotifications != null) {
int remainingPause = rspApdu.getTLVs().getTagAsInt(TLV.Tag.TAG_Pause) * 10;
Log.v("NFC", String.format(">> Security delay, remaining %f s", remainingPause / 1000.0));
if (breakOnNeedPause) {
break;
} else {
mNotifications.OnReadWait(remainingPause);
}
} else {
Log.v("NFC", String.format(">> [%s]: %s", cmdApdu.getCommandName(), Util.bytesToHex(rsp)));
}
} while (rspApdu.getSW1SW2() == SW.NEED_PAUSE);
} finally {
mNotifications.OnReadWait(0);
}
return rspApdu;
}
private CommandApdu StartPrepareCommand(INS ins) throws NoSuchAlgorithmException {
CommandApdu Apdu = new CommandApdu(ins);
byte[] baPIN = Util.calculateSHA256(mPIN);
Apdu.addTLV(TLV.Tag.TAG_PIN, baPIN);
if (ins != INS.Read) {
Apdu.addTLV(TLV.Tag.TAG_CardID, mCard.getCID());
}
return Apdu;
}
public void run_Read() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.Read);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
readResult = rspApdu.getTLVs();
parseReadResult();
run_ReadOrWriteIssuerDataAndDefineOfflineBalance();
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible PIN is invalid!\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("FAILED: [%04X]\n", rspApdu.getSW1SW2()));
}
}
public void run_ReadOrWriteIssuerDataAndDefineOfflineBalance() throws Exception {
TLVList tlvIssuerData;
if (mCard.getNeedWriteIssuerData()) {
run_WriteIssuerData(mCard.getIssuerData(), mCard.getIssuerDataSignature());
try {
tlvIssuerData = TLVList.fromBytes(mCard.getIssuerData());
} catch (TLVException e) {
e.printStackTrace();
tlvIssuerData = null;
}
} else {
try {
tlvIssuerData = run_ReadIssuerData();
} catch (Exception e) {
e.printStackTrace();
tlvIssuerData = null;
}
}
if (mCard.getStatus() == Tangem_Card.Status.Loaded) {
// try read offline balance data
if (tlvIssuerData != null && tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination) != null && mCard.getMaxSignatures() == mCard.getRemainingSignatures()) {
mCard.setOfflineBalance(tlvIssuerData.getTLV(TLV.Tag.TAG_Denomination).Value);
} else {
mCard.clearOfflineBalance();
}
} else {
mCard.clearOfflineBalance();
}
}
private TLVList readResult = null;
public void clearReadResult() {
sessionKey = null;
readResult = null;
}
public boolean haveReadResult() {
return readResult != null;
}
public void parseReadResult() throws TangemException, NoSuchProviderException, NoSuchAlgorithmException {
TLV tlvStatus = readResult.getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(Tangem_Card.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
TLV tlvCID = readResult.getTLV(TLV.Tag.TAG_CardID);
mCard.setCID(tlvCID.Value);
mCard.setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
mCard.setHealth(readResult.getTLV(TLV.Tag.TAG_Health).getAsInt());
if (mCard.getStatus() != Tangem_Card.Status.NotPersonalized) {
try {
TLV tlvCardPubkicKey = readResult.getTLV(TLV.Tag.TAG_CardPublicKey);
if (tlvCardPubkicKey == null)
throw new TangemException("Invalid answer format");
mCard.setCardPublicKey(tlvCardPubkicKey.Value);
TLVList tlvCardData = TLVList.fromBytes(readResult.getTLV(TLV.Tag.TAG_CardData).Value);
TLV tokenSymbol = tlvCardData.getTLV(TLV.Tag.TAG_Token_Symbol);
TLV contractAddress = tlvCardData.getTLV(TLV.Tag.TAG_Token_Contract_Address);
TLV tokens_decimal = tlvCardData.getTLV(TLV.Tag.TAG_Token_Decimal);
if (tokenSymbol != null)
mCard.setTokenSymbol(tokenSymbol.getAsString());
if (contractAddress != null)
mCard.setContractAddress(contractAddress.getAsString());
if (tokens_decimal != null)
mCard.setTokensDecimal(tokens_decimal.getAsInt());
// this method has reflection TokenSymbol
// you mast call setBlockchainIDFromCard after calling setTokensDecimal
mCard.setBlockchainIDFromCard(tlvCardData.getTLV(TLV.Tag.TAG_Blockchain_ID).getAsString());
byte[] tlvPersonalizationDT = tlvCardData.getTLV(TLV.Tag.TAG_ManufactureDateTime).Value;
int year = (tlvPersonalizationDT[0] & 0xFF) << 8 | (tlvPersonalizationDT[1] & 0xFF);
int month = tlvPersonalizationDT[2] - 1;
int day = tlvPersonalizationDT[3];
Calendar cd = Calendar.getInstance();
cd.set(year, month, day, 0, 0, 0);
mCard.setPersonalizationDateTime(cd.getTime());
try {
if (readResult.getTLV(TLV.Tag.TAG_Firmware) != null) {
mCard.setFirmwareVersion(readResult.getTLV(TLV.Tag.TAG_Firmware).getAsString());
} else {
mCard.setFirmwareVersion(tlvCardData.getTLV(TLV.Tag.TAG_Firmware).getAsString());
}
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Cannot get firmware version");
mCard.setFirmwareVersion("0.00");
}
try {
if (mCard.getFirmwareVersion().compareTo("1.05") < 0) {
mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value));
} else {
mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Data_PublicKey).Value));
}
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Cannot get issuer, try a version for older cards");
try {
mCard.setIssuer(Issuer.FindIssuer(tlvCardData.getTLV(TLV.Tag.TAG_Issuer_ID).getAsString(), readResult.getTLV(TLV.Tag.TAG_Issuer_Transaction_PublicKey).Value));
} catch (Exception ee) {
ee.printStackTrace();
Log.e(logTag, "Cannot get issuer");
mCard.setIssuer(Issuer.Unknown);
}
}
try {
mCard.setSettingsMask(readResult.getTagAsInt(TLV.Tag.TAG_SettingsMask));
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Can't get settings mask");
}
if (readResult.getTLV(TLV.Tag.TAG_PauseBeforePIN2) != null) {
mCard.setPauseBeforePIN2(10 * readResult.getTagAsInt(TLV.Tag.TAG_PauseBeforePIN2));
}
try {
mCard.setSigningMethod(readResult.getTagAsInt(TLV.Tag.TAG_SigningMethod));
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Can't get signing method");
mCard.setSigningMethod(0);
}
try {
mCard.setMaxSignatures(readResult.getTagAsInt(TLV.Tag.TAG_MaxSignatures));
} catch (Exception e) {
e.printStackTrace();
Log.e(logTag, "Can't get max signatures");
}
} catch (Exception e) {
e.printStackTrace();
throw new TangemException("Can't parse card data");
}
}
if (mCard.getStatus() == Tangem_Card.Status.Loaded) {
TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey);
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value);
byte pkUncompressed[] = p1.getEncoded(false);
byte pkCompresses[] = p1.getEncoded(true);
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
CoinEngine engineCoin = CoinEngineFactory.Create(mCard.getBlockchain());
String wallet = engineCoin.calculateAddress(mCard, pkUncompressed);
mCard.setWallet(wallet);
//mCard.setWallet(Blockchain.calculateWalletAddress(mCard, pkUncompressed));
mCard.setRemainingSignatures(readResult.getTagAsInt(TLV.Tag.TAG_RemainingSignatures));
} else {
mCard.setWallet("N/A");
}
}
public TLVList run_VerifyCard() throws Exception {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
}
if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) {
getCard().setManufacturer(Manufacturer.Unknown, false);
return null;
}
if (mCard.getCardPublicKey() == null) {
throw new TangemException("Not all data read, can't verify card!");
}
CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCard);
byte[] bChallenge = Util.generateRandomBytes(16);
rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
TLVList verifyResult = rspApdu.getTLVs();
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
verifyResult.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge));
TLV tlvSalt = verifyResult.getTLV(TLV.Tag.TAG_Salt);
TLV tlvCardSignature = verifyResult.getTLV(TLV.Tag.TAG_CardSignature);
// TLV tlvManufacturerSignature = verifyResult.getTLV(TLV.Tag.TAG_Manufacturer_Signature);
if (tlvSalt == null || tlvCardSignature == null) {
throw new TangemException("Not all data read, can't verify card!");
}
try {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
bs.write(bChallenge);
bs.write(tlvSalt.Value);
byte[] dataArray = bs.toByteArray();
if (CardCrypto.VerifySignature(mCard.getCardPublicKey(), dataArray, tlvCardSignature.Value)) {
getCard().setCardPublicKeyValid(true);
Log.i(logTag, "Card signature verification OK");
} else {
Log.e(logTag, "Card signature verification FAILED");
getCard().setCardPublicKeyValid(false);
}
//getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString(), bChallenge, tlvSalt.Value, tlvManufacturerSignature.Value), true);
getCard().setManufacturer(Manufacturer.FindManufacturer(readResult.getTLV(TLV.Tag.TAG_Manufacture_ID).getAsString()), true);
} catch (Exception e) {
e.printStackTrace();
getCard().setManufacturer(Manufacturer.Unknown, false);
}
return verifyResult;
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2()));
} else {
getCard().setManufacturer(Manufacturer.Unknown, false);
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public void run_CreateWallet(String PIN2) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.CreateWallet);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(true);
}
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(false);
}
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public TLVList run_CheckWallet() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.CheckWallet);
byte[] bChallenge = Util.generateRandomBytes(16);
rqApdu.addTLV(TLV.Tag.TAG_Challenge, bChallenge);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
TLVList Result = rspApdu.getTLVs();
Result.add(new TLV(TLV.Tag.TAG_Challenge, bChallenge));
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
return Result;
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public void run_CheckWalletWithSignatureVerify() throws Exception {
if (mCard.getCardPublicKey() == null || readResult == null) {
run_Read();
}
if (mCard.getStatus() == Tangem_Card.Status.NotPersonalized) {
getCard().setManufacturer(Manufacturer.Unknown, false);
return;
}
if (readResult.getTagAsInt(TLV.Tag.TAG_Status) != Tangem_Card.Status.Loaded.getCode()) {
throw new TangemException("Card must be loaded");
}
TLVList checkResult = run_CheckWallet();
if (checkResult == null) return;
TLV tlvPublicKey = readResult.getTLV(TLV.Tag.TAG_Wallet_PublicKey);
TLV tlvChallenge = checkResult.getTLV(TLV.Tag.TAG_Challenge);
TLV tlvSalt = checkResult.getTLV(TLV.Tag.TAG_Salt);
TLV tlvSignature = checkResult.getTLV(TLV.Tag.TAG_Signature);
if (tlvPublicKey == null || tlvChallenge == null || tlvSalt == null || tlvSignature == null) {
throw new TangemException("Not all data read, can't check signature!");
}
ByteArrayOutputStream bs = new ByteArrayOutputStream();
bs.write(tlvChallenge.Value);
bs.write(tlvSalt.Value);
byte[] dataArray = bs.toByteArray();
if (CardCrypto.VerifySignature(tlvPublicKey.Value, dataArray, tlvSignature.Value)) {
Log.i(logTag, "Signature verification OK");
mCard.setWalletPublicKeyValid(true);
} else {
mCard.setWalletPublicKeyValid(false);
}
}
public void run_PurgeWallet(String PIN2) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.PurgeWallet);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(true);
}
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(false);
}
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public void run_SwapPIN(String PIN2, String newPin, String newPin2, boolean breakOnNeedPause) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.SwapPIN);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
rqApdu.addTLV(TLV.Tag.TAG_NewPIN, Util.calculateSHA256(newPin));
rqApdu.addTLV(TLV.Tag.TAG_NewPIN2, Util.calculateSHA256(newPin2));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, breakOnNeedPause);
if (rspApdu.isStatus(SW.PIN1_CHANGED) || rspApdu.isStatus(SW.PIN2_CHANGED) || rspApdu.isStatus(SW.PINS_CHANGED) || rspApdu.isStatus(SW.PINS_NOT_CHANGED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
if (newPin2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(true);
} else {
mCard.setUseDefaultPIN2(false);
}
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(false);
}
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Invalid PIN\n", rspApdu.getSW1SW2()));
} else if (breakOnNeedPause && rspApdu.isStatus(SW.NEED_PAUSE)) {
throw new TangemException_NeedPause(String.format("FAILED: [%04X] - Need pause\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public void run_CheckPIN2isDefault() throws Exception {
if (mCard.isFirmwareNewer("1.19") || (mCard.isFirmwareNewer("1.12") && (mCard.getPauseBeforePIN2() == 0 || mCard.useSmartSecurityDelay()))) {
// can obtain SwapPIN(to default) answer without security delay - try check if PIN2 is default with card request
try {
run_SwapPIN(DefaultPIN2, mPIN, DefaultPIN2, true);
mCard.setUseDefaultPIN2(true);
} catch (TangemException_NeedPause e) {
mCard.setUseDefaultPIN2(null);
} catch (TangemException_InvalidPIN e) {
mCard.setUseDefaultPIN2(false);
}
} else {
mCard.setUseDefaultPIN2(null);
}
}
public TLVList run_SignHashes(String PIN2, byte[][] hashes, boolean UseIssuerValidation, byte[] issuerData, Issuer issuer) throws Exception {
ByteArrayOutputStream bs = new ByteArrayOutputStream();
if (hashes.length > 10) throw new Exception("To much hashes in one transaction!");
for (int i = 0; i < hashes.length; i++) {
if (i != 0 && hashes[0].length != hashes[i].length)
throw new Exception("Hashes length must be identical!");
bs.write(hashes[i]);
}
CommandApdu rqApdu = StartPrepareCommand(INS.Sign);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
rqApdu.addTLV_U8(TLV.Tag.TAG_TrOut_HashSize, hashes[0].length);
rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, bs.toByteArray());
if (UseIssuerValidation) {
byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), bs.toByteArray());
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerSignature);
}
if (issuerData != null) {
if (issuer == null || issuer == Issuer.Unknown)
throw new Exception("Need known Issuer to write issuer Data");
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), issuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature);
}
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
TLVList Result = rspApdu.getTLVs();
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(true);
}
return Result;
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(false);
}
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public TLVList run_SignRaw(String PIN2, byte[] bTxOutData) throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.Sign);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
rqApdu.addTLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData);
rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, "sha-256x2".getBytes("US-ASCII"));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
TLVList Result = rspApdu.getTLVs();
Result.add(new TLV(TLV.Tag.TAG_TrOut_Raw, bTxOutData));
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(true);
}
return Result;
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(false);
}
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
private byte[] run_VerifyCode(String hashAlgID, int codePageAddress, int codePageCount, byte[] challenge) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.VerifyCode);
rqApdu.addTLV(TLV.Tag.TAG_HashAlgID, hashAlgID.getBytes("US-ASCII"));
rqApdu.addTLV_U32(TLV.Tag.TAG_CodePageAddress, codePageAddress);
rqApdu.addTLV_U16(TLV.Tag.TAG_CodePageCount, codePageCount);
rqApdu.addTLV(TLV.Tag.TAG_Challenge, challenge);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
return rspApdu.getTLVs().getTLV(TLV.Tag.TAG_CodeHash).Value;
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
private void run_ValidateCard(String PIN2) throws Exception {
if (readResult == null) run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.ValidateCard);
rqApdu.addTLV(TLV.Tag.TAG_PIN2, Util.calculateSHA256(PIN2));
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(true);
}
} else if (rspApdu.isStatus(SW_PIN_ERROR)) {
if (PIN2.equals(DefaultPIN2)) {
mCard.setUseDefaultPIN2(false);
}
throw new TangemException_InvalidPIN(String.format("FAILED: [%04X] - Possible the PIN or PIN2 is invalid!\n", rspApdu.getSW1SW2()));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
private void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception {
run_Read();
CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerSignature);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
private TLVList run_ReadIssuerData() throws Exception {
CommandApdu rqApdu = StartPrepareCommand(INS.GetIssuerData);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
TLV issuerData = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data);
TLV issuerDataSignature = rspApdu.getTLVs().getTLV(TLV.Tag.TAG_Issuer_Data_Signature);
if (issuerData == null || issuerDataSignature == null)
throw new TangemException("Invalid answer format (GetIssuerData)");
ByteArrayOutputStream bsDataToVerify = new ByteArrayOutputStream();
bsDataToVerify.write(mCard.getCID());
bsDataToVerify.write(issuerData.Value);
try {
if (CardCrypto.VerifySignature(mCard.getIssuer().getPublicDataKey(), bsDataToVerify.toByteArray(), issuerDataSignature.Value)) {
mCard.setIssuerData(issuerData.Value, issuerDataSignature.Value);
return TLVList.fromBytes(issuerData.Value);
} else {
throw new TangemException("Invalid issuer data read (signature verification failed)");
}
} catch (Exception e) {
e.printStackTrace();
throw new TangemException("Invalid issuer data read");
}
} else {
throw new TangemException(String.format("Failed: %04X", rspApdu.getSW1SW2()));
}
}
public void run_GetSupportedEncryption() throws Exception {
mCard.encryptionMode = Tangem_Card.EncryptionMode.None;
do {
CommandApdu rqApdu = StartPrepareCommand(INS.Read);
Log.i(logTag, String.format("[%s]\n%s", rqApdu.getCommandName(), rqApdu.getTLVs().getParsedTLVs(" ")));
ResponseApdu rspApdu = SendAndReceive(rqApdu, false);
if (rspApdu.isStatus(SW.NEED_ENCRYPTION)) {
if (mCard.encryptionMode == Tangem_Card.EncryptionMode.None) {
mCard.encryptionMode = Tangem_Card.EncryptionMode.Fast;
} else if (mCard.encryptionMode == Tangem_Card.EncryptionMode.Fast) {
mCard.encryptionMode = Tangem_Card.EncryptionMode.Strong;
} else {
throw new Exception("Can't get supported encryption methods");
}
} else if (rspApdu.isStatus(SW.PROCESS_COMPLETED)) {
Log.i(logTag, String.format("OK: [%04X]\n%s", rspApdu.getSW1SW2(), rspApdu.getTLVs().getParsedTLVs(" ")));
readResult = rspApdu.getTLVs();
mCard.setPIN(mPIN);
break;
} else {
break;
}
} while (true);
}
}

View file

@ -0,0 +1,277 @@
package com.tangem.cardReader;
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.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
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,86 @@
package com.tangem.cardReader;
import android.content.Context;
import android.nfc.TagLostException;
import android.nfc.tech.IsoDep;
import java.io.IOException;
public class CustomCardReader implements Runnable {
protected NfcManager mNfcManager;
public interface UiCallbacks {
// display console messages
void onMessageSend(String raw, String name);
void onMessageRcv(String raw, String name);
void onOkay(String message);
void onError(String message);
void onStart(String message);
void onAction(String message, String name);
void onSeparator();
// clear console messages
void clearMessages();
// ui listeners
void setUserSelectListener(UiListener callback);
// cleanup, if needed
void onFinish(boolean err);
}
public interface UiListener {
void onUserSelect(String aid);
}
public static final int SW_NO_ERROR = 0x9000;
public static final int SW_GET_RESPONSE = 0x6700;
protected IsoDep mIsoDep;
protected UiCallbacks mUiCallbacks;
protected String mAid;
protected byte[] mAidBytes;
protected Context mContext;
public CustomCardReader(Context context, NfcManager manager, IsoDep isoDep, String aid, UiCallbacks uiCallbacks) {
this.mIsoDep = isoDep;
this.mAid = aid;
this.mAidBytes = Util.hexToBytes(aid);
this.mUiCallbacks = uiCallbacks;
this.mNfcManager = manager;
this.mContext = context;
}
// send command APDU, get response APDU, and display HEX data to user
protected ResponseApdu sendAndRcv(CommandApdu cmdApdu)
throws TagLostException, IOException {
byte[] cmdBytes = cmdApdu.toBytes();
String cmdStr = CommandApdu.toString(cmdBytes, cmdApdu.getLc());
//mUiCallbacks.onMessageSend(cmdStr, cmdApdu.getCommandName());
byte[] rsp = mIsoDep.transceive(cmdBytes);
ResponseApdu rspApdu = new ResponseApdu(rsp);
byte[] data = rspApdu.getData();
//mUiCallbacks.onMessageRcv(Util.bytesToHex(rsp), cmdApdu.getCommandName());
if (rspApdu.isParsedWithError()) {
mUiCallbacks.onError(rspApdu.getParseErroMessage());
}
/*
Log.d(TAG, "response APDU: " + Util.bytesToHex(rsp));
if (data.length > 0) {
Log.d(TAG, TLVUtil.prettyPrintAPDUResponse(data));
}
*/
return rspApdu;
}
@Override
public void run() {
}
}

View file

@ -0,0 +1,43 @@
package com.tangem.cardReader;
/**
* Created by dvol on 07.03.2018.
*/
public enum INS {
Unknown(0x00),
BootROM_SOS(0x40),
BootROM_Tangem(0xF0),
Personalize(0xF1),
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),
ReadBlockedData(0xE4),
CreateTestWallet(0xE0),
ExtractWalletKey(0xE1),
Test(0xE2),
Depersonalize(0xE3);
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,45 @@
package com.tangem.cardReader;
import android.app.Dialog;
import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;
import com.tangem.wallet.R;
/**
* Created by dvol on 18.02.2018.
*/
public class NFCEnableDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setCancelable(false)
.setIcon(R.drawable.ic_action_nfc_gray)
.setTitle(R.string.nfc_disabled)
.setMessage(R.string.enable_nfc)
.setPositiveButton(R.string.dialog_ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int id) {
// take user to wireless settings
getActivity().startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
}
})
.setNegativeButton(R.string.dialog_quit,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int id) {
dialog.cancel();
getActivity().finish();
}
});
return builder.create();
}
}

View file

@ -0,0 +1,153 @@
package com.tangem.cardReader;
import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.nfc.NfcAdapter;
import android.nfc.Tag;
import android.nfc.tech.IsoDep;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import java.io.IOException;
public class NfcManager {
private static final String TAG = "NfcManager";
// reader mode flags: listen for type A (not B), skipping ndef check
private static final int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK | NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS;
private NfcAdapter mNfcAdapter;
private NFCEnableDialog mEnableNfcDialog;
private Activity mActivity;
private NfcAdapter.ReaderCallback mReaderCallback;
private boolean broadcomWorkaround = false;
private static final int DELAY_PRESENCE = 1500;
public NfcManager(Activity activity, NfcAdapter.ReaderCallback readerCallback) {
mActivity = activity;
mReaderCallback = readerCallback;
mNfcAdapter = NfcAdapter.getDefaultAdapter(activity);
}
public void onResume() {
// register broadcast receiver
IntentFilter filter = new IntentFilter(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED);
mActivity.registerReceiver(mBroadcastReceiver, filter);
if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) {
ShowNFCEnableDialog();
} else {
enableReaderMode();
}
}
public void ShowNFCEnableDialog() {
mEnableNfcDialog=new NFCEnableDialog();
mEnableNfcDialog.show(mActivity.getFragmentManager(),"NFCEnableDialog");
}
public void onPause() {
mActivity.unregisterReceiver(mBroadcastReceiver);
disableReaderMode();
}
public void onStop() {
if (mEnableNfcDialog != null) {
mEnableNfcDialog.dismiss();
}
}
public void IgnoreTag(Tag tag) throws IOException {
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
// mNfcAdapter.ignore(tag, 500, null, null);
// }else{
IsoDep isoDep = IsoDep.get(tag);
if (isoDep != null) {
isoDep.close();
}
// }
}
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@SuppressWarnings("deprecation")
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null)
return;
if (action.equals(NfcAdapter.ACTION_ADAPTER_STATE_CHANGED)) {
int state = intent.getIntExtra(NfcAdapter.EXTRA_ADAPTER_STATE,
NfcAdapter.STATE_ON);
if (state == NfcAdapter.STATE_ON
|| state == NfcAdapter.STATE_TURNING_ON) {
Log.d(TAG, "state: " + state + " , dialog: "
+ mEnableNfcDialog);
if (mEnableNfcDialog != null) {
mEnableNfcDialog.dismiss();
}
if (state == NfcAdapter.STATE_ON) {
enableReaderMode();
}
} else {
if (mEnableNfcDialog == null || !mEnableNfcDialog.isVisible()) {
ShowNFCEnableDialog();
}
}
}
}
};
@TargetApi(Build.VERSION_CODES.KITKAT)
private void enableReaderMode() {
Bundle options = new Bundle();
if (broadcomWorkaround) {
/* This is a work around for some Broadcom chipsets that does
* the presence check by sending commands that interrupt the
* processing of the ongoing command.
*/
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, DELAY_PRESENCE);
}
mNfcAdapter.enableReaderMode(mActivity, mReaderCallback, READER_FLAGS, options);
}
@TargetApi(Build.VERSION_CODES.KITKAT)
private void disableReaderMode() {
if (mNfcAdapter != null) {
mNfcAdapter.disableReaderMode(mActivity);
}
}
private static final int REQUEST_NFC_PERMISSIONS = 1;
private static String[] PERMISSIONS_NFC = {
Manifest.permission.NFC
};
//Checks if the app has NFC permission
//If the app does not has permission then the user will be prompted to grant permissions
public static void verifyPermissions(Activity activity) {
// Check if we have write permission
int permission = ActivityCompat.checkSelfPermission(activity, Manifest.permission.NFC);
if (permission != PackageManager.PERMISSION_GRANTED) {
// We don't have permission so prompt the user
ActivityCompat.requestPermissions(
activity,
PERMISSIONS_NFC,
REQUEST_NFC_PERMISSIONS
);
}
}
}

View file

@ -0,0 +1,103 @@
package com.tangem.cardReader;
import org.spongycastle.crypto.CipherParameters;
import org.spongycastle.crypto.digests.SHA256Digest;
import org.spongycastle.crypto.macs.HMac;
import org.spongycastle.crypto.params.KeyParameter;
import java.security.InvalidKeyException;
import java.util.Arrays;
public final class PBKDF2 {
private static final HMac F =new HMac(new SHA256Digest());
/**
* Derive a key.
*
* @param password The password to derive the key from.
* @param iterations The iteration count.
* @return Returns a key derived with the specified parameters.
* @throws InvalidKeyException If the specified length for the derived key
* is to long.
*/
public static byte[] deriveKey(final byte[] password, final byte[] salt, final int iterations) throws InvalidKeyException {
return deriveKey(password, salt, iterations, F.getMacSize());
}
/**
* Derive a key with a specified length.
*
* @param password The password to derive the key from.
* @param iterations The iteration count.
* @param len The length of the derived key.
* @return Returns a key derived with the specified parameters.
* @throws InvalidKeyException If the specified length for the derived key
* is to long.
*/
public static byte[] deriveKey(final byte[] password, final byte[] salt, final int iterations, final int len) throws InvalidKeyException {
// Check key length
if (len > ((Math.pow(2, 32) - 1) * F.getMacSize()))
throw new InvalidKeyException("Derived key to long");
byte[] derivedKey = new byte[len];
final int J = 0;
final int K = F.getMacSize();
final int U = F.getMacSize() << 1;
final int B = K + U;
final byte[] workingArray = new byte[K + U + 4];
// Initialize F
CipherParameters macParams = new KeyParameter(password);
F.init(macParams);
// Perform iterations
for (int kpos = 0, blk = 1; kpos < len; kpos += K, blk++) {
storeInt32BE(blk, workingArray, B);
F.update(salt, 0, salt.length);
F.reset();
F.update(salt, 0, salt.length);
F.update(workingArray, B, 4);
F.doFinal(workingArray, U);
System.arraycopy(workingArray, U, workingArray, J, K);
for (int i = 1, j = J, k = K; i < iterations; i++) {
F.init(macParams);
F.update(workingArray, j, K);
F.doFinal(workingArray, k);
for (int u = U, v = k; u < B; u++, v++)
workingArray[u] ^= workingArray[v];
int swp = k;
k = j;
j = swp;
}
int tocpy = Math.min(len - kpos, K);
System.arraycopy(workingArray, U, derivedKey, kpos, tocpy);
}
Arrays.fill(workingArray, (byte) 0);
return derivedKey;
}
/**
* Convert a 32-bit integer value into a big-endian byte array
*
* @param value The integer value to convert
* @param bytes The byte array to store the converted value
* @param offSet The offset in the output byte array
*/
public static void storeInt32BE(int value, byte[] bytes, int offSet) {
bytes[offSet + 3] = (byte) (value);
bytes[offSet + 2] = (byte) (value >>> 8);
bytes[offSet + 1] = (byte) (value >>> 16);
bytes[offSet] = (byte) (value >>> 24);
}
}

View file

@ -0,0 +1,125 @@
package com.tangem.cardReader;
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();
}
}
mSw1 = 0x00FF & respApdu[respApdu.length - 2];
mSw2 = 0x00FF & respApdu[respApdu.length - 1];
mBytes = respApdu;
}
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));
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.cardReader;
/**
* 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,25 @@
package com.tangem.cardReader;
/**
* 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 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;
}

View file

@ -0,0 +1,222 @@
package com.tangem.cardReader;
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_CreateWalletAtPersonalize(0x0E),
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_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_Wallet_PrivateKey(0x70),
TAG_Card_PrivateKey(0x71),
TAG_Block_Reason(0x72),
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*/0xee), //TODO: quick fix
TAG_ValidatedBalance(0xC1),
TAG_LastSign_Date(0xC2);
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() {
//String s=String.valueOf(Value);
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());
}
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.cardReader;
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.cardReader;
/**
* Created by dvol on 23.06.2017.
*/
import android.support.annotation.NonNull;
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(@NonNull 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;
}
}

View file

@ -0,0 +1,959 @@
package com.tangem.cardReader;
import android.text.format.DateUtils;
import org.spongycastle.crypto.digests.RIPEMD160Digest;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SecureRandom;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.BitSet;
import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;
public class Util {
public static String getSpaces(int length) {
StringBuilder buf = new StringBuilder(length);
for (int i = 0; i < length; i++) {
buf.append(" ");
}
return buf.toString();
}
public static String prettyPrintHex(String in, int indent, boolean wrapLines) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < in.length(); i++) {
char c = in.charAt(i);
buf.append(c);
int nextPos = i+1;
if (wrapLines && nextPos % 32 == 0 && nextPos != in.length()) {
buf.append("\n").append(getSpaces(indent));
} else if (nextPos % 2 == 0 && nextPos != in.length()) {
//buf.append(" ");
}
}
return buf.toString();
}
public static String prettyPrintHex(String in, int indent){
return prettyPrintHex(in, indent, true);
}
public static String prettyPrintHex(byte[] data, int indent) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data), indent, true);
}
public static String prettyPrintHex(byte[] data) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data), 0, true);
}
public static String prettyPrintHex(byte[] data, int startPos, int length) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data, startPos, length), 0, true);
}
public static String prettyPrintHexNoWrap(byte[] data) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data), 0, false);
}
public static String prettyPrintHexNoWrap(byte[] data, int startPos, int length) {
return Util.prettyPrintHex(Util.byteArrayToHexString(data, startPos, length), 0, false);
}
public static String prettyPrintHexNoWrap(String in) {
return Util.prettyPrintHex(in, 0, false);
}
public static String prettyPrintHex(String in) {
return prettyPrintHex(in, 0, true);
}
public static String prettyPrintHex(BigInteger bi) {
byte[] data = bi.toByteArray();
if (data[0] == (byte) 0x00) {
byte[] tmp = new byte[data.length - 1];
System.arraycopy(data, 1, tmp, 0, data.length - 1);
data = tmp;
}
return prettyPrintHex(data);
}
public static byte[] performRSA(byte[] dataBytes, byte[] expBytes, byte[] modBytes) {
int inBytesLength = dataBytes.length;
if (expBytes[0] >= (byte) 0x80) {
//Prepend 0x00 to modulus
byte[] tmp = new byte[expBytes.length + 1];
tmp[0] = (byte) 0x00;
System.arraycopy(expBytes, 0, tmp, 1, expBytes.length);
expBytes = tmp;
}
if (modBytes[0] >= (byte) 0x80) {
//Prepend 0x00 to modulus
byte[] tmp = new byte[modBytes.length + 1];
tmp[0] = (byte) 0x00;
System.arraycopy(modBytes, 0, tmp, 1, modBytes.length);
modBytes = tmp;
}
if (dataBytes[0] >= (byte) 0x80) {
//Prepend 0x00 to signed data to avoid that the most significant bit is interpreted as the "signed" bit
byte[] tmp = new byte[dataBytes.length + 1];
tmp[0] = (byte) 0x00;
System.arraycopy(dataBytes, 0, tmp, 1, dataBytes.length);
dataBytes = tmp;
}
BigInteger exp = new BigInteger(expBytes);
BigInteger mod = new BigInteger(modBytes);
BigInteger data = new BigInteger(dataBytes);
byte[] result = data.modPow(exp, mod).toByteArray();
if (result.length == (inBytesLength+1) && result[0] == (byte)0x00) {
//Remove 0x00 from beginning of array
byte[] tmp = new byte[inBytesLength];
System.arraycopy(result, 1, tmp, 0, inBytesLength);
result = tmp;
}
return result;
}
public static byte[] calculateSHA1(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
return sha1.digest(data);
}
public static byte[] calculateSHA224(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-224");
return sha.digest(data);
}
public static byte[] calculateSHA256(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
return sha256.digest(data);
}
public static byte[] calculateSHA384(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-384");
return sha.digest(data);
}
public static byte[] calculateSHA512(byte[] data) throws NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA-512");
return sha.digest(data);
}
public static byte[] calculateSHA256(String Message) throws NoSuchAlgorithmException {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
byte data[]=Message.getBytes(Charset.forName("UTF-8"));
return sha256.digest(data);
}
public static byte[] calculateRIPEMD160(byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException {
//MessageDigest hashAlg = MessageDigest.getInstance("RIPEMD-160", "SC");
//return hashAlg.digest(data);
RIPEMD160Digest digest = new RIPEMD160Digest();
digest.update(data, 0, data.length);
byte[] out = new byte[20];
digest.doFinal(out, 0);
return out;
}
public static String byte2Hex(byte b) {
String[] HEX_DIGITS = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
int nb = b & 0xFF;
int i_1 = (nb >>> 4) & 0xF;
int i_2 = nb & 0xF;
return HEX_DIGITS[i_1] + HEX_DIGITS[i_2];
}
public static String short2Hex(short s) {
byte b1 = (byte) (s >>> 8);
byte b2 = (byte) (s & 0xFF);
return byte2Hex(b1) + byte2Hex(b2);
}
public static int byteToInt(byte b) {
return (int) b & 0xFF;
}
public static int byteToInt(byte first, byte second) {
int value = (first & 0xFF) << 8;
value += second & 0xFF;
return value;
}
public static short byte2Short(byte b1, byte b2) {
return (short) ((b1 << 8) | (b2 & 0xFF));
}
public static String getFormattedNanoTime(long nano) {
StringBuilder buf = new StringBuilder();
buf.append((int) (nano / 1000000));
buf.append("ms ");
buf.append(nano % 1000000);
buf.append("ns");
return buf.toString();
}
public static String formatDate(Date date)
{
return DateUtils.formatDateTime(null, date.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NUMERIC_DATE | DateUtils.FORMAT_SHOW_YEAR);
// return DateFormat.getDateInstance(DateFormat.SHORT).format(date);
}
public static String formatDateTime(Date date)
{
return formatDate(date)+" "+formatTime(date);
}
public static String formatTime(Date date)
{
return new SimpleDateFormat("HH:mm:ss").format(date);
// DateFormat.getTimeInstance(DateFormat.MEDIUM).format(date)
// return DateUtils.formatDateTime(null, date.getTime(), DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_24HOUR);//DateFormat.getTimeInstance(DateFormat.MEDIUM).format(date);
}
public static byte[] getCurrentDateAsNumericEncodedByteArray(){
SimpleDateFormat format = new SimpleDateFormat("yyMMdd", Locale.US);
return fromHexString(format.format(new Date()));
}
//This prints all non-control characters common to all parts of ISO/IEC 8859
//See EMV book 4 Annex B: Table 36: Common Character Set
public static String getSafePrintChars(byte[] byteArray) {
if (byteArray == null) {
return "";
// throw new IllegalArgumentException("Argument 'byteArray' cannot be null");
}
return getSafePrintChars(byteArray, 0, byteArray.length);
}
public static String getSafePrintChars(byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
return "";
// throw new IllegalArgumentException("Argument 'byteArray' cannot be null");
}
if(byteArray.length < startPos+length){
throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+byteArray.length+")");
}
StringBuilder buf = new StringBuilder();
for (int i = startPos; i < startPos+length; i++) {
if (byteArray[i] >= (byte) 0x20 && byteArray[i] < (byte) 0x7F) {
buf.append((char) byteArray[i]);
} else {
buf.append(".");
}
}
return buf.toString();
}
public static byte[] hexToBytes(String str) {
byte[] bytes = new byte[str.length() / 2];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = (byte) Integer.parseInt(str.substring(2 * i, 2 * i + 2),
16);
}
return bytes;
}
final private static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
if( bytes==null ) return "[EMPTY]";
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
/**
* Converts a byte array into a hex string.
* @param byteArray the byte array source
* @return a hex string representing the byte array
*/
public static String byteArrayToHexString(final byte[] byteArray) {
if (byteArray == null) {
return "";
}
return byteArrayToHexString(byteArray, 0, byteArray.length);
}
public static String byteArrayToHexString(final byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
return "";
}
if(byteArray.length < startPos+length){
throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+byteArray.length+")");
}
// int readBytes = byteArray.length;
StringBuilder hexData = new StringBuilder();
int onebyte;
for (int i = 0; i < length; i++) {
onebyte = ((0x000000ff & byteArray[startPos+i]) | 0xffffff00);
hexData.append(Integer.toHexString(onebyte).substring(6));
}
return hexData.toString();
}
public static String int2Hex(int i) {
String hex = Integer.toHexString(i);
if (hex.length() % 2 != 0) {
hex = "0" + hex;
}
return hex;
}
public static String int2HexZeroPad(int i) {
String hex = int2Hex(i);
if (hex.length() % 2 != 0) {
hex = "0" + hex;
}
return hex;
}
/**
* The length of the returned array depends on the size of the int
* @param value
* @return
*/
public static byte[] intToByteArray(int value) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte one = (byte) (value >>> 24);
byte two = (byte) (value >>> 16);
byte three = (byte) (value >>> 8);
byte four = (byte) (value);
boolean found = false;
if (one > 0x00) {
baos.write(one);
found = true;
}
if (found || two > 0x00) {
baos.write(two);
found = true;
}
if (found || three > 0x00) {
baos.write(three);
}
baos.write(four);
return baos.toByteArray();
}
/**
* Returns a byte array with length = 2
* @param value
* @return
*/
public static byte[] intToByteArray2(int value) {
return new byte[]{
(byte) (value >>> 8),
(byte) value};
}
/**
* Returns a byte array with length = 4
* @param value
* @return
*/
public static byte[] intToByteArray4(int value) {
return new byte[]{
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public static byte[] longToByteArray8(long value) {
return new byte[]{
(byte) (value >>> 56),
(byte) (value >>> 48),
(byte) (value >>> 40),
(byte) (value >>> 32),
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public static int byteArrayToInt(byte[] byteArray) {
if( byteArray.length==1 ) return byteArray[0]&0xFF;
java.nio.ByteBuffer BB=java.nio.ByteBuffer.wrap(byteArray);
switch (byteArray.length)
{
case 2: return BB.getShort();
case 4: return BB.getInt();
default: throw new IllegalArgumentException("Length must be 1,2 or 4. Length = " + byteArray.length);
}
}
public static long byteArrayToLong(byte[] byteArray) {
if( byteArray.length==1 ) return byteArray[0]&0xFF;
java.nio.ByteBuffer BB=java.nio.ByteBuffer.wrap(byteArray);
switch (byteArray.length)
{
case 2: return BB.getShort();
case 4: return BB.getInt();
case 8: return BB.getLong();
default: throw new IllegalArgumentException("Length must be 1,2,4 or 8. Length = " + byteArray.length);
}
}
public static byte[] longToByteArray(long value)
{
return new byte[]{
(byte) (value >>> 56),
(byte) (value >>> 48),
(byte) (value >>> 40),
(byte) (value >>> 32),
(byte) (value >>> 24),
(byte) (value >>> 16),
(byte) (value >>> 8),
(byte) value};
}
public static int byteArrayToInt(byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
throw new IllegalArgumentException("Parameter 'byteArray' cannot be null");
}
if (length <= 0 || length > 4) {
throw new IllegalArgumentException("Length must be between 1 and 4. Length = " + length);
}
if (length == 4 && Util.isBitSet(byteArray[startPos], 8)){
throw new IllegalArgumentException("Signed bit is set (leftmost bit): " + Util.byte2Hex(byteArray[startPos]));
}
int value = 0;
for (int i = 0; i < length; i++) {
value += ((byteArray[startPos+i] & 0xFF) << 8 * (length - i - 1));
}
return value;
}
public static long byteArrayToLong(byte[] byteArray, int startPos, int length) {
if (byteArray == null) {
throw new IllegalArgumentException("Parameter 'byteArray' cannot be null");
}
if (length <= 0 || length > 8) {
throw new IllegalArgumentException("Length must be between 1 and 4. Length = " + length);
}
if (length == 8 && Util.isBitSet(byteArray[startPos], 8)){
throw new IllegalArgumentException("Signed bit is set (leftmost bit): " + Util.byte2Hex(byteArray[startPos]));
}
long value = 0;
for (int i = 0; i < length; i++) {
value += ((byteArray[startPos+i] & (long)0xFF) << 8 * (length - i - 1));
}
return value;
}
public static byte[] fromHexString(String encoded) {
encoded = removeSpaces(encoded);
if (encoded.length() == 0){
return new byte[0];
}
if ((encoded.length() % 2) != 0) {
throw new IllegalArgumentException("Input string must contain an even number of characters: "+encoded);
}
final byte result[] = new byte[encoded.length() / 2];
final char enc[] = encoded.toCharArray();
for (int i = 0; i < enc.length; i += 2) {
StringBuilder curr = new StringBuilder(2);
curr.append(enc[i]).append(enc[i + 1]);
result[i / 2] = (byte) Integer.parseInt(curr.toString(), 16);
}
return result;
}
public static String removeCRLFTab(String s) {
StringTokenizer st = new StringTokenizer(s, "\r\n\t", false);
StringBuilder buf = new StringBuilder();
while (st.hasMoreElements()) {
buf.append(st.nextElement());
}
return buf.toString();
}
public static String removeSpaces(String s) {
return s.replaceAll(" ", "");
}
public static String readInputStreamToString(InputStream is, String encoding) throws IOException {
InputStreamReader input = new InputStreamReader(is, encoding);
final int CHARS_PER_PAGE = 5000; //counting spaces
final char[] buffer = new char[CHARS_PER_PAGE];
StringBuilder output = new StringBuilder(CHARS_PER_PAGE);
for (int read = input.read(buffer, 0, buffer.length);
read != -1;
read = input.read(buffer, 0, buffer.length)) {
output.append(buffer, 0, read);
}
String text = output.toString();
return text;
}
public static void writeStringToFile(String string, String fileName, boolean append) throws IOException {
BufferedWriter out = new BufferedWriter(new FileWriter(fileName, append));
out.write(string);
out.close();
}
/**
* Binary Coded Decimal (BCD)
* @param val
* @return
*/
public static byte[] intToBinaryEncodedDecimalByteArray(int val){
String str = String.valueOf(val);
if(str.length() % 2 != 0){
str = "0"+str;
}
return Util.fromHexString(str);
}
/**
* This method converts the literal hex representation of a byte to an int.
* eg 0x70 = 70 (int)
* @param b
*/
public static int binaryCodedDecimalToInt(byte b) {
String hex = Util.byte2Hex(b);
try {
return Integer.parseInt(hex);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("The hex representation of argument b must be digits", ex);
}
}
/**
* This method converts the literal hex representation of a decimal
* encoded in 1-5 bytes to an int.
* The value should not be larger than Integer.MAX_VALUE
*
* eg 0x70 = 70 (decimal)
* eg 0x21 47 48 36 47 = 2147483647 (decimal)
* @param hex
*/
public static int binaryHexCodedDecimalToInt(String hex) {
if (hex == null) {
throw new IllegalArgumentException("Param hex cannot be null");
}
hex = Util.removeSpaces(hex);
if (hex.length() > 10) {
throw new IllegalArgumentException("There must be a maximum of 5 hex octets. hex=" + hex);
}
try {
return Integer.parseInt(hex);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Argument hex must be all digits. hex="+hex, ex);
}
}
/**
* This method converts a 1-5 byte BCD to an int.
* eg 0x7099 = 7099 (int)
* @param bcdArray
*/
public static int binaryHexCodedDecimalToInt(byte[] bcdArray) {
if (bcdArray == null) {
throw new IllegalArgumentException("Param bcdArray cannot be null");
}
return binaryHexCodedDecimalToInt(Util.byteArrayToHexString(bcdArray));
}
/**
* This returns a String with length = 8
* @param val
* @return
*/
public static String byte2BinaryLiteral(byte val) {
String s = Integer.toBinaryString(Util.byteToInt(val));
if (s.length() < 8) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 8 - s.length(); i++) {
sb.append('0');
}
sb.append(s);
s = sb.toString();
}
return s;
}
/**
* Returns a bitset containing the values in bytes.
* The byte-ordering of bytes must be big-endian which means the most significant bit is in element 0.
*
* @param bytes
* @return
*/
public static BitSet byteArray2BitSet(byte[] bytes) {
BitSet bits = new BitSet();
for (int i = 0; i < bytes.length * 8; i++) {
if ((bytes[bytes.length - i / 8 - 1] & (1 << (i % 8))) > 0) {
bits.set(i);
}
}
return bits;
}
/* Returns a byte array of at least length 1.
* The most significant bit in the result is guaranteed not to be a 1
* (since BitSet does not support sign extension).
* The byte-ordering of the result is big-endian which means the most significant bit is in element 0.
* The bit at index 0 of the bit set is assumed to be the least significant bit.
*/
public static byte[] bitSet2ByteArray(BitSet bits) {
byte[] bytes = new byte[bits.length() / 8 + 1];
for (int i = 0; i < bits.length(); i++) {
if (bits.get(i)) {
bytes[bytes.length - i / 8 - 1] |= 1 << (i % 8);
}
}
return bytes;
}
/**
*
* @param val
* @param bitPos The leftmost bit is 8 (the most significant bit)
* @return
*/
public static boolean isBitSet(byte val, int bitPos) {
if (bitPos < 1 || bitPos > 8) {
throw new IllegalArgumentException("parameter 'bitPos' must be between 1 and 8. bitPos=" + bitPos);
}
if ((val >>> (bitPos - 1) & 0x1) == 1) {
return true;
}
return false;
}
// /**
// *
// * @param val
// * @return
// */
// public static int getBitsSetCount(byte val) {
// int numBitsSet = 0;
// for(int i=1; i<=8; i++){
// if(Util.isBitSet(val, i)){
// numBitsSet++;
// }
// }
// return numBitsSet;
// }
/**
*
* @param data
* @param bitPos The leftmost bit is 8
* @param on
* @return
*/
public static byte setBit(byte data, int bitPos, boolean on) {
if (bitPos < 1 || bitPos > 8) {
throw new IllegalArgumentException("parameter 'bitPos' must be between 1 and 8. bitPos=" + bitPos);
}
if (on) {
// set bit
return data |= 1 << (bitPos - 1);
} else {
// clear bit
return data &= ~(1 << (bitPos - 1));
}
}
public static byte[] generateRandomBytes(int numBytes) {
// TODO: get bytes from a hardware RNG, or set seed
byte[] rndBytes = new byte[numBytes];
SecureRandom random = new SecureRandom();
random.nextBytes(rndBytes);
return rndBytes;
}
public static byte generateRandomByte() {
SecureRandom random = new SecureRandom();
return (byte)(random.nextInt()&0xFF);
}
public static InputStream loadResource(Class<?> cls, String path){
return cls.getResourceAsStream(path);
}
/**
* Copies the specified array, prepending 0x00, or cutting off MSBytes if necessary
* @param original
* @param newLength
* @return
*/
public static byte[] resizeArray(byte[] original, int newLength) {
if(original == null){
throw new IllegalArgumentException("byte array cannot be null");
}
if(newLength < 0){
throw new IllegalArgumentException("Illegal new length: "+newLength+". Must be >= 0");
}
if(newLength == 0){
return new byte[0];
}
byte[] tmp = new byte[newLength];
int srcPos = tmp.length > original.length ? 0 : original.length - tmp.length;
int destPos = tmp.length > original.length ? tmp.length - original.length : 0;
int length = tmp.length > original.length ? original.length : tmp.length;
System.arraycopy(original, srcPos, tmp, destPos, length);
return tmp;
}
public static byte[] copyByteArray(byte[] array2Copy){
// byte[] copy = new byte[array2Copy.length];
// System.arraycopy(array2Copy, 0, copy, 0, array2Copy.length);
// return copy;
if (array2Copy == null) {
//return new byte[0] instead?
throw new IllegalArgumentException("Argument 'array2Copy' cannot be null");
}
return copyByteArray(array2Copy, 0, array2Copy.length);
}
public static byte[] copyByteArray(byte[] array2Copy, int startPos, int length){
if (array2Copy == null) {
//return new byte[0] instead?
throw new IllegalArgumentException("Argument 'array2Copy' cannot be null");
}
if(array2Copy.length < startPos+length){
throw new IllegalArgumentException("startPos("+startPos+")+length("+length+") > byteArray.length("+array2Copy.length+")");
}
byte[] copy = new byte[array2Copy.length];
System.arraycopy(array2Copy, startPos, copy, 0, length);
return copy;
}
public static String getStackTrace(Throwable t){
StringWriter sw = new StringWriter();
t.printStackTrace(new PrintWriter(sw));
return sw.toString();
}
public static Class<?> getCallerClass(int i) {
Class<?>[] classContext = new SecurityManager() {
@Override public Class<?>[] getClassContext() {
return super.getClassContext();
}
}.getClassContext();
if (classContext != null) {
for (int j = 0; j < classContext.length; j++) {
if (classContext[j] == Util.class) {
return classContext[i+j];
}
}
} else {
// SecurityManager.getClassContext() returns null on Android 4.0
try {
StackTraceElement[] classNames = Thread.currentThread().getStackTrace();
for (int j = 0; j < classNames.length; j++) {
if (Class.forName(classNames[j].getClassName()) == Util.class) {
return Class.forName(classNames[i+j].getClassName());
}
}
} catch (ClassNotFoundException e) { }
}
return null;
}
public static String decodeOID(byte[] enc){
StringBuilder sb = new StringBuilder();
//First OID Component (standard)
//0: ITU-T
//1: ISO
//2: joint-iso-itu-t
//Second OID Component (part in a multi part standard)
//0: standard
//1: registration-authority
//2: member-body
//3: identified-organization
long firstSubidentifier = 0;
int i=0;
while(Util.isBitSet(enc[i], 8)){
firstSubidentifier = (firstSubidentifier << 7) | (enc[i] & 0x7f);
i++;
}
firstSubidentifier = (firstSubidentifier << 7) | (enc[i] & 0x7f);
i++;
if(firstSubidentifier >= 80){
long firstOIDComp = 2;
long secondOIDComp = firstSubidentifier - 80;
sb.append(firstOIDComp).append(".").append(secondOIDComp);
}else{
long secondOIDComp = firstSubidentifier % 40;
long firstOIDComp = (firstSubidentifier - secondOIDComp)/40;
sb.append(firstOIDComp).append(".").append(secondOIDComp);
}
for(; i<enc.length; i++){
sb.append(".");
long subIdentifier = 0;
while(Util.isBitSet(enc[i], 8)){
subIdentifier = (subIdentifier << 7) | (enc[i] & 0x7f);
i++;
}
subIdentifier = (subIdentifier << 7) | (enc[i] & 0x7f);
sb.append(subIdentifier);
}
String oid = sb.toString();
String desc = getOIDDescription(oid);
return oid + ((desc!=null && !desc.isEmpty())?" ("+desc +")":"");
}
//Simple OID registry
//See: http://www.oid-info.com/
public static String getOIDDescription(String oid){
// 1.2.840 - one of 2 US country OIDs
// 1.2.840.114283 - Global Platform
// 1.3.6.1 - the Internet OID
// 1.3.6.1.4.1 - IANA-assigned company OIDs, used for private MIBs and such things
// 1.3.6.1.4.1.42 - Sun Microsystems
// 1.3.6.1.4.1.42.2 - Sun Products
// 1.3.6.1.4.1.42.2.110 - java[XML]software
// 1.3.6.1.4.1.42.2.110.1.2 - (Unknown - Java Card?)
if(oid.startsWith("1.2.840.114283.1")){
return "Global Platform - Card Recognition Data";
}
if(oid.startsWith("1.2.840.114283.2")){
return "Global Platform v"+oid.substring(17);
}
if(oid.startsWith("1.2.840.114283.3")){
return "Global Platform - Card Identification Scheme";
}
if(oid.startsWith("1.2.840.114283.4")){
return "Global Platform SCP "+oid.substring(17, 18) + " implementation option 0x"+Util.int2Hex(Integer.parseInt(oid.substring(19)));
}
if(oid.startsWith("1.2.840.114283")){
return "Global Platform";
}
if(oid.startsWith("1.2.840")){
return "USA";
}
if(oid.startsWith("1.3.6.1.4.1.42.2.110.1.2")){
return "Sun Microsystems - Java Card ?";
}
if(oid.startsWith("1.3.6.1.4.1.42.2")){
return "Sun Microsystems - Products";
}
// if(oid.startsWith("1.3.656.840."))
//JCOP includes GP refinements according to Visa GP 2.1.1 specification.
//This tag is populated accordingly (Visa specific).
//The last number tells you what configuration it is (3: SSD + PKI, 2: PKI, 1: just symmetric crypto).
//Unfortunately this standard is not open.
return "";
}
public static void main(String[] args) {
// System.out.println(Util.isBitSet((byte) 0x5f, 2)); // 0101 1111
// System.out.println(Util.isBitSet((byte) 0x9f, 2)); // 1001 1111
//
// System.out.println(Util.byte2Short((byte) 0x6F, (byte) 0xEF));
// System.out.println(Util.short2Hex(Util.byte2Short((byte) 0x6F, (byte) 0xEF)));
//
// System.out.println(Util.byteArrayToInt(new byte[]{(byte) 0x6F, (byte) 0xEF}));
// System.out.println(Util.byteArrayToHexString(Util.intToByteArray(28655)));
//
// System.out.println(Util.byte2BinaryLiteral((byte) 0x00));
// System.out.println(Util.byte2BinaryLiteral((byte) 0x3F));
// System.out.println(Util.byte2BinaryLiteral((byte) 0x80));
// System.out.println(Util.byte2BinaryLiteral((byte) 0xAA));
// System.out.println(Util.byte2BinaryLiteral((byte) 0xFF));
//
// System.out.println(Util.byte2BinaryLiteral((byte) 0x8A));
// System.out.println(Util.byte2BinaryLiteral(Util.setBit((byte) 0x8A, 5, true)));
// System.out.println(Util.byte2BinaryLiteral(Util.setBit((byte) 0x8A, 8, false)));
//
// System.out.println(Util.byteArrayToLong(Util.fromHexString("7f ff ff ff ff ff ff ff"), 0, 8));
// System.out.println(Util.byteArrayToLong(Util.fromHexString("22 18 09 04 0b 00 e0 30 23 07 00 00 00 42 d2 85 4e 23 07 00 00 00 00 21 69 42"), 13, 4));
System.out.println("1.2.840.114283.1 : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 01")));
System.out.println("1.2.840.114283.2.2.1.1 : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 02 02 01 01")));
System.out.println("1.2.840.114283.4.XXXX : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 04 02 15"))); //JCOP 31
System.out.println("1.2.840.114283.4.XXXX : " + decodeOID(Util.fromHexString("2a 86 48 86 fc 6b 04 01 05"))); //JCOP 31
System.out.println("Sun Microsystems : " + decodeOID(Util.fromHexString("2b 06 01 04 01 2a 02 6e 01 02")));
System.out.println("Unknown : " + decodeOID(Util.fromHexString("2b 85 10 86 48 64 02 01 03")));
System.out.println("{2 100 3} : " + decodeOID(Util.fromHexString("813403")));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01}, 0)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01}, 1)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01}, 2)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01, 0x02}, 1)));
System.out.println(Util.prettyPrintHexNoWrap(Util.resizeArray(new byte[]{0x01, 0x02}, 4)));
}
public static byte[] calculateCRC16(byte[] bytes) {
byte chBlock;
// STEP 1 Initialize the CRC-16 value
int wCRC = 0x6363; // ITU-V.41
int i = 0;
// STEP 2 Update data and Calucuate their CRC
do {
chBlock = bytes[i++];
chBlock ^= (byte) (wCRC & 0x00FF);
chBlock = (byte) (chBlock ^ (chBlock << 4));
wCRC = ((wCRC >> 8) ^ ((chBlock & 0xFF) << 8) & 0xFFFF) ^ (((chBlock & 0xFF) << 3) & 0xFFFF) ^ (((chBlock & 0xFF) >> 4) & 0xFFFF);// (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4);
} while (i < bytes.length);
return new byte[]{(byte) (wCRC & 0xFF), (byte) ((wCRC & 0xFFFF) >> 8)};
}
public static String formatDateTimeToFileName(Date date) {
return new SimpleDateFormat("yyyy_MM_dd__HH_mm_ss", Locale.US).format(date);
}
}