-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
kms_context_key_handler.go
202 lines (169 loc) · 6.69 KB
/
kms_context_key_handler.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
package s3crypto
import (
"fmt"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/kms"
"github.com/aws/aws-sdk-go/service/kms/kmsiface"
)
const (
// KMSContextWrap is a constant used during decryption to build a kms+context key handler
KMSContextWrap = "kms+context"
kmsAWSCEKContextKey = "aws:" + cekAlgorithmHeader
kmsReservedKeyConflictErrMsg = "conflict in reserved KMS Encryption Context key %s. This value is reserved for the S3 Encryption Client and cannot be set by the user"
kmsMismatchCEKAlg = "the content encryption algorithm used at encryption time does not match the algorithm stored for decryption time. The object may be altered or corrupted"
)
// NewKMSContextKeyGenerator builds a new kms+context key provider using the customer key ID and material
// description.
//
// Example:
//
// sess := session.Must(session.NewSession())
// cmkID := "KMS Key ARN"
// var matdesc s3crypto.MaterialDescription
// handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID, matdesc)
func NewKMSContextKeyGenerator(client kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGeneratorWithCEKAlg {
return newKMSContextKeyHandler(client, cmkID, matdesc)
}
// RegisterKMSContextWrapWithCMK registers the kms+context wrapping algorithm to the given WrapRegistry. The wrapper
// will be configured to only call KMS Decrypt using the provided CMK.
//
// Example:
//
// cr := s3crypto.NewCryptoRegistry()
// if err := RegisterKMSContextWrapWithCMK(); err != nil {
// panic(err) // handle error
// }
func RegisterKMSContextWrapWithCMK(registry *CryptoRegistry, client kmsiface.KMSAPI, cmkID string) error {
if registry == nil {
return errNilCryptoRegistry
}
return registry.AddWrap(KMSContextWrap, newKMSContextWrapEntryWithCMK(client, cmkID))
}
// RegisterKMSContextWrapWithAnyCMK registers the kms+context wrapping algorithm to the given WrapRegistry. The wrapper
// will be configured to call KMS decrypt without providing a CMK.
//
// Example:
//
// sess := session.Must(session.NewSession())
// cr := s3crypto.NewCryptoRegistry()
// if err := s3crypto.RegisterKMSContextWrapWithAnyCMK(cr, kms.New(sess)); err != nil {
// panic(err) // handle error
// }
func RegisterKMSContextWrapWithAnyCMK(registry *CryptoRegistry, client kmsiface.KMSAPI) error {
if registry == nil {
return errNilCryptoRegistry
}
return registry.AddWrap(KMSContextWrap, newKMSContextWrapEntryWithAnyCMK(client))
}
// newKMSContextWrapEntryWithCMK builds returns a new kms+context key provider and its decrypt handler.
// The returned handler will be configured to calls KMS Decrypt API without specifying a specific KMS CMK.
func newKMSContextWrapEntryWithCMK(kmsClient kmsiface.KMSAPI, cmkID string) WrapEntry {
// These values are read only making them thread safe
kp := &kmsContextKeyHandler{
kms: kmsClient,
cmkID: &cmkID,
}
return kp.decryptHandler
}
// newKMSContextWrapEntryWithAnyCMK builds returns a new kms+context key provider and its decrypt handler.
// The returned handler will be configured to calls KMS Decrypt API without specifying a specific KMS CMK.
func newKMSContextWrapEntryWithAnyCMK(kmsClient kmsiface.KMSAPI) WrapEntry {
// These values are read only making them thread safe
kp := &kmsContextKeyHandler{
kms: kmsClient,
}
return kp.decryptHandler
}
// kmsContextKeyHandler wraps the kmsKeyHandler to explicitly make this type incompatible with the v1 client
// by not exposing the old interface implementations.
type kmsContextKeyHandler struct {
kms kmsiface.KMSAPI
cmkID *string
CipherData
}
func (kp *kmsContextKeyHandler) isAWSFixture() bool {
return true
}
func newKMSContextKeyHandler(client kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) *kmsContextKeyHandler {
kp := &kmsContextKeyHandler{
kms: client,
cmkID: &cmkID,
}
if matdesc == nil {
matdesc = MaterialDescription{}
}
kp.CipherData.WrapAlgorithm = KMSContextWrap
kp.CipherData.MaterialDescription = matdesc
return kp
}
func (kp *kmsContextKeyHandler) GenerateCipherDataWithCEKAlg(ctx aws.Context, keySize int, ivSize int, cekAlgorithm string) (CipherData, error) {
cd := kp.CipherData.Clone()
if len(cekAlgorithm) == 0 {
return CipherData{}, fmt.Errorf("cek algorithm identifier must not be empty")
}
if _, ok := cd.MaterialDescription[kmsAWSCEKContextKey]; ok {
return CipherData{}, fmt.Errorf(kmsReservedKeyConflictErrMsg, kmsAWSCEKContextKey)
}
cd.MaterialDescription[kmsAWSCEKContextKey] = &cekAlgorithm
out, err := kp.kms.GenerateDataKeyWithContext(ctx,
&kms.GenerateDataKeyInput{
EncryptionContext: cd.MaterialDescription,
KeyId: kp.cmkID,
KeySpec: aws.String("AES_256"),
})
if err != nil {
return CipherData{}, err
}
iv, err := generateBytes(ivSize)
if err != nil {
return CipherData{}, err
}
cd.Key = out.Plaintext
cd.IV = iv
cd.EncryptedKey = out.CiphertextBlob
return cd, nil
}
// decryptHandler initializes a KMS keyprovider with a material description. This
// is used with Decrypting kms content, due to the cmkID being in the material description.
func (kp kmsContextKeyHandler) decryptHandler(env Envelope) (CipherDataDecrypter, error) {
if env.WrapAlg != KMSContextWrap {
return nil, fmt.Errorf("%s value `%s` did not match the expected algorithm `%s` for this handler", cekAlgorithmHeader, env.WrapAlg, KMSContextWrap)
}
m := MaterialDescription{}
err := m.decodeDescription([]byte(env.MatDesc))
if err != nil {
return nil, err
}
if v, ok := m[kmsAWSCEKContextKey]; !ok {
return nil, fmt.Errorf("required key %v is missing from encryption context", kmsAWSCEKContextKey)
} else if v == nil || *v != env.CEKAlg {
return nil, fmt.Errorf(kmsMismatchCEKAlg)
}
kp.MaterialDescription = m
kp.WrapAlgorithm = KMSContextWrap
return &kp, nil
}
// DecryptKey makes a call to KMS to decrypt the key.
func (kp *kmsContextKeyHandler) DecryptKey(key []byte) ([]byte, error) {
return kp.DecryptKeyWithContext(aws.BackgroundContext(), key)
}
// DecryptKeyWithContext makes a call to KMS to decrypt the key with request context.
func (kp *kmsContextKeyHandler) DecryptKeyWithContext(ctx aws.Context, key []byte) ([]byte, error) {
out, err := kp.kms.DecryptWithContext(ctx,
&kms.DecryptInput{
KeyId: kp.cmkID, // will be nil and not serialized if created with the AnyCMK constructor
EncryptionContext: kp.MaterialDescription,
CiphertextBlob: key,
GrantTokens: []*string{},
})
if err != nil {
return nil, err
}
return out.Plaintext, nil
}
var (
_ CipherDataGeneratorWithCEKAlg = (*kmsContextKeyHandler)(nil)
_ CipherDataDecrypter = (*kmsContextKeyHandler)(nil)
_ CipherDataDecrypterWithContext = (*kmsContextKeyHandler)(nil)
_ awsFixture = (*kmsContextKeyHandler)(nil)
)