@webex/internal-plugin-encryption 3.0.0-beta.4 → 3.0.0-beta.400
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/README.md +1 -3
- package/dist/config.js +0 -9
- package/dist/config.js.map +1 -1
- package/dist/constants.js +14 -0
- package/dist/constants.js.map +1 -0
- package/dist/encryption.js +25 -74
- package/dist/encryption.js.map +1 -1
- package/dist/ensure-buffer.browser.js +0 -12
- package/dist/ensure-buffer.browser.js.map +1 -1
- package/dist/ensure-buffer.js +5 -12
- package/dist/ensure-buffer.js.map +1 -1
- package/dist/index.js +7 -33
- package/dist/index.js.map +1 -1
- package/dist/kms-batcher.js +7 -30
- package/dist/kms-batcher.js.map +1 -1
- package/dist/kms-certificate-validation.js +24 -90
- package/dist/kms-certificate-validation.js.map +1 -1
- package/dist/kms-dry-error-interceptor.js +1 -23
- package/dist/kms-dry-error-interceptor.js.map +1 -1
- package/dist/kms-errors.js +21 -51
- package/dist/kms-errors.js.map +1 -1
- package/dist/kms.js +88 -218
- package/dist/kms.js.map +1 -1
- package/package.json +15 -15
- package/src/config.js +3 -3
- package/src/constants.js +3 -0
- package/src/encryption.js +74 -57
- package/src/ensure-buffer.browser.js +0 -1
- package/src/ensure-buffer.js +5 -5
- package/src/index.js +120 -96
- package/src/kms-batcher.js +53 -45
- package/src/kms-certificate-validation.js +48 -50
- package/src/kms-dry-error-interceptor.js +8 -4
- package/src/kms-errors.js +47 -16
- package/src/kms.js +219 -212
- package/test/integration/spec/encryption.js +313 -231
- package/test/integration/spec/kms.js +532 -405
- package/test/integration/spec/payload-transfom.js +69 -69
- package/test/unit/spec/encryption.js +21 -18
- package/test/unit/spec/kms-certificate-validation.js +76 -34
- package/test/unit/spec/kms-errors.js +70 -0
- package/test/unit/spec/kms.js +103 -0
package/src/index.js
CHANGED
|
@@ -8,126 +8,150 @@
|
|
|
8
8
|
// by using Promise.race to resolve replays (as more requests get enqueue for a
|
|
9
9
|
// specific action, accept whichever one completes first).
|
|
10
10
|
|
|
11
|
+
import '@webex/internal-plugin-device';
|
|
12
|
+
|
|
13
|
+
import '@webex/internal-plugin-mercury';
|
|
14
|
+
|
|
11
15
|
import {registerInternalPlugin} from '@webex/webex-core';
|
|
12
16
|
import {has, isObject, isString} from 'lodash';
|
|
13
17
|
|
|
14
18
|
import Encryption from './encryption';
|
|
15
19
|
import config from './config';
|
|
16
20
|
import {DryError} from './kms-errors';
|
|
17
|
-
|
|
18
|
-
import '@webex/internal-plugin-mercury';
|
|
21
|
+
|
|
19
22
|
import KmsDryErrorInterceptor from './kms-dry-error-interceptor';
|
|
20
23
|
|
|
21
24
|
let interceptors;
|
|
22
25
|
|
|
23
26
|
if (process.env.NODE_ENV === 'test') {
|
|
24
27
|
interceptors = {
|
|
25
|
-
KmsDryErrorInterceptor: KmsDryErrorInterceptor.create
|
|
28
|
+
KmsDryErrorInterceptor: KmsDryErrorInterceptor.create,
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
29
32
|
registerInternalPlugin('encryption', Encryption, {
|
|
30
33
|
payloadTransformer: {
|
|
31
|
-
predicates: [
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
34
|
+
predicates: [
|
|
35
|
+
{
|
|
36
|
+
name: 'encryptKmsMessage',
|
|
37
|
+
direction: 'outbound',
|
|
38
|
+
// I don't see any practical way to reduce complexity here.
|
|
39
|
+
// eslint-disable-next-line complexity
|
|
40
|
+
test(ctx, options) {
|
|
41
|
+
if (!has(options, 'body.kmsMessage')) {
|
|
42
|
+
return Promise.resolve(false);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isObject(options.body.kmsMessage)) {
|
|
46
|
+
return Promise.resolve(false);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// If this is a template for a kms message, assume another transform
|
|
50
|
+
// will fill it in later. This is a bit of a leaky abstraction, but the
|
|
51
|
+
// alternative is building a complex rules engine for controlling
|
|
52
|
+
// ordering of transforms
|
|
53
|
+
if (options.body.kmsMessage.keyUris && options.body.kmsMessage.keyUris.length === 0) {
|
|
54
|
+
return Promise.resolve(false);
|
|
55
|
+
}
|
|
56
|
+
if (
|
|
57
|
+
options.body.kmsMessage.resourceUri &&
|
|
58
|
+
(options.body.kmsMessage.resourceUri.includes('<KRO>') ||
|
|
59
|
+
options.body.kmsMessage.resourceUri.includes('<KEYURL>'))
|
|
60
|
+
) {
|
|
61
|
+
return Promise.resolve(false);
|
|
62
|
+
}
|
|
63
|
+
if (
|
|
64
|
+
options.body.kmsMessage.uri &&
|
|
65
|
+
(options.body.kmsMessage.uri.includes('<KRO>') ||
|
|
66
|
+
options.body.kmsMessage.uri.includes('<KEYURL>'))
|
|
67
|
+
) {
|
|
68
|
+
return Promise.resolve(false);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return Promise.resolve(true);
|
|
72
|
+
},
|
|
73
|
+
extract(options) {
|
|
74
|
+
return Promise.resolve(options.body);
|
|
75
|
+
},
|
|
60
76
|
},
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
{
|
|
78
|
+
name: 'decryptKmsMessage',
|
|
79
|
+
direction: 'inbound',
|
|
80
|
+
test(ctx, response) {
|
|
81
|
+
return Promise.resolve(
|
|
82
|
+
has(response, 'body.kmsMessage') && isString(response.body.kmsMessage)
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
extract(response) {
|
|
86
|
+
return Promise.resolve(response.body);
|
|
87
|
+
},
|
|
69
88
|
},
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
89
|
+
{
|
|
90
|
+
name: 'decryptErrorResponse',
|
|
91
|
+
direction: 'inbound',
|
|
92
|
+
test(ctx, reason) {
|
|
93
|
+
return Promise.resolve(Boolean(reason.body && reason.body.errorCode === 1900000));
|
|
94
|
+
},
|
|
95
|
+
extract(reason) {
|
|
96
|
+
return Promise.resolve(reason);
|
|
97
|
+
},
|
|
78
98
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return ctx.webex.internal.encryption.kms.prepareRequest(object.kmsMessage)
|
|
99
|
-
.then((req) => {
|
|
99
|
+
],
|
|
100
|
+
transforms: [
|
|
101
|
+
{
|
|
102
|
+
name: 'encryptKmsMessage',
|
|
103
|
+
fn(ctx, object) {
|
|
104
|
+
if (!object) {
|
|
105
|
+
return Promise.resolve();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!object.kmsMessage) {
|
|
109
|
+
return Promise.resolve();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (isString(object.kmsMessage)) {
|
|
113
|
+
return Promise.resolve();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return ctx.webex.internal.encryption.kms.prepareRequest(object.kmsMessage).then((req) => {
|
|
100
117
|
object.kmsMessage = req.wrapped;
|
|
101
118
|
});
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.
|
|
108
|
-
object.kmsMessage
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'decryptKmsMessage',
|
|
123
|
+
fn(ctx, object) {
|
|
124
|
+
return ctx.webex.internal.encryption.kms
|
|
125
|
+
.decryptKmsMessage(object.kmsMessage)
|
|
126
|
+
.then((kmsMessage) => {
|
|
127
|
+
object.kmsMessage = kmsMessage;
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'decryptErrorResponse',
|
|
133
|
+
fn(ctx, reason) {
|
|
134
|
+
const promises = reason.body.errors.map((error) =>
|
|
135
|
+
ctx.webex.internal.encryption.kms.decryptKmsMessage(error.description).then((desc) => {
|
|
136
|
+
error.description = desc;
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
promises.push(
|
|
141
|
+
ctx.webex.internal.encryption.kms
|
|
142
|
+
.decryptKmsMessage(reason.body.message)
|
|
143
|
+
.then((kmsMessage) => {
|
|
144
|
+
reason.body.message = kmsMessage;
|
|
145
|
+
})
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
return Promise.all(promises).then(() => Promise.reject(new DryError(reason)));
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
128
152
|
},
|
|
129
153
|
interceptors,
|
|
130
|
-
config
|
|
154
|
+
config,
|
|
131
155
|
});
|
|
132
156
|
|
|
133
157
|
export {default} from './encryption';
|
package/src/kms-batcher.js
CHANGED
|
@@ -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
|
|
|
@@ -23,14 +23,19 @@ const KmsBatcher = Batcher.extend({
|
|
|
23
23
|
processKmsMessageEvent(event) {
|
|
24
24
|
this.logger.info('kms-batcher: received kms message');
|
|
25
25
|
|
|
26
|
-
return Promise.all(
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
return Promise.all(
|
|
27
|
+
event.encryption.kmsMessages.map(
|
|
28
|
+
(kmsMessage) =>
|
|
29
|
+
new Promise((resolve) => {
|
|
30
|
+
/* istanbul ignore else */
|
|
31
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
32
|
+
this.logger.info('kms-batcher:', kmsMessage.body);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
resolve(this.acceptItem(kmsMessage));
|
|
36
|
+
})
|
|
37
|
+
)
|
|
38
|
+
);
|
|
34
39
|
},
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -39,30 +44,34 @@ const KmsBatcher = Batcher.extend({
|
|
|
39
44
|
* @returns {Promise<Object>}
|
|
40
45
|
*/
|
|
41
46
|
prepareItem(item) {
|
|
42
|
-
return this.getDeferredForRequest(item)
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
47
|
+
return this.getDeferredForRequest(item).then((defer) => {
|
|
48
|
+
const timeout = item[TIMEOUT_SYMBOL];
|
|
49
|
+
|
|
50
|
+
/* istanbul ignore if */
|
|
51
|
+
if (!timeout) {
|
|
52
|
+
throw new Error('timeout is required');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const timer = safeSetTimeout(() => {
|
|
56
|
+
this.logger.warn(
|
|
57
|
+
`kms: request timed out; request id: ${item.requestId}; timeout: ${timeout}`
|
|
58
|
+
);
|
|
59
|
+
this.handleItemFailure(
|
|
60
|
+
item,
|
|
61
|
+
new KmsTimeoutError({
|
|
54
62
|
timeout,
|
|
55
|
-
request: item
|
|
56
|
-
})
|
|
57
|
-
|
|
63
|
+
request: item,
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
}, timeout);
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
// Reminder: reassign `promise` is not a viable means of inserting into
|
|
69
|
+
// the Promise chain
|
|
70
|
+
defer.promise.then(() => clearTimeout(timer));
|
|
71
|
+
defer.promise.catch(() => clearTimeout(timer));
|
|
63
72
|
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
return item;
|
|
74
|
+
});
|
|
66
75
|
},
|
|
67
76
|
|
|
68
77
|
/**
|
|
@@ -71,11 +80,10 @@ const KmsBatcher = Batcher.extend({
|
|
|
71
80
|
* @returns {Promise<Array>}
|
|
72
81
|
*/
|
|
73
82
|
prepareRequest(queue) {
|
|
74
|
-
return this.webex.internal.encryption.kms._getKMSCluster()
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}));
|
|
83
|
+
return this.webex.internal.encryption.kms._getKMSCluster().then((cluster) => ({
|
|
84
|
+
destination: cluster,
|
|
85
|
+
kmsMessages: queue.map((req) => req.wrapped),
|
|
86
|
+
}));
|
|
79
87
|
},
|
|
80
88
|
|
|
81
89
|
/**
|
|
@@ -89,7 +97,7 @@ const KmsBatcher = Batcher.extend({
|
|
|
89
97
|
method: 'POST',
|
|
90
98
|
service: 'encryption',
|
|
91
99
|
resource: '/kms/messages',
|
|
92
|
-
body: payload
|
|
100
|
+
body: payload,
|
|
93
101
|
});
|
|
94
102
|
},
|
|
95
103
|
|
|
@@ -114,10 +122,9 @@ const KmsBatcher = Batcher.extend({
|
|
|
114
122
|
* @returns {Promise}
|
|
115
123
|
*/
|
|
116
124
|
handleItemSuccess(item) {
|
|
117
|
-
return this.getDeferredForResponse(item)
|
|
118
|
-
.
|
|
119
|
-
|
|
120
|
-
});
|
|
125
|
+
return this.getDeferredForResponse(item).then((defer) => {
|
|
126
|
+
defer.resolve(item.body);
|
|
127
|
+
});
|
|
121
128
|
},
|
|
122
129
|
|
|
123
130
|
/**
|
|
@@ -126,10 +133,11 @@ const KmsBatcher = Batcher.extend({
|
|
|
126
133
|
* @returns {Promise}
|
|
127
134
|
*/
|
|
128
135
|
handleItemFailure(item, reason) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
handleKmsKeyRevokedEncryptionFailure(item, this.webex);
|
|
137
|
+
|
|
138
|
+
return this.getDeferredForResponse(item).then((defer) => {
|
|
139
|
+
defer.reject(reason || new KmsError(item.body));
|
|
140
|
+
});
|
|
133
141
|
},
|
|
134
142
|
|
|
135
143
|
/**
|
|
@@ -146,7 +154,7 @@ const KmsBatcher = Batcher.extend({
|
|
|
146
154
|
*/
|
|
147
155
|
fingerprintResponse(item) {
|
|
148
156
|
return Promise.resolve(item.requestId);
|
|
149
|
-
}
|
|
157
|
+
},
|
|
150
158
|
});
|
|
151
159
|
|
|
152
160
|
export default KmsBatcher;
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
RSAPublicKey,
|
|
8
8
|
CertificateChainValidationEngine,
|
|
9
9
|
CryptoEngine,
|
|
10
|
-
setEngine
|
|
10
|
+
setEngine,
|
|
11
11
|
} from 'pkijs';
|
|
12
12
|
import {isArray} from 'lodash';
|
|
13
13
|
import jose from 'node-jose';
|
|
@@ -20,7 +20,7 @@ setEngine(
|
|
|
20
20
|
new CryptoEngine({
|
|
21
21
|
name: '',
|
|
22
22
|
crypto,
|
|
23
|
-
subtle: crypto.subtle
|
|
23
|
+
subtle: crypto.subtle,
|
|
24
24
|
})
|
|
25
25
|
);
|
|
26
26
|
|
|
@@ -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
|
|
@@ -83,7 +83,7 @@ const validateKtyHeader = ({kty}) => {
|
|
|
83
83
|
|
|
84
84
|
const validateKidHeader = ({kid}) => {
|
|
85
85
|
if (!isUri(kid)) {
|
|
86
|
-
throwError('
|
|
86
|
+
throwError("'kid' is not a valid URI");
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (parseUrl(kid).protocol !== VALID_KID_PROTOCOL) {
|
|
@@ -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) {
|
|
@@ -144,7 +144,7 @@ const validateCommonName = ([certificate], {kid}) => {
|
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
if (!validationSuccessful) {
|
|
147
|
-
throwError(
|
|
147
|
+
throwError("hostname of the 1st certificate does not match 'kid'");
|
|
148
148
|
}
|
|
149
149
|
};
|
|
150
150
|
|
|
@@ -158,23 +158,22 @@ const validateCommonName = ([certificate], {kid}) => {
|
|
|
158
158
|
* @throws {KMSError} if e or n doesn't match the first certificate
|
|
159
159
|
* @returns {void}
|
|
160
160
|
*/
|
|
161
|
-
const validatePublicCertificate =
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
};
|
|
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
|
+
};
|
|
178
177
|
|
|
179
178
|
/**
|
|
180
179
|
* Validates the list of certificates against the CAs provided
|
|
@@ -187,17 +186,14 @@ const validatePublicCertificate =
|
|
|
187
186
|
const validateCertificatesSignature = (certificates, caroots = []) => {
|
|
188
187
|
const certificateEngine = new CertificateChainValidationEngine({
|
|
189
188
|
trustedCerts: caroots.map(decodeCert),
|
|
190
|
-
certs: certificates
|
|
189
|
+
certs: certificates,
|
|
191
190
|
});
|
|
192
191
|
|
|
193
|
-
return certificateEngine.verify()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
192
|
+
return certificateEngine.verify().then(({result, resultCode, resultMessage}) => {
|
|
193
|
+
if (!result) {
|
|
194
|
+
throwError(`Certificate Validation failed [${resultCode}]: ${resultMessage}`);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
201
197
|
};
|
|
202
198
|
|
|
203
199
|
/**
|
|
@@ -210,25 +206,27 @@ const validateCertificatesSignature = (certificates, caroots = []) => {
|
|
|
210
206
|
* validate the KMS
|
|
211
207
|
* @returns {Promise} when resolved will return the jwt
|
|
212
208
|
*/
|
|
213
|
-
const validateKMS =
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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);
|
|
222
220
|
|
|
223
|
-
|
|
224
|
-
|
|
221
|
+
validateCommonName(certificates, jwt);
|
|
222
|
+
validatePublicCertificate(certificates, jwt);
|
|
225
223
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
224
|
+
// Skip validating signatures if no CA roots were provided
|
|
225
|
+
const promise = caroots
|
|
226
|
+
? validateCertificatesSignature(certificates, caroots)
|
|
227
|
+
: Promise.resolve();
|
|
229
228
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
});
|
|
229
|
+
return promise.then(() => jwt);
|
|
230
|
+
});
|
|
233
231
|
|
|
234
232
|
export default validateKMS;
|
|
@@ -24,7 +24,10 @@ export default class KmsDryErrorInterceptor extends Interceptor {
|
|
|
24
24
|
* @returns {Promise}
|
|
25
25
|
*/
|
|
26
26
|
onResponseError(options, reason) {
|
|
27
|
-
if (
|
|
27
|
+
if (
|
|
28
|
+
reason instanceof DryError &&
|
|
29
|
+
reason.message.match(/Failed to resolve authorization token in KmsMessage request for user/)
|
|
30
|
+
) {
|
|
28
31
|
this.webex.logger.error('DRY Request Failed due to kms/test-user flakiness');
|
|
29
32
|
this.webex.logger.error(reason);
|
|
30
33
|
|
|
@@ -43,13 +46,14 @@ export default class KmsDryErrorInterceptor extends Interceptor {
|
|
|
43
46
|
replay(options, reason) {
|
|
44
47
|
if (options.replayCount) {
|
|
45
48
|
options.replayCount += 1;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
49
|
+
} else {
|
|
48
50
|
options.replayCount = 1;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
if (options.replayCount > this.webex.config.maxAuthenticationReplays) {
|
|
52
|
-
this.webex.logger.error(
|
|
54
|
+
this.webex.logger.error(
|
|
55
|
+
`kms: failed after ${this.webex.config.maxAuthenticationReplays} replay attempts`
|
|
56
|
+
);
|
|
53
57
|
|
|
54
58
|
return Promise.reject(reason);
|
|
55
59
|
}
|