{"record":{"id":"c9d48c799d49271d","repo":"JuliusBrussee/caveman","slug":"githubapp-private-key-is-not-rsa","errorCode":null,"errorMessage":"githubapp: private key is not RSA","messagePattern":"githubapp: private key is not RSA","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/githubapp/githubapp.go","lineNumber":411,"sourceCode":"\n// parseRSAPrivateKey accepts a PKCS#1 (\"RSA PRIVATE KEY\") or PKCS#8\n// (\"PRIVATE KEY\") PEM — GitHub Apps download PKCS#1, but Cloud KMS / openssl\n// conversions emit PKCS#8, so we accept both.\nfunc parseRSAPrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {\n\tblock, _ := pem.Decode(pemBytes)\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key is not valid PEM\")\n\t}\n\tif key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {\n\t\treturn key, nil\n\t}\n\tparsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key is neither PKCS#1 nor PKCS#8 RSA: %w\", err)\n\t}\n\tkey, ok := parsed.(*rsa.PrivateKey)\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key is not RSA\")\n\t}\n\treturn key, nil\n}\n\n// snippet trims an error body so we never echo a large/secret-bearing response.\nfunc snippet(b []byte) string {\n\tconst max = 256\n\ts := strings.TrimSpace(string(b))\n\tif len(s) > max {\n\t\treturn s[:max] + \"…\"\n\t}\n\treturn s\n}\n","sourceCodeStart":393,"sourceCodeEnd":425,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/shared/platform/githubapp/githubapp.go#L393-L425","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Generate an RSA key instead: openssl genrsa -out app.pem 2048 (GitHub accepts 2048/4096).","Re-upload the new public key to the GitHub App settings (App settings -> 'Generate a private key' also produces RSA directly).","If policy requires EC keys, that conflicts with GitHub App RS256 signing - use an RSA key dedicated to this app.","Verify: openssl pkey -in app.pem -noout -text should print 'Private-Key: (2048 bit...'."],"exampleFix":"# before\nopenssl genpkey -algorithm ed25519 -out app.pem\n\n# after\nopenssl genrsa -out app.pem 2048\n# then upload/re-download via GitHub App settings","handlingStrategy":"type-guard","validationCode":"if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err != nil {\n    parsed, err8 := x509.ParsePKCS8PrivateKey(block.Bytes)\n    if err8 == nil {\n        if _, ok := parsed.(*rsa.PrivateKey); !ok {\n            return errors.New(\"key is not RSA; GitHub App signing requires RSA\")\n        }\n    }\n}","typeGuard":"func isRSAPrivateKey(b []byte) bool {\n    block, _ := pem.Decode(b)\n    if block == nil { return false }\n    if _, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil { return true }\n    k, err := x509.ParsePKCS8PrivateKey(block.Bytes)\n    return err == nil && strings.Contains(fmt.Sprintf(\"%T\", k), \"rsa\")\n}","tryCatchPattern":"if err := loadAppKey(pem); err != nil {\n    return fmt.Errorf(\"github app key: %w\", err) // regenerate as RSA 2048+ and re-upload\n}","preventionTips":["Standardize app key generation on openssl genrsa 2048.","Add an integration test that signs and verifies a JWT with the provisioned key before deploy.","Document that EC/Ed25519 keys cannot be used for GitHub App RS256."],"tags":["go","github-app","rsa","key-generation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}