@webex/internal-plugin-encryption 2.60.0 → 2.60.1-next.1

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.
package/src/encryption.js CHANGED
@@ -67,19 +67,19 @@ const Encryption = WebexPlugin.extend({
67
67
 
68
68
  /**
69
69
  * Validate and initiate a Download request for requested file
70
- *
70
+ * @param {Object} fileUrl - Plaintext
71
71
  * @param {Object} scr - Plaintext
72
72
  * @param {Object} options - optional parameters to download a file
73
73
  * @returns {promise}
74
74
  */
75
- download(scr, options) {
75
+ download(fileUrl, scr, options) {
76
76
  /* istanbul ignore if */
77
- if (!scr.loc) {
78
- return Promise.reject(new Error('`scr.loc` is required'));
77
+ if (!fileUrl || !scr) {
78
+ return Promise.reject(new Error('`scr` and `fileUrl` are required'));
79
79
  }
80
80
 
81
81
  const shunt = new EventEmitter();
82
- const promise = this._fetchDownloadUrl(scr, options)
82
+ const promise = this._fetchDownloadUrl(fileUrl, options)
83
83
  .then((uri) => {
84
84
  // eslint-disable-next-line no-shadow
85
85
  const options = {
@@ -103,26 +103,25 @@ const Encryption = WebexPlugin.extend({
103
103
 
104
104
  /**
105
105
  * Fetch Download URL for the requested file
106
- *
107
- * @param {Object} scr - Plaintext
106
+ * @param {Object} fileUrl - Plaintext
108
107
  * @param {Object} options - optional parameters to download a file
109
108
  * @returns {promise} url of the downloadable file
110
109
  */
111
- _fetchDownloadUrl(scr, options) {
110
+ _fetchDownloadUrl(fileUrl, options) {
112
111
  this.logger.info('encryption: retrieving download url for encrypted file');
113
112
 
114
- if (process.env.NODE_ENV !== 'production' && scr.loc.includes('localhost')) {
113
+ if (process.env.NODE_ENV !== 'production' && fileUrl.includes('localhost')) {
115
114
  this.logger.info(
116
115
  'encryption: bypassing webex files because this looks to be a test file on localhost'
117
116
  );
118
117
 
119
- return Promise.resolve(scr.loc);
118
+ return Promise.resolve(fileUrl);
120
119
  }
121
120
 
122
121
  const inputBody = {
123
- endpoints: [scr.loc],
122
+ endpoints: [fileUrl],
124
123
  };
125
- const endpointUrl = url.parse(scr.loc);
124
+ const endpointUrl = url.parse(fileUrl);
126
125
 
127
126
  // hardcode the url to use 'https' and the file service '/v1/download/endpoints' api
128
127
  endpointUrl.protocol = 'https';
@@ -137,21 +136,29 @@ const Encryption = WebexPlugin.extend({
137
136
  allow: options.params.allow,
138
137
  }
139
138
  : inputBody,
140
- }).then((res) => {
141
- // eslint-disable-next-line no-shadow
142
- const url = res.body.endpoints[scr.loc];
139
+ })
140
+ .then((res) => {
141
+ // eslint-disable-next-line no-shadow
142
+ const url = res.body.endpoints[fileUrl];
143
+
144
+ if (!url) {
145
+ this.logger.warn(
146
+ 'encryption: could not determine download url for `fileUrl`; attempting to download `fileUrl` directly'
147
+ );
143
148
 
144
- if (!url) {
149
+ return fileUrl;
150
+ }
151
+ this.logger.info('encryption: retrieved download url for encrypted file');
152
+
153
+ return url;
154
+ })
155
+ .catch((err) => {
145
156
  this.logger.warn(
146
- 'encryption: could not determine download url for `scr.loc`; attempting to download `scr.loc` directly'
157
+ `encryption: ${err} could not determine download url for ${fileUrl}; attempting to download ${fileUrl} directly`
147
158
  );
148
159
 
149
- return scr.loc;
150
- }
151
- this.logger.info('encryption: retrieved download url for encrypted file');
152
-
153
- return url;
154
- });
160
+ return fileUrl;
161
+ });
155
162
  },
156
163
 
157
164
  encryptBinary(file) {
@@ -5,7 +5,7 @@
5
5
  import {safeSetTimeout} from '@webex/common-timers';
6
6
  import {Batcher} from '@webex/webex-core';
7
7
 
8
- import {KmsError, KmsTimeoutError} from './kms-errors';
8
+ import {KmsError, KmsTimeoutError, handleKmsKeyRevokedEncryptionFailure} from './kms-errors';
9
9
 
10
10
  export const TIMEOUT_SYMBOL = Symbol('TIMEOUT_SYMBOL');
11
11
 
@@ -133,6 +133,8 @@ const KmsBatcher = Batcher.extend({
133
133
  * @returns {Promise}
134
134
  */
135
135
  handleItemFailure(item, reason) {
136
+ handleKmsKeyRevokedEncryptionFailure(item, this.webex);
137
+
136
138
  return this.getDeferredForResponse(item).then((defer) => {
137
139
  defer.reject(reason || new KmsError(item.body));
138
140
  });
@@ -29,7 +29,7 @@ const VALID_KID_PROTOCOL = 'kms:';
29
29
 
30
30
  const X509_COMMON_NAME_KEY = '2.5.4.3';
31
31
 
32
- const X509_SUBJECT_ALT_NAME_KEY = '2.5.29.17';
32
+ export const X509_SUBJECT_ALT_NAME_KEY = '2.5.29.17';
33
33
 
34
34
  /**
35
35
  * Customize Error so the SDK knows to quit retrying and notify
@@ -101,7 +101,7 @@ const validateKidHeader = ({kid}) => {
101
101
  * @throws {KMSError} if unable to validate certificate against KMS credentials
102
102
  * @returns {void}
103
103
  */
104
- const validateCommonName = ([certificate], {kid}) => {
104
+ export const validateCommonName = ([certificate], {kid}) => {
105
105
  const kidHostname = parseUrl(kid).hostname;
106
106
  let validationSuccessful = false;
107
107
 
@@ -112,7 +112,7 @@ const validateCommonName = ([certificate], {kid}) => {
112
112
  const {altNames} = extension.parsedValue;
113
113
 
114
114
  for (const entry of altNames) {
115
- const san = entry.value;
115
+ const san = entry.value.toLowerCase();
116
116
 
117
117
  validationSuccessful = san === kidHostname;
118
118
  if (validationSuccessful) {
package/src/kms-errors.js CHANGED
@@ -5,6 +5,12 @@
5
5
  import {Exception} from '@webex/common';
6
6
  import {WebexHttpError} from '@webex/webex-core';
7
7
 
8
+ import {
9
+ KMS_KEY_REVOKE_ERROR_CODES,
10
+ KMS_KEY_REVOKE_FAILURE,
11
+ KMS_KEY_REVOKE_ERROR_STATUS,
12
+ } from './constants';
13
+
8
14
  /**
9
15
  * Error class for KMS errors
10
16
  */
@@ -145,3 +151,17 @@ export class DryError extends WebexHttpError {
145
151
  return message;
146
152
  }
147
153
  }
154
+
155
+ /**
156
+ * Function triggers an event when specific encryption failures are received.
157
+ */
158
+
159
+ // eslint-disable-next-line consistent-return
160
+ export const handleKmsKeyRevokedEncryptionFailure = (item, webex) => {
161
+ if (
162
+ item.status === KMS_KEY_REVOKE_ERROR_STATUS &&
163
+ KMS_KEY_REVOKE_ERROR_CODES.includes(item.body.errorCode)
164
+ ) {
165
+ webex.internal.encryption.trigger(KMS_KEY_REVOKE_FAILURE);
166
+ }
167
+ };
@@ -158,7 +158,8 @@ describe('Encryption', function () {
158
158
  }));
159
159
  });
160
160
 
161
- describe('#download()', () => {
161
+ // SPARK-413317
162
+ describe.skip('#download()', () => {
162
163
  it('downloads and decrypts an encrypted file', () =>
163
164
  webex.internal.encryption
164
165
  .encryptBinary(FILE)
@@ -425,7 +425,6 @@ describe('Encryption', function () {
425
425
  describe('upload customer master key', () => {
426
426
  let uploadedkeyId;
427
427
 
428
- /* eslint-disable no-unused-expressions */
429
428
  skipInBrowser(it)('upload customer master key', () =>
430
429
  webex.internal.encryption.kms
431
430
  .deleteAllCustomerMasterKeys({assignedOrgId: spock.orgId})
@@ -22,18 +22,18 @@ describe('internal-plugin-encryption', () => {
22
22
  });
23
23
 
24
24
  describe('check _fetchDownloadUrl()', () => {
25
- const scrArray = [
25
+ const fileArray = [
26
26
  {
27
- loc: 'https://files-api-intb1.ciscospark.com/v1/spaces/a0cba376-fc05-4b88-af4b-cfffa7465f9a/contents/1d3931e7-9e31-46bc-8084-d766a8f72c99/versions/5fa9caf87a98410aae49e0173856a974/bytes',
27
+ url: 'https://files-api-intb1.ciscospark.com/v1/spaces/a0cba376-fc05-4b88-af4b-cfffa7465f9a/contents/1d3931e7-9e31-46bc-8084-d766a8f72c99/versions/5fa9caf87a98410aae49e0173856a974/bytes',
28
28
  },
29
29
  {
30
- loc: 'https://files-api-intb2.ciscospark.com/v1/spaces/a0cba376-fc05-4b88-af4b-cfffa7465f9a/contents/1d3931e7-9e31-46bc-8084-d766a8f72c99/versions/5fa9caf87a98410aae49e0173856a974/bytes',
30
+ url: 'https://files-api-intb2.ciscospark.com/v1/spaces/a0cba376-fc05-4b88-af4b-cfffa7465f9a/contents/1d3931e7-9e31-46bc-8084-d766a8f72c99/versions/5fa9caf87a98410aae49e0173856a974/bytes',
31
31
  },
32
32
  {
33
- loc: 'https://www.test-api.com/v1/spaces/test-path-name-space/contents/test-path-name-contents/versions/test-version/bytes',
33
+ url: 'https://www.test-api.com/v1/spaces/test-path-name-space/contents/test-path-name-contents/versions/test-version/bytes',
34
34
  },
35
35
  {
36
- loc: 'http://www.test-api.com/v1/spaces/test-path-name-space/contents/test-path-name-contents/versions/test-version/bytes',
36
+ url: 'http://www.test-api.com/v1/spaces/test-path-name-space/contents/test-path-name-contents/versions/test-version/bytes',
37
37
  },
38
38
  ];
39
39
  const options = undefined;
@@ -44,7 +44,7 @@ describe('internal-plugin-encryption', () => {
44
44
 
45
45
  spyStub = sinon.stub(webex.internal.encryption, 'request').callsFake(returnStub);
46
46
 
47
- scrArray.forEach((scr) => webex.internal.encryption._fetchDownloadUrl(scr, options));
47
+ fileArray.forEach((file) => webex.internal.encryption._fetchDownloadUrl(file.url, options));
48
48
  });
49
49
 
50
50
  it('verifying file service uris', () => {
@@ -68,10 +68,10 @@ describe('internal-plugin-encryption', () => {
68
68
  });
69
69
 
70
70
  it('verifying endpoints', () => {
71
- assert.equal(spyStub.args[0][0].body.endpoints[0], scrArray[0].loc);
72
- assert.equal(spyStub.args[1][0].body.endpoints[0], scrArray[1].loc);
73
- assert.equal(spyStub.args[2][0].body.endpoints[0], scrArray[2].loc);
74
- assert.equal(spyStub.args[3][0].body.endpoints[0], scrArray[3].loc);
71
+ assert.equal(spyStub.args[0][0].body.endpoints[0], fileArray[0].url);
72
+ assert.equal(spyStub.args[1][0].body.endpoints[0], fileArray[1].url);
73
+ assert.equal(spyStub.args[2][0].body.endpoints[0], fileArray[2].url);
74
+ assert.equal(spyStub.args[3][0].body.endpoints[0], fileArray[3].url);
75
75
  });
76
76
 
77
77
  afterEach(() => {
@@ -1,6 +1,6 @@
1
1
  import {assert} from '@webex/test-helper-chai';
2
2
 
3
- import validateCert, {KMSError} from '../../../src/kms-certificate-validation';
3
+ import validateCert, {KMSError, validateCommonName, X509_SUBJECT_ALT_NAME_KEY} from '../../../src/kms-certificate-validation';
4
4
 
5
5
  const caroots = [
6
6
  'MIID6TCCAtGgAwIBAgIURmBu688C9oUIJXlykr1J3fi5H4kwDQYJKoZIhvcNAQELBQAwgYMxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhDb2xvcmFkbzEPMA0GA1UEBwwGRGVudmVyMRAwDgYDVQQKDAdFeGFtcGxlMR8wHQYDVQQDDBZodHRwczovL2NhLmV4YW1wbGUuY29tMR0wGwYJKoZIhvcNAQkBFg5jYUBleGFtcGxlLmNvbTAeFw0yMDAyMDYyMDIyMDhaFw00MDAyMDEyMDIyMDhaMIGDMQswCQYDVQQGEwJVUzERMA8GA1UECAwIQ29sb3JhZG8xDzANBgNVBAcMBkRlbnZlcjEQMA4GA1UECgwHRXhhbXBsZTEfMB0GA1UEAwwWaHR0cHM6Ly9jYS5leGFtcGxlLmNvbTEdMBsGCSqGSIb3DQEJARYOY2FAZXhhbXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC7TaDWldwjU65y4fnNDIuNu4dZi3bZvaN9nJ3A8D9pwFcNx3DL5cPpafAkJuE/2ZBrsZxJWKwXLQFuNE9V3XVslv0OPgEZVfY5AKuPhVezRqEqsCdUgODMkJat6PE02r0NZRFpBiRCThh0wY5u/tiTiPgjHwEPhBEyLgcJ6FOWLn9wBsS4SvBzfppYGL5GW1G0eN9yORnKKgqkgyf0x8FvTMyVSjtkhcI/kA/8061sl4DFG6sefQmAOVvH7tp7YmN+jpQ7cOKQtjOpZS6Gp22u7LEI0/qb5n2QvjjcUQM81mN6CZ8nciWXRgjBhdAJJhmyMvcx8rnVb6vtU26fCaetAgMBAAGjUzBRMB0GA1UdDgQWBBRZiCyKaTYL94gwhxzktYg32qMOYjAfBgNVHSMEGDAWgBRZiCyKaTYL94gwhxzktYg32qMOYjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQATa2QkTGcj8IPjItnvg23ihlRjHdFHn6lB7uYPhcDurwRlBrlC2/OB44P3dHB9tEPbV4unoF9ftEKO3nNY3HUDcPrQwRqkPftlYYr4/6z/jnmNBRgiDICVaiTZNlX54fLiPsSAbIymPWLLLNtq17vjVEcfGUXhi/F+EkN/uXZ4yH6RK0YjBRwPV9cfziz1YsF2WVYVYtQErf+NTjnYR5S4Ba2kEqhI5j7mNhiafPNODaOchHcaRMvfWcBhlHt+atwNyPxNr4NP+cDjAWg0I8xAUdbZGQiRJecjkctolLHsfZXj+ulEv3eaKw7gSo3Aekexw8aZS7soy+VM1fzmLopw',
@@ -163,3 +163,36 @@ describe('internal-plugin-encryption', () => {
163
163
  });
164
164
  });
165
165
  });
166
+
167
+ describe('validateCommonName', () => {
168
+
169
+ const checkValidate = (SAN, kidHostname) => {
170
+ validateCommonName(
171
+ [
172
+ {
173
+ extensions: [
174
+ {
175
+ extnID: X509_SUBJECT_ALT_NAME_KEY,
176
+ parsedValue: {
177
+ altNames: [{value: 'Example.com'}],
178
+ },
179
+ },
180
+ ],
181
+ },
182
+ ],
183
+ {kid: 'https://Example.com'}
184
+ );
185
+ }
186
+
187
+ it('handles mixed case SAN', () => {
188
+ checkValidate('Example.com', 'https://Example.com');
189
+ });
190
+
191
+ it('handles different case SAN', () => {
192
+ checkValidate('ExAmpLe.cOm', 'https://example.com');
193
+ });
194
+
195
+ it('handles different case kid hostname', () => {
196
+ checkValidate('example.com', 'https://ExAmpLe.cOm');
197
+ });
198
+ });
@@ -0,0 +1,70 @@
1
+ import {assert} from '@webex/test-helper-chai';
2
+ import {handleKmsKeyRevokedEncryptionFailure} from '../../../src/kms-errors'
3
+ import sinon from 'sinon';
4
+
5
+ describe('handleKmsKeyRevokedEncryptionFailure', () => {
6
+
7
+ it('triggers `event:kms:key:revoke:encryption:failure` event when correct error code is detected', () => {
8
+ const webex = {
9
+ internal: {
10
+ encryption: {
11
+ trigger: sinon.spy()
12
+ },
13
+ },
14
+ }
15
+
16
+ const item = {
17
+ status: 405,
18
+ body: {
19
+ errorCode: 405005,
20
+ }
21
+ }
22
+
23
+ handleKmsKeyRevokedEncryptionFailure(item, webex);
24
+
25
+ assert.calledOnce(webex.internal.encryption.trigger);
26
+ assert.calledWithExactly(webex.internal.encryption.trigger, `event:kms:key:revoke:encryption:failure`);
27
+ });
28
+
29
+ it('does not trigger `event:kms:key:revoke:encryption:failure` event when correct status but wrong error code is detected', () => {
30
+ const webex = {
31
+ internal: {
32
+ encryption: {
33
+ trigger: sinon.spy()
34
+ },
35
+ },
36
+ }
37
+
38
+ const item = {
39
+ status: 405,
40
+ body: {
41
+ errorCode: 405009,
42
+ }
43
+ }
44
+
45
+ handleKmsKeyRevokedEncryptionFailure(item, webex);
46
+
47
+ assert.notCalled(webex.internal.encryption.trigger);
48
+ });
49
+
50
+ it('does not trigger `event:kms:key:revoke:encryption:failure` event when wrong status but correct error code is detected', () => {
51
+ const webex = {
52
+ internal: {
53
+ encryption: {
54
+ trigger: sinon.spy()
55
+ },
56
+ },
57
+ }
58
+
59
+ const item = {
60
+ status: 403,
61
+ body: {
62
+ errorCode: 405005,
63
+ }
64
+ }
65
+
66
+ handleKmsKeyRevokedEncryptionFailure(item, webex);
67
+
68
+ assert.notCalled(webex.internal.encryption.trigger);
69
+ });
70
+ });