Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AND-4928 move online attestation on backup flow #339

Merged
merged 1 commit into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 ->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Он не должен закрываться нигде? Или я не увидел просто?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

по логике не должно быть такого, что джобы висят и объект сервиса уже не нужен, + он не привязан к жц никак

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?,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

А дата класс не требует переопределения equals из-за использования массива байт?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

требует, но тут смысла не имеет особого

) : 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