...

Source file src/github.com/ory/fosite/handler/pkce/handler.go

Documentation: github.com/ory/fosite/handler/pkce

     1  /*
     2   * Copyright © 2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   * @author		Aeneas Rekkas <aeneas+oss@aeneas.io>
    17   * @copyright 	2015-2018 Aeneas Rekkas <aeneas+oss@aeneas.io>
    18   * @license 	Apache-2.0
    19   *
    20   */
    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  	// If set to true, clients must use PKCE.
    40  	Force bool
    41  
    42  	// If set to true, public clients must use PKCE.
    43  	ForceForPublicClients bool
    44  
    45  	// Whether or not to allow the plain challenge method (S256 should be used whenever possible, plain is really discouraged).
    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  	// This let's us define multiple response types, for example open id connect's id_token
    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  		// If the server requires Proof Key for Code Exchange (PKCE) by OAuth
    87  		// clients and the client does not send the "code_challenge" in
    88  		// the request, the authorization endpoint MUST return the authorization
    89  		// error response with the "error" value set to "invalid_request".  The
    90  		// "error_description" or the response of "error_uri" SHOULD explain the
    91  		// nature of error, e.g., code challenge required.
    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  	// If the server supporting PKCE does not support the requested
   106  	// transformation, the authorization endpoint MUST return the
   107  	// authorization error response with "error" value set to
   108  	// "invalid_request".  The "error_description" or the response of
   109  	// "error_uri" SHOULD explain the nature of error, e.g., transform
   110  	// algorithm not supported.
   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  	// code_verifier
   135  	// REQUIRED.  Code verifier
   136  	//
   137  	// The "code_challenge_method" is bound to the Authorization Code when
   138  	// the Authorization Code is issued.  That is the method that the token
   139  	// endpoint MUST use to verify the "code_verifier".
   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  	// NOTE: The code verifier SHOULD have enough entropy to make it
   167  	// 	impractical to guess the value.  It is RECOMMENDED that the output of
   168  	// 	a suitable random number generator be used to create a 32-octet
   169  	// 	sequence.  The octet sequence is then base64url-encoded to produce a
   170  	// 	43-octet URL safe string to use as the code verifier.
   171  
   172  	// Validation
   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  	// Upon receipt of the request at the token endpoint, the server
   185  	// verifies it by calculating the code challenge from the received
   186  	// "code_verifier" and comparing it with the previously associated
   187  	// "code_challenge", after first transforming it according to the
   188  	// "code_challenge_method" method specified by the client.
   189  	//
   190  	// 	If the "code_challenge_method" from Section 4.3 was "S256", the
   191  	// received "code_verifier" is hashed by SHA-256, base64url-encoded, and
   192  	// then compared to the "code_challenge", i.e.:
   193  	//
   194  	// BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge
   195  	//
   196  	// If the "code_challenge_method" from Section 4.3 was "plain", they are
   197  	// compared directly, i.e.:
   198  	//
   199  	// code_verifier == code_challenge.
   200  	//
   201  	// 	If the values are equal, the token endpoint MUST continue processing
   202  	// as normal (as defined by OAuth 2.0 [RFC6749]).  If the values are not
   203  	// equal, an error response indicating "invalid_grant" as described in
   204  	// Section 5.2 of [RFC6749] MUST be returned.
   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  	// grant_type REQUIRED.
   239  	// Value MUST be set to "authorization_code"
   240  	return requester.GetGrantTypes().ExactOne("authorization_code")
   241  }
   242  

View as plain text