1package s3crypto
2
3import (
4	"github.com/aws/aws-sdk-go/aws"
5	"github.com/aws/aws-sdk-go/aws/awserr"
6	"github.com/aws/aws-sdk-go/service/kms"
7	"github.com/aws/aws-sdk-go/service/kms/kmsiface"
8)
9
10const (
11	// KMSWrap is a constant used during decryption to build a KMS key handler.
12	KMSWrap = "kms"
13)
14
15// kmsKeyHandler will make calls to KMS to get the masterkey
16type kmsKeyHandler struct {
17	kms   kmsiface.KMSAPI
18	cmkID *string
19
20	// useProvidedCMK is toggled when using `kms` key wrapper with V2 client
21	useProvidedCMK bool
22
23	CipherData
24}
25
26// NewKMSKeyGenerator builds a new KMS key provider using the customer key ID and material
27// description.
28//
29// Example:
30//	sess := session.Must(session.NewSession())
31//	cmkID := "arn to key"
32//	matdesc := s3crypto.MaterialDescription{}
33//	handler := s3crypto.NewKMSKeyGenerator(kms.New(sess), cmkID)
34//
35// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
36func NewKMSKeyGenerator(kmsClient kmsiface.KMSAPI, cmkID string) CipherDataGenerator {
37	return NewKMSKeyGeneratorWithMatDesc(kmsClient, cmkID, MaterialDescription{})
38}
39
40func newKMSKeyHandler(client kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) *kmsKeyHandler {
41	// These values are read only making them thread safe
42	kp := &kmsKeyHandler{
43		kms:   client,
44		cmkID: &cmkID,
45	}
46
47	if matdesc == nil {
48		matdesc = MaterialDescription{}
49	}
50
51	matdesc["kms_cmk_id"] = &cmkID
52
53	kp.CipherData.WrapAlgorithm = KMSWrap
54	kp.CipherData.MaterialDescription = matdesc
55
56	return kp
57}
58
59// NewKMSKeyGeneratorWithMatDesc builds a new KMS key provider using the customer key ID and material
60// description.
61//
62// Example:
63//	sess := session.Must(session.NewSession())
64//	cmkID := "arn to key"
65//	matdesc := s3crypto.MaterialDescription{}
66//	handler := s3crypto.NewKMSKeyGeneratorWithMatDesc(kms.New(sess), cmkID, matdesc)
67//
68// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
69func NewKMSKeyGeneratorWithMatDesc(kmsClient kmsiface.KMSAPI, cmkID string, matdesc MaterialDescription) CipherDataGenerator {
70	return newKMSKeyHandler(kmsClient, cmkID, matdesc)
71}
72
73// NewKMSWrapEntry builds returns a new KMS key provider and its decrypt handler.
74//
75// Example:
76//	sess := session.Must(session.NewSession())
77//	customKMSClient := kms.New(sess)
78//	decryptHandler := s3crypto.NewKMSWrapEntry(customKMSClient)
79//
80//	svc := s3crypto.NewDecryptionClient(sess, func(svc *s3crypto.DecryptionClient) {
81//		svc.WrapRegistry[s3crypto.KMSWrap] = decryptHandler
82//	}))
83//
84// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
85func NewKMSWrapEntry(kmsClient kmsiface.KMSAPI) WrapEntry {
86	kp := newKMSWrapEntry(kmsClient)
87	return kp.decryptHandler
88}
89
90// RegisterKMSWrapWithCMK registers the `kms` wrapping algorithm to the given WrapRegistry. The wrapper will be
91// configured to call KMS Decrypt with the provided CMK.
92//
93// Example:
94//	sess := session.Must(session.NewSession())
95//	cr := s3crypto.NewCryptoRegistry()
96//	if err := s3crypto.RegisterKMSWrapWithCMK(cr, kms.New(sess), "cmkId"); err != nil {
97//		panic(err) // handle error
98//	}
99//
100// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
101func RegisterKMSWrapWithCMK(registry *CryptoRegistry, client kmsiface.KMSAPI, cmkID string) error {
102	if registry == nil {
103		return errNilCryptoRegistry
104	}
105	return registry.AddWrap(KMSWrap, newKMSWrapEntryWithCMK(client, cmkID))
106}
107
108// RegisterKMSWrapWithAnyCMK registers the `kms` wrapping algorithm to the given WrapRegistry. The wrapper will be
109// configured to call KMS Decrypt without providing a CMK.
110//
111// Example:
112//	sess := session.Must(session.NewSession())
113//	cr := s3crypto.NewCryptoRegistry()
114//	if err := s3crypto.RegisterKMSWrapWithAnyCMK(cr, kms.New(sess)); err != nil {
115//		panic(err) // handle error
116//	}
117//
118// deprecated: This feature is in maintenance mode, no new updates will be released. Please see https://docs.aws.amazon.com/general/latest/gr/aws_sdk_cryptography.html for more information.
119func RegisterKMSWrapWithAnyCMK(registry *CryptoRegistry, client kmsiface.KMSAPI) error {
120	if registry == nil {
121		return errNilCryptoRegistry
122	}
123	return registry.AddWrap(KMSWrap, NewKMSWrapEntry(client))
124}
125
126// newKMSWrapEntryWithCMK builds returns a new KMS key provider and its decrypt handler. The wrap entry will be configured
127// to only attempt to decrypt the data key using the provided CMK.
128func newKMSWrapEntryWithCMK(kmsClient kmsiface.KMSAPI, cmkID string) WrapEntry {
129	kp := newKMSWrapEntry(kmsClient)
130	kp.useProvidedCMK = true
131	kp.cmkID = &cmkID
132	return kp.decryptHandler
133}
134
135func newKMSWrapEntry(kmsClient kmsiface.KMSAPI) *kmsKeyHandler {
136	// These values are read only making them thread safe
137	kp := &kmsKeyHandler{
138		kms: kmsClient,
139	}
140
141	return kp
142}
143
144// decryptHandler initializes a KMS keyprovider with a material description. This
145// is used with Decrypting kms content, due to the cmkID being in the material description.
146func (kp kmsKeyHandler) decryptHandler(env Envelope) (CipherDataDecrypter, error) {
147	m := MaterialDescription{}
148	err := m.decodeDescription([]byte(env.MatDesc))
149	if err != nil {
150		return nil, err
151	}
152
153	_, ok := m["kms_cmk_id"]
154	if !ok {
155		return nil, awserr.New("MissingCMKIDError", "Material description is missing CMK ID", nil)
156	}
157
158	kp.CipherData.MaterialDescription = m
159	kp.WrapAlgorithm = KMSWrap
160
161	return &kp, nil
162}
163
164// DecryptKey makes a call to KMS to decrypt the key.
165func (kp *kmsKeyHandler) DecryptKey(key []byte) ([]byte, error) {
166	return kp.DecryptKeyWithContext(aws.BackgroundContext(), key)
167}
168
169// DecryptKeyWithContext makes a call to KMS to decrypt the key with request context.
170func (kp *kmsKeyHandler) DecryptKeyWithContext(ctx aws.Context, key []byte) ([]byte, error) {
171	in := &kms.DecryptInput{
172		EncryptionContext: kp.MaterialDescription,
173		CiphertextBlob:    key,
174		GrantTokens:       []*string{},
175	}
176
177	// useProvidedCMK will be true if a constructor was used with the new V2 client
178	if kp.useProvidedCMK {
179		in.KeyId = kp.cmkID
180	}
181
182	out, err := kp.kms.DecryptWithContext(ctx, in)
183	if err != nil {
184		return nil, err
185	}
186	return out.Plaintext, nil
187}
188
189// GenerateCipherData makes a call to KMS to generate a data key, Upon making
190// the call, it also sets the encrypted key.
191func (kp *kmsKeyHandler) GenerateCipherData(keySize, ivSize int) (CipherData, error) {
192	return kp.GenerateCipherDataWithContext(aws.BackgroundContext(), keySize, ivSize)
193}
194
195// GenerateCipherDataWithContext makes a call to KMS to generate a data key,
196// Upon making the call, it also sets the encrypted key.
197func (kp *kmsKeyHandler) GenerateCipherDataWithContext(ctx aws.Context, keySize, ivSize int) (CipherData, error) {
198	cd := kp.CipherData.Clone()
199
200	out, err := kp.kms.GenerateDataKeyWithContext(ctx,
201		&kms.GenerateDataKeyInput{
202			EncryptionContext: cd.MaterialDescription,
203			KeyId:             kp.cmkID,
204			KeySpec:           aws.String("AES_256"),
205		})
206	if err != nil {
207		return CipherData{}, err
208	}
209
210	iv, err := generateBytes(ivSize)
211	if err != nil {
212		return CipherData{}, err
213	}
214
215	cd.Key = out.Plaintext
216	cd.IV = iv
217	cd.EncryptedKey = out.CiphertextBlob
218
219	return cd, nil
220}
221
222func (kp kmsKeyHandler) isAWSFixture() bool {
223	return true
224}
225
226var (
227	_ CipherDataGenerator            = (*kmsKeyHandler)(nil)
228	_ CipherDataGeneratorWithContext = (*kmsKeyHandler)(nil)
229	_ CipherDataDecrypter            = (*kmsKeyHandler)(nil)
230	_ CipherDataDecrypterWithContext = (*kmsKeyHandler)(nil)
231	_ awsFixture                     = (*kmsKeyHandler)(nil)
232)
233