/
hmac.go
352 lines (295 loc) · 10.2 KB
/
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
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
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
package webcrypto
import (
"crypto/rand"
"crypto/sha1" //nolint:gosec
"crypto/sha256"
"crypto/sha512"
"fmt"
"hash"
"github.com/dop251/goja"
"gopkg.in/guregu/null.v3"
)
// HMACKeyGenParams represents the object that should be passed as the algorithm parameter
// into `SubtleCrypto.GenerateKey`, when generating an HMAC key.
type HMACKeyGenParams struct {
Algorithm
// Hash represents the name of the digest function to use. You can
// use any of the following: [Sha256], [Sha384],
// or [Sha512].
Hash Algorithm `js:"hash"`
// Length holds (a Number) the length of the key, in bits.
// If this is omitted, the length of the key is equal to the block size
// of the hash function you have chosen.
// Unless you have a good reason to use a different length, omit
// use the default.
Length null.Int `js:"length"`
}
func (hkgp HMACKeyGenParams) hash() string {
return hkgp.Hash.Name
}
// newHMACKeyGenParams creates a new HMACKeyGenParams object, from the normalized
// algorithm, and the params parameters passed by the user.
//
// It handles the logic of extracting the hash algorithm from the params object,
// and normalizing it. It also handles the logic of extracting the length
// attribute from the params object, and setting it to the default value if it's
// not present as described in the hmac `generateKey` [specification].
//
// [specification]: https://www.w3.org/TR/WebCryptoAPI/#hmac-operations
func newHMACKeyGenParams(rt *goja.Runtime, normalized Algorithm, params goja.Value) (*HMACKeyGenParams, error) {
// The specification doesn't explicitly tell us what to do if the
// hash field is not present, but we assume it's a mandatory field
// and throw an error if it's not present.
hashValue, err := traverseObject(rt, params, "hash")
if err != nil {
return nil, NewError(SyntaxError, "could not get hash from algorithm parameter")
}
// Although the specification doesn't explicitly ask us to do so, we
// normalize the hash algorithm here, as it shares the same definition
// as the AlgorithmIdentifier type, and we'll need it later on.
//
// Note @oleiade: The specification seems to assume that the normalization
// algorithm will normalize the existing Algorithm fields, and leave
// the rest untouched. As we are in the context of a statically typed
// language, we can't do that, so we need to normalize the hash
// algorithm here.
normalizedHash, err := normalizeAlgorithm(rt, hashValue, OperationIdentifierGenerateKey)
if err != nil {
return nil, err
}
// As the length attribute is optional and as the key generation process
// differentiates unset from zero-values, we need to handle the case
// where the length attribute is not present in the params object.
var length null.Int
algorithmLengthValue, err := traverseObject(rt, params, "length")
if err == nil {
length = null.IntFrom(algorithmLengthValue.ToInteger())
}
return &HMACKeyGenParams{
Algorithm: normalized,
Hash: normalizedHash,
Length: length,
}, nil
}
// GenerateKey generates a new HMAC key.
func (hkgp *HMACKeyGenParams) GenerateKey(
extractable bool,
keyUsages []CryptoKeyUsage,
) (*CryptoKey, error) {
// 1.
for _, usage := range keyUsages {
switch usage {
case SignCryptoKeyUsage, VerifyCryptoKeyUsage:
continue
default:
return nil, NewError(SyntaxError, "invalid key usage: "+usage)
}
}
// 2.
// We extract the length attribute from the algorithm object, as it's not
// part of the normalized algorithm, and as accessing the runtime from the
// callback below could lead to a race condition.
if !hkgp.Length.Valid {
var length bitLength
switch hkgp.Hash.Name {
case SHA1:
length = byteLength(sha1.BlockSize).asBitLength()
case SHA256:
length = byteLength(sha256.BlockSize).asBitLength()
case SHA384:
length = byteLength(sha512.BlockSize).asBitLength()
case SHA512:
length = byteLength(sha512.BlockSize).asBitLength()
default:
// This case should never happen, as the normalization algorithm
// should have thrown an error if the hash algorithm is invalid.
return nil, NewError(ImplementationError, "invalid hash algorithm: "+hkgp.Hash.Name)
}
hkgp.Length = null.IntFrom(int64(length))
}
if hkgp.Length.Int64 == 0 {
return nil, NewError(OperationError, "algorithm's length cannot be 0")
}
// 3.
randomKey := make([]byte, bitLength(hkgp.Length.Int64).asByteLength())
if _, err := rand.Read(randomKey); err != nil {
// 4.
return nil, NewError(OperationError, "failed to generate random key; reason: "+err.Error())
}
// 5.
key := &CryptoKey{Type: SecretCryptoKeyType, handle: randomKey}
// 6.
algorithm := &HMACKeyAlgorithm{}
// 7.
algorithm.Name = HMAC
algorithm.Length = hkgp.Length.Int64
// 8.
hash := KeyAlgorithm{}
// 9.
hash.Name = hkgp.Hash.Name
// 10.
algorithm.Hash = hash
// 11. 12. 13.
key.Algorithm = algorithm
key.Extractable = extractable
key.Usages = keyUsages
return key, nil
}
// Ensure that HMACKeyGenParams implements the KeyGenerator interface.
var _ KeyGenerator = &HMACKeyGenParams{}
// HMACKeyAlgorithm represents the algorithm of an HMAC key.
type HMACKeyAlgorithm struct {
KeyAlgorithm
// Hash represents the inner hash function to use.
Hash KeyAlgorithm `js:"hash"`
// Length represents he length (in bits) of the key.
Length int64 `js:"length"`
}
func (hka HMACKeyAlgorithm) hash() string {
return hka.Hash.Name
}
func exportHMACKey(ck *CryptoKey, format KeyFormat) (interface{}, error) {
// 1.
if ck.handle == nil {
return nil, NewError(OperationError, "key data is not accessible")
}
// 2.
bits, ok := ck.handle.([]byte)
if !ok {
return nil, NewError(OperationError, "key underlying data is not of the correct type")
}
// 4.
switch format {
case RawKeyFormat:
return bits, nil
case JwkKeyFormat:
m, err := exportSymmetricJWK(ck)
if err != nil {
return nil, NewError(ImplementationError, err.Error())
}
return m, nil
default:
// FIXME: note that we do not support JWK format, yet #37.
return nil, NewError(NotSupportedError, "unsupported key format "+format)
}
}
// HashFn returns the hash function to use for the HMAC key.
func (hka *HMACKeyAlgorithm) HashFn() (func() hash.Hash, error) {
hashFn, ok := getHashFn(hka.Hash.Name)
if !ok {
return nil, NewError(NotSupportedError, fmt.Sprintf("unsupported key hash algorithm %q", hka.Hash.Name))
}
return hashFn, nil
}
// HMACImportParams represents the object that should be passed as the algorithm parameter
// into `SubtleCrypto.GenerateKey`, when generating an HMAC key.
type HMACImportParams struct {
Algorithm
// Hash represents the name of the digest function to use. You can
// use any of the following: [Sha256], [Sha384],
// or [Sha512].
Hash Algorithm `js:"hash"`
// Length holds (a Number) the length of the key, in bits.
// If this is omitted, the length of the key is equal to the block size
// of the hash function you have chosen.
// Unless you have a good reason to use a different length, omit
// use the default.
Length null.Int `js:"length"`
}
var _ hasHash = (*HMACImportParams)(nil)
func (hip HMACImportParams) hash() string {
return hip.Hash.Name
}
// newHMACImportParams creates a new HMACImportParams object from the given
// algorithm and params objects.
func newHMACImportParams(rt *goja.Runtime, normalized Algorithm, params goja.Value) (*HMACImportParams, error) {
// The specification doesn't explicitly tell us what to do if the
// hash field is not present, but we assume it's a mandatory field
// and throw an error if it's not present.
hashValue, err := traverseObject(rt, params, "hash")
if err != nil {
return nil, NewError(SyntaxError, "could not get hash from algorithm parameter")
}
// Although the specification doesn't explicitly ask us to do so, we
// normalize the hash algorithm here, as it shares the same definition
// as the AlgorithmIdentifier type, and we'll need it later on.
//
// Note @oleiade: The specification seems to assume that the normalization
// algorithm will normalize the existing Algorithm fields, and leave
// the rest untouched. As we are in the context of a statically typed
// language, we can't do that, so we need to normalize the hash
// algorithm here.
normalizedHash, err := normalizeAlgorithm(rt, hashValue, OperationIdentifierGenerateKey)
if err != nil {
return nil, err
}
// As the length attribute is optional and as the key generation process
// differentiates unset from zero-values, we need to handle the case
// where the length attribute is not present in the params object.
var length null.Int
algorithmLengthValue, err := traverseObject(rt, params, "length")
if err == nil {
length = null.IntFrom(algorithmLengthValue.ToInteger())
}
return &HMACImportParams{
Algorithm: normalized,
Hash: normalizedHash,
Length: length,
}, nil
}
// ImportKey imports a key from raw key data. It implements the KeyImporter interface.
func (hip *HMACImportParams) ImportKey(
format KeyFormat,
keyData []byte,
keyUsages []CryptoKeyUsage,
) (*CryptoKey, error) {
// 2.
for _, usage := range keyUsages {
switch usage {
case SignCryptoKeyUsage, VerifyCryptoKeyUsage:
continue
default:
return nil, NewError(SyntaxError, "invalid key usage: "+usage)
}
}
// 3.
if format != RawKeyFormat && format != JwkKeyFormat {
return nil, NewError(NotSupportedError, "unsupported key format "+format)
}
hash := KeyAlgorithm{Algorithm{Name: hip.Hash.Name}}
// 4.
if format == JwkKeyFormat {
var err error
keyData, err = extractSymmetricJWK(keyData)
if err != nil {
return nil, NewError(DataError, err.Error())
}
}
// 5. 6.
length := byteLength(len(keyData)).asBitLength()
if length == 0 {
return nil, NewError(DataError, "key length cannot be 0")
}
// 7.
if hip.Length.Valid && hip.Length.Int64 != int64(length) {
return nil, NewError(DataError, "key length cannot be different from the length of the imported data")
}
// 8.
key := CryptoKey{
Type: SecretCryptoKeyType,
handle: keyData[:length.asByteLength()],
}
// 9.
algorithm := HMACKeyAlgorithm{}
// 10.
algorithm.Name = HMAC
// 11.
algorithm.Length = int64(length)
// 12.
algorithm.Hash = hash
// 13.
key.Algorithm = algorithm
return &key, nil
}
// Ensure that HMACImportParams implements the KeyImporter interface.
var _ KeyImporter = &HMACImportParams{}