Updated on 2026-08-14
This commit is contained in:
parent
698575606f
commit
44caf53216
7 changed files with 181 additions and 89 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue