@matter/general 0.14.1-alpha.0-20250606-a9bcd03f9 → 0.15.0-alpha.0-20250612-ddd428561
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/dist/cjs/codec/DerCodec.d.ts +12 -17
- package/dist/cjs/codec/DerCodec.d.ts.map +1 -1
- package/dist/cjs/codec/DerCodec.js +90 -51
- package/dist/cjs/codec/DerCodec.js.map +1 -1
- package/dist/cjs/codec/DerTypes.js +1 -1
- package/dist/cjs/codec/DnsCodec.d.ts +5 -5
- package/dist/cjs/crypto/Crypto.d.ts +111 -62
- package/dist/cjs/crypto/Crypto.d.ts.map +1 -1
- package/dist/cjs/crypto/Crypto.js +92 -31
- package/dist/cjs/crypto/Crypto.js.map +1 -1
- package/dist/cjs/crypto/CryptoError.d.ts +32 -0
- package/dist/cjs/crypto/CryptoError.d.ts.map +1 -0
- package/dist/cjs/crypto/CryptoError.js +44 -0
- package/dist/cjs/crypto/CryptoError.js.map +6 -0
- package/dist/cjs/crypto/Key.d.ts +2 -2
- package/dist/cjs/crypto/Key.d.ts.map +1 -1
- package/dist/cjs/crypto/Key.js +15 -16
- package/dist/cjs/crypto/Key.js.map +1 -1
- package/dist/cjs/crypto/Spake2p.js +5 -5
- package/dist/cjs/crypto/Spake2p.js.map +1 -1
- package/dist/cjs/crypto/StandardCrypto.d.ts +33 -0
- package/dist/cjs/crypto/StandardCrypto.d.ts.map +1 -0
- package/dist/cjs/crypto/StandardCrypto.js +208 -0
- package/dist/cjs/crypto/StandardCrypto.js.map +6 -0
- package/dist/cjs/crypto/aes/Aes.d.ts +21 -0
- package/dist/cjs/crypto/aes/Aes.d.ts.map +1 -0
- package/dist/cjs/crypto/aes/Aes.js +132 -0
- package/dist/cjs/crypto/aes/Aes.js.map +6 -0
- package/dist/cjs/crypto/aes/Ccm.d.ts +71 -0
- package/dist/cjs/crypto/aes/Ccm.d.ts.map +1 -0
- package/dist/cjs/crypto/aes/Ccm.js +194 -0
- package/dist/cjs/crypto/aes/Ccm.js.map +6 -0
- package/dist/cjs/crypto/aes/WordArray.d.ts +30 -0
- package/dist/cjs/crypto/aes/WordArray.d.ts.map +1 -0
- package/dist/cjs/crypto/aes/WordArray.js +91 -0
- package/dist/cjs/crypto/aes/WordArray.js.map +6 -0
- package/dist/cjs/crypto/index.d.ts +3 -0
- package/dist/cjs/crypto/index.d.ts.map +1 -1
- package/dist/cjs/crypto/index.js +3 -0
- package/dist/cjs/crypto/index.js.map +1 -1
- package/dist/cjs/crypto/nonentropic.d.ts +16 -0
- package/dist/cjs/crypto/nonentropic.d.ts.map +1 -0
- package/dist/cjs/crypto/nonentropic.js +70 -0
- package/dist/cjs/crypto/nonentropic.js.map +6 -0
- package/dist/cjs/environment/Environment.d.ts.map +1 -1
- package/dist/cjs/environment/Environment.js +1 -5
- package/dist/cjs/environment/Environment.js.map +1 -1
- package/dist/cjs/environment/RuntimeService.d.ts +2 -4
- package/dist/cjs/environment/RuntimeService.d.ts.map +1 -1
- package/dist/cjs/environment/RuntimeService.js +4 -4
- package/dist/cjs/environment/RuntimeService.js.map +1 -1
- package/dist/cjs/environment/VariableService.d.ts.map +1 -1
- package/dist/cjs/environment/VariableService.js +1 -0
- package/dist/cjs/environment/VariableService.js.map +1 -1
- package/dist/cjs/log/LogFormat.js +17 -11
- package/dist/cjs/log/LogFormat.js.map +1 -1
- package/dist/cjs/net/Network.d.ts +0 -1
- package/dist/cjs/net/Network.d.ts.map +1 -1
- package/dist/cjs/net/Network.js +0 -4
- package/dist/cjs/net/Network.js.map +1 -1
- package/dist/cjs/time/Time.d.ts.map +1 -1
- package/dist/cjs/time/Time.js +2 -2
- package/dist/cjs/time/Time.js.map +1 -1
- package/dist/cjs/util/Bytes.d.ts +6 -0
- package/dist/cjs/util/Bytes.d.ts.map +1 -1
- package/dist/cjs/util/Bytes.js +15 -1
- package/dist/cjs/util/Bytes.js.map +1 -1
- package/dist/cjs/util/DataWriter.d.ts +1 -1
- package/dist/cjs/util/DataWriter.js +2 -2
- package/dist/cjs/util/DataWriter.js.map +1 -1
- package/dist/cjs/util/DeepCopy.js +1 -1
- package/dist/cjs/util/DeepCopy.js.map +1 -1
- package/dist/cjs/util/GeneratedClass.d.ts +3 -3
- package/dist/cjs/util/GeneratedClass.d.ts.map +1 -1
- package/dist/cjs/util/GeneratedClass.js +99 -73
- package/dist/cjs/util/GeneratedClass.js.map +2 -2
- package/dist/cjs/util/Number.d.ts +0 -1
- package/dist/cjs/util/Number.d.ts.map +1 -1
- package/dist/cjs/util/Number.js +0 -4
- package/dist/cjs/util/Number.js.map +1 -1
- package/dist/esm/codec/DerCodec.d.ts +12 -17
- package/dist/esm/codec/DerCodec.d.ts.map +1 -1
- package/dist/esm/codec/DerCodec.js +90 -51
- package/dist/esm/codec/DerCodec.js.map +1 -1
- package/dist/esm/codec/DerTypes.js +2 -2
- package/dist/esm/codec/DnsCodec.d.ts +5 -5
- package/dist/esm/crypto/Crypto.d.ts +111 -62
- package/dist/esm/crypto/Crypto.d.ts.map +1 -1
- package/dist/esm/crypto/Crypto.js +93 -32
- package/dist/esm/crypto/Crypto.js.map +1 -1
- package/dist/esm/crypto/CryptoError.d.ts +32 -0
- package/dist/esm/crypto/CryptoError.d.ts.map +1 -0
- package/dist/esm/crypto/CryptoError.js +24 -0
- package/dist/esm/crypto/CryptoError.js.map +6 -0
- package/dist/esm/crypto/Key.d.ts +2 -2
- package/dist/esm/crypto/Key.d.ts.map +1 -1
- package/dist/esm/crypto/Key.js +15 -16
- package/dist/esm/crypto/Key.js.map +1 -1
- package/dist/esm/crypto/Spake2p.js +5 -5
- package/dist/esm/crypto/Spake2p.js.map +1 -1
- package/dist/esm/crypto/StandardCrypto.d.ts +33 -0
- package/dist/esm/crypto/StandardCrypto.d.ts.map +1 -0
- package/dist/esm/crypto/StandardCrypto.js +188 -0
- package/dist/esm/crypto/StandardCrypto.js.map +6 -0
- package/dist/esm/crypto/aes/Aes.d.ts +21 -0
- package/dist/esm/crypto/aes/Aes.d.ts.map +1 -0
- package/dist/esm/crypto/aes/Aes.js +112 -0
- package/dist/esm/crypto/aes/Aes.js.map +6 -0
- package/dist/esm/crypto/aes/Ccm.d.ts +71 -0
- package/dist/esm/crypto/aes/Ccm.d.ts.map +1 -0
- package/dist/esm/crypto/aes/Ccm.js +174 -0
- package/dist/esm/crypto/aes/Ccm.js.map +6 -0
- package/dist/esm/crypto/aes/WordArray.d.ts +30 -0
- package/dist/esm/crypto/aes/WordArray.d.ts.map +1 -0
- package/dist/esm/crypto/aes/WordArray.js +71 -0
- package/dist/esm/crypto/aes/WordArray.js.map +6 -0
- package/dist/esm/crypto/index.d.ts +3 -0
- package/dist/esm/crypto/index.d.ts.map +1 -1
- package/dist/esm/crypto/index.js +3 -0
- package/dist/esm/crypto/index.js.map +1 -1
- package/dist/esm/crypto/nonentropic.d.ts +16 -0
- package/dist/esm/crypto/nonentropic.d.ts.map +1 -0
- package/dist/esm/crypto/nonentropic.js +50 -0
- package/dist/esm/crypto/nonentropic.js.map +6 -0
- package/dist/esm/environment/Environment.d.ts.map +1 -1
- package/dist/esm/environment/Environment.js +1 -5
- package/dist/esm/environment/Environment.js.map +1 -1
- package/dist/esm/environment/RuntimeService.d.ts +2 -4
- package/dist/esm/environment/RuntimeService.d.ts.map +1 -1
- package/dist/esm/environment/RuntimeService.js +4 -4
- package/dist/esm/environment/RuntimeService.js.map +1 -1
- package/dist/esm/environment/VariableService.d.ts.map +1 -1
- package/dist/esm/environment/VariableService.js +1 -0
- package/dist/esm/environment/VariableService.js.map +1 -1
- package/dist/esm/log/LogFormat.js +17 -11
- package/dist/esm/log/LogFormat.js.map +1 -1
- package/dist/esm/net/Network.d.ts +0 -1
- package/dist/esm/net/Network.d.ts.map +1 -1
- package/dist/esm/net/Network.js +1 -5
- package/dist/esm/net/Network.js.map +1 -1
- package/dist/esm/time/Time.d.ts.map +1 -1
- package/dist/esm/time/Time.js +2 -2
- package/dist/esm/time/Time.js.map +1 -1
- package/dist/esm/util/Bytes.d.ts +6 -0
- package/dist/esm/util/Bytes.d.ts.map +1 -1
- package/dist/esm/util/Bytes.js +15 -1
- package/dist/esm/util/Bytes.js.map +1 -1
- package/dist/esm/util/DataWriter.d.ts +1 -1
- package/dist/esm/util/DataWriter.js +3 -3
- package/dist/esm/util/DataWriter.js.map +1 -1
- package/dist/esm/util/DeepCopy.js +1 -1
- package/dist/esm/util/DeepCopy.js.map +1 -1
- package/dist/esm/util/GeneratedClass.d.ts +3 -3
- package/dist/esm/util/GeneratedClass.d.ts.map +1 -1
- package/dist/esm/util/GeneratedClass.js +97 -71
- package/dist/esm/util/GeneratedClass.js.map +2 -2
- package/dist/esm/util/Number.d.ts +0 -1
- package/dist/esm/util/Number.d.ts.map +1 -1
- package/dist/esm/util/Number.js +0 -4
- package/dist/esm/util/Number.js.map +1 -1
- package/package.json +3 -3
- package/src/codec/DerCodec.ts +106 -52
- package/src/codec/DerTypes.ts +2 -2
- package/src/crypto/Crypto.ts +196 -76
- package/src/crypto/CryptoError.ts +32 -0
- package/src/crypto/Key.ts +17 -18
- package/src/crypto/Spake2p.ts +5 -5
- package/src/crypto/StandardCrypto.ts +252 -0
- package/src/crypto/aes/Aes.ts +210 -0
- package/src/crypto/aes/Ccm.ts +350 -0
- package/src/crypto/aes/README.md +4 -0
- package/src/crypto/aes/WordArray.ts +105 -0
- package/src/crypto/index.ts +3 -0
- package/src/crypto/nonentropic.ts +65 -0
- package/src/environment/Environment.ts +1 -6
- package/src/environment/RuntimeService.ts +5 -5
- package/src/environment/VariableService.ts +1 -0
- package/src/log/LogFormat.ts +19 -11
- package/src/net/Network.ts +1 -7
- package/src/time/Time.ts +4 -4
- package/src/util/Bytes.ts +19 -0
- package/src/util/DataWriter.ts +3 -3
- package/src/util/DeepCopy.ts +2 -2
- package/src/util/GeneratedClass.ts +161 -102
- package/src/util/Number.ts +0 -4
package/src/crypto/Key.ts
CHANGED
|
@@ -9,14 +9,13 @@ import { DerCodec, DerNode, DerType } from "../codec/DerCodec.js";
|
|
|
9
9
|
import { MatterError, NotImplementedError } from "../MatterError.js";
|
|
10
10
|
import { Bytes } from "../util/Bytes.js";
|
|
11
11
|
import { ec } from "./Crypto.js";
|
|
12
|
+
import { KeyInputError } from "./CryptoError.js";
|
|
12
13
|
|
|
13
14
|
const {
|
|
14
15
|
numberToBytesBE,
|
|
15
16
|
p256: { ProjectivePoint },
|
|
16
17
|
} = ec;
|
|
17
18
|
|
|
18
|
-
class KeyError extends MatterError {}
|
|
19
|
-
|
|
20
19
|
const JWK_KEYS = [
|
|
21
20
|
"crv",
|
|
22
21
|
"d",
|
|
@@ -228,7 +227,7 @@ function checkDerVersion(type: string, node: DerNode | undefined, version: numbe
|
|
|
228
227
|
node && node._tag === DerType.Integer && node._bytes && node._bytes.length === 1 && node._bytes[0];
|
|
229
228
|
|
|
230
229
|
if (derVersion !== version) {
|
|
231
|
-
throw new
|
|
230
|
+
throw new KeyInputError(`${type} key version mismatch`);
|
|
232
231
|
}
|
|
233
232
|
}
|
|
234
233
|
|
|
@@ -237,14 +236,14 @@ function getDerObjectID(type: string, node?: DerNode) {
|
|
|
237
236
|
|
|
238
237
|
if (id) return id;
|
|
239
238
|
|
|
240
|
-
throw new
|
|
239
|
+
throw new KeyInputError(`Missing object in ${type} key`);
|
|
241
240
|
}
|
|
242
241
|
|
|
243
242
|
function getDerCurve(type: string, node?: DerNode) {
|
|
244
243
|
const oid = getDerObjectID(type, node);
|
|
245
244
|
const curve = (<any>CurveLookup)[Bytes.toHex(oid)];
|
|
246
245
|
if (curve) return curve;
|
|
247
|
-
throw new
|
|
246
|
+
throw new KeyInputError(`Unsupported ${type} EC curve`);
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
function getDerKey(type: string, node?: DerNode, derType: DerType = DerType.OctetString) {
|
|
@@ -297,7 +296,7 @@ namespace Translators {
|
|
|
297
296
|
const algorithmElements = outer?._elements?.[1]?._elements;
|
|
298
297
|
const algorithm = getDerObjectID("PKCS #8", algorithmElements?.[0]);
|
|
299
298
|
if (Bytes.toHex(algorithm) !== Asn1ObjectID.ecPublicKey) {
|
|
300
|
-
throw new
|
|
299
|
+
throw new KeyInputError("Unsupported PKCS #8 decryption algorithm");
|
|
301
300
|
}
|
|
302
301
|
|
|
303
302
|
// Curve
|
|
@@ -306,7 +305,7 @@ namespace Translators {
|
|
|
306
305
|
// Private key
|
|
307
306
|
const innerBytes = outer?._elements?.[2]._bytes;
|
|
308
307
|
if (innerBytes === undefined || innerBytes === null) {
|
|
309
|
-
throw new
|
|
308
|
+
throw new KeyInputError("Invalid PKCS #8 key");
|
|
310
309
|
}
|
|
311
310
|
const inner = DerCodec.decode(innerBytes);
|
|
312
311
|
const key = getDerKey("PKCS #8", inner?._elements?.[1]);
|
|
@@ -331,7 +330,7 @@ namespace Translators {
|
|
|
331
330
|
// Algorithm
|
|
332
331
|
const algorithm = getDerObjectID("SPKI", algorithmElements?.[0]);
|
|
333
332
|
if (Bytes.toHex(algorithm) !== Asn1ObjectID.ecPublicKey) {
|
|
334
|
-
throw new
|
|
333
|
+
throw new KeyInputError("Unsupported SPKI decryption algorithm");
|
|
335
334
|
}
|
|
336
335
|
|
|
337
336
|
// Curve
|
|
@@ -354,19 +353,19 @@ namespace Translators {
|
|
|
354
353
|
export const publicBits = {
|
|
355
354
|
set: function (this: Key, input: Uint8Array) {
|
|
356
355
|
if (!(input.length % 2)) {
|
|
357
|
-
throw new
|
|
356
|
+
throw new KeyInputError("Invalid public key encoding");
|
|
358
357
|
}
|
|
359
358
|
|
|
360
359
|
switch (input[0]) {
|
|
361
360
|
case 2:
|
|
362
361
|
case 3:
|
|
363
|
-
throw new
|
|
362
|
+
throw new KeyInputError("Unsupported public key compression");
|
|
364
363
|
|
|
365
364
|
case 4:
|
|
366
365
|
break;
|
|
367
366
|
|
|
368
367
|
case 5:
|
|
369
|
-
throw new
|
|
368
|
+
throw new KeyInputError("Illegal public key format specifier");
|
|
370
369
|
}
|
|
371
370
|
|
|
372
371
|
const coordinateLength = (input.length - 1) / 2;
|
|
@@ -446,7 +445,7 @@ function inferCurve(key: Key, bytes: number) {
|
|
|
446
445
|
break;
|
|
447
446
|
|
|
448
447
|
default:
|
|
449
|
-
throw new
|
|
448
|
+
throw new KeyInputError(`Cannot infer named curve from key length ${bytes}`);
|
|
450
449
|
}
|
|
451
450
|
}
|
|
452
451
|
}
|
|
@@ -502,7 +501,7 @@ export function Key(properties: Partial<Key>) {
|
|
|
502
501
|
get: () => {
|
|
503
502
|
const result = that[target];
|
|
504
503
|
if (result === undefined) {
|
|
505
|
-
throw new
|
|
504
|
+
throw new KeyInputError(`Key field ${target} is not defined`);
|
|
506
505
|
}
|
|
507
506
|
return result;
|
|
508
507
|
},
|
|
@@ -517,8 +516,8 @@ export function Key(properties: Partial<Key>) {
|
|
|
517
516
|
|
|
518
517
|
/** Compute public point from private EC key */
|
|
519
518
|
function derivePublicFromPrivate() {
|
|
520
|
-
if (that.type !== KeyType.EC) throw new
|
|
521
|
-
if (!that.private) throw new
|
|
519
|
+
if (that.type !== KeyType.EC) throw new KeyInputError("EC key type required to compute public point");
|
|
520
|
+
if (!that.private) throw new KeyInputError("EC private key required to compute public point");
|
|
522
521
|
|
|
523
522
|
const crv = that.crv;
|
|
524
523
|
let keyLength: number;
|
|
@@ -532,7 +531,7 @@ export function Key(properties: Partial<Key>) {
|
|
|
532
531
|
break;
|
|
533
532
|
|
|
534
533
|
default:
|
|
535
|
-
throw new
|
|
534
|
+
throw new KeyInputError(`Unsupported elliptic curve ${crv}`);
|
|
536
535
|
}
|
|
537
536
|
|
|
538
537
|
// Compute
|
|
@@ -559,7 +558,7 @@ export function Key(properties: Partial<Key>) {
|
|
|
559
558
|
}
|
|
560
559
|
|
|
561
560
|
/**
|
|
562
|
-
*
|
|
561
|
+
* EC private key factory.
|
|
563
562
|
*/
|
|
564
563
|
export function PrivateKey(privateKey: Uint8Array | BinaryKeyPair, options?: Partial<Key>) {
|
|
565
564
|
let priv, pub;
|
|
@@ -578,7 +577,7 @@ export function PrivateKey(privateKey: Uint8Array | BinaryKeyPair, options?: Par
|
|
|
578
577
|
}
|
|
579
578
|
|
|
580
579
|
/**
|
|
581
|
-
*
|
|
580
|
+
* EC public key factory.
|
|
582
581
|
*/
|
|
583
582
|
export function PublicKey(publicKey: Uint8Array, options?: Partial<Key>) {
|
|
584
583
|
return Key({
|
package/src/crypto/Spake2p.ts
CHANGED
|
@@ -32,7 +32,7 @@ export class Spake2p {
|
|
|
32
32
|
static async computeW0W1({ iterations, salt }: PbkdfParameters, pin: number) {
|
|
33
33
|
const pinWriter = new DataWriter(Endian.Little);
|
|
34
34
|
pinWriter.writeUInt32(pin);
|
|
35
|
-
const ws = await Crypto.
|
|
35
|
+
const ws = await Crypto.createPbkdf2Key(pinWriter.toByteArray(), salt, iterations, CRYPTO_W_SIZE_BYTES * 2);
|
|
36
36
|
const w0 = mod(bytesToNumberBE(ws.slice(0, 40)), P256_CURVE.n);
|
|
37
37
|
const w1 = mod(bytesToNumberBE(ws.slice(40, 80)), P256_CURVE.n);
|
|
38
38
|
return { w0, w1 };
|
|
@@ -96,12 +96,12 @@ export class Spake2p {
|
|
|
96
96
|
const Ka = TT_HASH.slice(0, 16);
|
|
97
97
|
const Ke = TT_HASH.slice(16, 32);
|
|
98
98
|
|
|
99
|
-
const KcAB = await Crypto.
|
|
99
|
+
const KcAB = await Crypto.createHkdfKey(Ka, new Uint8Array(0), Bytes.fromString("ConfirmationKeys"), 32);
|
|
100
100
|
const KcA = KcAB.slice(0, 16);
|
|
101
101
|
const KcB = KcAB.slice(16, 32);
|
|
102
102
|
|
|
103
|
-
const hAY = await Crypto.
|
|
104
|
-
const hBX = await Crypto.
|
|
103
|
+
const hAY = await Crypto.signHmac(KcA, Y);
|
|
104
|
+
const hBX = await Crypto.signHmac(KcB, X);
|
|
105
105
|
|
|
106
106
|
return { Ke, hAY, hBX };
|
|
107
107
|
}
|
|
@@ -118,7 +118,7 @@ export class Spake2p {
|
|
|
118
118
|
this.addToContext(TTwriter, Z);
|
|
119
119
|
this.addToContext(TTwriter, V);
|
|
120
120
|
this.addToContext(TTwriter, numberToBytesBE(this.w0, 32));
|
|
121
|
-
return Crypto.
|
|
121
|
+
return Crypto.computeSha256(TTwriter.toByteArray());
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
private addToContext(TTwriter: DataWriter<Endian.Little>, data: Uint8Array) {
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
*
|
|
4
|
+
* Portions copyright 2022-2023 Project CHIP Authors
|
|
5
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { DerBigUint, DerCodec, DerError } from "#codec/DerCodec.js";
|
|
9
|
+
import { Boot } from "#util/Boot.js";
|
|
10
|
+
import { Bytes } from "#util/Bytes.js";
|
|
11
|
+
import { Ccm } from "./aes/Ccm.js";
|
|
12
|
+
import { Crypto, CRYPTO_SYMMETRIC_KEY_LENGTH, CryptoDsaEncoding } from "./Crypto.js";
|
|
13
|
+
import { CryptoVerifyError, KeyInputError } from "./CryptoError.js";
|
|
14
|
+
import { CurveType, Key, KeyType, PrivateKey, PublicKey } from "./Key.js";
|
|
15
|
+
|
|
16
|
+
const subtle = globalThis.crypto.subtle;
|
|
17
|
+
|
|
18
|
+
const SIGNATURE_ALGORITHM = <EcdsaParams>{
|
|
19
|
+
name: "ECDSA",
|
|
20
|
+
namedCurve: "P-256",
|
|
21
|
+
hash: { name: "SHA-256" },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A {@link Crypto} implementation based on standard "crypto.subtle" with missing portions implemented using JS.
|
|
26
|
+
*
|
|
27
|
+
* WARNING: This code is unaudited. Use a trusted native alternative where available.
|
|
28
|
+
*
|
|
29
|
+
* This module is mostly based on {@link crypto.subtle}. This should be a reliable native implementation. However,
|
|
30
|
+
* Web Crypto doesn't support AES-CCM required by Matter so fall back to a JS implementation for that. See relevant
|
|
31
|
+
* warnings in the "aes" subdirectory.
|
|
32
|
+
*/
|
|
33
|
+
export class StandardCrypto implements Crypto {
|
|
34
|
+
implementationName = "JS";
|
|
35
|
+
|
|
36
|
+
static provider() {
|
|
37
|
+
return new StandardCrypto();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getRandomData(length: number): Uint8Array {
|
|
41
|
+
const result = new Uint8Array(length);
|
|
42
|
+
crypto.getRandomValues(result);
|
|
43
|
+
return result;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
encrypt(key: Uint8Array, data: Uint8Array, nonce: Uint8Array, associatedData?: Uint8Array) {
|
|
47
|
+
const ccm = Ccm(key);
|
|
48
|
+
return ccm.encrypt({ pt: data, nonce, adata: associatedData });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
decrypt(key: Uint8Array, data: Uint8Array, nonce: Uint8Array, associatedData?: Uint8Array) {
|
|
52
|
+
const ccm = Ccm(key);
|
|
53
|
+
return ccm.decrypt({ ct: data, nonce, adata: associatedData });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async computeSha256(buffer: Uint8Array | Uint8Array[]) {
|
|
57
|
+
if (Array.isArray(buffer)) {
|
|
58
|
+
buffer = Bytes.concat(...buffer);
|
|
59
|
+
}
|
|
60
|
+
return new Uint8Array(await subtle.digest("SHA-256", buffer));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async createPbkdf2Key(secret: Uint8Array, salt: Uint8Array, iteration: number, keyLength: number) {
|
|
64
|
+
const key = await importKey("raw", secret, "PBKDF2", false, ["deriveBits"]);
|
|
65
|
+
const bits = await subtle.deriveBits(
|
|
66
|
+
{
|
|
67
|
+
name: "PBKDF2",
|
|
68
|
+
hash: "SHA-256",
|
|
69
|
+
salt: salt,
|
|
70
|
+
iterations: iteration,
|
|
71
|
+
},
|
|
72
|
+
key,
|
|
73
|
+
keyLength * 8,
|
|
74
|
+
);
|
|
75
|
+
return new Uint8Array(bits);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async createHkdfKey(
|
|
79
|
+
secret: Uint8Array,
|
|
80
|
+
salt: Uint8Array,
|
|
81
|
+
info: Uint8Array,
|
|
82
|
+
length: number = CRYPTO_SYMMETRIC_KEY_LENGTH,
|
|
83
|
+
) {
|
|
84
|
+
const key = await importKey("raw", secret, "HKDF", false, ["deriveBits"]);
|
|
85
|
+
const bits = await subtle.deriveBits(
|
|
86
|
+
{
|
|
87
|
+
name: "HKDF",
|
|
88
|
+
hash: "SHA-256",
|
|
89
|
+
salt: salt,
|
|
90
|
+
info: info,
|
|
91
|
+
},
|
|
92
|
+
key,
|
|
93
|
+
8 * length,
|
|
94
|
+
);
|
|
95
|
+
return new Uint8Array(bits);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async signHmac(secret: Uint8Array, data: Uint8Array) {
|
|
99
|
+
const key = await importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
100
|
+
return new Uint8Array(await subtle.sign("HMAC", key, data));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async signEcdsa(key: JsonWebKey, data: Uint8Array | Uint8Array[], dsaEncoding?: CryptoDsaEncoding) {
|
|
104
|
+
if (Array.isArray(data)) {
|
|
105
|
+
data = Bytes.concat(...data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const { crv, kty, d, x, y } = key;
|
|
109
|
+
|
|
110
|
+
key = {
|
|
111
|
+
kty,
|
|
112
|
+
crv,
|
|
113
|
+
d,
|
|
114
|
+
x,
|
|
115
|
+
y,
|
|
116
|
+
ext: true, // Required by some subtle implementations to sign
|
|
117
|
+
key_ops: ["sign"],
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const subtleKey = await importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["sign"]);
|
|
121
|
+
|
|
122
|
+
const ieeeP1363 = await subtle.sign(SIGNATURE_ALGORITHM, subtleKey, data);
|
|
123
|
+
|
|
124
|
+
if (dsaEncoding !== "der") return new Uint8Array(ieeeP1363);
|
|
125
|
+
|
|
126
|
+
const bytesPerComponent = ieeeP1363.byteLength / 2;
|
|
127
|
+
|
|
128
|
+
return DerCodec.encode({
|
|
129
|
+
r: DerBigUint(ieeeP1363.slice(0, bytesPerComponent)),
|
|
130
|
+
s: DerBigUint(ieeeP1363.slice(bytesPerComponent)),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async verifyEcdsa(key: JsonWebKey, data: Uint8Array, signature: Uint8Array, dsaEncoding?: CryptoDsaEncoding) {
|
|
135
|
+
const { crv, kty, x, y } = key;
|
|
136
|
+
key = { crv, kty, x, y };
|
|
137
|
+
const subtleKey = await importKey("jwk", key, SIGNATURE_ALGORITHM, false, ["verify"]);
|
|
138
|
+
|
|
139
|
+
if (dsaEncoding === "der") {
|
|
140
|
+
try {
|
|
141
|
+
const decoded = DerCodec.decode(signature);
|
|
142
|
+
|
|
143
|
+
const r = DerCodec.decodeBigUint(decoded?._elements?.[0], 32);
|
|
144
|
+
const s = DerCodec.decodeBigUint(decoded?._elements?.[1], 32);
|
|
145
|
+
|
|
146
|
+
signature = Bytes.concat(r, s);
|
|
147
|
+
} catch (cause) {
|
|
148
|
+
DerError.accept(cause);
|
|
149
|
+
|
|
150
|
+
throw new CryptoVerifyError("Invalid DER signature", { cause });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const verified = await subtle.verify(SIGNATURE_ALGORITHM, subtleKey, signature, data);
|
|
155
|
+
|
|
156
|
+
if (!verified) {
|
|
157
|
+
throw new CryptoVerifyError("Signature verification failed");
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async createKeyPair() {
|
|
162
|
+
const subtleKey = await subtle.generateKey(
|
|
163
|
+
{
|
|
164
|
+
// We must specify either ECDH or ECDSA to get an EC key but we may use the key for either (but not for
|
|
165
|
+
// both)
|
|
166
|
+
name: "ECDH",
|
|
167
|
+
namedCurve: "P-256",
|
|
168
|
+
},
|
|
169
|
+
true,
|
|
170
|
+
|
|
171
|
+
// We must also specify usage but will drop this on export
|
|
172
|
+
["deriveKey"],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
// Do not export as JWK because we do not want to inherit the algorithm and key_ops
|
|
176
|
+
const key = await subtle.exportKey("jwk", subtleKey.privateKey);
|
|
177
|
+
|
|
178
|
+
// Extract only private and public fields; we do not want key_ops
|
|
179
|
+
return Key({
|
|
180
|
+
kty: KeyType.EC,
|
|
181
|
+
crv: CurveType.p256,
|
|
182
|
+
d: key.d,
|
|
183
|
+
x: key.x,
|
|
184
|
+
y: key.y,
|
|
185
|
+
}) as PrivateKey;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async generateDhSecret(key: PrivateKey, peerKey: PublicKey) {
|
|
189
|
+
const subtleKey = await importKey(
|
|
190
|
+
"jwk",
|
|
191
|
+
key,
|
|
192
|
+
{
|
|
193
|
+
name: "ECDH",
|
|
194
|
+
namedCurve: "P-256",
|
|
195
|
+
},
|
|
196
|
+
false,
|
|
197
|
+
["deriveBits"],
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const subtlePeerKey = await importKey(
|
|
201
|
+
"jwk",
|
|
202
|
+
peerKey,
|
|
203
|
+
{
|
|
204
|
+
name: "ECDH",
|
|
205
|
+
namedCurve: "P-256",
|
|
206
|
+
},
|
|
207
|
+
false,
|
|
208
|
+
[],
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
const secret = await subtle.deriveBits(
|
|
212
|
+
{
|
|
213
|
+
name: "ECDH",
|
|
214
|
+
public: subtlePeerKey,
|
|
215
|
+
},
|
|
216
|
+
subtleKey,
|
|
217
|
+
256,
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
return new Uint8Array(secret);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Only install if VM supports Web Crypto
|
|
225
|
+
if ((globalThis.crypto as any)?.subtle?.[Symbol.toStringTag] === "SubtleCrypto") {
|
|
226
|
+
Boot.init(() => {
|
|
227
|
+
Crypto.provider = StandardCrypto.provider;
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function importKey(
|
|
232
|
+
format: "jwk",
|
|
233
|
+
keyData: JsonWebKey,
|
|
234
|
+
algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
|
|
235
|
+
extractable: boolean,
|
|
236
|
+
keyUsages: ReadonlyArray<KeyUsage>,
|
|
237
|
+
): Promise<CryptoKey>;
|
|
238
|
+
function importKey(
|
|
239
|
+
format: Exclude<KeyFormat, "jwk">,
|
|
240
|
+
keyData: BufferSource,
|
|
241
|
+
algorithm: AlgorithmIdentifier | RsaHashedImportParams | EcKeyImportParams | HmacImportParams | AesKeyAlgorithm,
|
|
242
|
+
extractable: boolean,
|
|
243
|
+
keyUsages: KeyUsage[],
|
|
244
|
+
): Promise<CryptoKey>;
|
|
245
|
+
|
|
246
|
+
async function importKey(...params: unknown[]) {
|
|
247
|
+
try {
|
|
248
|
+
return await crypto.subtle.importKey(...(params as Parameters<SubtleCrypto["importKey"]>));
|
|
249
|
+
} catch (cause) {
|
|
250
|
+
throw new KeyInputError("Invalid key", { cause });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Project CHIP Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SJCL: https://github.com/bitwiseshiftleft/sjcl/blob/master/core/aes.js
|
|
9
|
+
*
|
|
10
|
+
* OpenSSL: https://github.com/openssl/openssl/blob/master/crypto/aes/aes_core.c
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { WordArray } from "./WordArray.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* AES core block cipher implementation.
|
|
17
|
+
*
|
|
18
|
+
* WARNING: Unaudited. Consider platform replacement if available.
|
|
19
|
+
*/
|
|
20
|
+
export function Aes(key: Uint8Array) {
|
|
21
|
+
const { encryptKey, decryptKey } = expandKey(key);
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
encrypt(pt: WordArray, ct = pt) {
|
|
25
|
+
return crypt(pt, ct, encryptKey, Tables.enc);
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
decrypt(ct: WordArray, pt = ct) {
|
|
29
|
+
return crypt(ct, pt, decryptKey, Tables.dec);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Precomputed tables for AES algorithm.
|
|
36
|
+
*/
|
|
37
|
+
interface Tables {
|
|
38
|
+
mix1: WordArray;
|
|
39
|
+
mix2: WordArray;
|
|
40
|
+
mix3: WordArray;
|
|
41
|
+
mix4: WordArray;
|
|
42
|
+
sbox: WordArray;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let etabs: Tables | undefined, dtabs: Tables | undefined;
|
|
46
|
+
|
|
47
|
+
const Tables = {
|
|
48
|
+
get enc() {
|
|
49
|
+
if (!etabs) {
|
|
50
|
+
generateTables();
|
|
51
|
+
}
|
|
52
|
+
return etabs!;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
get dec() {
|
|
56
|
+
if (!dtabs) {
|
|
57
|
+
generateTables();
|
|
58
|
+
}
|
|
59
|
+
return dtabs!;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const mixNames = ["mix1", "mix2", "mix3", "mix4"] as const;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Generate tables used by {@link crypt}.
|
|
67
|
+
*
|
|
68
|
+
* These tables hold static data used during encryption and decryption.
|
|
69
|
+
*/
|
|
70
|
+
function generateTables() {
|
|
71
|
+
etabs = Tables();
|
|
72
|
+
dtabs = Tables();
|
|
73
|
+
|
|
74
|
+
const d = Table(),
|
|
75
|
+
th = Table();
|
|
76
|
+
let i: number, x: number, xInv: number, x2: number, x4: number, x8: number, s: number, tEnc: number, tDec: number;
|
|
77
|
+
|
|
78
|
+
for (i = 0; i < 256; i++) {
|
|
79
|
+
th[(d[i] = (i << 1) ^ ((i >> 7) * 283)) ^ i] = i;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (x = xInv = 0; !etabs.sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
|
|
83
|
+
s = xInv ^ (xInv << 1) ^ (xInv << 2) ^ (xInv << 3) ^ (xInv << 4);
|
|
84
|
+
s = (s >> 8) ^ (s & 255) ^ 99;
|
|
85
|
+
etabs.sbox[x] = s;
|
|
86
|
+
dtabs.sbox[s] = x;
|
|
87
|
+
|
|
88
|
+
x8 = d[(x4 = d[(x2 = d[x])])];
|
|
89
|
+
tDec = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100);
|
|
90
|
+
tEnc = (d[s] * 0x101) ^ (s * 0x1010100);
|
|
91
|
+
|
|
92
|
+
for (const name of mixNames) {
|
|
93
|
+
etabs[name][x] = tEnc = (tEnc << 24) ^ (tEnc >>> 8);
|
|
94
|
+
dtabs[name][s] = tDec = (tDec << 24) ^ (tDec >>> 8);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Tables() {
|
|
99
|
+
return Object.fromEntries([...mixNames, "sbox"].map(k => [k, Table()])) as unknown as Tables;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function Table() {
|
|
103
|
+
return WordArray(256);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* AES computation.
|
|
109
|
+
*
|
|
110
|
+
* Performs encryption/decryption of one or more blocks in {@link data}.
|
|
111
|
+
*
|
|
112
|
+
* {@link input} and {@link output} may be the same buffer to operate in place.
|
|
113
|
+
*/
|
|
114
|
+
function crypt(input: WordArray, output: WordArray, roundKeys: WordArray, tabs: Tables) {
|
|
115
|
+
const decrypt = tabs === dtabs,
|
|
116
|
+
numRounds = roundKeys.length / 4 - 2;
|
|
117
|
+
|
|
118
|
+
// These are the precomputed tables generated in generateTables
|
|
119
|
+
const { mix1, mix2, mix3, mix4, sbox } = tabs;
|
|
120
|
+
|
|
121
|
+
// Allocate working variables for each word in the block
|
|
122
|
+
let a = input[0] ^ roundKeys[0],
|
|
123
|
+
b = input[decrypt ? 3 : 1] ^ roundKeys[1],
|
|
124
|
+
c = input[2] ^ roundKeys[2],
|
|
125
|
+
d = input[decrypt ? 1 : 3] ^ roundKeys[3],
|
|
126
|
+
roundKeyAt = 4;
|
|
127
|
+
|
|
128
|
+
// Perform computation (sub bytes, shift rows, mix columns & add round key) for each round
|
|
129
|
+
for (let i = 0; i < numRounds; i++) {
|
|
130
|
+
// Note that we need to use unsigned bit shift (>>>) for the most significant byte because JS integers are
|
|
131
|
+
// 32-bit unsigned and the sign will otherwise mess us up
|
|
132
|
+
const atemp =
|
|
133
|
+
mix1[a >>> 24] ^ mix2[(b >> 16) & 255] ^ mix3[(c >> 8) & 255] ^ mix4[d & 255] ^ roundKeys[roundKeyAt++];
|
|
134
|
+
const btemp =
|
|
135
|
+
mix1[b >>> 24] ^ mix2[(c >> 16) & 255] ^ mix3[(d >> 8) & 255] ^ mix4[a & 255] ^ roundKeys[roundKeyAt++];
|
|
136
|
+
const ctemp =
|
|
137
|
+
mix1[c >>> 24] ^ mix2[(d >> 16) & 255] ^ mix3[(a >> 8) & 255] ^ mix4[b & 255] ^ roundKeys[roundKeyAt++];
|
|
138
|
+
d = mix1[d >>> 24] ^ mix2[(a >> 16) & 255] ^ mix3[(b >> 8) & 255] ^ mix4[c & 255] ^ roundKeys[roundKeyAt++];
|
|
139
|
+
a = atemp;
|
|
140
|
+
b = btemp;
|
|
141
|
+
c = ctemp;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Write working blocks into output buffer
|
|
145
|
+
for (let i = 0; i < 4; i++) {
|
|
146
|
+
output[decrypt ? 3 & -i : i] =
|
|
147
|
+
(sbox[a >>> 24] << 24) ^
|
|
148
|
+
(sbox[(b >> 16) & 255] << 16) ^
|
|
149
|
+
(sbox[(c >> 8) & 255] << 8) ^
|
|
150
|
+
sbox[d & 255] ^
|
|
151
|
+
roundKeys[roundKeyAt++];
|
|
152
|
+
const atemp = a;
|
|
153
|
+
a = b;
|
|
154
|
+
b = c;
|
|
155
|
+
c = d;
|
|
156
|
+
d = atemp;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return output;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Schedule the key used for encryption.
|
|
164
|
+
*
|
|
165
|
+
* This generates keys for each round based off of the input key.
|
|
166
|
+
*/
|
|
167
|
+
function expandKey(key: Uint8Array) {
|
|
168
|
+
const inputLength = key.length / 4,
|
|
169
|
+
roundsNeeded = inputLength + 7,
|
|
170
|
+
wordsNeeded = roundsNeeded * 4,
|
|
171
|
+
encryptKey = WordArray.fromByteArray(key, wordsNeeded),
|
|
172
|
+
sbox = Tables.enc.sbox;
|
|
173
|
+
|
|
174
|
+
for (let i = inputLength, rcon = 1; i < wordsNeeded; i++) {
|
|
175
|
+
let temp = encryptKey[i - 1];
|
|
176
|
+
|
|
177
|
+
if (i % inputLength === 0 || (inputLength === 8 && i % inputLength === 4)) {
|
|
178
|
+
temp =
|
|
179
|
+
(sbox[temp >>> 24] << 24) ^
|
|
180
|
+
(sbox[(temp >> 16) & 255] << 16) ^
|
|
181
|
+
(sbox[(temp >> 8) & 255] << 8) ^
|
|
182
|
+
sbox[temp & 255];
|
|
183
|
+
|
|
184
|
+
if (i % inputLength === 0) {
|
|
185
|
+
temp = (temp << 8) ^ (temp >>> 24) ^ (rcon << 24);
|
|
186
|
+
rcon = (rcon << 1) ^ ((rcon >> 7) * 283);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
encryptKey[i] = encryptKey[i - inputLength] ^ temp;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const { mix1, mix2, mix3, mix4 } = Tables.dec,
|
|
194
|
+
decryptKey = WordArray(encryptKey.length);
|
|
195
|
+
|
|
196
|
+
for (let i = encryptKey.length, j = 0; i; j++, i--) {
|
|
197
|
+
const tmp = encryptKey[j & 3 ? i : i - 4];
|
|
198
|
+
if (i <= 4 || j < 4) {
|
|
199
|
+
decryptKey[j] = tmp;
|
|
200
|
+
} else {
|
|
201
|
+
decryptKey[j] =
|
|
202
|
+
mix1[sbox[tmp >>> 24]] ^
|
|
203
|
+
mix2[sbox[(tmp >> 16) & 255]] ^
|
|
204
|
+
mix3[sbox[(tmp >> 8) & 255]] ^
|
|
205
|
+
mix4[sbox[tmp & 255]];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return { encryptKey, decryptKey };
|
|
210
|
+
}
|