-
Notifications
You must be signed in to change notification settings - Fork 137
/
mac.go
177 lines (152 loc) · 4.64 KB
/
mac.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
package crypto
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"errors"
"time"
)
var (
errMACExpired = errors.New("mac: expired")
errMACInvalid = errors.New("mac: the value is not valid")
)
const (
macLen = 32 // sha256 hash size
timeLen = 8 // int64 for unix timestamp in seconds
)
// MACConfig contains all the options to encode or decode a message along with
// a proof of integrity and authenticity.
//
// Key is the secret used for the HMAC key. It should contain at least 16 bytes
// and should be generated by a PRNG.
//
// Name is an optional message name that won't be contained in the MACed
// messaged itself but will be MACed against.
type MACConfig struct {
Name string
MaxAge time.Duration
MaxLen int
}
// EncodeAuthMessage associates the given value with a message authentication
// code for integrity and authenticity.
//
// If the value, when base64 encoded with a fixed size header is longer than
// the configured maximum length, it will panic.
//
// Message format (name prefix is in MAC but removed from message):
//
// <---------------- MAC input ---------------->
// <---------- Message ---------->
// | name | additional data | time | value | hmac |
// | ---- | --- | 8 bytes | --- | 32 bytes |
func EncodeAuthMessage(c MACConfig, key, value, additionalData []byte) ([]byte, error) {
// Create message with MAC
preludeLen := len(c.Name) + len(additionalData)
messageAndMACLen := timeLen + len(value) + macLen
totalCap := preludeLen + messageAndMACLen
buf := make([]byte, 0, totalCap)
// Append name and additional data if any
if len(c.Name) > 0 {
buf = append(buf, c.Name...)
}
if len(additionalData) > 0 {
buf = append(buf, additionalData...)
}
// Append timestamp.
// Increase the len of the buffer of 8 bytes; its capacity allows it.
buf = buf[:preludeLen+timeLen]
binary.BigEndian.PutUint64(buf[preludeLen:], uint64(Timestamp()))
// Append value if any
if len(value) > 0 {
buf = append(buf, value...)
}
// Append MAC signature
buf = append(buf, createMAC(key, buf)...)
// Skip name and additional data prelude
buf = buf[preludeLen:]
// Check length
if c.MaxLen > 0 {
if base64.RawURLEncoding.EncodedLen(len(buf)) > c.MaxLen {
panic("the value is too long")
}
}
// Encode to base64
return Base64Encode(buf), nil
}
// DecodeAuthMessage verifies a message authentified with message
// authentication code and returns the message value algon with the issued time
// of the message.
func DecodeAuthMessage(c MACConfig, key, enc, additionalData []byte) ([]byte, error) {
// Check length
if c.MaxLen > 0 {
if len(enc) > c.MaxLen {
return nil, errMACInvalid
}
}
preludeLen := len(c.Name) + len(additionalData)
decCap := base64.RawURLEncoding.DecodedLen(len(enc))
totalCap := decCap + preludeLen
// decCap is the maximum size of the decoded value. The real decoded size may
// be inferior. This is an early check only.
if decCap < macLen+timeLen {
return nil, errMACInvalid
}
buf := make([]byte, 0, totalCap)
// Prepend name and additional data if any
if len(c.Name) > 0 {
buf = append(buf, c.Name...)
}
if len(additionalData) > 0 {
buf = append(buf, additionalData...)
}
// Decode the base64-encoded MAC
{
// Increase len of buffer to write the decoded value, its capacity allows
// it, using the maximum buffer size calculated.
buf = buf[:preludeLen+decCap]
decLen, err := base64.RawURLEncoding.Decode(buf[preludeLen:], enc)
if err != nil {
return nil, errMACInvalid
}
// We re-check the len of the buffer, that is already checked against the
// maximum size of the decoded value `decCap`.
if decLen < macLen+timeLen {
return nil, errMACInvalid
}
// Shrink the buffer back to the exact size of the base64 decoded value.
buf = buf[:preludeLen+decLen]
}
// Verify message with MAC
{
mac := buf[len(buf)-macLen:]
buf = buf[:len(buf)-macLen]
if !verifyMAC(key, buf, mac) {
return nil, errMACInvalid
}
}
// Skip hidden prefix
buf = buf[preludeLen:]
// Read time and verify time ranges
var timeBuf []byte
timeBuf, buf = buf[:timeLen], buf[timeLen:]
if c.MaxAge != 0 {
t := time.Unix(int64(binary.BigEndian.Uint64(timeBuf)), 0)
if t.Add(c.MaxAge).Before(time.Now()) {
return nil, errMACExpired
}
}
// Returns the value
return buf, nil
}
// createMAC creates a MAC with HMAC-SHA256
func createMAC(key, value []byte) []byte {
mac := hmac.New(sha256.New, key)
_, _ = mac.Write(value)
return mac.Sum(nil)
}
// verifyMAC returns true is the MAC is valid
func verifyMAC(key, value []byte, mac []byte) bool {
expectedMAC := createMAC(key, value)
return hmac.Equal(mac, expectedMAC)
}