Merge pull request #597 from vector-im/ts-conversion-matrix-ssss
Convert matrix/ssss to typescript
This commit is contained in:
commit
dacdc1aec6
10 changed files with 94 additions and 46 deletions
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
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";
|
||||
|
||||
export class AccountSetupViewModel extends ViewModel {
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
import {KeyType} from "../../../matrix/ssss/index.js";
|
||||
import {KeyType} from "../../../matrix/ssss/index";
|
||||
import {createEnum} from "../../../utils/enum";
|
||||
|
||||
export const Status = createEnum("Enabled", "SetupKey", "SetupPhrase", "Pending");
|
||||
|
|
|
@ -42,8 +42,8 @@ import {
|
|||
writeKey as ssssWriteKey,
|
||||
removeKey as ssssRemoveKey,
|
||||
keyFromDehydratedDeviceKey as createSSSSKeyFromDehydratedDeviceKey
|
||||
} from "./ssss/index.js";
|
||||
import {SecretStorage} from "./ssss/SecretStorage.js";
|
||||
} from "./ssss/index";
|
||||
import {SecretStorage} from "./ssss/SecretStorage";
|
||||
import {ObservableValue, RetainedObservableValue} from "../observable/ObservableValue";
|
||||
|
||||
const PICKLE_KEY = "DEFAULT_KEY";
|
||||
|
|
|
@ -15,8 +15,8 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
const DEHYDRATION_LIBOLM_PICKLE_ALGORITHM = "org.matrix.msc2697.v1.olm.libolm_pickle";
|
||||
import {KeyDescription} from "../ssss/common.js";
|
||||
import {keyFromCredentialAndDescription} from "../ssss/index.js";
|
||||
import {KeyDescription} from "../ssss/common";
|
||||
import {keyFromCredentialAndDescription} from "../ssss/index";
|
||||
|
||||
export async function getDehydratedDevice(hsApi, olm, platform, log) {
|
||||
try {
|
||||
|
|
|
@ -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
|
||||
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 {
|
||||
constructor({key, platform}) {
|
||||
private readonly _key: Key;
|
||||
private readonly _platform: Platform;
|
||||
|
||||
constructor({key, platform}: {key: Key, platform: Platform}) {
|
||||
this._key = key;
|
||||
this._platform = platform;
|
||||
}
|
||||
|
||||
async readSecret(name, txn) {
|
||||
async readSecret(name: string, txn: Transaction): Promise<string | undefined> {
|
||||
const accountData = await txn.accountData.get(name);
|
||||
if (!accountData) {
|
||||
return;
|
||||
}
|
||||
const encryptedData = accountData?.content?.encrypted?.[this._key.id];
|
||||
const encryptedData = accountData?.content?.encrypted?.[this._key.id] as EncryptedData;
|
||||
if (!encryptedData) {
|
||||
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;
|
||||
// now derive the aes and mac key from the 4s key
|
||||
const hkdfKey = await this._platform.crypto.derive.hkdf(
|
|
@ -14,25 +14,42 @@ See the License for the specific language governing permissions and
|
|||
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 {
|
||||
constructor(id, keyDescription) {
|
||||
private readonly _id: string;
|
||||
private readonly _keyDescription: KeyDescriptionData;
|
||||
|
||||
constructor(id: string, keyDescription: KeyDescriptionData) {
|
||||
this._id = id;
|
||||
this._keyDescription = keyDescription;
|
||||
}
|
||||
|
||||
get id() {
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
get passphraseParams() {
|
||||
get passphraseParams(): KeyDescriptionData["passphrase"] {
|
||||
return this._keyDescription?.passphrase;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
get algorithm(): string {
|
||||
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") {
|
||||
const kd = this._keyDescription;
|
||||
if (kd.mac) {
|
||||
|
@ -53,33 +70,36 @@ export class KeyDescription {
|
|||
}
|
||||
|
||||
export class Key {
|
||||
constructor(keyDescription, binaryKey) {
|
||||
private readonly _keyDescription: KeyDescription;
|
||||
private readonly _binaryKey: Uint8Array;
|
||||
|
||||
constructor(keyDescription: KeyDescription, binaryKey: Uint8Array) {
|
||||
this._keyDescription = keyDescription;
|
||||
this._binaryKey = binaryKey;
|
||||
}
|
||||
|
||||
withDescription(description) {
|
||||
withDescription(description: KeyDescription): Key {
|
||||
return new Key(description, this._binaryKey);
|
||||
}
|
||||
|
||||
get description() {
|
||||
get description(): KeyDescription {
|
||||
return this._keyDescription;
|
||||
}
|
||||
|
||||
get id() {
|
||||
get id(): string {
|
||||
return this._keyDescription.id;
|
||||
}
|
||||
|
||||
get binaryKey() {
|
||||
get binaryKey(): Uint8Array {
|
||||
return this._binaryKey;
|
||||
}
|
||||
|
||||
get algorithm() {
|
||||
get algorithm(): string {
|
||||
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 {utf8, base64} = encoding;
|
||||
const {derive, aes, hmac} = crypto;
|
|
@ -14,17 +14,26 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {KeyDescription, Key} from "./common.js";
|
||||
import {keyFromPassphrase} from "./passphrase.js";
|
||||
import {keyFromRecoveryKey} from "./recoveryKey.js";
|
||||
import {KeyDescription, Key} from "./common";
|
||||
import {keyFromPassphrase} from "./passphrase";
|
||||
import {keyFromRecoveryKey} from "./recoveryKey";
|
||||
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`;
|
||||
|
||||
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([
|
||||
storage.storeNames.accountData
|
||||
]);
|
||||
|
@ -37,30 +46,30 @@ async function readDefaultKeyDescription(storage) {
|
|||
if (!keyAccountData) {
|
||||
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});
|
||||
}
|
||||
|
||||
export async function readKey(txn) {
|
||||
export async function readKey(txn: Transaction): Promise<Key | undefined> {
|
||||
const keyData = await txn.session.get(SSSS_KEY);
|
||||
if (!keyData) {
|
||||
return;
|
||||
}
|
||||
const keyAccountData = await txn.accountData.get(`m.secret_storage.key.${keyData.id}`);
|
||||
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) {
|
||||
await txn.session.remove(SSSS_KEY);
|
||||
export async function removeKey(txn: Transaction): Promise<void> {
|
||||
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);
|
||||
if (!keyDescription) {
|
||||
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);
|
||||
}
|
||||
|
||||
export async function keyFromCredentialAndDescription(type, credential, keyDescription, platform, olm) {
|
||||
let key;
|
||||
export async function keyFromCredentialAndDescription(type: KeyType, credential: string, keyDescription: KeyDescription, platform: Platform, olm: Olm): Promise<Key> {
|
||||
let key: Key;
|
||||
if (type === KeyType.Passphrase) {
|
||||
key = await keyFromPassphrase(keyDescription, credential, platform);
|
||||
} else if (type === KeyType.RecoveryKey) {
|
||||
|
@ -80,9 +89,9 @@ export async function keyFromCredentialAndDescription(type, credential, keyDescr
|
|||
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);
|
||||
if (await keyDescription.isCompatible(key, platform)) {
|
||||
return key.withDescription(keyDescription);
|
||||
if (await keyDescription?.isCompatible(key, platform)) {
|
||||
return key.withDescription(keyDescription!);
|
||||
}
|
||||
}
|
|
@ -14,7 +14,9 @@ See the License for the specific language governing permissions and
|
|||
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_BITSIZE = 256;
|
||||
|
@ -25,7 +27,7 @@ const DEFAULT_BITSIZE = 256;
|
|||
* @param {Platform} platform
|
||||
* @return {Key}
|
||||
*/
|
||||
export async function keyFromPassphrase(keyDescription, passphrase, platform) {
|
||||
export async function keyFromPassphrase(keyDescription: KeyDescription, passphrase: string, platform: Platform): Promise<Key> {
|
||||
const {passphraseParams} = keyDescription;
|
||||
if (!passphraseParams) {
|
||||
throw new Error("not a passphrase key");
|
|
@ -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
|
||||
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
|
||||
|
@ -23,7 +27,7 @@ const OLM_RECOVERY_KEY_PREFIX = [0x8B, 0x01];
|
|||
* @param {string} recoveryKey
|
||||
* @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, ''));
|
||||
|
||||
let parity = 0;
|
|
@ -2,7 +2,8 @@
|
|||
"compilerOptions": {
|
||||
"strictNullChecks": true,
|
||||
"noEmit": true,
|
||||
"target": "ES2020"
|
||||
"target": "ES2020",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"exclude": [
|
||||
"src/sdk/paths/*"
|
||||
|
|
Reference in a new issue