1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-08-07 23:02:56 +03:00

Move crypto/recoverykey.ts to crypto-api/recovery-key.ts (#4399)

* Move `crypto/recoverykey.ts` to `crypto-api/recovery-key.ts`

* Re-export `crypto-api/recovery-key` into `crypto/recoverykey`

* Add a bit of doc

* Deprecate `MatrixClient.isValidRecoveryKey` and `MatrixClient.keyBackupKeyFromRecoveryKey`

* Import `index.ts` directly

* Update `recovery-key.ts` doc

* Add tests for `decodeRecoveryKey`

* Move `recovery-key.spec.ts` file
This commit is contained in:
Florian Duros
2024-09-13 15:26:30 +02:00
committed by GitHub
parent febe27ddcc
commit 8cf5df73ee
8 changed files with 134 additions and 50 deletions

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2024 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 { decodeRecoveryKey, encodeRecoveryKey } from "../../../src/crypto-api";
describe("recovery key", () => {
describe("decodeRecoveryKey", () => {
it("should thrown an incorrect length error", () => {
const key = [0, 1];
const encodedKey = encodeRecoveryKey(key)!;
expect(() => decodeRecoveryKey(encodedKey)).toThrow("Incorrect length");
});
it("should thrown an incorrect parity", () => {
const key = Array.from({ length: 32 }, (_, i) => i);
let encodedKey = encodeRecoveryKey(key)!;
// Mutate the encoded key to have incorrect parity
encodedKey = encodedKey.replace("EsSz", "EsSZ");
expect(() => decodeRecoveryKey(encodedKey!)).toThrow("Incorrect parity");
});
it("should decode a valid encoded key", () => {
const key = Array.from({ length: 32 }, (_, i) => i);
const encodedKey = encodeRecoveryKey(key)!;
expect(decodeRecoveryKey(encodedKey)).toEqual(new Uint8Array(key));
});
});
});

View File

@@ -85,7 +85,6 @@ import {
isCryptoAvailable, isCryptoAvailable,
} from "./crypto/index.ts"; } from "./crypto/index.ts";
import { DeviceInfo } from "./crypto/deviceinfo.ts"; import { DeviceInfo } from "./crypto/deviceinfo.ts";
import { decodeRecoveryKey } from "./crypto/recoverykey.ts";
import { keyFromAuthData } from "./crypto/key_passphrase.ts"; import { keyFromAuthData } from "./crypto/key_passphrase.ts";
import { User, UserEvent, UserEventHandlerMap } from "./models/user.ts"; import { User, UserEvent, UserEventHandlerMap } from "./models/user.ts";
import { getHttpUriForMxc } from "./content-repo.ts"; import { getHttpUriForMxc } from "./content-repo.ts";
@@ -223,7 +222,13 @@ import { LocalNotificationSettings } from "./@types/local_notifications.ts";
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.ts"; import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.ts";
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend.ts"; import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend.ts";
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.ts"; import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.ts";
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api/index.ts"; import {
BootstrapCrossSigningOpts,
CrossSigningKeyInfo,
CryptoApi,
decodeRecoveryKey,
ImportRoomKeysOpts,
} from "./crypto-api/index.ts";
import { DeviceInfoMap } from "./crypto/DeviceList.ts"; import { DeviceInfoMap } from "./crypto/DeviceList.ts";
import { import {
AddSecretStorageKeyOpts, AddSecretStorageKeyOpts,
@@ -3627,6 +3632,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
return this.crypto.backupManager.flagAllGroupSessionsForBackup(); return this.crypto.backupManager.flagAllGroupSessionsForBackup();
} }
/**
* Return true if recovery key is valid.
* Try to decode the recovery key and check if it's successful.
* @param recoveryKey
* @deprecated Use {@link decodeRecoveryKey} directly
*/
public isValidRecoveryKey(recoveryKey: string): boolean { public isValidRecoveryKey(recoveryKey: string): boolean {
try { try {
decodeRecoveryKey(recoveryKey); decodeRecoveryKey(recoveryKey);
@@ -3658,6 +3669,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
* *
* @param recoveryKey - The recovery key * @param recoveryKey - The recovery key
* @returns key backup key * @returns key backup key
* @deprecated Use {@link decodeRecoveryKey} directly
*/ */
public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array { public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array {
return decodeRecoveryKey(recoveryKey); return decodeRecoveryKey(recoveryKey);

View File

@@ -967,3 +967,4 @@ export interface OwnDeviceKeys {
export * from "./verification.ts"; export * from "./verification.ts";
export * from "./keybackup.ts"; export * from "./keybackup.ts";
export * from "./recovery-key.ts";

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2024 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 bs58 from "bs58";
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
const KEY_SIZE = 32;
/**
* Encode a recovery key using the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation}
* @param key
*/
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
let parity = 0;
for (let i = 0; i < buf.length - 1; ++i) {
parity ^= buf[i];
}
buf[buf.length - 1] = parity;
const base58key = bs58.encode(buf);
return base58key.match(/.{1,4}/g)?.join(" ");
}
/**
* Decode a recovery key encoded with the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation} encoding.
* @param recoveryKey
*/
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
let parity = 0;
for (const b of result) {
parity ^= b;
}
if (parity !== 0) {
throw new Error("Incorrect parity");
}
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
throw new Error("Incorrect prefix");
}
}
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
throw new Error("Incorrect length");
}
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
}

View File

@@ -27,7 +27,6 @@ import { DeviceTrustLevel } from "./CrossSigning.ts";
import { keyFromPassphrase } from "./key_passphrase.ts"; import { keyFromPassphrase } from "./key_passphrase.ts";
import { encodeUri, safeSet, sleep } from "../utils.ts"; import { encodeUri, safeSet, sleep } from "../utils.ts";
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts"; import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
import { encodeRecoveryKey } from "./recoverykey.ts";
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts"; import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts";
import { import {
Curve25519SessionData, Curve25519SessionData,
@@ -41,6 +40,7 @@ import { CryptoEvent } from "./index.ts";
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.ts"; import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.ts";
import { BackupTrustInfo } from "../crypto-api/keybackup.ts"; import { BackupTrustInfo } from "../crypto-api/keybackup.ts";
import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts"; import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
import { encodeRecoveryKey } from "../crypto-api/index.ts";
const KEY_BACKUP_KEYS_PER_REQUEST = 200; const KEY_BACKUP_KEYS_PER_REQUEST = 200;
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms

View File

@@ -42,7 +42,6 @@ import { VerificationBase } from "./verification/Base.ts";
import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode.ts"; import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode.ts";
import { SAS as SASVerification } from "./verification/SAS.ts"; import { SAS as SASVerification } from "./verification/SAS.ts";
import { keyFromPassphrase } from "./key_passphrase.ts"; import { keyFromPassphrase } from "./key_passphrase.ts";
import { decodeRecoveryKey, encodeRecoveryKey } from "./recoverykey.ts";
import { VerificationRequest } from "./verification/request/VerificationRequest.ts"; import { VerificationRequest } from "./verification/request/VerificationRequest.ts";
import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel.ts"; import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel.ts";
import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel.ts"; import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel.ts";
@@ -89,8 +88,10 @@ import {
BootstrapCrossSigningOpts, BootstrapCrossSigningOpts,
CrossSigningKeyInfo, CrossSigningKeyInfo,
CrossSigningStatus, CrossSigningStatus,
decodeRecoveryKey,
DecryptionFailureCode, DecryptionFailureCode,
DeviceVerificationStatus, DeviceVerificationStatus,
encodeRecoveryKey,
EventEncryptionInfo, EventEncryptionInfo,
EventShieldColour, EventShieldColour,
EventShieldReason, EventShieldReason,

View File

@@ -14,48 +14,5 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import bs58 from "bs58"; // Re-export to avoid breaking changes
export * from "../crypto-api/recovery-key.ts";
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
const KEY_SIZE = 32;
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
let parity = 0;
for (let i = 0; i < buf.length - 1; ++i) {
parity ^= buf[i];
}
buf[buf.length - 1] = parity;
const base58key = bs58.encode(buf);
return base58key.match(/.{1,4}/g)?.join(" ");
}
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
let parity = 0;
for (const b of result) {
parity ^= b;
}
if (parity !== 0) {
throw new Error("Incorrect parity");
}
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
throw new Error("Incorrect prefix");
}
}
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
throw new Error("Incorrect length");
}
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
}

View File

@@ -58,6 +58,7 @@ import {
OwnDeviceKeys, OwnDeviceKeys,
UserVerificationStatus, UserVerificationStatus,
VerificationRequest, VerificationRequest,
encodeRecoveryKey,
} from "../crypto-api/index.ts"; } from "../crypto-api/index.ts";
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts"; import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts";
import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts"; import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts";
@@ -66,7 +67,6 @@ import { SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../sec
import { CrossSigningIdentity } from "./CrossSigningIdentity.ts"; import { CrossSigningIdentity } from "./CrossSigningIdentity.ts";
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts"; import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts";
import { keyFromPassphrase } from "../crypto/key_passphrase.ts"; import { keyFromPassphrase } from "../crypto/key_passphrase.ts";
import { encodeRecoveryKey } from "../crypto/recoverykey.ts";
import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts"; import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts";
import { EventType, MsgType } from "../@types/event.ts"; import { EventType, MsgType } from "../@types/event.ts";
import { CryptoEvent } from "../crypto/index.ts"; import { CryptoEvent } from "../crypto/index.ts";