1
21
22 package fosite
23
24 import (
25 "context"
26 "crypto/ecdsa"
27 "crypto/rsa"
28 "encoding/json"
29 "fmt"
30 "net/http"
31 "net/url"
32 "time"
33
34 "github.com/ory/x/errorsx"
35
36 "github.com/ory/fosite/token/jwt"
37 "github.com/pkg/errors"
38 jose "gopkg.in/square/go-jose.v2"
39 )
40
41
42 type ClientAuthenticationStrategy func(context.Context, *http.Request, url.Values) (Client, error)
43
44 const clientAssertionJWTBearerType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
45
46 func (f *Fosite) findClientPublicJWK(oidcClient OpenIDConnectClient, t *jwt.Token, expectsRSAKey bool) (interface{}, error) {
47 if set := oidcClient.GetJSONWebKeys(); set != nil {
48 return findPublicKey(t, set, expectsRSAKey)
49 }
50
51 if location := oidcClient.GetJSONWebKeysURI(); len(location) > 0 {
52 keys, err := f.JWKSFetcherStrategy.Resolve(location, false)
53 if err != nil {
54 return nil, err
55 }
56
57 if key, err := findPublicKey(t, keys, expectsRSAKey); err == nil {
58 return key, nil
59 }
60
61 keys, err = f.JWKSFetcherStrategy.Resolve(location, true)
62 if err != nil {
63 return nil, err
64 }
65
66 return findPublicKey(t, keys, expectsRSAKey)
67 }
68
69 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("The OAuth 2.0 Client has no JSON Web Keys set registered, but they are needed to complete the request."))
70 }
71
72
73
74 func (f *Fosite) AuthenticateClient(ctx context.Context, r *http.Request, form url.Values) (Client, error) {
75 if f.ClientAuthenticationStrategy == nil {
76 return f.DefaultClientAuthenticationStrategy(ctx, r, form)
77 }
78 return f.ClientAuthenticationStrategy(ctx, r, form)
79 }
80
81
82
83 func (f *Fosite) DefaultClientAuthenticationStrategy(ctx context.Context, r *http.Request, form url.Values) (Client, error) {
84 if assertionType := form.Get("client_assertion_type"); assertionType == clientAssertionJWTBearerType {
85 assertion := form.Get("client_assertion")
86 if len(assertion) == 0 {
87 return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("The client_assertion request parameter must be set when using client_assertion_type of '%s'.", clientAssertionJWTBearerType))
88 }
89
90 var clientID string
91 var client Client
92
93 token, err := jwt.ParseWithClaims(assertion, jwt.MapClaims{}, func(t *jwt.Token) (interface{}, error) {
94 var err error
95 clientID, _, err = clientCredentialsFromRequestBody(form, false)
96 if err != nil {
97 return nil, err
98 }
99
100 if clientID == "" {
101 claims := t.Claims
102 if sub, ok := claims["sub"].(string); !ok {
103 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("The claim 'sub' from the client_assertion JSON Web Token is undefined."))
104 } else {
105 clientID = sub
106 }
107 }
108
109 client, err = f.Store.GetClient(ctx, clientID)
110 if err != nil {
111 return nil, errorsx.WithStack(ErrInvalidClient.WithWrap(err).WithDebug(err.Error()))
112 }
113
114 oidcClient, ok := client.(OpenIDConnectClient)
115 if !ok {
116 return nil, errorsx.WithStack(ErrInvalidRequest.WithHint("The server configuration does not support OpenID Connect specific authentication methods."))
117 }
118
119 switch oidcClient.GetTokenEndpointAuthMethod() {
120 case "private_key_jwt":
121 break
122 case "none":
123 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("This requested OAuth 2.0 client does not support client authentication, however 'client_assertion' was provided in the request."))
124 case "client_secret_post":
125 fallthrough
126 case "client_secret_basic":
127 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however 'client_assertion' was provided in the request.", oidcClient.GetTokenEndpointAuthMethod()))
128 case "client_secret_jwt":
129 fallthrough
130 default:
131 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("This requested OAuth 2.0 client only supports client authentication method '%s', however that method is not supported by this server.", oidcClient.GetTokenEndpointAuthMethod()))
132 }
133
134 if oidcClient.GetTokenEndpointAuthSigningAlgorithm() != fmt.Sprintf("%s", t.Header["alg"]) {
135 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The 'client_assertion' uses signing algorithm '%s' but the requested OAuth 2.0 Client enforces signing algorithm '%s'.", t.Header["alg"], oidcClient.GetTokenEndpointAuthSigningAlgorithm()))
136 }
137 switch t.Method {
138 case jose.RS256, jose.RS384, jose.RS512:
139 return f.findClientPublicJWK(oidcClient, t, true)
140 case jose.ES256, jose.ES384, jose.ES512:
141 return f.findClientPublicJWK(oidcClient, t, false)
142 case jose.PS256, jose.PS384, jose.PS512:
143 return f.findClientPublicJWK(oidcClient, t, true)
144 case jose.HS256, jose.HS384, jose.HS512:
145 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("This authorization server does not support client authentication method 'client_secret_jwt'."))
146 default:
147 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The 'client_assertion' request parameter uses unsupported signing algorithm '%s'.", t.Header["alg"]))
148 }
149 })
150 if err != nil {
151
152 var e *jwt.ValidationError
153 if errors.As(err, &e) {
154 if e.Inner != nil {
155 return nil, e.Inner
156 }
157 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Unable to verify the integrity of the 'client_assertion' value.").WithWrap(err).WithDebug(err.Error()))
158 }
159 return nil, err
160 } else if err := token.Claims.Valid(); err != nil {
161 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Unable to verify the request object because its claims could not be validated, check if the expiry time is set correctly.").WithWrap(err).WithDebug(err.Error()))
162 }
163
164 claims := token.Claims
165 var jti string
166 if !claims.VerifyIssuer(clientID, true) {
167 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'iss' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client."))
168 } else if f.TokenURL == "" {
169 return nil, errorsx.WithStack(ErrMisconfiguration.WithHint("The authorization server's token endpoint URL has not been set."))
170 } else if sub, ok := claims["sub"].(string); !ok || sub != clientID {
171 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'sub' from 'client_assertion' must match the 'client_id' of the OAuth 2.0 Client."))
172 } else if jti, ok = claims["jti"].(string); !ok || len(jti) == 0 {
173 return nil, errorsx.WithStack(ErrInvalidClient.WithHint("Claim 'jti' from 'client_assertion' must be set but is not."))
174 } else if f.Store.ClientAssertionJWTValid(ctx, jti) != nil {
175 return nil, errorsx.WithStack(ErrJTIKnown.WithHint("Claim 'jti' from 'client_assertion' MUST only be used once."))
176 }
177
178
179 var expiry int64
180 err = nil
181 switch exp := claims["exp"].(type) {
182 case float64:
183 expiry = int64(exp)
184 case int64:
185 expiry = exp
186 case json.Number:
187 expiry, err = exp.Int64()
188 default:
189 err = ErrInvalidClient.WithHint("Unable to type assert the expiry time from claims. This should not happen as we validate the expiry time already earlier with token.Claims.Valid()")
190 }
191
192 if err != nil {
193 return nil, errorsx.WithStack(err)
194 }
195 if err := f.Store.SetClientAssertionJWT(ctx, jti, time.Unix(expiry, 0)); err != nil {
196 return nil, err
197 }
198
199 if auds, ok := claims["aud"].([]interface{}); !ok {
200 if !claims.VerifyAudience(f.TokenURL, true) {
201 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.TokenURL))
202 }
203 } else {
204 var found bool
205 for _, aud := range auds {
206 if a, ok := aud.(string); ok && a == f.TokenURL {
207 found = true
208 break
209 }
210 }
211
212 if !found {
213 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("Claim 'audience' from 'client_assertion' must match the authorization server's token endpoint '%s'.", f.TokenURL))
214 }
215 }
216
217 return client, nil
218 } else if len(assertionType) > 0 {
219 return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("Unknown client_assertion_type '%s'.", assertionType))
220 }
221
222 clientID, clientSecret, err := clientCredentialsFromRequest(r, form)
223 if err != nil {
224 return nil, err
225 }
226
227 client, err := f.Store.GetClient(ctx, clientID)
228 if err != nil {
229 return nil, errorsx.WithStack(ErrInvalidClient.WithWrap(err).WithDebug(err.Error()))
230 }
231
232 if oidcClient, ok := client.(OpenIDConnectClient); !ok {
233
234 } else if ok && form.Get("client_id") != "" && form.Get("client_secret") != "" && oidcClient.GetTokenEndpointAuthMethod() != "client_secret_post" {
235 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_post' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_post'.", oidcClient.GetTokenEndpointAuthMethod()))
236 } else if _, secret, basicOk := r.BasicAuth(); basicOk && ok && secret != "" && oidcClient.GetTokenEndpointAuthMethod() != "client_secret_basic" {
237 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'client_secret_basic' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'client_secret_basic'.", oidcClient.GetTokenEndpointAuthMethod()))
238 } else if ok && oidcClient.GetTokenEndpointAuthMethod() != "none" && client.IsPublic() {
239 return nil, errorsx.WithStack(ErrInvalidClient.WithHintf("The OAuth 2.0 Client supports client authentication method '%s', but method 'none' was requested. You must configure the OAuth 2.0 client's 'token_endpoint_auth_method' value to accept 'none'.", oidcClient.GetTokenEndpointAuthMethod()))
240 }
241
242 if client.IsPublic() {
243 return client, nil
244 }
245
246
247 if err := f.checkClientSecret(ctx, client, []byte(clientSecret)); err != nil {
248 return nil, errorsx.WithStack(ErrInvalidClient.WithWrap(err).WithDebug(err.Error()))
249 }
250
251 return client, nil
252 }
253
254 func (f *Fosite) checkClientSecret(ctx context.Context, client Client, clientSecret []byte) error {
255 var err error
256 err = f.Hasher.Compare(ctx, client.GetHashedSecret(), clientSecret)
257 if err == nil {
258 return nil
259 }
260 cc, ok := client.(ClientWithSecretRotation)
261 if !ok {
262 return err
263 }
264 for _, hash := range cc.GetRotatedHashes() {
265 err = f.Hasher.Compare(ctx, hash, clientSecret)
266 if err == nil {
267 return nil
268 }
269 }
270
271 return err
272 }
273
274 func findPublicKey(t *jwt.Token, set *jose.JSONWebKeySet, expectsRSAKey bool) (interface{}, error) {
275 keys := set.Keys
276 if len(keys) == 0 {
277 return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("The retrieved JSON Web Key Set does not contain any keys."))
278 }
279
280 kid, ok := t.Header["kid"].(string)
281 if ok {
282 keys = set.Key(kid)
283 }
284
285 if len(keys) == 0 {
286 return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("The JSON Web Token uses signing key with kid '%s', which could not be found.", kid))
287 }
288
289 for _, key := range keys {
290 if key.Use != "sig" {
291 continue
292 }
293 if expectsRSAKey {
294 if k, ok := key.Key.(*rsa.PublicKey); ok {
295 return k, nil
296 }
297 } else {
298 if k, ok := key.Key.(*ecdsa.PublicKey); ok {
299 return k, nil
300 }
301 }
302 }
303
304 if expectsRSAKey {
305 return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("Unable to find RSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid))
306 } else {
307 return nil, errorsx.WithStack(ErrInvalidRequest.WithHintf("Unable to find ECDSA public key with use='sig' for kid '%s' in JSON Web Key Set.", kid))
308 }
309 }
310
311 func clientCredentialsFromRequest(r *http.Request, form url.Values) (clientID, clientSecret string, err error) {
312 if id, secret, ok := r.BasicAuth(); !ok {
313 return clientCredentialsFromRequestBody(form, true)
314 } else if clientID, err = url.QueryUnescape(id); err != nil {
315 return "", "", errorsx.WithStack(ErrInvalidRequest.WithHint("The client id in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithWrap(err).WithDebug(err.Error()))
316 } else if clientSecret, err = url.QueryUnescape(secret); err != nil {
317 return "", "", errorsx.WithStack(ErrInvalidRequest.WithHint("The client secret in the HTTP authorization header could not be decoded from 'application/x-www-form-urlencoded'.").WithWrap(err).WithDebug(err.Error()))
318 }
319
320 return clientID, clientSecret, nil
321 }
322
323 func clientCredentialsFromRequestBody(form url.Values, forceID bool) (clientID, clientSecret string, err error) {
324 clientID = form.Get("client_id")
325 clientSecret = form.Get("client_secret")
326
327 if clientID == "" && forceID {
328 return "", "", errorsx.WithStack(ErrInvalidRequest.WithHint("Client credentials missing or malformed in both HTTP Authorization header and HTTP POST body."))
329 }
330
331 return clientID, clientSecret, nil
332 }
333
View as plain text