Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JWTs signed with ECDSA (ES256, etc.) #15181

Open
micolous opened this issue Oct 10, 2024 · 7 comments
Open

Support JWTs signed with ECDSA (ES256, etc.) #15181

micolous opened this issue Oct 10, 2024 · 7 comments
Labels
feature-request Issue which suggest an idea, enhancement or feature to implement

Comments

@micolous
Copy link

What problem are you trying to solve?

I'm trying to authenticate Jitsi users with Kanidm.

It looks like jitsi-keycloak-adapter would bridge the gap between Kanidm providing a JWT with OIDC, and then Jitsi authenticating the user with a JWT in a query parameter.

It looks like the actual verification of the token is done by Jitsi Meet.

Its utility functions support HS256, HS384, HS512, RS256, RS384 and RS512 algorithms, with a Prosody plugin:

local alg_sign = {
['HS256'] = function(data, key) return hmac.new(key, 'sha256'):final(data) end,
['HS384'] = function(data, key) return hmac.new(key, 'sha384'):final(data) end,
['HS512'] = function(data, key) return hmac.new(key, 'sha512'):final(data) end,
['RS256'] = function(data, key) return signRS(data, key, 'sha256') end,
['RS384'] = function(data, key) return signRS(data, key, 'sha384') end,
['RS512'] = function(data, key) return signRS(data, key, 'sha512') end
}
local alg_verify = {
['HS256'] = function(data, signature, key) return signature == alg_sign['HS256'](data, key) end,
['HS384'] = function(data, signature, key) return signature == alg_sign['HS384'](data, key) end,
['HS512'] = function(data, signature, key) return signature == alg_sign['HS512'](data, key) end,
['RS256'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha256') end,
['RS384'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha384') end,
['RS512'] = function(data, signature, key) return verifyRS(data, signature, key, 'sha512') end
}

The plugin can also fetch a public key from a keyserver, but this only supports RSA:

if alg.sub(alg,1,2) ~= "RS" then
return false, "not-allowed", "'kid' claim only support with RS family";
end

However, Kanidm's default OAuth2 configuration provides a JWT signed with ES256, which Jitsi Meet does not support.

What solution would you like to see?

Jitsi Meet should be able to verify a JWT signed with ECDSA/ES256, in addition to existing algorithms.

Support for ES256 is recommended "plus" in RFC 7518 section 3.1, where "plus" indicates that the requirement strength is likely to be increased in a future version of the specification.

RFC 7518 was published in 2015, so the recommendation is more than a little dated. 😄

Is there an alternative?

Kanidm can be configured to support RSA, but it considers it to be a legacy algorithm because RSA requires significantly longer keys to provide equivalent security to ECDSA.

@micolous micolous added the feature-request Issue which suggest an idea, enhancement or feature to implement label Oct 10, 2024
@damencho
Copy link
Member

Does the Kanadim provides a jwt with the fields and values required by jitsi-meet?
I would guess that there are differences, so your best bet is to create page where authenticated with one token you can generate the other one, I guess like the adapter you mentioned.
Other than that, any PRs for new algorithms are welcome.

@micolous
Copy link
Author

Does the Kanadim provides a jwt with the fields and values required by jitsi-meet?

Kanidm's OAuth 2.0 access tokens are RFC 9068 JWTs – so any service can introspect the tokens it issues directly, and check their validity against its certificates (distributed with JWKS).

But I haven't tested that far yet. 😄

I would guess that there are differences, so your best bet is to create page where authenticated with one token you can generate the other one, I guess like the adapter you mentioned.

On further inspection, it looks like jitsi-keycloak-adapter treats the OIDC authorisation token as an opaque value to fetch user info (ie: not RFC 9068) and then extracts some values and re-signs the JWTs itself with its own key material.

I suspect that in an ideal world, if an OIDC IdP provides RFC 9068 JWTs and there is some way to provide the IdP's public keys to Jitsi, an "adapter" for such an IdP shouldn't need to do much crypto or hold (m)any secrets at all – just complete a PKCE challenge (generate a random value and stash it somewhere, and SHA256 the value for the authorisation code request).

There's be nothing precluding that kind of flow from happening with either RSA or ECDSA – but it would mean that both the IdP and Jitsi would need to support the same algorithms.

@damencho
Copy link
Member

Kanidm's OAuth 2.0 access tokens are RFC 9068 JWTs – so any service can introspect the tokens it issues directly, and check their validity against its certificates (distributed with JWKS).

But I haven't tested that far yet. 😄

Yep, but we require some special values, the token produced needs to look like this:
https://github.com/jitsi/lib-jitsi-meet/blob/master/doc/tokens.md#payload

@micolous
Copy link
Author

micolous commented Oct 10, 2024

Ah, right... so the problem is more difficult then.

While this is getting pretty far from the original request for ECDSA, I think Jitsi is using that in a non-standard/incorrect way too, comparing Jitsi token structure with RFC 7519:

  • iss: section 4.1.1 says this identifies the principal that issued the token. This would suggest it should contain something like auth.example.com if that is the domain handing out the JWTs.

    Jitsi uses this as the application ID of the client.

  • sub: section 4.1.2 says that this identifies the principal that is the subject of the JWT (ie: the caller), and that it is scoped to be unique within the context of the issuer or globally unique.

    Jitsi uses this to identify the scope of the token: either the tenant, the domain of the MUC, or * for anything.

    This usage actually seems similar to the standard aud (Audience) claim, defined in section 4.1.3 – and could be combined with the Jitsi's room claim.

    A valid use case for sub would be something like an opaque unique identifier for a user, which Jitsi expects to be in context.user.id.

While RFC 7519 allows for arbitrary other claims, Jitsi's context claim seems to significantly overlap with OIDC Core 1.0's standard claims.

As a side note, allowing * to scope tokens is a significant security risk. If an adapter provides valid JWTs with * (eg: nordeck/jitsi-keycloak-adapter#21, d3473r/jitsi-keycloak#103, Renater/Jitsi-SAML2JWT#7), then the JWT can be used on any Jitsi instance with the same JWT configuration (eg: Jitsi instances with different security settings connected to the same IdP).

While I see there was a goal was to remove a lot of authentication logic from Jitsi's core, the way it uses JWTs is incompatible with everything, and that's leading to insecure adapters.

I'd much rather put this through an IdP which can implement standard OAuth 2.0 / OIDC flows securely, and have Jitsi consume them directly.

@micolous
Copy link
Author

Ah, I see there's a token_no_wildcard contrib module which disables wildcard functionality. 🎉

@aaronkvanmeerten
Copy link
Member

This is an excellent and in-depth analysis of the circumstances, thank you very much for laying all this out. I am primarily responsible for the situation as it is today, due to poorly understanding the RFC combined with my intention to solve an internal problem for the team without properly considering possible external uses. I have been considering ways to improve the situation to be more compatible with external identity providers, but haven't completed any full thoughts on the matter.

All of this discussion probably deserves a topic on community.jitsi.org, so I suggest we move it there.

@micolous
Copy link
Author

No worries, I recognise this is a difficult problem space with complex requirements, and that cipher support is just one small step towards achieving my goal.

I'm still researching things to get a better understanding where everything is at, so I'll follow up there when I'm ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request Issue which suggest an idea, enhancement or feature to implement
Projects
None yet
Development

No branches or pull requests

3 participants