Skip to main content
Private preview. fremforge is in private preview — invited customers only. Content is still subject to change. Request access →
Keyless commit signing with gitsign

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 signingKeyless (sigstore)
Identity binding“Matches an SSH key on file”“Signed by you@org.com attested by Microsoft Entra”
Key lifetimePermanent (rotation = manual)10-minute cert, refreshed via OIDC
Lost laptop = lost keyYou re-add the new key everywhereYou log in to your IdP — done
MFA on signingWhatever you set on the keyWhatever your IdP requires (typically MFA every refresh)
Auditable in 3rd-party CIHard (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 --version

2. 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-url

Replace 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: write permission
  • Microsoft Entra workload identityaz 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 OIDCIssuer extension 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

  1. gitsign opens your IdP’s OIDC sign-in flow.
  2. Your IdP issues a short-lived ID token attesting your identity.
  3. gitsign sends the token to fremforge’s Fulcio CA (sign.frem.sh).
  4. Fulcio validates the token, mints a 10-minute X.509 cert with your email in the SAN + IdP issuer URL in the OIDCIssuer extension. Signing key lives in a T Cloud DEW HSM (FIPS 140-2 Level 3, German jurisdiction).
  5. gitsign signs the commit body with the cert’s private key (held only in your local browser memory for the cert’s lifetime).
  6. 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.
  7. The signature, cert, and timestamp are embedded in the commit’s gpgsig header (CMS/PKCS7 format).
  8. 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.
  9. 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.