@webex/internal-plugin-encryption 2.59.3-next.1 → 2.59.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.eslintrc.js +6 -6
  2. package/README.md +42 -42
  3. package/babel.config.js +3 -3
  4. package/dist/config.js +21 -21
  5. package/dist/config.js.map +1 -1
  6. package/dist/encryption.js +57 -57
  7. package/dist/encryption.js.map +1 -1
  8. package/dist/ensure-buffer.browser.js +7 -7
  9. package/dist/ensure-buffer.browser.js.map +1 -1
  10. package/dist/ensure-buffer.js +7 -7
  11. package/dist/ensure-buffer.js.map +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/kms-batcher.js +38 -38
  15. package/dist/kms-batcher.js.map +1 -1
  16. package/dist/kms-certificate-validation.js +50 -50
  17. package/dist/kms-certificate-validation.js.map +1 -1
  18. package/dist/kms-dry-error-interceptor.js +15 -15
  19. package/dist/kms-dry-error-interceptor.js.map +1 -1
  20. package/dist/kms-errors.js +16 -16
  21. package/dist/kms-errors.js.map +1 -1
  22. package/dist/kms.js +171 -171
  23. package/dist/kms.js.map +1 -1
  24. package/jest.config.js +3 -3
  25. package/package.json +19 -20
  26. package/process +1 -1
  27. package/src/config.js +50 -50
  28. package/src/encryption.js +257 -257
  29. package/src/ensure-buffer.browser.js +37 -37
  30. package/src/ensure-buffer.js +20 -20
  31. package/src/index.js +159 -159
  32. package/src/kms-batcher.js +158 -158
  33. package/src/kms-certificate-validation.js +232 -232
  34. package/src/kms-dry-error-interceptor.js +65 -65
  35. package/src/kms-errors.js +147 -147
  36. package/src/kms.js +848 -848
  37. package/test/integration/spec/encryption.js +448 -448
  38. package/test/integration/spec/kms.js +800 -800
  39. package/test/integration/spec/payload-transfom.js +97 -97
  40. package/test/unit/spec/encryption.js +82 -82
  41. package/test/unit/spec/kms-certificate-validation.js +165 -165
  42. package/test/unit/spec/kms.js +103 -103
@@ -1,232 +1,232 @@
1
- import {parse as parseUrl} from 'url';
2
-
3
- import {isUri} from 'valid-url';
4
- import {fromBER} from 'asn1js';
5
- import {
6
- Certificate,
7
- RSAPublicKey,
8
- CertificateChainValidationEngine,
9
- CryptoEngine,
10
- setEngine,
11
- } from 'pkijs';
12
- import {isArray} from 'lodash';
13
- import jose from 'node-jose';
14
- import crypto from 'isomorphic-webcrypto';
15
- import {Buffer} from 'safe-buffer';
16
-
17
- setEngine(
18
- 'newEngine',
19
- crypto,
20
- new CryptoEngine({
21
- name: '',
22
- crypto,
23
- subtle: crypto.subtle,
24
- })
25
- );
26
-
27
- const VALID_KTY = 'RSA';
28
- const VALID_KID_PROTOCOL = 'kms:';
29
-
30
- const X509_COMMON_NAME_KEY = '2.5.4.3';
31
-
32
- const X509_SUBJECT_ALT_NAME_KEY = '2.5.29.17';
33
-
34
- /**
35
- * Customize Error so the SDK knows to quit retrying and notify
36
- * the user
37
- */
38
- export class KMSError extends Error {
39
- /**
40
- * add kmsError field to notify
41
- * @param {string} message
42
- */
43
- constructor(message) {
44
- super(message);
45
- this.kmsError = true;
46
- }
47
- }
48
-
49
- const throwError = (err) => {
50
- throw new KMSError(`INVALID KMS: ${err}`);
51
- };
52
-
53
- /**
54
- * Converts the PEM string to a pkijs certificate object
55
- * @param {string} pem PEM representation of a certificate
56
- * @returns {Certificate} pkijs object of the certificate
57
- */
58
- const decodeCert = (pem) => {
59
- if (typeof pem !== 'string') {
60
- throwError('certificate needs to be a string');
61
- }
62
-
63
- const der = Buffer.from(pem, 'base64');
64
- const ber = new Uint8Array(der).buffer;
65
-
66
- const asn1 = fromBER(ber);
67
-
68
- return new Certificate({schema: asn1.result});
69
- };
70
-
71
- /**
72
- * Validate the 'kty' property of the KMS credentials
73
- * @param {Object} JWT KMS credentials
74
- * @param {string} JWT.kty type of certificate
75
- * @throws {KMSError} if kty is not a valid type
76
- * @returns {void}
77
- */
78
- const validateKtyHeader = ({kty}) => {
79
- if (kty !== VALID_KTY) {
80
- throwError(`'kty' header must be '${VALID_KTY}'`);
81
- }
82
- };
83
-
84
- const validateKidHeader = ({kid}) => {
85
- if (!isUri(kid)) {
86
- throwError("'kid' is not a valid URI");
87
- }
88
-
89
- if (parseUrl(kid).protocol !== VALID_KID_PROTOCOL) {
90
- throwError(`'kid' protocol must be '${VALID_KID_PROTOCOL}'`);
91
- }
92
- };
93
-
94
- /**
95
- * Checks the first certificate matches the 'kid' in the JWT.
96
- * It first checks the Subject Alternative Name then it checks
97
- * the Common Name
98
- * @param {Certificate} certificate represents the KMS
99
- * @param {Object} JWT KMS credentials
100
- * @param {string} JWT.kid the uri of the KMS
101
- * @throws {KMSError} if unable to validate certificate against KMS credentials
102
- * @returns {void}
103
- */
104
- const validateCommonName = ([certificate], {kid}) => {
105
- const kidHostname = parseUrl(kid).hostname;
106
- let validationSuccessful = false;
107
-
108
- if (certificate.extensions) {
109
- // Subject Alt Names are in here
110
- for (const extension of certificate.extensions) {
111
- if (extension.extnID === X509_SUBJECT_ALT_NAME_KEY) {
112
- const {altNames} = extension.parsedValue;
113
-
114
- for (const entry of altNames) {
115
- const san = entry.value;
116
-
117
- validationSuccessful = san === kidHostname;
118
- if (validationSuccessful) {
119
- break;
120
- }
121
- }
122
-
123
- if (validationSuccessful) {
124
- break;
125
- }
126
- }
127
- }
128
- }
129
-
130
- if (!validationSuccessful) {
131
- // Didn't match kid in the Subject Alt Names, checking the Common Name
132
- const subjectAttributes = certificate.subject.typesAndValues;
133
-
134
- for (const attribute of subjectAttributes) {
135
- if (attribute.type === X509_COMMON_NAME_KEY) {
136
- const commonName = attribute.value.valueBlock.value;
137
-
138
- validationSuccessful = commonName === kidHostname;
139
- if (validationSuccessful) {
140
- break;
141
- }
142
- }
143
- }
144
- }
145
-
146
- if (!validationSuccessful) {
147
- throwError("hostname of the 1st certificate does not match 'kid'");
148
- }
149
- };
150
-
151
- /**
152
- * Validate the first KMS certificate against the information
153
- * provided in the JWT
154
- * @param {Certificate} certificate first certificate the identifies the KMS
155
- * @param {Object} JWT credentials of the KMS
156
- * @param {string} JWT.e Public exponent of the first certificate
157
- * @param {string} KWT.n Modulus of the first certificate
158
- * @throws {KMSError} if e or n doesn't match the first certificate
159
- * @returns {void}
160
- */
161
- const validatePublicCertificate = ([certificate], {e: publicExponent, n: modulus}) => {
162
- const {encode} = jose.util.base64url;
163
-
164
- const publicKey = certificate.subjectPublicKeyInfo.subjectPublicKey;
165
- const asn1PublicCert = fromBER(publicKey.valueBlock.valueHex);
166
- const publicCert = new RSAPublicKey({schema: asn1PublicCert.result});
167
- const publicExponentHex = publicCert.publicExponent.valueBlock.valueHex;
168
- const modulusHex = publicCert.modulus.valueBlock.valueHex;
169
-
170
- if (publicExponent !== encode(publicExponentHex)) {
171
- throwError('Public exponent is invalid');
172
- }
173
- if (modulus !== encode(modulusHex)) {
174
- throwError('Modulus is invalid');
175
- }
176
- };
177
-
178
- /**
179
- * Validates the list of certificates against the CAs provided
180
- * @param {certificate[]} certificates list of certificates provided
181
- * by the KMS to certify itself
182
- * @param {string[]} [caroots=[]] list of Certificate Authorities used to
183
- * validate the KMS's certificates
184
- * @returns {Promise} rejects if unable to validate the certificates
185
- */
186
- const validateCertificatesSignature = (certificates, caroots = []) => {
187
- const certificateEngine = new CertificateChainValidationEngine({
188
- trustedCerts: caroots.map(decodeCert),
189
- certs: certificates,
190
- });
191
-
192
- return certificateEngine.verify().then(({result, resultCode, resultMessage}) => {
193
- if (!result) {
194
- throwError(`Certificate Validation failed [${resultCode}]: ${resultMessage}`);
195
- }
196
- });
197
- };
198
-
199
- /**
200
- * Validates the information provided by the KMS. This is a curried function.
201
- * The first function takes the caroots param and returns a second function.
202
- * The second function takes the credentials of the KMS and validates it
203
- * @param {string[]} caroots PEM encoded certificates that will be used
204
- * as Certificate Authorities
205
- * @param {Object} jwt Object containing the fields necessary to
206
- * validate the KMS
207
- * @returns {Promise} when resolved will return the jwt
208
- */
209
- const validateKMS =
210
- (caroots) =>
211
- (jwt = {}) =>
212
- Promise.resolve().then(() => {
213
- validateKtyHeader(jwt);
214
- validateKidHeader(jwt);
215
-
216
- if (!(isArray(jwt.x5c) && jwt.x5c.length > 0)) {
217
- throwError('JWK does not contain a list of certificates');
218
- }
219
- const certificates = jwt.x5c.map(decodeCert);
220
-
221
- validateCommonName(certificates, jwt);
222
- validatePublicCertificate(certificates, jwt);
223
-
224
- // Skip validating signatures if no CA roots were provided
225
- const promise = caroots
226
- ? validateCertificatesSignature(certificates, caroots)
227
- : Promise.resolve();
228
-
229
- return promise.then(() => jwt);
230
- });
231
-
232
- export default validateKMS;
1
+ import {parse as parseUrl} from 'url';
2
+
3
+ import {isUri} from 'valid-url';
4
+ import {fromBER} from 'asn1js';
5
+ import {
6
+ Certificate,
7
+ RSAPublicKey,
8
+ CertificateChainValidationEngine,
9
+ CryptoEngine,
10
+ setEngine,
11
+ } from 'pkijs';
12
+ import {isArray} from 'lodash';
13
+ import jose from 'node-jose';
14
+ import crypto from 'isomorphic-webcrypto';
15
+ import {Buffer} from 'safe-buffer';
16
+
17
+ setEngine(
18
+ 'newEngine',
19
+ crypto,
20
+ new CryptoEngine({
21
+ name: '',
22
+ crypto,
23
+ subtle: crypto.subtle,
24
+ })
25
+ );
26
+
27
+ const VALID_KTY = 'RSA';
28
+ const VALID_KID_PROTOCOL = 'kms:';
29
+
30
+ const X509_COMMON_NAME_KEY = '2.5.4.3';
31
+
32
+ const X509_SUBJECT_ALT_NAME_KEY = '2.5.29.17';
33
+
34
+ /**
35
+ * Customize Error so the SDK knows to quit retrying and notify
36
+ * the user
37
+ */
38
+ export class KMSError extends Error {
39
+ /**
40
+ * add kmsError field to notify
41
+ * @param {string} message
42
+ */
43
+ constructor(message) {
44
+ super(message);
45
+ this.kmsError = true;
46
+ }
47
+ }
48
+
49
+ const throwError = (err) => {
50
+ throw new KMSError(`INVALID KMS: ${err}`);
51
+ };
52
+
53
+ /**
54
+ * Converts the PEM string to a pkijs certificate object
55
+ * @param {string} pem PEM representation of a certificate
56
+ * @returns {Certificate} pkijs object of the certificate
57
+ */
58
+ const decodeCert = (pem) => {
59
+ if (typeof pem !== 'string') {
60
+ throwError('certificate needs to be a string');
61
+ }
62
+
63
+ const der = Buffer.from(pem, 'base64');
64
+ const ber = new Uint8Array(der).buffer;
65
+
66
+ const asn1 = fromBER(ber);
67
+
68
+ return new Certificate({schema: asn1.result});
69
+ };
70
+
71
+ /**
72
+ * Validate the 'kty' property of the KMS credentials
73
+ * @param {Object} JWT KMS credentials
74
+ * @param {string} JWT.kty type of certificate
75
+ * @throws {KMSError} if kty is not a valid type
76
+ * @returns {void}
77
+ */
78
+ const validateKtyHeader = ({kty}) => {
79
+ if (kty !== VALID_KTY) {
80
+ throwError(`'kty' header must be '${VALID_KTY}'`);
81
+ }
82
+ };
83
+
84
+ const validateKidHeader = ({kid}) => {
85
+ if (!isUri(kid)) {
86
+ throwError("'kid' is not a valid URI");
87
+ }
88
+
89
+ if (parseUrl(kid).protocol !== VALID_KID_PROTOCOL) {
90
+ throwError(`'kid' protocol must be '${VALID_KID_PROTOCOL}'`);
91
+ }
92
+ };
93
+
94
+ /**
95
+ * Checks the first certificate matches the 'kid' in the JWT.
96
+ * It first checks the Subject Alternative Name then it checks
97
+ * the Common Name
98
+ * @param {Certificate} certificate represents the KMS
99
+ * @param {Object} JWT KMS credentials
100
+ * @param {string} JWT.kid the uri of the KMS
101
+ * @throws {KMSError} if unable to validate certificate against KMS credentials
102
+ * @returns {void}
103
+ */
104
+ const validateCommonName = ([certificate], {kid}) => {
105
+ const kidHostname = parseUrl(kid).hostname;
106
+ let validationSuccessful = false;
107
+
108
+ if (certificate.extensions) {
109
+ // Subject Alt Names are in here
110
+ for (const extension of certificate.extensions) {
111
+ if (extension.extnID === X509_SUBJECT_ALT_NAME_KEY) {
112
+ const {altNames} = extension.parsedValue;
113
+
114
+ for (const entry of altNames) {
115
+ const san = entry.value;
116
+
117
+ validationSuccessful = san === kidHostname;
118
+ if (validationSuccessful) {
119
+ break;
120
+ }
121
+ }
122
+
123
+ if (validationSuccessful) {
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ }
129
+
130
+ if (!validationSuccessful) {
131
+ // Didn't match kid in the Subject Alt Names, checking the Common Name
132
+ const subjectAttributes = certificate.subject.typesAndValues;
133
+
134
+ for (const attribute of subjectAttributes) {
135
+ if (attribute.type === X509_COMMON_NAME_KEY) {
136
+ const commonName = attribute.value.valueBlock.value;
137
+
138
+ validationSuccessful = commonName === kidHostname;
139
+ if (validationSuccessful) {
140
+ break;
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ if (!validationSuccessful) {
147
+ throwError("hostname of the 1st certificate does not match 'kid'");
148
+ }
149
+ };
150
+
151
+ /**
152
+ * Validate the first KMS certificate against the information
153
+ * provided in the JWT
154
+ * @param {Certificate} certificate first certificate the identifies the KMS
155
+ * @param {Object} JWT credentials of the KMS
156
+ * @param {string} JWT.e Public exponent of the first certificate
157
+ * @param {string} KWT.n Modulus of the first certificate
158
+ * @throws {KMSError} if e or n doesn't match the first certificate
159
+ * @returns {void}
160
+ */
161
+ const validatePublicCertificate = ([certificate], {e: publicExponent, n: modulus}) => {
162
+ const {encode} = jose.util.base64url;
163
+
164
+ const publicKey = certificate.subjectPublicKeyInfo.subjectPublicKey;
165
+ const asn1PublicCert = fromBER(publicKey.valueBlock.valueHex);
166
+ const publicCert = new RSAPublicKey({schema: asn1PublicCert.result});
167
+ const publicExponentHex = publicCert.publicExponent.valueBlock.valueHex;
168
+ const modulusHex = publicCert.modulus.valueBlock.valueHex;
169
+
170
+ if (publicExponent !== encode(publicExponentHex)) {
171
+ throwError('Public exponent is invalid');
172
+ }
173
+ if (modulus !== encode(modulusHex)) {
174
+ throwError('Modulus is invalid');
175
+ }
176
+ };
177
+
178
+ /**
179
+ * Validates the list of certificates against the CAs provided
180
+ * @param {certificate[]} certificates list of certificates provided
181
+ * by the KMS to certify itself
182
+ * @param {string[]} [caroots=[]] list of Certificate Authorities used to
183
+ * validate the KMS's certificates
184
+ * @returns {Promise} rejects if unable to validate the certificates
185
+ */
186
+ const validateCertificatesSignature = (certificates, caroots = []) => {
187
+ const certificateEngine = new CertificateChainValidationEngine({
188
+ trustedCerts: caroots.map(decodeCert),
189
+ certs: certificates,
190
+ });
191
+
192
+ return certificateEngine.verify().then(({result, resultCode, resultMessage}) => {
193
+ if (!result) {
194
+ throwError(`Certificate Validation failed [${resultCode}]: ${resultMessage}`);
195
+ }
196
+ });
197
+ };
198
+
199
+ /**
200
+ * Validates the information provided by the KMS. This is a curried function.
201
+ * The first function takes the caroots param and returns a second function.
202
+ * The second function takes the credentials of the KMS and validates it
203
+ * @param {string[]} caroots PEM encoded certificates that will be used
204
+ * as Certificate Authorities
205
+ * @param {Object} jwt Object containing the fields necessary to
206
+ * validate the KMS
207
+ * @returns {Promise} when resolved will return the jwt
208
+ */
209
+ const validateKMS =
210
+ (caroots) =>
211
+ (jwt = {}) =>
212
+ Promise.resolve().then(() => {
213
+ validateKtyHeader(jwt);
214
+ validateKidHeader(jwt);
215
+
216
+ if (!(isArray(jwt.x5c) && jwt.x5c.length > 0)) {
217
+ throwError('JWK does not contain a list of certificates');
218
+ }
219
+ const certificates = jwt.x5c.map(decodeCert);
220
+
221
+ validateCommonName(certificates, jwt);
222
+ validatePublicCertificate(certificates, jwt);
223
+
224
+ // Skip validating signatures if no CA roots were provided
225
+ const promise = caroots
226
+ ? validateCertificatesSignature(certificates, caroots)
227
+ : Promise.resolve();
228
+
229
+ return promise.then(() => jwt);
230
+ });
231
+
232
+ export default validateKMS;
@@ -1,65 +1,65 @@
1
- /*!
2
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
- */
4
-
5
- import {Interceptor} from '@webex/http-core';
6
-
7
- import {DryError} from './kms-errors';
8
- /**
9
- * Interceptor (only to be used in test mode) intended to replay requests that
10
- * fail as a result of the test-user incompatibility in KMS.
11
- * @class
12
- */
13
- export default class KmsDryErrorInterceptor extends Interceptor {
14
- /**
15
- * @returns {KmsDryErrorInterceptor}
16
- */
17
- static create() {
18
- return new KmsDryErrorInterceptor({webex: this});
19
- }
20
-
21
- /**
22
- * @param {Object} options
23
- * @param {Exception} reason
24
- * @returns {Promise}
25
- */
26
- onResponseError(options, reason) {
27
- if (
28
- reason instanceof DryError &&
29
- reason.message.match(/Failed to resolve authorization token in KmsMessage request for user/)
30
- ) {
31
- this.webex.logger.error('DRY Request Failed due to kms/test-user flakiness');
32
- this.webex.logger.error(reason);
33
-
34
- return this.replay(options, reason);
35
- }
36
-
37
- return Promise.reject(reason);
38
- }
39
-
40
- /**
41
- * Replays the request
42
- * @param {Object} options
43
- * @param {DryError} reason
44
- * @returns {Object}
45
- */
46
- replay(options, reason) {
47
- if (options.replayCount) {
48
- options.replayCount += 1;
49
- } else {
50
- options.replayCount = 1;
51
- }
52
-
53
- if (options.replayCount > this.webex.config.maxAuthenticationReplays) {
54
- this.webex.logger.error(
55
- `kms: failed after ${this.webex.config.maxAuthenticationReplays} replay attempts`
56
- );
57
-
58
- return Promise.reject(reason);
59
- }
60
-
61
- this.webex.logger.info(`kms: replaying request ${options.replayCount} time`);
62
-
63
- return this.webex.request(options);
64
- }
65
- }
1
+ /*!
2
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
+ */
4
+
5
+ import {Interceptor} from '@webex/http-core';
6
+
7
+ import {DryError} from './kms-errors';
8
+ /**
9
+ * Interceptor (only to be used in test mode) intended to replay requests that
10
+ * fail as a result of the test-user incompatibility in KMS.
11
+ * @class
12
+ */
13
+ export default class KmsDryErrorInterceptor extends Interceptor {
14
+ /**
15
+ * @returns {KmsDryErrorInterceptor}
16
+ */
17
+ static create() {
18
+ return new KmsDryErrorInterceptor({webex: this});
19
+ }
20
+
21
+ /**
22
+ * @param {Object} options
23
+ * @param {Exception} reason
24
+ * @returns {Promise}
25
+ */
26
+ onResponseError(options, reason) {
27
+ if (
28
+ reason instanceof DryError &&
29
+ reason.message.match(/Failed to resolve authorization token in KmsMessage request for user/)
30
+ ) {
31
+ this.webex.logger.error('DRY Request Failed due to kms/test-user flakiness');
32
+ this.webex.logger.error(reason);
33
+
34
+ return this.replay(options, reason);
35
+ }
36
+
37
+ return Promise.reject(reason);
38
+ }
39
+
40
+ /**
41
+ * Replays the request
42
+ * @param {Object} options
43
+ * @param {DryError} reason
44
+ * @returns {Object}
45
+ */
46
+ replay(options, reason) {
47
+ if (options.replayCount) {
48
+ options.replayCount += 1;
49
+ } else {
50
+ options.replayCount = 1;
51
+ }
52
+
53
+ if (options.replayCount > this.webex.config.maxAuthenticationReplays) {
54
+ this.webex.logger.error(
55
+ `kms: failed after ${this.webex.config.maxAuthenticationReplays} replay attempts`
56
+ );
57
+
58
+ return Promise.reject(reason);
59
+ }
60
+
61
+ this.webex.logger.info(`kms: replaying request ${options.replayCount} time`);
62
+
63
+ return this.webex.request(options);
64
+ }
65
+ }