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 for PKCE #58

Open
emirror-de opened this issue Oct 25, 2024 · 4 comments
Open

Support for PKCE #58

emirror-de opened this issue Oct 25, 2024 · 4 comments
Labels
enhancement New feature or request

Comments

@emirror-de
Copy link

I am trying to use this library in combination with kanidm, but it requires PKCE.

Is support for PKCE planned in the near future? I would be happy to help implementing if desired.

Additional info: https://www.oauth.com/oauth2-servers/pkce/

@jebrosen jebrosen added the enhancement New feature or request label Oct 26, 2024
@jebrosen
Copy link
Owner

So a while back (probably months or years?) I had actually started investigating PKCE and concluded that it was usually not relevant for servers using rocket_oauth2: PKCE primarily protects against a malicious application that is able to observe ("intercept") responses from and requests to the authorization server (including the authorization code). In the context of a desktop or mobile application, the redirect URI (often a custom URI scheme) can be registered by the malicious application to perform this interception so this is a pretty identifiable weak point.

But in the context of the web client/server model rocket_oauth2 is typically used with, the malicious application would need to be running on the same machine as the rocket_oauth2 server to intercept the particular code -- which is typically more difficult to pull off than a custom URI scheme registration, and much more catastrophic in other ways.

As I understand things today, the recommendation is to use PKCE "everywhere" (for additional reasons) and I do think it's worth supporting. That said I don't actively maintain this library at present -- so I'd be happy to integrate a working implementation, or if it's small I might get around to it some time. One particular obstacle I remember, and one reason I didn't implement it before, was the requirement for some storage to associate the challenge to the code across requests. If that's doable with a secure Cookie that would probably be best -- otherwise, this will require some kind of persistent storage or cache and APIs to configure them. I considered in-memory but that's a no-go for multi-instance deployments - either the storage needs to be shared across all instances, or the traffic has to be pinned such that the same client always reaches the same instance.

@emirror-de
Copy link
Author

Yes, I agree to your thinking. Going for the secure cookie sounds reasonable to me as well. I have no experience with pinning the network traffic yet, but it sounds way more complicated and error prone than solving it with a cookie. I will give it a try this week.

@emirror-de
Copy link
Author

emirror-de commented Dec 17, 2024

So, I found the possibility to disable PKCE challenge for kanidm. But somehow I am not able to create an example for this where I can build upon starting the implementation for PKCE support.

After successfully logging into kanidm I do get redirected to the /auth/kanidm route with the state and code parameter. But somehow rocket_oauth2 returns an Certificate error. The certificates I am using are freshly generated so I do not understand why they should be expired already.

See the logs below:

     Running `target/debug/rocket-kanidm`
🔧 Configured for debug.
   >> address: 127.0.0.1
   >> port: 8000
   >> workers: 64
   >> max blocking threads: 512
   >> ident: Rocket
   >> IP header: X-Real-IP
   >> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB
   >> temp dir: /tmp
   >> http/2: true
   >> keep-alive: 5s
   >> tls: enabled w/o mtls
   >> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s
   >> log level: normal
   >> cli colors: true
   >> secret key: [generated]
Warning: secrets enabled without a stable `secret_key`
   >> disable `secrets` feature or configure a `secret_key`
   >> this becomes an error in non-debug profiles
📬 Routes:
   >> (index) GET /
   >> (index_2) GET / [2]
   >> (kanidm_callback) GET /auth/kanidm
   >> (kanidm_login) GET /login/kanidm
📡 Fairings:
   >> rocket_oauth2::fairing (ignite)
   >> Shield (liftoff, response, singleton)
🛡️ Shield:
   >> Permissions-Policy: interest-cohort=()
   >> X-Content-Type-Options: nosniff
   >> X-Frame-Options: SAMEORIGIN
🚀 Rocket has launched from https://127.0.0.1:8000
Warning: tls handshake with 127.0.0.1:36920 failed: received corrupt message of type InvalidContentType
Warning: tls handshake with 127.0.0.1:36926 failed: received corrupt message of type InvalidContentType
Warning: tls handshake with 127.0.0.1:43830 failed: received corrupt message of type InvalidContentType
Warning: tls handshake with 127.0.0.1:43858 failed: received fatal alert: CertificateUnknown
Warning: tls handshake with 127.0.0.1:43862 failed: received fatal alert: CertificateUnknown
GET / text/html:
   >> Matched: (index) GET /
   >> Request guard `Kanidm` is forwarding.
   >> Outcome: Forward(401 Unauthorized)
   >> Matched: (index_2) GET / [2]
   >> Outcome: Success(200 OK)
   >> Response succeeded.
GET /favicon.ico image/avif:
   >> No matching routes for GET /favicon.ico image/avif.
   >> No 404 catcher registered. Using Rocket default.
   >> Response succeeded.
GET /login/kanidm text/html:
   >> Matched: (kanidm_login) GET /login/kanidm
   >> Outcome: Success(303 See Other)
   >> Response succeeded.
GET /auth/kanidm?state=hiif8gfm2QHtRMBYk01ATw&code=gAAAAABnYYvZhkoMT7VFFjoW2w26Tg_XCtca-1KXKOap_lo-M21x-2ybcNDbTsEVFX6r57jUgy7TVckzck6SDOgjn9uSwM_vfkS87VNyLCh6e1dUxj7ZCSpJdjKSPHkqdwwvy6Iluhh4IGcrg_gQROBrfbSW-LUruULUIAPU4W4tqbsvz4JNznilHjwoyS9pglMaZFrbNEuVbOT5udVhMdk9CDo7CGTVm1NxYr--dcMW04G-6WobcRD542ZniMjsbiedRZ5No8CRXVag9jEsnbMPNKUL98R7mlLxGxXD-2fW1RP70GKZTYSP6v7fIw97-bOZcvZy8dnnn7BZcF_oRpbJr5GtZn1sUCWF3nPnWKtyhiA-avQg8DE%3D text/html:
   >> Matched: (kanidm_callback) GET /auth/kanidm
Warning: OAuth2 token exchange failed: failed to exchange token: client error (Connect)
   >> Request guard `TokenResponse < Kanidm >` failed: Error { kind: ExchangeFailure, source: Some(hyper_util::client::legacy::Error(Connect, Custom { kind: Other, error: Custom { kind: InvalidData, error: InvalidCertificate(Expired) } })) }.
   >> Outcome: Error(400 Bad Request)
   >> No 400 catcher registered. Using Rocket default.
   >> Response succeeded.

I uploaded the rocket test application here. I described the setup for kanidm in the README.md file.

In addition to that, here is the result of kanidm system oauth2 list command after setting up:

class: account
class: memberof
class: oauth2_resource_server
class: oauth2_resource_server_basic
class: object
directmemberof: idm_all_accounts@localhost
displayname: Rocket Oauth2
es256_private_key_der: private_binary
memberof: idm_all_accounts@localhost
name: rocket_testapp
oauth2_allow_insecure_client_disable_pkce: true
oauth2_rs_basic_secret: hidden
oauth2_rs_origin: https://localhost:8000/auth/kanidm
oauth2_rs_origin_landing: https://localhost:8000/
oauth2_rs_scope_map: rocket_oauth@localhost: {"email", "openid", "profile"}
oauth2_rs_token_key: hidden
oauth2_strict_redirect_uri: true
spn: rocket_testapp@localhost
uuid: 1727b4d7-9c38-4c89-a2b6-a676c78412e8

Any ideas what is going wrong here?

EDIT: I also added the changes from #39

@jebrosen
Copy link
Owner

@emirror-de

I think I would start from this error:

>> Request guard `TokenResponse < Kanidm >` failed: Error { kind: ExchangeFailure, source: Some(hyper_util::client::legacy::Error(Connect, Custom { kind: Other, error: Custom { kind: InvalidData, error: InvalidCertificate(Expired) } })) }.

This is indicating that (I'm assuming) a request from the rocket server process to kanidm is unhappy with the certificate. I actually do expect some kind of error here about what looks like probably a self-signed certificate (please do correct me if I'm wrong there). But Expired is interesting - it might be worth double-checking the Not After date with e.g. openssl x509 -in cert.pem -text.

HyperRustlsAdapter uses with_native_roots from hyper-rustls, which should include system-trusted certificates (suitable for typical HTTPS) but will likely not include any self-signed certificates. So, it may also be necessary for your scenario to reimplement Adapter with a different configuration there too - and as a matter of fact I see a TODO comment about something along those same lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

2 participants