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-9330 Offline Card attestation error content #435

Merged
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
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
Loading