-
-
Notifications
You must be signed in to change notification settings - Fork 964
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
feat: client-side PKCE take 3 #4078
Conversation
79b76d6
to
03478f9
Compare
408913d
to
8397b5c
Compare
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #4078 +/- ##
==========================================
- Coverage 78.92% 78.46% -0.46%
==========================================
Files 373 376 +3
Lines 27123 26728 -395
==========================================
- Hits 21406 20973 -433
- Misses 4129 4150 +21
- Partials 1588 1605 +17 ☔ View full report in Codecov by Sentry. |
b098326
to
0c294ed
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice and clean! The only thing that could suprise users is that pkce set to force
will also change the callback URL, but at least it is well documented in the config schema.
Maybe we could set the issuer URL for more of the pre-configured providers, so that this will be an automatic security improvement for as many users as possible?
This change introduces a new configuration for OIDC providers: pkce with values auto (default), never, force. When auto is specified or the field is omitted, Kratos will perform autodiscovery and perform PKCE when the server advertises support for it. This requires the issuer_url to be set for the provider. never completely disables PKCE support. This is only theoretically useful: when a provider advertises PKCE support but doesn't actually implement it. force always sends a PKCE challenge in the initial redirect URL, regardless of what the provider advertises. This setting is useful when the provider offers PKCE but doesn't advertise it in his ./well-known/openid-configuration. Important: When setting pkce: force, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. Instead of <base-url>/self-service/methods/oidc/callback/<provider>, you must use <base-url>/self-service/methods/oidc/callback (note missing last path segment). This is to enable the use of the same OAuth client ID+secret when configuring several Kratos OIDC providers, without having to whitelist individual redirect_uris for each Kratos provider config.
0c294ed
to
91b5cd3
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff! I'm mostly worried about regressions for existing OIDC providers. In particular I'm worried about redirects we do in Ory Network.
The easiest way to test this is to set up kratos before this patch with some OIDC connections, and then check if those work like they did before after those changes.
func encryptState(ctx context.Context, c cipher.Cipher, state *oidcv1.State) (ciphertext string, err error) { | ||
m, err := proto.Marshal(state) | ||
if err != nil { | ||
return "", herodot.ErrInternalServerError.WithReasonf("Unable to marshal state: %s", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add WithStack for trace information across all error returns, it makes debugging significantly easier :)
|
||
var newStyleState = false | ||
|
||
func TestHookEnableNewStyleState(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please don't reference the testing library in production code, but instead move this to _test.go
files. If you include it, the testing library adds flags and has other side effects on the production binary. If you need to include this function in another else (like we do with persister tests), move it to a separate package. If the package is not referenced by production code, it is not included.
For example, running go run . version -test.short
no longer fails with an invalid argument (as opposed to go run . version -asdf
), because the flag is now defined. This can have unintended consequences / side effects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code will be removed as soon as we have completed the two-step rollout on Ory Network.
@@ -43,6 +44,11 @@ func (c *XChaCha20Poly1305) Encrypt(ctx context.Context, message []byte) (string | |||
return "", herodot.ErrInternalServerError.WithWrap(err).WithReason("Unable to generate key") | |||
} | |||
|
|||
// Make sure the size calculation does not overflow. | |||
if len(message) > math.MaxInt-aead.NonceSize()-aead.Overhead() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is taken from cryptopasta, if I recall correctly. Could you point me to the documentation / issue this addresses? This method is being used in several places, and I am worried this will introduce regressions. Granted, MaxInt is pretty large.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The AES-GCM code comes from cryptopasta. The XChaCha code was hand-written from the start.
In an earlier revision of this PR, I touched this code but reverted it later to the original version. GitHub's security scanner then started complaining, correctly, that the size calculation below could overflow. Even though the code was unchanged from master. So I added this assertion, which is the exact same as the one in Hydra.
@@ -63,10 +62,12 @@ func TestGetCmd(t *testing.T) { | |||
return out | |||
} | |||
transform := func(token string) string { | |||
if !encrypt { | |||
return token | |||
if encrypt { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this changed because we now, somewhere, properly check if the token can be decrypted and then this test failed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly. I changed most tests across Kratos to actually use encryption, and this one was the only one to fail. Good sign :)
config.ViperKeySecretsCookie: []string{randx.MustString(32, randx.AlphaNum)}, | ||
config.ViperKeySecretsDefault: []string{randx.MustString(32, randx.AlphaNum)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these now always required? If so, have the quickstarts been adjusted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are not strictly required, but I would consider it insecure to run Kratos without these even before this changeset.
@@ -5,6 +5,28 @@ | |||
"ui": { | |||
"method": "POST", | |||
"nodes": [ | |||
{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it expected for these existing OIDC snapshots to be updated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, because I configured additional OIDC providers (those with PKCE=force,auto,never).
return []oauth2.AuthCodeOption{oauth2.VerifierOption(s.GetPkceVerifier())} | ||
} | ||
|
||
func maybePKCE(ctx context.Context, d pkceDependencies, _p Provider) (verifier string) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: underscore is possible to use in Go, but uncommon since it's the throwaway variable name, e.g. _ context.Context
. Better would be to use p Provider
and oauth2Provider, ok := p.(OAuth2Provider)
} | ||
|
||
func (s *State) String() string { | ||
return base64.RawURLEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", s.FlowID, s.Data))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We rely on this format in RedirectSocialAuthenticationFlows
in our other repository. Changing this method will most likely cause regressions there. Are you aware of this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I am aware, that's why the toggle (which defaults to the old-style state generation) exists in the first place. I will adjust the code in cloud to work with both state generation versions before we activate the new one.
} | ||
|
||
func ParseStateCompatiblity(ctx context.Context, c cipher.Cipher, s string) (*oidcv1.State, error) { | ||
// new-style: encrypted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New style probably won't work with RedirectSocialAuthenticationFlows
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, we need a two-step rollout for this (hence the temporary toggle).
This change introduces a new configuration for OIDC providers:
pkce
with valuesauto
(default),never
,force
.When
auto
is specified or the field is omitted, Kratos will perform autodiscovery and perform PKCE when the server advertises support for it. This requires theissuer_url
to be set for the provider.never
completely disables PKCE support. This is only theoretically useful: when a provider advertises PKCE support but doesn't actually implement it.force
always sends a PKCE challenge in the initial redirect URL, regardless of what the provider advertises. This setting is useful when the provider offers PKCE but doesn't advertise it in his./well-known/openid-configuration
.Important: When setting
pkce: force
, you must whitelist a different return URL for your OAuth2 client in the provider's configuration. Instead of<base-url>/self-service/methods/oidc/callback/<provider>
, you must use<base-url>/self-service/methods/oidc/callback
(note missing last path segment). This is to enable the use of the same OAuth client ID+secret when configuring several Kratos OIDC providers, without having to whitelist individualredirect_uri
s for each Kratos provider config.Supersedes #4033
Supersedes #4059
Closes #4009
Ref https://github.com/ory-corp/cloud/issues/6613