/
aes_mac.go
181 lines (150 loc) · 4.56 KB
/
aes_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
178
179
180
181
// (c) 2022-2022, LDC Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package aesmac
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha1"
"crypto/subtle"
"fmt"
"io"
"github.com/ldclabs/cose/go/key"
)
// GenerateKey generates a new key with given algorithm for AES-CBC-MAC.
func GenerateKey(alg key.Alg) (key.Key, error) {
keyLen, _ := getKeyLen(alg)
if keyLen == 0 {
return nil, fmt.Errorf(`cose/go/key/aesmac: GenerateKey: algorithm mismatch %q`, alg.String())
}
kb := key.GetRandomBytes(uint16(keyLen))
_, err := io.ReadFull(rand.Reader, kb)
if err != nil {
return nil, fmt.Errorf("cose/go/key/aesmac: GenerateKey: %w", err)
}
idhash := sha1.New()
idhash.Write(kb)
return map[key.IntKey]any{
key.ParamKty: key.KtySymmetric,
key.ParamKid: idhash.Sum(nil)[:10], // default kid, can be set to other value.
key.ParamAlg: alg,
key.ParamK: kb, // REQUIRED
}, nil
}
// CheckKey checks whether the given key is a valid AES-CBC-MAC key.
//
// Reference https://datatracker.ietf.org/doc/html/rfc9053#name-hash-based-message-authenti
func CheckKey(k key.Key) error {
if k.Kty() != key.KtySymmetric {
return fmt.Errorf(`cose/go/key/aesmac: CheckKey: invalid key type, expected "Symmetric", got %q`, k.Kty().String())
}
for p := range k {
switch p {
case key.ParamKty, key.ParamKid, key.ParamK:
// continue
case key.ParamAlg: // optional
switch k.Alg() {
case key.AlgAESMAC12864, key.AlgAESMAC25664, key.AlgAESMAC128128, key.AlgAESMAC256128:
// continue
default:
return fmt.Errorf(`cose/go/key/aesmac: CheckKey: algorithm mismatch %q`, k.Alg().String())
}
case key.ParamOps: // optional
for _, op := range k.Ops() {
switch op {
case key.OpMACCreate, key.OpMACVerify:
// continue
default:
return fmt.Errorf(`cose/go/key/aesmac: CheckKey: invalid parameter key_ops %q`, op)
}
}
default:
return fmt.Errorf(`cose/go/key/aesmac: CheckKey: redundant parameter %q`, k.ParamString(p))
}
}
// REQUIRED
kb, err := k.GetBytes(key.ParamK)
if err != nil {
return fmt.Errorf(`cose/go/key/aesmac: CheckKey: invalid parameter k, %v`, err)
}
keyLen, _ := getKeyLen(k.Alg())
if len(kb) != keyLen {
return fmt.Errorf(`cose/go/key/aesmac: CheckKey: invalid parameter k`)
}
return nil
}
type aesMAC struct {
key key.Key
block cipher.Block
tagSize int
}
// NewAESMAC creates a key.MACer for the given AES-CBC-MAC key.
func NewAESMAC(k key.Key) (key.MACer, error) {
if err := CheckKey(k); err != nil {
return nil, err
}
cek, _ := k.GetBytes(key.ParamK)
_, tagSize := getKeyLen(k.Alg())
block, err := aes.NewCipher(cek)
if err != nil {
return nil, err
}
return &aesMAC{key: k, tagSize: tagSize, block: block}, nil
}
// MACCreate implements the key.MACer interface.
// MACCreate computes message authentication code (MAC) for the given data.
func (h *aesMAC) MACCreate(data []byte) ([]byte, error) {
if !h.key.Ops().EmptyOrHas(key.OpMACCreate) {
return nil, fmt.Errorf("cose/go/key/aesmac: MACCreate: invalid key_ops")
}
return h.create(data)
}
// MACVerify implements the key.MACer interface.
// MACVerify verifies whether the given MAC is a correct message authentication code (MAC) the given data.
func (h *aesMAC) MACVerify(data, mac []byte) error {
if !h.key.Ops().EmptyOrHas(key.OpMACVerify) {
return fmt.Errorf("cose/go/key/aesmac: MACCreate: invalid key_ops")
}
expectedMAC, err := h.create(data)
if err != nil {
return err
}
if subtle.ConstantTimeCompare(expectedMAC, mac) == 1 {
return nil
}
return fmt.Errorf("cose/go/key/aesmac: VerifyMAC: invalid MAC")
}
// the IV is fixed to all zeros
// Reference https://datatracker.ietf.org/doc/html/rfc9053#section-3.2
var fixedIV = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
func (h *aesMAC) create(plaintext []byte) ([]byte, error) {
x := len(plaintext) % aes.BlockSize
if x > 0 {
x = aes.BlockSize - x
}
ciphertext := append(make([]byte, 0, len(plaintext)+x), plaintext...)
mode := cipher.NewCBCEncrypter(h.block, fixedIV)
mode.CryptBlocks(ciphertext, ciphertext)
tag := make([]byte, h.tagSize)
copy(tag, ciphertext[len(ciphertext)-aes.BlockSize:]) // last block message
return tag, nil
}
// Key implements the key.MACer interface.
// Key returns the key in MACer.
func (h *aesMAC) Key() key.Key {
return h.key
}
func getKeyLen(alg key.Alg) (keyLen, tagSize int) {
switch alg {
case key.AlgAESMAC12864, key.AlgReserved:
return 16, 8
case key.AlgAESMAC25664:
return 32, 8
case key.AlgAESMAC128128:
return 16, 16
case key.AlgAESMAC256128:
return 32, 16
default:
return 0, 0
}
}