JWKS parsing fails entirely when provider includes ES256K (secp256k1 in Telegram OIDC)
Preflight checklist
- I could not find a solution in the existing issues, docs, nor discussions.
- I agree to follow this project's Code of Conduct.
- I have read and am following this repository's Contribution Guidelines.
- I have joined the Ory Community Slack.
- I am signed up to the Ory Security Patch Newsletter.
Ory Network Project
No response
Describe the bug
Kratos fails to verify Telegram OIDC tokens with the following error:
failed to verify signature: fetching keys oidc: failed to decode keys: got Content-Type = application/json, but could not unmarshal as JSON: go-jose/go-jose: unsupported elliptic curve 'secp256k1'
Telegram recently added an ES256K key (crv: secp256k1, kid: oidc-es256k-1) to their JWKS at https://oauth.telegram.org/.well-known/jwks.json. go-jose fails to unmarshal the entire JWK set when it encounters this unsupported curve, even though the token is signed with the RS256 key (kid: oidc-1) which is valid. All Telegram OIDC logins are broken as a result.
Reproducing the bug
- Configure a generic OIDC provider with issuer_url: https://oauth.telegram.org
- Attempt login via Telegram
- Kratos logs the error above when verifying the id_token signature
Relevant log output
{
"error": {
"debug": "",
"message": "The request was malformed or contained invalid parameters",
"reason": "failed to verify signature: fetching keys oidc: failed to decode keys: got Content-Type = application/json, but could not unmarshal as JSON: go-jose/go-jose: unsupported elliptic curve 'secp256k1' {\n \"keys\": [\n {\n \"alg\": \"RS256\",\n \"e\": \"AQAB\",\n \"ext\": true,\n \"key_ops\": [\n \"verify\"\n ],\n \"kty\": \"RSA\",\n \"n\": \"5RneLtsKvVcxdv6gu6gxEQu30Cru5NiMQnY6SNr9ZyZFZ4ya-pfHNuaZXJ6QPG0JSFwoxeOkEO2-eZN_REVPm448PvjjsR1eQdZ5QpEkNxnItFcmxkHH91v5cgf52_EI9BGO-MT6f1vaBSg3uWHFlDxI7J2AYxNvd1_Nf3TkgrrR7gyJFTmEIai5RefGnA0KGNYDlRIGUzrz2F05n6gTaHFT_iHL5UHatTZA4GCiUSjIOuwqu5pE5uZge20TFv3cxXMQaFw_xv1pgQt_Rq8eoCN7TS0RQ0zjWKiad-W286BcFectXsUm03p5Nq_kY4mf_7rqwX_B8yy_bBreyKn7RQ\",\n \"kid\": \"oidc-1\"\n },\n {\n \"alg\": \"ES256\",\n \"kty\": \"EC\",\n \"x\": \"ahVYrohhX6YA7w0P2gUNSwMFbaabCgBZFkeq9bWdmwU\",\n \"y\": \"Ea8nKJ34VQMA7zv8aYDfzcBhXEjnWQ9C06jVke_eUV0\",\n \"crv\": \"P-256\",\n \"kid\": \"oidc-es256-1\",\n \"use\": \"sig\"\n },\n {\n \"alg\": \"EdDSA\",\n \"crv\": \"Ed25519\",\n \"x\": \"i6BEafXMEe4osXgUTffpKAm6Cn6F2bhqPZoclunTAV4\",\n \"kty\": \"OKP\",\n \"kid\": \"oidc-eddsa-1\",\n \"use\": \"sig\"\n },\n {\n \"alg\": \"ES256K\",\n \"kty\": \"EC\",\n \"x\": \"vsk5i5YJu8H_VPL7DWTgVGXBPrqgkyNmYvfgOrVut38\",\n \"y\": \"Mrg56tBhVeorHPXK1LbTX2jP7rEqOHIatM96HFzVMIU\",\n \"crv\": \"secp256k1\",\n \"kid\": \"oidc-es256k-1\",\n \"use\": \"sig\"\n }\n ]\n}",
"stack_trace": "\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*ProviderGenericOIDC).verifyAndDecodeClaimsWithProvider\n\t/project/selfservice/strategy/oidc/provider_generic_oidc.go:103\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*ProviderGenericOIDC).claimsFromIDToken\n\t/project/selfservice/strategy/oidc/provider_generic_oidc.go:190\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*ProviderGenericOIDC).Claims\n\t/project/selfservice/strategy/oidc/provider_generic_oidc.go:123\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).HandleCallback\n\t/project/selfservice/strategy/oidc/strategy.go:506\ngithub.com/ory/kratos/selfservice/strategy.disabledWriter\n\t/project/selfservice/strategy/handler.go:26\ngithub.com/ory/kratos/selfservice/strategy/oidc.(*Strategy).RegisterPublicRoutes.IsDisabled.func1\n\t/project/selfservice/strategy/handler.go:31\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerResponseSize.func1\n\t/go/pkg/mod/github.com/prometheus/[email protected]/prometheus/promhttp/instrument_server.go:296\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerCounter.func1\n\t/go/pkg/mod/github.com/prometheus/[email protected]/prometheus/promhttp/instrument_server.go:147\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func1\n\t/go/pkg/mod/github.com/prometheus/[email protected]/prometheus/promhttp/instrument_server.go:97\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerDuration.func2\n\t/go/pkg/mod/github.com/prometheus/[email protected]/prometheus/promhttp/instrument_server.go:109\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/prometheus/client_golang/prometheus/promhttp.InstrumentHandlerRequestSize.func1\n\t/go/pkg/mod/github.com/prometheus/[email protected]/prometheus/promhttp/instrument_server.go:243\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/ory/x/prometheusx.(*HTTPMetrics).Instrument.(*HTTPMetrics).instrumentHandlerStatusBucket.func1\n\t/project/oryx/prometheusx/metrics.go:112\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\nnet/http.(*ServeMux).ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2828\ngithub.com/ory/x/httprouterx.(*router).ServeHTTP\n\t/project/oryx/httprouterx/router.go:113\ngithub.com/ory/kratos/cmd/daemon.servePublic.SpanNameRecorderMiddleware.func4\n\t/project/oryx/otelx/middleware.go:37\nnet/http.HandlerFunc.ServeHTTP\n\t/usr/local/go/src/net/http/server.go:2286\ngithub.com/ory/nosurf.(*CSRFHandler).handleSuccess\n\t/go/pkg/mod/github.com/ory/[email protected]/handler.go:234\ngithub.com/ory/nosurf.(*CSRFHandler).ServeHTTP\n\t/go/pkg/mod/github.com/ory/[email protected]/handler.go:185\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\ngithub.com/ory/kratos/cmd/daemon.servePublic.func1\n\t/project/cmd/daemon/serve.go:83\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/[email protected]/negroni.go:38\ngithub.com/ory/x/metricsx.(*Service).ServeHTTP\n\t/project/oryx/metricsx/middleware.go:281",
"status": "Bad Request",
"status_code": 400
},
}Relevant configuration
oidc:
config:
- id: telegram
label: "Telegram"
provider: generic
client_id: xxxxx
client_secret: xxxxxxxxxxxx
issuer_url: https://oauth.telegram.org
pkce: "never"
mapper_url: xxxxxxxxx
scope:
- openid
- profileVersion
v26.2.0
On which operating system are you observing this issue?
Linux
In which environment are you deploying?
Kubernetes with Helm
Additional Context
This is a regression. No Kratos or provider config was changed on our side. Telegram added the ES256K key to support TON blockchain wallet authentication, which is unrelated to standard OIDC flows.
The fix likely belongs in go-jose (graceful skip of unknown curve keys) or in Kratos as a pre-filter step before passing the JWK set to go-jose. Worth checking whether upgrading to new version of go-jose already resolves this behavior.
Affected JWKS entry: {"alg":"ES256K","kty":"EC","crv":"secp256k1","kid":"oidc-es256k-1","use":"sig"}
Source: ory/kratos