...

Source file src/github.com/ory/fosite/token/hmac/hmacsha.go

Documentation: github.com/ory/fosite/token/hmac

     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 hmac is the default implementation for generating and validating challenges. It uses SHA-512/256 to
    23  // generate and validate challenges.
    24  
    25  package hmac
    26  
    27  import (
    28  	"crypto/hmac"
    29  	"crypto/sha512"
    30  	"encoding/base64"
    31  	"fmt"
    32  	"hash"
    33  	"strings"
    34  	"sync"
    35  
    36  	"github.com/ory/x/errorsx"
    37  
    38  	"github.com/pkg/errors"
    39  
    40  	"github.com/ory/fosite"
    41  )
    42  
    43  // HMACStrategy is responsible for generating and validating challenges.
    44  type HMACStrategy struct {
    45  	TokenEntropy         int
    46  	GlobalSecret         []byte
    47  	RotatedGlobalSecrets [][]byte
    48  	Hash                 func() hash.Hash // Hash is a hash.Hash function to use to generate and validate challenges. If nil, sha512.New512_256 is used.
    49  	sync.Mutex
    50  }
    51  
    52  const (
    53  	// key should be at least 256 bit long, making it
    54  	minimumEntropy = 32
    55  
    56  	// the secrets (client and global) should each have at least 16 characters making it harder to guess them
    57  	minimumSecretLength = 32
    58  )
    59  
    60  var b64 = base64.URLEncoding.WithPadding(base64.NoPadding)
    61  
    62  // Generate generates a token and a matching signature or returns an error.
    63  // This method implements rfc6819 Section 5.1.4.2.2: Use High Entropy for Secrets.
    64  func (c *HMACStrategy) Generate() (string, string, error) {
    65  	c.Lock()
    66  	defer c.Unlock()
    67  
    68  	if len(c.GlobalSecret) < minimumSecretLength {
    69  		return "", "", errors.Errorf("secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got %d byte", len(c.GlobalSecret))
    70  	}
    71  
    72  	var signingKey [32]byte
    73  	copy(signingKey[:], c.GlobalSecret)
    74  
    75  	if c.TokenEntropy < minimumEntropy {
    76  		c.TokenEntropy = minimumEntropy
    77  	}
    78  
    79  	// When creating secrets not intended for usage by human users (e.g.,
    80  	// client secrets or token handles), the authorization server should
    81  	// include a reasonable level of entropy in order to mitigate the risk
    82  	// of guessing attacks.  The token value should be >=128 bits long and
    83  	// constructed from a cryptographically strong random or pseudo-random
    84  	// number sequence (see [RFC4086] for best current practice) generated
    85  	// by the authorization server.
    86  	tokenKey, err := RandomBytes(c.TokenEntropy)
    87  	if err != nil {
    88  		return "", "", errorsx.WithStack(err)
    89  	}
    90  
    91  	signature := c.generateHMAC(tokenKey, &signingKey)
    92  
    93  	encodedSignature := b64.EncodeToString(signature)
    94  	encodedToken := fmt.Sprintf("%s.%s", b64.EncodeToString(tokenKey), encodedSignature)
    95  	return encodedToken, encodedSignature, nil
    96  }
    97  
    98  // Validate validates a token and returns its signature or an error if the token is not valid.
    99  func (c *HMACStrategy) Validate(token string) (err error) {
   100  	var keys [][]byte
   101  
   102  	if len(c.GlobalSecret) > 0 {
   103  		keys = append(keys, c.GlobalSecret)
   104  	}
   105  
   106  	if len(c.RotatedGlobalSecrets) > 0 {
   107  		keys = append(keys, c.RotatedGlobalSecrets...)
   108  	}
   109  
   110  	for _, key := range keys {
   111  		if err = c.validate(key, token); err == nil {
   112  			return nil
   113  		} else if errors.Is(err, fosite.ErrTokenSignatureMismatch) {
   114  		} else {
   115  			return err
   116  		}
   117  	}
   118  
   119  	if err == nil {
   120  		return errors.New("a secret for signing HMAC-SHA512/256 is expected to be defined, but none were")
   121  	}
   122  
   123  	return err
   124  }
   125  
   126  func (c *HMACStrategy) validate(secret []byte, token string) error {
   127  	if len(secret) < minimumSecretLength {
   128  		return errors.Errorf("secret for signing HMAC-SHA512/256 is expected to be 32 byte long, got %d byte", len(secret))
   129  	}
   130  
   131  	var signingKey [32]byte
   132  	copy(signingKey[:], secret)
   133  
   134  	split := strings.Split(token, ".")
   135  	if len(split) != 2 {
   136  		return errorsx.WithStack(fosite.ErrInvalidTokenFormat)
   137  	}
   138  
   139  	tokenKey := split[0]
   140  	tokenSignature := split[1]
   141  	if tokenKey == "" || tokenSignature == "" {
   142  		return errorsx.WithStack(fosite.ErrInvalidTokenFormat)
   143  	}
   144  
   145  	decodedTokenSignature, err := b64.DecodeString(tokenSignature)
   146  	if err != nil {
   147  		return errorsx.WithStack(err)
   148  	}
   149  
   150  	decodedTokenKey, err := b64.DecodeString(tokenKey)
   151  	if err != nil {
   152  		return errorsx.WithStack(err)
   153  	}
   154  
   155  	expectedMAC := c.generateHMAC(decodedTokenKey, &signingKey)
   156  	if !hmac.Equal(expectedMAC, decodedTokenSignature) {
   157  		// Hash is invalid
   158  		return errorsx.WithStack(fosite.ErrTokenSignatureMismatch)
   159  	}
   160  
   161  	return nil
   162  }
   163  
   164  func (c *HMACStrategy) Signature(token string) string {
   165  	split := strings.Split(token, ".")
   166  
   167  	if len(split) != 2 {
   168  		return ""
   169  	}
   170  
   171  	return split[1]
   172  }
   173  
   174  func (c *HMACStrategy) generateHMAC(data []byte, key *[32]byte) []byte {
   175  	hasher := c.Hash
   176  	if hasher == nil {
   177  		hasher = sha512.New512_256
   178  	}
   179  	h := hmac.New(hasher, key[:])
   180  	// sha512.digest.Write() always returns nil for err, the panic should never happen
   181  	_, err := h.Write(data)
   182  	if err != nil {
   183  		panic(err)
   184  	}
   185  	return h.Sum(nil)
   186  }
   187  

View as plain text