JuliusBrussee/caveman · error
githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w
Error message
githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w
What it means
Thrown by parseRSAPrivateKey (githubapp.go:407): the input was valid PEM, but its payload parsed as neither PKCS#1 RSA (x509.ParsePKCS1PrivateKey failed) nor PKCS#8 (x509.ParsePKCS8PrivateKey failed). The wrapped error is the PKCS#8 failure, which is usually the more informative of the two. So the block decoded fine; its DER content is some other format.
Source
Thrown at shared/platform/githubapp/githubapp.go:407
return resp.StatusCode, nil, fmt.Errorf("githubapp: read response: %w", err)
}
return resp.StatusCode, raw, nil
}
// 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
- Confirm the block header: it must be 'RSA PRIVATE KEY' (PKCS#1) or 'PRIVATE KEY' (PKCS#8), unencrypted.
- If it says 'ENCRYPTED PRIVATE KEY', decrypt once: openssl pkcs8 -in enc.pem -out plain.pem -nocrypt (or re-generate unencrypted).
- If a certificate was supplied by mistake, point the config at the private key file instead.
- Regenerate the key pair and re-upload to the GitHub App if the body is corrupt.
Example fix
# before: encrypted key (BEGIN ENCRYPTED PRIVATE KEY) # after: strip the passphrase openssl pkcs8 -in encrypted.pem -nocrypt -out plain.pem # use plain.pem contents as the private key
Defensive patterns
Strategy: validation
Validate before calling
block, _ := pem.Decode(keyBytes)
if block == nil { return errors.New("not PEM") }
if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {
if _, err8 := x509.ParsePKCS8PrivateKey(block.Bytes); err8 != nil {
return fmt.Errorf("key is neither PKCS#1 nor PKCS#8: %v/%v", err, err8)
}
} Try / catch
if err := parseKey(pemBytes); err != nil {
return fmt.Errorf("github app key rejected (check ENCRYPTED header / cert-vs-key swap): %w", err)
} Prevention
- Reject keys whose PEM header reads 'ENCRYPTED PRIVATE KEY' during provisioning.
- Keep tls.crt and tls.key in clearly named files to prevent swaps.
- Verify keys with openssl pkey -in key.pem -noout before deployment.
When it happens
Trigger: A PEM block whose type is not a private key at all (e.g. -----BEGIN CERTIFICATE----- or -----BEGIN PUBLIC KEY----- passed to the key parser), an encrypted PKCS#8 key (PBES2 envelope the parser will not decrypt), or a corrupted body where headers survive but base64/DER is damaged.
Common situations: Passing the certificate file where the key file belongs (tls.crt vs tls.key swap); a key generated encrypted (openssl genrsa -aes256) whose PEM header says 'ENCRYPTED PRIVATE KEY'; hand-edited base64; a block truncated internally though the END line exists.
Related errors
- githubapp: private key is not valid PEM
- githubapp: private key is not RSA
- %s (%s): certificate %d is unparseable, so the bundle is inc
- %s (%s): trailing PEM block is truncated after %d certificat
- %s (%s) contains no valid PEM certificate
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/30bbf8e40af3287b.
Report an issue: GitHub.