JuliusBrussee/caveman · error

githubapp: private key is not RSA

Error message

githubapp: private key is not RSA

What it means

Thrown by parseRSAPrivateKey (githubapp.go:411): the PEM payload parsed successfully as PKCS#8, but the resulting key is not an *rsa.PrivateKey - it is an EC, Ed25519, or other algorithmic type. GitHub App JWT signing uses RS256, so only RSA keys are accepted; the type assertion parsed.(*rsa.PrivateKey) fails and this error returns.

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 27d5a3981a)

Solutions

  1. Generate an RSA key instead: openssl genrsa -out app.pem 2048 (GitHub accepts 2048/4096).
  2. Re-upload the new public key to the GitHub App settings (App settings -> 'Generate a private key' also produces RSA directly).
  3. If policy requires EC keys, that conflicts with GitHub App RS256 signing - use an RSA key dedicated to this app.
  4. Verify: openssl pkey -in app.pem -noout -text should print 'Private-Key: (2048 bit...'.

Example fix

# before
openssl genpkey -algorithm ed25519 -out app.pem

# after
openssl genrsa -out app.pem 2048
# then upload/re-download via GitHub App settings
Defensive patterns

Strategy: type-guard

Validate before calling

if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
    parsed, err8 := x509.ParsePKCS8PrivateKey(block.Bytes)
    if err8 == nil {
        if _, ok := parsed.(*rsa.PrivateKey); !ok {
            return errors.New("key is not RSA; GitHub App signing requires RSA")
        }
    }
}

Type guard

func isRSAPrivateKey(b []byte) bool {
    block, _ := pem.Decode(b)
    if block == nil { return false }
    if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { return true }
    k, err := x509.ParsePKCS8PrivateKey(block.Bytes)
    return err == nil && strings.Contains(fmt.Sprintf("%T", k), "rsa")
}

Try / catch

if err := loadAppKey(pem); err != nil {
    return fmt.Errorf("github app key: %w", err) // regenerate as RSA 2048+ and re-upload
}

Prevention

When it happens

Trigger: The private key was generated as EC (openssl ecparam -name prime256v1 ...) or Ed25519 (openssl genpkey -algorithm ed25519), stored as PKCS#8 -----BEGIN PRIVATE KEY-----, and passed to the GitHub App client. Parse succeeds; the RSA type check does not.

Common situations: A security team mandated EC keys org-wide; a KMS export defaulted to EC; openssl command copied from a modern tutorial that generates Ed25519; the same key reused for two integrations where the other one accepted EC.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/c9d48c799d49271d. Report an issue: GitHub.