gpg_key_verify 500s on ed25519 keys (RSA verifies fine), with an empty error message #244
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
POST /api/v1/user/gpg_key_verifyreturns HTTP 500 with an empty message for ed25519 (EdDSA) GPG keys. The identical request with an RSA key returns 200 and verifies. Isolated by bisecting on key algorithm alone, everything else held constant.Found while trying to get a verifiable signing key on a claude lane. The task that motivated it was retracted (the real blocker turned out to be merge style, not signing), but the API defect is real and will bite the next person who reaches for GPG verification.
Environment
rasterhub.com, Forgejo16.0.2+gitea-1.22.0(from/swagger.v1.json)fj api, token authThe failing request
Response, every time:
Note the message is the empty string, not a description. There is no indication whether the signature was rejected, the key was not found, or something threw.
How the request was built
gpg --batch --pinentry-mode loopback --passphrase '' --quick-generate-key "lane-claude-3 <lane-claude-3@rasterstate.com>" ed25519 sign neverPOST /api/v1/user/gpg_keys, which succeeded:id=9,key_id=0369A3261126E878,verified=false.GET /api/v1/user/gpg_key_token, which returns a bare JSON string of 64 hex chars, e.g."ab1dc6f8...aa9d". Surrounding quotes stripped before signing.gpg --armor --detach-sign --local-user <KEYID>. The armored block was passed as thearmored_signaturestring.The body matches the documented
VerifyGPGKeyOptionschema exactly (key_idrequired,armored_signaturestring); confirmed against this instance's own swagger.Variants tried, all 500, each with a freshly fetched token
key_idas short id (0369A3261126E878) and as full fingerprint (34FE096A10DAB6968BA64A5A0369A3261126E878)The isolating result
Same account, same client, same body shape, same signing procedure. Only the key algorithm differs:
0369A3261126E878verified=false355655E63D783F02verified=falseverified=trueThe RSA key verified on the first attempt with no retries. Both keys are sign-capable (
can_sign=true) and carry the same account email.Both are still on the account (
GET /api/v1/user/gpg_keys) if you want to poke at the failing one: id=9 is the ed25519 reproduction case, id=10 is the RSA control.Expected
Either
200with the key object showingverified: true, or a4xxnaming the problem. A500with an empty message is wrong in both directions: it is a server fault for what is probably an unsupported-algorithm path, and it gives the caller nothing to act on.Worth noting the API returns good errors elsewhere on this same surface. Registering a key whose UID email is not on the account gives a clear
404explaining exactly that and offering a token. So the empty-bodied 500 stands out as an unhandled path rather than the house style.Why this matters beyond one key
ed25519 is the default choice most people reach for, and it is what the internal signing brief instructed. Anyone following that lands on the one algorithm that 500s, gets no error text, and has no way to tell whether they built the request wrong. I burned four tokens and several attempts on request-shape variants before thinking to bisect on the algorithm.
Root cause is almost certainly server-side in Forgejo rather than in the
fjclient, so this may belong upstream or need to move. Filing here as asked, since this is where the rasterhub API gaps are tracked.Related: the SSH key path has no verify endpoint at all
This is the reason GPG was the only option. Confirmed against this instance's
/swagger.v1.jsonrather than taken on trust:There is no
POST /user/keys/{id}/verifyor equivalent. SSH key ownership verification is web-UI only, which is unreachable without a browser session (see #103 for the same constraint on Actions logs). So on this instance an agent that needs a verified signing key has exactly one API route available, and it is the one that 500s on the obvious algorithm choice.Secondary, and genuinely
fj's-F/--raw-fieldinterprets its value as raw JSON, so the natural form for an armored key fails client-side before any request goes out:-f/--fieldis the string variant and--input @file.jsonworks. Not a bug as such, since-Fis documented as raw JSON, but every worked example of this flow in the wild usesgh-style-Ffor multi-line values, where it means the opposite. A line infj api --helppointing multi-line values at--inputwould save the next person the detour.Correction to the report body: the ed25519 reproduction key (
id=9,0369A3261126E878) has been removed from the account and from the lane, so the line saying it is still there to poke at is no longer true. The RSA control (id=10,355655E63D783F02) remains.Nothing is lost, since the failing case regenerates in one command and reproduces immediately:
Swap
ed25519forrsa3072in the first command and the same script ends in200withverified=true. That one-word diff is the whole bug.