JuliusBrussee/caveman · critical

githubapp: private key is not RSA

Error message

githubapp: private key is not RSA

What it means

Raised by parseRSAPrivateKey when the supplied PEM block decodes successfully but its DER payload is neither a PKCS#1 RSA key nor a PKCS#8 key. It fires after both x509.ParsePKCS1PrivateKey and x509.ParsePKCS8PrivateKey fail on block.Bytes, meaning the input is a well-formed PEM envelope around a non-RSA payload: typically an EC or Ed25519 key, a corrupted/truncated key body, or a certificate mistakenly passed where the GitHub App private key was expected. The input at fault is the private key bytes given to New (from the app's downloaded .pem or its KMS/openssl-converted equivalent), which must contain an RSA private key for GitHub App JWT signing.

Solutions

  1. Download the GitHub App's .pem from GitHub App settings — it is always RSA.
  2. If generating your own key, use openssl genrsa 2048 and verify with openssl rsa -in key.pem -check.
  3. Inspect the key header: 'BEGIN EC PRIVATE KEY' or EC-content PKCS#8 must be replaced by an RSA key.

Example fix

// before
$ openssl genpkey -algorithm ED25519 -out app.key  # non-RSA key
// after
$ openssl genrsa -out app.private-key.pem 2048     # RSA key for GitHub App
Defensive patterns

Strategy: validation

Validate before calling

func isRSAKey(s string) bool {
	block, _ := pem.Decode([]byte(s))
	if block == nil {
		return false
	}
	if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
		return true
	}
	parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return false
	}
	_, ok := parsed.(*rsa.PrivateKey)
	return ok
}

Type guard

func isRSAKey(s string) bool {
	block, _ := pem.Decode([]byte(s))
	if block == nil {
		return false
	}
	parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return false
	}
	_, ok := parsed.(*rsa.PrivateKey)
	return ok
}

Try / catch

client, err := githubapp.New(pem)
if err != nil {
	if strings.Contains(err.Error(), "is not RSA") {
		return errors.New("GitHub App keys must be RSA; re-download the .pem from app settings")
	}
	return err
}

Prevention

When it happens

Trigger: New() called with a PKCS#8 PEM containing an EC, Ed25519, or otherwise non-RSA private key.

Common situations: Generating a key with openssl genpkey -algorithm EC or ssh-keygen -t ed25519 and using it as a GitHub App key instead of the RSA key downloaded from GitHub; swapping in an unrelated non-RSA key from another service.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/c9d48c799d49271d. Report an issue: GitHub.

Appendix: source

Thrown at shared/platform/githubapp/githubapp.go:411

// parseRSAPrivateKey accepts a PKCS#1 ("RSA PRIVATE KEY") or PKCS#8
// ("PRIVATE KEY") PEM — GitHub Apps download PKCS#1, but Cloud KMS / openssl
// conversions emit PKCS#8, so we accept both.
func parseRSAPrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
	block, _ := pem.Decode(pemBytes)
	if block == nil {
		return nil, fmt.Errorf("githubapp: private key is not valid PEM")
	}
	if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
		return key, nil
	}
	parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w", err)
	}
	key, ok := parsed.(*rsa.PrivateKey)
	if !ok {
		return nil, fmt.Errorf("githubapp: private key is not RSA")
	}
	return key, nil
}

// snippet trims an error body so we never echo a large/secret-bearing response.
func snippet(b []byte) string {
	const max = 256
	s := strings.TrimSpace(string(b))
	if len(s) > max {
		return s[:max] + "…"
	}
	return s
}

View on GitHub (pinned to 3ee70a1026)