Skip to content

Commit 8cf5df7

Browse files
authored
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
1 parent febe27d commit 8cf5df7

File tree

8 files changed

+134
-50
lines changed

8 files changed

+134
-50
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2024 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { decodeRecoveryKey, encodeRecoveryKey } from "../../../src/crypto-api";
18+
19+
describe("recovery key", () => {
20+
describe("decodeRecoveryKey", () => {
21+
it("should thrown an incorrect length error", () => {
22+
const key = [0, 1];
23+
const encodedKey = encodeRecoveryKey(key)!;
24+
25+
expect(() => decodeRecoveryKey(encodedKey)).toThrow("Incorrect length");
26+
});
27+
28+
it("should thrown an incorrect parity", () => {
29+
const key = Array.from({ length: 32 }, (_, i) => i);
30+
let encodedKey = encodeRecoveryKey(key)!;
31+
// Mutate the encoded key to have incorrect parity
32+
encodedKey = encodedKey.replace("EsSz", "EsSZ");
33+
34+
expect(() => decodeRecoveryKey(encodedKey!)).toThrow("Incorrect parity");
35+
});
36+
37+
it("should decode a valid encoded key", () => {
38+
const key = Array.from({ length: 32 }, (_, i) => i);
39+
const encodedKey = encodeRecoveryKey(key)!;
40+
41+
expect(decodeRecoveryKey(encodedKey)).toEqual(new Uint8Array(key));
42+
});
43+
});
44+
});

src/client.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ import {
8585
isCryptoAvailable,
8686
} from "./crypto/index.ts";
8787
import { DeviceInfo } from "./crypto/deviceinfo.ts";
88-
import { decodeRecoveryKey } from "./crypto/recoverykey.ts";
8988
import { keyFromAuthData } from "./crypto/key_passphrase.ts";
9089
import { User, UserEvent, UserEventHandlerMap } from "./models/user.ts";
9190
import { getHttpUriForMxc } from "./content-repo.ts";
@@ -223,7 +222,13 @@ import { LocalNotificationSettings } from "./@types/local_notifications.ts";
223222
import { buildFeatureSupportMap, Feature, ServerSupport } from "./feature.ts";
224223
import { BackupDecryptor, CryptoBackend } from "./common-crypto/CryptoBackend.ts";
225224
import { RUST_SDK_STORE_PREFIX } from "./rust-crypto/constants.ts";
226-
import { BootstrapCrossSigningOpts, CrossSigningKeyInfo, CryptoApi, ImportRoomKeysOpts } from "./crypto-api/index.ts";
225+
import {
226+
BootstrapCrossSigningOpts,
227+
CrossSigningKeyInfo,
228+
CryptoApi,
229+
decodeRecoveryKey,
230+
ImportRoomKeysOpts,
231+
} from "./crypto-api/index.ts";
227232
import { DeviceInfoMap } from "./crypto/DeviceList.ts";
228233
import {
229234
AddSecretStorageKeyOpts,
@@ -3627,6 +3632,12 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
36273632
return this.crypto.backupManager.flagAllGroupSessionsForBackup();
36283633
}
36293634

3635+
/**
3636+
* Return true if recovery key is valid.
3637+
* Try to decode the recovery key and check if it's successful.
3638+
* @param recoveryKey
3639+
* @deprecated Use {@link decodeRecoveryKey} directly
3640+
*/
36303641
public isValidRecoveryKey(recoveryKey: string): boolean {
36313642
try {
36323643
decodeRecoveryKey(recoveryKey);
@@ -3658,6 +3669,7 @@ export class MatrixClient extends TypedEventEmitter<EmittedEvents, ClientEventHa
36583669
*
36593670
* @param recoveryKey - The recovery key
36603671
* @returns key backup key
3672+
* @deprecated Use {@link decodeRecoveryKey} directly
36613673
*/
36623674
public keyBackupKeyFromRecoveryKey(recoveryKey: string): Uint8Array {
36633675
return decodeRecoveryKey(recoveryKey);

src/crypto-api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,3 +967,4 @@ export interface OwnDeviceKeys {
967967

968968
export * from "./verification.ts";
969969
export * from "./keybackup.ts";
970+
export * from "./recovery-key.ts";

src/crypto-api/recovery-key.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2024 The Matrix.org Foundation C.I.C.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import bs58 from "bs58";
18+
19+
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
20+
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
21+
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
22+
const KEY_SIZE = 32;
23+
24+
/**
25+
* Encode a recovery key using the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation}
26+
* @param key
27+
*/
28+
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
29+
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
30+
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
31+
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
32+
33+
let parity = 0;
34+
for (let i = 0; i < buf.length - 1; ++i) {
35+
parity ^= buf[i];
36+
}
37+
buf[buf.length - 1] = parity;
38+
const base58key = bs58.encode(buf);
39+
40+
return base58key.match(/.{1,4}/g)?.join(" ");
41+
}
42+
43+
/**
44+
* Decode a recovery key encoded with the Matrix {@link https://spec.matrix.org/v1.11/appendices/#cryptographic-key-representation | Cryptographic key representation} encoding.
45+
* @param recoveryKey
46+
*/
47+
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
48+
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
49+
50+
let parity = 0;
51+
for (const b of result) {
52+
parity ^= b;
53+
}
54+
if (parity !== 0) {
55+
throw new Error("Incorrect parity");
56+
}
57+
58+
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
59+
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
60+
throw new Error("Incorrect prefix");
61+
}
62+
}
63+
64+
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
65+
throw new Error("Incorrect length");
66+
}
67+
68+
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
69+
}

src/crypto/backup.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ import { DeviceTrustLevel } from "./CrossSigning.ts";
2727
import { keyFromPassphrase } from "./key_passphrase.ts";
2828
import { encodeUri, safeSet, sleep } from "../utils.ts";
2929
import { IndexedDBCryptoStore } from "./store/indexeddb-crypto-store.ts";
30-
import { encodeRecoveryKey } from "./recoverykey.ts";
3130
import { calculateKeyCheck, decryptAES, encryptAES, IEncryptedPayload } from "./aes.ts";
3231
import {
3332
Curve25519SessionData,
@@ -41,6 +40,7 @@ import { CryptoEvent } from "./index.ts";
4140
import { ClientPrefix, HTTPError, MatrixError, Method } from "../http-api/index.ts";
4241
import { BackupTrustInfo } from "../crypto-api/keybackup.ts";
4342
import { BackupDecryptor } from "../common-crypto/CryptoBackend.ts";
43+
import { encodeRecoveryKey } from "../crypto-api/index.ts";
4444

4545
const KEY_BACKUP_KEYS_PER_REQUEST = 200;
4646
const KEY_BACKUP_CHECK_RATE_LIMIT = 5000; // ms

src/crypto/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import { VerificationBase } from "./verification/Base.ts";
4242
import { ReciprocateQRCode, SCAN_QR_CODE_METHOD, SHOW_QR_CODE_METHOD } from "./verification/QRCode.ts";
4343
import { SAS as SASVerification } from "./verification/SAS.ts";
4444
import { keyFromPassphrase } from "./key_passphrase.ts";
45-
import { decodeRecoveryKey, encodeRecoveryKey } from "./recoverykey.ts";
4645
import { VerificationRequest } from "./verification/request/VerificationRequest.ts";
4746
import { InRoomChannel, InRoomRequests } from "./verification/request/InRoomChannel.ts";
4847
import { Request, ToDeviceChannel, ToDeviceRequests } from "./verification/request/ToDeviceChannel.ts";
@@ -89,8 +88,10 @@ import {
8988
BootstrapCrossSigningOpts,
9089
CrossSigningKeyInfo,
9190
CrossSigningStatus,
91+
decodeRecoveryKey,
9292
DecryptionFailureCode,
9393
DeviceVerificationStatus,
94+
encodeRecoveryKey,
9495
EventEncryptionInfo,
9596
EventShieldColour,
9697
EventShieldReason,

src/crypto/recoverykey.ts

Lines changed: 2 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,48 +14,5 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import bs58 from "bs58";
18-
19-
// picked arbitrarily but to try & avoid clashing with any bitcoin ones
20-
// (which are also base58 encoded, but bitcoin's involve a lot more hashing)
21-
const OLM_RECOVERY_KEY_PREFIX = [0x8b, 0x01];
22-
const KEY_SIZE = 32;
23-
24-
export function encodeRecoveryKey(key: ArrayLike<number>): string | undefined {
25-
const buf = Buffer.alloc(OLM_RECOVERY_KEY_PREFIX.length + key.length + 1);
26-
buf.set(OLM_RECOVERY_KEY_PREFIX, 0);
27-
buf.set(key, OLM_RECOVERY_KEY_PREFIX.length);
28-
29-
let parity = 0;
30-
for (let i = 0; i < buf.length - 1; ++i) {
31-
parity ^= buf[i];
32-
}
33-
buf[buf.length - 1] = parity;
34-
const base58key = bs58.encode(buf);
35-
36-
return base58key.match(/.{1,4}/g)?.join(" ");
37-
}
38-
39-
export function decodeRecoveryKey(recoveryKey: string): Uint8Array {
40-
const result = bs58.decode(recoveryKey.replace(/ /g, ""));
41-
42-
let parity = 0;
43-
for (const b of result) {
44-
parity ^= b;
45-
}
46-
if (parity !== 0) {
47-
throw new Error("Incorrect parity");
48-
}
49-
50-
for (let i = 0; i < OLM_RECOVERY_KEY_PREFIX.length; ++i) {
51-
if (result[i] !== OLM_RECOVERY_KEY_PREFIX[i]) {
52-
throw new Error("Incorrect prefix");
53-
}
54-
}
55-
56-
if (result.length !== OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE + 1) {
57-
throw new Error("Incorrect length");
58-
}
59-
60-
return Uint8Array.from(result.slice(OLM_RECOVERY_KEY_PREFIX.length, OLM_RECOVERY_KEY_PREFIX.length + KEY_SIZE));
61-
}
17+
// Re-export to avoid breaking changes
18+
export * from "../crypto-api/recovery-key.ts";

src/rust-crypto/rust-crypto.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {
5858
OwnDeviceKeys,
5959
UserVerificationStatus,
6060
VerificationRequest,
61+
encodeRecoveryKey,
6162
} from "../crypto-api/index.ts";
6263
import { deviceKeysToDeviceMap, rustDeviceToJsDevice } from "./device-converter.ts";
6364
import { IDownloadKeyResult, IQueryKeysRequest } from "../client.ts";
@@ -66,7 +67,6 @@ import { SECRET_STORAGE_ALGORITHM_V1_AES, ServerSideSecretStorage } from "../sec
6667
import { CrossSigningIdentity } from "./CrossSigningIdentity.ts";
6768
import { secretStorageCanAccessSecrets, secretStorageContainsCrossSigningKeys } from "./secret-storage.ts";
6869
import { keyFromPassphrase } from "../crypto/key_passphrase.ts";
69-
import { encodeRecoveryKey } from "../crypto/recoverykey.ts";
7070
import { isVerificationEvent, RustVerificationRequest, verificationMethodIdentifierToMethod } from "./verification.ts";
7171
import { EventType, MsgType } from "../@types/event.ts";
7272
import { CryptoEvent } from "../crypto/index.ts";

0 commit comments

Comments
 (0)