Skip to content

Commit

Permalink
AND-9330 Offline Card attestation error content
Browse files Browse the repository at this point in the history
  • Loading branch information
Mama1emon committed Dec 27, 2024
1 parent 525abd4 commit a05f6e4
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 108 deletions.
1 change: 1 addition & 0 deletions .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ fun TangemSdkError.localizedDescriptionRes(): TangemSdkErrorDescription {
is TangemSdkError.NonHardenedDerivationNotSupported,
is TangemSdkError.AuthenticationNotInitialized,
is TangemSdkError.NfcFeatureIsUnavailable,
is TangemSdkError.CardOfflineVerificationFailed,
-> TangemSdkErrorDescription()

is TangemSdkError.BackupFailedEmptyWallets,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ sealed class TangemSdkError(code: Int) : TangemError(code) {
* Returns if NFC feature for device is not supported
*/
class NfcFeatureIsUnavailable : TangemSdkError(code = 50027)

class CardOfflineVerificationFailed : TangemSdkError(code = 50028)

/**
* Get error according to the pin type
* @param userCodeType: Specific user code type
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,19 @@ class AttestationTask(
currentAttestationStatus = currentAttestationStatus.copy(
cardKeyAttestation = Attestation.Status.Failed,
)
continueAttestation(session, callback)

val isDevelopmentCard =
session.environment.card!!.firmwareVersion.type == FirmwareVersion.FirmwareType.Sdk

if (isDevelopmentCard) {
session.viewDelegate.attestationDidFail(
isDevCard = true,
positive = { complete(session, callback) },
negative = { callback(CompletionResult.Failure(TangemSdkError.UserCancelled())) },
)
} else {
callback(CompletionResult.Failure(TangemSdkError.CardOfflineVerificationFailed()))
}
} else {
callback(CompletionResult.Failure(result.error))
}
Expand All @@ -97,78 +109,6 @@ class AttestationTask(
}
}

private fun runWalletsAttestation(session: CardSession, callback: CompletionCallback<Attestation>) {
attestWallets(session) { result ->
when (result) {
is CompletionResult.Success -> {
// Wallets attestation completed. Update status and continue attestation
val hasWarnings = result.data
val status = if (hasWarnings) Attestation.Status.Warning else Attestation.Status.Verified
currentAttestationStatus = currentAttestationStatus.copy(walletKeysAttestation = status)
runExtraAttestation(session, callback)
}
is CompletionResult.Failure -> {
// Wallets attestation failed. Update status and continue attestation
if (result.error is TangemSdkError.CardVerificationFailed) {
currentAttestationStatus = currentAttestationStatus.copy(
walletKeysAttestation = Attestation.Status.Failed,
)
runExtraAttestation(session, callback)
} else {
callback(CompletionResult.Failure(result.error))
}
}
}
}
}

private fun runExtraAttestation(session: CardSession, callback: CompletionCallback<Attestation>) {
// TODO: ATTEST_CARD_FIRMWARE, ATTEST_CARD_UNIQUENESS
waitForOnlineAndComplete(session, callback)
}

private fun attestWallets(session: CardSession, callback: CompletionCallback<Boolean>) {
session.scope.launch {
val card = session.environment.card!!
val walletsKeys = card.wallets.map { it.publicKey }
val attestationCommands = walletsKeys.map { AttestWalletKeyCommand(it) }

// check for hacking attempts with signs
var hasWarnings = card.wallets.mapNotNull { it.totalSignedHashes }.any { it > MAX_COUNTER }
var shouldReturn = false
var flowIsCompleted = false

if (attestationCommands.isEmpty()) {
callback(CompletionResult.Success(hasWarnings))
return@launch
}

flow {
attestationCommands.forEach { emit(it) }
}.onCompletion {
flowIsCompleted = true
}.collect {
if (shouldReturn) return@collect

it.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
// check for hacking attempts with attestWallet
if (result.data.counter != null && result.data.counter > MAX_COUNTER) {
hasWarnings = true
}
if (flowIsCompleted) callback(CompletionResult.Success(hasWarnings))
}
is CompletionResult.Failure -> {
shouldReturn = true
callback(CompletionResult.Failure(result.error))
}
}
}
}
}
}

/**
* Dev card will not pass online attestation. Or, if the card already failed offline attestation,
* we can skip online part. So, we can send the error to the publisher immediately
Expand Down Expand Up @@ -222,62 +162,39 @@ class AttestationTask(
}
}

private fun retryOnline(session: CardSession, callback: CompletionCallback<Attestation>) {
onlineAttestationSubscription = null
onlineAttestationChannel.cancel()
onlineAttestationChannel = ConflatedBroadcastChannel()

val card = session.environment.card.guard {
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
return
}

runOnlineAttestation(session.scope, card)
waitForOnlineAndComplete(session, callback)
}

private fun processAttestationReport(session: CardSession, callback: CompletionCallback<Attestation>) {
when (currentAttestationStatus.status) {
Attestation.Status.Failed, Attestation.Status.Skipped -> {
Attestation.Status.Failed,
Attestation.Status.Skipped,
-> {
val isDevelopmentCard = session.environment.card!!.firmwareVersion.type ==
FirmwareVersion.FirmwareType.Sdk

// Possible production sample or development card
if (isDevelopmentCard || session.environment.config.allowUntrustedCards) {
session.viewDelegate.attestationDidFail(
isDevelopmentCard,
{
complete(session, callback)
},
) {
callback(CompletionResult.Failure(TangemSdkError.UserCancelled()))
}
isDevCard = isDevelopmentCard,
positive = { complete(session, callback) },
negative = { callback(CompletionResult.Failure(TangemSdkError.UserCancelled())) },
)
} else {
callback(CompletionResult.Failure(TangemSdkError.CardVerificationFailed()))
}
}
Attestation.Status.Verified -> {
complete(session, callback)
}
Attestation.Status.Verified -> complete(session, callback)
Attestation.Status.VerifiedOffline -> {
if (session.environment.config.attestationMode == Mode.Offline) {
complete(session, callback)
return
}

session.viewDelegate.attestationCompletedOffline(
{
complete(session, callback)
},
{
callback(CompletionResult.Failure(TangemSdkError.UserCancelled()))
},
{
positive = { complete(session, callback) },
negative = { callback(CompletionResult.Failure(TangemSdkError.UserCancelled())) },
retry = {
retryOnline(session) { result ->
when (result) {
is CompletionResult.Success -> {
processAttestationReport(session, callback)
}
is CompletionResult.Success -> processAttestationReport(session, callback)
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
Expand All @@ -292,6 +209,92 @@ class AttestationTask(
}
}

private fun retryOnline(session: CardSession, callback: CompletionCallback<Attestation>) {
onlineAttestationSubscription = null
onlineAttestationChannel.cancel()
onlineAttestationChannel = ConflatedBroadcastChannel()

val card = session.environment.card.guard {
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
return
}

runOnlineAttestation(session.scope, card)
waitForOnlineAndComplete(session, callback)
}

private fun runWalletsAttestation(session: CardSession, callback: CompletionCallback<Attestation>) {
attestWallets(session) { result ->
when (result) {
is CompletionResult.Success -> {
// Wallets attestation completed. Update status and continue attestation
val hasWarnings = result.data
val status = if (hasWarnings) Attestation.Status.Warning else Attestation.Status.Verified
currentAttestationStatus = currentAttestationStatus.copy(walletKeysAttestation = status)
runExtraAttestation(session, callback)
}
is CompletionResult.Failure -> {
// Wallets attestation failed. Update status and continue attestation
if (result.error is TangemSdkError.CardVerificationFailed) {
currentAttestationStatus = currentAttestationStatus.copy(
walletKeysAttestation = Attestation.Status.Failed,
)
runExtraAttestation(session, callback)
} else {
callback(CompletionResult.Failure(result.error))
}
}
}
}
}

private fun attestWallets(session: CardSession, callback: CompletionCallback<Boolean>) {
session.scope.launch {
val card = session.environment.card!!
val walletsKeys = card.wallets.map { it.publicKey }
val attestationCommands = walletsKeys.map { AttestWalletKeyCommand(it) }

// check for hacking attempts with signs
var hasWarnings = card.wallets.mapNotNull { it.totalSignedHashes }.any { it > MAX_COUNTER }
var shouldReturn = false
var flowIsCompleted = false

if (attestationCommands.isEmpty()) {
callback(CompletionResult.Success(hasWarnings))
return@launch
}

flow {
attestationCommands.forEach { emit(it) }
}.onCompletion {
flowIsCompleted = true
}.collect {
if (shouldReturn) return@collect

it.run(session) { result ->
when (result) {
is CompletionResult.Success -> {
// check for hacking attempts with attestWallet
if (result.data.counter != null && result.data.counter > MAX_COUNTER) {
hasWarnings = true
}
if (flowIsCompleted) callback(CompletionResult.Success(hasWarnings))
}
is CompletionResult.Failure -> {
shouldReturn = true
callback(CompletionResult.Failure(result.error))
}
}
}
}
}
}

private fun runExtraAttestation(session: CardSession, callback: CompletionCallback<Attestation>) {
// TODO: ATTEST_CARD_FIRMWARE, ATTEST_CARD_UNIQUENESS
waitForOnlineAndComplete(session, callback)
}

private fun complete(session: CardSession, callback: CompletionCallback<Attestation>) {
session.environment.card = session.environment.card?.copy(attestation = currentAttestationStatus)
callback(CompletionResult.Success(currentAttestationStatus))
Expand Down

0 comments on commit a05f6e4

Please sign in to comment.