@microsoft/ccf-app 3.0.0-dev5 → 3.0.0-rc0
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/converters.js +32 -0
- package/crypto.d.ts +20 -0
- package/crypto.js +27 -7
- package/global.d.ts +150 -24
- package/package.json +3 -1
- package/polyfill.js +195 -130
package/converters.js
CHANGED
|
@@ -19,8 +19,29 @@
|
|
|
19
19
|
* @module
|
|
20
20
|
*/
|
|
21
21
|
import { ccf } from "./global.js";
|
|
22
|
+
function checkBoolean(val) {
|
|
23
|
+
if (typeof val !== "boolean") {
|
|
24
|
+
throw new TypeError(`Value ${val} is not a boolean`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function checkNumber(val) {
|
|
28
|
+
if (typeof val !== "number") {
|
|
29
|
+
throw new TypeError(`Value ${val} is not a number`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function checkBigInt(val) {
|
|
33
|
+
if (typeof val !== "bigint") {
|
|
34
|
+
throw new TypeError(`Value ${val} is not a bigint`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function checkString(val) {
|
|
38
|
+
if (typeof val !== "string") {
|
|
39
|
+
throw new TypeError(`Value ${val} is not a string`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
22
42
|
class BoolConverter {
|
|
23
43
|
encode(val) {
|
|
44
|
+
checkBoolean(val);
|
|
24
45
|
const buf = new ArrayBuffer(1);
|
|
25
46
|
new DataView(buf).setUint8(0, val ? 1 : 0);
|
|
26
47
|
return buf;
|
|
@@ -31,6 +52,7 @@ class BoolConverter {
|
|
|
31
52
|
}
|
|
32
53
|
class Int8Converter {
|
|
33
54
|
encode(val) {
|
|
55
|
+
checkNumber(val);
|
|
34
56
|
if (val < -128 || val > 127) {
|
|
35
57
|
throw new RangeError("value is not within int8 range");
|
|
36
58
|
}
|
|
@@ -44,6 +66,7 @@ class Int8Converter {
|
|
|
44
66
|
}
|
|
45
67
|
class Uint8Converter {
|
|
46
68
|
encode(val) {
|
|
69
|
+
checkNumber(val);
|
|
47
70
|
if (val < 0 || val > 255) {
|
|
48
71
|
throw new RangeError("value is not within uint8 range");
|
|
49
72
|
}
|
|
@@ -57,6 +80,7 @@ class Uint8Converter {
|
|
|
57
80
|
}
|
|
58
81
|
class Int16Converter {
|
|
59
82
|
encode(val) {
|
|
83
|
+
checkNumber(val);
|
|
60
84
|
if (val < -32768 || val > 32767) {
|
|
61
85
|
throw new RangeError("value is not within int16 range");
|
|
62
86
|
}
|
|
@@ -70,6 +94,7 @@ class Int16Converter {
|
|
|
70
94
|
}
|
|
71
95
|
class Uint16Converter {
|
|
72
96
|
encode(val) {
|
|
97
|
+
checkNumber(val);
|
|
73
98
|
if (val < 0 || val > 65535) {
|
|
74
99
|
throw new RangeError("value is not within uint16 range");
|
|
75
100
|
}
|
|
@@ -83,6 +108,7 @@ class Uint16Converter {
|
|
|
83
108
|
}
|
|
84
109
|
class Int32Converter {
|
|
85
110
|
encode(val) {
|
|
111
|
+
checkNumber(val);
|
|
86
112
|
if (val < -2147483648 || val > 2147483647) {
|
|
87
113
|
throw new RangeError("value is not within int32 range");
|
|
88
114
|
}
|
|
@@ -96,6 +122,7 @@ class Int32Converter {
|
|
|
96
122
|
}
|
|
97
123
|
class Uint32Converter {
|
|
98
124
|
encode(val) {
|
|
125
|
+
checkNumber(val);
|
|
99
126
|
if (val < 0 || val > 4294967295) {
|
|
100
127
|
throw new RangeError("value is not within uint32 range");
|
|
101
128
|
}
|
|
@@ -109,6 +136,7 @@ class Uint32Converter {
|
|
|
109
136
|
}
|
|
110
137
|
class Int64Converter {
|
|
111
138
|
encode(val) {
|
|
139
|
+
checkBigInt(val);
|
|
112
140
|
const buf = new ArrayBuffer(8);
|
|
113
141
|
new DataView(buf).setBigInt64(0, val, true);
|
|
114
142
|
return buf;
|
|
@@ -119,6 +147,7 @@ class Int64Converter {
|
|
|
119
147
|
}
|
|
120
148
|
class Uint64Converter {
|
|
121
149
|
encode(val) {
|
|
150
|
+
checkBigInt(val);
|
|
122
151
|
const buf = new ArrayBuffer(8);
|
|
123
152
|
new DataView(buf).setBigUint64(0, val, true);
|
|
124
153
|
return buf;
|
|
@@ -129,6 +158,7 @@ class Uint64Converter {
|
|
|
129
158
|
}
|
|
130
159
|
class Float32Converter {
|
|
131
160
|
encode(val) {
|
|
161
|
+
checkNumber(val);
|
|
132
162
|
const buf = new ArrayBuffer(4);
|
|
133
163
|
new DataView(buf).setFloat32(0, val, true);
|
|
134
164
|
return buf;
|
|
@@ -139,6 +169,7 @@ class Float32Converter {
|
|
|
139
169
|
}
|
|
140
170
|
class Float64Converter {
|
|
141
171
|
encode(val) {
|
|
172
|
+
checkNumber(val);
|
|
142
173
|
const buf = new ArrayBuffer(8);
|
|
143
174
|
new DataView(buf).setFloat64(0, val, true);
|
|
144
175
|
return buf;
|
|
@@ -149,6 +180,7 @@ class Float64Converter {
|
|
|
149
180
|
}
|
|
150
181
|
class StringConverter {
|
|
151
182
|
encode(val) {
|
|
183
|
+
checkString(val);
|
|
152
184
|
return ccf.strToBuf(val);
|
|
153
185
|
}
|
|
154
186
|
decode(buf) {
|
package/crypto.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare const generateRsaKeyPair: (size: number, exponent?: number | unde
|
|
|
10
10
|
* @inheritDoc CCF.generateEcdsaKeyPair
|
|
11
11
|
*/
|
|
12
12
|
export declare const generateEcdsaKeyPair: (curve: string) => import("./global.js").CryptoKeyPair;
|
|
13
|
+
/**
|
|
14
|
+
* @inheritDoc CCFCrypto.generateEcdsaKeyPair
|
|
15
|
+
*/
|
|
16
|
+
export declare const generateEddsaKeyPair: (curve: string) => import("./global.js").CryptoKeyPair;
|
|
13
17
|
/**
|
|
14
18
|
* @inheritDoc CCF.wrapKey
|
|
15
19
|
*/
|
|
@@ -30,4 +34,20 @@ export declare const isValidX509CertBundle: (pem: string) => boolean;
|
|
|
30
34
|
* @inheritDoc CCF.isValidX509CertChain
|
|
31
35
|
*/
|
|
32
36
|
export declare const isValidX509CertChain: (chain: string, trusted: string) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* @inheritDoc CCF.pubPemToJwk
|
|
39
|
+
*/
|
|
40
|
+
export declare const pubPemToJwk: (pem: string, kid?: string | undefined) => import("./global.js").JsonWebKeyECPublic;
|
|
41
|
+
/**
|
|
42
|
+
* @inheritDoc CCF.pemToJwk
|
|
43
|
+
*/
|
|
44
|
+
export declare const pemToJwk: (pem: string, kid?: string | undefined) => import("./global.js").JsonWebKeyECPrivate;
|
|
45
|
+
/**
|
|
46
|
+
* @inheritDoc CCF.pubRsaPemToJwk
|
|
47
|
+
*/
|
|
48
|
+
export declare const pubRsaPemToJwk: (pem: string, kid?: string | undefined) => import("./global.js").JsonWebKeyRSAPublic;
|
|
49
|
+
/**
|
|
50
|
+
* @inheritDoc CCF.rsaPemToJwk
|
|
51
|
+
*/
|
|
52
|
+
export declare const rsaPemToJwk: (pem: string, kid?: string | undefined) => import("./global.js").JsonWebKeyRSAPrivate;
|
|
33
53
|
export { WrapAlgoParams, AesKwpParams, RsaOaepParams, RsaOaepAesKwpParams, CryptoKeyPair, DigestAlgorithm, SigningAlgorithm, RsaPkcsParams, EcdsaParams, } from "./global";
|
package/crypto.js
CHANGED
|
@@ -17,19 +17,23 @@ import { ccf } from "./global.js";
|
|
|
17
17
|
/**
|
|
18
18
|
* @inheritDoc CCF.generateAesKey
|
|
19
19
|
*/
|
|
20
|
-
export const generateAesKey = ccf.generateAesKey;
|
|
20
|
+
export const generateAesKey = ccf.crypto.generateAesKey;
|
|
21
21
|
/**
|
|
22
22
|
* @inheritDoc CCF.generateRsaKeyPair
|
|
23
23
|
*/
|
|
24
|
-
export const generateRsaKeyPair = ccf.generateRsaKeyPair;
|
|
24
|
+
export const generateRsaKeyPair = ccf.crypto.generateRsaKeyPair;
|
|
25
25
|
/**
|
|
26
26
|
* @inheritDoc CCF.generateEcdsaKeyPair
|
|
27
27
|
*/
|
|
28
|
-
export const generateEcdsaKeyPair = ccf.generateEcdsaKeyPair;
|
|
28
|
+
export const generateEcdsaKeyPair = ccf.crypto.generateEcdsaKeyPair;
|
|
29
|
+
/**
|
|
30
|
+
* @inheritDoc CCFCrypto.generateEcdsaKeyPair
|
|
31
|
+
*/
|
|
32
|
+
export const generateEddsaKeyPair = ccf.crypto.generateEddsaKeyPair;
|
|
29
33
|
/**
|
|
30
34
|
* @inheritDoc CCF.wrapKey
|
|
31
35
|
*/
|
|
32
|
-
export const wrapKey = ccf.wrapKey;
|
|
36
|
+
export const wrapKey = ccf.crypto.wrapKey;
|
|
33
37
|
/**
|
|
34
38
|
* @inheritDoc CCFCrypto.verifySignature
|
|
35
39
|
*/
|
|
@@ -37,12 +41,28 @@ export const verifySignature = ccf.crypto.verifySignature;
|
|
|
37
41
|
/**
|
|
38
42
|
* @inheritDoc CCF.digest
|
|
39
43
|
*/
|
|
40
|
-
export const digest = ccf.digest;
|
|
44
|
+
export const digest = ccf.crypto.digest;
|
|
41
45
|
/**
|
|
42
46
|
* @inheritDoc CCF.isValidX509CertBundle
|
|
43
47
|
*/
|
|
44
|
-
export const isValidX509CertBundle = ccf.isValidX509CertBundle;
|
|
48
|
+
export const isValidX509CertBundle = ccf.crypto.isValidX509CertBundle;
|
|
45
49
|
/**
|
|
46
50
|
* @inheritDoc CCF.isValidX509CertChain
|
|
47
51
|
*/
|
|
48
|
-
export const isValidX509CertChain = ccf.isValidX509CertChain;
|
|
52
|
+
export const isValidX509CertChain = ccf.crypto.isValidX509CertChain;
|
|
53
|
+
/**
|
|
54
|
+
* @inheritDoc CCF.pubPemToJwk
|
|
55
|
+
*/
|
|
56
|
+
export const pubPemToJwk = ccf.crypto.pubPemToJwk;
|
|
57
|
+
/**
|
|
58
|
+
* @inheritDoc CCF.pemToJwk
|
|
59
|
+
*/
|
|
60
|
+
export const pemToJwk = ccf.crypto.pemToJwk;
|
|
61
|
+
/**
|
|
62
|
+
* @inheritDoc CCF.pubRsaPemToJwk
|
|
63
|
+
*/
|
|
64
|
+
export const pubRsaPemToJwk = ccf.crypto.pubRsaPemToJwk;
|
|
65
|
+
/**
|
|
66
|
+
* @inheritDoc CCF.rsaPemToJwk
|
|
67
|
+
*/
|
|
68
|
+
export const rsaPemToJwk = ccf.crypto.rsaPemToJwk;
|
package/global.d.ts
CHANGED
|
@@ -164,11 +164,11 @@ export interface RsaOaepAesKwpParams {
|
|
|
164
164
|
export declare type WrapAlgoParams = RsaOaepParams | AesKwpParams | RsaOaepAesKwpParams;
|
|
165
165
|
export interface CryptoKeyPair {
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
167
|
+
* Private key in PEM encoding.
|
|
168
168
|
*/
|
|
169
169
|
privateKey: string;
|
|
170
170
|
/**
|
|
171
|
-
*
|
|
171
|
+
* Public key in PEM encoding.
|
|
172
172
|
*/
|
|
173
173
|
publicKey: string;
|
|
174
174
|
}
|
|
@@ -193,6 +193,63 @@ export interface EcdsaParams {
|
|
|
193
193
|
}
|
|
194
194
|
export declare type SigningAlgorithm = RsaPkcsParams | EcdsaParams;
|
|
195
195
|
export declare type DigestAlgorithm = "SHA-256";
|
|
196
|
+
/**
|
|
197
|
+
* Interfaces for JSON Web Key objects, as per [RFC7517](https://www.rfc-editor.org/rfc/rfc751).
|
|
198
|
+
*/
|
|
199
|
+
export interface JsonWebKey {
|
|
200
|
+
/**
|
|
201
|
+
* Key type.
|
|
202
|
+
*/
|
|
203
|
+
kty: string;
|
|
204
|
+
/**
|
|
205
|
+
* Key ID.
|
|
206
|
+
*/
|
|
207
|
+
kid?: string;
|
|
208
|
+
}
|
|
209
|
+
export interface JsonWebKeyECPublic extends JsonWebKey {
|
|
210
|
+
/**
|
|
211
|
+
* Elliptic curve identifier.
|
|
212
|
+
*/
|
|
213
|
+
crv: string;
|
|
214
|
+
/**
|
|
215
|
+
* Base64url-encoded x coordinate.
|
|
216
|
+
*/
|
|
217
|
+
x: string;
|
|
218
|
+
/**
|
|
219
|
+
* Base64url-encoded y coordinate.
|
|
220
|
+
*/
|
|
221
|
+
y: string;
|
|
222
|
+
}
|
|
223
|
+
export interface JsonWebKeyECPrivate extends JsonWebKeyECPublic {
|
|
224
|
+
/**
|
|
225
|
+
* Base64url-encoded d coordinate.
|
|
226
|
+
*/
|
|
227
|
+
d: string;
|
|
228
|
+
}
|
|
229
|
+
export interface JsonWebKeyRSAPublic extends JsonWebKey {
|
|
230
|
+
/**
|
|
231
|
+
* Base64url-encoded modulus.
|
|
232
|
+
*/
|
|
233
|
+
n: string;
|
|
234
|
+
/**
|
|
235
|
+
* Base64url-encoded exponent.
|
|
236
|
+
*/
|
|
237
|
+
e: string;
|
|
238
|
+
}
|
|
239
|
+
export interface JsonWebKeyRSAPrivate extends JsonWebKeyRSAPublic {
|
|
240
|
+
/**
|
|
241
|
+
* Private exponent.
|
|
242
|
+
*/
|
|
243
|
+
d: string;
|
|
244
|
+
/**
|
|
245
|
+
* Additional exponents.
|
|
246
|
+
*/
|
|
247
|
+
p: string;
|
|
248
|
+
q: string;
|
|
249
|
+
dp: string;
|
|
250
|
+
dq: string;
|
|
251
|
+
qi: string;
|
|
252
|
+
}
|
|
196
253
|
export interface CCFCrypto {
|
|
197
254
|
/**
|
|
198
255
|
* Returns whether digital signature is valid.
|
|
@@ -205,6 +262,83 @@ export interface CCFCrypto {
|
|
|
205
262
|
* signing algorithm or if an unknown algorithm is used.
|
|
206
263
|
*/
|
|
207
264
|
verifySignature(algorithm: SigningAlgorithm, key: string, signature: ArrayBuffer, data: ArrayBuffer): boolean;
|
|
265
|
+
/**
|
|
266
|
+
* Generate an AES key.
|
|
267
|
+
*
|
|
268
|
+
* @param size The length in bits of the key to generate. 128, 192, or 256.
|
|
269
|
+
*/
|
|
270
|
+
generateAesKey(size: number): ArrayBuffer;
|
|
271
|
+
/**
|
|
272
|
+
* Generate an RSA key pair.
|
|
273
|
+
*
|
|
274
|
+
* @param size The length in bits of the RSA modulus. Minimum: 2048.
|
|
275
|
+
* @param exponent The public exponent. Default: 65537.
|
|
276
|
+
*/
|
|
277
|
+
generateRsaKeyPair(size: number, exponent?: number): CryptoKeyPair;
|
|
278
|
+
/**
|
|
279
|
+
* Generate an ECDSA key pair.
|
|
280
|
+
*
|
|
281
|
+
* @param curve The name of the curve, one of "secp256r1", "secp256k1", "secp384r1".
|
|
282
|
+
*/
|
|
283
|
+
generateEcdsaKeyPair(curve: string): CryptoKeyPair;
|
|
284
|
+
/**
|
|
285
|
+
* Generate an EdDSA key pair.
|
|
286
|
+
*
|
|
287
|
+
* @param curve The name of the curve. Currently only "curve25519" is supported.
|
|
288
|
+
*/
|
|
289
|
+
generateEddsaKeyPair(curve: string): CryptoKeyPair;
|
|
290
|
+
/**
|
|
291
|
+
* Wraps a key using a wrapping key.
|
|
292
|
+
*
|
|
293
|
+
* Constraints on the `key` and `wrappingKey` parameters depend
|
|
294
|
+
* on the wrapping algorithm that is used (`wrapAlgo`).
|
|
295
|
+
*/
|
|
296
|
+
wrapKey(key: ArrayBuffer, wrappingKey: ArrayBuffer, wrapAlgo: WrapAlgoParams): ArrayBuffer;
|
|
297
|
+
/**
|
|
298
|
+
* Generate a digest (hash) of the given data.
|
|
299
|
+
*/
|
|
300
|
+
digest(algorithm: DigestAlgorithm, data: ArrayBuffer): ArrayBuffer;
|
|
301
|
+
/**
|
|
302
|
+
* Returns whether a string is a PEM-encoded bundle of X.509 certificates.
|
|
303
|
+
*
|
|
304
|
+
* A bundle consists of one or more certificates.
|
|
305
|
+
* Certificates in the bundle do not have to be related to each other.
|
|
306
|
+
* Validation is only syntactical, properties like validity dates are not evaluated.
|
|
307
|
+
*/
|
|
308
|
+
isValidX509CertBundle(pem: string): boolean;
|
|
309
|
+
/**
|
|
310
|
+
* Returns whether a certificate chain is valid given a set of trusted certificates.
|
|
311
|
+
* The chain and trusted certificates are PEM-encoded bundles of X.509 certificates.
|
|
312
|
+
*/
|
|
313
|
+
isValidX509CertChain(chain: string, trusted: string): boolean;
|
|
314
|
+
/**
|
|
315
|
+
* Converts an elliptic curve public key as PEM to JSON Web Key (JWK) object.
|
|
316
|
+
*
|
|
317
|
+
* @param pem Elliptic curve public key as PEM
|
|
318
|
+
* @param kid Key identifier (optional)
|
|
319
|
+
*/
|
|
320
|
+
pubPemToJwk(pem: string, kid?: string): JsonWebKeyECPublic;
|
|
321
|
+
/**
|
|
322
|
+
* Converts an elliptic curve private key as PEM to JSON Web Key (JWK) object.
|
|
323
|
+
*
|
|
324
|
+
* @param pem Elliptic curve private key as PEM
|
|
325
|
+
* @param kid Key identifier (optional)
|
|
326
|
+
*/
|
|
327
|
+
pemToJwk(pem: string, kid?: string): JsonWebKeyECPrivate;
|
|
328
|
+
/**
|
|
329
|
+
* Converts an RSA public key as PEM to JSON Web Key (JWK) object.
|
|
330
|
+
*
|
|
331
|
+
* @param pem RSA public key as PEM
|
|
332
|
+
* @param kid Key identifier (optional)
|
|
333
|
+
*/
|
|
334
|
+
pubRsaPemToJwk(pem: string, kid?: string): JsonWebKeyRSAPublic;
|
|
335
|
+
/**
|
|
336
|
+
* Converts an RSA private key as PEM to JSON Web Key (JWK) object.
|
|
337
|
+
*
|
|
338
|
+
* @param pem RSA private key as PEM
|
|
339
|
+
* @param kid Key identifier (optional)
|
|
340
|
+
*/
|
|
341
|
+
rsaPemToJwk(pem: string, kid?: string): JsonWebKeyRSAPrivate;
|
|
208
342
|
}
|
|
209
343
|
export interface CCFRpc {
|
|
210
344
|
/**
|
|
@@ -309,46 +443,38 @@ export interface CCF {
|
|
|
309
443
|
*/
|
|
310
444
|
bufToJsonCompatible<T extends JsonCompatible<T>>(v: ArrayBuffer): T;
|
|
311
445
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
* @param size The length in bits of the key to generate. 128, 192, or 256.
|
|
446
|
+
* @deprecated This method has been moved to ccf.crypto namespace
|
|
447
|
+
* @see crypto.generateAesKey
|
|
315
448
|
*/
|
|
316
449
|
generateAesKey(size: number): ArrayBuffer;
|
|
317
450
|
/**
|
|
318
|
-
*
|
|
319
|
-
*
|
|
320
|
-
* @param size The length in bits of the RSA modulus. Minimum: 2048.
|
|
321
|
-
* @param exponent The public exponent. Default: 65537.
|
|
451
|
+
* @deprecated This method has been moved to ccf.crypto namespace
|
|
452
|
+
* @see crypto.generateRsaKeyPair
|
|
322
453
|
*/
|
|
323
454
|
generateRsaKeyPair(size: number, exponent?: number): CryptoKeyPair;
|
|
324
455
|
/**
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
* @param curve The name of the curve, one of "secp256r1", "secp384r1".
|
|
456
|
+
* @deprecated This method has been moved to ccf.crypto namespace
|
|
457
|
+
* @see crypto.generateEcdsaKeyPair
|
|
328
458
|
*/
|
|
329
459
|
generateEcdsaKeyPair(curve: string): CryptoKeyPair;
|
|
330
460
|
/**
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
* Constraints on the `key` and `wrappingKey` parameters depend
|
|
334
|
-
* on the wrapping algorithm that is used (`wrapAlgo`).
|
|
461
|
+
* @deprecated This method has been moved to ccf.crypto namespace
|
|
462
|
+
* @see crypto.wrapKey
|
|
335
463
|
*/
|
|
336
464
|
wrapKey(key: ArrayBuffer, wrappingKey: ArrayBuffer, wrapAlgo: WrapAlgoParams): ArrayBuffer;
|
|
337
465
|
/**
|
|
338
|
-
*
|
|
466
|
+
* @deprecated This method has been moved to ccf.crypto namespace
|
|
467
|
+
* @see crypto.digest
|
|
339
468
|
*/
|
|
340
469
|
digest(algorithm: DigestAlgorithm, data: ArrayBuffer): ArrayBuffer;
|
|
341
470
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* A bundle consists of one or more certificates.
|
|
345
|
-
* Certificates in the bundle do not have to be related to each other.
|
|
346
|
-
* Validation is only syntactical, properties like validity dates are not evaluated.
|
|
471
|
+
* @deprecated
|
|
472
|
+
* @see crypto.isValidX509CertBundle
|
|
347
473
|
*/
|
|
348
474
|
isValidX509CertBundle(pem: string): boolean;
|
|
349
475
|
/**
|
|
350
|
-
*
|
|
351
|
-
*
|
|
476
|
+
* @deprecated This method has been moved to ccf.crypto namespace
|
|
477
|
+
* @see crypto.isValidX509CertChain
|
|
352
478
|
*/
|
|
353
479
|
isValidX509CertChain(chain: string, trusted: string): boolean;
|
|
354
480
|
crypto: CCFCrypto;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@microsoft/ccf-app",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-rc0",
|
|
4
4
|
"description": "CCF app support package",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"files": [
|
|
@@ -19,12 +19,14 @@
|
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/chai": "^4.2.15",
|
|
22
|
+
"@types/jsrsasign": "^10.5.4",
|
|
22
23
|
"@types/mocha": "^10.0.0",
|
|
23
24
|
"@types/node": "^18.0.0",
|
|
24
25
|
"@types/node-forge": "^1.0.0",
|
|
25
26
|
"chai": "^4.3.4",
|
|
26
27
|
"colors": "1.4.0",
|
|
27
28
|
"cross-env": "^7.0.3",
|
|
29
|
+
"jsrsasign": "^10.5.27",
|
|
28
30
|
"mocha": "^10.0.0",
|
|
29
31
|
"node-forge": "^1.2.0",
|
|
30
32
|
"ts-node": "^10.4.0",
|
package/polyfill.js
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
|
-
import * as
|
|
17
|
+
import * as jscrypto from "crypto";
|
|
18
18
|
import { TextEncoder, TextDecoder } from "util";
|
|
19
|
+
import * as rs from "jsrsasign";
|
|
19
20
|
// JavaScript's Map uses reference equality for non-primitive types,
|
|
20
21
|
// whereas CCF compares the content of the ArrayBuffer.
|
|
21
22
|
// To achieve CCF's semantics, all keys are base64-encoded.
|
|
@@ -93,10 +94,10 @@ class CCFPolyfill {
|
|
|
93
94
|
this.crypto = {
|
|
94
95
|
verifySignature(algorithm, key, signature, data) {
|
|
95
96
|
let padding = undefined;
|
|
96
|
-
const pubKey =
|
|
97
|
+
const pubKey = jscrypto.createPublicKey(key);
|
|
97
98
|
if (pubKey.asymmetricKeyType == "rsa") {
|
|
98
99
|
if (algorithm.name === "RSASSA-PKCS1-v1_5") {
|
|
99
|
-
padding =
|
|
100
|
+
padding = jscrypto.constants.RSA_PKCS1_PADDING;
|
|
100
101
|
}
|
|
101
102
|
else {
|
|
102
103
|
throw new Error("incompatible signing algorithm for given key type");
|
|
@@ -111,7 +112,7 @@ class CCFPolyfill {
|
|
|
111
112
|
throw new Error("unrecognized signing algorithm");
|
|
112
113
|
}
|
|
113
114
|
const hashAlg = algorithm.hash.replace("-", "").toLowerCase();
|
|
114
|
-
const verifier =
|
|
115
|
+
const verifier = jscrypto.createVerify(hashAlg);
|
|
115
116
|
verifier.update(new Uint8Array(data));
|
|
116
117
|
return verifier.verify({
|
|
117
118
|
key: pubKey,
|
|
@@ -119,6 +120,189 @@ class CCFPolyfill {
|
|
|
119
120
|
padding: padding,
|
|
120
121
|
}, new Uint8Array(signature));
|
|
121
122
|
},
|
|
123
|
+
generateAesKey(size) {
|
|
124
|
+
return nodeBufToArrBuf(jscrypto.randomBytes(size / 8));
|
|
125
|
+
},
|
|
126
|
+
generateRsaKeyPair(size, exponent) {
|
|
127
|
+
const rsaKeyPair = jscrypto.generateKeyPairSync("rsa", {
|
|
128
|
+
modulusLength: size,
|
|
129
|
+
publicExponent: exponent,
|
|
130
|
+
publicKeyEncoding: {
|
|
131
|
+
type: "spki",
|
|
132
|
+
format: "pem",
|
|
133
|
+
},
|
|
134
|
+
privateKeyEncoding: {
|
|
135
|
+
type: "pkcs8",
|
|
136
|
+
format: "pem",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return rsaKeyPair;
|
|
140
|
+
},
|
|
141
|
+
generateEcdsaKeyPair(curve) {
|
|
142
|
+
var curve_name = curve;
|
|
143
|
+
if (curve == "secp256r1")
|
|
144
|
+
curve_name = "prime256v1";
|
|
145
|
+
const ecdsaKeyPair = jscrypto.generateKeyPairSync("ec", {
|
|
146
|
+
namedCurve: curve_name,
|
|
147
|
+
publicKeyEncoding: {
|
|
148
|
+
type: "spki",
|
|
149
|
+
format: "pem",
|
|
150
|
+
},
|
|
151
|
+
privateKeyEncoding: {
|
|
152
|
+
type: "pkcs8",
|
|
153
|
+
format: "pem",
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
return ecdsaKeyPair;
|
|
157
|
+
},
|
|
158
|
+
generateEddsaKeyPair(curve) {
|
|
159
|
+
// `type` is always "ed25519" because currently only "curve25519" is supported for `curve`.
|
|
160
|
+
const type = "ed25519";
|
|
161
|
+
const ecdsaKeyPair = jscrypto.generateKeyPairSync(type, {
|
|
162
|
+
publicKeyEncoding: {
|
|
163
|
+
type: "spki",
|
|
164
|
+
format: "pem",
|
|
165
|
+
},
|
|
166
|
+
privateKeyEncoding: {
|
|
167
|
+
type: "pkcs8",
|
|
168
|
+
format: "pem",
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
return ecdsaKeyPair;
|
|
172
|
+
},
|
|
173
|
+
wrapKey(key, wrappingKey, parameters) {
|
|
174
|
+
if (parameters.name === "RSA-OAEP") {
|
|
175
|
+
return nodeBufToArrBuf(jscrypto.publicEncrypt({
|
|
176
|
+
key: Buffer.from(wrappingKey),
|
|
177
|
+
oaepHash: "sha256",
|
|
178
|
+
oaepLabel: parameters.label
|
|
179
|
+
? new Uint8Array(parameters.label)
|
|
180
|
+
: undefined,
|
|
181
|
+
padding: jscrypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
182
|
+
}, new Uint8Array(key)));
|
|
183
|
+
}
|
|
184
|
+
else if (parameters.name === "AES-KWP") {
|
|
185
|
+
const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649
|
|
186
|
+
const cipher = jscrypto.createCipheriv("id-aes256-wrap-pad", new Uint8Array(wrappingKey), iv);
|
|
187
|
+
return nodeBufToArrBuf(Buffer.concat([cipher.update(new Uint8Array(key)), cipher.final()]));
|
|
188
|
+
}
|
|
189
|
+
else if (parameters.name === "RSA-OAEP-AES-KWP") {
|
|
190
|
+
const randomAesKey = this.generateAesKey(parameters.aesKeySize);
|
|
191
|
+
const wrap1 = this.wrapKey(randomAesKey, wrappingKey, {
|
|
192
|
+
name: "RSA-OAEP",
|
|
193
|
+
label: parameters.label,
|
|
194
|
+
});
|
|
195
|
+
const wrap2 = this.wrapKey(key, randomAesKey, {
|
|
196
|
+
name: "AES-KWP",
|
|
197
|
+
});
|
|
198
|
+
return nodeBufToArrBuf(Buffer.concat([Buffer.from(wrap1), Buffer.from(wrap2)]));
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
throw new Error("unsupported wrapAlgo.name");
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
digest(algorithm, data) {
|
|
205
|
+
if (algorithm === "SHA-256") {
|
|
206
|
+
return nodeBufToArrBuf(jscrypto.createHash("sha256").update(new Uint8Array(data)).digest());
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
throw new Error("unsupported algorithm");
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
isValidX509CertBundle(pem) {
|
|
213
|
+
if ("X509Certificate" in jscrypto) {
|
|
214
|
+
const sep = "-----END CERTIFICATE-----";
|
|
215
|
+
const items = pem.split(sep);
|
|
216
|
+
if (items.length === 1) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const pems = items.slice(0, -1).map((p) => p + sep);
|
|
220
|
+
for (const [i, p] of pems.entries()) {
|
|
221
|
+
try {
|
|
222
|
+
new jscrypto.X509Certificate(p);
|
|
223
|
+
}
|
|
224
|
+
catch (e) {
|
|
225
|
+
console.error(`cert ${i} is not valid: ${e.message}`);
|
|
226
|
+
console.error(p);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
throw new Error("X509 validation unsupported, Node.js version too old (< 15.6.0)");
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
isValidX509CertChain(chain, trusted) {
|
|
237
|
+
if (!("X509Certificate" in jscrypto)) {
|
|
238
|
+
throw new Error("X509 validation unsupported, Node.js version too old (< 15.6.0)");
|
|
239
|
+
}
|
|
240
|
+
try {
|
|
241
|
+
const toX509Array = (pem) => {
|
|
242
|
+
const sep = "-----END CERTIFICATE-----";
|
|
243
|
+
const items = pem.split(sep);
|
|
244
|
+
if (items.length === 1) {
|
|
245
|
+
return [];
|
|
246
|
+
}
|
|
247
|
+
const pems = items.slice(0, -1).map((p) => p + sep);
|
|
248
|
+
const arr = pems.map((pem) => new jscrypto.X509Certificate(pem));
|
|
249
|
+
return arr;
|
|
250
|
+
};
|
|
251
|
+
const certsChain = toX509Array(chain);
|
|
252
|
+
const certsTrusted = toX509Array(trusted);
|
|
253
|
+
if (certsChain.length === 0) {
|
|
254
|
+
throw new Error("chain cannot be empty");
|
|
255
|
+
}
|
|
256
|
+
for (let i = 0; i < certsChain.length - 1; i++) {
|
|
257
|
+
if (!certsChain[i].checkIssued(certsChain[i + 1])) {
|
|
258
|
+
throw new Error(`chain[${i}] is not issued by chain[${i + 1}]`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
for (const certChain of certsChain) {
|
|
262
|
+
for (const certTrusted of certsTrusted) {
|
|
263
|
+
if (certChain.fingerprint === certTrusted.fingerprint) {
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
if (certChain.verify(certTrusted.publicKey)) {
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
throw new Error("none of the chain certificates are identical to or issued by a trusted certificate");
|
|
272
|
+
}
|
|
273
|
+
catch (e) {
|
|
274
|
+
console.error(`certificate chain validation failed: ${e.message}`);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
pubPemToJwk(pem, kid) {
|
|
279
|
+
let jwk = rs.KEYUTIL.getJWK(rs.KEYUTIL.getKey(pem));
|
|
280
|
+
if (kid !== undefined) {
|
|
281
|
+
jwk.kid = kid;
|
|
282
|
+
}
|
|
283
|
+
return jwk;
|
|
284
|
+
},
|
|
285
|
+
pemToJwk(pem, kid) {
|
|
286
|
+
let jwk = rs.KEYUTIL.getJWK(rs.KEYUTIL.getKey(pem));
|
|
287
|
+
if (kid !== undefined) {
|
|
288
|
+
jwk.kid = kid;
|
|
289
|
+
}
|
|
290
|
+
return jwk;
|
|
291
|
+
},
|
|
292
|
+
pubRsaPemToJwk(pem, kid) {
|
|
293
|
+
let jwk = rs.KEYUTIL.getJWK(rs.KEYUTIL.getKey(pem));
|
|
294
|
+
if (kid !== undefined) {
|
|
295
|
+
jwk.kid = kid;
|
|
296
|
+
}
|
|
297
|
+
return jwk;
|
|
298
|
+
},
|
|
299
|
+
rsaPemToJwk(pem, kid) {
|
|
300
|
+
let jwk = rs.KEYUTIL.getJWK(rs.KEYUTIL.getKey(pem));
|
|
301
|
+
if (kid !== undefined) {
|
|
302
|
+
jwk.kid = kid;
|
|
303
|
+
}
|
|
304
|
+
return jwk;
|
|
305
|
+
},
|
|
122
306
|
};
|
|
123
307
|
}
|
|
124
308
|
strToBuf(s) {
|
|
@@ -134,144 +318,25 @@ class CCFPolyfill {
|
|
|
134
318
|
return JSON.parse(this.bufToStr(v));
|
|
135
319
|
}
|
|
136
320
|
generateAesKey(size) {
|
|
137
|
-
return
|
|
321
|
+
return this.crypto.generateAesKey(size);
|
|
138
322
|
}
|
|
139
323
|
generateRsaKeyPair(size, exponent) {
|
|
140
|
-
|
|
141
|
-
modulusLength: size,
|
|
142
|
-
publicExponent: exponent,
|
|
143
|
-
publicKeyEncoding: {
|
|
144
|
-
type: "spki",
|
|
145
|
-
format: "pem",
|
|
146
|
-
},
|
|
147
|
-
privateKeyEncoding: {
|
|
148
|
-
type: "pkcs8",
|
|
149
|
-
format: "pem",
|
|
150
|
-
},
|
|
151
|
-
});
|
|
152
|
-
return rsaKeyPair;
|
|
324
|
+
return this.crypto.generateRsaKeyPair(size, exponent);
|
|
153
325
|
}
|
|
154
326
|
generateEcdsaKeyPair(curve) {
|
|
155
|
-
|
|
156
|
-
if (curve == "secp256r1")
|
|
157
|
-
curve_name = "prime256v1";
|
|
158
|
-
const ecdsaKeyPair = crypto.generateKeyPairSync("ec", {
|
|
159
|
-
namedCurve: curve_name,
|
|
160
|
-
publicKeyEncoding: {
|
|
161
|
-
type: "spki",
|
|
162
|
-
format: "pem",
|
|
163
|
-
},
|
|
164
|
-
privateKeyEncoding: {
|
|
165
|
-
type: "pkcs8",
|
|
166
|
-
format: "pem",
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
return ecdsaKeyPair;
|
|
327
|
+
return this.crypto.generateEcdsaKeyPair(curve);
|
|
170
328
|
}
|
|
171
329
|
wrapKey(key, wrappingKey, parameters) {
|
|
172
|
-
|
|
173
|
-
return nodeBufToArrBuf(crypto.publicEncrypt({
|
|
174
|
-
key: Buffer.from(wrappingKey),
|
|
175
|
-
oaepHash: "sha256",
|
|
176
|
-
oaepLabel: parameters.label
|
|
177
|
-
? new Uint8Array(parameters.label)
|
|
178
|
-
: undefined,
|
|
179
|
-
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
180
|
-
}, new Uint8Array(key)));
|
|
181
|
-
}
|
|
182
|
-
else if (parameters.name === "AES-KWP") {
|
|
183
|
-
const iv = Buffer.from("A65959A6", "hex"); // defined in RFC 5649
|
|
184
|
-
const cipher = crypto.createCipheriv("id-aes256-wrap-pad", new Uint8Array(wrappingKey), iv);
|
|
185
|
-
return nodeBufToArrBuf(Buffer.concat([cipher.update(new Uint8Array(key)), cipher.final()]));
|
|
186
|
-
}
|
|
187
|
-
else if (parameters.name === "RSA-OAEP-AES-KWP") {
|
|
188
|
-
const randomAesKey = this.generateAesKey(parameters.aesKeySize);
|
|
189
|
-
const wrap1 = this.wrapKey(randomAesKey, wrappingKey, {
|
|
190
|
-
name: "RSA-OAEP",
|
|
191
|
-
label: parameters.label,
|
|
192
|
-
});
|
|
193
|
-
const wrap2 = this.wrapKey(key, randomAesKey, {
|
|
194
|
-
name: "AES-KWP",
|
|
195
|
-
});
|
|
196
|
-
return nodeBufToArrBuf(Buffer.concat([Buffer.from(wrap1), Buffer.from(wrap2)]));
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
throw new Error("unsupported wrapAlgo.name");
|
|
200
|
-
}
|
|
330
|
+
return this.crypto.wrapKey(key, wrappingKey, parameters);
|
|
201
331
|
}
|
|
202
332
|
digest(algorithm, data) {
|
|
203
|
-
|
|
204
|
-
return nodeBufToArrBuf(crypto.createHash("sha256").update(new Uint8Array(data)).digest());
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
throw new Error("unsupported algorithm");
|
|
208
|
-
}
|
|
333
|
+
return this.crypto.digest(algorithm, data);
|
|
209
334
|
}
|
|
210
335
|
isValidX509CertBundle(pem) {
|
|
211
|
-
|
|
212
|
-
const sep = "-----END CERTIFICATE-----";
|
|
213
|
-
const items = pem.split(sep);
|
|
214
|
-
if (items.length === 1) {
|
|
215
|
-
return false;
|
|
216
|
-
}
|
|
217
|
-
const pems = items.slice(0, -1).map((p) => p + sep);
|
|
218
|
-
for (const [i, p] of pems.entries()) {
|
|
219
|
-
try {
|
|
220
|
-
new crypto.X509Certificate(p);
|
|
221
|
-
}
|
|
222
|
-
catch (e) {
|
|
223
|
-
console.error(`cert ${i} is not valid: ${e.message}`);
|
|
224
|
-
console.error(p);
|
|
225
|
-
return false;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
return true;
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
throw new Error("X509 validation unsupported, Node.js version too old (< 15.6.0)");
|
|
232
|
-
}
|
|
336
|
+
return this.crypto.isValidX509CertBundle(pem);
|
|
233
337
|
}
|
|
234
338
|
isValidX509CertChain(chain, trusted) {
|
|
235
|
-
|
|
236
|
-
throw new Error("X509 validation unsupported, Node.js version too old (< 15.6.0)");
|
|
237
|
-
}
|
|
238
|
-
try {
|
|
239
|
-
const toX509Array = (pem) => {
|
|
240
|
-
const sep = "-----END CERTIFICATE-----";
|
|
241
|
-
const items = pem.split(sep);
|
|
242
|
-
if (items.length === 1) {
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
const pems = items.slice(0, -1).map((p) => p + sep);
|
|
246
|
-
const arr = pems.map((pem) => new crypto.X509Certificate(pem));
|
|
247
|
-
return arr;
|
|
248
|
-
};
|
|
249
|
-
const certsChain = toX509Array(chain);
|
|
250
|
-
const certsTrusted = toX509Array(trusted);
|
|
251
|
-
if (certsChain.length === 0) {
|
|
252
|
-
throw new Error("chain cannot be empty");
|
|
253
|
-
}
|
|
254
|
-
for (let i = 0; i < certsChain.length - 1; i++) {
|
|
255
|
-
if (!certsChain[i].checkIssued(certsChain[i + 1])) {
|
|
256
|
-
throw new Error(`chain[${i}] is not issued by chain[${i + 1}]`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
for (const certChain of certsChain) {
|
|
260
|
-
for (const certTrusted of certsTrusted) {
|
|
261
|
-
if (certChain.fingerprint === certTrusted.fingerprint) {
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
if (certChain.verify(certTrusted.publicKey)) {
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
throw new Error("none of the chain certificates are identical to or issued by a trusted certificate");
|
|
270
|
-
}
|
|
271
|
-
catch (e) {
|
|
272
|
-
console.error(`certificate chain validation failed: ${e.message}`);
|
|
273
|
-
return false;
|
|
274
|
-
}
|
|
339
|
+
return this.crypto.isValidX509CertChain(chain, trusted);
|
|
275
340
|
}
|
|
276
341
|
}
|
|
277
342
|
globalThis.ccf = new CCFPolyfill();
|