/
CaseClient.ts
98 lines (85 loc) · 6.08 KB
/
CaseClient.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* @license
* Copyright 2022-2023 Project CHIP Authors
* SPDX-License-Identifier: Apache-2.0
*/
import { TlvOperationalCertificate } from "../../certificate/CertificateManager.js";
import { Crypto } from "../../crypto/Crypto.js";
import { Fabric } from "../../fabric/Fabric.js";
import { MessageExchange } from "../../protocol/MessageExchange.js";
import { MatterController } from "../../MatterController.js";
import {
KDFSR1_KEY_INFO, KDFSR2_INFO, KDFSR2_KEY_INFO, KDFSR3_INFO, RESUME1_MIC_NONCE, RESUME2_MIC_NONCE,
TlvEncryptedDataSigma2, TlvSignedData, TBE_DATA2_NONCE, TBE_DATA3_NONCE, TlvEncryptedDataSigma3
} from "./CaseMessages.js";
import { CaseClientMessenger } from "./CaseMessenger.js";
import { ByteArray } from "../../util/ByteArray.js";
import { Logger } from "../../log/Logger.js";
import { NodeId } from "../../datatype/NodeId.js";
const logger = Logger.get("CaseClient");
export class CaseClient {
async pair(client: MatterController, exchange: MessageExchange<MatterController>, fabric: Fabric, peerNodeId: NodeId) {
const messenger = new CaseClientMessenger(exchange);
// Generate pairing info
const random = Crypto.getRandom();
const sessionId = client.getNextAvailableSessionId();
const { operationalIdentityProtectionKey, operationalCert: nodeOpCert, intermediateCACert } = fabric;
const { publicKey: ecdhPublicKey, ecdh } = Crypto.ecdhGeneratePublicKey();
// Send sigma1
let sigma1Bytes;
let resumptionRecord = client.findResumptionRecordByNodeId(peerNodeId);
if (resumptionRecord !== undefined) {
const { sharedSecret, resumptionId } = resumptionRecord;
const resumeKey = await Crypto.hkdf(sharedSecret, ByteArray.concat(random, resumptionId), KDFSR1_KEY_INFO);
const resumeMic = Crypto.encrypt(resumeKey, new ByteArray(0), RESUME1_MIC_NONCE);
sigma1Bytes = await messenger.sendSigma1({ sessionId, destinationId: fabric.getDestinationId(peerNodeId, random), ecdhPublicKey, random, resumptionId, resumeMic });
} else {
sigma1Bytes = await messenger.sendSigma1({ sessionId, destinationId: fabric.getDestinationId(peerNodeId, random), ecdhPublicKey, random });
}
let secureSession;
const { sigma2Bytes, sigma2, sigma2Resume } = await messenger.readSigma2();
if (sigma2Resume !== undefined) {
// Process sigma2 resume
if (resumptionRecord === undefined) throw new Error("Received an unexpected sigma2Resume");
const { sharedSecret, fabric } = resumptionRecord;
const { sessionId: peerSessionId, resumptionId, resumeMic } = sigma2Resume;
const resumeSalt = ByteArray.concat(random, resumptionId);
const resumeKey = await Crypto.hkdf(sharedSecret, resumeSalt, KDFSR2_KEY_INFO);
Crypto.decrypt(resumeKey, resumeMic, RESUME2_MIC_NONCE);
const secureSessionSalt = ByteArray.concat(random, resumptionRecord.resumptionId);
secureSession = await client.createSecureSession(sessionId, fabric, peerNodeId, peerSessionId, sharedSecret, secureSessionSalt, true, true);
await messenger.sendSuccess();
logger.info(`Case client: session resumed with ${messenger.getChannelName()}`);
resumptionRecord.resumptionId = resumptionId; /* update resumptionId */
} else {
// Process sigma2
const { ecdhPublicKey: peerEcdhPublicKey, encrypted: peerEncrypted, random: peerRandom, sessionId: peerSessionId } = sigma2;
const sharedSecret = Crypto.ecdhGenerateSecret(peerEcdhPublicKey, ecdh);
const sigma2Salt = ByteArray.concat(operationalIdentityProtectionKey, peerRandom, peerEcdhPublicKey, Crypto.hash(sigma1Bytes));
const sigma2Key = await Crypto.hkdf(sharedSecret, sigma2Salt, KDFSR2_INFO);
const peerEncryptedData = Crypto.decrypt(sigma2Key, peerEncrypted, TBE_DATA2_NONCE);
const { nodeOpCert: peerNewOpCert, intermediateCACert: peerIntermediateCACert, signature: peerSignature, resumptionId: peerResumptionId } = TlvEncryptedDataSigma2.decode(peerEncryptedData);
const peerSignatureData = TlvSignedData.encode({ nodeOpCert: peerNewOpCert, intermediateCACert: peerIntermediateCACert, ecdhPublicKey: peerEcdhPublicKey, peerEcdhPublicKey: ecdhPublicKey });
const { ellipticCurvePublicKey: peerPublicKey, subject: { nodeId: peerNodeIdCert } } = TlvOperationalCertificate.decode(peerNewOpCert);
if (peerNodeIdCert.id !== peerNodeId.id) throw new Error("The node ID in the peer certificate doesn't match the expected peer node ID");
Crypto.verifySpki(peerPublicKey, peerSignatureData, peerSignature);
// Generate and send sigma3
const sigma3Salt = ByteArray.concat(operationalIdentityProtectionKey, Crypto.hash([sigma1Bytes, sigma2Bytes]));
const sigma3Key = await Crypto.hkdf(sharedSecret, sigma3Salt, KDFSR3_INFO);
const signatureData = TlvSignedData.encode({ nodeOpCert, intermediateCACert, ecdhPublicKey, peerEcdhPublicKey });
const signature = fabric.sign(signatureData);
const encryptedData = TlvEncryptedDataSigma3.encode({ nodeOpCert, intermediateCACert, signature });
const encrypted = Crypto.encrypt(sigma3Key, encryptedData, TBE_DATA3_NONCE);
const sigma3Bytes = await messenger.sendSigma3({ encrypted });
await messenger.waitForSuccess();
// All good! Create secure session
const secureSessionSalt = ByteArray.concat(operationalIdentityProtectionKey, Crypto.hash([sigma1Bytes, sigma2Bytes, sigma3Bytes]));
secureSession = await client.createSecureSession(sessionId, fabric, peerNodeId, peerSessionId, sharedSecret, secureSessionSalt, true, false);
logger.info(`Case client: Paired succesfully with ${messenger.getChannelName()}`);
resumptionRecord = { fabric, peerNodeId, sharedSecret, resumptionId: peerResumptionId };
}
messenger.close();
client.saveResumptionRecord(resumptionRecord);
return secureSession;
}
}