Keyless commit signing with gitsign
Sign Git commits with your IdP identity, not a long-lived SSH key. fremforge’s keyless commit signing uses Sigstore primitives — but every component runs on T Cloud eu-de, the root CA lives in a FIPS 140-2 Level 3 HSM in Germany, and your signatures are bound to whatever IdP your fremforge org already trusts (Microsoft Entra, Okta, Google Workspace, Authentik, anything that speaks OIDC).
Every signed commit shows up in Forgejo as Verified by sigstore: <your-email> via <your-IdP>. No keyserver. No expiry surprises. No “did I import that key on the new laptop?”.
Why bother (vs. SSH signing)
| SSH-key signing | Keyless (sigstore) | |
|---|---|---|
| Identity binding | “Matches an SSH key on file” | “Signed by you@org.com attested by Microsoft Entra” |
| Key lifetime | Permanent (rotation = manual) | 10-minute cert, refreshed via OIDC |
| Lost laptop = lost key | You re-add the new key everywhere | You log in to your IdP — done |
| MFA on signing | Whatever you set on the key | Whatever your IdP requires (typically MFA every refresh) |
| Auditable in 3rd-party CI | Hard (custom Forgejo API call) | cosign verify works against fremforge’s CA |
Both signing methods coexist on fremforge — your tenant admin chooses the strictness level (see Org policies below). New developers should default to gitsign; existing SSH-signing setups keep working.
Setup (one-time)
1. Install gitsign
# macOS (Homebrew)
brew install sigstore/tap/gitsign
# Linux (binary)
curl -sLO https://github.com/sigstore/gitsign/releases/latest/download/gitsign-linux-amd64
chmod +x gitsign-linux-amd64 && sudo mv gitsign-linux-amd64 /usr/local/bin/gitsign
# Verify
gitsign --version2. Configure git
git config --global gpg.x509.program gitsign
git config --global gpg.format x509
git config --global commit.gpgsign true
# Point gitsign at fremforge's private Fulcio + TSA (not the public Sigstore).
git config --global gitsign.fulcio https://sign.frem.sh
git config --global gitsign.timestamp-server-url https://tsa.frem.sh
git config --global gitsign.rekor "" # explicitly empty — fremforge uses TSA-only, no transparency log
git config --global gitsign.connectorID https://your-idp-issuer-urlReplace https://your-idp-issuer-url with the OIDC issuer URL your org configured (see the Org policies → Allowed OIDC issuers section of your fremforge admin tab, or ask your org admin).
3. Sign your first commit
cd your-repo
git commit -S -m "First signed commit"A browser tab opens, you authenticate to your IdP (one tap if you’re already signed in), and the commit is signed. The browser cache holds the cert for ~10 minutes so subsequent commits in the same session don’t re-prompt.
4. Verify it worked
git verify-commit HEAD
# tlog index: <n>
# Good signature from [you@yourorg.com](you@yourorg.com)
# Validated Git signature: true
# Validated Rekor entry: true (or "skipped" — fremforge doesn't run Rekor)On fremforge’s web UI, your commit shows the Verified by sigstore badge next to the commit hash.
CI / headless signing
If you’re signing from CI or a machine without a browser:
# Get an OIDC token from your IdP (different per IdP — see below)
export SIGSTORE_ID_TOKEN="<bearer-token-from-your-idp>"
# Then sign as usual
git commit -S -m "Signed from CI"Per-IdP token-fetching recipes:
- GitHub Actions → built-in
id-token: writepermission - Microsoft Entra workload identity →
az account get-access-token --resource <fulcio-audience> - Authentik → device-code OAuth flow via
gitsign’s headless mode - Anything else → any OIDC-conformant token works as long as the issuer is in your org’s allowlist
Org policies
Two settings on /<your-org>/_admin/auth-policy control how keyless signing behaves:
- Allowed OIDC issuer URLs — must include the issuer URL of your IdP. The pre-receive hook reads the cert’s
OIDCIssuerextension and rejects any commit whose issuer isn’t in this list. Empty list = sigstore signing disabled for this org. - Strict mode — when on, only sigstore-signed commits are accepted on branches with Require signed commits enabled. SSH-signed commits get rejected. Don’t flip this until all your developers have migrated.
Troubleshooting
gitsign: oidc issuer not allowed by Fulcio: your IdP’s issuer URL is correct, but the platform Fulcio doesn’t trust it yet. Tenant admin: open /<slug>/_admin/auth-policy and add the issuer URL to Allowed OIDC issuer URLs. fremforge reconciles Fulcio’s config inline + restarts the relevant pod automatically (~30 seconds). No operator ticket needed.
gitsign: cert expired before push completed: you ran a long-running script between cert mint and push. The cert is 10-minute. Run git commit --amend --no-edit -S to re-sign with a fresh cert before pushing.
Verified badge missing from a signed commit: check /<your-org>/<repo>/commit/<sha>/status — the fremforge-sigstore-verified status appears as a check next to other CI checks. If it’s “failure”, click for the reason. If it’s missing entirely, the pre-receive hook didn’t fire — either the commit is too old (pushed before policy was enabled) or your push went through an unexpected path. Contact support with the commit SHA.
Forgejo’s “Verified” badge says “unsigned” but git verify-commit works: Forgejo’s native verifier doesn’t yet understand sigstore signatures — that’s why we use the commit-status badge instead. The fremforge-sigstore-verified status is the source of truth. (Tracking gitea#34361 for upstream support.)
How it works under the hood
gitsignopens your IdP’s OIDC sign-in flow.- Your IdP issues a short-lived ID token attesting your identity.
gitsignsends the token to fremforge’s Fulcio CA (sign.frem.sh).- Fulcio validates the token, mints a 10-minute X.509 cert with your email in the SAN + IdP issuer URL in the
OIDCIssuerextension. Signing key lives in a T Cloud DEW HSM (FIPS 140-2 Level 3, German jurisdiction). gitsignsigns the commit body with the cert’s private key (held only in your local browser memory for the cert’s lifetime).- fremforge’s TSA (
tsa.frem.sh) signs an RFC 3161 trusted timestamp that binds “cert was valid at this exact second” — this is what makes verification work even after the cert expires. - The signature, cert, and timestamp are embedded in the commit’s
gpgsigheader (CMS/PKCS7 format). - You push. fremforge’s pre-receive hook verifies the cert chain → our Fulcio root, the timestamp → our TSA root, and the OIDC issuer against your org’s allowed list.
- Forgejo’s commit-status API records the result.
Verification needs only the Fulcio root cert + TSA root — no network calls during verify. CA downtime doesn’t affect already-signed commits, only new signing attempts.
Independently verifying outside fremforge
If you want a third-party CI (or just your laptop) to verify fremforge-signed commits without trusting fremforge:
# Fetch fremforge's CA root + TSA root (publicly served, integrity via TLS)
curl -sf https://docs.frem.sh/_pki/fulcio-root.pem > /tmp/fremforge-fulcio.pem
curl -sf https://docs.frem.sh/_pki/tsa-root.pem > /tmp/fremforge-tsa.pem
# Use cosign to verify a specific commit
cosign verify-blob \
--certificate-chain /tmp/fremforge-fulcio.pem \
--timestamp-certificate-chain /tmp/fremforge-tsa.pem \
--certificate-identity "you@yourorg.com" \
--certificate-oidc-issuer "https://your-idp-issuer" \
<commit-bytes>Same signature format as any other Sigstore-ecosystem signature — works with cosign, slsa-verifier, and the rest of the OpenSSF supply-chain tooling.