@matter/general 0.16.0-alpha.0-20250820-24939dd26 → 0.16.0-alpha.0-20250822-523588650
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/crypto/Key.d.ts +8 -7
- package/dist/cjs/crypto/Key.d.ts.map +1 -1
- package/dist/cjs/crypto/Key.js +8 -7
- package/dist/cjs/crypto/Key.js.map +2 -2
- package/dist/cjs/crypto/NodeJsStyleCrypto.d.ts +49 -0
- package/dist/cjs/crypto/NodeJsStyleCrypto.d.ts.map +1 -0
- package/dist/cjs/crypto/NodeJsStyleCrypto.js +174 -0
- package/dist/cjs/crypto/NodeJsStyleCrypto.js.map +6 -0
- package/dist/cjs/crypto/StandardCrypto.d.ts +2 -1
- package/dist/cjs/crypto/StandardCrypto.d.ts.map +1 -1
- package/dist/cjs/crypto/StandardCrypto.js +4 -3
- package/dist/cjs/crypto/StandardCrypto.js.map +1 -1
- package/dist/cjs/crypto/index.d.ts +1 -0
- package/dist/cjs/crypto/index.d.ts.map +1 -1
- package/dist/cjs/crypto/index.js +1 -0
- package/dist/cjs/crypto/index.js.map +1 -1
- package/dist/cjs/environment/Environment.d.ts +6 -0
- package/dist/cjs/environment/Environment.d.ts.map +1 -1
- package/dist/cjs/environment/Environment.js +22 -5
- package/dist/cjs/environment/Environment.js.map +1 -1
- package/dist/esm/crypto/Key.d.ts +8 -7
- package/dist/esm/crypto/Key.d.ts.map +1 -1
- package/dist/esm/crypto/Key.js +8 -7
- package/dist/esm/crypto/Key.js.map +2 -2
- package/dist/esm/crypto/NodeJsStyleCrypto.d.ts +49 -0
- package/dist/esm/crypto/NodeJsStyleCrypto.d.ts.map +1 -0
- package/dist/esm/crypto/NodeJsStyleCrypto.js +162 -0
- package/dist/esm/crypto/NodeJsStyleCrypto.js.map +6 -0
- package/dist/esm/crypto/StandardCrypto.d.ts +2 -1
- package/dist/esm/crypto/StandardCrypto.d.ts.map +1 -1
- package/dist/esm/crypto/StandardCrypto.js +4 -3
- package/dist/esm/crypto/StandardCrypto.js.map +1 -1
- package/dist/esm/crypto/index.d.ts +1 -0
- package/dist/esm/crypto/index.d.ts.map +1 -1
- package/dist/esm/crypto/index.js +1 -0
- package/dist/esm/crypto/index.js.map +1 -1
- package/dist/esm/environment/Environment.d.ts +6 -0
- package/dist/esm/environment/Environment.d.ts.map +1 -1
- package/dist/esm/environment/Environment.js +22 -5
- package/dist/esm/environment/Environment.js.map +1 -1
- package/package.json +2 -2
- package/src/crypto/Key.ts +9 -9
- package/src/crypto/NodeJsStyleCrypto.ts +212 -0
- package/src/crypto/StandardCrypto.ts +5 -3
- package/src/crypto/index.ts +1 -0
- package/src/environment/Environment.ts +27 -6
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2022-2025 Matter.js Authors
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Bytes } from "#util/Bytes.js";
|
|
8
|
+
import { asError } from "#util/Error.js";
|
|
9
|
+
import { Identity } from "#util/Type.js";
|
|
10
|
+
import {
|
|
11
|
+
Crypto,
|
|
12
|
+
CRYPTO_AUTH_TAG_LENGTH,
|
|
13
|
+
CRYPTO_EC_CURVE,
|
|
14
|
+
CRYPTO_EC_KEY_BYTES,
|
|
15
|
+
CRYPTO_ENCRYPT_ALGORITHM,
|
|
16
|
+
CRYPTO_HASH_ALGORITHM,
|
|
17
|
+
CRYPTO_SYMMETRIC_KEY_LENGTH,
|
|
18
|
+
CryptoDsaEncoding,
|
|
19
|
+
} from "./Crypto.js";
|
|
20
|
+
import { CryptoDecryptError, CryptoVerifyError } from "./CryptoError.js";
|
|
21
|
+
import { PrivateKey, PublicKey } from "./Key.js";
|
|
22
|
+
|
|
23
|
+
// Note that this is a type-only import, not a runtime dependency.
|
|
24
|
+
import type * as NodeJsCryptoApi from "node:crypto";
|
|
25
|
+
|
|
26
|
+
// Ensure we don't reference global crypto accidentally
|
|
27
|
+
declare const crypto: never;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A crypto API implemented in the style of Node.js.
|
|
31
|
+
*/
|
|
32
|
+
export interface NodeJsCryptoApiLike extends Identity<typeof NodeJsCryptoApi> {}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A crypto implementation that uses the Node.js crypto API.
|
|
36
|
+
*
|
|
37
|
+
* It is Node.js "style" because there are many packages that emulate the Node.js API. As of now (mid-2025) these are
|
|
38
|
+
* sometimes more mature than the available Web Crypto implementation.
|
|
39
|
+
*
|
|
40
|
+
* This module does not import the Node.js crypto implementation directly. You must provide a crypto implementation to
|
|
41
|
+
* use it.
|
|
42
|
+
*/
|
|
43
|
+
export class NodeJsStyleCrypto extends Crypto {
|
|
44
|
+
implementationName = "Node.js";
|
|
45
|
+
|
|
46
|
+
#crypto: NodeJsCryptoApiLike;
|
|
47
|
+
|
|
48
|
+
constructor(crypto: NodeJsCryptoApiLike) {
|
|
49
|
+
super();
|
|
50
|
+
|
|
51
|
+
this.#crypto = crypto;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
encrypt(key: Bytes, data: Bytes, nonce: Bytes, aad?: Bytes): Bytes {
|
|
55
|
+
const cipher = this.#crypto.createCipheriv(CRYPTO_ENCRYPT_ALGORITHM, Bytes.of(key), Bytes.of(nonce), {
|
|
56
|
+
authTagLength: CRYPTO_AUTH_TAG_LENGTH,
|
|
57
|
+
});
|
|
58
|
+
if (aad !== undefined) {
|
|
59
|
+
cipher.setAAD(Bytes.of(aad), { plaintextLength: data.byteLength });
|
|
60
|
+
}
|
|
61
|
+
const encrypted = cipher.update(Bytes.of(data));
|
|
62
|
+
cipher.final();
|
|
63
|
+
return Bytes.concat(Bytes.of(encrypted), Bytes.of(cipher.getAuthTag()));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
decrypt(key: Bytes, encrypted: Bytes, nonce: Bytes, aad?: Bytes): Bytes {
|
|
67
|
+
const cipher = this.#crypto.createDecipheriv(CRYPTO_ENCRYPT_ALGORITHM, Bytes.of(key), Bytes.of(nonce), {
|
|
68
|
+
authTagLength: CRYPTO_AUTH_TAG_LENGTH,
|
|
69
|
+
});
|
|
70
|
+
const data = Bytes.of(encrypted);
|
|
71
|
+
const plaintextLength = data.length - CRYPTO_AUTH_TAG_LENGTH;
|
|
72
|
+
if (aad !== undefined) {
|
|
73
|
+
cipher.setAAD(Bytes.of(aad), { plaintextLength });
|
|
74
|
+
}
|
|
75
|
+
cipher.setAuthTag(data.slice(plaintextLength));
|
|
76
|
+
const result = cipher.update(data.slice(0, plaintextLength));
|
|
77
|
+
try {
|
|
78
|
+
cipher.final();
|
|
79
|
+
} catch (e) {
|
|
80
|
+
throw new CryptoDecryptError(`${CRYPTO_ENCRYPT_ALGORITHM} decryption failed: ${asError(e).message}`);
|
|
81
|
+
}
|
|
82
|
+
return Bytes.of(result);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
randomBytes(length: number): Bytes {
|
|
86
|
+
return Bytes.of(this.#crypto.randomBytes(length));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
ecdhGeneratePublicKey(): { publicKey: Bytes; ecdh: any } {
|
|
90
|
+
const ecdh = this.#crypto.createECDH(CRYPTO_EC_CURVE);
|
|
91
|
+
ecdh.generateKeys();
|
|
92
|
+
return { publicKey: Bytes.of(ecdh.getPublicKey()), ecdh: ecdh };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
ecdhGeneratePublicKeyAndSecret(peerPublicKey: Bytes): {
|
|
96
|
+
publicKey: Bytes;
|
|
97
|
+
sharedSecret: Bytes;
|
|
98
|
+
} {
|
|
99
|
+
const ecdh = this.#crypto.createECDH(CRYPTO_EC_CURVE);
|
|
100
|
+
ecdh.generateKeys();
|
|
101
|
+
return {
|
|
102
|
+
publicKey: Bytes.of(ecdh.getPublicKey()),
|
|
103
|
+
sharedSecret: Bytes.of(ecdh.computeSecret(Bytes.of(peerPublicKey))),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
computeSha256(data: Bytes | Bytes[]): Bytes {
|
|
108
|
+
const hasher = this.#crypto.createHash(CRYPTO_HASH_ALGORITHM);
|
|
109
|
+
if (Array.isArray(data)) {
|
|
110
|
+
data.forEach(chunk => hasher.update(Bytes.of(chunk)));
|
|
111
|
+
} else {
|
|
112
|
+
hasher.update(Bytes.of(data));
|
|
113
|
+
}
|
|
114
|
+
return Bytes.of(hasher.digest());
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
createPbkdf2Key(secret: Bytes, salt: Bytes, iteration: number, keyLength: number): Promise<Bytes> {
|
|
118
|
+
return new Promise<Bytes>((resolver, rejecter) => {
|
|
119
|
+
this.#crypto.pbkdf2(
|
|
120
|
+
Bytes.of(secret),
|
|
121
|
+
Bytes.of(salt),
|
|
122
|
+
iteration,
|
|
123
|
+
keyLength,
|
|
124
|
+
CRYPTO_HASH_ALGORITHM,
|
|
125
|
+
(error, key) => {
|
|
126
|
+
if (error !== null) rejecter(error);
|
|
127
|
+
resolver(Bytes.of(key));
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
createHkdfKey(
|
|
134
|
+
secret: Bytes,
|
|
135
|
+
salt: Bytes,
|
|
136
|
+
info: Bytes,
|
|
137
|
+
length: number = CRYPTO_SYMMETRIC_KEY_LENGTH,
|
|
138
|
+
): Promise<Bytes> {
|
|
139
|
+
return new Promise<Bytes>((resolver, rejecter) => {
|
|
140
|
+
this.#crypto.hkdf(
|
|
141
|
+
CRYPTO_HASH_ALGORITHM,
|
|
142
|
+
Bytes.of(secret),
|
|
143
|
+
Bytes.of(salt),
|
|
144
|
+
Bytes.of(info),
|
|
145
|
+
length,
|
|
146
|
+
(error, key) => {
|
|
147
|
+
if (error !== null) rejecter(error);
|
|
148
|
+
resolver(Bytes.of(key));
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
signHmac(key: Bytes, data: Bytes): Bytes {
|
|
155
|
+
const hmac = this.#crypto.createHmac(CRYPTO_HASH_ALGORITHM, Bytes.of(key));
|
|
156
|
+
hmac.update(Bytes.of(data));
|
|
157
|
+
return Bytes.of(hmac.digest());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
signEcdsa(privateKey: JsonWebKey, data: Bytes | Bytes[], dsaEncoding: CryptoDsaEncoding = "ieee-p1363"): Bytes {
|
|
161
|
+
const signer = this.#crypto.createSign(CRYPTO_HASH_ALGORITHM);
|
|
162
|
+
if (Array.isArray(data)) {
|
|
163
|
+
data.forEach(chunk => signer.update(Bytes.of(chunk)));
|
|
164
|
+
} else {
|
|
165
|
+
signer.update(Bytes.of(data));
|
|
166
|
+
}
|
|
167
|
+
return Bytes.of(
|
|
168
|
+
signer.sign({
|
|
169
|
+
key: privateKey as any,
|
|
170
|
+
format: "jwk",
|
|
171
|
+
type: "pkcs8",
|
|
172
|
+
dsaEncoding,
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
verifyEcdsa(publicKey: JsonWebKey, data: Bytes, signature: Bytes, dsaEncoding: CryptoDsaEncoding = "ieee-p1363") {
|
|
178
|
+
const verifier = this.#crypto.createVerify(CRYPTO_HASH_ALGORITHM);
|
|
179
|
+
verifier.update(Bytes.of(data));
|
|
180
|
+
const success = verifier.verify(
|
|
181
|
+
{
|
|
182
|
+
key: publicKey as any,
|
|
183
|
+
format: "jwk",
|
|
184
|
+
type: "spki",
|
|
185
|
+
dsaEncoding,
|
|
186
|
+
},
|
|
187
|
+
Bytes.of(signature),
|
|
188
|
+
);
|
|
189
|
+
if (!success) throw new CryptoVerifyError("Signature verification failed");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
createKeyPair() {
|
|
193
|
+
// Note that we this key may be used for DH or DSA but we use an ECDH to generate
|
|
194
|
+
const ecdh = this.#crypto.createECDH(CRYPTO_EC_CURVE);
|
|
195
|
+
ecdh.generateKeys();
|
|
196
|
+
|
|
197
|
+
// The key exported from Node doesn't include most-significant bytes that are 0. This doesn't affect how we
|
|
198
|
+
// currently use keys but it's a little weird so 0 pad to avoid future confusion
|
|
199
|
+
const privateKey = new Uint8Array(CRYPTO_EC_KEY_BYTES);
|
|
200
|
+
const nodePrivateKey = ecdh.getPrivateKey();
|
|
201
|
+
privateKey.set(nodePrivateKey, CRYPTO_EC_KEY_BYTES - nodePrivateKey.length);
|
|
202
|
+
|
|
203
|
+
return PrivateKey(privateKey, { publicKey: Bytes.of(ecdh.getPublicKey()) });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
generateDhSecret(key: PrivateKey, peerKey: PublicKey): Bytes {
|
|
207
|
+
const ecdh = this.#crypto.createECDH(CRYPTO_EC_CURVE);
|
|
208
|
+
ecdh.setPrivateKey(Bytes.of(key.privateBits));
|
|
209
|
+
|
|
210
|
+
return Bytes.of(ecdh.computeSecret(Bytes.of(peerKey.publicBits)));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -9,6 +9,7 @@ import { DerBigUint, DerCodec, DerError } from "#codec/DerCodec.js";
|
|
|
9
9
|
import { Environment } from "#environment/Environment.js";
|
|
10
10
|
import { ImplementationError } from "#MatterError.js";
|
|
11
11
|
import { Bytes } from "#util/Bytes.js";
|
|
12
|
+
import { MaybePromise } from "#util/Promises.js";
|
|
12
13
|
import { describeList } from "#util/String.js";
|
|
13
14
|
import { Ccm } from "./aes/Ccm.js";
|
|
14
15
|
import { Crypto, CRYPTO_SYMMETRIC_KEY_LENGTH, CryptoDsaEncoding } from "./Crypto.js";
|
|
@@ -135,9 +136,10 @@ export class StandardCrypto extends Crypto {
|
|
|
135
136
|
);
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
signHmac(secret: Bytes, data: Bytes): MaybePromise<Bytes> {
|
|
140
|
+
return this.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]).then(key =>
|
|
141
|
+
this.#subtle.sign("HMAC", key, Bytes.exclusive(data)),
|
|
142
|
+
);
|
|
141
143
|
}
|
|
142
144
|
|
|
143
145
|
async signEcdsa(key: JsonWebKey, data: Bytes | Bytes[], dsaEncoding?: CryptoDsaEncoding) {
|
package/src/crypto/index.ts
CHANGED
|
@@ -9,6 +9,7 @@ export * from "./CryptoConstants.js";
|
|
|
9
9
|
export * from "./CryptoError.js";
|
|
10
10
|
export * from "./Key.js";
|
|
11
11
|
export * from "./MockCrypto.js";
|
|
12
|
+
export * from "./NodeJsStyleCrypto.js";
|
|
12
13
|
export * from "./Spake2p.js";
|
|
13
14
|
export * from "./StandardCrypto.js";
|
|
14
15
|
export * from "./WebCrypto.js";
|
|
@@ -32,7 +32,7 @@ const logger = Logger.get("Environment");
|
|
|
32
32
|
* TODO - could remove global singletons by moving here
|
|
33
33
|
*/
|
|
34
34
|
export class Environment {
|
|
35
|
-
#services?: Map<abstract new (...args: any[]) => any, Environmental.Service>;
|
|
35
|
+
#services?: Map<abstract new (...args: any[]) => any, Environmental.Service | null>;
|
|
36
36
|
#name: string;
|
|
37
37
|
#parent?: Environment;
|
|
38
38
|
#added = Observable<[type: abstract new (...args: any[]) => {}, instance: {}]>();
|
|
@@ -48,7 +48,13 @@ export class Environment {
|
|
|
48
48
|
* Determine if an environmental service is available.
|
|
49
49
|
*/
|
|
50
50
|
has(type: abstract new (...args: any[]) => any): boolean {
|
|
51
|
-
|
|
51
|
+
const mine = this.#services?.get(type);
|
|
52
|
+
|
|
53
|
+
if (mine === null) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return mine !== undefined || (this.#parent?.has(type) ?? false);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -57,13 +63,15 @@ export class Environment {
|
|
|
57
63
|
get<T extends object>(type: abstract new (...args: any[]) => T): T {
|
|
58
64
|
let instance = this.#services?.get(type) ?? this.#parent?.maybeGet(type);
|
|
59
65
|
|
|
60
|
-
if (instance) {
|
|
66
|
+
if (instance !== undefined && instance !== null) {
|
|
61
67
|
return instance as T;
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
if (instance !== null) {
|
|
71
|
+
if ((type as Environmental.Factory<T>)[Environmental.create]) {
|
|
72
|
+
this.set(type, (instance = (type as any)[Environmental.create](this)));
|
|
73
|
+
return instance as T;
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
|
|
69
77
|
throw new UnsupportedDependencyError(`Required dependency ${type.name}`, "is not available");
|
|
@@ -99,6 +107,19 @@ export class Environment {
|
|
|
99
107
|
}
|
|
100
108
|
}
|
|
101
109
|
|
|
110
|
+
/**
|
|
111
|
+
* Prevent this environment from automatically instantiating or retrieving a service from parent environment.
|
|
112
|
+
*
|
|
113
|
+
* @param type the class of the service to block
|
|
114
|
+
*/
|
|
115
|
+
block(type: abstract new (...args: any[]) => any) {
|
|
116
|
+
if (this.has(type)) {
|
|
117
|
+
this.delete(type);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
this.#services?.set(type, null);
|
|
121
|
+
}
|
|
122
|
+
|
|
102
123
|
/**
|
|
103
124
|
* Remove and close an environmental service.
|
|
104
125
|
*/
|