fix(op): Add mitigation for PKCE Downgrade Attack (#741)

* fix(op): Add mitigation for PKCE downgrade attack

* chore(op): add test for PKCE verification
This commit is contained in:
Ayato 2025-04-29 23:33:31 +09:00 committed by GitHub
parent 5913c5a074
commit 4f0ed79c0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 8 deletions

View file

@ -80,12 +80,9 @@ func AuthorizeCodeClient(ctx context.Context, tokenReq *oidc.AccessTokenRequest,
} }
codeChallenge := request.GetCodeChallenge() codeChallenge := request.GetCodeChallenge()
if codeChallenge != nil { err = AuthorizeCodeChallenge(tokenReq.CodeVerifier, codeChallenge)
err = AuthorizeCodeChallenge(tokenReq.CodeVerifier, codeChallenge) if err != nil {
return nil, nil, err
if err != nil {
return nil, nil, err
}
} }
if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion { if tokenReq.ClientAssertionType == oidc.ClientAssertionTypeJWTAssertion {

View file

@ -132,11 +132,19 @@ func AuthorizeClientIDSecret(ctx context.Context, clientID, clientSecret string,
// AuthorizeCodeChallenge authorizes a client by validating the code_verifier against the previously sent // AuthorizeCodeChallenge authorizes a client by validating the code_verifier against the previously sent
// code_challenge of the auth request (PKCE) // code_challenge of the auth request (PKCE)
func AuthorizeCodeChallenge(codeVerifier string, challenge *oidc.CodeChallenge) error { func AuthorizeCodeChallenge(codeVerifier string, challenge *oidc.CodeChallenge) error {
if challenge == nil {
if codeVerifier != "" {
return oidc.ErrInvalidRequest().WithDescription("code_verifier unexpectedly provided")
}
return nil
}
if codeVerifier == "" { if codeVerifier == "" {
return oidc.ErrInvalidRequest().WithDescription("code_challenge required") return oidc.ErrInvalidRequest().WithDescription("code_verifier required")
} }
if !oidc.VerifyCodeChallenge(challenge, codeVerifier) { if !oidc.VerifyCodeChallenge(challenge, codeVerifier) {
return oidc.ErrInvalidGrant().WithDescription("invalid code challenge") return oidc.ErrInvalidGrant().WithDescription("invalid code_verifier")
} }
return nil return nil
} }

View file

@ -0,0 +1,75 @@
package op_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/oidc/v3/pkg/oidc"
"github.com/zitadel/oidc/v3/pkg/op"
)
func TestAuthorizeCodeChallenge(t *testing.T) {
tests := []struct {
name string
codeVerifier string
codeChallenge *oidc.CodeChallenge
want func(t *testing.T, err error)
}{
{
name: "missing both code_verifier and code_challenge",
codeVerifier: "",
codeChallenge: nil,
want: func(t *testing.T, err error) {
assert.Nil(t, err)
},
},
{
name: "valid code_verifier",
codeVerifier: "Hello World!",
codeChallenge: &oidc.CodeChallenge{
Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk",
Method: oidc.CodeChallengeMethodS256,
},
want: func(t *testing.T, err error) {
assert.Nil(t, err)
},
},
{
name: "invalid code_verifier",
codeVerifier: "Hi World!",
codeChallenge: &oidc.CodeChallenge{
Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk",
Method: oidc.CodeChallengeMethodS256,
},
want: func(t *testing.T, err error) {
assert.ErrorContains(t, err, "invalid code_verifier")
},
},
{
name: "code_verifier provided without code_challenge",
codeVerifier: "code_verifier",
codeChallenge: nil,
want: func(t *testing.T, err error) {
assert.ErrorContains(t, err, "code_verifier unexpectedly provided")
},
},
{
name: "empty code_verifier",
codeVerifier: "",
codeChallenge: &oidc.CodeChallenge{
Challenge: "f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk",
Method: oidc.CodeChallengeMethodS256,
},
want: func(t *testing.T, err error) {
assert.ErrorContains(t, err, "code_verifier required")
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := op.AuthorizeCodeChallenge(tt.codeVerifier, tt.codeChallenge)
tt.want(t, err)
})
}
}