chore(types): typed secret storage key cache; drop the cryptoCallbacks cast (#210) #468
@@ -87,7 +87,7 @@ export const initClient = async (session: Session): Promise<MatrixClient> => {
|
|||||||
// the user (KeyUploadBanner) instead of retrying silently forever.
|
// the user (KeyUploadBanner) instead of retrying silently forever.
|
||||||
fetchFn: watchKeyUploads((input, init) => globalThis.fetch(input, init)),
|
fetchFn: watchKeyUploads((input, init) => globalThis.fetch(input, init)),
|
||||||
timelineSupport: true,
|
timelineSupport: true,
|
||||||
cryptoCallbacks: cryptoCallbacks as any,
|
cryptoCallbacks,
|
||||||
// SAS (emoji) + QR-code verification (show/scan/reciprocate).
|
// SAS (emoji) + QR-code verification (show/scan/reciprocate).
|
||||||
verificationMethods: ['m.sas.v1', 'm.qr_code.show.v1', 'm.qr_code.scan.v1', 'm.reciprocate.v1'],
|
verificationMethods: ['m.sas.v1', 'm.qr_code.show.v1', 'm.qr_code.scan.v1', 'm.reciprocate.v1'],
|
||||||
oauthClientId: oidcIdentity?.oidc.clientId,
|
oauthClientId: oidcIdentity?.oidc.clientId,
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
const secretStorageKeys = new Map();
|
|
||||||
|
|
||||||
export function storePrivateKey(keyId, privateKey) {
|
|
||||||
if (privateKey instanceof Uint8Array === false) {
|
|
||||||
throw new Error('Unable to store, privateKey is invalid.');
|
|
||||||
}
|
|
||||||
secretStorageKeys.set(keyId, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
function hasPrivateKey(keyId) {
|
|
||||||
return secretStorageKeys.get(keyId) instanceof Uint8Array;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPrivateKey(keyId) {
|
|
||||||
return secretStorageKeys.get(keyId);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearSecretStorageKeys() {
|
|
||||||
secretStorageKeys.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSecretStorageKey({ keys }) {
|
|
||||||
const keyIds = Object.keys(keys);
|
|
||||||
const keyId = keyIds.find(hasPrivateKey);
|
|
||||||
if (!keyId) return undefined;
|
|
||||||
const privateKey = getPrivateKey(keyId);
|
|
||||||
return [keyId, privateKey];
|
|
||||||
}
|
|
||||||
|
|
||||||
function cacheSecretStorageKey(keyId, keyInfo, privateKey) {
|
|
||||||
secretStorageKeys.set(keyId, privateKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const cryptoCallbacks = {
|
|
||||||
getSecretStorageKey,
|
|
||||||
cacheSecretStorageKey,
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { clearSecretStorageKeys, cryptoCallbacks, storePrivateKey } from './secretStorageKeys';
|
||||||
|
|
||||||
|
const ask = (...ids: string[]) =>
|
||||||
|
cryptoCallbacks.getSecretStorageKey!(
|
||||||
|
{ keys: Object.fromEntries(ids.map((id) => [id, {} as never])) },
|
||||||
|
'm.cross_signing.master',
|
||||||
|
);
|
||||||
|
|
||||||
|
test('[#210] secret storage key cache: null when unknown, key when stored, copies input', async () => {
|
||||||
|
clearSecretStorageKeys();
|
||||||
|
assert.equal(await ask('k1'), null);
|
||||||
|
|
||||||
|
const key = new Uint8Array([1, 2, 3]);
|
||||||
|
storePrivateKey('k1', key);
|
||||||
|
key[0] = 9; // the cache keeps its own copy
|
||||||
|
assert.deepEqual(await ask('k0', 'k1'), ['k1', new Uint8Array([1, 2, 3])]);
|
||||||
|
|
||||||
|
cryptoCallbacks.cacheSecretStorageKey!('k2', {} as never, new Uint8Array([7]));
|
||||||
|
assert.deepEqual(await ask('k2'), ['k2', new Uint8Array([7])]);
|
||||||
|
|
||||||
|
clearSecretStorageKeys();
|
||||||
|
assert.equal(await ask('k1', 'k2'), null);
|
||||||
|
assert.throws(() => storePrivateKey('k3', 'nope' as never), /invalid/);
|
||||||
|
});
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type { CryptoCallbacks } from 'matrix-js-sdk/lib/crypto-api';
|
||||||
|
|
||||||
|
const secretStorageKeys = new Map<string, Uint8Array<ArrayBuffer>>();
|
||||||
|
|
||||||
|
export function storePrivateKey(keyId: string, privateKey: Uint8Array) {
|
||||||
|
if (privateKey instanceof Uint8Array === false) {
|
||||||
|
throw new Error('Unable to store, privateKey is invalid.');
|
||||||
|
}
|
||||||
|
// Our own copy, on a plain ArrayBuffer (what the SDK's callback type wants).
|
||||||
|
secretStorageKeys.set(keyId, new Uint8Array(privateKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearSecretStorageKeys() {
|
||||||
|
secretStorageKeys.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cryptoCallbacks: CryptoCallbacks = {
|
||||||
|
async getSecretStorageKey({ keys }) {
|
||||||
|
const keyId = Object.keys(keys).find((id) => secretStorageKeys.get(id) instanceof Uint8Array);
|
||||||
|
const privateKey = keyId ? secretStorageKeys.get(keyId) : undefined;
|
||||||
|
// The SDK expects null (not undefined) when no key is cached.
|
||||||
|
return keyId && privateKey ? [keyId, privateKey] : null;
|
||||||
|
},
|
||||||
|
cacheSecretStorageKey(keyId, keyInfo, privateKey) {
|
||||||
|
secretStorageKeys.set(keyId, privateKey);
|
||||||
|
},
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user