Skip to content

JWKS parsing fails entirely when provider includes ES256K (secp256k1 in Telegram OIDC) #4572

Description

@SKozak

Preflight checklist

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

  1. Configure a generic OIDC provider with issuer_url: https://oauth.telegram.org
  2. Attempt login via Telegram
  3. 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/client_golang@v1.23.0/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/client_golang@v1.23.0/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/client_golang@v1.23.0/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/client_golang@v1.23.0/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/client_golang@v1.23.0/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/nosurf@v1.2.7/handler.go:234\ngithub.com/ory/nosurf.(*CSRFHandler).ServeHTTP\n\t/go/pkg/mod/github.com/ory/nosurf@v1.2.7/handler.go:185\ngithub.com/urfave/negroni.(*Negroni).UseHandler.Wrap.func1\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:46\ngithub.com/urfave/negroni.HandlerFunc.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/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/negroni@v1.0.0/negroni.go:29\ngithub.com/urfave/negroni.middleware.ServeHTTP\n\t/go/pkg/mod/github.com/urfave/negroni@v1.0.0/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
      - profile

Version

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"}

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is not working.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions