Merge pull request #597 from vector-im/ts-conversion-matrix-ssss

Convert matrix/ssss to typescript
This commit is contained in:
Bruno Windels 2021-12-09 18:54:25 +01:00 committed by GitHub
commit dacdc1aec6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 94 additions and 46 deletions

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import {ViewModel} from "./ViewModel.js"; import {ViewModel} from "./ViewModel.js";
import {KeyType} from "../matrix/ssss/index.js"; import {KeyType} from "../matrix/ssss/index";
import {Status} from "./session/settings/SessionBackupViewModel.js"; import {Status} from "./session/settings/SessionBackupViewModel.js";
export class AccountSetupViewModel extends ViewModel { export class AccountSetupViewModel extends ViewModel {

View file

@ -15,7 +15,7 @@ limitations under the License.
*/ */
import {ViewModel} from "../../ViewModel.js"; import {ViewModel} from "../../ViewModel.js";
import {KeyType} from "../../../matrix/ssss/index.js"; import {KeyType} from "../../../matrix/ssss/index";
import {createEnum} from "../../../utils/enum"; import {createEnum} from "../../../utils/enum";
export const Status = createEnum("Enabled", "SetupKey", "SetupPhrase", "Pending"); export const Status = createEnum("Enabled", "SetupKey", "SetupPhrase", "Pending");

View file

@ -42,8 +42,8 @@ import {
writeKey as ssssWriteKey, writeKey as ssssWriteKey,
removeKey as ssssRemoveKey, removeKey as ssssRemoveKey,
keyFromDehydratedDeviceKey as createSSSSKeyFromDehydratedDeviceKey keyFromDehydratedDeviceKey as createSSSSKeyFromDehydratedDeviceKey
} from "./ssss/index.js"; } from "./ssss/index";
import {SecretStorage} from "./ssss/SecretStorage.js"; import {SecretStorage} from "./ssss/SecretStorage";
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue"; import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue";
const PICKLE_KEY = "DEFAULT_KEY"; const PICKLE_KEY = "DEFAULT_KEY";

View file

@ -15,8 +15,8 @@ limitations under the License.
*/ */
const DEHYDRATION_LIBOLM_PICKLE_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle"; const DEHYDRATION_LIBOLM_PICKLE_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle";
import {KeyDescription} from "../ssss/common.js"; import {KeyDescription} from "../ssss/common";
import {keyFromCredentialAndDescription} from "../ssss/index.js"; import {keyFromCredentialAndDescription} from "../ssss/index";
export async function getDehydratedDevice(hsApi, olm, platform, log) { export async function getDehydratedDevice(hsApi, olm, platform, log) {
try { try {

View file

@ -13,19 +13,31 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import type {Key} from "./common";
import type {Platform} from "../../platform/web/Platform.js";
import type {Transaction} from "../storage/idb/Transaction";
type EncryptedData = {
iv: string;
ciphertext: string;
mac: string;
}
export class SecretStorage { export class SecretStorage {
constructor({key, platform}) { private readonly _key: Key;
private readonly _platform: Platform;
constructor({key, platform}: {key: Key, platform: Platform}) {
this._key = key; this._key = key;
this._platform = platform; this._platform = platform;
} }
async readSecret(name, txn) { async readSecret(name: string, txn: Transaction): Promise<string | undefined> {
const accountData = await txn.accountData.get(name); const accountData = await txn.accountData.get(name);
if (!accountData) { if (!accountData) {
return; return;
} }
const encryptedData = accountData?.content?.encrypted?.[this._key.id]; const encryptedData = accountData?.content?.encrypted?.[this._key.id] as EncryptedData;
if (!encryptedData) { if (!encryptedData) {
throw new Error(`Secret ${accountData.type} is not encrypted for key ${this._key.id}`); throw new Error(`Secret ${accountData.type} is not encrypted for key ${this._key.id}`);
} }
@ -37,7 +49,7 @@ export class SecretStorage {
} }
} }
async _decryptAESSecret(type, encryptedData) { async _decryptAESSecret(type: string, encryptedData: EncryptedData): Promise<string> {
const {base64, utf8} = this._platform.encoding; const {base64, utf8} = this._platform.encoding;
// now derive the aes and mac key from the 4s key // now derive the aes and mac key from the 4s key
const hkdfKey = await this._platform.crypto.derive.hkdf( const hkdfKey = await this._platform.crypto.derive.hkdf(

View file

@ -14,25 +14,42 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import type {Platform} from "../../platform/web/Platform.js";
export type KeyDescriptionData = {
algorithm: string;
passphrase: {
algorithm: string;
iterations: number;
salt: string;
bits?: number;
};
mac: string;
iv: string;
}
export class KeyDescription { export class KeyDescription {
constructor(id, keyDescription) { private readonly _id: string;
private readonly _keyDescription: KeyDescriptionData;
constructor(id: string, keyDescription: KeyDescriptionData) {
this._id = id; this._id = id;
this._keyDescription = keyDescription; this._keyDescription = keyDescription;
} }
get id() { get id(): string {
return this._id; return this._id;
} }
get passphraseParams() { get passphraseParams(): KeyDescriptionData["passphrase"] {
return this._keyDescription?.passphrase; return this._keyDescription?.passphrase;
} }
get algorithm() { get algorithm(): string {
return this._keyDescription?.algorithm; return this._keyDescription?.algorithm;
} }
async isCompatible(key, platform) { async isCompatible(key: Key, platform: Platform): Promise<boolean> {
if (this.algorithm === "m.secret_storage.v1.aes-hmac-sha2") { if (this.algorithm === "m.secret_storage.v1.aes-hmac-sha2") {
const kd = this._keyDescription; const kd = this._keyDescription;
if (kd.mac) { if (kd.mac) {
@ -53,33 +70,36 @@ export class KeyDescription {
} }
export class Key { export class Key {
constructor(keyDescription, binaryKey) { private readonly _keyDescription: KeyDescription;
private readonly _binaryKey: Uint8Array;
constructor(keyDescription: KeyDescription, binaryKey: Uint8Array) {
this._keyDescription = keyDescription; this._keyDescription = keyDescription;
this._binaryKey = binaryKey; this._binaryKey = binaryKey;
} }
withDescription(description) { withDescription(description: KeyDescription): Key {
return new Key(description, this._binaryKey); return new Key(description, this._binaryKey);
} }
get description() { get description(): KeyDescription {
return this._keyDescription; return this._keyDescription;
} }
get id() { get id(): string {
return this._keyDescription.id; return this._keyDescription.id;
} }
get binaryKey() { get binaryKey(): Uint8Array {
return this._binaryKey; return this._binaryKey;
} }
get algorithm() { get algorithm(): string {
return this._keyDescription.algorithm; return this._keyDescription.algorithm;
} }
} }
async function calculateKeyMac(key, ivStr, platform) { async function calculateKeyMac(key: BufferSource, ivStr: string, platform: Platform): Promise<string> {
const {crypto, encoding} = platform; const {crypto, encoding} = platform;
const {utf8, base64} = encoding; const {utf8, base64} = encoding;
const {derive, aes, hmac} = crypto; const {derive, aes, hmac} = crypto;

View file

@ -14,17 +14,26 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {KeyDescription, Key} from "./common.js"; import {KeyDescription, Key} from "./common";
import {keyFromPassphrase} from "./passphrase.js"; import {keyFromPassphrase} from "./passphrase";
import {keyFromRecoveryKey} from "./recoveryKey.js"; import {keyFromRecoveryKey} from "./recoveryKey";
import {SESSION_E2EE_KEY_PREFIX} from "../e2ee/common.js"; import {SESSION_E2EE_KEY_PREFIX} from "../e2ee/common.js";
import {createEnum} from "../../utils/enum"; import type {Storage} from "../storage/idb/Storage";
import type {Transaction} from "../storage/idb/Transaction";
import type {KeyDescriptionData} from "./common";
import type {Platform} from "../../platform/web/Platform.js";
import type * as OlmNamespace from "@matrix-org/olm"
type Olm = typeof OlmNamespace;
const SSSS_KEY = `${SESSION_E2EE_KEY_PREFIX}ssssKey`; const SSSS_KEY = `${SESSION_E2EE_KEY_PREFIX}ssssKey`;
export const KeyType = createEnum("RecoveryKey", "Passphrase"); export enum KeyType {
"RecoveryKey",
"Passphrase"
}
async function readDefaultKeyDescription(storage) { async function readDefaultKeyDescription(storage: Storage): Promise<KeyDescription | undefined> {
const txn = await storage.readTxn([ const txn = await storage.readTxn([
storage.storeNames.accountData storage.storeNames.accountData
]); ]);
@ -37,30 +46,30 @@ async function readDefaultKeyDescription(storage) {
if (!keyAccountData) { if (!keyAccountData) {
return; return;
} }
return new KeyDescription(id, keyAccountData.content); return new KeyDescription(id, keyAccountData.content as KeyDescriptionData);
} }
export async function writeKey(key, txn) { export async function writeKey(key: Key, txn: Transaction): Promise<void> {
txn.session.set(SSSS_KEY, {id: key.id, binaryKey: key.binaryKey}); txn.session.set(SSSS_KEY, {id: key.id, binaryKey: key.binaryKey});
} }
export async function readKey(txn) { export async function readKey(txn: Transaction): Promise<Key | undefined> {
const keyData = await txn.session.get(SSSS_KEY); const keyData = await txn.session.get(SSSS_KEY);
if (!keyData) { if (!keyData) {
return; return;
} }
const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${keyData.id}`); const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${keyData.id}`);
if (keyAccountData) { if (keyAccountData) {
return new Key(new KeyDescription(keyData.id, keyAccountData.content), keyData.binaryKey); return new Key(new KeyDescription(keyData.id, keyAccountData.content as KeyDescriptionData), keyData.binaryKey);
} }
} }
export async function removeKey(txn) { export async function removeKey(txn: Transaction): Promise<void> {
await txn.session.remove(SSSS_KEY); txn.session.remove(SSSS_KEY);
} }
export async function keyFromCredential(type, credential, storage, platform, olm) { export async function keyFromCredential(type: KeyType, credential: string, storage: Storage, platform: Platform, olm: Olm): Promise<Key> {
const keyDescription = await readDefaultKeyDescription(storage); const keyDescription = await readDefaultKeyDescription(storage);
if (!keyDescription) { if (!keyDescription) {
throw new Error("Could not find a default secret storage key in account data"); throw new Error("Could not find a default secret storage key in account data");
@ -68,8 +77,8 @@ export async function keyFromCredential(type, credential, storage, platform, olm
return await keyFromCredentialAndDescription(type, credential, keyDescription, platform, olm); return await keyFromCredentialAndDescription(type, credential, keyDescription, platform, olm);
} }
export async function keyFromCredentialAndDescription(type, credential, keyDescription, platform, olm) { export async function keyFromCredentialAndDescription(type: KeyType, credential: string, keyDescription: KeyDescription, platform: Platform, olm: Olm): Promise<Key> {
let key; let key: Key;
if (type === KeyType.Passphrase) { if (type === KeyType.Passphrase) {
key = await keyFromPassphrase(keyDescription, credential, platform); key = await keyFromPassphrase(keyDescription, credential, platform);
} else if (type === KeyType.RecoveryKey) { } else if (type === KeyType.RecoveryKey) {
@ -80,9 +89,9 @@ export async function keyFromCredentialAndDescription(type, credential, keyDescr
return key; return key;
} }
export async function keyFromDehydratedDeviceKey(key, storage, platform) { export async function keyFromDehydratedDeviceKey(key: Key, storage: Storage, platform: Platform): Promise<Key | undefined> {
const keyDescription = await readDefaultKeyDescription(storage); const keyDescription = await readDefaultKeyDescription(storage);
if (await keyDescription.isCompatible(key, platform)) { if (await keyDescription?.isCompatible(key, platform)) {
return key.withDescription(keyDescription); return key.withDescription(keyDescription!);
} }
} }

View file

@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Key} from "./common.js"; import {Key} from "./common";
import type {KeyDescription} from "./common";
import type {Platform} from "../../platform/web/Platform.js";
const DEFAULT_ITERATIONS = 500000; const DEFAULT_ITERATIONS = 500000;
const DEFAULT_BITSIZE = 256; const DEFAULT_BITSIZE = 256;
@ -25,7 +27,7 @@ const DEFAULT_BITSIZE = 256;
* @param {Platform} platform * @param {Platform} platform
* @return {Key} * @return {Key}
*/ */
export async function keyFromPassphrase(keyDescription, passphrase, platform) { export async function keyFromPassphrase(keyDescription: KeyDescription, passphrase: string, platform: Platform): Promise<Key> {
const {passphraseParams} = keyDescription; const {passphraseParams} = keyDescription;
if (!passphraseParams) { if (!passphraseParams) {
throw new Error("not a passphrase key"); throw new Error("not a passphrase key");

View file

@ -13,9 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import {Key} from "./common.js"; import {Key} from "./common";
import {KeyDescription} from "./common";
import type {Platform} from "../../platform/web/Platform.js";
import type * as OlmNamespace from "@matrix-org/olm";
type Olm = typeof OlmNamespace;
const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01]; const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01] as const;
/** /**
* @param {Olm} olm * @param {Olm} olm
@ -23,7 +27,7 @@ const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
* @param {string} recoveryKey * @param {string} recoveryKey
* @return {Key} * @return {Key}
*/ */
export function keyFromRecoveryKey(keyDescription, recoveryKey, olm, platform) { export function keyFromRecoveryKey(keyDescription: KeyDescription, recoveryKey: string, olm: Olm, platform: Platform): Key {
const result = platform.encoding.base58.decode(recoveryKey.replace(/ /g, '')); const result = platform.encoding.base58.decode(recoveryKey.replace(/ /g, ''));
let parity = 0; let parity = 0;

View file

@ -2,7 +2,8 @@
"compilerOptions": { "compilerOptions": {
"strictNullChecks": true, "strictNullChecks": true,
"noEmit": true, "noEmit": true,
"target": "ES2020" "target": "ES2020",
"moduleResolution": "node"
}, },
"exclude": [ "exclude": [
"src/sdk/paths/*" "src/sdk/paths/*"