Updated on 2026-08-14
This commit is contained in:
parent
d60a65a57d
commit
1357ddf95d
12 changed files with 71 additions and 46 deletions
|
|
@ -5,13 +5,10 @@ import com.tangem.commands.OpenSessionCommand
|
|||
import com.tangem.commands.ReadCommand
|
||||
import com.tangem.common.CompletionResult
|
||||
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.getType
|
||||
import com.tangem.crypto.EncryptionHelper
|
||||
import com.tangem.crypto.FastEncryptionHelper
|
||||
import com.tangem.crypto.StrongEncryptionHelper
|
||||
import com.tangem.crypto.pbkdf2Hash
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.*
|
||||
|
|
@ -222,9 +219,10 @@ class CardSession(
|
|||
scope.launch {
|
||||
subscription.consumeAsFlow()
|
||||
.filterNotNull()
|
||||
.map { establishEncryption(apdu.ins) }
|
||||
.map { establishEncryptionIfNeeded() }
|
||||
.map { apdu.encrypt(environment.encryptionMode, environment.encryptionKey) }
|
||||
.map { encryptedApdu -> reader.transceiveApdu(encryptedApdu) }
|
||||
.map { responseApdu -> decrypt(responseApdu) }
|
||||
.catch { if (it is TangemSdkError) callback(CompletionResult.Failure(it)) }
|
||||
.collect { result ->
|
||||
subscription.cancel()
|
||||
|
|
@ -233,21 +231,14 @@ class CardSession(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun establishEncryption(ins: Int): CompletionResult<Boolean> {
|
||||
|
||||
if (environment.encryptionKey != null) return CompletionResult.Success(true)
|
||||
|
||||
if (ins == Instruction.Personalize.code) {
|
||||
environment.encryptionKey = null
|
||||
private suspend fun establishEncryptionIfNeeded(): CompletionResult<Boolean> {
|
||||
if (environment.encryptionMode == EncryptionMode.NONE || environment.encryptionKey != null) {
|
||||
return CompletionResult.Success(true)
|
||||
}
|
||||
|
||||
val encryptionHelper: EncryptionHelper =
|
||||
when (environment.encryptionMode) {
|
||||
EncryptionMode.NONE -> return CompletionResult.Success(true)
|
||||
EncryptionMode.FAST -> FastEncryptionHelper()
|
||||
EncryptionMode.STRONG -> StrongEncryptionHelper()
|
||||
}
|
||||
val encryptionHelper = EncryptionHelper.create(environment.encryptionMode)
|
||||
?: return CompletionResult.Success(true)
|
||||
|
||||
val openSesssionCommand = OpenSessionCommand(encryptionHelper.keyA)
|
||||
val apdu = openSesssionCommand.serialize(environment)
|
||||
|
||||
|
|
@ -269,4 +260,19 @@ class CardSession(
|
|||
is CompletionResult.Failure -> return CompletionResult.Failure(response.error)
|
||||
}
|
||||
}
|
||||
|
||||
private fun decrypt(result: CompletionResult<ResponseApdu>): CompletionResult<ResponseApdu> {
|
||||
return when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
try {
|
||||
CompletionResult.Success(
|
||||
result.data.decrypt(environment.encryptionKey)
|
||||
)
|
||||
} catch (error: TangemSdkError) {
|
||||
return CompletionResult.Failure(error)
|
||||
}
|
||||
}
|
||||
is CompletionResult.Failure -> result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,7 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
|
||||
open fun performPreCheck(card: Card): TangemSdkError? = null
|
||||
|
||||
open fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? = error
|
||||
open fun mapError(card: Card?, error: TangemSdkError): TangemSdkError = error
|
||||
|
||||
fun transceive(session: CardSession, callback: (result: CompletionResult<T>) -> Unit) {
|
||||
|
||||
|
|
@ -63,10 +63,10 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
when (result) {
|
||||
is CompletionResult.Failure -> {
|
||||
if (session.environment.handleErrors) {
|
||||
performAfterCheck(session.environment.card, result.error)?.let {
|
||||
callback(CompletionResult.Failure(it))
|
||||
return@transceiveApdu
|
||||
}
|
||||
val error = mapError(session.environment.card, result.error)
|
||||
callback(CompletionResult.Failure(error))
|
||||
return@transceiveApdu
|
||||
|
||||
}
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
|
|
@ -98,13 +98,7 @@ abstract class Command<T : CommandResponse> : ApduSerializable<T>, CardSessionRu
|
|||
when (responseApdu.statusWord) {
|
||||
StatusWord.ProcessCompleted, StatusWord.Pin1Changed,
|
||||
StatusWord.Pin2Changed, StatusWord.PinsChanged -> {
|
||||
try {
|
||||
val decryptedResponseApdu =
|
||||
responseApdu.decrypt(session.environment.encryptionKey)
|
||||
callback(CompletionResult.Success(decryptedResponseApdu))
|
||||
} catch (error: TangemSdkError) {
|
||||
callback(CompletionResult.Failure(error))
|
||||
}
|
||||
callback(CompletionResult.Success(responseApdu))
|
||||
}
|
||||
StatusWord.NeedPause -> {
|
||||
// NeedPause is returned from the card whenever security delay is triggered.
|
||||
|
|
|
|||
|
|
@ -51,11 +51,11 @@ class CreateWalletCommand : Command<CreateWalletResponse>() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -48,11 +48,11 @@ class PurgeWalletCommand : Command<PurgeWalletResponse>() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -367,11 +367,11 @@ class Card(
|
|||
*/
|
||||
class ReadCommand : Command<Card>() {
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin1Required()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -63,11 +63,11 @@ class SignCommand(private val hashes: Array<ByteArray>) : Command<SignResponse>(
|
|||
}
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -60,11 +60,11 @@ class WriteIssuerDataCommand(
|
|||
return null
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) {
|
||||
return TangemSdkError.DataCannotBeWritten()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean =
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ class WriteIssuerExtraDataCommand(
|
|||
return null
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams && isCounterRequired(card)) {
|
||||
return TangemSdkError.DataCannotBeWritten()
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ class WriteIssuerExtraDataCommand(
|
|||
card?.settingsMask?.contains(Settings.ProtectIssuerDataAgainstReplay) == true) {
|
||||
return TangemSdkError.OverwritingDataIsProhibited()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
private fun isCounterValid(issuerDataCounter: Int?, card: Card): Boolean =
|
||||
|
|
@ -134,7 +134,7 @@ class WriteIssuerExtraDataCommand(
|
|||
}
|
||||
is CompletionResult.Failure -> {
|
||||
if (session.environment.handleErrors) {
|
||||
performAfterCheck(session.environment.card, result.error)?.let {
|
||||
mapError(session.environment.card, result.error)?.let {
|
||||
callback(CompletionResult.Failure(it))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,11 +45,11 @@ class WriteUserDataCommand(private val userData: ByteArray? = null, private val
|
|||
return null
|
||||
}
|
||||
|
||||
override fun performAfterCheck(card: Card?, error: TangemSdkError): TangemSdkError? {
|
||||
override fun mapError(card: Card?, error: TangemSdkError): TangemSdkError {
|
||||
if (error is TangemSdkError.InvalidParams) {
|
||||
return TangemSdkError.Pin2OrCvcRequired()
|
||||
}
|
||||
return null
|
||||
return error
|
||||
}
|
||||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.commands.personalization
|
||||
|
||||
import com.tangem.CardSession
|
||||
import com.tangem.EncryptionMode
|
||||
import com.tangem.SessionEnvironment
|
||||
import com.tangem.TangemSdkError
|
||||
|
|
@ -9,6 +10,7 @@ import com.tangem.commands.CardStatus
|
|||
import com.tangem.commands.Command
|
||||
import com.tangem.commands.common.CardDeserializer
|
||||
import com.tangem.commands.personalization.entities.*
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.apdu.CommandApdu
|
||||
import com.tangem.common.apdu.Instruction
|
||||
import com.tangem.common.apdu.ResponseApdu
|
||||
|
|
@ -37,6 +39,18 @@ class PersonalizeCommand(
|
|||
private val acquirer: Acquirer? = null
|
||||
) : Command<Card>() {
|
||||
|
||||
override fun run(session: CardSession, callback: (result: CompletionResult<Card>) -> Unit) {
|
||||
val encryptionMode = session.environment.encryptionMode
|
||||
val encryptionKey = session.environment.encryptionKey
|
||||
session.environment.encryptionMode = EncryptionMode.NONE
|
||||
session.environment.encryptionKey = devPersonalizationKey
|
||||
super.run(session) { result ->
|
||||
session.environment.encryptionMode = encryptionMode
|
||||
session.environment.encryptionKey = encryptionKey
|
||||
callback(result)
|
||||
}
|
||||
}
|
||||
|
||||
override fun performPreCheck(card: Card): TangemSdkError? {
|
||||
if (card.status != CardStatus.NotPersonalized) {
|
||||
return TangemSdkError.AlreadyPersonalized()
|
||||
|
|
@ -46,11 +60,10 @@ class PersonalizeCommand(
|
|||
|
||||
override fun serialize(environment: SessionEnvironment): CommandApdu {
|
||||
return CommandApdu(Instruction.Personalize, serializePersonalizationData(config))
|
||||
.encrypt(EncryptionMode.NONE, devPersonalizationKey)
|
||||
}
|
||||
|
||||
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): Card {
|
||||
return CardDeserializer.deserialize(apdu.decrypt(devPersonalizationKey))
|
||||
return CardDeserializer.deserialize(apdu)
|
||||
}
|
||||
|
||||
private fun serializePersonalizationData(config: CardConfig): ByteArray {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ class ResponseApdu(private val data: ByteArray) {
|
|||
fun decrypt(encryptionKey: ByteArray?): ResponseApdu {
|
||||
if (encryptionKey == null) return this
|
||||
|
||||
if (data.size < 18) throw TangemSdkError.InvalidResponse()
|
||||
//nothing to decrypt
|
||||
if (data.size < 18) return this
|
||||
|
||||
val responseData = data.copyOf(data.size - 2)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.crypto
|
||||
|
||||
import com.tangem.EncryptionMode
|
||||
import org.spongycastle.jce.interfaces.ECPublicKey
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyPairGenerator
|
||||
|
|
@ -11,6 +12,16 @@ interface EncryptionHelper {
|
|||
val keyA: ByteArray
|
||||
|
||||
fun generateSecret(keyB: ByteArray): ByteArray
|
||||
|
||||
companion object {
|
||||
fun create(encryptionMode: EncryptionMode): EncryptionHelper? {
|
||||
return when (encryptionMode) {
|
||||
EncryptionMode.NONE -> null
|
||||
EncryptionMode.FAST -> FastEncryptionHelper()
|
||||
EncryptionMode.STRONG -> StrongEncryptionHelper()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StrongEncryptionHelper : EncryptionHelper {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue