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

  1. Confirm the block header: it must be 'RSA PRIVATE KEY' (PKCS#1) or 'PRIVATE KEY' (PKCS#8), unencrypted.
  2. If it says 'ENCRYPTED PRIVATE KEY', decrypt once: openssl pkcs8 -in enc.pem -out plain.pem -nocrypt (or re-generate unencrypted).
  3. If a certificate was supplied by mistake, point the config at the private key file instead.
  4. 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

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


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