You've already forked matrix-js-sdk
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:
44
spec/unit/crypto-api/recovery-key.spec.ts
Normal file
44
spec/unit/crypto-api/recovery-key.spec.ts
Normal 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));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -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);
|
||||||
|
@@ -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";
|
||||||
|
69
src/crypto-api/recovery-key.ts
Normal file
69
src/crypto-api/recovery-key.ts
Normal 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));
|
||||||
|
}
|
@@ -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
|
||||||
|
@@ -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,
|
||||||
|
@@ -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));
|
|
||||||
}
|
|
||||||
|
@@ -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";
|
||||||
|
Reference in New Issue
Block a user