forked from mystiq/hydrogen-web
move curve25519 code to separate file
This commit is contained in:
parent
86caa5f9b1
commit
cfb94206f9
2 changed files with 113 additions and 91 deletions
89
src/matrix/e2ee/megolm/keybackup/Curve25519.ts
Normal file
89
src/matrix/e2ee/megolm/keybackup/Curve25519.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
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 {MEGOLM_ALGORITHM} from "../../common";
|
||||||
|
import type {RoomKey} from "../decryption/RoomKey";
|
||||||
|
|
||||||
|
import type {BaseBackupInfo, SignatureMap, SessionKeyInfo} from "./KeyBackup";
|
||||||
|
import type * as OlmNamespace from "@matrix-org/olm";
|
||||||
|
type Olm = typeof OlmNamespace;
|
||||||
|
|
||||||
|
export const Algorithm = "m.megolm_backup.v1.curve25519-aes-sha2";
|
||||||
|
|
||||||
|
export type BackupInfo = BaseBackupInfo & {
|
||||||
|
algorithm: typeof Algorithm,
|
||||||
|
auth_data: AuthData,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthData = {
|
||||||
|
public_key: string,
|
||||||
|
signatures: SignatureMap
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SessionData = {
|
||||||
|
ciphertext: string,
|
||||||
|
mac: string,
|
||||||
|
ephemeral: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BackupEncryption {
|
||||||
|
constructor(
|
||||||
|
private readonly encryption: Olm.PkEncryption,
|
||||||
|
private readonly decryption: Olm.PkDecryption
|
||||||
|
) {}
|
||||||
|
|
||||||
|
static fromAuthData(authData: AuthData, privateKey: Uint8Array, olm: Olm): BackupEncryption {
|
||||||
|
const expectedPubKey = authData.public_key;
|
||||||
|
const decryption = new olm.PkDecryption();
|
||||||
|
const encryption = new olm.PkEncryption();
|
||||||
|
try {
|
||||||
|
const pubKey = decryption.init_with_private_key(privateKey);
|
||||||
|
if (pubKey !== expectedPubKey) {
|
||||||
|
throw new Error(`Bad backup key, public key does not match. Calculated ${pubKey} but expected ${expectedPubKey}`);
|
||||||
|
}
|
||||||
|
encryption.set_recipient_key(pubKey);
|
||||||
|
} catch(err) {
|
||||||
|
decryption.free();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return new BackupEncryption(encryption, decryption);
|
||||||
|
}
|
||||||
|
|
||||||
|
decryptRoomKey(sessionData: SessionData): SessionKeyInfo {
|
||||||
|
const sessionInfo = this.decryption.decrypt(
|
||||||
|
sessionData.ephemeral,
|
||||||
|
sessionData.mac,
|
||||||
|
sessionData.ciphertext,
|
||||||
|
);
|
||||||
|
return JSON.parse(sessionInfo) as SessionKeyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptRoomKey(key: RoomKey, sessionKey: string): SessionData {
|
||||||
|
const sessionInfo: SessionKeyInfo = {
|
||||||
|
algorithm: MEGOLM_ALGORITHM,
|
||||||
|
sender_key: key.senderKey,
|
||||||
|
sender_claimed_keys: {ed25519: key.claimedEd25519Key},
|
||||||
|
forwarding_curve25519_key_chain: [],
|
||||||
|
session_key: sessionKey
|
||||||
|
};
|
||||||
|
return this.encryption.encrypt(JSON.stringify(sessionInfo)) as SessionData;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
this.decryption.free();
|
||||||
|
this.encryption.free();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,9 +15,9 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {StoreNames} from "../../../storage/common";
|
import {StoreNames} from "../../../storage/common";
|
||||||
import {LRUCache} from "../../../../utils/LRUCache";
|
|
||||||
import {keyFromStorage, keyFromBackup} from "../decryption/RoomKey";
|
import {keyFromStorage, keyFromBackup} from "../decryption/RoomKey";
|
||||||
import {MEGOLM_ALGORITHM} from "../../common";
|
import {MEGOLM_ALGORITHM} from "../../common";
|
||||||
|
import * as Curve25519 from "./Curve25519";
|
||||||
|
|
||||||
import type {HomeServerApi} from "../../../net/HomeServerApi";
|
import type {HomeServerApi} from "../../../net/HomeServerApi";
|
||||||
import type {IncomingRoomKey, RoomKey} from "../decryption/RoomKey";
|
import type {IncomingRoomKey, RoomKey} from "../decryption/RoomKey";
|
||||||
|
@ -31,48 +31,28 @@ import type {Transaction} from "../../../storage/idb/Transaction";
|
||||||
import type * as OlmNamespace from "@matrix-org/olm";
|
import type * as OlmNamespace from "@matrix-org/olm";
|
||||||
type Olm = typeof OlmNamespace;
|
type Olm = typeof OlmNamespace;
|
||||||
|
|
||||||
type SignatureMap = {
|
export type SignatureMap = {
|
||||||
[userId: string]: {[deviceIdAndAlgorithm: string]: string}
|
[userId: string]: {[deviceIdAndAlgorithm: string]: string}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BaseBackupInfo {
|
export type BaseBackupInfo = {
|
||||||
version: string,
|
version: string,
|
||||||
etag: string,
|
etag: string,
|
||||||
count: number,
|
count: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
const Curve25519Algorithm = "m.megolm_backup.v1.curve25519-aes-sha2";
|
type OtherBackupInfo = BaseBackupInfo & {
|
||||||
|
|
||||||
interface Curve25519BackupInfo extends BaseBackupInfo {
|
|
||||||
algorithm: typeof Curve25519Algorithm,
|
|
||||||
auth_data: Curve25519AuthData,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OtherBackupInfo extends BaseBackupInfo {
|
|
||||||
algorithm: "other"
|
algorithm: "other"
|
||||||
};
|
};
|
||||||
|
|
||||||
type BackupInfo = Curve25519BackupInfo | OtherBackupInfo;
|
type BackupInfo = Curve25519.BackupInfo | OtherBackupInfo;
|
||||||
|
type AuthData = Curve25519.AuthData;
|
||||||
|
|
||||||
interface Curve25519AuthData {
|
|
||||||
public_key: string,
|
|
||||||
signatures: SignatureMap
|
|
||||||
}
|
|
||||||
|
|
||||||
type AuthData = Curve25519AuthData;
|
|
||||||
|
|
||||||
type SessionInfo = {
|
type SessionInfo = {
|
||||||
first_message_index: number,
|
first_message_index: number,
|
||||||
forwarded_count: number,
|
forwarded_count: number,
|
||||||
is_verified: boolean,
|
is_verified: boolean,
|
||||||
session_data: Curve29915SessionData | any
|
session_data: Curve25519.SessionData | any
|
||||||
}
|
|
||||||
|
|
||||||
type Curve29915SessionData = {
|
|
||||||
ciphertext: string,
|
|
||||||
mac: string,
|
|
||||||
ephemeral: string,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MegOlmSessionKeyInfo = {
|
type MegOlmSessionKeyInfo = {
|
||||||
|
@ -83,12 +63,20 @@ type MegOlmSessionKeyInfo = {
|
||||||
session_key: string
|
session_key: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionKeyInfo = MegOlmSessionKeyInfo | {algorithm: string};
|
export type SessionKeyInfo = MegOlmSessionKeyInfo | {algorithm: string};
|
||||||
|
|
||||||
|
type KeyBackupPayload = {
|
||||||
|
rooms: {
|
||||||
|
[roomId: string]: {
|
||||||
|
sessions: {[sessionId: string]: SessionInfo}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class KeyBackup {
|
export class KeyBackup {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly backupInfo: BackupInfo,
|
private readonly backupInfo: BackupInfo,
|
||||||
private readonly algorithm: Curve25519,
|
private readonly crypto: Curve25519.BackupEncryption,
|
||||||
private readonly hsApi: HomeServerApi,
|
private readonly hsApi: HomeServerApi,
|
||||||
private readonly keyLoader: KeyLoader,
|
private readonly keyLoader: KeyLoader,
|
||||||
private readonly storage: Storage,
|
private readonly storage: Storage,
|
||||||
|
@ -100,7 +88,7 @@ export class KeyBackup {
|
||||||
if (!sessionResponse.session_data) {
|
if (!sessionResponse.session_data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const sessionKeyInfo = this.algorithm.decryptRoomKey(sessionResponse.session_data);
|
const sessionKeyInfo = this.crypto.decryptRoomKey(sessionResponse.session_data as Curve25519.SessionData);
|
||||||
if (sessionKeyInfo?.algorithm === MEGOLM_ALGORITHM) {
|
if (sessionKeyInfo?.algorithm === MEGOLM_ALGORITHM) {
|
||||||
return keyFromBackup(roomId, sessionId, sessionKeyInfo);
|
return keyFromBackup(roomId, sessionId, sessionKeyInfo);
|
||||||
} else if (sessionKeyInfo?.algorithm) {
|
} else if (sessionKeyInfo?.algorithm) {
|
||||||
|
@ -131,13 +119,7 @@ export class KeyBackup {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const roomKeys = await Promise.all(keysNeedingBackup.map(k => keyFromStorage(k.roomId, k.senderKey, k.sessionId, txn)));
|
const roomKeys = await Promise.all(keysNeedingBackup.map(k => keyFromStorage(k.roomId, k.senderKey, k.sessionId, txn)));
|
||||||
const payload: {
|
const payload: KeyBackupPayload = { rooms: {} };
|
||||||
rooms: {
|
|
||||||
[roomId: string]: {
|
|
||||||
sessions: {[sessionId: string]: SessionInfo}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} = { rooms: {} };
|
|
||||||
const payloadRooms = payload.rooms;
|
const payloadRooms = payload.rooms;
|
||||||
for (const key of roomKeys) {
|
for (const key of roomKeys) {
|
||||||
if (key) {
|
if (key) {
|
||||||
|
@ -174,7 +156,7 @@ export class KeyBackup {
|
||||||
first_message_index: firstMessageIndex,
|
first_message_index: firstMessageIndex,
|
||||||
forwarded_count: 0,
|
forwarded_count: 0,
|
||||||
is_verified: false,
|
is_verified: false,
|
||||||
session_data: this.algorithm.encryptRoomKey(roomKey, sessionKey)
|
session_data: this.crypto.encryptRoomKey(roomKey, sessionKey)
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -184,7 +166,7 @@ export class KeyBackup {
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.algorithm.dispose();
|
this.crypto.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
static async fromSecretStorage(platform: Platform, olm: Olm, secretStorage: SecretStorage, hsApi: HomeServerApi, keyLoader: KeyLoader, storage: Storage, txn: Transaction): Promise<KeyBackup | undefined> {
|
static async fromSecretStorage(platform: Platform, olm: Olm, secretStorage: SecretStorage, hsApi: HomeServerApi, keyLoader: KeyLoader, storage: Storage, txn: Transaction): Promise<KeyBackup | undefined> {
|
||||||
|
@ -192,61 +174,12 @@ export class KeyBackup {
|
||||||
if (base64PrivateKey) {
|
if (base64PrivateKey) {
|
||||||
const privateKey = new Uint8Array(platform.encoding.base64.decode(base64PrivateKey));
|
const privateKey = new Uint8Array(platform.encoding.base64.decode(base64PrivateKey));
|
||||||
const backupInfo = await hsApi.roomKeysVersion().response() as BackupInfo;
|
const backupInfo = await hsApi.roomKeysVersion().response() as BackupInfo;
|
||||||
if (backupInfo.algorithm === Curve25519Algorithm) {
|
if (backupInfo.algorithm === Curve25519.Algorithm) {
|
||||||
const algorithm = Curve25519.fromAuthData(backupInfo.auth_data, privateKey, olm);
|
const crypto = Curve25519.BackupEncryption.fromAuthData(backupInfo.auth_data, privateKey, olm);
|
||||||
return new KeyBackup(backupInfo, algorithm, hsApi, keyLoader, storage, platform);
|
return new KeyBackup(backupInfo, crypto, hsApi, keyLoader, storage, platform);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Unknown backup algorithm: ${backupInfo.algorithm}`);
|
throw new Error(`Unknown backup algorithm: ${backupInfo.algorithm}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Curve25519 {
|
|
||||||
constructor(
|
|
||||||
private readonly encryption: Olm.PkEncryption,
|
|
||||||
private readonly decryption: Olm.PkDecryption
|
|
||||||
) {}
|
|
||||||
|
|
||||||
static fromAuthData(authData: Curve25519AuthData, privateKey: Uint8Array, olm: Olm): Curve25519 {
|
|
||||||
const expectedPubKey = authData.public_key;
|
|
||||||
const decryption = new olm.PkDecryption();
|
|
||||||
const encryption = new olm.PkEncryption();
|
|
||||||
try {
|
|
||||||
const pubKey = decryption.init_with_private_key(privateKey);
|
|
||||||
if (pubKey !== expectedPubKey) {
|
|
||||||
throw new Error(`Bad backup key, public key does not match. Calculated ${pubKey} but expected ${expectedPubKey}`);
|
|
||||||
}
|
|
||||||
encryption.set_recipient_key(pubKey);
|
|
||||||
} catch(err) {
|
|
||||||
decryption.free();
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
return new Curve25519(encryption, decryption);
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptRoomKey(sessionData: Curve29915SessionData): SessionKeyInfo {
|
|
||||||
const sessionInfo = this.decryption.decrypt(
|
|
||||||
sessionData.ephemeral,
|
|
||||||
sessionData.mac,
|
|
||||||
sessionData.ciphertext,
|
|
||||||
);
|
|
||||||
return JSON.parse(sessionInfo) as SessionKeyInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptRoomKey(key: RoomKey, sessionKey: string): Curve29915SessionData {
|
|
||||||
const sessionInfo: SessionKeyInfo = {
|
|
||||||
algorithm: MEGOLM_ALGORITHM,
|
|
||||||
sender_key: key.senderKey,
|
|
||||||
sender_claimed_keys: {ed25519: key.claimedEd25519Key},
|
|
||||||
forwarding_curve25519_key_chain: [],
|
|
||||||
session_key: sessionKey
|
|
||||||
};
|
|
||||||
return this.encryption.encrypt(JSON.stringify(sessionInfo)) as Curve29915SessionData;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
this.decryption.free();
|
|
||||||
this.encryption.free();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue