@webex/internal-plugin-encryption 2.59.2 → 2.59.3-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.
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 +20 -19
  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,800 +1,800 @@
1
- /* eslint-env browser */
2
- /*!
3
- * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
4
- */
5
-
6
- import '@webex/internal-plugin-encryption';
7
-
8
- import {assert, expect} from '@webex/test-helper-chai';
9
- import sinon from 'sinon';
10
- import WebexCore from '@webex/webex-core';
11
- import testUsers from '@webex/test-helper-test-users';
12
- import uuid from 'uuid';
13
- import {skipInBrowser} from '@webex/test-helper-mocha';
14
-
15
- const debug = require('debug')('kms');
16
-
17
- describe('Encryption', function () {
18
- this.timeout(30000);
19
- describe('KMS', () => {
20
- let mccoy, webex, spock;
21
-
22
- function str2ab(str) {
23
- const buf = new ArrayBuffer(str.length);
24
- const bufView = new Uint8Array(buf);
25
-
26
- for (let i = 0, strLen = str.length; i < strLen; i += 1) {
27
- bufView[i] = str.charCodeAt(i);
28
- }
29
-
30
- return buf;
31
- }
32
-
33
- function arrayBufferToBase64(buffer) {
34
- let binary = '';
35
- const bytes = new Uint8Array(buffer);
36
- const len = bytes.byteLength;
37
-
38
- for (let i = 0; i < len; i += 1) {
39
- binary += String.fromCharCode(bytes[i]);
40
- }
41
-
42
- return window.btoa(binary);
43
- }
44
-
45
- before('create test user', () =>
46
- testUsers.create({count: 2, config: {roles: [{name: 'id_full_admin'}]}}).then((users) => {
47
- spock = users[0];
48
- webex = new WebexCore({
49
- credentials: {
50
- authorization: spock.token,
51
- },
52
- });
53
- spock.webex = webex;
54
- assert.isTrue(webex.canAuthorize);
55
-
56
- mccoy = users[1];
57
- mccoy.webex = new WebexCore({
58
- credentials: {
59
- authorization: mccoy.token,
60
- },
61
- });
62
-
63
- assert.isTrue(mccoy.webex.canAuthorize);
64
-
65
- return mccoy.webex.internal.device.register();
66
- })
67
- );
68
-
69
- after(() => webex && webex.internal.mercury.disconnect());
70
-
71
- let expiredUser;
72
-
73
- // TODO: Re-enable SPARK-133280
74
- it.skip('errs using an invalid token', () =>
75
- testUsers
76
- .create({count: 1})
77
- .then((users) => {
78
- [expiredUser] = users;
79
- expiredUser.webex = new WebexCore({
80
- credentials: {
81
- authorization: 'invalidToken',
82
- },
83
- });
84
- expiredUser.webex.internal.device.register();
85
- })
86
- .then(() => expiredUser.webex.internal.encryption.kms.createUnboundKeys({count: 1}))
87
- .catch((err) => {
88
- assert.equal(err.statusCode, 401);
89
- }));
90
-
91
- describe('#createResource()', () => {
92
- it('creates a kms resource object', () =>
93
- webex.internal.encryption.kms.createUnboundKeys({count: 1}).then(([key]) =>
94
- webex.internal.encryption.kms
95
- .createResource({
96
- userIds: [webex.internal.device.userId],
97
- key,
98
- })
99
- .then((kro) => {
100
- assert.property(kro, 'uri');
101
- assert.property(kro, 'keyUris');
102
- assert.lengthOf(kro.keyUris, 1);
103
- assert.include(kro.keyUris, key.uri);
104
- assert.property(kro, 'authorizationUris');
105
- assert.lengthOf(kro.authorizationUris, 1);
106
- })
107
- ));
108
- });
109
-
110
- describe('#addAuthorization()', () => {
111
- let boundedKeyUri, kro, otherKro;
112
-
113
- before('create a resource', () =>
114
- webex.internal.encryption.kms
115
- .createUnboundKeys({count: 1})
116
- .then(([key]) =>
117
- webex.internal.encryption.kms.createResource({
118
- key,
119
- })
120
- )
121
- .then((k) => {
122
- kro = k;
123
- boundedKeyUri = kro.keyUris[0];
124
- assert.lengthOf(kro.authorizationUris, 1);
125
- })
126
- );
127
-
128
- it('authorizes a user to a key', () =>
129
- webex.internal.encryption.kms
130
- .addAuthorization({
131
- userIds: [mccoy.webex.internal.device.userId],
132
- kroUri: kro.uri,
133
- })
134
- .then(([auth]) => {
135
- assert.equal(auth.resourceUri, kro.uri);
136
- assert.equal(auth.authId, mccoy.webex.internal.device.userId);
137
-
138
- return mccoy.webex.internal.encryption.kms.fetchKey({uri: boundedKeyUri});
139
- }));
140
-
141
- it('authorizes a resource to a key', () =>
142
- webex.internal.encryption.kms
143
- .createUnboundKeys({count: 1})
144
- .then(([key]) => webex.internal.encryption.kms.createResource({key}))
145
- .then((k) => {
146
- otherKro = k;
147
-
148
- return webex.internal.encryption.kms.addAuthorization({
149
- authIds: [otherKro.uri],
150
- kro,
151
- });
152
- })
153
- .then(([auth]) => {
154
- assert.equal(auth.resourceUri, kro.uri);
155
- assert.equal(auth.authId, otherKro.uri);
156
- }));
157
- });
158
-
159
- /**
160
- *Test listAuthorizations function
161
- *Setup: Create a resource, then authorize a user to a key, then authorize a resource to a key
162
- *Test:
163
- *1)Invoke listAuthorizations on the resource, verify that the function returns the array that contains two authorized items
164
- *2)Remove the authorized user from the resource, then invoke listAuthorizations again, verify that the returned array doesn't have the user.
165
- *3)Remove the authorized resource from the resource again, then invoke listAuthorizations again, verify that he returned array doesn't have the resource.
166
- */
167
- describe('#listAuthorizations()', () => {
168
- let boundedKeyUri, kro, otherKro;
169
- let testResourceId;
170
-
171
- before('creates a resource', () =>
172
- webex.internal.encryption.kms
173
- .createUnboundKeys({count: 1})
174
- .then(([key]) =>
175
- webex.internal.encryption.kms.createResource({
176
- key,
177
- })
178
- )
179
- .then((k) => {
180
- kro = k;
181
- boundedKeyUri = kro.keyUris[0];
182
- assert.lengthOf(kro.authorizationUris, 1);
183
- })
184
- );
185
-
186
- before('authorizes a user to a key', () =>
187
- webex.internal.encryption.kms
188
- .addAuthorization({
189
- userIds: [mccoy.webex.internal.device.userId],
190
- kroUri: kro.uri,
191
- })
192
- .then(([auth]) => {
193
- assert.equal(auth.resourceUri, kro.uri);
194
- assert.equal(auth.authId, mccoy.webex.internal.device.userId);
195
-
196
- return mccoy.webex.internal.encryption.kms.fetchKey({uri: boundedKeyUri});
197
- })
198
- );
199
-
200
- before('authorizes a resource to a key', () =>
201
- webex.internal.encryption.kms
202
- .createUnboundKeys({count: 1})
203
- .then(([key]) => webex.internal.encryption.kms.createResource({key}))
204
- .then((k) => {
205
- otherKro = k;
206
- testResourceId = otherKro.uri;
207
-
208
- return webex.internal.encryption.kms.addAuthorization({
209
- authIds: [testResourceId],
210
- kro,
211
- });
212
- })
213
- .then(([auth]) => {
214
- assert.equal(auth.resourceUri, kro.uri);
215
- assert.equal(auth.authId, otherKro.uri);
216
- })
217
- );
218
-
219
- it('list authorizations', () =>
220
- webex.internal.encryption.kms
221
- .listAuthorizations({kroUri: kro.uri})
222
- .then((authorizations) => {
223
- assert.equal(authorizations.length, 3);
224
- assert.include(
225
- authorizations.map((a) => a.authId),
226
- mccoy.webex.internal.device.userId
227
- );
228
- assert.include(
229
- authorizations.map((a) => a.authId),
230
- spock.id
231
- );
232
- assert.include(
233
- authorizations.map((a) => a.resourceUri),
234
- testResourceId
235
- );
236
- assert.include(
237
- authorizations.map((a) => a.resourceUri),
238
- kro.uri
239
- );
240
- }));
241
-
242
- it('rejects normally for users that are not authorized', () =>
243
- testUsers.create({count: 1}).then(([user]) => {
244
- const us = new WebexCore({
245
- credentials: {
246
- authorization: user.token,
247
- },
248
- });
249
-
250
- return assert
251
- .isRejected(us.internal.encryption.kms.listAuthorizations({kroUri: kro.uri}))
252
- .then((err) => {
253
- console.error(err);
254
- assert.equal(err.status, 403, 'We should get a Not Authorized response from kms');
255
- })
256
- .then(() => us.internal.mercury.disconnect());
257
- }));
258
-
259
- it('remove the user and verify this user is not in the authorization list ', () =>
260
- webex.internal.encryption.kms
261
- .removeAuthorization({
262
- userId: mccoy.webex.internal.device.userId,
263
- kroUri: kro.uri,
264
- })
265
- .then(([auth]) => {
266
- assert.equal(auth.resourceUri, kro.uri);
267
- assert.equal(auth.authId, mccoy.webex.internal.device.userId);
268
-
269
- return webex.internal.encryption.kms
270
- .listAuthorizations({kro})
271
- .then((authorizations) => {
272
- assert.equal(authorizations.length, 2);
273
- assert.include(
274
- authorizations.map((a) => a.authId),
275
- spock.id
276
- );
277
- assert.include(
278
- authorizations.map((a) => a.resourceUri),
279
- testResourceId
280
- );
281
- assert.include(
282
- authorizations.map((a) => a.resourceUri),
283
- kro.uri
284
- );
285
- });
286
- }));
287
-
288
- it('remove the resource and verify this resource is not in the authorizaiton list', () =>
289
- webex.internal.encryption.kms
290
- .removeAuthorization({
291
- userId: testResourceId,
292
- kroUri: kro.uri,
293
- })
294
- .then(([auth]) => {
295
- assert.equal(auth.resourceUri, kro.uri);
296
- assert.equal(auth.authId, testResourceId);
297
-
298
- return webex.internal.encryption.kms
299
- .listAuthorizations({kroUri: kro.uri})
300
- .then((authorizations) => {
301
- assert.equal(authorizations.length, 1);
302
- assert.include(
303
- authorizations.map((a) => a.authId),
304
- spock.id
305
- );
306
- assert.include(
307
- authorizations.map((a) => a.resourceUri),
308
- kro.uri
309
- );
310
- });
311
- }));
312
- });
313
-
314
- describe('#removeAuthorization()', () => {
315
- let boundedKeyUri, kro, otherKro;
316
-
317
- before('create resource', () =>
318
- webex.internal.encryption.kms
319
- .createUnboundKeys({count: 1})
320
- .then(([key]) =>
321
- webex.internal.encryption.kms.createResource({
322
- key,
323
- })
324
- )
325
- .then((k) => {
326
- kro = k;
327
- boundedKeyUri = kro.keyUris[0];
328
- assert.lengthOf(kro.authorizationUris, 1);
329
- })
330
- );
331
-
332
- before('create another resource', () =>
333
- webex.internal.encryption.kms
334
- .createUnboundKeys({count: 1})
335
- .then(([key]) =>
336
- webex.internal.encryption.kms.createResource({
337
- key,
338
- })
339
- )
340
- .then((k) => {
341
- otherKro = k;
342
- })
343
- );
344
-
345
- before('add auths to resource', () =>
346
- webex.internal.encryption.kms
347
- .addAuthorization({
348
- authIds: [otherKro.uri, mccoy.webex.internal.device.userId],
349
- kro,
350
- })
351
- .then(([kroAuth, userAuth]) => {
352
- assert.equal(kroAuth.authId, otherKro.uri);
353
- assert.equal(userAuth.authId, mccoy.webex.internal.device.userId);
354
- })
355
- );
356
-
357
- it('deauthorizes a user from a key', () =>
358
- webex.internal.encryption.kms
359
- .removeAuthorization({
360
- userId: mccoy.webex.internal.device.userId,
361
- kroUri: kro.uri,
362
- })
363
- .then(([auth]) => {
364
- assert.equal(auth.resourceUri, kro.uri);
365
- assert.equal(auth.authId, mccoy.webex.internal.device.userId);
366
-
367
- return assert.isRejected(
368
- mccoy.webex.internal.encryption.kms.fetchKey({uri: boundedKeyUri})
369
- );
370
- }));
371
-
372
- it('deauthorizes a resource from a key', () =>
373
- webex.internal.encryption.kms
374
- .removeAuthorization({
375
- authId: otherKro.uri,
376
- kro,
377
- })
378
- .then(([auth]) => {
379
- assert.equal(auth.resourceUri, kro.uri);
380
- assert.equal(auth.authId, otherKro.uri);
381
- }));
382
- });
383
-
384
- describe('#bindKey()', () => {
385
- let key2, kro;
386
-
387
- it('binds a resource to a key', () =>
388
- webex.internal.encryption.kms
389
- .createUnboundKeys({count: 2})
390
- .then((keys) => {
391
- key2 = keys[1];
392
-
393
- return webex.internal.encryption.kms.createResource({
394
- userIds: [webex.internal.device.userId],
395
- key: keys[0],
396
- });
397
- })
398
- .then((k) => {
399
- kro = k;
400
-
401
- return webex.internal.encryption.kms.bindKey({kro, key: key2});
402
- })
403
- .then((key) => {
404
- assert.equal(key.uri, key2.uri);
405
- assert.property(key, 'bindDate');
406
- assert.property(key, 'resourceUri');
407
- assert.equal(key.resourceUri, kro.uri);
408
- }));
409
- });
410
-
411
- describe('#createUnboundKeys()', () => {
412
- it('requests unbound keys from the KMS', () =>
413
- webex.internal.encryption.kms.createUnboundKeys({count: 2}).then((keys) => {
414
- assert.lengthOf(keys, 2);
415
-
416
- const [key1, key2] = keys;
417
-
418
- assert.property(key1, 'uri');
419
- assert.property(key1, 'jwk');
420
- assert.property(key2, 'uri');
421
- assert.property(key2, 'jwk');
422
- }));
423
- });
424
-
425
- describe('upload customer master key', () => {
426
- let uploadedkeyId;
427
-
428
- /* eslint-disable no-unused-expressions */
429
- skipInBrowser(it)('upload customer master key', () =>
430
- webex.internal.encryption.kms
431
- .deleteAllCustomerMasterKeys({assignedOrgId: spock.orgId})
432
- .then(() => webex.internal.encryption.kms.fetchPublicKey({assignedOrgId: spock.orgId}))
433
- .then((publicKey) => {
434
- assert.isNotEmpty(publicKey);
435
- const pemHeader = '-----BEGIN PUBLIC KEY-----';
436
- const pemFooter = '-----END PUBLIC KEY-----';
437
- const publicContent = publicKey.substring(
438
- pemHeader.length,
439
- publicKey.length - pemFooter.length
440
- );
441
- const binaryDerString = window.atob(publicContent);
442
- // convert from a binary string to an ArrayBuffer
443
- const binaryDer = str2ab(binaryDerString);
444
-
445
- return window.crypto.subtle.importKey(
446
- 'spki',
447
- binaryDer,
448
- {
449
- name: 'RSA-OAEP',
450
- hash: 'SHA-256',
451
- },
452
- true,
453
- ['encrypt']
454
- );
455
- })
456
- .then((publicKey) => {
457
- const buf = window.crypto.getRandomValues(new Uint8Array(16));
458
-
459
- return window.crypto.subtle.encrypt(
460
- {
461
- name: 'RSA-OAEP',
462
- },
463
- publicKey,
464
- buf
465
- );
466
- })
467
- .then((encryptedData) =>
468
- webex.internal.encryption.kms.uploadCustomerMasterKey({
469
- assignedOrgId: spock.orgId,
470
- customerMasterKey: arrayBufferToBase64(encryptedData),
471
- })
472
- )
473
- .then((uploadRes) => {
474
- uploadedkeyId = uploadRes.customerMasterKeys[0].uri;
475
-
476
- return webex.internal.encryption.kms.listAllCustomerMasterKey({
477
- assignedOrgId: spock.orgId,
478
- });
479
- })
480
- .then((listCmksRes) => {
481
- const cmks = listCmksRes.customerMasterKeys;
482
- const uploadedCmk = cmks.find((cmk) => cmk.uri === uploadedkeyId);
483
-
484
- expect(uploadedCmk).to.not.be.null;
485
-
486
- return webex.internal.encryption.kms.changeCustomerMasterKeyState({
487
- keyId: uploadedkeyId,
488
- keyState: 'ACTIVE',
489
- assignedOrgId: spock.orgId,
490
- });
491
- })
492
- .then((activeRes) => {
493
- expect(activeRes.customerMasterKeys[0].usageState).to.have.string('ACTIVE');
494
-
495
- // return webex.internal.encryption.kms.useGlobalMasterKey({assignedOrgId: spock.orgId});
496
- return webex.internal.encryption.kms.deleteAllCustomerMasterKeys({
497
- assignedOrgId: spock.orgId,
498
- });
499
- })
500
- );
501
- });
502
-
503
- describe('#fetchKey()', () => {
504
- let key;
505
-
506
- it('retrieves a specific key', () =>
507
- webex.internal.encryption.kms
508
- .createUnboundKeys({count: 1})
509
- .then(([k]) => {
510
- key = k;
511
-
512
- return webex.internal.encryption.kms.fetchKey({uri: key.uri});
513
- })
514
- .then((key2) => {
515
- assert.property(key2, 'uri');
516
- assert.property(key2, 'jwk');
517
- assert.notEqual(key2, key);
518
- assert.equal(key2.uri, key.uri);
519
- }));
520
- });
521
-
522
- describe('#fetchKey(onBehalfOf)', () => {
523
- let jim;
524
-
525
- before('create compliance officer test user', () =>
526
- testUsers
527
- .create({
528
- count: 1,
529
- config: {
530
- roles: [{name: 'spark.kms_orgagent'}],
531
- },
532
- })
533
- .then((users) => {
534
- jim = users[0];
535
-
536
- jim.webex = new WebexCore({
537
- credentials: {
538
- authorization: jim.token,
539
- },
540
- });
541
- assert.isTrue(jim.webex.canAuthorize);
542
- })
543
- );
544
-
545
- before('connect compliance officer to mercury', () => jim.webex.internal.mercury.connect());
546
-
547
- after(() => jim.webex && jim.webex.internal.mercury.disconnect());
548
-
549
- let key;
550
-
551
- it('retrieve key on behalf of another user', () =>
552
- spock.webex.internal.encryption.kms
553
- .createUnboundKeys({count: 1})
554
- .then(([k]) => {
555
- key = k;
556
-
557
- // Compliance Officer Jim fetches a key on behalf of Spock
558
- return jim.webex.internal.encryption.kms.fetchKey({uri: key.uri, onBehalfOf: spock.id});
559
- })
560
- .then((key2) => {
561
- assert.property(key2, 'uri');
562
- assert.property(key2, 'jwk');
563
- assert.notEqual(key2, key);
564
- assert.equal(key2.uri, key.uri);
565
- }));
566
-
567
- it('retrieve key on behalf of self', () =>
568
- jim.webex.internal.encryption.kms
569
- .createUnboundKeys({count: 1})
570
- .then(([k]) => {
571
- key = k;
572
-
573
- // Compliance Officer Jim fetches a key on behalf of himself
574
- // This covers an edge case documented by https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-240862.
575
- return jim.webex.internal.encryption.kms.fetchKey({uri: key.uri, onBehalfOf: jim.id});
576
- })
577
- .then((key2) => {
578
- assert.property(key2, 'uri');
579
- assert.property(key2, 'jwk');
580
- assert.notEqual(key2, key);
581
- assert.equal(key2.uri, key.uri);
582
- }));
583
-
584
- // Spock creates the key and Jim is not in the KRO so he should not have access
585
- it('retrieve key on behalf of self but self does not have access', () =>
586
- spock.webex.internal.encryption.kms
587
- .createUnboundKeys({count: 1})
588
- .then(([k]) => {
589
- key = k;
590
-
591
- // Compliance Officer Jim fetches a key on behalf of himself but he is not in the KRO
592
- return jim.webex.internal.encryption.kms.fetchKey({uri: key.uri, onBehalfOf: jim.id});
593
- })
594
- .then(() => {
595
- expect.fail(
596
- 'It should not be possible to retrieve a key on behalf of another user without the spark.kms_orgagent role'
597
- );
598
- })
599
- .catch((error) => {
600
- // Expect a Forbidden error
601
- expect(error.body.status).to.equal(403);
602
- }));
603
-
604
- it('error retrieving key, on behalf of another user, without spark.kms_orgagent role', () =>
605
- spock.webex.internal.encryption.kms
606
- .createUnboundKeys({count: 1})
607
- .then(([k]) => {
608
- key = k;
609
-
610
- // Normal user McCoy fails to fetch a key on behalf of Spock
611
- return mccoy.webex.internal.encryption.kms.fetchKey({
612
- uri: key.uri,
613
- onBehalfOf: spock.id,
614
- });
615
- })
616
- .then(() => {
617
- expect.fail(
618
- 'It should not be possible to retrieve a key on behalf of another user without the spark.kms_orgagent role'
619
- );
620
- })
621
- .catch((error) => {
622
- // Expect a Forbidden error
623
- expect(error.body.status).to.equal(403);
624
- }));
625
-
626
- it('retrieve key on behalf of other users in quick succession', () => {
627
- let spockKey, mccoyKey;
628
-
629
- return Promise.all([
630
- spock.webex.internal.encryption.kms.createUnboundKeys({count: 1}),
631
- mccoy.webex.internal.encryption.kms.createUnboundKeys({count: 1}),
632
- ])
633
- .then(([[spockK], [mccoyK]]) => {
634
- spockKey = spockK;
635
- mccoyKey = mccoyK;
636
-
637
- // Compliance Officer Jim fetches keys on behalf of users
638
- return Promise.all([
639
- jim.webex.internal.encryption.kms.fetchKey({uri: spockKey.uri, onBehalfOf: spock.id}),
640
- jim.webex.internal.encryption.kms.fetchKey({uri: mccoyKey.uri, onBehalfOf: mccoy.id}),
641
- ]);
642
- })
643
- .then(([spockK, mccoyK]) => {
644
- assert.property(spockK, 'uri');
645
- assert.property(spockK, 'jwk');
646
- assert.notEqual(spockK, spockKey);
647
- assert.equal(spockK.uri, spockKey.uri);
648
-
649
- assert.property(mccoyK, 'uri');
650
- assert.property(mccoyK, 'jwk');
651
- assert.notEqual(mccoyK, mccoyKey);
652
- assert.equal(mccoyK.uri, mccoyKey.uri);
653
- });
654
- });
655
- });
656
-
657
- describe('#ping()', () => {
658
- it('sends a ping to the kms', () =>
659
- webex.internal.encryption.kms.ping().then((res) => {
660
- assert.property(res, 'status');
661
- assert.equal(res.status, 200);
662
- assert.property(res, 'requestId');
663
- }));
664
- });
665
-
666
- describe('when ecdhe negotiation times out', () => {
667
- let originalKmsTimeout, webex2, spy;
668
-
669
- before('create test user', () =>
670
- testUsers.create({count: 1}).then(([u]) => {
671
- webex2 = new WebexCore({
672
- credentials: {
673
- authorization: u.token,
674
- },
675
- });
676
- assert.isTrue(webex.canAuthorize);
677
- })
678
- );
679
-
680
- after(() => webex2 && webex2.internal.mercury.disconnect());
681
-
682
- beforeEach('alter config', () => {
683
- originalKmsTimeout = webex2.config.encryption.kmsInitialTimeout;
684
- webex2.config.encryption.kmsInitialTimeout = 100;
685
- spy = sinon.spy(webex2.internal.encryption.kms, 'prepareRequest');
686
- });
687
-
688
- afterEach(() => {
689
- webex2.config.encryption.kmsInitialTimeout = originalKmsTimeout;
690
- });
691
-
692
- afterEach(() => spy.restore());
693
-
694
- it('handles late ecdhe responses', () =>
695
- webex2.internal.encryption.kms.ping().then(() => {
696
- // callCount should be at least 3:
697
- // 1 for the initial ping message
698
- // 1 when the ecdh key gets renegotiated
699
- // 1 when the pings gets sent again
700
- debug(`ecdhe: spy call count: ${spy.callCount}`);
701
- assert.isAbove(
702
- spy.callCount,
703
- 2,
704
- "If this test fails, we've made previously-assumed-to-be-impossible performance gains in cloudapps; please update this test accordingly."
705
- );
706
- }));
707
-
708
- describe('when ecdhe is renegotiated', () => {
709
- let ecdhMaxTimeout;
710
-
711
- before('alter config', () => {
712
- ecdhMaxTimeout = webex2.config.encryption.ecdhMaxTimeout;
713
- webex2.config.encryption.ecdhMaxTimeout = 2000;
714
- });
715
-
716
- after(() => {
717
- webex2.config.encryption.ecdhMaxTimeout = ecdhMaxTimeout;
718
- });
719
-
720
- it('limits the number of retries', () =>
721
- webex2.internal.encryption.kms.ping().then(() => {
722
- debug(`retry: spy call count: ${spy.callCount}`);
723
- assert.isBelow(spy.callCount, 5);
724
- }));
725
- });
726
- });
727
-
728
- describe('when the kms is in another org', () => {
729
- let fedWebex;
730
-
731
- before('create test user in other org', () =>
732
- testUsers
733
- .create({
734
- count: 1,
735
- config: {
736
- email: `webex-js-sdk--kms-fed--${uuid.v4()}@wx2.example.com`,
737
- entitlements: ['webExSquared'],
738
- orgId: 'kmsFederation',
739
- },
740
- })
741
- .then((users) => {
742
- const fedUser = users[0];
743
-
744
- assert.equal(fedUser.orgId, '75dcf6c2-247d-4e3d-a32c-ff3ee28398eb');
745
- assert.notEqual(fedUser.orgId, spock.orgId);
746
-
747
- fedWebex = new WebexCore({
748
- credentials: {
749
- authorization: fedUser.token,
750
- },
751
- });
752
- assert.isTrue(fedWebex.canAuthorize);
753
- })
754
- );
755
-
756
- before('connect federated user to mercury', () => fedWebex.internal.mercury.connect());
757
-
758
- after(() => fedWebex && fedWebex.internal.mercury.disconnect());
759
-
760
- it('responds to pings', () =>
761
- fedWebex.internal.encryption.kms.ping().then((res) => {
762
- assert.property(res, 'status');
763
- assert.equal(res.status, 200);
764
- assert.property(res, 'requestId');
765
- }));
766
-
767
- let key;
768
-
769
- it('lets federated users retrieve keys from the main org', () =>
770
- webex.internal.encryption.kms
771
- .createUnboundKeys({count: 1})
772
- .then(([k]) => {
773
- key = k;
774
-
775
- return webex.internal.encryption.kms.createResource({
776
- userIds: [webex.internal.device.userId, fedWebex.internal.device.userId],
777
- key,
778
- });
779
- })
780
- .then(() => fedWebex.internal.encryption.kms.fetchKey({uri: key.uri}))
781
- .then((fedKey) => assert.equal(fedKey.keyUri, key.keyUri)));
782
-
783
- let fedKey;
784
-
785
- it('lets non-federated users retrieve keys from the federated org', () =>
786
- fedWebex.internal.encryption.kms
787
- .createUnboundKeys({count: 1})
788
- .then(([k]) => {
789
- fedKey = k;
790
-
791
- return fedWebex.internal.encryption.kms.createResource({
792
- userIds: [fedWebex.internal.device.userId, webex.internal.device.userId],
793
- key: fedKey,
794
- });
795
- })
796
- .then(() => webex.internal.encryption.kms.fetchKey({uri: fedKey.uri}))
797
- .then((key) => assert.equal(key.keyUri, fedKey.keyUri)));
798
- });
799
- });
800
- });
1
+ /* eslint-env browser */
2
+ /*!
3
+ * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
4
+ */
5
+
6
+ import '@webex/internal-plugin-encryption';
7
+
8
+ import {assert, expect} from '@webex/test-helper-chai';
9
+ import sinon from 'sinon';
10
+ import WebexCore from '@webex/webex-core';
11
+ import testUsers from '@webex/test-helper-test-users';
12
+ import uuid from 'uuid';
13
+ import {skipInBrowser} from '@webex/test-helper-mocha';
14
+
15
+ const debug = require('debug')('kms');
16
+
17
+ describe('Encryption', function () {
18
+ this.timeout(30000);
19
+ describe('KMS', () => {
20
+ let mccoy, webex, spock;
21
+
22
+ function str2ab(str) {
23
+ const buf = new ArrayBuffer(str.length);
24
+ const bufView = new Uint8Array(buf);
25
+
26
+ for (let i = 0, strLen = str.length; i < strLen; i += 1) {
27
+ bufView[i] = str.charCodeAt(i);
28
+ }
29
+
30
+ return buf;
31
+ }
32
+
33
+ function arrayBufferToBase64(buffer) {
34
+ let binary = '';
35
+ const bytes = new Uint8Array(buffer);
36
+ const len = bytes.byteLength;
37
+
38
+ for (let i = 0; i < len; i += 1) {
39
+ binary += String.fromCharCode(bytes[i]);
40
+ }
41
+
42
+ return window.btoa(binary);
43
+ }
44
+
45
+ before('create test user', () =>
46
+ testUsers.create({count: 2, config: {roles: [{name: 'id_full_admin'}]}}).then((users) => {
47
+ spock = users[0];
48
+ webex = new WebexCore({
49
+ credentials: {
50
+ authorization: spock.token,
51
+ },
52
+ });
53
+ spock.webex = webex;
54
+ assert.isTrue(webex.canAuthorize);
55
+
56
+ mccoy = users[1];
57
+ mccoy.webex = new WebexCore({
58
+ credentials: {
59
+ authorization: mccoy.token,
60
+ },
61
+ });
62
+
63
+ assert.isTrue(mccoy.webex.canAuthorize);
64
+
65
+ return mccoy.webex.internal.device.register();
66
+ })
67
+ );
68
+
69
+ after(() => webex && webex.internal.mercury.disconnect());
70
+
71
+ let expiredUser;
72
+
73
+ // TODO: Re-enable SPARK-133280
74
+ it.skip('errs using an invalid token', () =>
75
+ testUsers
76
+ .create({count: 1})
77
+ .then((users) => {
78
+ [expiredUser] = users;
79
+ expiredUser.webex = new WebexCore({
80
+ credentials: {
81
+ authorization: 'invalidToken',
82
+ },
83
+ });
84
+ expiredUser.webex.internal.device.register();
85
+ })
86
+ .then(() => expiredUser.webex.internal.encryption.kms.createUnboundKeys({count: 1}))
87
+ .catch((err) => {
88
+ assert.equal(err.statusCode, 401);
89
+ }));
90
+
91
+ describe('#createResource()', () => {
92
+ it('creates a kms resource object', () =>
93
+ webex.internal.encryption.kms.createUnboundKeys({count: 1}).then(([key]) =>
94
+ webex.internal.encryption.kms
95
+ .createResource({
96
+ userIds: [webex.internal.device.userId],
97
+ key,
98
+ })
99
+ .then((kro) => {
100
+ assert.property(kro, 'uri');
101
+ assert.property(kro, 'keyUris');
102
+ assert.lengthOf(kro.keyUris, 1);
103
+ assert.include(kro.keyUris, key.uri);
104
+ assert.property(kro, 'authorizationUris');
105
+ assert.lengthOf(kro.authorizationUris, 1);
106
+ })
107
+ ));
108
+ });
109
+
110
+ describe('#addAuthorization()', () => {
111
+ let boundedKeyUri, kro, otherKro;
112
+
113
+ before('create a resource', () =>
114
+ webex.internal.encryption.kms
115
+ .createUnboundKeys({count: 1})
116
+ .then(([key]) =>
117
+ webex.internal.encryption.kms.createResource({
118
+ key,
119
+ })
120
+ )
121
+ .then((k) => {
122
+ kro = k;
123
+ boundedKeyUri = kro.keyUris[0];
124
+ assert.lengthOf(kro.authorizationUris, 1);
125
+ })
126
+ );
127
+
128
+ it('authorizes a user to a key', () =>
129
+ webex.internal.encryption.kms
130
+ .addAuthorization({
131
+ userIds: [mccoy.webex.internal.device.userId],
132
+ kroUri: kro.uri,
133
+ })
134
+ .then(([auth]) => {
135
+ assert.equal(auth.resourceUri, kro.uri);
136
+ assert.equal(auth.authId, mccoy.webex.internal.device.userId);
137
+
138
+ return mccoy.webex.internal.encryption.kms.fetchKey({uri: boundedKeyUri});
139
+ }));
140
+
141
+ it('authorizes a resource to a key', () =>
142
+ webex.internal.encryption.kms
143
+ .createUnboundKeys({count: 1})
144
+ .then(([key]) => webex.internal.encryption.kms.createResource({key}))
145
+ .then((k) => {
146
+ otherKro = k;
147
+
148
+ return webex.internal.encryption.kms.addAuthorization({
149
+ authIds: [otherKro.uri],
150
+ kro,
151
+ });
152
+ })
153
+ .then(([auth]) => {
154
+ assert.equal(auth.resourceUri, kro.uri);
155
+ assert.equal(auth.authId, otherKro.uri);
156
+ }));
157
+ });
158
+
159
+ /**
160
+ *Test listAuthorizations function
161
+ *Setup: Create a resource, then authorize a user to a key, then authorize a resource to a key
162
+ *Test:
163
+ *1)Invoke listAuthorizations on the resource, verify that the function returns the array that contains two authorized items
164
+ *2)Remove the authorized user from the resource, then invoke listAuthorizations again, verify that the returned array doesn't have the user.
165
+ *3)Remove the authorized resource from the resource again, then invoke listAuthorizations again, verify that he returned array doesn't have the resource.
166
+ */
167
+ describe('#listAuthorizations()', () => {
168
+ let boundedKeyUri, kro, otherKro;
169
+ let testResourceId;
170
+
171
+ before('creates a resource', () =>
172
+ webex.internal.encryption.kms
173
+ .createUnboundKeys({count: 1})
174
+ .then(([key]) =>
175
+ webex.internal.encryption.kms.createResource({
176
+ key,
177
+ })
178
+ )
179
+ .then((k) => {
180
+ kro = k;
181
+ boundedKeyUri = kro.keyUris[0];
182
+ assert.lengthOf(kro.authorizationUris, 1);
183
+ })
184
+ );
185
+
186
+ before('authorizes a user to a key', () =>
187
+ webex.internal.encryption.kms
188
+ .addAuthorization({
189
+ userIds: [mccoy.webex.internal.device.userId],
190
+ kroUri: kro.uri,
191
+ })
192
+ .then(([auth]) => {
193
+ assert.equal(auth.resourceUri, kro.uri);
194
+ assert.equal(auth.authId, mccoy.webex.internal.device.userId);
195
+
196
+ return mccoy.webex.internal.encryption.kms.fetchKey({uri: boundedKeyUri});
197
+ })
198
+ );
199
+
200
+ before('authorizes a resource to a key', () =>
201
+ webex.internal.encryption.kms
202
+ .createUnboundKeys({count: 1})
203
+ .then(([key]) => webex.internal.encryption.kms.createResource({key}))
204
+ .then((k) => {
205
+ otherKro = k;
206
+ testResourceId = otherKro.uri;
207
+
208
+ return webex.internal.encryption.kms.addAuthorization({
209
+ authIds: [testResourceId],
210
+ kro,
211
+ });
212
+ })
213
+ .then(([auth]) => {
214
+ assert.equal(auth.resourceUri, kro.uri);
215
+ assert.equal(auth.authId, otherKro.uri);
216
+ })
217
+ );
218
+
219
+ it('list authorizations', () =>
220
+ webex.internal.encryption.kms
221
+ .listAuthorizations({kroUri: kro.uri})
222
+ .then((authorizations) => {
223
+ assert.equal(authorizations.length, 3);
224
+ assert.include(
225
+ authorizations.map((a) => a.authId),
226
+ mccoy.webex.internal.device.userId
227
+ );
228
+ assert.include(
229
+ authorizations.map((a) => a.authId),
230
+ spock.id
231
+ );
232
+ assert.include(
233
+ authorizations.map((a) => a.resourceUri),
234
+ testResourceId
235
+ );
236
+ assert.include(
237
+ authorizations.map((a) => a.resourceUri),
238
+ kro.uri
239
+ );
240
+ }));
241
+
242
+ it('rejects normally for users that are not authorized', () =>
243
+ testUsers.create({count: 1}).then(([user]) => {
244
+ const us = new WebexCore({
245
+ credentials: {
246
+ authorization: user.token,
247
+ },
248
+ });
249
+
250
+ return assert
251
+ .isRejected(us.internal.encryption.kms.listAuthorizations({kroUri: kro.uri}))
252
+ .then((err) => {
253
+ console.error(err);
254
+ assert.equal(err.status, 403, 'We should get a Not Authorized response from kms');
255
+ })
256
+ .then(() => us.internal.mercury.disconnect());
257
+ }));
258
+
259
+ it('remove the user and verify this user is not in the authorization list ', () =>
260
+ webex.internal.encryption.kms
261
+ .removeAuthorization({
262
+ userId: mccoy.webex.internal.device.userId,
263
+ kroUri: kro.uri,
264
+ })
265
+ .then(([auth]) => {
266
+ assert.equal(auth.resourceUri, kro.uri);
267
+ assert.equal(auth.authId, mccoy.webex.internal.device.userId);
268
+
269
+ return webex.internal.encryption.kms
270
+ .listAuthorizations({kro})
271
+ .then((authorizations) => {
272
+ assert.equal(authorizations.length, 2);
273
+ assert.include(
274
+ authorizations.map((a) => a.authId),
275
+ spock.id
276
+ );
277
+ assert.include(
278
+ authorizations.map((a) => a.resourceUri),
279
+ testResourceId
280
+ );
281
+ assert.include(
282
+ authorizations.map((a) => a.resourceUri),
283
+ kro.uri
284
+ );
285
+ });
286
+ }));
287
+
288
+ it('remove the resource and verify this resource is not in the authorizaiton list', () =>
289
+ webex.internal.encryption.kms
290
+ .removeAuthorization({
291
+ userId: testResourceId,
292
+ kroUri: kro.uri,
293
+ })
294
+ .then(([auth]) => {
295
+ assert.equal(auth.resourceUri, kro.uri);
296
+ assert.equal(auth.authId, testResourceId);
297
+
298
+ return webex.internal.encryption.kms
299
+ .listAuthorizations({kroUri: kro.uri})
300
+ .then((authorizations) => {
301
+ assert.equal(authorizations.length, 1);
302
+ assert.include(
303
+ authorizations.map((a) => a.authId),
304
+ spock.id
305
+ );
306
+ assert.include(
307
+ authorizations.map((a) => a.resourceUri),
308
+ kro.uri
309
+ );
310
+ });
311
+ }));
312
+ });
313
+
314
+ describe('#removeAuthorization()', () => {
315
+ let boundedKeyUri, kro, otherKro;
316
+
317
+ before('create resource', () =>
318
+ webex.internal.encryption.kms
319
+ .createUnboundKeys({count: 1})
320
+ .then(([key]) =>
321
+ webex.internal.encryption.kms.createResource({
322
+ key,
323
+ })
324
+ )
325
+ .then((k) => {
326
+ kro = k;
327
+ boundedKeyUri = kro.keyUris[0];
328
+ assert.lengthOf(kro.authorizationUris, 1);
329
+ })
330
+ );
331
+
332
+ before('create another resource', () =>
333
+ webex.internal.encryption.kms
334
+ .createUnboundKeys({count: 1})
335
+ .then(([key]) =>
336
+ webex.internal.encryption.kms.createResource({
337
+ key,
338
+ })
339
+ )
340
+ .then((k) => {
341
+ otherKro = k;
342
+ })
343
+ );
344
+
345
+ before('add auths to resource', () =>
346
+ webex.internal.encryption.kms
347
+ .addAuthorization({
348
+ authIds: [otherKro.uri, mccoy.webex.internal.device.userId],
349
+ kro,
350
+ })
351
+ .then(([kroAuth, userAuth]) => {
352
+ assert.equal(kroAuth.authId, otherKro.uri);
353
+ assert.equal(userAuth.authId, mccoy.webex.internal.device.userId);
354
+ })
355
+ );
356
+
357
+ it('deauthorizes a user from a key', () =>
358
+ webex.internal.encryption.kms
359
+ .removeAuthorization({
360
+ userId: mccoy.webex.internal.device.userId,
361
+ kroUri: kro.uri,
362
+ })
363
+ .then(([auth]) => {
364
+ assert.equal(auth.resourceUri, kro.uri);
365
+ assert.equal(auth.authId, mccoy.webex.internal.device.userId);
366
+
367
+ return assert.isRejected(
368
+ mccoy.webex.internal.encryption.kms.fetchKey({uri: boundedKeyUri})
369
+ );
370
+ }));
371
+
372
+ it('deauthorizes a resource from a key', () =>
373
+ webex.internal.encryption.kms
374
+ .removeAuthorization({
375
+ authId: otherKro.uri,
376
+ kro,
377
+ })
378
+ .then(([auth]) => {
379
+ assert.equal(auth.resourceUri, kro.uri);
380
+ assert.equal(auth.authId, otherKro.uri);
381
+ }));
382
+ });
383
+
384
+ describe('#bindKey()', () => {
385
+ let key2, kro;
386
+
387
+ it('binds a resource to a key', () =>
388
+ webex.internal.encryption.kms
389
+ .createUnboundKeys({count: 2})
390
+ .then((keys) => {
391
+ key2 = keys[1];
392
+
393
+ return webex.internal.encryption.kms.createResource({
394
+ userIds: [webex.internal.device.userId],
395
+ key: keys[0],
396
+ });
397
+ })
398
+ .then((k) => {
399
+ kro = k;
400
+
401
+ return webex.internal.encryption.kms.bindKey({kro, key: key2});
402
+ })
403
+ .then((key) => {
404
+ assert.equal(key.uri, key2.uri);
405
+ assert.property(key, 'bindDate');
406
+ assert.property(key, 'resourceUri');
407
+ assert.equal(key.resourceUri, kro.uri);
408
+ }));
409
+ });
410
+
411
+ describe('#createUnboundKeys()', () => {
412
+ it('requests unbound keys from the KMS', () =>
413
+ webex.internal.encryption.kms.createUnboundKeys({count: 2}).then((keys) => {
414
+ assert.lengthOf(keys, 2);
415
+
416
+ const [key1, key2] = keys;
417
+
418
+ assert.property(key1, 'uri');
419
+ assert.property(key1, 'jwk');
420
+ assert.property(key2, 'uri');
421
+ assert.property(key2, 'jwk');
422
+ }));
423
+ });
424
+
425
+ describe('upload customer master key', () => {
426
+ let uploadedkeyId;
427
+
428
+ /* eslint-disable no-unused-expressions */
429
+ skipInBrowser(it)('upload customer master key', () =>
430
+ webex.internal.encryption.kms
431
+ .deleteAllCustomerMasterKeys({assignedOrgId: spock.orgId})
432
+ .then(() => webex.internal.encryption.kms.fetchPublicKey({assignedOrgId: spock.orgId}))
433
+ .then((publicKey) => {
434
+ assert.isNotEmpty(publicKey);
435
+ const pemHeader = '-----BEGIN PUBLIC KEY-----';
436
+ const pemFooter = '-----END PUBLIC KEY-----';
437
+ const publicContent = publicKey.substring(
438
+ pemHeader.length,
439
+ publicKey.length - pemFooter.length
440
+ );
441
+ const binaryDerString = window.atob(publicContent);
442
+ // convert from a binary string to an ArrayBuffer
443
+ const binaryDer = str2ab(binaryDerString);
444
+
445
+ return window.crypto.subtle.importKey(
446
+ 'spki',
447
+ binaryDer,
448
+ {
449
+ name: 'RSA-OAEP',
450
+ hash: 'SHA-256',
451
+ },
452
+ true,
453
+ ['encrypt']
454
+ );
455
+ })
456
+ .then((publicKey) => {
457
+ const buf = window.crypto.getRandomValues(new Uint8Array(16));
458
+
459
+ return window.crypto.subtle.encrypt(
460
+ {
461
+ name: 'RSA-OAEP',
462
+ },
463
+ publicKey,
464
+ buf
465
+ );
466
+ })
467
+ .then((encryptedData) =>
468
+ webex.internal.encryption.kms.uploadCustomerMasterKey({
469
+ assignedOrgId: spock.orgId,
470
+ customerMasterKey: arrayBufferToBase64(encryptedData),
471
+ })
472
+ )
473
+ .then((uploadRes) => {
474
+ uploadedkeyId = uploadRes.customerMasterKeys[0].uri;
475
+
476
+ return webex.internal.encryption.kms.listAllCustomerMasterKey({
477
+ assignedOrgId: spock.orgId,
478
+ });
479
+ })
480
+ .then((listCmksRes) => {
481
+ const cmks = listCmksRes.customerMasterKeys;
482
+ const uploadedCmk = cmks.find((cmk) => cmk.uri === uploadedkeyId);
483
+
484
+ expect(uploadedCmk).to.not.be.null;
485
+
486
+ return webex.internal.encryption.kms.changeCustomerMasterKeyState({
487
+ keyId: uploadedkeyId,
488
+ keyState: 'ACTIVE',
489
+ assignedOrgId: spock.orgId,
490
+ });
491
+ })
492
+ .then((activeRes) => {
493
+ expect(activeRes.customerMasterKeys[0].usageState).to.have.string('ACTIVE');
494
+
495
+ // return webex.internal.encryption.kms.useGlobalMasterKey({assignedOrgId: spock.orgId});
496
+ return webex.internal.encryption.kms.deleteAllCustomerMasterKeys({
497
+ assignedOrgId: spock.orgId,
498
+ });
499
+ })
500
+ );
501
+ });
502
+
503
+ describe('#fetchKey()', () => {
504
+ let key;
505
+
506
+ it('retrieves a specific key', () =>
507
+ webex.internal.encryption.kms
508
+ .createUnboundKeys({count: 1})
509
+ .then(([k]) => {
510
+ key = k;
511
+
512
+ return webex.internal.encryption.kms.fetchKey({uri: key.uri});
513
+ })
514
+ .then((key2) => {
515
+ assert.property(key2, 'uri');
516
+ assert.property(key2, 'jwk');
517
+ assert.notEqual(key2, key);
518
+ assert.equal(key2.uri, key.uri);
519
+ }));
520
+ });
521
+
522
+ describe('#fetchKey(onBehalfOf)', () => {
523
+ let jim;
524
+
525
+ before('create compliance officer test user', () =>
526
+ testUsers
527
+ .create({
528
+ count: 1,
529
+ config: {
530
+ roles: [{name: 'spark.kms_orgagent'}],
531
+ },
532
+ })
533
+ .then((users) => {
534
+ jim = users[0];
535
+
536
+ jim.webex = new WebexCore({
537
+ credentials: {
538
+ authorization: jim.token,
539
+ },
540
+ });
541
+ assert.isTrue(jim.webex.canAuthorize);
542
+ })
543
+ );
544
+
545
+ before('connect compliance officer to mercury', () => jim.webex.internal.mercury.connect());
546
+
547
+ after(() => jim.webex && jim.webex.internal.mercury.disconnect());
548
+
549
+ let key;
550
+
551
+ it('retrieve key on behalf of another user', () =>
552
+ spock.webex.internal.encryption.kms
553
+ .createUnboundKeys({count: 1})
554
+ .then(([k]) => {
555
+ key = k;
556
+
557
+ // Compliance Officer Jim fetches a key on behalf of Spock
558
+ return jim.webex.internal.encryption.kms.fetchKey({uri: key.uri, onBehalfOf: spock.id});
559
+ })
560
+ .then((key2) => {
561
+ assert.property(key2, 'uri');
562
+ assert.property(key2, 'jwk');
563
+ assert.notEqual(key2, key);
564
+ assert.equal(key2.uri, key.uri);
565
+ }));
566
+
567
+ it('retrieve key on behalf of self', () =>
568
+ jim.webex.internal.encryption.kms
569
+ .createUnboundKeys({count: 1})
570
+ .then(([k]) => {
571
+ key = k;
572
+
573
+ // Compliance Officer Jim fetches a key on behalf of himself
574
+ // This covers an edge case documented by https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-240862.
575
+ return jim.webex.internal.encryption.kms.fetchKey({uri: key.uri, onBehalfOf: jim.id});
576
+ })
577
+ .then((key2) => {
578
+ assert.property(key2, 'uri');
579
+ assert.property(key2, 'jwk');
580
+ assert.notEqual(key2, key);
581
+ assert.equal(key2.uri, key.uri);
582
+ }));
583
+
584
+ // Spock creates the key and Jim is not in the KRO so he should not have access
585
+ it('retrieve key on behalf of self but self does not have access', () =>
586
+ spock.webex.internal.encryption.kms
587
+ .createUnboundKeys({count: 1})
588
+ .then(([k]) => {
589
+ key = k;
590
+
591
+ // Compliance Officer Jim fetches a key on behalf of himself but he is not in the KRO
592
+ return jim.webex.internal.encryption.kms.fetchKey({uri: key.uri, onBehalfOf: jim.id});
593
+ })
594
+ .then(() => {
595
+ expect.fail(
596
+ 'It should not be possible to retrieve a key on behalf of another user without the spark.kms_orgagent role'
597
+ );
598
+ })
599
+ .catch((error) => {
600
+ // Expect a Forbidden error
601
+ expect(error.body.status).to.equal(403);
602
+ }));
603
+
604
+ it('error retrieving key, on behalf of another user, without spark.kms_orgagent role', () =>
605
+ spock.webex.internal.encryption.kms
606
+ .createUnboundKeys({count: 1})
607
+ .then(([k]) => {
608
+ key = k;
609
+
610
+ // Normal user McCoy fails to fetch a key on behalf of Spock
611
+ return mccoy.webex.internal.encryption.kms.fetchKey({
612
+ uri: key.uri,
613
+ onBehalfOf: spock.id,
614
+ });
615
+ })
616
+ .then(() => {
617
+ expect.fail(
618
+ 'It should not be possible to retrieve a key on behalf of another user without the spark.kms_orgagent role'
619
+ );
620
+ })
621
+ .catch((error) => {
622
+ // Expect a Forbidden error
623
+ expect(error.body.status).to.equal(403);
624
+ }));
625
+
626
+ it('retrieve key on behalf of other users in quick succession', () => {
627
+ let spockKey, mccoyKey;
628
+
629
+ return Promise.all([
630
+ spock.webex.internal.encryption.kms.createUnboundKeys({count: 1}),
631
+ mccoy.webex.internal.encryption.kms.createUnboundKeys({count: 1}),
632
+ ])
633
+ .then(([[spockK], [mccoyK]]) => {
634
+ spockKey = spockK;
635
+ mccoyKey = mccoyK;
636
+
637
+ // Compliance Officer Jim fetches keys on behalf of users
638
+ return Promise.all([
639
+ jim.webex.internal.encryption.kms.fetchKey({uri: spockKey.uri, onBehalfOf: spock.id}),
640
+ jim.webex.internal.encryption.kms.fetchKey({uri: mccoyKey.uri, onBehalfOf: mccoy.id}),
641
+ ]);
642
+ })
643
+ .then(([spockK, mccoyK]) => {
644
+ assert.property(spockK, 'uri');
645
+ assert.property(spockK, 'jwk');
646
+ assert.notEqual(spockK, spockKey);
647
+ assert.equal(spockK.uri, spockKey.uri);
648
+
649
+ assert.property(mccoyK, 'uri');
650
+ assert.property(mccoyK, 'jwk');
651
+ assert.notEqual(mccoyK, mccoyKey);
652
+ assert.equal(mccoyK.uri, mccoyKey.uri);
653
+ });
654
+ });
655
+ });
656
+
657
+ describe('#ping()', () => {
658
+ it('sends a ping to the kms', () =>
659
+ webex.internal.encryption.kms.ping().then((res) => {
660
+ assert.property(res, 'status');
661
+ assert.equal(res.status, 200);
662
+ assert.property(res, 'requestId');
663
+ }));
664
+ });
665
+
666
+ describe('when ecdhe negotiation times out', () => {
667
+ let originalKmsTimeout, webex2, spy;
668
+
669
+ before('create test user', () =>
670
+ testUsers.create({count: 1}).then(([u]) => {
671
+ webex2 = new WebexCore({
672
+ credentials: {
673
+ authorization: u.token,
674
+ },
675
+ });
676
+ assert.isTrue(webex.canAuthorize);
677
+ })
678
+ );
679
+
680
+ after(() => webex2 && webex2.internal.mercury.disconnect());
681
+
682
+ beforeEach('alter config', () => {
683
+ originalKmsTimeout = webex2.config.encryption.kmsInitialTimeout;
684
+ webex2.config.encryption.kmsInitialTimeout = 100;
685
+ spy = sinon.spy(webex2.internal.encryption.kms, 'prepareRequest');
686
+ });
687
+
688
+ afterEach(() => {
689
+ webex2.config.encryption.kmsInitialTimeout = originalKmsTimeout;
690
+ });
691
+
692
+ afterEach(() => spy.restore());
693
+
694
+ it('handles late ecdhe responses', () =>
695
+ webex2.internal.encryption.kms.ping().then(() => {
696
+ // callCount should be at least 3:
697
+ // 1 for the initial ping message
698
+ // 1 when the ecdh key gets renegotiated
699
+ // 1 when the pings gets sent again
700
+ debug(`ecdhe: spy call count: ${spy.callCount}`);
701
+ assert.isAbove(
702
+ spy.callCount,
703
+ 2,
704
+ "If this test fails, we've made previously-assumed-to-be-impossible performance gains in cloudapps; please update this test accordingly."
705
+ );
706
+ }));
707
+
708
+ describe('when ecdhe is renegotiated', () => {
709
+ let ecdhMaxTimeout;
710
+
711
+ before('alter config', () => {
712
+ ecdhMaxTimeout = webex2.config.encryption.ecdhMaxTimeout;
713
+ webex2.config.encryption.ecdhMaxTimeout = 2000;
714
+ });
715
+
716
+ after(() => {
717
+ webex2.config.encryption.ecdhMaxTimeout = ecdhMaxTimeout;
718
+ });
719
+
720
+ it('limits the number of retries', () =>
721
+ webex2.internal.encryption.kms.ping().then(() => {
722
+ debug(`retry: spy call count: ${spy.callCount}`);
723
+ assert.isBelow(spy.callCount, 5);
724
+ }));
725
+ });
726
+ });
727
+
728
+ describe('when the kms is in another org', () => {
729
+ let fedWebex;
730
+
731
+ before('create test user in other org', () =>
732
+ testUsers
733
+ .create({
734
+ count: 1,
735
+ config: {
736
+ email: `webex-js-sdk--kms-fed--${uuid.v4()}@wx2.example.com`,
737
+ entitlements: ['webExSquared'],
738
+ orgId: 'kmsFederation',
739
+ },
740
+ })
741
+ .then((users) => {
742
+ const fedUser = users[0];
743
+
744
+ assert.equal(fedUser.orgId, '75dcf6c2-247d-4e3d-a32c-ff3ee28398eb');
745
+ assert.notEqual(fedUser.orgId, spock.orgId);
746
+
747
+ fedWebex = new WebexCore({
748
+ credentials: {
749
+ authorization: fedUser.token,
750
+ },
751
+ });
752
+ assert.isTrue(fedWebex.canAuthorize);
753
+ })
754
+ );
755
+
756
+ before('connect federated user to mercury', () => fedWebex.internal.mercury.connect());
757
+
758
+ after(() => fedWebex && fedWebex.internal.mercury.disconnect());
759
+
760
+ it('responds to pings', () =>
761
+ fedWebex.internal.encryption.kms.ping().then((res) => {
762
+ assert.property(res, 'status');
763
+ assert.equal(res.status, 200);
764
+ assert.property(res, 'requestId');
765
+ }));
766
+
767
+ let key;
768
+
769
+ it('lets federated users retrieve keys from the main org', () =>
770
+ webex.internal.encryption.kms
771
+ .createUnboundKeys({count: 1})
772
+ .then(([k]) => {
773
+ key = k;
774
+
775
+ return webex.internal.encryption.kms.createResource({
776
+ userIds: [webex.internal.device.userId, fedWebex.internal.device.userId],
777
+ key,
778
+ });
779
+ })
780
+ .then(() => fedWebex.internal.encryption.kms.fetchKey({uri: key.uri}))
781
+ .then((fedKey) => assert.equal(fedKey.keyUri, key.keyUri)));
782
+
783
+ let fedKey;
784
+
785
+ it('lets non-federated users retrieve keys from the federated org', () =>
786
+ fedWebex.internal.encryption.kms
787
+ .createUnboundKeys({count: 1})
788
+ .then(([k]) => {
789
+ fedKey = k;
790
+
791
+ return fedWebex.internal.encryption.kms.createResource({
792
+ userIds: [fedWebex.internal.device.userId, webex.internal.device.userId],
793
+ key: fedKey,
794
+ });
795
+ })
796
+ .then(() => webex.internal.encryption.kms.fetchKey({uri: fedKey.uri}))
797
+ .then((key) => assert.equal(key.keyUri, fedKey.keyUri)));
798
+ });
799
+ });
800
+ });