1package s3crypto
2
3import (
4	"fmt"
5
6	"github.com/aws/aws-sdk-go/aws"
7	"github.com/aws/aws-sdk-go/aws/awserr"
8	"github.com/aws/aws-sdk-go/service/kms"
9	"github.com/aws/aws-sdk-go/service/kms/kmsiface"
10)
11
12const (
13	// KMSWrap is a constant used during decryption to build a KMS key handler.
14	KMSWrap = "kms"
15
16	// KMSContextWrap is a constant used during decryption to build a kms+context key handler
17	KMSContextWrap = "kms+context"
18)
19
20// kmsKeyHandler will make calls to KMS to get the masterkey
21type kmsKeyHandler struct {
22	kms         kmsiface.KMSAPI
23	cmkID       *string
24	withContext bool
25
26	CipherData
27}
28
29// NewKMSKeyGenerator builds a new KMS key provider using the customer key ID and material
30// description.
31//
32// Example:
33//	sess := session.New(&aws.Config{})
34//	cmkID := "arn to key"
35//	matdesc := s3crypto.MaterialDescription{}
36//	handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
37//
38// deprecated: See NewKMSContextKeyGenerator
39func NewKMSKeyGenerator(kmsClient kmsiface.KMSAPI, cmkID string) CipherDataGenerator {
40	return NewKMSKeyGeneratorWithMatDesc(kmsClient, cmkID, MaterialDescription{})
41}
42
43// NewKMSContextKeyGenerator builds a new kms+context key provider using the customer key ID and material
44// description.
45//
46// Example:
47//	sess := session.New(&aws.Config{})
48//	cmkID := "arn to key"
49//	matdesc := s3crypto.MaterialDescription{}
50//	handler := s3crypto.NewKMSContextKeyGenerator(kms.New(sess), cmkID)
51func NewKMSContextKeyGenerator(client kmsiface.KMSAPI, cmkID string) CipherDataGeneratorWithCEKAlg {
52	return NewKMSContextKeyGeneratorWithMatDesc(client, cmkID, MaterialDescription{})
53}
54
55func newKMSKeyHandler(client kmsiface.KMSAPI, cmkID string, withContext bool, matdesc MaterialDescription) *kmsKeyHandler {
56	// These values are read only making them thread safe
57	kp := &kmsKeyHandler{
58		kms:         client,
59		cmkID:       &cmkID,
60		withContext: withContext,
61	}
62
63	if matdesc == nil {
64		matdesc = MaterialDescription{}
65	}
66
67	// These values are read only making them thread safe
68	if kp.withContext {
69		kp.CipherData.WrapAlgorithm = KMSContextWrap
70	} else {
71		matdesc["kms_cmk_id"] = &cmkID
72		kp.CipherData.WrapAlgorithm = KMSWrap
73	}
74	kp.CipherData.MaterialDescription = matdesc
75	return kp
76}
77
78// NewKMSKeyGeneratorWithMatDesc builds a new KMS key provider using the customer key ID and material
79// description.
80//
81// Example:
82//	sess := session.New(&aws.Config{})
83//	cmkID := "arn to key"
84//	matdesc := s3crypto.MaterialDescription{}
85//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
86//
87// deprecated: See NewKMSContextKeyGeneratorWithMatDesc
88func NewKMSKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGenerator {
89	return newKMSKeyHandler(kmsClient, cmkID, false, matdesc)
90}
91
92// NewKMSContextKeyGeneratorWithMatDesc builds a new kms+context key provider using the customer key ID and material
93// description.
94//
95// Example:
96//	sess := session.New(&aws.Config{})
97//	cmkID := "arn to key"
98//	matdesc := s3crypto.MaterialDescription{}
99//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
100func NewKMSContextKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGeneratorWithCEKAlg {
101	return newKMSKeyHandler(kmsClient, cmkID, true, matdesc)
102}
103
104// NewKMSWrapEntry builds returns a new KMS key provider and its decrypt handler.
105//
106// Example:
107//	sess := session.New(&aws.Config{})
108//	customKMSClient := kms.New(sess)
109//	decryptHandler := s3crypto.NewKMSWrapEntry(customKMSClient)
110//
111//	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient) {
112//		svc.WrapRegistry[s3crypto.KMSWrap] = decryptHandler
113//	}))
114//
115// deprecated: See NewKMSContextWrapEntry
116func NewKMSWrapEntry(kmsClient kmsiface.KMSAPI) WrapEntry {
117	// These values are read only making them thread safe
118	kp := &kmsKeyHandler{
119		kms: kmsClient,
120	}
121
122	return kp.decryptHandler
123}
124
125// NewKMSContextWrapEntry builds returns a new KMS key provider and its decrypt handler.
126//
127// Example:
128//	sess := session.New(&aws.Config{})
129//	customKMSClient := kms.New(sess)
130//	decryptHandler := s3crypto.NewKMSContextWrapEntry(customKMSClient)
131//
132//	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient) {
133//		svc.WrapRegistry[s3crypto.KMSContextWrap] = decryptHandler
134//	}))
135func NewKMSContextWrapEntry(kmsClient kmsiface.KMSAPI) WrapEntry {
136	// These values are read only making them thread safe
137	kp := &kmsKeyHandler{
138		kms:         kmsClient,
139		withContext: true,
140	}
141
142	return kp.decryptHandler
143}
144
145// decryptHandler initializes a KMS keyprovider with a material description. This
146// is used with Decrypting kms content, due to the cmkID being in the material description.
147func (kp kmsKeyHandler) decryptHandler(env Envelope) (CipherDataDecrypter, error) {
148	m := MaterialDescription{}
149	err := m.decodeDescription([]byte(env.MatDesc))
150	if err != nil {
151		return nil, err
152	}
153
154	cmkID, ok := m["kms_cmk_id"]
155	if !kp.withContext && !ok {
156		return nil, awserr.New("MissingCMKIDError", "Material description is missing CMK ID", nil)
157	}
158
159	kp.CipherData.MaterialDescription = m
160	kp.cmkID = cmkID
161	kp.WrapAlgorithm = KMSWrap
162	if kp.withContext {
163		kp.WrapAlgorithm = KMSContextWrap
164	}
165	return &kp, nil
166}
167
168// DecryptKey makes a call to KMS to decrypt the key.
169func (kp *kmsKeyHandler) DecryptKey(key []byte) ([]byte, error) {
170	return kp.DecryptKeyWithContext(aws.BackgroundContext(), key)
171}
172
173// DecryptKeyWithContext makes a call to KMS to decrypt the key with request context.
174func (kp *kmsKeyHandler) DecryptKeyWithContext(ctx aws.Context, key []byte) ([]byte, error) {
175	out, err := kp.kms.DecryptWithContext(ctx,
176		&kms.DecryptInput{
177			EncryptionContext: kp.CipherData.MaterialDescription,
178			CiphertextBlob:    key,
179			GrantTokens:       []*string{},
180		})
181	if err != nil {
182		return nil, err
183	}
184	return out.Plaintext, nil
185}
186
187// GenerateCipherData makes a call to KMS to generate a data key, Upon making
188// the call, it also sets the encrypted key.
189func (kp *kmsKeyHandler) GenerateCipherData(keySize, ivSize int) (CipherData, error) {
190	return kp.GenerateCipherDataWithContext(aws.BackgroundContext(), keySize, ivSize)
191}
192
193func (kp kmsKeyHandler) GenerateCipherDataWithCEKAlg(keySize, ivSize int, cekAlgorithm string) (CipherData, error) {
194	return kp.GenerateCipherDataWithCEKAlgWithContext(aws.BackgroundContext(), keySize, ivSize, cekAlgorithm)
195}
196
197// GenerateCipherDataWithContext makes a call to KMS to generate a data key,
198// Upon making the call, it also sets the encrypted key.
199func (kp *kmsKeyHandler) GenerateCipherDataWithContext(ctx aws.Context, keySize, ivSize int) (CipherData, error) {
200	return kp.GenerateCipherDataWithCEKAlgWithContext(ctx, keySize, ivSize, "")
201}
202
203func (kp kmsKeyHandler) GenerateCipherDataWithCEKAlgWithContext(ctx aws.Context, keySize int, ivSize int, cekAlgorithm string) (CipherData, error) {
204	md := kp.CipherData.MaterialDescription
205
206	wrapAlgorithm := KMSWrap
207	if kp.withContext {
208		wrapAlgorithm = KMSContextWrap
209		if len(cekAlgorithm) == 0 {
210			return CipherData{}, fmt.Errorf("CEK algorithm identifier must not be empty")
211		}
212		md["aws:"+cekAlgorithmHeader] = &cekAlgorithm
213	}
214
215	out, err := kp.kms.GenerateDataKeyWithContext(ctx,
216		&kms.GenerateDataKeyInput{
217			EncryptionContext: md,
218			KeyId:             kp.cmkID,
219			KeySpec:           aws.String("AES_256"),
220		})
221	if err != nil {
222		return CipherData{}, err
223	}
224
225	iv, err := generateBytes(ivSize)
226	if err != nil {
227		return CipherData{}, err
228	}
229
230	cd := CipherData{
231		Key:                 out.Plaintext,
232		IV:                  iv,
233		WrapAlgorithm:       wrapAlgorithm,
234		MaterialDescription: md,
235		EncryptedKey:        out.CiphertextBlob,
236	}
237	return cd, nil
238}
239
240func (kp *kmsKeyHandler) isUsingDeprecatedFeatures() error {
241	if !kp.withContext {
242		return errDeprecatedCipherDataGenerator
243	}
244	return nil
245}
246