Skip to content

fix: add gpg --export fallback for keyboxd compatibility#84

Open
avivi55 wants to merge 4 commits into
0x77dev:mainfrom
avivi55:new_gpg_keybox_lookup
Open

fix: add gpg --export fallback for keyboxd compatibility#84
avivi55 wants to merge 4 commits into
0x77dev:mainfrom
avivi55:new_gpg_keybox_lookup

Conversation

@avivi55
Copy link
Copy Markdown

@avivi55 avivi55 commented Apr 21, 2026

The modern versions of GnuPG (>= 2.4.1) enable use-keyboxd by default. This migrates public key management away from the legacy pubring.kbx file into an internal SQLite database managed exclusively by the keyboxd background daemon.
Thus, because here we only read the on-disk .kbx file, it fails to find newly imported keys or smartcard certificates that are only tracked by keyboxd.

We don't use GnuPG's keyboxd to access keybox databases. Instead, we read the certificates directly from the keybox database if we discover one.
https://crates.io/crates/sequoia-chameleon-gnupg

This implementation is based on keybox files created by GnuPG 2.2.23 and the way they are handled by the kbxutil program from that version of GnuPG.
https://docs.rs/sequoia-ipc/latest/sequoia_ipc/keybox/struct.Keybox.html

This PR adds a fallback mechanism to the certificate loading process. If a key isn't found in the legacy keybox, the code now shells out to gpg --export -a to request the certificate directly from GnuPG's IPC/daemon. I know it isn't pretty but I don't know how to do this otherwise.

I don't know if this issue is only on my part, but without this I couldn't make my security key appear in my .kbx file.

Summary by CodeRabbit

  • Bug Fixes
    • Improved certificate retrieval: when a local lookup finds no matches, the system now attempts to export certificates via GPG as a fallback. If the export succeeds and returns data, the certificate is accepted; on failure or empty output the original error path is preserved but with additional debug logging. This reduces missed certificates and improves reliability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1a2efcb0-d81e-431f-8ad8-06063616ae89

📥 Commits

Reviewing files that changed from the base of the PR and between 71b9cdc and 33cf7a4.

📒 Files selected for processing (1)
  • crates/gpg/src/keybox.rs
✅ Files skipped from review due to trivial changes (1)
  • crates/gpg/src/keybox.rs

Walkthrough

When load_cert finds no matches in the keybox, it now attempts a fallback: run gpg --export -a <spec>, parse non-empty stdout into a Cert and return it; on command failure, empty output, or execution errors it logs debug details and falls back to the original error path.

Changes

Cohort / File(s) Summary
Certificate Fallback Loading
crates/gpg/src/keybox.rs
Added fallback in load_cert to invoke gpg --export -a <spec> when keybox lookup yields no matches; parse stdout into a Cert on success, log debug details and proceed to original error handling on failure.

Sequence Diagram(s)

sequenceDiagram
  participant Loader as Keybox loader
  participant GPG as `gpg --export -a`
  participant Parser as Cert parser

  Loader->>Loader: perform keybox lookup
  alt no matches
    Loader->>GPG: spawn process with spec
    GPG-->>Loader: stdout / exit status
    alt stdout non-empty & exit success
      Loader->>Parser: pass exported bytes
      Parser-->>Loader: parsed Cert
      Loader-->>Client: return Cert
    else exit failure or empty stdout
      Loader-->>Loader: log debug details
      Loader-->>Client: return original "no matching cert" error
    end
  else matches found
    Loader-->>Client: return matching Cert(s)
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 I dug through keys and found no light,
So I called gpg in the quiet night,
It sang me bytes in armored streams,
I parsed the tune and stitched new seams,
Hooray — a fallback snug and bright! 🥕🔑

🚥 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 and concisely describes the main change: adding a gpg --export fallback to handle keyboxd compatibility issues when keybox searches fail.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/gpg/src/keybox.rs (1)

150-155: ⚠️ Potential issue | 🟠 Major

Make the fallback run when legacy keybox loading fails.

load_keybox_certs()? returns before Line 155, so keyboxd-only setups with no readable/non-empty pubring.kbx still never reach the new gpg --export fallback.

🐛 Proposed fix
   tracing::debug!("Searching GnuPG keybox");
-  let certs = load_keybox_certs()?;
-  let matches = find_certs_in_keybox(&certs, spec);
+  let (matches, keybox_error) = match load_keybox_certs() {
+    Ok(certs) => (find_certs_in_keybox(&certs, spec), None),
+    Err(e) => {
+      tracing::debug!(error = %e, "Keybox lookup failed, trying `gpg --export` fallback");
+      (Vec::new(), Some(e))
+    }
+  };
 
   if matches.is_empty() {
     tracing::debug!(spec = spec, "No matching cert in keybox, trying `gpg --export` fallback");
@@
-    bail!(
-      "No matching certificate found for '{}'. Provide a .asc file path or import the key into your keybox.",
-      spec
-    );
+    if let Some(e) = keybox_error {
+      bail!(
+        "No matching certificate found for '{}'. Provide a .asc file path or import the key into your keybox. Keybox lookup also failed: {:#}",
+        spec,
+        e
+      );
+    }
+
+    bail!(
+      "No matching certificate found for '{}'. Provide a .asc file path or import the key into your keybox.",
+      spec
+    );
   }

Also applies to: 171-174

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/gpg/src/keybox.rs` around lines 150 - 155, load_keybox_certs()? can
return an Err and cause an early return, preventing the intended `gpg --export`
fallback when the keybox is absent or unreadable; instead, call
load_keybox_certs() without the `?` and match its Result, treating Err (and
empty cert list) as an empty certs Vec so execution continues to
find_certs_in_keybox(spec) and then triggers the fallback when
matches.is_empty(); apply the same pattern to the other occurrence (the later
block around the second find_certs_in_keybox call) so both paths fall back to
`gpg --export` rather than returning early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/gpg/src/keybox.rs`:
- Around line 157-164: The match arm that handles gpg export currently trusts
non-empty out.stdout and calls Cert::from_bytes; change this to first check
out.status.success() before attempting to parse and only call Cert::from_bytes
when both out.status.success() is true and out.stdout is non-empty (the
Command::new("gpg") invocation/match and Cert::from_bytes call are the targets).
If the status is not success, log or include out.stderr (and the exit status) in
the tracing::debug/error message so failed gpg exports are visible for
debugging, and fall through to the original error path when export failed or
produced no valid output.

---

Outside diff comments:
In `@crates/gpg/src/keybox.rs`:
- Around line 150-155: load_keybox_certs()? can return an Err and cause an early
return, preventing the intended `gpg --export` fallback when the keybox is
absent or unreadable; instead, call load_keybox_certs() without the `?` and
match its Result, treating Err (and empty cert list) as an empty certs Vec so
execution continues to find_certs_in_keybox(spec) and then triggers the fallback
when matches.is_empty(); apply the same pattern to the other occurrence (the
later block around the second find_certs_in_keybox call) so both paths fall back
to `gpg --export` rather than returning early.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9779ec24-fc57-4420-8ccc-80b04b7c9764

📥 Commits

Reviewing files that changed from the base of the PR and between bfe5db8 and 71b9cdc.

📒 Files selected for processing (1)
  • crates/gpg/src/keybox.rs

Comment thread crates/gpg/src/keybox.rs Outdated
avivi55 and others added 2 commits April 22, 2026 18:40
Seems like a reasonable change

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@0x77dev 0x77dev self-requested a review May 4, 2026 13:55
@0x77dev
Copy link
Copy Markdown
Owner

0x77dev commented May 4, 2026

@avivi55, thanks for your contribution! I will take a deeper look during the week.

I don't quite like the GPG binary invocation; the whole point of the project is to avoid relying on it, except for challenge-response in air-gapped environments...

In the meantime, I approved CI so people can benefit from the Nix cache for running your branch.

@avivi55
Copy link
Copy Markdown
Author

avivi55 commented May 6, 2026

Yeah I know, but it seems that sequoia doesn't support the modern way of storing the GPG keys.
It is really unfortunate. I tried looking for a crate that is compatible, but I failed to do so.

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