16
16
import { KmsClientSupplier } from './kms_client_supplier' // eslint-disable-line no-unused-vars
17
17
import {
18
18
needs ,
19
- Keyring ,
19
+ Keyring , // eslint-disable-line no-unused-vars
20
20
EncryptionMaterial , // eslint-disable-line no-unused-vars
21
21
DecryptionMaterial , // eslint-disable-line no-unused-vars
22
22
SupportedAlgorithmSuites , // eslint-disable-line no-unused-vars
@@ -34,123 +34,147 @@ import { regionFromKmsKeyArn } from './region_from_kms_key_arn'
34
34
35
35
export interface KmsKeyringInput < Client extends KMS > {
36
36
clientProvider : KmsClientSupplier < Client >
37
- kmsKeys ?: string [ ]
38
- generatorKmsKey ?: string
37
+ keyIds ?: string [ ]
38
+ generatorKeyId ?: string
39
39
grantTokens ?: string
40
40
}
41
41
42
- export abstract class KmsKeyring < S extends SupportedAlgorithmSuites , Client extends KMS > extends Keyring < S > {
43
- public kmsKeys ! : string [ ]
44
- public generatorKmsKey ?: string
45
- public clientProvider ! : KmsClientSupplier < Client >
46
- public grantTokens ?: string
47
-
48
- constructor ( { clientProvider, generatorKmsKey, kmsKeys = [ ] , grantTokens } : KmsKeyringInput < Client > ) {
49
- super ( )
50
- /* Precondition: All KMS key arns must be valid. */
51
- needs ( ! generatorKmsKey || ! ! regionFromKmsKeyArn ( generatorKmsKey ) , 'Malformed arn.' )
52
- needs ( kmsKeys . every ( keyarn => ! ! regionFromKmsKeyArn ( keyarn ) ) , 'Malformed arn.' )
53
- /* Precondition: clientProvider needs to be a callable function. */
54
- needs ( typeof clientProvider === 'function' , '' )
55
-
56
- readOnlyProperty ( this , 'clientProvider' , clientProvider )
57
- readOnlyProperty ( this , 'kmsKeys' , kmsKeys )
58
- readOnlyProperty ( this , 'generatorKmsKey' , generatorKmsKey )
59
- readOnlyProperty ( this , 'grantTokens' , grantTokens )
60
- }
61
-
62
- /* Keyrings *must* preserve the order of EDK's. The generatorKmsKey is the first on this list. */
63
- async _onEncrypt ( material : EncryptionMaterial < S > , context ?: EncryptionContext ) {
64
- const kmsKeys = this . kmsKeys . slice ( )
65
- const { clientProvider, generatorKmsKey, grantTokens } = this
66
- if ( generatorKmsKey && ! material . hasUnencryptedDataKey ) {
67
- const dataKey = await generateDataKey ( clientProvider , material . suite . keyLengthBytes , generatorKmsKey , context , grantTokens )
68
- /* Precondition: A generatorKmsKey must generate if we do not have an unencrypted data key.
69
- * Client supplier is allowed to return undefined if, for example, user wants to exclude particular
70
- * regions. But if we are here it means that user configured keyring with a KMS key that was
71
- * incompatible with the client supplier in use.
72
- */
73
- if ( ! dataKey ) throw new Error ( 'Generator KMS key did not generate a data key' )
74
-
75
- const flags = KeyringTraceFlag . WRAPPING_KEY_GENERATED_DATA_KEY |
76
- KeyringTraceFlag . WRAPPING_KEY_SIGNED_ENC_CTX |
77
- KeyringTraceFlag . WRAPPING_KEY_ENCRYPTED_DATA_KEY
78
- const trace : KeyringTrace = { keyNamespace : KMS_PROVIDER_ID , keyName : dataKey . KeyId , flags }
79
-
80
- material
81
- /* Postcondition: The unencryptedDataKey length must match the algorithm specification.
82
- * See cryptographic_materials as setUnencryptedDataKey will throw in this case.
83
- */
84
- . setUnencryptedDataKey ( dataKey . Plaintext , trace )
85
- . addEncryptedDataKey ( kms2EncryptedDataKey ( dataKey ) , KeyringTraceFlag . WRAPPING_KEY_ENCRYPTED_DATA_KEY )
86
- } else if ( generatorKmsKey ) {
87
- kmsKeys . unshift ( generatorKmsKey )
88
- }
42
+ export interface KeyRing < S extends SupportedAlgorithmSuites , Client extends KMS > extends Keyring < S > {
43
+ keyIds : string [ ]
44
+ generatorKeyId ?: string
45
+ clientProvider : KmsClientSupplier < Client >
46
+ grantTokens ?: string
47
+ _onEncrypt ( material : EncryptionMaterial < S > , context ?: EncryptionContext ) : Promise < EncryptionMaterial < S > >
48
+ _onDecrypt ( material : DecryptionMaterial < S > , encryptedDataKeys : EncryptedDataKey [ ] , context ?: EncryptionContext ) : Promise < DecryptionMaterial < S > >
49
+ }
89
50
90
- /* Precondition: If a generator does not exist, an unencryptedDataKey *must* already exist.
91
- * Furthermore *only* CMK's explicitly designated as generators can generate data keys.
92
- * See cryptographic_materials as getUnencryptedDataKey will throw in this case.
93
- */
94
- const unencryptedDataKey = material . getUnencryptedDataKey ( )
51
+ export interface KmsKeyRingConstructible < S extends SupportedAlgorithmSuites , Client extends KMS > {
52
+ new ( input : KmsKeyringInput < Client > ) : KeyRing < S , Client >
53
+ }
95
54
96
- const flags = KeyringTraceFlag . WRAPPING_KEY_ENCRYPTED_DATA_KEY | KeyringTraceFlag . WRAPPING_KEY_SIGNED_ENC_CTX
97
- for ( const kmsKey of kmsKeys ) {
98
- const kmsEDK = await encrypt ( clientProvider , unencryptedDataKey , kmsKey , context , grantTokens )
55
+ export interface KeyRingConstructible < S extends SupportedAlgorithmSuites > {
56
+ new ( ) : Keyring < S >
57
+ }
99
58
100
- /* clientProvider may not return a client, in this case there is not an EDK to add */
101
- if ( kmsEDK ) material . addEncryptedDataKey ( kms2EncryptedDataKey ( kmsEDK ) , flags )
59
+ export function KmsKeyringClass < S extends SupportedAlgorithmSuites , Client extends KMS > (
60
+ BaseKeyring : KeyRingConstructible < S >
61
+ ) : KmsKeyRingConstructible < S , Client > {
62
+ class KmsKeyring extends BaseKeyring implements KeyRing < S , Client > {
63
+ public keyIds ! : string [ ]
64
+ public generatorKeyId ?: string
65
+ public clientProvider ! : KmsClientSupplier < Client >
66
+ public grantTokens ?: string
67
+
68
+ constructor ( { clientProvider, generatorKeyId, keyIds = [ ] , grantTokens } : KmsKeyringInput < Client > ) {
69
+ super ( )
70
+ /* Precondition: This is an abstract class. (But TypeScript does not have a clean way to model this) */
71
+ needs ( this . constructor !== KmsKeyring , 'new KmsKeyring is not allowed' )
72
+ /* Precondition: All KMS key arns must be valid. */
73
+ needs ( ! generatorKeyId || ! ! regionFromKmsKeyArn ( generatorKeyId ) , 'Malformed arn.' )
74
+ needs ( keyIds . every ( keyarn => ! ! regionFromKmsKeyArn ( keyarn ) ) , 'Malformed arn.' )
75
+ /* Precondition: clientProvider needs to be a callable function. */
76
+ needs ( typeof clientProvider === 'function' , '' )
77
+
78
+ readOnlyProperty ( this , 'clientProvider' , clientProvider )
79
+ readOnlyProperty ( this , 'keyIds' , keyIds )
80
+ readOnlyProperty ( this , 'generatorKeyId' , generatorKeyId )
81
+ readOnlyProperty ( this , 'grantTokens' , grantTokens )
102
82
}
103
83
104
- return material
105
- }
106
-
107
- async _onDecrypt ( material : DecryptionMaterial < S > , encryptedDataKeys : EncryptedDataKey [ ] , context ?: EncryptionContext ) {
108
- const kmsKeys = this . kmsKeys . slice ( )
109
- const { clientProvider, generatorKmsKey, grantTokens } = this
110
- if ( generatorKmsKey ) kmsKeys . unshift ( generatorKmsKey )
111
-
112
- /* If there are no key IDs in the list, keyring is in "discovery" mode and will attempt KMS calls with
113
- * every ARN it comes across in the message. If there are key IDs in the list, it will cross check the
114
- * ARN it reads with that list before attempting KMS calls. Note that if caller provided key IDs in
115
- * anything other than a CMK ARN format, the SDK will not attempt to decrypt those data keys, because
116
- * the EDK data format always specifies the CMK with the full (non-alias) ARN.
117
- */
118
- const decryptableEDKs = encryptedDataKeys
119
- . filter ( ( { providerId, providerInfo } ) => {
120
- if ( providerId !== KMS_PROVIDER_ID ) return false
121
- return kmsKeys . length
122
- ? kmsKeys . includes ( providerInfo )
123
- : true
124
- } )
125
-
126
- for ( const edk of decryptableEDKs ) {
127
- let dataKey : Required < DecryptOutput > | false = false
128
- try {
129
- dataKey = await decrypt ( clientProvider , edk , context , grantTokens )
130
- } catch ( e ) {
131
- // there should be some debug here? or wrap?
132
- // Failures decrypt should not short-circuit the process
133
- // If the caller does not have access they may have access
134
- // through another Keyring.
84
+ /* Keyrings *must* preserve the order of EDK's. The generatorKeyId is the first on this list. */
85
+ async _onEncrypt ( material : EncryptionMaterial < S > , context ?: EncryptionContext ) {
86
+ const keyIds = this . keyIds . slice ( )
87
+ const { clientProvider, generatorKeyId, grantTokens } = this
88
+ if ( generatorKeyId && ! material . hasUnencryptedDataKey ) {
89
+ const dataKey = await generateDataKey ( clientProvider , material . suite . keyLengthBytes , generatorKeyId , context , grantTokens )
90
+ /* Precondition: A generatorKeyId must generate if we do not have an unencrypted data key.
91
+ * Client supplier is allowed to return undefined if, for example, user wants to exclude particular
92
+ * regions. But if we are here it means that user configured keyring with a KMS key that was
93
+ * incompatible with the client supplier in use.
94
+ */
95
+ if ( ! dataKey ) throw new Error ( 'Generator KMS key did not generate a data key' )
96
+
97
+ const flags = KeyringTraceFlag . WRAPPING_KEY_GENERATED_DATA_KEY |
98
+ KeyringTraceFlag . WRAPPING_KEY_SIGNED_ENC_CTX |
99
+ KeyringTraceFlag . WRAPPING_KEY_ENCRYPTED_DATA_KEY
100
+ const trace : KeyringTrace = { keyNamespace : KMS_PROVIDER_ID , keyName : dataKey . KeyId , flags }
101
+
102
+ material
103
+ /* Postcondition: The unencryptedDataKey length must match the algorithm specification.
104
+ * See cryptographic_materials as setUnencryptedDataKey will throw in this case.
105
+ */
106
+ . setUnencryptedDataKey ( dataKey . Plaintext , trace )
107
+ . addEncryptedDataKey ( kms2EncryptedDataKey ( dataKey ) , KeyringTraceFlag . WRAPPING_KEY_ENCRYPTED_DATA_KEY )
108
+ } else if ( generatorKeyId ) {
109
+ keyIds . unshift ( generatorKeyId )
135
110
}
136
111
137
- /* Check for early return (Postcondition): clientProvider may not return a client. */
138
- if ( ! dataKey ) continue
112
+ /* Precondition: If a generator does not exist, an unencryptedDataKey *must* already exist.
113
+ * Furthermore *only* CMK's explicitly designated as generators can generate data keys.
114
+ * See cryptographic_materials as getUnencryptedDataKey will throw in this case.
115
+ */
116
+ const unencryptedDataKey = material . getUnencryptedDataKey ( )
139
117
140
- /* Postcondition: The KeyId from KMS must match the encoded KeyID. */
141
- needs ( dataKey . KeyId === edk . providerInfo , 'KMS Decryption key does not match serialized provider.' )
118
+ const flags = KeyringTraceFlag . WRAPPING_KEY_ENCRYPTED_DATA_KEY | KeyringTraceFlag . WRAPPING_KEY_SIGNED_ENC_CTX
119
+ for ( const kmsKey of keyIds ) {
120
+ const kmsEDK = await encrypt ( clientProvider , unencryptedDataKey , kmsKey , context , grantTokens )
142
121
143
- const flags = KeyringTraceFlag . WRAPPING_KEY_DECRYPTED_DATA_KEY | KeyringTraceFlag . WRAPPING_KEY_VERIFIED_ENC_CTX
144
- const trace : KeyringTrace = { keyNamespace : KMS_PROVIDER_ID , keyName : dataKey . KeyId , flags }
122
+ /* clientProvider may not return a client, in this case there is not an EDK to add */
123
+ if ( kmsEDK ) material . addEncryptedDataKey ( kms2EncryptedDataKey ( kmsEDK ) , flags )
124
+ }
145
125
146
- /* Postcondition: The unencryptedDataKey length must match the algorithm specification.
147
- * See cryptographic_materials as setUnencryptedDataKey will throw in this case.
148
- */
149
- material . setUnencryptedDataKey ( dataKey . Plaintext , trace )
150
126
return material
151
127
}
152
128
153
- return material
129
+ async _onDecrypt ( material : DecryptionMaterial < S > , encryptedDataKeys : EncryptedDataKey [ ] , context ?: EncryptionContext ) {
130
+ const keyIds = this . keyIds . slice ( )
131
+ const { clientProvider, generatorKeyId, grantTokens } = this
132
+ if ( generatorKeyId ) keyIds . unshift ( generatorKeyId )
133
+
134
+ /* If there are no key IDs in the list, keyring is in "discovery" mode and will attempt KMS calls with
135
+ * every ARN it comes across in the message. If there are key IDs in the list, it will cross check the
136
+ * ARN it reads with that list before attempting KMS calls. Note that if caller provided key IDs in
137
+ * anything other than a CMK ARN format, the SDK will not attempt to decrypt those data keys, because
138
+ * the EDK data format always specifies the CMK with the full (non-alias) ARN.
139
+ */
140
+ const decryptableEDKs = encryptedDataKeys
141
+ . filter ( ( { providerId, providerInfo } ) => {
142
+ if ( providerId !== KMS_PROVIDER_ID ) return false
143
+ return keyIds . length
144
+ ? keyIds . includes ( providerInfo )
145
+ : true
146
+ } )
147
+
148
+ for ( const edk of decryptableEDKs ) {
149
+ let dataKey : Required < DecryptOutput > | false = false
150
+ try {
151
+ dataKey = await decrypt ( clientProvider , edk , context , grantTokens )
152
+ } catch ( e ) {
153
+ // there should be some debug here? or wrap?
154
+ // Failures decrypt should not short-circuit the process
155
+ // If the caller does not have access they may have access
156
+ // through another Keyring.
157
+ }
158
+
159
+ /* Check for early return (Postcondition): clientProvider may not return a client. */
160
+ if ( ! dataKey ) continue
161
+
162
+ /* Postcondition: The KeyId from KMS must match the encoded KeyID. */
163
+ needs ( dataKey . KeyId === edk . providerInfo , 'KMS Decryption key does not match serialized provider.' )
164
+
165
+ const flags = KeyringTraceFlag . WRAPPING_KEY_DECRYPTED_DATA_KEY | KeyringTraceFlag . WRAPPING_KEY_VERIFIED_ENC_CTX
166
+ const trace : KeyringTrace = { keyNamespace : KMS_PROVIDER_ID , keyName : dataKey . KeyId , flags }
167
+
168
+ /* Postcondition: The unencryptedDataKey length must match the algorithm specification.
169
+ * See cryptographic_materials as setUnencryptedDataKey will throw in this case.
170
+ */
171
+ material . setUnencryptedDataKey ( dataKey . Plaintext , trace )
172
+ return material
173
+ }
174
+
175
+ return material
176
+ }
154
177
}
178
+ immutableClass ( KmsKeyring )
179
+ return KmsKeyring
155
180
}
156
- immutableClass ( KmsKeyring )
0 commit comments