Skip to content

Commit

Permalink
Merge pull request #339 from tangem/feature/AND-4928-online-attestati…
Browse files Browse the repository at this point in the history
…on-on-backup

AND-4928 move online attestation on backup flow
  • Loading branch information
kozarezvlad authored Jan 17, 2024
2 parents a89cfbe + 3fd66be commit 945f409
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 88 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.tangem.operations.backup

import com.tangem.common.CompletionResult
import com.tangem.common.core.CompletionCallback
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.services.Result
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvTag
import com.tangem.crypto.sign
import com.tangem.operations.attestation.OnlineCardVerifier

class BackupCertificateProvider {

private val onlineCardVerifier: OnlineCardVerifier = OnlineCardVerifier()

suspend fun getCertificate(
cardId: String,
cardPublicKey: ByteArray,
developmentMode: Boolean,
callback: CompletionCallback<ByteArray>,
) {
if (developmentMode) {
val issuerPrivateKey =
"11121314151617184771ED81F2BACF57479E4735EB1405083927372D40DA9E92".hexToBytes()
val issuerSignature = cardPublicKey.sign(issuerPrivateKey)
callback(CompletionResult.Success(generateCertificate(cardPublicKey, issuerSignature)))
return
}

when (
val result =
onlineCardVerifier.getCardData(cardId, cardPublicKey)
) {
is Result.Success -> {
val signature = result.data.issuerSignature.guard {
callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed()))
return
}
callback(
CompletionResult.Success(
generateCertificate(cardPublicKey, signature.hexToBytes()),
),
)
}

is Result.Failure ->
callback(CompletionResult.Failure(TangemSdkError.IssuerSignatureLoadingFailed()))
}
}

private fun generateCertificate(cardPublicKey: ByteArray, issuerSignature: ByteArray): ByteArray {
return TlvBuilder().run {
append(TlvTag.CardPublicKey, cardPublicKey)
append(TlvTag.IssuerDataSignature, issuerSignature)
serialize()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.tangem.operations.backup

import com.squareup.moshi.JsonClass
import com.tangem.Log
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.common.CardIdFormatter
Expand All @@ -16,11 +17,17 @@ import com.tangem.common.extensions.calculateSha256
import com.tangem.common.extensions.guard
import com.tangem.common.json.MoshiJsonConverter
import com.tangem.common.services.secure.SecureStorage
import com.tangem.common.tlv.TlvBuilder
import com.tangem.common.tlv.TlvTag
import com.tangem.operations.CommandResponse
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus
import java.io.PrintWriter
import java.io.StringWriter

@Suppress("LargeClass")
class BackupService(
Expand Down Expand Up @@ -65,7 +72,16 @@ class BackupService(
*/
var skipCompatibilityChecks: Boolean = false

private val backupScope = CoroutineScope(Dispatchers.IO) + CoroutineExceptionHandler { _, throwable ->
val sw = StringWriter()
throwable.printStackTrace(PrintWriter(sw))
val exceptionAsString: String = sw.toString()
Log.error { exceptionAsString }
throw throwable
}

private val handleErrors = sdk.config.handleErrors
private val backupCertificateProvider = BackupCertificateProvider()

init {
updateState()
Expand All @@ -85,7 +101,22 @@ class BackupService(
callback(CompletionResult.Failure(TangemSdkError.TooMuchBackupCards()))
return
}
readBackupCard(primaryCard, callback)

if (primaryCard.certificate != null) {
readBackupCard(primaryCard, callback)
return
}
backupScope.launch {
fetchCertificate(
cardId = primaryCard.cardId,
cardPublicKey = primaryCard.cardPublicKey,
onFailed = { callback(CompletionResult.Failure(it)) },
) {
val updatedPrimaryCard = primaryCard.copy(certificate = it)
repo.data = repo.data.copy(primaryCard = updatedPrimaryCard)
readBackupCard(updatedPrimaryCard, callback)
}
}
}

fun setAccessCode(code: String): CompletionResult<Unit> {
Expand Down Expand Up @@ -159,7 +190,7 @@ class BackupService(
} ?: Message(header = stringsLocator.getString(StringsLocator.ID.BACKUP_PREPARE_PRIMARY_CARD_MESSAGE))

sdk.startSessionWithRunnable(
runnable = StartPrimaryCardLinkingTask(),
runnable = StartPrimaryCardLinkingCommand(),
cardId = cardId,
initialMessage = message,
iconScanRes = iconScanRes,
Expand All @@ -175,6 +206,28 @@ class BackupService(
}
}

private suspend fun fetchCertificate(
cardId: String,
cardPublicKey: ByteArray,
onFailed: (TangemSdkError) -> Unit,
onLoaded: (ByteArray) -> Unit,
) {
backupCertificateProvider.getCertificate(
cardId = cardId,
cardPublicKey = cardPublicKey,
developmentMode = false,
) {
val certificate = when (it) {
is CompletionResult.Success -> it.data
is CompletionResult.Failure -> {
onFailed(TangemSdkError.IssuerSignatureLoadingFailed())
return@getCertificate
}
}
onLoaded(certificate)
}
}

private fun handleCompletion(result: CompletionResult<Card>, callback: CompletionCallback<Card>) {
when (result) {
is CompletionResult.Success -> {
Expand Down Expand Up @@ -205,13 +258,21 @@ class BackupService(
return currentState
}

private fun addBackupCard(backupCard: BackupCard) {
val updatedList = repo.data.backupCards
.filter { it.cardId != backupCard.cardId }
.plus(backupCard)

repo.data = repo.data.copy(backupCards = updatedList)
updateState()
private fun addBackupCard(backupCard: BackupCard, callback: CompletionCallback<Unit>) {
backupScope.launch {
fetchCertificate(
backupCard.cardId,
backupCard.cardPublicKey,
onFailed = { callback(CompletionResult.Failure(it)) },
) { cert ->
val updatedBackupCard = backupCard.copy(certificate = cert)
val updatedList = repo.data.backupCards
.filter { it.cardId != updatedBackupCard.cardId }
.plus(updatedBackupCard)
repo.data = repo.data.copy(backupCards = updatedList)
updateState()
}
}
}

private fun readBackupCard(primaryCard: PrimaryCard, callback: CompletionCallback<Unit>) {
Expand All @@ -227,7 +288,7 @@ class BackupService(
) { result ->
when (result) {
is CompletionResult.Success -> {
addBackupCard(result.data)
addBackupCard(result.data, callback)
callback(CompletionResult.Success(Unit))
}

Expand Down Expand Up @@ -370,6 +431,7 @@ class BackupService(

private fun onBackupCompleted() {
repo.reset()
backupScope.cancel()
}

sealed class State {
Expand Down Expand Up @@ -402,11 +464,10 @@ class RawPrimaryCard(

@Suppress("LongParameterList")
@JsonClass(generateAdapter = true)
class PrimaryCard(
data class PrimaryCard(
val cardId: String,
override val cardPublicKey: ByteArray,
val cardPublicKey: ByteArray,
val linkingKey: ByteArray,
override val issuerSignature: ByteArray,
// For compatibility check with backup card
val existingWalletsCount: Int,
val isHDWalletAllowed: Boolean,
Expand All @@ -415,19 +476,20 @@ class PrimaryCard(
val batchId: String?, // for compatibility with interrupted backups
val firmwareVersion: FirmwareVersion?, // for compatibility with interrupted backups
val isKeysImportAllowed: Boolean?, // for compatibility with interrupted backups
) : CertificateProvider {
constructor(rawPrimaryCard: RawPrimaryCard, issuerSignature: ByteArray) : this(
val certificate: ByteArray?,
) : CommandResponse {
constructor(rawPrimaryCard: RawPrimaryCard, certificate: ByteArray) : this(
cardId = rawPrimaryCard.cardId,
cardPublicKey = rawPrimaryCard.cardPublicKey,
linkingKey = rawPrimaryCard.linkingKey,
issuerSignature = issuerSignature,
existingWalletsCount = rawPrimaryCard.existingWalletsCount,
isHDWalletAllowed = rawPrimaryCard.isHDWalletAllowed,
issuer = rawPrimaryCard.issuer,
walletCurves = rawPrimaryCard.walletCurves,
batchId = rawPrimaryCard.batchId,
firmwareVersion = rawPrimaryCard.firmwareVersion,
isKeysImportAllowed = rawPrimaryCard.isKeysImportAllowed,
certificate = certificate,
)
}

Expand All @@ -440,19 +502,19 @@ class RawBackupCard(
) : CommandResponse

@JsonClass(generateAdapter = true)
class BackupCard(
data class BackupCard(
val cardId: String,
override val cardPublicKey: ByteArray,
val cardPublicKey: ByteArray,
val linkingKey: ByteArray,
val attestSignature: ByteArray,
override val issuerSignature: ByteArray,
) : CertificateProvider {
constructor(rawBackupCard: RawBackupCard, issuerSignature: ByteArray) : this(
val certificate: ByteArray?,
) {
constructor(rawBackupCard: RawBackupCard, certificate: ByteArray) : this(
cardId = rawBackupCard.cardId,
cardPublicKey = rawBackupCard.cardPublicKey,
linkingKey = rawBackupCard.linkingKey,
attestSignature = rawBackupCard.attestSignature,
issuerSignature = issuerSignature,
certificate = certificate,
)
}

Expand Down Expand Up @@ -516,17 +578,3 @@ class BackupRepo(

enum class StorageKey { BackupData }
}

interface CertificateProvider {
val cardPublicKey: ByteArray
val issuerSignature: ByteArray
}

@Throws(TangemSdkError::class)
fun CertificateProvider.generateCertificate(): ByteArray {
return TlvBuilder().run {
append(TlvTag.CardPublicKey, cardPublicKey)
append(TlvTag.IssuerDataSignature, issuerSignature)
serialize()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class LinkBackupCardsCommand(
TlvBuilder().apply {
append(TlvTag.FileIndex, index)
append(TlvTag.BackupCardLinkingKey, card.linkingKey)
append(TlvTag.Certificate, card.generateCertificate())
append(TlvTag.Certificate, card.certificate)
append(TlvTag.CardSignature, card.attestSignature)
}.serialize()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class LinkPrimaryCardCommand(
tlvBuilder.append(TlvTag.Pin, environment.accessCode.value)
tlvBuilder.append(TlvTag.Pin2, environment.passcode.value)
tlvBuilder.append(TlvTag.PrimaryCardLinkingKey, primaryCard.linkingKey)
tlvBuilder.append(TlvTag.Certificate, primaryCard.generateCertificate())
tlvBuilder.append(TlvTag.Certificate, primaryCard.certificate)
tlvBuilder.append(TlvTag.BackupAttestSignature, attestSignature)
tlvBuilder.append(TlvTag.NewPin, accessCode)
tlvBuilder.append(TlvTag.NewPin2, passcode)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.tangem.operations.Command

/**
*/
class StartPrimaryCardLinkingCommand : Command<RawPrimaryCard>() {
class StartPrimaryCardLinkingCommand : Command<PrimaryCard>() {

override val allowsRequestAccessCodeFromRepository: Boolean
get() = false
Expand Down Expand Up @@ -45,7 +45,7 @@ class StartPrimaryCardLinkingCommand : Command<RawPrimaryCard>() {
return CommandApdu(Instruction.StartPrimaryCardLinking, tlvBuilder.serialize())
}

override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): RawPrimaryCard {
override fun deserialize(environment: SessionEnvironment, apdu: ResponseApdu): PrimaryCard {
val tlvData = apdu.getTlvData()
?: throw TangemSdkError.DeserializeApduFailed()

Expand All @@ -55,7 +55,7 @@ class StartPrimaryCardLinkingCommand : Command<RawPrimaryCard>() {

val card = environment.card ?: throw TangemSdkError.MissingPreflightRead()

return RawPrimaryCard(
return PrimaryCard(
cardId = decoder.decode(TlvTag.CardId),
cardPublicKey = cardPublicKey,
linkingKey = decoder.decode(TlvTag.PrimaryCardLinkingKey),
Expand All @@ -66,6 +66,7 @@ class StartPrimaryCardLinkingCommand : Command<RawPrimaryCard>() {
batchId = card.batchId,
firmwareVersion = card.firmwareVersion,
isKeysImportAllowed = card.settings.isKeysImportAllowed,
certificate = null,
)
}
}
Loading

0 comments on commit 945f409

Please sign in to comment.