Skip to content

Commit fa125ed

Browse files
authored
Add support for PASE verification value (#153)
1 parent c80a70f commit fa125ed

File tree

6 files changed

+53
-41
lines changed

6 files changed

+53
-41
lines changed

src/Device.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ class Device {
8686
.addScanner(await MdnsScanner.create())
8787
.addBroadcaster(await MdnsBroadcaster.create())
8888
.addProtocolHandler(new SecureChannelProtocol(
89-
new PaseServer(passcode, { iteration: 1000, salt: Crypto.getRandomData(32) }),
89+
await PaseServer.fromPin({ iteration: 1000, salt: Crypto.getRandomData(32) }, passcode),
9090
new CaseServer(),
9191
))
9292
.addProtocolHandler(new InteractionServer()
@@ -107,7 +107,8 @@ class Device {
107107
capabilityMinima: {
108108
caseSessionsPerFabric: 3,
109109
subscriptionsPerFabric: 3,
110-
}
110+
},
111+
serialNumber: `node-matter-${packageJson.version}`,
111112
}, {}),
112113
new ClusterServer(GeneralCommissioningCluster, {}, {
113114
breadcrumb: BigInt(0),

src/crypto/Spake2p.ts

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,33 @@ export interface PbkdfParameters {
2121
}
2222

2323
export class Spake2p {
24-
constructor(
25-
private readonly context: ByteArray,
26-
private readonly random: BN,
27-
/* visible for tests */ readonly w0: BN,
28-
private readonly w1: BN,
29-
) {}
3024

31-
static async create(context: ByteArray, {iteration, salt}: PbkdfParameters, pin: number) {
25+
static async computeW0W1({iteration, salt}: PbkdfParameters, pin: number) {
3226
const pinWriter = new DataWriter(Endian.Little);
3327
pinWriter.writeUInt32(pin);
3428
const ws = await Crypto.pbkdf2(pinWriter.toByteArray(), salt, iteration, 80);
35-
const random = Crypto.getRandomBN(32, P256_CURVE.p);
3629
const w0 = new BN(ws.slice(0, 40)).mod(P256_CURVE.n);
3730
const w1 = new BN(ws.slice(40, 80)).mod(P256_CURVE.n);
38-
return new Spake2p(context, random, w0, w1);
31+
return { w0, w1 };
32+
}
33+
34+
static async computeW0L(pbkdfParameters: PbkdfParameters, pin: number) {
35+
const { w0, w1 } = await this.computeW0W1(pbkdfParameters, pin);
36+
const L = P256_CURVE.g.mul(w1).encode();
37+
return { w0, L };
3938
}
4039

40+
static async create(context: ByteArray, w0: BN) {
41+
const random = Crypto.getRandomBN(32, P256_CURVE.p);
42+
return new Spake2p(context, random, w0);
43+
}
44+
45+
constructor(
46+
private readonly context: ByteArray,
47+
private readonly random: BN,
48+
private readonly w0: BN,
49+
) {}
50+
4151
computeX(): ByteArray {
4252
const X = P256_CURVE.g.mul(this.random).add(M.mul(this.w0));
4353
return ByteArray.from(X.encode());
@@ -48,20 +58,21 @@ export class Spake2p {
4858
return ByteArray.from(Y.encode());
4959
}
5060

51-
async computeSecretAndVerifiersFromY(X: ByteArray, Y: ByteArray) {
61+
async computeSecretAndVerifiersFromY(w1: BN, X: ByteArray, Y: ByteArray) {
5262
const YPoint = P256_CURVE.decodePoint(Y);
5363
if (!YPoint.validate()) throw new Error("Y is not on the curve");
5464
const yNwo = YPoint.add(N.mul(this.w0).neg());
5565
const Z = yNwo.mul(this.random);
56-
const V = yNwo.mul(this.w1);
66+
const V = yNwo.mul(w1);
5767
return this.computeSecretAndVerifiers(X, Y, ByteArray.from(Z.encode()), ByteArray.from(V.encode()));
5868
}
5969

60-
async computeSecretAndVerifiersFromX(X: ByteArray, Y: ByteArray) {
70+
async computeSecretAndVerifiersFromX(L: ByteArray, X: ByteArray, Y: ByteArray) {
6171
const XPoint = P256_CURVE.decodePoint(X);
72+
const LPoint = P256_CURVE.decodePoint(L);
6273
if (!XPoint.validate()) throw new Error("X is not on the curve");
6374
const Z = XPoint.add(M.mul(this.w0).neg()).mul(this.random);
64-
const V = P256_CURVE.g.mul(this.w1).mul(this.random);
75+
const V = LPoint.mul(this.random);
6576
return this.computeSecretAndVerifiers(X, Y, ByteArray.from(Z.encode()), ByteArray.from(V.encode()));
6677
}
6778

src/matter/session/secure/PaseClient.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import { ByteArray } from "@project-chip/matter.js";
1616
const logger = Logger.get("PaseClient");
1717

1818
export class PaseClient {
19-
constructor() {}
20-
2119
async pair(client: MatterController, exchange: MessageExchange<MatterController>, setupPin: number) {
2220
const messenger = new PaseClientMessenger(exchange);
2321
const random = Crypto.getRandom();
@@ -29,13 +27,14 @@ export class PaseClient {
2927
if (pbkdfParameters === undefined) throw new Error("Missing requested PbkdfParameters in the response");
3028

3129
// Compute pake1 and read pake2
32-
const spake2p = await Spake2p.create(Crypto.hash([ SPAKE_CONTEXT, requestPayload, responsePayload ]), pbkdfParameters, setupPin);
30+
const { w0, w1 } = await Spake2p.computeW0W1(pbkdfParameters, setupPin);
31+
const spake2p = await Spake2p.create(Crypto.hash([ SPAKE_CONTEXT, requestPayload, responsePayload ]), w0);
3332
const X = spake2p.computeX();
3433
await messenger.sendPasePake1({x: X});
3534

3635
// Process pack2 and send pake3
3736
const { y: Y, verifier } = await messenger.readPasePake2();
38-
const { Ke, hAY, hBX } = await spake2p.computeSecretAndVerifiersFromY(X, Y);
37+
const { Ke, hAY, hBX } = await spake2p.computeSecretAndVerifiersFromY(w1, X, Y);
3938
if (!verifier.equals(hBX)) throw new Error("Received incorrect key confirmation from the receiver");
4039
await messenger.sendPasePake3({ verifier: hAY });
4140

src/matter/session/secure/PaseServer.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,27 @@ import { SECURE_CHANNEL_PROTOCOL_ID } from "./SecureChannelMessages";
1414
import { MatterDevice } from "../../MatterDevice";
1515
import { Logger } from "../../../log/Logger";
1616
import { ByteArray } from "@project-chip/matter.js";
17+
import BN from "bn.js";
1718

1819
const logger = Logger.get("PaseServer");
1920

2021
export class PaseServer implements ProtocolHandler<MatterDevice> {
2122

23+
static async fromPin(pbkdfParameters: PbkdfParameters, setupPinCode: number) {
24+
const { w0, L } = await Spake2p.computeW0L(pbkdfParameters, setupPinCode);
25+
return new PaseServer(w0, L, pbkdfParameters);
26+
}
27+
28+
static fromVerificationValue(verificationValue: ByteArray) {
29+
const w0 = new BN(verificationValue.slice(0, 32));
30+
const L = verificationValue.slice(32, 32 + 65);
31+
return new PaseServer(w0, L);
32+
}
33+
2234
constructor(
23-
private readonly setupPinCode: number,
24-
private readonly pbkdfParameters: PbkdfParameters,
35+
private readonly w0: BN,
36+
private readonly L: ByteArray,
37+
private readonly pbkdfParameters?: PbkdfParameters,
2538
) {}
2639

2740
getId(): number {
@@ -49,10 +62,10 @@ export class PaseServer implements ProtocolHandler<MatterDevice> {
4962
const responsePayload = await messenger.sendPbkdfParamResponse({ peerRandom, random, sessionId, mrpParameters, pbkdfParameters: hasPbkdfParameters ? undefined : this.pbkdfParameters });
5063

5164
// Process pake1 and send pake2
52-
const spake2p = await Spake2p.create(Crypto.hash([ SPAKE_CONTEXT, requestPayload, responsePayload ]), this.pbkdfParameters, this.setupPinCode);
65+
const spake2p = await Spake2p.create(Crypto.hash([ SPAKE_CONTEXT, requestPayload, responsePayload ]), this.w0);
5366
const { x: X } = await messenger.readPasePake1();
5467
const Y = spake2p.computeY();
55-
const { Ke, hAY, hBX } = await spake2p.computeSecretAndVerifiersFromX(X, Y);
68+
const { Ke, hAY, hBX } = await spake2p.computeSecretAndVerifiersFromX(this.L, X, Y);
5669
await messenger.sendPasePake2({ y: Y, verifier: hBX });
5770

5871
// Read and process pake3

test/IntegrationTest.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe("Integration", () => {
9393
.addNetInterface(await UdpInterface.create(matterPort, "udp6", SERVER_IP))
9494
.addBroadcaster(await MdnsBroadcaster.create())
9595
.addProtocolHandler(new SecureChannelProtocol(
96-
new PaseServer(setupPin, { iteration: 1000, salt: Crypto.getRandomData(32) }),
96+
await PaseServer.fromPin({ iteration: 1000, salt: Crypto.getRandomData(32) }, setupPin),
9797
new CaseServer(),
9898
))
9999
.addProtocolHandler(new InteractionServer()

test/crypto/Spake2pTest.ts

Lines changed: 5 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@ describe("Spake2p", () => {
1515
const context = ByteArray.fromString("SPAKE2+-P256-SHA256-HKDF draft-01");
1616
const w0 = new BN("e6887cf9bdfb7579c69bf47928a84514b5e355ac034863f7ffaf4390e67d798c", "hex");
1717
const w1 = new BN("24b5ae4abda868ec9336ffc3b78ee31c5755bef1759227ef5372ca139b94e512", "hex");
18+
const L = ByteArray.fromHex("0495645cfb74df6e58f9748bb83a86620bab7c82e107f57d6870da8cbcb2ff9f7063a14b6402c62f99afcb9706a4d1a143273259fe76f1c605a3639745a92154b9");
1819
const x = new BN("5b478619804f4938d361fbba3a20648725222f0a54cc4c876139efe7d9a21786", "hex");
1920
const y = new BN("766770dad8c8eecba936823c0aed044b8c3c4f7655e8beec44a15dcbcaf78e5e", "hex");
2021
const X = ByteArray.fromHex("04a6db23d001723fb01fcfc9d08746c3c2a0a3feff8635d29cad2853e7358623425cf39712e928054561ba71e2dc11f300f1760e71eb177021a8f85e78689071cd");
2122
const Y = ByteArray.fromHex("04390d29bf185c3abf99f150ae7c13388c82b6be0c07b1b8d90d26853e84374bbdc82becdb978ca3792f472424106a2578012752c11938fcf60a41df75ff7cf947");
2223
const Ke = ByteArray.fromHex("ea3276d68334576097e04b19ee5a3a8b");
2324
const hAY = ByteArray.fromHex("71d9412779b6c45a2c615c9df3f1fd93dc0aaf63104da8ece4aa1b5a3a415fea");
2425
const hBX = ByteArray.fromHex("095dc0400355cc233fde7437811815b3c1524aae80fd4e6810cf531cf11d20e3");
25-
const spake2pInitiator = new Spake2p(context, x, w0, w1);
26-
const spake2pReceiver = new Spake2p(context, y, w0, w1);
26+
const spake2pInitiator = new Spake2p(context, x, w0);
27+
const spake2pReceiver = new Spake2p(context, y, w0);
2728

2829
it("generates X", () => {
2930
const result = spake2pInitiator.computeX();
@@ -38,23 +39,22 @@ describe("Spake2p", () => {
3839
});
3940

4041
it("generates shared secret and key confirmation for the initiator", async () => {
41-
const result = await spake2pInitiator.computeSecretAndVerifiersFromY(X, Y);
42+
const result = await spake2pInitiator.computeSecretAndVerifiersFromY(w1, X, Y);
4243

4344
assert.equal(result.Ke.toHex(), Ke.toHex());
4445
assert.equal(result.hAY.toHex(), hAY.toHex());
4546
assert.equal(result.hBX.toHex(), hBX.toHex());
4647
});
4748

4849
it("generates shared secret and key confirmation for the receiver", async () => {
49-
const result = await spake2pReceiver.computeSecretAndVerifiersFromX(X, Y);
50+
const result = await spake2pReceiver.computeSecretAndVerifiersFromX(L, X, Y);
5051

5152
assert.equal(result.Ke.toHex(), Ke.toHex());
5253
assert.equal(result.hAY.toHex(), hAY.toHex());
5354
assert.equal(result.hBX.toHex(), hBX.toHex());
5455
});
5556
});
5657

57-
5858
context("context hash test", () => {
5959
it("generates the correct context hash", () => {
6060
// Test data captured from https://github.com/project-chip/connectedhomeip/
@@ -70,17 +70,5 @@ describe("Spake2p", () => {
7070

7171
assert.equal(result.toHex(), "c49718b0275b6f81fd6a081f6c34c5833382b75b3bd997895d13a51c71a02855");
7272
});
73-
74-
it("generates the correct ws0 from the pin", async () => {
75-
// Test data captured from https://github.com/project-chip/connectedhomeip/
76-
const pin = 20202021;
77-
const salt = ByteArray.fromHex("438df2ea5143215c4ec5f1bbf7a4d9b1374f62320f2c88e25cc18ff5e5d1bbf6");
78-
const iteration = 1000;
79-
80-
const spake2p = await Spake2p.create(Buffer.alloc(0), { iteration, salt }, pin);
81-
const result = spake2p.w0;
82-
83-
assert.equal(result.toString("hex"), "987aede3f3f32756971b905820b0bbdad2a6e236838a865b043e64878b5db6d0");
84-
});
8573
});
8674
});

0 commit comments

Comments
 (0)