Updated on 2026-08-14
This commit is contained in:
parent
36d478fbc9
commit
4b325b213b
34 changed files with 1257 additions and 1132 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 issuer’s 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 issuer’s 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 issuer’s 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 issuer’s 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 {
|
||||
|
|
|
|||
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue