Updated on 2026-08-14
This commit is contained in:
commit
5e7300987c
243 changed files with 28116 additions and 0 deletions
243
app/src/main/java/com/tangem/cardReader/CardCrypto.java
Normal file
243
app/src/main/java/com/tangem/cardReader/CardCrypto.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
954
app/src/main/java/com/tangem/cardReader/CardProtocol.java
Normal file
954
app/src/main/java/com/tangem/cardReader/CardProtocol.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
277
app/src/main/java/com/tangem/cardReader/CommandApdu.java
Normal file
277
app/src/main/java/com/tangem/cardReader/CommandApdu.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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() {
|
||||
}
|
||||
}
|
||||
43
app/src/main/java/com/tangem/cardReader/INS.java
Normal file
43
app/src/main/java/com/tangem/cardReader/INS.java
Normal 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;
|
||||
}
|
||||
}
|
||||
45
app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java
Normal file
45
app/src/main/java/com/tangem/cardReader/NFCEnableDialog.java
Normal 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();
|
||||
}
|
||||
}
|
||||
153
app/src/main/java/com/tangem/cardReader/NfcManager.java
Normal file
153
app/src/main/java/com/tangem/cardReader/NfcManager.java
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
103
app/src/main/java/com/tangem/cardReader/PBKDF2.java
Normal file
103
app/src/main/java/com/tangem/cardReader/PBKDF2.java
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
125
app/src/main/java/com/tangem/cardReader/ResponseApdu.java
Normal file
125
app/src/main/java/com/tangem/cardReader/ResponseApdu.java
Normal 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());
|
||||
}
|
||||
}
|
||||
43
app/src/main/java/com/tangem/cardReader/SW.java
Normal file
43
app/src/main/java/com/tangem/cardReader/SW.java
Normal 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 "???";
|
||||
}
|
||||
}
|
||||
25
app/src/main/java/com/tangem/cardReader/SettingsMask.java
Normal file
25
app/src/main/java/com/tangem/cardReader/SettingsMask.java
Normal 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;
|
||||
|
||||
}
|
||||
222
app/src/main/java/com/tangem/cardReader/TLV.java
Normal file
222
app/src/main/java/com/tangem/cardReader/TLV.java
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
18
app/src/main/java/com/tangem/cardReader/TLVException.java
Normal file
18
app/src/main/java/com/tangem/cardReader/TLVException.java
Normal 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);
|
||||
}
|
||||
}
|
||||
72
app/src/main/java/com/tangem/cardReader/TLVList.java
Normal file
72
app/src/main/java/com/tangem/cardReader/TLVList.java
Normal 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;
|
||||
}
|
||||
}
|
||||
959
app/src/main/java/com/tangem/cardReader/Util.java
Normal file
959
app/src/main/java/com/tangem/cardReader/Util.java
Normal 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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue