Updated on 2026-08-14

This commit is contained in:
Tangem 2019-10-25 16:55:38 +03:00
parent 698575606f
commit 44caf53216
7 changed files with 181 additions and 89 deletions

View file

@ -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
}

View file

@ -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<SignCommandResponse>() {
class SignCommand(private val hashes: Array<ByteArray>, private val cardId: String)
: CommandSerializer<SignResponse>() {
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<Tlv>) {
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
)
class SignResponse(
val cardId: String,
val signature: ByteArray,
val remainingSignatures: Int,
val signedHashes: Int
) : CommandResponse

View file

@ -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)
}
}

View file

@ -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()
}

View file

@ -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)))
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)
}

View file

@ -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()
}
}

View file

@ -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<Event : CommandResponse>(
private val commandSerializer: CommandSerializer<Event>,
delegate: CardManagerDelegate? = null,
reader: CardReader) : Task<Event>(delegate, reader) {
override fun onRun(cardEnvironment: CardEnvironment,
callback: (result: TaskEvent<Event>) -> Unit) {
sendCommand(commandSerializer, cardEnvironment) {
delegate?.closeNfcPopup()
callback(it)
}
}
}