Skip to content

Dpop thumbprint validation#942

Open
pmathew92 wants to merge 13 commits intodpop_thumbprint_fixfrom
dpop_thumbprint_validation
Open

Dpop thumbprint validation#942
pmathew92 wants to merge 13 commits intodpop_thumbprint_fixfrom
dpop_thumbprint_validation

Conversation

@pmathew92
Copy link
Contributor

Changes

This PR adds the DPoP validation check before making call to the oauth/token endpoint

Checklist

@pmathew92 pmathew92 marked this pull request as ready for review March 25, 2026 07:28
@pmathew92 pmathew92 requested a review from a team as a code owner March 25, 2026 07:28
Copilot AI review requested due to automatic review settings March 25, 2026 07:28
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a DPoP state validation gate to credentials refresh flows to prevent calling /oauth/token with stale/misaligned DPoP key material.

Changes:

  • Introduces validateDPoPState(tokenType) in BaseCredentialsManager to verify DPoP key presence/configuration and thumbprint alignment (with migration/backfill behavior).
  • Calls DPoP validation before ssoExchange(...) and renewAuth(...) in both CredentialsManager and SecureCredentialsManager.
  • Captures APICredentials.type when reading cached API credentials to drive validation during API-credentials refresh.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
auth0/src/main/java/com/auth0/android/authentication/storage/BaseCredentialsManager.kt Adds centralized DPoP validation (key exists, client configured, thumbprint matches/backfills).
auth0/src/main/java/com/auth0/android/authentication/storage/CredentialsManager.kt Runs DPoP validation before token refresh/SSO exchange; threads API-credential token type into validation.
auth0/src/main/java/com/auth0/android/authentication/storage/SecureCredentialsManager.kt Runs DPoP validation before token refresh/SSO exchange; threads decrypted API-credential token type into validation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +232 to +237
val currentThumbprint = try {
DPoPUtil.getPublicKeyJWK()
} catch (e: DPoPException) {
Log.e(this::class.java.simpleName, "Failed to read DPoP key thumbprint", e)
null
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

validateDPoPState only catches DPoPException around DPoPUtil.getPublicKeyJWK(), but KeyStore.getKey(...) can also throw other runtime/checked exceptions (e.g., UnrecoverableKeyException, NoSuchAlgorithmException, ClassCastException) via DPoPKeyStore.getKeyPair(). As written, those would bubble up during token refresh and can crash the app. Consider broadening the catch to Exception (or Throwable if you want to be extra defensive) and returning a CredentialsManagerException instead of throwing.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This will be fixed as a separate PR at the root level

Comment on lines +239 to +244
if (storedThumbprint != null) {
if (currentThumbprint != storedThumbprint) {
Log.w(this::class.java.simpleName, "DPoP key thumbprint mismatch. Clearing stale credentials.")
clearCredentials()
return CredentialsManagerException(CredentialsManagerException.Code.DPOP_KEY_MISSING)
}
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

When storedThumbprint is set, a failure to read currentThumbprint (resulting in null) will be treated as a thumbprint mismatch, which clears credentials and returns DPOP_KEY_MISSING. This can log users out on transient/unknown KeyStore read issues even though the key alias exists (you already checked hasKeyPair()). Consider handling currentThumbprint == null as an indeterminate state (e.g., return an error without clearing, or retry) and only clearing when you have a non-null thumbprint that actually differs.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is intentional. hasKeyPair only checks if the KeyAlias exists or not. getPublicKeyJWK() actually reads the key and extracts the public key. If the alias exists but the key can't be read, the key is effectively unusable — any DPoP proof generation will also fail. So throwing error at this point is the correct approach

Comment on lines +207 to +211
protected fun validateDPoPState(tokenType: String?): CredentialsManagerException? {
val storedThumbprint = storage.retrieveString(KEY_DPOP_THUMBPRINT)
val isDPoPBound = (tokenType?.equals("DPoP", ignoreCase = true) == true)
|| (storedThumbprint != null)
if (!isDPoPBound) return null
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This change introduces new DPoP validation behavior that can fail early (and in some cases clear stored credentials) during renewAuth(...) / ssoExchange(...). There are existing unit tests for both CredentialsManager and SecureCredentialsManager, but none appear to cover DPoP scenarios; please add tests that exercise the new paths (key missing, thumbprint mismatch, and client not configured) and assert both the returned CredentialsManagerException code and whether credentials are cleared/backfilled as intended.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Tests will be added later

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants