{"record":{"id":"8ed2346afe962ead","repo":"FiloSottile/age","slug":"unexpected-public-key-type","errorCode":null,"errorMessage":"unexpected public key type","messagePattern":"unexpected public key type","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"agessh/agessh.go","lineNumber":61,"sourceCode":"\tsshKey ssh.PublicKey\n\tpubKey *rsa.PublicKey\n}\n\nvar _ age.Recipient = &RSARecipient{}\n\nfunc NewRSARecipient(pk ssh.PublicKey) (*RSARecipient, error) {\n\tif pk.Type() != \"ssh-rsa\" {\n\t\treturn nil, errors.New(\"SSH public key is not an RSA key\")\n\t}\n\tr := &RSARecipient{\n\t\tsshKey: pk,\n\t}\n\n\tif pk, ok := pk.(ssh.CryptoPublicKey); ok {\n\t\tif pk, ok := pk.CryptoPublicKey().(*rsa.PublicKey); ok {\n\t\t\tr.pubKey = pk\n\t\t} else {\n\t\t\treturn nil, errors.New(\"unexpected public key type\")\n\t\t}\n\t} else {\n\t\treturn nil, errors.New(\"pk does not implement ssh.CryptoPublicKey\")\n\t}\n\tif r.pubKey.N.BitLen() < 2048 {\n\t\treturn nil, errors.New(\"RSA key size is too small\")\n\t}\n\treturn r, nil\n}\n\nfunc (r *RSARecipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {\n\tif r.pubKey.N.BitLen() < 2048 {\n\t\treturn nil, errors.New(\"RSA key size is too small\")\n\t}\n\tl := &age.Stanza{\n\t\tType: \"ssh-rsa\",\n\t\tArgs: []string{sshFingerprint(r.sshKey)},\n\t}","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/agessh/agessh.go#L43-L79","documentation":"After confirming the SSH key type is \"ssh-rsa\", NewRSARecipient extracts the underlying crypto key via ssh.CryptoPublicKey.CryptoPublicKey() and requires it to be a *rsa.PublicKey. This error fires when the type assertion to *rsa.PublicKey fails, meaning the key reports ssh-rsa on the wire but its Go crypto representation is not an *rsa.PublicKey. This is effectively unreachable with stock golang.org/x/crypto/ssh parsers and indicates a non-standard ssh.PublicKey implementation.","triggerScenarios":"Passing a custom ssh.PublicKey implementation whose Type() returns \"ssh-rsa\" but whose CryptoPublicKey() returns something other than *rsa.PublicKey (e.g. nil or a wrapper type).","commonSituations":"Custom ssh.PublicKey wrappers/proxies (logging or key-policy middleware) that delegate Type() but not CryptoPublicKey() correctly; keys produced by non-standard parsing code.","solutions":["Ensure CryptoPublicKey() of the custom key returns the underlying *rsa.PublicKey.","If wrapping a key, embed/forward the original key's CryptoPublicKey() result.","Prefer keys parsed by golang.org/x/crypto/ssh.ParsePublicKey or ParseAuthorizedKey, which always return consistent implementations."],"exampleFix":"// before\ntype myKey struct{ ssh.PublicKey } // CryptoPublicKey may not delegate\n// after\ntype myKey struct{ ssh.PublicKey }\nfunc (k myKey) CryptoPublicKey() crypto.PublicKey { return k.PublicKey.CryptoPublicKey() }","handlingStrategy":"type-guard","validationCode":"cpk, ok := pk.(ssh.CryptoPublicKey)\nif !ok {\n    return errors.New(\"key lacks CryptoPublicKey\")\n}\nif _, ok := cpk.CryptoPublicKey().(*rsa.PublicKey); !ok {\n    return errors.New(\"underlying key is not *rsa.PublicKey\")\n}","typeGuard":"func toRSAPublicKey(pk ssh.PublicKey) (*rsa.PublicKey, bool) {\n    cpk, ok := pk.(ssh.CryptoPublicKey)\n    if !ok { return nil, false }\n    rsa, ok := cpk.CryptoPublicKey().(*rsa.PublicKey)\n    return rsa, ok\n}","tryCatchPattern":"rec, err := agessh.NewRSARecipient(pk)\nif err != nil {\n    if strings.Contains(err.Error(), \"unexpected public key type\") {\n        return fmt.Errorf(\"inconsistent key implementation %T\", pk)\n    }\n    return err\n}","preventionTips":["Only pass keys from ssh.ParsePublicKey/ssh.NewPublicKey/ParseAuthorizedKey.","If wrapping ssh.PublicKey, always delegate CryptoPublicKey().","Add a unit test asserting custom key types satisfy the interface contract."],"tags":["ssh","rsa","type-assertion","age"],"backgroundTag":"ssh-key-type-mismatch","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}