1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
import { expect, describe, it } from "bun:test";
import { createPrivateKey } from "crypto";
import fs from "fs";
import path from "path";
const PS_SUPPORTED = true;
const ASYMMETRIC_KEY_DETAILS_SUPPORTED = true;
const RSA_PSS_KEY_DETAILS_SUPPORTED = true;
const allowedAlgorithmsForKeys = {
"ec": ["ES256", "ES384", "ES512"],
"rsa": ["RS256", "PS256", "RS384", "PS384", "RS512", "PS512"],
"rsa-pss": ["PS256", "PS384", "PS512"],
};
const allowedCurves = {
ES256: "prime256v1",
ES384: "secp384r1",
ES512: "secp521r1",
};
function validateAsymmetricKey(algorithm, key) {
if (!algorithm || !key) return;
const keyType = key.asymmetricKeyType;
if (!keyType) return;
const allowedAlgorithms = allowedAlgorithmsForKeys[keyType];
if (!allowedAlgorithms) {
throw new Error(`Unknown key type "${keyType}".`);
}
if (!allowedAlgorithms.includes(algorithm)) {
throw new Error(`"alg" parameter for "${keyType}" key type must be one of: ${allowedAlgorithms.join(", ")}.`);
}
/*
* Ignore the next block from test coverage because it gets executed
* conditionally depending on the Node version. Not ignoring it would
* prevent us from reaching the target % of coverage for versions of
* Node under 15.7.0.
*/
/* istanbul ignore next */
if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) {
switch (keyType) {
case "ec":
const keyCurve = key.asymmetricKeyDetails.namedCurve;
const allowedCurve = allowedCurves[algorithm];
if (keyCurve !== allowedCurve) {
throw new Error(`"alg" parameter "${algorithm}" requires curve "${allowedCurve}".`);
}
break;
case "rsa-pss":
if (RSA_PSS_KEY_DETAILS_SUPPORTED) {
const length = parseInt(algorithm.slice(-3), 10);
const { hashAlgorithm, mgf1HashAlgorithm, saltLength } = key.asymmetricKeyDetails;
if (hashAlgorithm !== `sha${length}` || mgf1HashAlgorithm !== hashAlgorithm) {
throw new Error(
`Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" ${algorithm}.`,
);
}
if (saltLength !== undefined && saltLength > length >> 3) {
throw new Error(
`Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" ${algorithm}.`,
);
}
}
break;
}
}
}
function loadKey(filename) {
return createPrivateKey(fs.readFileSync(path.join(__dirname, filename)));
}
const algorithmParams = {
RS256: {
invalidPrivateKey: loadKey("secp384r1-private.pem"),
},
ES256: {
invalidPrivateKey: loadKey("priv.pem"),
},
};
if (PS_SUPPORTED) {
algorithmParams.PS256 = {
invalidPrivateKey: loadKey("secp384r1-private.pem"),
};
}
describe("Asymmetric key validation", function () {
Object.keys(algorithmParams).forEach(function (algorithm) {
describe(algorithm, function () {
const keys = algorithmParams[algorithm];
describe("when validating a key with an invalid private key type", function () {
it("should throw an error", function () {
const expectedErrorMessage = /"alg" parameter for "[\w\d-]+" key type must be one of:/;
expect(function () {
validateAsymmetricKey(algorithm, keys.invalidPrivateKey);
}).toThrow(expectedErrorMessage);
});
});
});
});
describe("when the function has missing parameters", function () {
it("should pass the validation if no key has been provided", function () {
const algorithm = "ES256";
validateAsymmetricKey(algorithm);
});
it.todo("should pass the validation if no algorithm has been provided", function () {
const key = loadKey("dsa-private.pem");
validateAsymmetricKey(null, key);
});
});
describe("when validating a key with an unsupported type", function () {
it.todo("should throw an error", function () {
const algorithm = "RS256";
const key = loadKey("dsa-private.pem");
const expectedErrorMessage = 'Unknown key type "dsa".';
expect(function () {
validateAsymmetricKey(algorithm, key);
}).toThrow(expectedErrorMessage);
});
});
describe("Elliptic curve algorithms", function () {
const curvesAlgorithms = [
{ algorithm: "ES256", curve: "prime256v1" },
{ algorithm: "ES384", curve: "secp384r1" },
{ algorithm: "ES512", curve: "secp521r1" },
];
const curvesKeys = [
{ curve: "prime256v1", key: loadKey("prime256v1-private.pem") },
{ curve: "secp384r1", key: loadKey("secp384r1-private.pem") },
{ curve: "secp521r1", key: loadKey("secp521r1-private.pem") },
];
describe("when validating keys generated using Elliptic Curves", function () {
curvesAlgorithms.forEach(function (curveAlgorithm) {
curvesKeys.forEach(curveKeys => {
if (curveKeys.curve !== curveAlgorithm.curve) {
if (ASYMMETRIC_KEY_DETAILS_SUPPORTED) {
it(`should throw an error when validating an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function () {
expect(() => {
validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
}).toThrow(`"alg" parameter "${curveAlgorithm.algorithm}" requires curve "${curveAlgorithm.curve}".`);
});
} else {
it(`should pass the validation for incorrect keys if the Node version does not support checking the key's curve name`, function () {
expect(() => {
validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
}).not.toThrow();
});
}
} else {
it(`should accept an ${curveAlgorithm.algorithm} token for key with curve ${curveKeys.curve}`, function () {
expect(() => {
validateAsymmetricKey(curveAlgorithm.algorithm, curveKeys.key);
}).not.toThrow();
});
}
});
});
});
});
if (RSA_PSS_KEY_DETAILS_SUPPORTED) {
describe.todo("RSA-PSS algorithms", function () {
// const key = loadKey('rsa-pss-private.pem');
it(`it should throw an error when validating a key with wrong RSA-RSS parameters`, function () {
const algorithm = "PS512";
expect(function () {
validateAsymmetricKey(algorithm, key);
}).toThrow(
'Invalid key for this operation, its RSA-PSS parameters do not meet the requirements of "alg" PS512',
);
});
it(`it should throw an error when validating a key with invalid salt length`, function () {
const algorithm = "PS256";
const shortSaltKey = loadKey("rsa-pss-invalid-salt-length-private.pem");
expect(function () {
validateAsymmetricKey(algorithm, shortSaltKey);
}).toThrow(
'Invalid key for this operation, its RSA-PSS parameter saltLength does not meet the requirements of "alg" PS256.',
);
});
it(`it should pass the validation when the key matches all the requirements for the algorithm`, function () {
expect(function () {
const algorithm = "PS256";
validateAsymmetricKey(algorithm, key);
}).not.toThrow();
});
});
}
});
|