/
token_hmac.go
130 lines (105 loc) · 3.07 KB
/
token_hmac.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package oauth2
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
"strings"
uuid "github.com/satori/go.uuid"
"go.uber.org/zap"
)
var (
TokenEntropy = 32
SecretLength = 32
)
// HMACTokenGenerator is responsible for generating and validating challenges.
type HMACTokenGenerator struct {
secret []byte
}
var b64 = base64.URLEncoding.WithPadding(base64.NoPadding)
func NewHMACTokenGenerator(secret []byte) *HMACTokenGenerator {
return &HMACTokenGenerator{secret}
}
// 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 *HMACTokenGenerator) CreateAccessToken(req *CreateAccessTokenRequest) (string, error) {
if len(c.secret) < SecretLength/2 {
Logger.Error("Secret is not strong enough")
return "", ServerError("Secret is not strong enough", nil)
}
key, err := RandomBytes(TokenEntropy)
if err != nil {
Logger.Error("cloud not generate token", zap.Error(err))
return "", ServerError("cloud not generate token", err)
}
if len(key) < TokenEntropy {
Logger.Error("could not read enough random data for key generation")
return "", ServerError("could not read enough random data for key generation", err)
}
useSecret := append([]byte{}, c.secret...)
mac := hmac.New(sha256.New, useSecret)
_, err = mac.Write(key)
if err != nil {
Logger.Error("hmac write error", zap.Error(err))
return "", ServerError("hmac write error", err)
}
signature := mac.Sum([]byte{})
encodedSignature := b64.EncodeToString(signature)
encodedToken := fmt.Sprintf("%s.%s", b64.EncodeToString(key), encodedSignature)
return encodedToken, nil
}
// Validate validates a token and returns its signature or an error if the token is not valid.
func (c *HMACTokenGenerator) Validate(token string) error {
split := strings.Split(token, ".")
if len(split) != 2 {
return errors.New("invalid token format")
}
key := split[0]
signature := split[1]
if key == "" || signature == "" {
return errors.New("invalid token format")
}
decodedSignature, err := b64.DecodeString(signature)
if err != nil {
return err
}
decodedKey, err := b64.DecodeString(key)
if err != nil {
return err
}
useSecret := append([]byte{}, c.secret...)
mac := hmac.New(sha256.New, useSecret)
_, err = mac.Write(decodedKey)
if err != nil {
return err
}
if !hmac.Equal(decodedSignature, mac.Sum([]byte{})) {
// Hash is invalid
return errors.New("token signature mismatch")
}
return nil
}
func (c *HMACTokenGenerator) Signature(token string) string {
split := strings.Split(token, ".")
if len(split) != 2 {
return ""
}
return split[1]
}
func (c *HMACTokenGenerator) CreateCode() string {
return uuid.NewV4().String()
}
func (c *HMACTokenGenerator) CreateRefreshToken() string {
return uuid.NewV4().String()
}
// RandomBytes returns n random bytes by reading from crypto/rand.Reader
func RandomBytes(n int) ([]byte, error) {
bytes := make([]byte, n)
if _, err := io.ReadFull(rand.Reader, bytes); err != nil {
return []byte{}, err
}
return bytes, nil
}