Updated on 2026-08-14

This commit is contained in:
Tangem 2019-01-23 00:38:27 +03:00
parent 36d478fbc9
commit 4b325b213b
34 changed files with 1257 additions and 1132 deletions

View file

@ -4,6 +4,15 @@ import com.tangem.tangemcard.util.Log;
import com.tangem.tangemcard.util.PBKDF2;
import com.tangem.tangemcard.util.Util;
import net.i2p.crypto.eddsa.EdDSAEngine;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.EdDSASecurityProvider;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.spongycastle.asn1.ASN1EncodableVector;
import org.spongycastle.asn1.ASN1Integer;
import org.spongycastle.asn1.DERSequence;
@ -17,6 +26,7 @@ import java.math.BigInteger;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
@ -39,122 +49,225 @@ import javax.crypto.spec.SecretKeySpec;
public class CardCrypto {
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
Security.addProvider(new EdDSASecurityProvider());
}
public enum Curve {secp256k1, ed25519}
public static PublicKey LoadPublicKey(Curve curve, byte[] publicKeyArray) throws Exception {
if (publicKeyArray == null) throw new Exception("Public key not specified!");
switch (curve) {
case secp256k1: {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
ECPoint p1 = spec.getCurve().decodePoint(publicKeyArray);
ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec);
return factory.generatePublic(keySpec);
}
case ed25519: {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
EdDSAPublicKeySpec pubKey = new EdDSAPublicKeySpec(publicKeyArray, spec);
return new EdDSAPublicKey(pubKey);
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static PublicKey LoadPublicKey(byte[] publicKeyArray) throws Exception {
if( publicKeyArray==null ) throw new Exception("Public key not specified!");
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
return LoadPublicKey(Curve.secp256k1, publicKeyArray);
}
ECPoint p1 = spec.getCurve().decodePoint(publicKeyArray);
ECPublicKeySpec keySpec = new ECPublicKeySpec(p1, spec);
public static boolean VerifySignature(Curve curve, byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
switch (curve) {
case secp256k1: {
Signature signatureInstance = Signature.getInstance("SHA256withECDSA");
PublicKey publicKey = LoadPublicKey(publicKeyArray);
signatureInstance.initVerify(publicKey);
signatureInstance.update(data);
return factory.generatePublic(keySpec);
ASN1EncodableVector v = new ASN1EncodableVector();
int size = signature.length / 2;
v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, 0, size))));
v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(signature, size, size * 2))));
byte[] sigDer = new DERSequence(v).getEncoded();
return signatureInstance.verify(sigDer);
}
case ed25519: {
data = Util.calculateSHA512(data);
PublicKey publicKey = LoadPublicKey(curve, publicKeyArray);
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
Signature signatureInstance = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
signatureInstance.initVerify(publicKey);
signatureInstance.update(data);
return signatureInstance.verify(signature);
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static boolean VerifySignature(byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
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);
return VerifySignature(Curve.secp256k1, publicKeyArray, data, signature);
}
public static boolean VerifySignature(String curveID, byte[] publicKeyArray, byte[] data, byte[] signature) throws Exception {
Curve curve;
try {
curve = Curve.valueOf(curveID);
} catch (Exception e) {
throw new Exception("Card EC curve (" + curveID + ") isn't supported!");
}
return VerifySignature(curve, publicKeyArray, data, signature);
}
public static byte[] Signature(Curve curve, byte[] privateKeyArray, byte[] data) throws Exception {
switch (curve) {
case secp256k1: {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
KeyFactory factory = KeyFactory.getInstance("EC", "SC");
ECPrivateKeySpec keySpecP = new ECPrivateKeySpec(new BigInteger(1, privateKeyArray), spec);
Signature signature = Signature.getInstance("SHA256withECDSA");
PrivateKey privateKey = factory.generatePrivate(keySpecP);
signature.initSign(privateKey);
signature.update(data);
byte[] enc = signature.sign();
if (enc[0] != 0x30) throw new Exception("bad encoding 1");
if ((enc[1] & 0x80) != 0) throw new Exception("unsupported length encoding 1");
if (enc[2] != 0x02) throw new Exception("bad encoding 2");
if ((enc[3] & 0x80) != 0) throw new Exception("unsupported length encoding 2");
int rLength = enc[3];
if (enc[4 + rLength] != 0x02) throw new Exception("bad encoding 3");
if ((enc[5 + rLength] & 0x80) != 0) throw new Exception("unsupported length encoding 3");
int sLength = enc[5 + rLength];
int sPos = 6 + rLength;
byte[] res = new byte[64];
if (rLength <= 32) {
System.arraycopy(enc, 4, res, 32 - rLength, rLength);
rLength = 32;
} else if (rLength == 33 && enc[4] == 0) {
rLength--;
System.arraycopy(enc, 5, res, 0, rLength);
} else {
Log.e("cardCrypto", "r-length:" + String.valueOf(rLength));
Log.e("cardCrypto", "s-length:" + String.valueOf(sLength));
Log.e("cardCrypto", "enc:" + Util.bytesToHex(enc));
throw new Exception("unsupported r-length - r-length:" + String.valueOf(rLength) + ",s-length:" + String.valueOf(sLength) + ",enc:" + Util.bytesToHex(enc));
}
if (sLength <= 32) {
System.arraycopy(enc, sPos, res, rLength + 32 - sLength, sLength);
sLength = 32;
} else if (sLength == 33 && enc[sPos] == 0) {
System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1);
} else {
Log.e("cardCrypto", "s-length:" + String.valueOf(sLength));
Log.e("cardCrypto", "r-length:" + String.valueOf(rLength));
Log.e("cardCrypto", "enc:" + Util.bytesToHex(enc));
throw new Exception("unsupported s-length - r-length:" + String.valueOf(rLength) + ",s-length:" + String.valueOf(sLength) + ",enc:" + Util.bytesToHex(enc));
}
if (!VerifySignature(GeneratePublicKey(privateKeyArray), data, res)) {
throw new Exception("Signature self verify failed - r-length:" + String.valueOf(rLength) + ",s-length:" + String.valueOf(sLength) + ",enc:" + Util.bytesToHex(enc) + ",res:" + Util.bytesToHex(res));
}
return res;
}
case ed25519: {
data = Util.calculateSHA512(data);
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
//Signature sgr = Signature.getInstance("EdDSA", "I2P");
Signature signatureInstance = new EdDSAEngine(MessageDigest.getInstance(spec.getHashAlgorithm()));
EdDSAPrivateKeySpec privateKeySpec = new EdDSAPrivateKeySpec(privateKeyArray, spec);
PrivateKey privateKey = new EdDSAPrivateKey(privateKeySpec);
signatureInstance.initSign(privateKey);
signatureInstance.update(data);
return signatureInstance.sign();
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static byte[] Signature(byte[] privateKeyArray, byte[] data) throws Exception {
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;
return Signature(Curve.secp256k1, privateKeyArray, data);
}
public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws NoSuchProviderException, NoSuchAlgorithmException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
public static byte[] GeneratePublicKey(Curve curve, byte[] privateKeyArray) throws Exception {
switch (curve) {
case secp256k1: {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1,privateKeyArray)).getEncoded(false);
byte[] publicKeyArray = spec.getG().multiply(new BigInteger(1, privateKeyArray)).getEncoded(false);
return publicKeyArray;
return publicKeyArray;
}
case ed25519: {
EdDSAParameterSpec spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
EdDSAPrivateKeySpec privateKeySpec = new EdDSAPrivateKeySpec(privateKeyArray, spec);
EdDSAPublicKeySpec publicKeySpec = new EdDSAPublicKeySpec(privateKeySpec.getA(), spec);
EdDSAPublicKey publicKey = new EdDSAPublicKey(publicKeySpec);
return publicKey.getAbyte();
}
default:
throw new Exception(curve.toString() + " not supported");
}
}
public static byte[] GeneratePublicKey(byte[] privateKeyArray) throws Exception {
return GeneratePublicKey(Curve.secp256k1, privateKeyArray);
}
/**
* Computes the PBKDF2 hash of a password.
* 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
* @param password the password to hash.
* @param salt the salt
* @param iterations the iteration count (slowness factor)
* @return the PBDKF2 hash of the password
*/
public static byte[] pbkdf2(byte[] password, byte[] salt, int iterations)
throws InvalidKeyException {
return PBKDF2.deriveKey(password, salt, iterations);
}
public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] mEncryptedData = cipher.doFinal(data);
return mEncryptedData;
public static byte[] Encrypt(byte[] key, byte[] data, boolean UsePKCS7) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
if (UsePKCS7) {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/PKCS7PADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING", "SC");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] mEncryptedData = cipher.doFinal(data);
return mEncryptedData;
} else {
SecretKeySpec skeySpec = new SecretKeySpec(key, "AES/CBC/NOPADDING");
Cipher cipher = Cipher.getInstance("AES/CBC/NOPADDING", "SC");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, new IvParameterSpec(new byte[16]));
byte[] mEncryptedData = cipher.doFinal(data);
return mEncryptedData;
}
}
public static byte[] Encrypt(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchProviderException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
return Encrypt(key, data, true);
}
public static byte[] Decrypt(byte[] key, byte[] data, boolean UsePKCS7)

View file

@ -144,6 +144,7 @@ public class CardProtocol {
/**
* Constructor
*
* @param reader - NFC Reader interface
* @param card - TangemCard object, stored data from previous reading or null for reading a card for the first time
* @param notifications - UI notification callbacks
@ -151,11 +152,10 @@ public class CardProtocol {
public CardProtocol(NfcReader reader, TangemCard card, Notifications notifications) {
mIsoDep = reader;
mNotifications = notifications;
if (card != null)
{
if (card != null) {
mPIN = card.getPIN();
mCard = card;
}else{
} else {
mPIN = null;
mCard = new TangemCard(Util.byteArrayToHexString(mIsoDep.getId()));
}
@ -177,6 +177,7 @@ public class CardProtocol {
public TangemException_TagLost() {
super("Tag lost");
}
public TangemException_TagLost(String message) {
super(message);
}
@ -231,7 +232,7 @@ public class CardProtocol {
* @return UID byte array
*/
public byte[] GetUID() {
if (mIsoDep == null ) return null;
if (mIsoDep == null) return null;
return mIsoDep.getId();
}
@ -239,7 +240,7 @@ public class CardProtocol {
* Return ISO 14443-3 tag reading timeout
*/
public int getTimeout() {
if (mIsoDep == null ) return 60000;
if (mIsoDep == null) return 60000;
return mIsoDep.getTimeout();
}
@ -410,10 +411,10 @@ public class CardProtocol {
* Should have a prior opened encryption session if encryption is used
* See [1] 4
*
* @param cmdApdu - APDU command to send
* @param breakOnNeedPause - Specifies what to do when the card requests a security delay (interrupt transfer or wait till the end of the delay )
* @return - response APDU
* @throws Exception - if something went wrong
* @param cmdApdu - APDU command to send
* @param breakOnNeedPause - Specifies what to do when the card requests a security delay (interrupt transfer or wait till the end of the delay )
* @return - response APDU
* @throws Exception - if something went wrong
*/
private ResponseApdu SendAndReceive(CommandApdu cmdApdu, boolean breakOnNeedPause) throws Exception {
if (mCard.encryptionMode != TangemCard.EncryptionMode.None) {
@ -548,8 +549,7 @@ public class CardProtocol {
return readResult != null;
}
public TLVList getReadResult()
{
public TLVList getReadResult() {
return readResult;
}
@ -707,12 +707,13 @@ public class CardProtocol {
TLVList checkResult = run_CheckWallet();
if (checkResult == null) return;
TLV tlvCurveID = readResult.getTLV(TLV.Tag.TAG_CurveID);
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) {
if (tlvCurveID == null || tlvPublicKey == null || tlvChallenge == null || tlvSalt == null || tlvSignature == null) {
throw new TangemException("Not all data read, can't check signature!");
}
@ -721,7 +722,7 @@ public class CardProtocol {
bs.write(tlvSalt.Value);
byte[] dataArray = bs.toByteArray();
if (CardCrypto.VerifySignature(tlvPublicKey.Value, dataArray, tlvSignature.Value)) {
if (CardCrypto.VerifySignature(tlvCurveID.getAsString(), tlvPublicKey.Value, dataArray, tlvSignature.Value)) {
Log.i(logTag, "Signature verification OK");
mCard.setWalletPublicKeyValid(true);
} else {
@ -810,16 +811,17 @@ public class CardProtocol {
/**
* SIGN command to sign hashes - SigningMethod=0,2,4 (see {@link TangemCard.SigningMethod})
* See [1] 8.6
* @param PIN2 - PIN2 code to confirm operation
* @param hashes - array of digests to sign (max 10 digest at a time)
*
* @param PIN2 - PIN2 code to confirm operation
* @param hashes - array of digests to sign (max 10 digest at a time)
* @param issuerTransactionSignature - signature of hashes, if card need issuer validation before sign (for SigningMethod=2)
* @param issuerData - new issuerData to write on card (only for SigningMethod=4, null for other)
* @param issuerDataSignature - signature of issuerData, if issuerData specified(for SigningMethod=4)
* @param issuerData - new issuerData to write on card (only for SigningMethod=4, null for other)
* @param issuerDataSignature - signature of issuerData, if issuerData specified(for SigningMethod=4)
* @return TLVList with card answer contained wallet signatures of digests from hashes array (in case of success)
* @throws Exception - if something went wrong
*/
public TLVList run_SignHashes(String PIN2, byte[][] hashes, byte[] issuerTransactionSignature, byte[] issuerData, byte[] issuerDataSignature) throws Exception {
if ( mCard.getSigningMethod()!=TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer && mCard.getSigningMethod()!=TangemCard.SigningMethod.Sign_Hash ){
if (mCard.getSigningMethod() != TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer && mCard.getSigningMethod() != TangemCard.SigningMethod.Sign_Hash) {
throw new TangemException("Card don't support signing hashes!");
}
@ -835,19 +837,19 @@ public class CardProtocol {
rqApdu.addTLV_U8(TLV.Tag.TAG_TrOut_HashSize, hashes[0].length);
rqApdu.addTLV(TLV.Tag.TAG_TrOut_Hash, bs.toByteArray());
if (issuerData != null) {
if ( mCard.getSigningMethod()!=TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer_And_WriteIssuerData )
if (mCard.getSigningMethod() != TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer_And_WriteIssuerData)
throw new TangemException("Card don't support simultaneous sign with write issuer data!");
if (issuerDataSignature == null )
if (issuerDataSignature == null)
throw new TangemException("Card require issuer validation before write issuer data");
bs.write(issuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature);
}
if (issuerTransactionSignature!=null) {
if (issuerTransactionSignature != null) {
//byte[] issuerSignature = CardCrypto.Signature(issuer.getPrivateTransactionKey(), bs.toByteArray());
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerTransactionSignature);
}else if ( mCard.getSigningMethod()==TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer ){
} else if (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer) {
throw new TangemException("Card require issuer validation before sign the transaction!");
}
@ -875,12 +877,13 @@ public class CardProtocol {
/**
* SIGN raw tx - SigningMethod=1 (see {@link TangemCard.SigningMethod})
* See [1] 8.6
* @param PIN2 - PIN2 code to confirm operation
* @param hashAlgID - name of hash alg, used for signature
* @param bTxOutData - part of raw transaction to sign
*
* @param PIN2 - PIN2 code to confirm operation
* @param hashAlgID - name of hash alg, used for signature
* @param bTxOutData - part of raw transaction to sign
* @param issuerTransactionSignature - signature of hashes, if card need issuer validation before sign (for SigningMethod=2)
* @param issuerData - new issuerData to write on card (only for SigningMethod=4, null for other)
* @param issuerDataSignature - signature of issuerData, if issuerData specified(for SigningMethod=4)
* @param issuerData - new issuerData to write on card (only for SigningMethod=4, null for other)
* @param issuerDataSignature - signature of issuerData, if issuerData specified(for SigningMethod=4)
* @return TLVList with card answer contained wallet signatures of bTxOutData(in case of success)
* @throws Exception - if something went wrong
*/
@ -897,18 +900,18 @@ public class CardProtocol {
bs.write(bTxOutData);
if (issuerData != null) {
if ( mCard.getSigningMethod()!=TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer_And_WriteIssuerData )
if (mCard.getSigningMethod() != TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer_And_WriteIssuerData)
throw new TangemException("Card don't support simultaneous sign with write issuer data!");
if (issuerDataSignature == null )
if (issuerDataSignature == null)
throw new TangemException("Card require issuer validation before write issuer data");
bs.write(issuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data_Signature, issuerDataSignature);
}
if (issuerTransactionSignature!=null) {
if (issuerTransactionSignature != null) {
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Transaction_Signature, issuerTransactionSignature);
}else if ( mCard.getSigningMethod()==TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer ){
} else if (mCard.getSigningMethod() == TangemCard.SigningMethod.Sign_Hash_Validated_By_Issuer) {
throw new TangemException("Card require issuer validation before sign the transaction!");
}
@ -940,10 +943,11 @@ public class CardProtocol {
* VERIFY_CODE command internally reads a segment of COS binary code beginning at Code_Page_Address and having length of [64 x Code_Page_Count] bytes.
* Then it appends Challenge to the code segment, calculates resulting hash and returns it in the response.
* The application needs to ensure that returned hash coincides with the one stored in the hash library (see {@see Firmwares}).
* @param hashAlgID - sha-256, sha-1, sha-224, sha-384, sha-512, crc-16
*
* @param hashAlgID - sha-256, sha-1, sha-224, sha-384, sha-512, crc-16
* @param codePageAddress - Value from 0 to ~3000, take from {@see Firmwares}
* @param codePageCount - Number of 32-byte pages to read: from 1 to 5, take from {@see Firmwares}
* @param challenge - Additional challenge value from 1 to 10, take from {@see Firmwares}
* @param codePageCount - Number of 32-byte pages to read: from 1 to 5, take from {@see Firmwares}
* @param challenge - Additional challenge value from 1 to 10, take from {@see Firmwares}
* @return digest bytes to compare with one stored in {@see Firmwares}
* @throws Exception - if something went wrong
*/
@ -979,6 +983,7 @@ public class CardProtocol {
* Card_Validation_Counter and its signature to issuers card validation back-end (server). The server should verify the signature and update Card_Validation_Counter value if
* previous value is less than the new one. If the server reveals that submitted Card_Validation_Counter value is less than previous value, then the card having this CID is
* deemed compromised and should not be accepted by the application.
*
* @param PIN2 - PIN2 code to confirm operation
* @throws Exception - if something went wrong
*/
@ -1011,12 +1016,13 @@ public class CardProtocol {
* This command re-writes Issuer_Data data block (max 512 bytes) and its issuers signature.
* Issuer_Data is never changed or parsed from within the Tangem COS. The issuer defines purpose of use, format and payload of Issuer_Data.
* For example, this field may contain information about wallet balance signed by the issuer or additional issuers attestation data
* @param issuerData - new issuerData
*
* @param issuerData - new issuerData
* @param issuerSignature - signature of issuerData with IssuerDataKey
* @throws Exception - if something went wrong
*/
public void run_WriteIssuerData(byte[] issuerData, byte[] issuerSignature) throws Exception {
if( readResult==null ) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
if (readResult == null) throw new TangemException("Before run_VerifyCard execute run_Read card first!");
CommandApdu rqApdu = StartPrepareCommand(INS.WriteIssuerData);
rqApdu.addTLV(TLV.Tag.TAG_Issuer_Data, issuerData);
@ -1037,6 +1043,7 @@ public class CardProtocol {
* GET_ISSUER_DATA command and verify verify issuer signature of returned data
* See [1] 3.3, 8.7
* This command returns Issuer_Data data block and its issuers signature.
*
* @return TLVList with issuerData (if success read and verify)
* @throws Exception - if something went wrong
*/
@ -1084,6 +1091,7 @@ public class CardProtocol {
* Execute consecutive READ commands with increasing encryption level from EncryptionMode.None to EncryptionMode.Strong, see {@link TangemCard.EncryptionMode}
* If card requires stricter encryption level it returns SW.NEED_ENCRYPTION status word
* Once READ is successfully executed - save answer to {@see readResult}, save current PIN and encryption mode to {@link TangemCard} and return
*
* @throws Exception - if something went wrong
*/
public void run_GetSupportedEncryption() throws Exception {

View file

@ -4,6 +4,7 @@ import com.tangem.tangemcard.data.external.CardDataSubstitutionProvider;
import com.tangem.tangemcard.data.Manufacturer;
import com.tangem.tangemcard.data.external.PINsProvider;
import com.tangem.tangemcard.data.TangemCard;
import com.tangem.tangemcard.reader.CardCrypto;
import com.tangem.tangemcard.reader.CardProtocol;
import com.tangem.tangemcard.reader.NfcReader;
import com.tangem.tangemcard.reader.TLV;
@ -20,6 +21,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import static com.tangem.tangemcard.reader.CardCrypto.Curve.secp256k1;
/**
* Base class for card communication task
*/
@ -29,7 +32,7 @@ public class CustomReadCardTask extends Thread {
protected NfcReader mIsoDep;
protected CardProtocol.Notifications mNotifications;
protected boolean isCancelled = false;
protected TangemCard mCard ;
protected TangemCard mCard;
CardDataSubstitutionProvider localStorage;
PINsProvider pinsProvider;
CardProtocol protocol;
@ -48,9 +51,9 @@ public class CustomReadCardTask extends Thread {
public CustomReadCardTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, CardProtocol.Notifications notifications) {
mIsoDep = reader;
mNotifications = notifications;
localStorage= cardDataSubstitutionProvider;
this.pinsProvider=pinsProvider;
mCard=card;
localStorage = cardDataSubstitutionProvider;
this.pinsProvider = pinsProvider;
mCard = card;
}
/**
@ -109,7 +112,7 @@ public class CustomReadCardTask extends Thread {
* @throws CardProtocol.TangemException - if something went wrong
*/
public void parseReadResult() throws CardProtocol.TangemException {
if( mCard==null ) mCard=protocol.getCard();
if (mCard == null) mCard = protocol.getCard();
// These tags always present in the parsed response: TAG_Status, TAG_CID, TAG_Manufacture_ID, TAG_Health, TAG_Firmware
TLV tlvStatus = protocol.getReadResult().getTLV(TLV.Tag.TAG_Status);
mCard.setStatus(TangemCard.Status.fromCode(Util.byteArrayToInt(tlvStatus.Value)));
@ -199,7 +202,7 @@ public class CustomReadCardTask extends Thread {
mCard.setContractAddress("0x0c056b0cda0763cc14b8b2d6c02465c91e33ec72");
} else {
//CardDataSubstitutionProvider localStorage = new CardDataSubstitutionProvider(mContext);
if( localStorage!=null ) localStorage.applySubstitution(mCard);
if (localStorage != null) localStorage.applySubstitution(mCard);
}
} catch (Exception e) {
Log.e(TAG, "Can't apply card data substitution");
@ -245,15 +248,26 @@ public class CustomReadCardTask extends Thread {
if (mCard.getStatus() == TangemCard.Status.Loaded) {
TLV tlvPublicKey = protocol.getReadResult().getTLV(TLV.Tag.TAG_Wallet_PublicKey);
String curveID = protocol.getReadResult().getTLV(TLV.Tag.TAG_CurveID).getAsString();
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value);
CardCrypto.Curve curve = CardCrypto.Curve.valueOf(curveID);
switch (curve) {
case secp256k1:
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec("secp256k1");
ECPoint p1 = spec.getCurve().decodePoint(tlvPublicKey.Value);
byte pkUncompressed[] = p1.getEncoded(false);
byte pkUncompressed[] = p1.getEncoded(false);
byte pkCompresses[] = p1.getEncoded(true);
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
byte pkCompresses[] = p1.getEncoded(true);
mCard.setWalletPublicKey(pkUncompressed);
mCard.setWalletPublicKeyRar(pkCompresses);
break;
case ed25519:
mCard.setWalletPublicKey(tlvPublicKey.Value);
mCard.setWalletPublicKeyRar(tlvPublicKey.Value);
break;
}
mCard.setRemainingSignatures(protocol.getReadResult().getTagAsInt(TLV.Tag.TAG_RemainingSignatures));
@ -363,7 +377,7 @@ public class CustomReadCardTask extends Thread {
protocol.setPIN(PIN);
protocol.run_Read();
pinsProvider.setLastUsedPIN(PIN);
if (!Arrays.equals(mCard.getCID(),protocol.getReadResult().getTLV(TLV.Tag.TAG_CardID).Value)) {
if (!Arrays.equals(mCard.getCID(), protocol.getReadResult().getTLV(TLV.Tag.TAG_CardID).Value)) {
throw new CardProtocol.TangemException("Card must be the same. Reading attempt on different card!");
}
}

View file

@ -15,9 +15,9 @@ public class SignTask extends CustomReadCardTask {
public static final String TAG = SignTask.class.getSimpleName();
/**
* Payment Engine request/notifications during sign process
* Transaction Engine request/notifications during sign process
*/
public interface PaymentToSign {
public interface TransactionToSign {
boolean isSigningMethodSupported(TangemCard.SigningMethod signingMethod);
byte[][] getHashesToSign() throws Exception;
@ -31,11 +31,11 @@ public class SignTask extends CustomReadCardTask {
byte[] onSignCompleted(byte[] signature) throws Exception;
}
private PaymentToSign paymentToSign;
private TransactionToSign transactionToSign;
public SignTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, CardProtocol.Notifications notifications, PaymentToSign paymentToSign) {
public SignTask(TangemCard card, NfcReader reader, CardDataSubstitutionProvider cardDataSubstitutionProvider, PINsProvider pinsProvider, CardProtocol.Notifications notifications, TransactionToSign transactionToSign) {
super(card, reader, cardDataSubstitutionProvider, pinsProvider, notifications);
this.paymentToSign = paymentToSign;
this.transactionToSign = transactionToSign;
}
@Override
@ -51,40 +51,40 @@ public class SignTask extends CustomReadCardTask {
mNotifications.onReadWait(mCard.getPauseBeforePIN2());
}
if (!paymentToSign.isSigningMethodSupported(mCard.getSigningMethod())) {
if (!transactionToSign.isSigningMethodSupported(mCard.getSigningMethod())) {
throw new CardProtocol.TangemException("Signing method isn't supported!");
}
TLVList signResult;
switch (mCard.getSigningMethod()) {
case Sign_Hash:
signResult = protocol.run_SignHashes(pinsProvider.getPIN2(), paymentToSign.getHashesToSign(), null, null, null);
signResult = protocol.run_SignHashes(pinsProvider.getPIN2(), transactionToSign.getHashesToSign(), null, null, null);
break;
case Sign_Hash_Validated_By_Issuer:
case Sign_Hash_Validated_By_Issuer_And_WriteIssuerData:
ByteArrayOutputStream bs = new ByteArrayOutputStream();
byte[][] hashes = paymentToSign.getHashesToSign();
byte[][] hashes = transactionToSign.getHashesToSign();
if (hashes.length > 10) throw new CardProtocol.TangemException("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 CardProtocol.TangemException("Hashes length must be identical!");
bs.write(hashes[i]);
}
signResult = protocol.run_SignHashes(pinsProvider.getPIN2(), hashes, paymentToSign.getIssuerTransactionSignature(bs.toByteArray()), null, null);
signResult = protocol.run_SignHashes(pinsProvider.getPIN2(), hashes, transactionToSign.getIssuerTransactionSignature(bs.toByteArray()), null, null);
break;
case Sign_Raw:
signResult = protocol.run_SignRaw(pinsProvider.getPIN2(), paymentToSign.getHashAlgToSign(), paymentToSign.getRawDataToSign(), null, null, null);
signResult = protocol.run_SignRaw(pinsProvider.getPIN2(), transactionToSign.getHashAlgToSign(), transactionToSign.getRawDataToSign(), null, null, null);
break;
case Sign_Raw_Validated_By_Issuer:
case Sign_Raw_Validated_By_Issuer_And_WriteIssuerData:
byte[] txOut = paymentToSign.getRawDataToSign();
signResult = protocol.run_SignRaw(pinsProvider.getPIN2(), paymentToSign.getHashAlgToSign(), txOut, paymentToSign.getIssuerTransactionSignature(txOut), null, null);
byte[] txOut = transactionToSign.getRawDataToSign();
signResult = protocol.run_SignRaw(pinsProvider.getPIN2(), transactionToSign.getHashAlgToSign(), txOut, transactionToSign.getIssuerTransactionSignature(txOut), null, null);
break;
default:
throw new CardProtocol.TangemException("Signing method isn't supported!");
}
paymentToSign.onSignCompleted(signResult.getTLV(TLV.Tag.TAG_Signature).Value);
transactionToSign.onSignCompleted(signResult.getTLV(TLV.Tag.TAG_Signature).Value);
mNotifications.onReadProgress(protocol, 100);
}