{"record":{"id":"743596018164a98c","repo":"router-for-me/CLIProxyAPI","slug":"client-key-is-not-rsa","errorCode":null,"errorMessage":"client key is not rsa","messagePattern":"client key is not rsa","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/home/certificate.go","lineNumber":276,"sourceCode":"\treturn nil\n}\n\nfunc parseRSAPrivateKeyPEM(raw []byte) (*rsa.PrivateKey, error) {\n\tblock, _ := pem.Decode(raw)\n\tif block == nil {\n\t\treturn nil, fmt.Errorf(\"client key pem is invalid\")\n\t}\n\tswitch block.Type {\n\tcase \"RSA PRIVATE KEY\":\n\t\treturn x509.ParsePKCS1PrivateKey(block.Bytes)\n\tcase \"PRIVATE KEY\":\n\t\tkey, errParse := x509.ParsePKCS8PrivateKey(block.Bytes)\n\t\tif errParse != nil {\n\t\t\treturn nil, errParse\n\t\t}\n\t\trsaKey, ok := key.(*rsa.PrivateKey)\n\t\tif !ok {\n\t\t\treturn nil, fmt.Errorf(\"client key is not rsa\")\n\t\t}\n\t\treturn rsaKey, nil\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"client key pem type %q is unsupported\", block.Type)\n\t}\n}\n\nfunc createClientCSR(certificateID string, key *rsa.PrivateKey) ([]byte, error) {\n\tcertificateID = strings.TrimSpace(certificateID)\n\tif certificateID == \"\" {\n\t\treturn nil, fmt.Errorf(\"certificate id is required\")\n\t}\n\ttemplate := &x509.CertificateRequest{\n\t\tSubject: pkix.Name{\n\t\t\tCommonName: certificateID,\n\t\t},\n\t}\n\tder, errCreate := x509.CreateCertificateRequest(rand.Reader, template, key)","sourceCodeStart":258,"sourceCodeEnd":294,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/home/certificate.go#L258-L294","documentation":"Returned by parseRSAPrivateKeyPEM in internal/home/certificate.go when a PKCS#8 'PRIVATE KEY' PEM parses successfully but the underlying key is not an RSA key (e.g. ECDSA or Ed25519). The home mTLS flow builds an RSA-based CSR (createClientCSR takes *rsa.PrivateKey), so non-RSA keys are rejected.","triggerScenarios":"Generating the client key with openssl ecparam -name ... or openssl genkey ed25519 or openssl genpkey -algorithm EC and pointing client-key at it; using a modern default-key-type tool that emits Ed25519.","commonSituations":"Operator followed a generic TLS tutorial that recommends EC keys; ssh-keygen output reused as TLS key; cert tooling upgraded to emit EC by default.","solutions":["Generate an RSA key: openssl genrsa -out client.key 2048 (or 3072/4096)","Re-run the enrollment flow with the new RSA key so the CSR and issued cert match","If you must keep EC keys, that is unsupported by this code path — switch to RSA"],"exampleFix":"# before\nopenssl genpkey -algorithm Ed25519 -out client.key\n\n# after\nopenssl genrsa -out client.key 2048","handlingStrategy":"validation","validationCode":"// after parsing, or preflight via openssl; in Go preflight the PEM type:\nblock, _ := pem.Decode(raw)\nif block == nil || (block.Type != \"RSA PRIVATE KEY\" && block.Type != \"PRIVATE KEY\") {\n    return errors.New(\"client key must be RSA PEM (PKCS1 or PKCS8)\")\n}\n// note: PKCS8 non-RSA still fails at parse; generate with `openssl genrsa`","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Document `openssl genrsa -out client.key 2048` as the only supported key generation command","Add a preflight step that runs `openssl rsa -in client.key -noout` during provisioning"],"tags":["go","tls","private-key","rsa","home"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}