From 44caf532160f17017f7793a2ff0a69277e35431e Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 25 Oct 2019 16:55:38 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../src/main/java/com/tangem/Card.kt | 23 ----- .../java/com/tangem/commands/SignCommand.kt | 92 ++++++++++++------- .../java/com/tangem/crypto/CryptoUtils.kt | 10 ++ .../main/java/com/tangem/crypto/Ed25519.kt | 18 ++++ .../main/java/com/tangem/crypto/Sepc256k1.kt | 74 ++++++++++++++- .../main/java/com/tangem/tasks/SignTask.kt | 32 ------- .../com/tangem/tasks/SingleCommandTask.kt | 21 +++++ 7 files changed, 181 insertions(+), 89 deletions(-) delete mode 100644 tangem-card-new/src/main/java/com/tangem/Card.kt delete mode 100644 tangem-card-new/src/main/java/com/tangem/tasks/SignTask.kt create mode 100644 tangem-card-new/src/main/java/com/tangem/tasks/SingleCommandTask.kt diff --git a/tangem-card-new/src/main/java/com/tangem/Card.kt b/tangem-card-new/src/main/java/com/tangem/Card.kt deleted file mode 100644 index df131c8ad6..0000000000 --- a/tangem-card-new/src/main/java/com/tangem/Card.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.tangem - -data class Card( - val cid: String -) { - -// private var CID: ByteArray? = null -// private var status: Status? = null -// private var UID: String? = null -// private var blockchainID: String? = null -// private var manufacturer = Manufacturer.Unknown -// private var manufacturerConfirmed = false -// private var maxSignatures: Int = 0 -// private var remainingSignatures: Int = 0 -// private var PIN: String? = null -// private var pbWalletKey: ByteArray? = null -// private var pbCardKey: ByteArray? = null -// private var pbWalletKeyRar: ByteArray? = null -// private var dtPersonalization: Date? = null -// private var terminalPrivateKey: ByteArray? = null -// private var terminalPublicKey: ByteArray? = null -// private var terminalIsLinked = false -} \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-card-new/src/main/java/com/tangem/commands/SignCommand.kt index 68e2a8284f..5893449400 100644 --- a/tangem-card-new/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-card-new/src/main/java/com/tangem/commands/SignCommand.kt @@ -3,49 +3,75 @@ package com.tangem.commands import com.tangem.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.extentions.calculateSha256 +import com.tangem.common.extentions.hexToBytes +import com.tangem.common.tlv.Tlv +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.crypto.sign import com.tangem.enums.Instruction +import com.tangem.tasks.TaskError -class SignCommand(val signCommandData: SignCommandData) : CommandSerializer() { +class SignCommand(private val hashes: Array, private val cardId: String) + : CommandSerializer() { override val instruction = Instruction.Sign override val instructionCode = instruction.code + private val hashSizes = if (hashes.isNotEmpty()) hashes.first().size else 0 + private val dataToSign = flattenHashes() + + private fun flattenHashes(): ByteArray { + checkForErrors() + return hashes.reduce { arr1, arr2 -> arr1 + arr2 } + } + + private fun checkForErrors() { + if (hashes.isEmpty()) throw TaskError.EmptyHashes() + if (hashes.size > 10) throw TaskError.TooMuchHashes() + if (hashes.any { it.size != hashSizes }) throw TaskError.HashSizeMustBeEqual() + } + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + val tlvData = mutableListOf( + Tlv(TlvTag.Pin, TlvTag.Pin.code, cardEnvironment.pin1.calculateSha256()), + Tlv(TlvTag.Pin2, TlvTag.Pin2.code, cardEnvironment.pin2.calculateSha256()), + Tlv(TlvTag.CardId, TlvTag.CardId.code, cardId.hexToBytes()), + Tlv(TlvTag.TransactionOutHashSize, + TlvTag.TransactionOutHashSize.code, byteArrayOf(hashSizes.toByte())), + Tlv(TlvTag.TransactionOutHash, TlvTag.TransactionOutHash.code, dataToSign) + ) + + addTerminalSignature(cardEnvironment, tlvData) + + return CommandApdu(instructionCode, tlvData) } - override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignCommandResponse? { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + private fun addTerminalSignature(cardEnvironment: CardEnvironment, tlvData: MutableList) { + cardEnvironment.terminalKeys?.let { + val signedData = dataToSign.sign(it.privateKey) + tlvData.add(Tlv(TlvTag.TerminalTransactionSignature, + TlvTag.TerminalTransactionSignature.code, signedData)) + tlvData.add(Tlv(TlvTag.TerminalPublicKey, TlvTag.TerminalPublicKey.code, it.publicKey)) + } } + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + val tlvMapper = TlvMapper(tlvData) + return SignResponse( + tlvMapper.map(TlvTag.CardId), + tlvMapper.map(TlvTag.Signature), + tlvMapper.map(TlvTag.RemainingSignatures), + tlvMapper.map(TlvTag.SignedHashes) + ) + } } -class SignCommandResponse( - -) : CommandResponse - - -data class SignCommandData( - val cid: String, - val pin1: String, - val pin2: String, - val cvc: String, - val transactionHash: TransactionHash, - val issuerData: IssuerData, - val terminalPublicKey: ByteArray? = null, - val terminalTransactionSignature: ByteArray? = null - -) - -data class TransactionHash( - val transactionHash: ByteArray, - val transactionRaw: ByteArray, - val hashName: String -) - -data class IssuerData( - val issuerData: ByteArray, - val issuerDataCounter: Int, - val issuerTransactionSignature: ByteArray, - val issuerDataSignature: ByteArray -) \ No newline at end of file +class SignResponse( + val cardId: String, + val signature: ByteArray, + val remainingSignatures: Int, + val signedHashes: Int +) : CommandResponse \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-card-new/src/main/java/com/tangem/crypto/CryptoUtils.kt index c8c8ac2630..14ca802dc2 100644 --- a/tangem-card-new/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-card-new/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -26,4 +26,14 @@ fun initCrypto() { } +fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCurve.Secp256k1): ByteArray { + + return when (curve) { + EllipticCurve.Secp256k1 -> signSecp256k1(this,privateKeyArray) + EllipticCurve.Ed25519 -> signEd25519(this, privateKeyArray) + } + + +} + diff --git a/tangem-card-new/src/main/java/com/tangem/crypto/Ed25519.kt b/tangem-card-new/src/main/java/com/tangem/crypto/Ed25519.kt index a825889aec..7c07cc0f68 100644 --- a/tangem-card-new/src/main/java/com/tangem/crypto/Ed25519.kt +++ b/tangem-card-new/src/main/java/com/tangem/crypto/Ed25519.kt @@ -2,8 +2,10 @@ package com.tangem.crypto import com.tangem.common.extentions.calculateSha512 import net.i2p.crypto.eddsa.EdDSAEngine +import net.i2p.crypto.eddsa.EdDSAPrivateKey import net.i2p.crypto.eddsa.EdDSAPublicKey import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable +import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import java.security.MessageDigest import java.security.PublicKey @@ -24,4 +26,20 @@ private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec) return EdDSAPublicKey(pubKey) +} + +internal fun signEd25519(data: ByteArray, privateKeyArray: ByteArray): ByteArray { + + val dataSha512 = data.calculateSha512() + val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) + val signatureInstance = EdDSAEngine(MessageDigest.getInstance(spec.hashAlgorithm)) + + val privateKeySpec = EdDSAPrivateKeySpec(privateKeyArray, spec) + val privateKey = EdDSAPrivateKey(privateKeySpec) + + signatureInstance.initSign(privateKey) + signatureInstance.update(dataSha512) + + return signatureInstance.sign() + } \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/crypto/Sepc256k1.kt b/tangem-card-new/src/main/java/com/tangem/crypto/Sepc256k1.kt index a85e7cf8be..af90eb6539 100644 --- a/tangem-card-new/src/main/java/com/tangem/crypto/Sepc256k1.kt +++ b/tangem-card-new/src/main/java/com/tangem/crypto/Sepc256k1.kt @@ -1,9 +1,11 @@ package com.tangem.crypto +import com.tangem.common.extentions.toHexString import org.spongycastle.asn1.ASN1EncodableVector import org.spongycastle.asn1.ASN1Integer import org.spongycastle.asn1.DERSequence import org.spongycastle.jce.ECNamedCurveTable +import org.spongycastle.jce.spec.ECPrivateKeySpec import org.spongycastle.jce.spec.ECPublicKeySpec import java.math.BigInteger import java.security.KeyFactory @@ -40,4 +42,74 @@ private fun calculateR(signature: ByteArray, size: Int): ASN1Integer = ASN1Integer(BigInteger(1, signature.copyOfRange(0, size))) private fun calculateS(signature: ByteArray, size: Int): ASN1Integer = - ASN1Integer(BigInteger(1, signature.copyOfRange(size, size * 2))) \ No newline at end of file + ASN1Integer(BigInteger(1, signature.copyOfRange(size, size * 2))) + + +internal fun signSecp256k1(data: ByteArray, privateKeyArray: ByteArray): ByteArray { + + val spec = ECNamedCurveTable.getParameterSpec("secp256k1") + val factory = KeyFactory.getInstance("EC", "SC") + + val keySpecP = ECPrivateKeySpec(BigInteger(1, privateKeyArray), spec) + + val signature = Signature.getInstance("SHA256withECDSA") + + val privateKey = factory.generatePrivate(keySpecP) + signature.initSign(privateKey) + signature.update(data) + + val enc = signature.sign() + checkSignatureForErrors(enc) + + val res = toByte64(enc) + + if (!verifySecp256k1(generatePublicKey(privateKeyArray), data, res)) { + throw Exception("Signature self verify failed - ,enc:" + enc.toHexString() + ",res:" + res.toHexString()) + } + + return res +} + +private fun checkSignatureForErrors(enc: ByteArray) { + if (enc[0].toInt() != 0x30) throw Exception("bad encoding 1") + if (enc[1].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 1") + if (enc[2].toInt() != 0x02) throw Exception("bad encoding 2") + if (enc[3].toInt() and 0x80 != 0) throw Exception("unsupported length encoding 2") + var rLength = enc[3].toInt() + if (enc[4 + rLength].toInt() != 0x02) throw Exception("bad encoding 3") + if (enc[5 + rLength].toInt() and 0x80 != 0) + throw Exception("unsupported length encoding 3") +} + +private fun toByte64(enc: ByteArray): ByteArray { + + var rLength = enc[3].toInt() + var sLength = enc[5 + rLength].toInt() + + val sPos = 6 + rLength + val res = ByteArray(64) + if (rLength <= 32) { + System.arraycopy(enc, 4, res, 32 - rLength, rLength) + rLength = 32 + } else if (rLength == 33 && enc[4].toInt() == 0) { + rLength-- + System.arraycopy(enc, 5, res, 0, rLength) + } else { + throw Exception("unsupported r-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString()) + } + if (sLength <= 32) { + System.arraycopy(enc, sPos, res, rLength + 32 - sLength, sLength) + sLength = 32 + } else if (sLength == 33 && enc[sPos].toInt() == 0) { + System.arraycopy(enc, sPos + 1, res, rLength, sLength - 1) + } else { + throw Exception("unsupported s-length - r-length:" + rLength.toString() + ",s-length:" + sLength.toString() + ",enc:" + enc.toHexString()) + } + + return res +} + +private fun generatePublicKey(privateKeyArray: ByteArray): ByteArray { + val spec = ECNamedCurveTable.getParameterSpec("secp256k1") + return spec.g.multiply(BigInteger(1, privateKeyArray)).getEncoded(false) +} \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/tasks/SignTask.kt b/tangem-card-new/src/main/java/com/tangem/tasks/SignTask.kt deleted file mode 100644 index 662c814936..0000000000 --- a/tangem-card-new/src/main/java/com/tangem/tasks/SignTask.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.tangem.tasks - -import com.tangem.CardEnvironment -import com.tangem.CardManagerDelegate -import com.tangem.CardReader -import com.tangem.commands.SignCommand -import com.tangem.commands.SignCommandData - -class SignTask(private val signCommandData: SignCommandData, - delegate: CardManagerDelegate? = null, - reader: CardReader) : Task(delegate, reader) { - - private lateinit var callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit - - override fun onRun(cardEnvironment: CardEnvironment, - callback: (result: TaskResult, cardEnvironment: CardEnvironment) -> Unit) { - - this.callback = callback - - val signCommand = SignCommand(signCommandData) - - sendCommand(signCommand, cardEnvironment) { - delegate?.closeNfcPopup() - run { callback(it, updateEnvironment()) } - } - } - - private fun updateEnvironment(): CardEnvironment { - //TODO: set logic of changing CardEnvironment when needed - return CardEnvironment() - } -} \ No newline at end of file diff --git a/tangem-card-new/src/main/java/com/tangem/tasks/SingleCommandTask.kt b/tangem-card-new/src/main/java/com/tangem/tasks/SingleCommandTask.kt new file mode 100644 index 0000000000..dd5b3b098d --- /dev/null +++ b/tangem-card-new/src/main/java/com/tangem/tasks/SingleCommandTask.kt @@ -0,0 +1,21 @@ +package com.tangem.tasks + +import com.tangem.CardEnvironment +import com.tangem.CardManagerDelegate +import com.tangem.CardReader +import com.tangem.commands.CommandResponse +import com.tangem.commands.CommandSerializer + +class SingleCommandTask( + private val commandSerializer: CommandSerializer, + delegate: CardManagerDelegate? = null, + reader: CardReader) : Task(delegate, reader) { + + override fun onRun(cardEnvironment: CardEnvironment, + callback: (result: TaskEvent) -> Unit) { + sendCommand(commandSerializer, cardEnvironment) { + delegate?.closeNfcPopup() + callback(it) + } + } +}