# TASK-002A: Implement the OAuth PKCE protocol core Status: complete Owner: Codex Reviewer: Claude ## Outcome Provide a deterministic, Android-independent OAuth authorization contract that generates fresh PKCE/state values, requests only approved read scopes, and strictly validates callbacks before any authorization code can be exchanged. ## Writable paths - `app/src/main/java/com/ubisam/ubgitea/core/auth/protocol/` - `app/src/test/java/com/ubisam/ubgitea/core/auth/protocol/` - `docs/decisions/002-oauth-security.md` - `tasks/002-oauth-spike.md` - `tasks/002a-oauth-protocol-core.md` - `tasks/002b-oauth-android-integration.md` ## Acceptance criteria - [x] A fresh cryptographically random verifier and state are generated for every authorization attempt. - [x] PKCE uses SHA-256 and unpadded Base64 URL encoding with method `S256`. - [x] Authorization requests use HTTPS, the registered client ID and redirect URI, and only approved read scopes. - [x] Callback validation requires an exact redirect scheme, host, effective port, and path. - [x] Callback validation rejects missing, duplicated, mismatched, or expired state before accepting code or error parameters. - [x] Missing, blank, or duplicated codes and malformed query encoding are rejected. - [x] OAuth denial is represented separately from malformed callbacks. - [x] No client secret, token, Android type, or logging appears in the protocol package. - [x] Focused tests cover URL construction, freshness, PKCE vectors, scope, redirect validation, state, expiry, denial, code, and malformed input. ## Security and privacy This package must remain pure and must never persist or log authorization codes, tokens, verifier values, or state values. ## Required checks ```text ./gradlew assembleDebug ./gradlew testDebugUnitTest ./gradlew lintDebug ``` ## Evidence Recorded on 2026-08-25 using JDK 17 and Android SDK 36: - Focused OAuth protocol tests — 21 tests, 0 failures, 0 errors. - `./gradlew assembleDebug` — passed. - `./gradlew testDebugUnitTest` — passed; 43 tests across 9 suites, 0 failures, 0 errors. - `./gradlew lintDebug` — passed. - Sensitive-pattern scan excluding generated and Git metadata — no assigned client secret, token, password, or private-key value found. - `git diff --check` — passed. - Automatic string representations for transaction, request, authorization code, and denial results redact sensitive values. - Actual browser, App Link, token exchange, Keystore, and identity verification remain in TASK-002B. ## Review findings - P2 verifier grammar — fixed by enforcing the RFC 7636 unreserved ASCII character set and 43–128 character length; adversarial tests added. - P2 callback pollution/encoding — fixed by rejecting conflicting response keys and duplicate recognized parameters, and using strict UTF-8 percent decoding; adversarial tests added. - P3 time arithmetic overflow — fixed with saturating comparison boundaries and `Long.MIN_VALUE`/`Long.MAX_VALUE` tests. - Independent re-review confirmed all findings closed with no remaining P0-P3 findings.