-
Notifications
You must be signed in to change notification settings - Fork 56
fix(kotlin-sdk): Android host-app integration fixes (key security policy, unmanaged-identity reads, typed signing error) #4060
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
Open
bfoss765
wants to merge
6
commits into
dashpay:feat/kotlin-sdk-and-example-app
Choose a base branch
from
bfoss765:fix/kotlin-sdk-android-integration
base: feat/kotlin-sdk-and-example-app
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
3ee676b
feat(kotlin-sdk): configurable identity-key security policy + queryab…
bfoss765 40baa06
feat(kotlin-sdk): typed SigningKeyUnavailable error for missing signi…
bfoss765 16f54d1
fix(kotlin-sdk): Dashpay reads return null/empty for unmanaged identi…
bfoss765 d3aa786
fix(kotlin-sdk): stop upgrade deleting the legacy identity-key AES ke…
bfoss765 f8dd367
fix(kotlin-sdk): recover pre-alias-split RSA keys; distinguish invali…
bfoss765 374d023
fix(kotlin-sdk): don't let wrong-key RSA recovery throw uncaught from…
bfoss765 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
...s/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/security/KeySecurityPolicy.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package org.dashfoundation.dashsdk.security | ||
|
|
||
| /** | ||
| * Security policy for the Keystore alias that wraps **identity private | ||
| * keys** ([WalletStorage.storePrivateKey] / [WalletStorage.retrievePrivateKey]). | ||
| * | ||
| * The SDK's default alias gates every identity-key decrypt behind Android | ||
| * user authentication (biometric / device credential) with a short | ||
| * post-unlock validity window — the right model when the SDK owns the | ||
| * auth UX. Host apps that already gate wallet access behind their own | ||
| * auth model (e.g. an app-level PIN that decrypts the wallet) end up with | ||
| * a *second*, redundant auth prompt at signing time — and signing fails | ||
| * outside the ~30 s window entirely when no [BiometricGate] is wired. | ||
| * [DEVICE_BOUND] lets such hosts opt into a non-gated (but still | ||
| * hardware-backed, non-exportable) wrapping key instead. | ||
| * | ||
| * ## Semantics | ||
| * | ||
| * - [AUTH_GATED] — the default; behavior matches the historical one. New | ||
| * identity keys are wrapped under [KeystoreManager.KEYS_ALIAS_AUTH_GATED] | ||
| * (the legacy [KeystoreManager.KEYS_ALIAS] is kept read-only so pre-RSA | ||
| * blobs migrate rather than strand): encrypt (store) never prompts, decrypt | ||
| * (sign) requires user authentication within | ||
| * [KeystoreManager.AUTH_VALIDITY_SECONDS] of a biometric / device-credential | ||
| * auth, re-promptable through a [BiometricGate]. | ||
| * - [DEVICE_BOUND] — identity keys are wrapped under the separate | ||
| * [KeystoreManager.KEYS_ALIAS_DEVICE_BOUND] alias: the same | ||
| * StrongBox-preferring, non-exportable RSA wrapping pair, but with **no** | ||
| * `setUserAuthenticationRequired` gate, so decrypts never throw | ||
| * `UserNotAuthenticatedException` and never need a [BiometricGate]. | ||
| * Keys remain bound to this device's Keystore (`setUnlockedDeviceRequired` | ||
| * still applies) — the host app is responsible for gating *access* to | ||
| * signing flows (PIN, biometrics, session policy) itself. | ||
| * | ||
| * ## Choosing and switching | ||
| * | ||
| * The two policies use **distinct Keystore aliases**, and a blob written | ||
| * under one alias can only be decrypted by that alias's private key. Pick | ||
| * the policy once per install and construct [WalletStorage] / | ||
| * [KeystoreManager] with it consistently: switching an existing install to | ||
| * the other policy leaves previously stored identity keys undecryptable | ||
| * (they surface through the key-health / re-derive path, e.g. | ||
| * `PlatformWalletManager.repairIdentityKey`, which re-encrypts under the | ||
| * current policy's alias). Mnemonics ([KeystoreManager.MASTER_ALIAS]) are | ||
| * unaffected — this policy governs identity keys only. | ||
| */ | ||
| enum class KeySecurityPolicy { | ||
| /** | ||
| * Identity-key decrypts require Android user authentication within | ||
| * [KeystoreManager.AUTH_VALIDITY_SECONDS] (the historical default). | ||
| */ | ||
| AUTH_GATED, | ||
|
|
||
| /** | ||
| * Identity-key decrypts are hardware-backed but not auth-gated; the | ||
| * host app supplies its own authentication model. | ||
| */ | ||
| DEVICE_BOUND, | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: Concurrent pending-key updates can overwrite each other
recordPendingIdentityKeyandclearPendingIdentityKeyperform non-atomic read-modify-write operations onMutableStateFlow.value. The persistence callback runs synchronously on its Rust caller thread, while the newly addedmarkIdentityKeyRepairedcan be invoked from an arbitrary host thread throughrepairIdentityKey. If a repair clears key A while a persistence callback records key B, both can read the same map and one write can discard the other. Losing the record leaves a watch-only key without the queryable pending state this PR adds; losing the clear leaves a repaired key stale. UseMutableStateFlow.updateor another atomic synchronization mechanism for both mutations.source: ['sonnet5-security-auditor']
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Resolved in
f8dd367— Concurrent pending-key updates can overwrite each other no longer present.Auto-resolved by the review system based on the latest commit diff. If you believe this was closed in error, reopen the thread.