-
Notifications
You must be signed in to change notification settings - Fork 83
/
auth.go
240 lines (204 loc) · 6.02 KB
/
auth.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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
// Copyright © 2015-2023 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ntp
import (
"bytes"
"crypto/aes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/binary"
"encoding/hex"
)
// AuthType specifies the cryptographic hash algorithm used to generate a
// symmetric key authentication digest (or CMAC) for an NTP message. Please
// note that MD5 and SHA1 are no longer considered secure; they appear here
// solely for compatibility with existing NTP server implementations.
type AuthType int
const (
AuthNone AuthType = iota // no authentication
AuthMD5 // MD5 digest
AuthSHA1 // SHA-1 digest
AuthSHA256 // SHA-2 digest (256 bits)
AuthSHA512 // SHA-2 digest (512 bits)
AuthAES128 // AES-128-CMAC
AuthAES256 // AES-256-CMAC
)
// AuthOptions contains fields used to configure symmetric key authentication
// for an NTP query.
type AuthOptions struct {
// Type determines the cryptographic hash algorithm used to compute the
// authentication digest or CMAC.
Type AuthType
// The cryptographic key used by the client to perform authentication. The
// key may be hex-encoded or ascii-encoded. To use a hex-encoded key,
// prefix it by "HEX:". To use an ascii-encoded key, prefix it by
// "ASCII:". For example, "HEX:6931564b4a5a5045766c55356b30656c7666316c"
// or "ASCII:cvuZyN4C8HX8hNcAWDWp".
Key string
// The identifier used by the NTP server to identify which key to use
// for authentication purposes.
KeyID uint16
}
var algorithms = []struct {
MinKeySize int
MaxKeySize int
DigestSize int
CalcDigest func(payload, key []byte) []byte
}{
{0, 0, 0, nil}, // AuthNone
{4, 32, 16, calcDigest_MD5}, // AuthMD5
{4, 32, 20, calcDigest_SHA1}, // AuthSHA1
{4, 32, 20, calcDigest_SHA256}, // AuthSHA256
{4, 32, 20, calcDigest_SHA512}, // AuthSHA512
{16, 16, 16, calcCMAC_AES}, // AuthAES128
{32, 32, 16, calcCMAC_AES}, // AuthAES256
}
func calcDigest_MD5(payload, key []byte) []byte {
digest := md5.Sum(append(key, payload...))
return digest[:]
}
func calcDigest_SHA1(payload, key []byte) []byte {
digest := sha1.Sum(append(key, payload...))
return digest[:]
}
func calcDigest_SHA256(payload, key []byte) []byte {
digest := sha256.Sum256(append(key, payload...))
return digest[:20]
}
func calcDigest_SHA512(payload, key []byte) []byte {
digest := sha512.Sum512(append(key, payload...))
return digest[:20]
}
func calcCMAC_AES(payload, key []byte) []byte {
// calculate the CMAC according to the algorithm defined in RFC 4493. See
// https://tools.ietf.org/html/rfc4493 for details.
c, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// Generate subkeys.
const rb = 0x87
k1 := make([]byte, 16)
k2 := make([]byte, 16)
c.Encrypt(k1, k1)
double(k1, k1, rb)
double(k2, k1, rb)
// Process all but the last block.
cmac := make([]byte, 16)
for ; len(payload) > 16; payload = payload[16:] {
xor(cmac, payload[:16])
c.Encrypt(cmac, cmac)
}
// Process the last block, padding as necessary.
if len(payload) == 16 {
xor(cmac, payload)
xor(cmac, k1)
} else {
xor(cmac, pad(payload))
xor(cmac, k2)
}
c.Encrypt(cmac, cmac)
return cmac
}
func pad(block []byte) []byte {
pad := make([]byte, 16-len(block))
pad[0] = 0x80
return append(block, pad...)
}
func double(dst, src []byte, xor int) {
_ = src[15] // compiler hint: bounds check
s0 := binary.BigEndian.Uint64(src[0:8])
s1 := binary.BigEndian.Uint64(src[8:16])
carry := int(s0 >> 63)
d0 := (s0 << 1) | (s1 >> 63)
d1 := (s1 << 1) ^ uint64(subtle.ConstantTimeSelect(carry, xor, 0))
_ = dst[15] // compiler hint: bounds check
binary.BigEndian.PutUint64(dst[0:8], d0)
binary.BigEndian.PutUint64(dst[8:16], d1)
}
func xor(dst, src []byte) {
_ = src[15] // compiler hint: bounds check
s0 := binary.BigEndian.Uint64(src[0:8])
s1 := binary.BigEndian.Uint64(src[8:16])
_ = dst[15] // compiler hint: bounds check
d0 := s0 ^ binary.BigEndian.Uint64(dst[0:8])
d1 := s1 ^ binary.BigEndian.Uint64(dst[8:16])
binary.BigEndian.PutUint64(dst[0:8], d0)
binary.BigEndian.PutUint64(dst[8:16], d1)
}
func decodeAuthKey(opt AuthOptions) (key []byte, err error) {
if opt.Type == AuthNone {
return nil, nil
}
var keyIn string
var isHex bool
switch {
case len(opt.Key) >= 4 && opt.Key[:4] == "HEX:":
isHex, keyIn = true, opt.Key[4:]
case len(opt.Key) >= 6 && opt.Key[:6] == "ASCII:":
isHex, keyIn = false, opt.Key[6:]
case len(opt.Key) > 20:
isHex, keyIn = true, opt.Key
default:
isHex, keyIn = false, opt.Key
}
if isHex {
key, err = hex.DecodeString(keyIn)
if err != nil {
return nil, ErrInvalidAuthKey
}
} else {
key = []byte(keyIn)
}
a := algorithms[opt.Type]
if len(key) < a.MinKeySize {
return nil, ErrInvalidAuthKey
}
if len(key) > a.MaxKeySize {
key = key[:a.MaxKeySize]
}
return key, nil
}
func appendMAC(buf *bytes.Buffer, opt AuthOptions, key []byte) {
if opt.Type == AuthNone {
return
}
a := algorithms[opt.Type]
payload := buf.Bytes()
digest := a.CalcDigest(payload, key)
binary.Write(buf, binary.BigEndian, uint32(opt.KeyID))
binary.Write(buf, binary.BigEndian, digest)
}
func verifyMAC(buf []byte, opt AuthOptions, key []byte) error {
if opt.Type == AuthNone {
return nil
}
// Validate that there are enough bytes at the end of the message to
// contain a MAC.
const headerSize = 48
a := algorithms[opt.Type]
macLen := 4 + a.DigestSize
remain := len(buf) - headerSize
if remain < macLen || (remain%4) != 0 {
return ErrAuthFailed
}
// The key ID returned by the server must be the same as the key ID sent
// to the server.
payloadLen := len(buf) - macLen
mac := buf[payloadLen:]
keyID := binary.BigEndian.Uint32(mac[:4])
if keyID != uint32(opt.KeyID) {
return ErrAuthFailed
}
// Calculate and compare digests.
payload := buf[:payloadLen]
digest := a.CalcDigest(payload, key)
if subtle.ConstantTimeCompare(digest, mac[4:]) != 1 {
return ErrAuthFailed
}
return nil
}