// Package hmac is the default implementation for generating and validating challenges. It uses HMAC-SHA256 to // generate and validate challenges. package hmac import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "fmt" "strings" "github.com/ory/fosite" "github.com/pkg/errors" ) // HMACStrategy is responsible for generating and validating challenges. type HMACStrategy struct { AuthCodeEntropy int GlobalSecret []byte } const ( // key should be at least 256 bit long, making it minimumEntropy = 32 // the secrets (client and global) should each have at least 16 characters making it harder to guess them minimumSecretLength = 32 ) var b64 = base64.URLEncoding.WithPadding(base64.NoPadding) // Generate generates a token and a matching signature or returns an error. // This method implements rfc6819 Section 5.1.4.2.2: Use High Entropy for Secrets. func (c *HMACStrategy) Generate() (string, string, error) { if len(c.GlobalSecret) < minimumSecretLength/2 { return "", "", errors.New("Secret is not strong enough") } if c.AuthCodeEntropy < minimumEntropy { c.AuthCodeEntropy = minimumEntropy } // When creating secrets not intended for usage by human users (e.g., // client secrets or token handles), the authorization server should // include a reasonable level of entropy in order to mitigate the risk // of guessing attacks. The token value should be >=128 bits long and // constructed from a cryptographically strong random or pseudo-random // number sequence (see [RFC4086] for best current practice) generated // by the authorization server. key, err := RandomBytes(c.AuthCodeEntropy) if err != nil { return "", "", errors.WithStack(err) } if len(key) < c.AuthCodeEntropy { return "", "", errors.New("Could not read enough random data for key generation") } useSecret := append([]byte{}, c.GlobalSecret...) mac := hmac.New(sha256.New, useSecret) _, err = mac.Write(key) if err != nil { return "", "", errors.WithStack(err) } signature := mac.Sum([]byte{}) encodedSignature := b64.EncodeToString(signature) encodedToken := fmt.Sprintf("%s.%s", b64.EncodeToString(key), encodedSignature) return encodedToken, encodedSignature, nil } // Validate validates a token and returns its signature or an error if the token is not valid. func (c *HMACStrategy) Validate(token string) error { split := strings.Split(token, ".") if len(split) != 2 { return errors.WithStack(fosite.ErrInvalidTokenFormat) } key := split[0] signature := split[1] if key == "" || signature == "" { return errors.WithStack(fosite.ErrInvalidTokenFormat) } decodedSignature, err := b64.DecodeString(signature) if err != nil { return errors.WithStack(err) } decodedKey, err := b64.DecodeString(key) if err != nil { return errors.WithStack(err) } useSecret := append([]byte{}, c.GlobalSecret...) mac := hmac.New(sha256.New, useSecret) _, err = mac.Write(decodedKey) if err != nil { return errors.WithStack(err) } if !hmac.Equal(decodedSignature, mac.Sum([]byte{})) { // Hash is invalid return errors.WithStack(fosite.ErrTokenSignatureMismatch) } return nil } func (c *HMACStrategy) Signature(token string) string { split := strings.Split(token, ".") if len(split) != 2 { return "" } return split[1] }