Skip to content

Stage and retire backup certificate pins#419

Merged
kwsantiago merged 2 commits into
mainfrom
feat/cert-pin-backup-staging-ui
Jul 15, 2026
Merged

Stage and retire backup certificate pins#419
kwsantiago merged 2 commits into
mainfrom
feat/cert-pin-backup-staging-ui

Conversation

@kwsantiago

@kwsantiago kwsantiago commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Makes RFC 7469 backup certificate pins usable from the app. keep-frost-net already supports multiple SPKI pins per host so a relay certificate rotation verifies against either the current or the next key during the overlap, but on mobile that capability was dormant: the automatic flow only pins an unpinned host, and the UI could only clear a host's pins wholesale.

Changes

  • Bump keep.version to pick up the keep-mobile stage_certificate_pin / remove_certificate_pin FFI (backend merged in keep-mobile: expose backup cert-pin staging and per-pin removal keep#807).
  • Add reflection compat wrappers (stageCertificatePinCompat / removeCertificatePinCompat) alongside the existing getCertificatePinsCompat, so the app compiles regardless of binding freshness.
  • Certificate Pins settings card:
    • "Stage backup pin" dialog to add a second SPKI pin to an already-pinned host, with client-side validation that the hash is a 64-hex SHA-256 digest before it reaches the core.
    • Per-row retire now removes that specific pin instead of clearing the whole host; when it is the host's only pin, the dialog warns that the host will fall back to trust-on-first-use on the next connection.
  • Success/failure toast on staging.

Testing

  • New unit tests for the pin-count and hash-validation helpers (CertificatePinsCardTest).
  • Local compileDebugKotlin + testDebugUnitTest green against freshly generated bindings.

Summary by CodeRabbit

  • New Features

    • Added support for staging backup certificate pins from Settings.
    • Added the ability to retire individual certificate pins with confirmation.
    • Added validation requiring a 64-character hexadecimal SPKI hash.
    • Added status messages for successful or failed pin operations.
  • Bug Fixes

    • Improved safeguards when removing the last pin for a host.
  • Tests

    • Added coverage for pin retirement logic and SPKI hash validation.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kwsantiago, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cbba9d83-b1d8-4fa1-bfa9-fea6bb2ab0c4

📥 Commits

Reviewing files that changed from the base of the PR and between 1091522 and 773a5b9.

📒 Files selected for processing (3)
  • app/src/main/kotlin/io/privkey/keep/MainActivity.kt
  • app/src/main/kotlin/io/privkey/keep/SecurityCards.kt
  • app/src/main/res/values/strings_settings.xml

Walkthrough

Adds certificate pin staging and per-pin retirement APIs, wires them through the settings UI, validates SPKI hashes, and adds retirement/staging dialogs with supporting tests and strings.

Changes

Certificate pin lifecycle

Layer / File(s) Summary
Compatibility API and app facade
app/src/main/kotlin/io/privkey/keep/CertificatePinning.kt, app/src/main/kotlin/io/privkey/keep/KeepMobileApp.kt, keep.version
Adds reflective staging/removal operations, maps removal results to CertPinRemoval, and exposes null-safe KeepMobileApp methods.
Settings callback wiring
app/src/main/kotlin/io/privkey/keep/MainActivity.kt
Passes staging and retirement callbacks through the settings hierarchy, performs operations on IO, refreshes pins, and reports staging status.
Pin controls and validation
app/src/main/kotlin/io/privkey/keep/SecurityCards.kt, app/src/main/res/values/strings_settings.xml, app/src/test/kotlin/io/privkey/keep/CertificatePinsCardTest.kt
Adds staging and retirement dialogs, host-aware confirmation text, SPKI validation, localized strings, and helper tests.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant CertificatePinsCard
  participant SettingsTab
  participant KeepMobileApp
  participant KeepMobile
  User->>CertificatePinsCard: Confirm staging or retirement
  CertificatePinsCard->>SettingsTab: Invoke pin callback
  SettingsTab->>KeepMobileApp: Stage or remove certificate pin
  KeepMobileApp->>KeepMobile: Call compatibility API
  KeepMobile-->>KeepMobileApp: Return operation result
  KeepMobileApp-->>SettingsTab: Return status
  SettingsTab-->>CertificatePinsCard: Refresh displayed pins
Loading

Possibly related PRs

Suggested reviewers: wksantiago

Poem

I’m a rabbit guarding hashes bright,
Staging backups through the night.
Retire one, but leave the rest,
Confirm each choice before the nest.
Fresh pins bloom in settings’ light!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding support to stage and retire backup certificate pins.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/cert-pin-backup-staging-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/src/main/kotlin/io/privkey/keep/SecurityCards.kt (1)

225-228: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Compile the regular expression once.

isValidSpkiHash is called on every keystroke because it evaluates canStage during the text field's recomposition. Compiling a new Regex object each time is inefficient. Consider extracting the Regex to a top-level constant.

♻️ Proposed refactor
+private val SPKI_HASH_REGEX = Regex("[0-9a-fA-F]{64}")
+
 /** A valid SPKI pin is a SHA-256 digest: exactly 64 hex characters. */
 internal fun isValidSpkiHash(hash: String): Boolean =
-    hash.matches(Regex("[0-9a-fA-F]{64}"))
+    hash.matches(SPKI_HASH_REGEX)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/main/kotlin/io/privkey/keep/SecurityCards.kt` around lines 225 - 228,
Extract the SPKI hash pattern used by isValidSpkiHash into a top-level compiled
Regex constant, then have isValidSpkiHash reuse that constant for validation on
each call.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@app/src/main/kotlin/io/privkey/keep/SecurityCards.kt`:
- Around line 225-228: Extract the SPKI hash pattern used by isValidSpkiHash
into a top-level compiled Regex constant, then have isValidSpkiHash reuse that
constant for validation on each call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3e1d2ad3-c3f4-4db9-a473-d3e4044d0360

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa66e9 and 1091522.

📒 Files selected for processing (7)
  • app/src/main/kotlin/io/privkey/keep/CertificatePinning.kt
  • app/src/main/kotlin/io/privkey/keep/KeepMobileApp.kt
  • app/src/main/kotlin/io/privkey/keep/MainActivity.kt
  • app/src/main/kotlin/io/privkey/keep/SecurityCards.kt
  • app/src/main/res/values/strings_settings.xml
  • app/src/test/kotlin/io/privkey/keep/CertificatePinsCardTest.kt
  • keep.version

@kwsantiago kwsantiago merged commit c039fcf into main Jul 15, 2026
4 checks passed
@kwsantiago kwsantiago deleted the feat/cert-pin-backup-staging-ui branch July 15, 2026 17:50
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.

1 participant