diff --git a/tangem-core/src/main/java/com/tangem/CardManager.kt b/tangem-core/src/main/java/com/tangem/CardManager.kt index 4094d935bc..1503f8dc12 100644 --- a/tangem-core/src/main/java/com/tangem/CardManager.kt +++ b/tangem-core/src/main/java/com/tangem/CardManager.kt @@ -69,18 +69,7 @@ class CardManager( */ fun sign(hashes: Array, cardId: String, callback: (result: TaskEvent) -> Unit) { - val signCommand: SignCommand - try { - signCommand = SignCommand(hashes) - } catch (error: Exception) { - if (error is TaskError) { - callback(TaskEvent.Completion(error)) - } else { - Log.e(this::class.simpleName!!, error.message ?: "") - callback(TaskEvent.Completion(TaskError.UnknownError())) - } - return - } + val signCommand = SignCommand(hashes) val task = SingleCommandTask(signCommand) runTask(task, cardId, callback) } diff --git a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt index e5376f2cc7..28f0cd2d43 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CheckWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -52,11 +49,14 @@ class CheckWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Challenge, challenge) - return CommandApdu(Instruction.CheckWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CheckWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CheckWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt index 92ee3d0d90..134474fb23 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CommandSerializer.kt @@ -38,7 +38,7 @@ abstract class CommandSerializer { * @return Remaining security delay in milliseconds. */ fun deserializeSecurityDelay(responseApdu: ResponseApdu, cardEnvironment: CardEnvironment): Int? { - val tlv = responseApdu.getTlvData(cardEnvironment.encryptionKey) + val tlv = responseApdu.getTlvData() return tlv?.find { it.tag == TlvTag.Pause }?.value?.toInt() } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt index 0c142ee296..18b20f1394 100644 --- a/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/CreateWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -46,11 +43,14 @@ class CreateWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) - return CommandApdu(Instruction.CreateWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.CreateWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): CreateWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt new file mode 100644 index 0000000000..9b78cb8bea --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/commands/OpenSessionCommand.kt @@ -0,0 +1,41 @@ +package com.tangem.commands + +import com.tangem.common.CardEnvironment +import com.tangem.common.apdu.CommandApdu +import com.tangem.common.apdu.Instruction +import com.tangem.common.apdu.ResponseApdu +import com.tangem.common.tlv.TlvBuilder +import com.tangem.common.tlv.TlvMapper +import com.tangem.common.tlv.TlvTag +import com.tangem.tasks.TaskError + +class OpenSessionResponse( + val sessionKeyB: ByteArray, + val uid: ByteArray +) : CommandResponse + + +class OpenSessionCommand(private val sessionKeyA: ByteArray) : CommandSerializer() { + override fun serialize(cardEnvironment: CardEnvironment): CommandApdu { + val tlvBuilder = TlvBuilder() + tlvBuilder.append(TlvTag.SessionKeyA, sessionKeyA) + return CommandApdu( + Instruction.OpenSession, tlvBuilder.serialize(), + encryptionMode = cardEnvironment.encryptionMode + ) + } + + override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): OpenSessionResponse? { + val tlvData = responseApdu.getTlvData() ?: return null + + return try { + val mapper = TlvMapper(tlvData) + OpenSessionResponse( + sessionKeyB = mapper.map(TlvTag.SessionKeyB), + uid = mapper.map(TlvTag.Uid) + ) + } catch (exception: Exception) { + throw TaskError.SerializeCommandError() + } + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt index cef8aca229..4eb379e521 100644 --- a/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/PurgeWalletCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -37,11 +34,14 @@ class PurgeWalletCommand : CommandSerializer() { tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.PurgeWallet, tlvBuilder.serialize()) + return CommandApdu( + Instruction.PurgeWallet, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): PurgeWalletResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt index 0a161f392c..b735e54d8f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadCommand.kt @@ -310,11 +310,14 @@ class ReadCommand : CommandSerializer() { */ tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.TerminalPublicKey, cardEnvironment.terminalKeys?.publicKey) - return CommandApdu(Instruction.Read, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Read, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): Card? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val tlvMapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt index c681f5fed9..fea83e979f 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerDataCommand.kt @@ -60,11 +60,14 @@ class ReadIssuerDataCommand( tlvBuilder.append(TlvTag.Pin, cardEnvironment.pin1) tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadData) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt index 4e3f757395..3a6e8309ff 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadIssuerExtraDataCommand.kt @@ -67,11 +67,14 @@ class ReadIssuerExtraDataCommand( tlvBuilder.append(TlvTag.CardId, cardEnvironment.cardId) tlvBuilder.append(TlvTag.Mode, IssuerDataMode.ReadExtraData) tlvBuilder.append(TlvTag.Offset, offset) - return CommandApdu(Instruction.ReadIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.ReadIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadIssuerExtraDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt index 3628d9cad7..ba5954fe01 100644 --- a/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/ReadUserDataCommand.kt @@ -57,11 +57,14 @@ class ReadUserDataCommand: CommandSerializer() { builder.append(TlvTag.CardId, cardEnvironment.cardId) builder.append(TlvTag.Pin, cardEnvironment.pin1) - return CommandApdu(Instruction.ReadUserData, builder.serialize()) + return CommandApdu( + Instruction.ReadUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): ReadUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt index d76722e96c..6f0b7bfacc 100644 --- a/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/SignCommand.kt @@ -4,9 +4,6 @@ import com.tangem.common.CardEnvironment import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.Instruction import com.tangem.common.apdu.ResponseApdu -import com.tangem.common.extensions.calculateSha256 -import com.tangem.common.extensions.hexToBytes -import com.tangem.common.tlv.Tlv import com.tangem.common.tlv.TlvBuilder import com.tangem.common.tlv.TlvMapper import com.tangem.common.tlv.TlvTag @@ -60,7 +57,10 @@ class SignCommand(private val hashes: Array) tlvBuilder.append(TlvTag.Cvc, cardEnvironment.cvc) addTerminalSignature(cardEnvironment, tlvBuilder) - return CommandApdu(Instruction.Sign, tlvBuilder.serialize()) + return CommandApdu( + Instruction.Sign, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } /** @@ -79,7 +79,7 @@ class SignCommand(private val hashes: Array) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): SignResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null val tlvMapper = TlvMapper(tlvData) return SignResponse( diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt index 2ca018f2a0..dbc7f15640 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerDataCommand.kt @@ -45,11 +45,14 @@ class WriteIssuerDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, issuerDataSignature) tlvBuilder.append(TlvTag.IssuerDataCounter, issuerDataCounter) - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt index 7a1b987cd5..753607f0a9 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteIssuerExtraDataCommand.kt @@ -60,7 +60,10 @@ class WriteIssuerExtraDataCommand( tlvBuilder.append(TlvTag.IssuerDataSignature, finalizingSignature) } } - return CommandApdu(Instruction.WriteIssuerData, tlvBuilder.serialize()) + return CommandApdu( + Instruction.WriteIssuerData, tlvBuilder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } private fun getDataToWrite(): ByteArray = @@ -72,7 +75,7 @@ class WriteIssuerExtraDataCommand( } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteIssuerDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { val mapper = TlvMapper(tlvData) diff --git a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt index d512c4ded7..804abf6601 100644 --- a/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt +++ b/tangem-core/src/main/java/com/tangem/commands/WriteUserDataCommand.kt @@ -47,11 +47,14 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val if (userProtectedCounter != null || userProtectedData != null) builder.append(TlvTag.Pin2, cardEnvironment.pin2) - return CommandApdu(Instruction.WriteUserData, builder.serialize()) + return CommandApdu( + Instruction.WriteUserData, builder.serialize(), + cardEnvironment.encryptionMode, cardEnvironment.encryptionKey + ) } override fun deserialize(cardEnvironment: CardEnvironment, responseApdu: ResponseApdu): WriteUserDataResponse? { - val tlvData = responseApdu.getTlvData() ?: return null + val tlvData = responseApdu.getTlvData(cardEnvironment.encryptionKey) ?: return null return try { WriteUserDataResponse(TlvMapper(tlvData).map(TlvTag.CardId)) diff --git a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt index 21728a8c44..c556f427b3 100644 --- a/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt +++ b/tangem-core/src/main/java/com/tangem/common/CardEnvironment.kt @@ -10,7 +10,8 @@ data class CardEnvironment( val pin2: String = DEFAULT_PIN2, val cardId: String? = null, val terminalKeys: KeyPair? = null, - val encryptionKey: ByteArray? = null, + var encryptionMode: EncryptionMode = EncryptionMode.NONE, + var encryptionKey: ByteArray? = null, val cvc: ByteArray? = null ) { diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt index 450618ced5..1a2c2be076 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/CommandApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu import com.tangem.common.EncryptionMode +import com.tangem.common.extensions.calculateCrc16 +import com.tangem.common.extensions.toByteArray +import com.tangem.crypto.encrypt import java.io.ByteArrayOutputStream /** @@ -8,21 +11,19 @@ import java.io.ByteArrayOutputStream * to a raw data that can be sent to the card. * * @property ins Instruction code that determines the type of request for the card. - * @property tlvList A list of TLVs that are to be sent to the card + * @property tlvs Tlvs encoded to a [ByteArray] that are to be sent to the card. */ class CommandApdu( private val ins: Int, private val tlvs: ByteArray, - private val cla: Byte = ISO_CLA, - private val p1: Byte = 0x00, - private val p2: Byte = 0x00, - private val le: Int = 0x00, private val encryptionMode: EncryptionMode = EncryptionMode.NONE, - private val encryptionKey: ByteArray? = null) { + private val encryptionKey: ByteArray? = null, + + private val cla: Int = ISO_CLA) { constructor( instruction: Instruction, @@ -36,6 +37,19 @@ class CommandApdu( encryptionKey = encryptionKey ) + private val p1: Int + private val p2: Int + + init { + if (ins == Instruction.OpenSession.code) { + p1 = 0x00 + p2 = encryptionMode.code.toInt() + } else { + p1 = encryptionMode.code.toInt() + p2 = 0x00 + } + } + /** * Request converted to a raw data @@ -48,33 +62,37 @@ class CommandApdu( private fun toBytes(): ByteArray { - - val lc = tlvs.size + val data = if (encryptionKey != null) tlvs.encrypt() else tlvs val byteStream = ByteArrayOutputStream() - byteStream.write(cla.toInt()) + byteStream.write(cla) byteStream.write(ins) - byteStream.write(p1.toInt()) - byteStream.write(p2.toInt()) - if (lc != 0) { - writeLength(byteStream, lc) - byteStream.write(tlvs) + byteStream.write(p1) + byteStream.write(p2) + if (data.isNotEmpty()) { + byteStream.writeLength(data.size) + byteStream.write(data) } return byteStream.toByteArray() } - private fun writeLength(stream: ByteArrayOutputStream, lc: Int) { - stream.write(0) - stream.write(lc shr 8) - stream.write(lc and 0xFF) + private fun ByteArrayOutputStream.writeLength(lc: Int) { + this.write(0) + this.write(lc shr 8) + this.write(lc and 0xFF) } - private fun encrypt() { - TODO("not implemented") - } + private fun ByteArray.encrypt(): ByteArray { + val crc: ByteArray = tlvs.calculateCrc16() + val stream = ByteArrayOutputStream() + stream.write(this.size.toByteArray(2)) + stream.write(crc) + stream.write(this) + return stream.toByteArray().encrypt(encryptionKey!!) + } companion object { - const val ISO_CLA = 0x00.toByte() + const val ISO_CLA = 0x00 } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt index e98c78874b..e7e8625384 100644 --- a/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt +++ b/tangem-core/src/main/java/com/tangem/common/apdu/ResponseApdu.kt @@ -1,6 +1,9 @@ package com.tangem.common.apdu +import com.tangem.common.extensions.calculateCrc16 import com.tangem.common.tlv.Tlv +import com.tangem.crypto.decrypt +import java.io.ByteArrayInputStream /** * Stores response data from the card and parses it to [Tlv] and [StatusWord]. @@ -25,15 +28,39 @@ class ResponseApdu(private val data: ByteArray) { * (Encryption / decryption functionality is not implemented yet.) */ fun getTlvData(encryptionKey: ByteArray? = null): List? { - return when { - data.size <= 2 -> null - else -> Tlv.deserialize(data.copyOf(data.size - 2)) + return if (data.size <= 2) { + null + } else { + val responseData = data.copyOf(data.size - 2) + return if (encryptionKey != null) { + if (data.size >= 18) { + val decryptedData = decrypt(responseData, encryptionKey) + Tlv.deserialize(decryptedData) + } else { + null + } + } else { + Tlv.deserialize(responseData) + } } } + private fun decrypt(responseData: ByteArray, encryptionKey: ByteArray): ByteArray { + val decryptedData: ByteArray = responseData.decrypt(encryptionKey) - private fun decrypt(encryptionKey: ByteArray) { - TODO("not implemented") + val inputStream = ByteArrayInputStream(decryptedData) + val baLength = ByteArray(2) + inputStream.read(baLength) + val length = (baLength[0].toInt() and 0xFF) * 256 + (baLength[1].toInt() and 0xFF) + if (length > decryptedData.size - 4) throw Exception("Can't decrypt - data size invalid") + val baCRC = ByteArray(2) + inputStream.read(baCRC) + val answerData = ByteArray(length) + inputStream.read(answerData) + val crc: ByteArray = answerData.calculateCrc16() + if (!baCRC.contentEquals(crc)) throw Exception("Can't decrypt - crc invalid") + + return answerData } } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt index f7e416d700..ac201f5fcb 100644 --- a/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt +++ b/tangem-core/src/main/java/com/tangem/common/extensions/ByteArray.kt @@ -6,6 +6,7 @@ import java.nio.ByteBuffer import java.security.MessageDigest import java.util.* import kotlin.experimental.and +import kotlin.experimental.xor /** * Extension functions for [ByteArray]. @@ -53,4 +54,21 @@ fun ByteArray.toCompressedPublicKey(): ByteArray { } else { this } +} + +fun ByteArray.calculateCrc16(): ByteArray { + var chBlock: Byte + // STEP 1 Initialize the CRC-16 value + var wCRC = 0x6363 // ITU-V.41 + var i = 0 + // STEP 2 Update data and Calucuate their CRC + do { + chBlock = this.get(i++) + chBlock = chBlock xor (wCRC and 0x00FF).toByte() + val chBlockInt = (chBlock.toInt() xor (chBlock.toInt() shl 4)) + wCRC = wCRC shr 8 xor (chBlockInt and 0xFF shl 8) and 0xFFFF xor (chBlockInt and 0xFF shl 3 and 0xFFFF) xor (chBlockInt and 0xFF shr 4 and 0xFFFF) + // (wCRC>>8)^((int)chBlock<<8)^((int) chBlock<<3)^((int)chBlock>>4); + } while (i < this.size) + + return byteArrayOf((wCRC and 0xFF).toByte(), (wCRC and 0xFFFF shr 8).toByte()) } \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt index d37500e221..c9f3275dfe 100644 --- a/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt +++ b/tangem-core/src/main/java/com/tangem/common/tlv/TlvTag.kt @@ -51,6 +51,7 @@ enum class TlvTag(val code: Int) { SessionKeyA(0x1A), SessionKeyB(0x1B), + Uid(0x0B), Pause(0x1C), ManufactureId(0x20), @@ -66,7 +67,6 @@ enum class TlvTag(val code: Int) { Mode(0x23), Offset(0x24), - IsActivated(0x3A), ActivationSeed(0x3B), ResetPin(0x36), @@ -95,7 +95,6 @@ enum class TlvTag(val code: Int) { ProductMask(0x8A), PaymentFlowVersion(0x54), - TokenSymbol(0xA0), TokenContractAddress(0xA1), TokenDecimal(0xA2), diff --git a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt index 734b3d8f7a..d7643bdaae 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/CryptoUtils.kt @@ -2,8 +2,12 @@ package com.tangem.crypto import com.tangem.commands.EllipticCurve import net.i2p.crypto.eddsa.EdDSASecurityProvider +import java.security.PublicKey import java.security.SecureRandom import java.security.Security +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec object CryptoUtils { @@ -62,6 +66,16 @@ object CryptoUtils { EllipticCurve.Ed25519 -> Ed25519.generatePublicKey(privateKeyArray) } } + + fun loadPublicKey( + publicKey: ByteArray, + curve: EllipticCurve = EllipticCurve.Secp256k1 + ): PublicKey { + return when (curve) { + EllipticCurve.Secp256k1 -> Secp256k1.loadPublicKey(publicKey) + EllipticCurve.Ed25519 -> Ed25519.loadPublicKey(publicKey) + } + } } /** @@ -79,4 +93,28 @@ fun ByteArray.sign(privateKeyArray: ByteArray, curve: EllipticCurve = EllipticCu } } +fun ByteArray.encrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec, "SC") + cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this) +} + +fun ByteArray.decrypt(key: ByteArray, usePkcs7: Boolean = true): ByteArray { + val spec = if (usePkcs7) ENCRYPTION_SPEC_PKCS7 else ENCRYPTION_SPEC_NO_PADDING + val secretKeySpec = SecretKeySpec(key, spec) + val cipher = Cipher.getInstance(spec) + cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, IvParameterSpec(ByteArray(16))) + return cipher.doFinal(this.copyOfRange(0, this.size)) +} + +fun ByteArray.pbkdf2Hash(salt: ByteArray, iterations: Int): ByteArray { + return Pbkdf2().deriveKey(this, salt, iterations) +} + +private const val ENCRYPTION_SPEC_PKCS7 = "AES/CBC/PKCS7PADDING" +private const val ENCRYPTION_SPEC_NO_PADDING = "AES/CBC/NOPADDING" + + diff --git a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt index fd0cf14cc1..5c07b5a4e9 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Ed25519.kt @@ -24,7 +24,7 @@ object Ed25519 { return signatureInstance.verify(signature) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519) val pubKey = EdDSAPublicKeySpec(publicKeyArray, spec) return EdDSAPublicKey(pubKey) diff --git a/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt new file mode 100644 index 0000000000..ab3d97445c --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/EncryptionHelper.kt @@ -0,0 +1,50 @@ +package com.tangem.crypto + +import org.spongycastle.jce.interfaces.ECPublicKey +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.SecureRandom +import java.security.spec.ECGenParameterSpec +import javax.crypto.KeyAgreement + +interface EncryptionHelper { + val keyA: ByteArray + + fun generateSecret(keyB: ByteArray): ByteArray +} + +class StrongEncryptionHelper : EncryptionHelper { + private val keyPair = generateKeyPair() + private val keyAgreement = generateKeyAgreement(keyPair) + override val keyA = provideKeyA(keyPair) + + override fun generateSecret(keyB: ByteArray): ByteArray { + keyAgreement.doPhase(CryptoUtils.loadPublicKey(keyB), true) + return keyAgreement.generateSecret() + } + + private fun generateKeyPair(): KeyPair { + val kpgen = KeyPairGenerator.getInstance("ECDH", "SC") + kpgen.initialize(ECGenParameterSpec("secp256k1"), SecureRandom()) + return kpgen.generateKeyPair() + } + + private fun generateKeyAgreement(keyPair: KeyPair): KeyAgreement { + val keyAgreement = KeyAgreement.getInstance("ECDH", "SC") + keyAgreement.init(keyPair.private) + return keyAgreement + } + + private fun provideKeyA(keyPair: KeyPair): ByteArray { + val eckey = keyPair.public as ECPublicKey + return eckey.q.getEncoded(false) + } +} + +class FastEncryptionHelper : EncryptionHelper { + override val keyA = CryptoUtils.generateRandomBytes(16) + + override fun generateSecret(keyB: ByteArray): ByteArray { + return keyA + keyB + } +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt new file mode 100644 index 0000000000..4c532b3692 --- /dev/null +++ b/tangem-core/src/main/java/com/tangem/crypto/Pbkdf2.kt @@ -0,0 +1,88 @@ +package com.tangem.crypto + +import org.spongycastle.crypto.CipherParameters +import org.spongycastle.crypto.digests.SHA256Digest +import org.spongycastle.crypto.macs.HMac +import org.spongycastle.crypto.params.KeyParameter +import java.security.InvalidKeyException +import java.util.* +import kotlin.experimental.xor +import kotlin.math.min +import kotlin.math.pow + +class Pbkdf2 { + private val F: HMac = HMac(SHA256Digest()) + + fun deriveKey(password: ByteArray, salt: ByteArray, iterations: Int): ByteArray { + + val macSize = F.macSize + // Check key length + if (macSize > (2.0.pow(32.0) - 1) * macSize) throw InvalidKeyException("Derived key to long") + + val derivedKey = ByteArray(macSize) + + val J = 0 + val K: Int = macSize + val U: Int = macSize shl 1 + val B = K + U + val workingArray = ByteArray(K + U + 4) + + // Initialize F + val macParams: CipherParameters = KeyParameter(password) + F.init(macParams) + + // Perform iterations + var kpos = 0 + var blk = 1 + while (kpos < macSize) { + storeInt32BE(blk, workingArray, B) + F.update(salt, 0, salt.size) + F.reset() + F.update(salt, 0, salt.size) + F.update(workingArray, B, 4) + F.doFinal(workingArray, U) + System.arraycopy(workingArray, U, workingArray, J, K) + var i = 1 + var j = J + var k = K + while (i < iterations) { + F.init(macParams) + F.update(workingArray, j, K) + F.doFinal(workingArray, k) + var u = U + var v = k + while (u < B) { + workingArray[u] = workingArray[u] xor workingArray[v] + u++ + v++ + } + val swp = k + k = j + j = swp + i++ + } + val tocpy = min(macSize - kpos, K) + System.arraycopy(workingArray, U, derivedKey, kpos, tocpy) + kpos += K + blk++ + } + Arrays.fill(workingArray, 0.toByte()) + return derivedKey + } + + /** + * Convert a 32-bit integer value into a big-endian byte array + * + * @param value The integer value to convert + * @param bytes The byte array to store the converted value + * @param offSet The offset in the output byte array + */ + private fun storeInt32BE(value: Int, bytes: ByteArray, offSet: Int) { + bytes[offSet + 3] = value.toByte() + bytes[offSet + 2] = (value ushr 8).toByte() + bytes[offSet + 1] = (value ushr 16).toByte() + bytes[offSet] = (value ushr 24).toByte() + } + + +} \ No newline at end of file diff --git a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt index 2a62979bed..97eba2a81c 100644 --- a/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt +++ b/tangem-core/src/main/java/com/tangem/crypto/Secp256k1.kt @@ -29,7 +29,7 @@ object Secp256k1 { return signatureInstance.verify(sigDer) } - private fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { + internal fun loadPublicKey(publicKeyArray: ByteArray): PublicKey { val spec = ECNamedCurveTable.getParameterSpec("secp256k1") val factory = KeyFactory.getInstance("EC", "SC") diff --git a/tangem-core/src/main/java/com/tangem/tasks/Task.kt b/tangem-core/src/main/java/com/tangem/tasks/Task.kt index 2e193ecc5e..9b5c13df6f 100644 --- a/tangem-core/src/main/java/com/tangem/tasks/Task.kt +++ b/tangem-core/src/main/java/com/tangem/tasks/Task.kt @@ -3,14 +3,17 @@ package com.tangem.tasks import com.tangem.CardManagerDelegate import com.tangem.CardReader import com.tangem.Log -import com.tangem.commands.Card -import com.tangem.commands.CommandResponse -import com.tangem.commands.CommandSerializer -import com.tangem.commands.ReadCommand +import com.tangem.commands.* import com.tangem.common.CardEnvironment import com.tangem.common.CompletionResult +import com.tangem.common.EncryptionMode import com.tangem.common.apdu.CommandApdu import com.tangem.common.apdu.StatusWord +import com.tangem.common.extensions.calculateSha256 +import com.tangem.crypto.EncryptionHelper +import com.tangem.crypto.FastEncryptionHelper +import com.tangem.crypto.StrongEncryptionHelper +import com.tangem.crypto.pbkdf2Hash /** * An error class that represent typical errors that may occur when performing Tangem SDK tasks. @@ -137,8 +140,41 @@ abstract class Task { Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} is initiated") - val commandApdu = command.serialize(cardEnvironment) - sendRequest(command, commandApdu, cardEnvironment, callback) + when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + } + EncryptionMode.FAST, EncryptionMode.STRONG -> { + if (cardEnvironment.encryptionKey != null ) { + val commandApdu = command.serialize(cardEnvironment) + sendRequest(command, commandApdu, cardEnvironment, callback) + return + } + val encryptionHelper: EncryptionHelper = + if (cardEnvironment.encryptionMode == EncryptionMode.STRONG) { + StrongEncryptionHelper() + } else { + FastEncryptionHelper() + } + val openSessionCommand = OpenSessionCommand(encryptionHelper.keyA) + val openSessionApdu = openSessionCommand.serialize(cardEnvironment) + sendRequest(openSessionCommand, openSessionApdu, cardEnvironment) { result -> + when (result) { + is CompletionResult.Success -> { + val uid = result.data.uid + val protocolKey = cardEnvironment.pin1.calculateSha256().pbkdf2Hash(uid, 50) + val secret = encryptionHelper.generateSecret(result.data.sessionKeyB) + val sessionKey = (secret + protocolKey).calculateSha256() + cardEnvironment.encryptionKey = sessionKey + + sendCommand(command, cardEnvironment, callback) + } + is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error)) + } + } + } + } } private fun sendRequest(command: CommandSerializer, @@ -171,9 +207,26 @@ abstract class Task { StatusWord.InvalidState -> callback(CompletionResult.Failure(TaskError.InvalidState())) StatusWord.InsNotSupported -> callback(CompletionResult.Failure(TaskError.InsNotSupported())) - StatusWord.NeedEncryption -> callback(CompletionResult.Failure(TaskError.NeedEncryption())) + StatusWord.NeedEncryption -> { + when (cardEnvironment.encryptionMode) { + EncryptionMode.NONE -> { + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.FAST + } + EncryptionMode.FAST -> { + cardEnvironment.encryptionKey = null + cardEnvironment.encryptionMode = EncryptionMode.STRONG + } + EncryptionMode.STRONG -> { + Log.e(this::class.simpleName!!, "Encryption doesn't work") + callback(CompletionResult.Failure(TaskError.NeedEncryption())) + return@transceiveApdu + } + } + sendCommand(command, cardEnvironment, callback) + } StatusWord.NeedPause -> { - // When NeedPause is returned from the card whenever security delay is triggered. + // NeedPause is returned from the card whenever security delay is triggered. val remainingTime = command.deserializeSecurityDelay(responseApdu, cardEnvironment) if (remainingTime != null) delegate?.onSecurityDelay(remainingTime, securityDelayDuration) Log.i(this::class.simpleName!!, "Nfc command ${command::class.simpleName!!} triggered security delay of $remainingTime milliseconds") @@ -201,7 +254,6 @@ abstract class Task { callback(TaskEvent.Completion(readResult.error)) } is CompletionResult.Success -> { - val receivedCardId = readResult.data.cardId securityDelayDuration = readResult.data.pauseBeforePin2 ?: 0 @@ -215,10 +267,7 @@ abstract class Task { onRun(newEnvironment, readResult.data, callback) } } - - } - } }