-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
auth_token.go
204 lines (171 loc) · 5.69 KB
/
auth_token.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
// Package token implements authentication by HMAC-signed security token.
package token
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"errors"
"time"
"github.com/tinode/chat/server/auth"
"github.com/tinode/chat/server/store"
"github.com/tinode/chat/server/store/types"
)
// authenticator is a singleton instance of the authenticator.
type authenticator struct {
name string
hmacSalt []byte
lifetime time.Duration
serialNumber int
}
// tokenLayout defines positioning of various bytes in token.
// [8:UID][4:expires][2:authLevel][2:serial-number][2:feature-bits][32:signature] = 50 bytes
type tokenLayout struct {
// User ID.
Uid uint64
// Token expiration time.
Expires uint32
// User's authentication level.
AuthLevel uint16
// Serial number - to invalidate all tokens if needed.
SerialNumber uint16
// Bitmap with feature bits.
Features uint16
}
// Init initializes the authenticator: parses the config and sets salt, serial number and lifetime.
func (ta *authenticator) Init(jsonconf json.RawMessage, name string) error {
if name == "" {
return errors.New("auth_token: authenticator name cannot be blank")
}
if ta.name != "" {
return errors.New("auth_token: already initialized as " + ta.name + "; " + name)
}
type configType struct {
// Key for signing tokens
Key []byte `json:"key"`
// Datatabase or other serial number, to invalidate all issued tokens at once.
SerialNum int `json:"serial_num"`
// Token expiration time
ExpireIn int `json:"expire_in"`
}
var config configType
if err := json.Unmarshal(jsonconf, &config); err != nil {
return errors.New("auth_token: failed to parse config: " + err.Error() + "(" + string(jsonconf) + ")")
}
if len(config.Key) < sha256.Size {
return errors.New("auth_token: the key is missing or too short")
}
if config.ExpireIn <= 0 {
return errors.New("auth_token: invalid expiration value")
}
ta.name = name
ta.hmacSalt = config.Key
ta.lifetime = time.Duration(config.ExpireIn) * time.Second
ta.serialNumber = config.SerialNum
return nil
}
// IsInitialized returns true if the handler is initialized.
func (ta *authenticator) IsInitialized() bool {
return ta.name != ""
}
// AddRecord is not supprted, will produce an error.
func (authenticator) AddRecord(rec *auth.Rec, secret []byte, remoteAddr string) (*auth.Rec, error) {
return nil, types.ErrUnsupported
}
// UpdateRecord is not supported, will produce an error.
func (authenticator) UpdateRecord(rec *auth.Rec, secret []byte, remoteAddr string) (*auth.Rec, error) {
return nil, types.ErrUnsupported
}
// Authenticate checks validity of provided token.
func (ta *authenticator) Authenticate(token []byte, remoteAddr string) (*auth.Rec, []byte, error) {
var tl tokenLayout
dataSize := binary.Size(&tl)
if len(token) < dataSize+sha256.Size {
// Token is too short
return nil, nil, types.ErrMalformed
}
buf := bytes.NewBuffer(token)
err := binary.Read(buf, binary.LittleEndian, &tl)
if err != nil {
return nil, nil, types.ErrMalformed
}
hbuf := new(bytes.Buffer)
binary.Write(hbuf, binary.LittleEndian, &tl)
// Check signature.
hasher := hmac.New(sha256.New, ta.hmacSalt)
hasher.Write(hbuf.Bytes())
if !hmac.Equal(token[dataSize:dataSize+sha256.Size], hasher.Sum(nil)) {
return nil, nil, types.ErrFailed
}
// Check authentication level for validity.
if auth.Level(tl.AuthLevel) > auth.LevelRoot {
return nil, nil, types.ErrMalformed
}
// Check serial number.
if int(tl.SerialNumber) != ta.serialNumber {
return nil, nil, types.ErrFailed
}
// Check token expiration time.
expires := time.Unix(int64(tl.Expires), 0).UTC()
if expires.Before(time.Now().Add(1 * time.Second)) {
return nil, nil, types.ErrExpired
}
return &auth.Rec{
Uid: types.Uid(tl.Uid),
AuthLevel: auth.Level(tl.AuthLevel),
Lifetime: auth.Duration(time.Until(expires)),
Features: auth.Feature(tl.Features),
State: types.StateUndefined}, nil, nil
}
// GenSecret generates a new token.
func (ta *authenticator) GenSecret(rec *auth.Rec) ([]byte, time.Time, error) {
if rec.Lifetime == 0 {
rec.Lifetime = auth.Duration(ta.lifetime)
} else if rec.Lifetime < 0 {
return nil, time.Time{}, types.ErrExpired
}
expires := time.Now().Add(time.Duration(rec.Lifetime)).UTC().Round(time.Millisecond)
tl := tokenLayout{
Uid: uint64(rec.Uid),
Expires: uint32(expires.Unix()),
AuthLevel: uint16(rec.AuthLevel),
SerialNumber: uint16(ta.serialNumber),
Features: uint16(rec.Features),
}
buf := new(bytes.Buffer)
binary.Write(buf, binary.LittleEndian, &tl)
hasher := hmac.New(sha256.New, ta.hmacSalt)
hasher.Write(buf.Bytes())
binary.Write(buf, binary.LittleEndian, hasher.Sum(nil))
return buf.Bytes(), expires, nil
}
// AsTag is not supported, will produce an empty string.
func (authenticator) AsTag(token string) string {
return ""
}
// IsUnique is not supported, will produce an error.
func (authenticator) IsUnique(token []byte, remoteAddr string) (bool, error) {
return false, types.ErrUnsupported
}
// DelRecords adds disabled user ID to a stop list.
func (authenticator) DelRecords(uid types.Uid) error {
return nil
}
// RestrictedTags returns tag namespaces restricted by this authenticator (none for token).
func (authenticator) RestrictedTags() ([]string, error) {
return nil, nil
}
// GetResetParams returns authenticator parameters passed to password reset handler
// (none for token).
func (authenticator) GetResetParams(uid types.Uid) (map[string]interface{}, error) {
return nil, nil
}
const realName = "token"
// GetRealName returns the hardcoded name of the authenticator.
func (authenticator) GetRealName() string {
return realName
}
func init() {
store.RegisterAuthScheme(realName, &authenticator{})
}