...
1
21
22 package pkce
23
24 import (
25 "context"
26 "crypto/sha256"
27 "encoding/base64"
28 "regexp"
29
30 "github.com/ory/x/errorsx"
31
32 "github.com/pkg/errors"
33
34 "github.com/ory/fosite"
35 "github.com/ory/fosite/handler/oauth2"
36 )
37
38 type Handler struct {
39
40 Force bool
41
42
43 ForceForPublicClients bool
44
45
46 EnablePlainChallengeMethod bool
47
48 AuthorizeCodeStrategy oauth2.AuthorizeCodeStrategy
49 Storage PKCERequestStorage
50 }
51
52 var verifierWrongFormat = regexp.MustCompile("[^\\w\\.\\-~]")
53
54 func (c *Handler) HandleAuthorizeEndpointRequest(ctx context.Context, ar fosite.AuthorizeRequester, resp fosite.AuthorizeResponder) error {
55
56 if !ar.GetResponseTypes().Has("code") {
57 return nil
58 }
59
60 challenge := ar.GetRequestForm().Get("code_challenge")
61 method := ar.GetRequestForm().Get("code_challenge_method")
62 client := ar.GetClient()
63
64 if err := c.validate(challenge, method, client); err != nil {
65 return err
66 }
67
68 code := resp.GetCode()
69 if len(code) == 0 {
70 return errorsx.WithStack(fosite.ErrServerError.WithDebug("The PKCE handler must be loaded after the authorize code handler."))
71 }
72
73 signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(code)
74 if err := c.Storage.CreatePKCERequestSession(ctx, signature, ar.Sanitize([]string{
75 "code_challenge",
76 "code_challenge_method",
77 })); err != nil {
78 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
79 }
80
81 return nil
82 }
83
84 func (c *Handler) validate(challenge, method string, client fosite.Client) error {
85 if challenge == "" {
86
87
88
89
90
91
92 if c.Force {
93 return errorsx.WithStack(fosite.ErrInvalidRequest.
94 WithHint("Clients must include a code_challenge when performing the authorize code flow, but it is missing.").
95 WithDebug("The server is configured in a way that enforces PKCE for clients."))
96 }
97 if c.ForceForPublicClients && client.IsPublic() {
98 return errorsx.WithStack(fosite.ErrInvalidRequest.
99 WithHint("This client must include a code_challenge when performing the authorize code flow, but it is missing.").
100 WithDebug("The server is configured in a way that enforces PKCE for this client."))
101 }
102 return nil
103 }
104
105
106
107
108
109
110
111 switch method {
112 case "S256":
113 break
114 case "plain":
115 fallthrough
116 case "":
117 if !c.EnablePlainChallengeMethod {
118 return errorsx.WithStack(fosite.ErrInvalidRequest.
119 WithHint("Clients must use code_challenge_method=S256, plain is not allowed.").
120 WithDebug("The server is configured in a way that enforces PKCE S256 as challenge method for clients."))
121 }
122 default:
123 return errorsx.WithStack(fosite.ErrInvalidRequest.
124 WithHint("The code_challenge_method is not supported, use S256 instead."))
125 }
126 return nil
127 }
128
129 func (c *Handler) HandleTokenEndpointRequest(ctx context.Context, request fosite.AccessRequester) error {
130 if !c.CanHandleTokenEndpointRequest(request) {
131 return errorsx.WithStack(fosite.ErrUnknownRequest)
132 }
133
134
135
136
137
138
139
140 verifier := request.GetRequestForm().Get("code_verifier")
141
142 code := request.GetRequestForm().Get("code")
143 signature := c.AuthorizeCodeStrategy.AuthorizeCodeSignature(code)
144 authorizeRequest, err := c.Storage.GetPKCERequestSession(ctx, signature, request.GetSession())
145 if errors.Is(err, fosite.ErrNotFound) {
146 return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("Unable to find initial PKCE data tied to this request").WithWrap(err).WithDebug(err.Error()))
147 } else if err != nil {
148 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
149 }
150
151 if err := c.Storage.DeletePKCERequestSession(ctx, signature); err != nil {
152 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
153 }
154
155 challenge := authorizeRequest.GetRequestForm().Get("code_challenge")
156 method := authorizeRequest.GetRequestForm().Get("code_challenge_method")
157 client := authorizeRequest.GetClient()
158 if err := c.validate(challenge, method, client); err != nil {
159 return err
160 }
161
162 if !c.Force && challenge == "" && verifier == "" {
163 return nil
164 }
165
166
167
168
169
170
171
172
173 if len(verifier) < 43 {
174 return errorsx.WithStack(fosite.ErrInvalidGrant.
175 WithHint("The PKCE code verifier must be at least 43 characters."))
176 } else if len(verifier) > 128 {
177 return errorsx.WithStack(fosite.ErrInvalidGrant.
178 WithHint("The PKCE code verifier can not be longer than 128 characters."))
179 } else if verifierWrongFormat.MatchString(verifier) {
180 return errorsx.WithStack(fosite.ErrInvalidGrant.
181 WithHint("The PKCE code verifier must only contain [a-Z], [0-9], '-', '.', '_', '~'."))
182 }
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 switch method {
206 case "S256":
207 hash := sha256.New()
208 if _, err := hash.Write([]byte(verifier)); err != nil {
209 return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
210 }
211
212 if base64.RawURLEncoding.EncodeToString(hash.Sum([]byte{})) != challenge {
213 return errorsx.WithStack(fosite.ErrInvalidGrant.
214 WithHint("The PKCE code challenge did not match the code verifier."))
215 }
216 break
217 case "plain":
218 fallthrough
219 default:
220 if verifier != challenge {
221 return errorsx.WithStack(fosite.ErrInvalidGrant.
222 WithHint("The PKCE code challenge did not match the code verifier."))
223 }
224 }
225
226 return nil
227 }
228
229 func (c *Handler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
230 return nil
231 }
232
233 func (c *Handler) CanSkipClientAuth(requester fosite.AccessRequester) bool {
234 return false
235 }
236
237 func (c *Handler) CanHandleTokenEndpointRequest(requester fosite.AccessRequester) bool {
238
239
240 return requester.GetGrantTypes().ExactOne("authorization_code")
241 }
242
View as plain text