You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-01 04:43:29 +03:00
Merge pull request #1235 from matrix-org/foldleft/12299-local-ssk
Store USK and SSK locally
This commit is contained in:
@@ -78,7 +78,9 @@
|
|||||||
"eslint-plugin-babel": "^5.3.0",
|
"eslint-plugin-babel": "^5.3.0",
|
||||||
"eslint-plugin-jest": "^23.0.4",
|
"eslint-plugin-jest": "^23.0.4",
|
||||||
"exorcist": "^1.0.1",
|
"exorcist": "^1.0.1",
|
||||||
|
"fake-indexeddb": "^3.0.0",
|
||||||
"jest": "^24.9.0",
|
"jest": "^24.9.0",
|
||||||
|
"jest-localstorage-mock": "^2.4.0",
|
||||||
"jsdoc": "^3.5.5",
|
"jsdoc": "^3.5.5",
|
||||||
"matrix-mock-request": "^1.2.3",
|
"matrix-mock-request": "^1.2.3",
|
||||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
"olm": "https://packages.matrix.org/npm/olm/olm-3.1.4.tgz",
|
||||||
|
|||||||
246
spec/unit/crypto/CrossSigningInfo.spec.js
Normal file
246
spec/unit/crypto/CrossSigningInfo.spec.js
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2020 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 '../../olm-loader';
|
||||||
|
import {
|
||||||
|
CrossSigningInfo,
|
||||||
|
createCryptoStoreCacheCallbacks,
|
||||||
|
} from '../../../src/crypto/CrossSigning';
|
||||||
|
import {
|
||||||
|
IndexedDBCryptoStore,
|
||||||
|
} from '../../../src/crypto/store/indexeddb-crypto-store';
|
||||||
|
import {MemoryCryptoStore} from '../../../src/crypto/store/memory-crypto-store';
|
||||||
|
import 'fake-indexeddb/auto';
|
||||||
|
import 'jest-localstorage-mock';
|
||||||
|
|
||||||
|
const userId = "@alice:example.com";
|
||||||
|
|
||||||
|
// Private key for tests only
|
||||||
|
const testKey = new Uint8Array([
|
||||||
|
0xda, 0x5a, 0x27, 0x60, 0xe3, 0x3a, 0xc5, 0x82,
|
||||||
|
0x9d, 0x12, 0xc3, 0xbe, 0xe8, 0xaa, 0xc2, 0xef,
|
||||||
|
0xae, 0xb1, 0x05, 0xc1, 0xe7, 0x62, 0x78, 0xa6,
|
||||||
|
0xd7, 0x1f, 0xf8, 0x2c, 0x51, 0x85, 0xf0, 0x1d,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const types = [
|
||||||
|
{ type: "master", shouldCache: false },
|
||||||
|
{ type: "self_signing", shouldCache: true },
|
||||||
|
{ type: "user_signing", shouldCache: true },
|
||||||
|
{ type: "invalid", shouldCache: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
const badKey = Uint8Array.from(testKey);
|
||||||
|
badKey[0] ^= 1;
|
||||||
|
|
||||||
|
const masterKeyPub = "nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk";
|
||||||
|
|
||||||
|
describe("CrossSigningInfo.getCrossSigningKey", function() {
|
||||||
|
if (!global.Olm) {
|
||||||
|
console.warn('Not running megolm backup unit tests: libolm not present');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeAll(function() {
|
||||||
|
return global.Olm.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw if no callback is provided", async () => {
|
||||||
|
const info = new CrossSigningInfo(userId);
|
||||||
|
await expect(info.getCrossSigningKey("master")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("should throw if the callback returns falsey",
|
||||||
|
async ({type, shouldCache}) => {
|
||||||
|
const info = new CrossSigningInfo(userId, {
|
||||||
|
getCrossSigningKey: () => false,
|
||||||
|
});
|
||||||
|
await expect(info.getCrossSigningKey(type)).rejects.toThrow("falsey");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw if the expected key doesn't come back", async () => {
|
||||||
|
const info = new CrossSigningInfo(userId, {
|
||||||
|
getCrossSigningKey: () => masterKeyPub,
|
||||||
|
});
|
||||||
|
await expect(info.getCrossSigningKey("master", "")).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return a key from its callback", async () => {
|
||||||
|
const info = new CrossSigningInfo(userId, {
|
||||||
|
getCrossSigningKey: () => testKey,
|
||||||
|
});
|
||||||
|
const [pubKey, ab] = await info.getCrossSigningKey("master", masterKeyPub);
|
||||||
|
expect(pubKey).toEqual(masterKeyPub);
|
||||||
|
expect(ab).toEqual({a: 106712, b: 106712});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("should request a key from the cache callback (if set)" +
|
||||||
|
" and does not call app if one is found" +
|
||||||
|
" %o",
|
||||||
|
async ({ type, shouldCache }) => {
|
||||||
|
const getCrossSigningKey = jest.fn().mockImplementation(() => {
|
||||||
|
if (shouldCache) {
|
||||||
|
return Promise.reject(new Error("Regular callback called"));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(testKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
|
||||||
|
const info = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
{ getCrossSigningKey },
|
||||||
|
{ getCrossSigningKeyCache },
|
||||||
|
);
|
||||||
|
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
|
||||||
|
expect(pubKey).toEqual(masterKeyPub);
|
||||||
|
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
|
||||||
|
if (shouldCache) {
|
||||||
|
expect(getCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("should store a key with the cache callback (if set)",
|
||||||
|
async ({ type, shouldCache }) => {
|
||||||
|
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
|
||||||
|
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const info = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
{ getCrossSigningKey },
|
||||||
|
{ storeCrossSigningKeyCache },
|
||||||
|
);
|
||||||
|
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
|
||||||
|
expect(pubKey).toEqual(masterKeyPub);
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(shouldCache ? 1 : 0);
|
||||||
|
if (shouldCache) {
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls[0][0]).toBe(type);
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls[0][1]).toBe(testKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("does not store a bad key to the cache",
|
||||||
|
async ({ type, shouldCache }) => {
|
||||||
|
const getCrossSigningKey = jest.fn().mockResolvedValue(badKey);
|
||||||
|
const storeCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const info = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
{ getCrossSigningKey },
|
||||||
|
{ storeCrossSigningKeyCache },
|
||||||
|
);
|
||||||
|
await expect(info.getCrossSigningKey(type, masterKeyPub)).rejects.toThrow();
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls.length).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("does not store a value to the cache if it came from the cache",
|
||||||
|
async ({ type, shouldCache }) => {
|
||||||
|
const getCrossSigningKey = jest.fn().mockImplementation(() => {
|
||||||
|
if (shouldCache) {
|
||||||
|
return Promise.reject(new Error("Regular callback called"));
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(testKey);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(testKey);
|
||||||
|
const storeCrossSigningKeyCache = jest.fn().mockRejectedValue(
|
||||||
|
new Error("Tried to store a value from cache"),
|
||||||
|
);
|
||||||
|
const info = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
{ getCrossSigningKey },
|
||||||
|
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
|
||||||
|
);
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(0);
|
||||||
|
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
|
||||||
|
expect(pubKey).toEqual(masterKeyPub);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("requests a key from the cache callback (if set) and then calls app" +
|
||||||
|
" if one is not found", async ({ type, shouldCache }) => {
|
||||||
|
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
|
||||||
|
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(undefined);
|
||||||
|
const storeCrossSigningKeyCache = jest.fn();
|
||||||
|
const info = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
{ getCrossSigningKey },
|
||||||
|
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
|
||||||
|
);
|
||||||
|
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
|
||||||
|
expect(pubKey).toEqual(masterKeyPub);
|
||||||
|
expect(getCrossSigningKey.mock.calls.length).toBe(1);
|
||||||
|
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
|
||||||
|
|
||||||
|
/* Also expect that the cache gets updated */
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each(types)("requests a key from the cache callback (if set) and then" +
|
||||||
|
" calls app if that key doesn't match", async ({ type, shouldCache }) => {
|
||||||
|
const getCrossSigningKey = jest.fn().mockResolvedValue(testKey);
|
||||||
|
const getCrossSigningKeyCache = jest.fn().mockResolvedValue(badKey);
|
||||||
|
const storeCrossSigningKeyCache = jest.fn();
|
||||||
|
const info = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
{ getCrossSigningKey },
|
||||||
|
{ getCrossSigningKeyCache, storeCrossSigningKeyCache },
|
||||||
|
);
|
||||||
|
const [pubKey] = await info.getCrossSigningKey(type, masterKeyPub);
|
||||||
|
expect(pubKey).toEqual(masterKeyPub);
|
||||||
|
expect(getCrossSigningKey.mock.calls.length).toBe(1);
|
||||||
|
expect(getCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
|
||||||
|
|
||||||
|
/* Also expect that the cache gets updated */
|
||||||
|
expect(storeCrossSigningKeyCache.mock.calls.length).toBe(shouldCache ? 1 : 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note that MemoryStore is weird. It's only used for testing - as far as I can tell,
|
||||||
|
* it's not possible to get one in normal execution unless you hack as we do here.
|
||||||
|
*/
|
||||||
|
describe.each([
|
||||||
|
["IndexedDBCryptoStore",
|
||||||
|
() => new IndexedDBCryptoStore(global.indexedDB, "tests")],
|
||||||
|
["LocalStorageCryptoStore",
|
||||||
|
() => new IndexedDBCryptoStore(undefined, "tests")],
|
||||||
|
["MemoryCryptoStore", () => {
|
||||||
|
const store = new IndexedDBCryptoStore(undefined, "tests");
|
||||||
|
store._backendPromise = Promise.resolve(new MemoryCryptoStore());
|
||||||
|
return store;
|
||||||
|
}],
|
||||||
|
])("CrossSigning > createCryptoStoreCacheCallbacks [%s]", function(name, dbFactory) {
|
||||||
|
let store;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
store = dbFactory();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await store.deleteAllData();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should cache data to the store and retrieves it", async () => {
|
||||||
|
const { getCrossSigningKeyCache, storeCrossSigningKeyCache } =
|
||||||
|
createCryptoStoreCacheCallbacks(store);
|
||||||
|
await storeCrossSigningKeyCache("master", testKey);
|
||||||
|
|
||||||
|
// If we've not saved anything, don't expect anything
|
||||||
|
// Definitely don't accidentally return the wrong key for the type
|
||||||
|
const nokey = await getCrossSigningKeyCache("self", "");
|
||||||
|
expect(nokey).toBeNull();
|
||||||
|
|
||||||
|
const key = await getCrossSigningKeyCache("master", "");
|
||||||
|
expect(key).toEqual(testKey);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -120,7 +120,6 @@ async function distributeEvent(ownRequest, theirRequest, event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("verification request unit tests", function() {
|
describe("verification request unit tests", function() {
|
||||||
|
|
||||||
beforeAll(function() {
|
beforeAll(function() {
|
||||||
setupWebcrypto();
|
setupWebcrypto();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ limitations under the License.
|
|||||||
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
|
import {decodeBase64, encodeBase64, pkSign, pkVerify} from './olmlib';
|
||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import {logger} from '../logger';
|
import {logger} from '../logger';
|
||||||
|
import {IndexedDBCryptoStore} from '../crypto/store/indexeddb-crypto-store';
|
||||||
|
|
||||||
function publicKeyFromKeyInfo(keyInfo) {
|
function publicKeyFromKeyInfo(keyInfo) {
|
||||||
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
|
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
|
||||||
@@ -40,8 +41,9 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* @param {string} userId the user that the information is about
|
* @param {string} userId the user that the information is about
|
||||||
* @param {object} callbacks Callbacks used to interact with the app
|
* @param {object} callbacks Callbacks used to interact with the app
|
||||||
* Requires getCrossSigningKey and saveCrossSigningKeys
|
* Requires getCrossSigningKey and saveCrossSigningKeys
|
||||||
|
* @param {object} cacheCallbacks Callbacks used to interact with the cache
|
||||||
*/
|
*/
|
||||||
constructor(userId, callbacks) {
|
constructor(userId, callbacks, cacheCallbacks) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// you can't change the userId
|
// you can't change the userId
|
||||||
@@ -50,6 +52,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
value: userId,
|
value: userId,
|
||||||
});
|
});
|
||||||
this._callbacks = callbacks || {};
|
this._callbacks = callbacks || {};
|
||||||
|
this._cacheCallbacks = cacheCallbacks || {};
|
||||||
this.keys = {};
|
this.keys = {};
|
||||||
this.firstUse = true;
|
this.firstUse = true;
|
||||||
}
|
}
|
||||||
@@ -62,6 +65,8 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* @returns {Array} An array with [ public key, Olm.PkSigning ]
|
* @returns {Array} An array with [ public key, Olm.PkSigning ]
|
||||||
*/
|
*/
|
||||||
async getCrossSigningKey(type, expectedPubkey) {
|
async getCrossSigningKey(type, expectedPubkey) {
|
||||||
|
const shouldCache = ["self_signing", "user_signing"].indexOf(type) >= 0;
|
||||||
|
|
||||||
if (!this._callbacks.getCrossSigningKey) {
|
if (!this._callbacks.getCrossSigningKey) {
|
||||||
throw new Error("No getCrossSigningKey callback supplied");
|
throw new Error("No getCrossSigningKey callback supplied");
|
||||||
}
|
}
|
||||||
@@ -70,22 +75,47 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
expectedPubkey = this.getId(type);
|
expectedPubkey = this.getId(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
|
function validateKey(key) {
|
||||||
|
if (!key) return;
|
||||||
|
const signing = new global.Olm.PkSigning();
|
||||||
|
const gotPubkey = signing.init_with_seed(key);
|
||||||
|
if (gotPubkey === expectedPubkey) {
|
||||||
|
return [gotPubkey, signing];
|
||||||
|
}
|
||||||
|
signing.free();
|
||||||
|
}
|
||||||
|
|
||||||
|
let privkey;
|
||||||
|
if (this._cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
|
||||||
|
privkey = await this._cacheCallbacks
|
||||||
|
.getCrossSigningKeyCache(type, expectedPubkey);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheresult = validateKey(privkey);
|
||||||
|
if (cacheresult) {
|
||||||
|
return cacheresult;
|
||||||
|
}
|
||||||
|
|
||||||
|
privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
|
||||||
|
const result = validateKey(privkey);
|
||||||
|
if (result) {
|
||||||
|
if (this._cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
|
||||||
|
await this._cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* No keysource even returned a key */
|
||||||
if (!privkey) {
|
if (!privkey) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"getCrossSigningKey callback for " + type + " returned falsey",
|
"getCrossSigningKey callback for " + type + " returned falsey",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const signing = new global.Olm.PkSigning();
|
|
||||||
const gotPubkey = signing.init_with_seed(privkey);
|
/* We got some keys from the keysource, but none of them were valid */
|
||||||
if (gotPubkey !== expectedPubkey) {
|
throw new Error(
|
||||||
signing.free();
|
"Key type " + type + " from getCrossSigningKey callback did not match",
|
||||||
throw new Error(
|
);
|
||||||
"Key type " + type + " from getCrossSigningKey callback did not match",
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return [gotPubkey, signing];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromStorage(obj, userId) {
|
static fromStorage(obj, userId) {
|
||||||
@@ -539,3 +569,28 @@ export class DeviceTrustLevel {
|
|||||||
return this._tofu;
|
return this._tofu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function createCryptoStoreCacheCallbacks(store) {
|
||||||
|
return {
|
||||||
|
getCrossSigningKeyCache: function(type, _expectedPublicKey) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
return store.doTxn(
|
||||||
|
'readonly',
|
||||||
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
|
(txn) => {
|
||||||
|
store.getCrossSigningPrivateKey(txn, resolve, type);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
storeCrossSigningKeyCache: function(type, key) {
|
||||||
|
return store.doTxn(
|
||||||
|
'readwrite',
|
||||||
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
|
(txn) => {
|
||||||
|
store.storeCrossSigningPrivateKey(txn, type, key);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
CrossSigningLevel,
|
CrossSigningLevel,
|
||||||
DeviceTrustLevel,
|
DeviceTrustLevel,
|
||||||
UserTrustLevel,
|
UserTrustLevel,
|
||||||
|
createCryptoStoreCacheCallbacks,
|
||||||
} from './CrossSigning';
|
} from './CrossSigning';
|
||||||
import {SECRET_STORAGE_ALGORITHM_V1, SecretStorage} from './SecretStorage';
|
import {SECRET_STORAGE_ALGORITHM_V1, SecretStorage} from './SecretStorage';
|
||||||
import {OutgoingRoomKeyRequestManager} from './OutgoingRoomKeyRequestManager';
|
import {OutgoingRoomKeyRequestManager} from './OutgoingRoomKeyRequestManager';
|
||||||
@@ -220,8 +221,13 @@ export function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
this._inRoomVerificationRequests = new InRoomRequests();
|
this._inRoomVerificationRequests = new InRoomRequests();
|
||||||
|
|
||||||
const cryptoCallbacks = this._baseApis._cryptoCallbacks || {};
|
const cryptoCallbacks = this._baseApis._cryptoCallbacks || {};
|
||||||
|
const cacheCallbacks = createCryptoStoreCacheCallbacks(cryptoStore);
|
||||||
|
|
||||||
this._crossSigningInfo = new CrossSigningInfo(userId, cryptoCallbacks);
|
this._crossSigningInfo = new CrossSigningInfo(
|
||||||
|
userId,
|
||||||
|
cryptoCallbacks,
|
||||||
|
cacheCallbacks,
|
||||||
|
);
|
||||||
|
|
||||||
this._secretStorage = new SecretStorage(
|
this._secretStorage = new SecretStorage(
|
||||||
baseApis, cryptoCallbacks, this._crossSigningInfo,
|
baseApis, cryptoCallbacks, this._crossSigningInfo,
|
||||||
|
|||||||
@@ -341,11 +341,28 @@ export class Backend {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCrossSigningPrivateKey(txn, func, type) {
|
||||||
|
const objectStore = txn.objectStore("account");
|
||||||
|
const getReq = objectStore.get(`ssss_cache:${type}`);
|
||||||
|
getReq.onsuccess = function() {
|
||||||
|
try {
|
||||||
|
func(getReq.result || null);
|
||||||
|
} catch (e) {
|
||||||
|
abortWithException(txn, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
storeCrossSigningKeys(txn, keys) {
|
storeCrossSigningKeys(txn, keys) {
|
||||||
const objectStore = txn.objectStore("account");
|
const objectStore = txn.objectStore("account");
|
||||||
objectStore.put(keys, "crossSigningKeys");
|
objectStore.put(keys, "crossSigningKeys");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeCrossSigningPrivateKey(txn, type, key) {
|
||||||
|
const objectStore = txn.objectStore("account");
|
||||||
|
objectStore.put(key, `ssss_cache:${type}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
|
|
||||||
countEndToEndSessions(txn, func) {
|
countEndToEndSessions(txn, func) {
|
||||||
|
|||||||
@@ -308,6 +308,16 @@ export class IndexedDBCryptoStore {
|
|||||||
this._backend.getCrossSigningKeys(txn, func);
|
this._backend.getCrossSigningKeys(txn, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
* @param {function(string)} func Called with the private key
|
||||||
|
* @param {string} type A key type
|
||||||
|
*/
|
||||||
|
async getCrossSigningPrivateKey(txn, func, type) {
|
||||||
|
const backend = await this._connect();
|
||||||
|
return backend.getCrossSigningPrivateKey(txn, func, type);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write the cross-signing keys back to the store
|
* Write the cross-signing keys back to the store
|
||||||
*
|
*
|
||||||
@@ -318,6 +328,19 @@ export class IndexedDBCryptoStore {
|
|||||||
this._backend.storeCrossSigningKeys(txn, keys);
|
this._backend.storeCrossSigningKeys(txn, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the cross-signing private keys back to the store
|
||||||
|
*
|
||||||
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
* @param {string} type The type of cross-signing private key to store
|
||||||
|
* @param {string} key keys object as getCrossSigningKeys()
|
||||||
|
*/
|
||||||
|
storeCrossSigningPrivateKey(txn, type, key) {
|
||||||
|
this._backendPromise.then(backend => {
|
||||||
|
backend.storeCrossSigningPrivateKey(txn, type, key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Olm sessions
|
// Olm sessions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -367,12 +367,23 @@ export class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
func(keys);
|
func(keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCrossSigningPrivateKey(txn, func, type) {
|
||||||
|
const key = getJsonItem(this.store, E2E_PREFIX + `ssss_cache.${type}`);
|
||||||
|
func(key ? Uint8Array.from(key) : key);
|
||||||
|
}
|
||||||
|
|
||||||
storeCrossSigningKeys(txn, keys) {
|
storeCrossSigningKeys(txn, keys) {
|
||||||
setJsonItem(
|
setJsonItem(
|
||||||
this.store, KEY_CROSS_SIGNING_KEYS, keys,
|
this.store, KEY_CROSS_SIGNING_KEYS, keys,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeCrossSigningPrivateKey(txn, type, key) {
|
||||||
|
setJsonItem(
|
||||||
|
this.store, E2E_PREFIX + `ssss_cache.${type}`, Array.from(key),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
doTxn(mode, stores, func) {
|
doTxn(mode, stores, func) {
|
||||||
return Promise.resolve(func(null));
|
return Promise.resolve(func(null));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export class MemoryCryptoStore {
|
|||||||
this._outgoingRoomKeyRequests = [];
|
this._outgoingRoomKeyRequests = [];
|
||||||
this._account = null;
|
this._account = null;
|
||||||
this._crossSigningKeys = null;
|
this._crossSigningKeys = null;
|
||||||
|
this._privateKeys = {};
|
||||||
|
|
||||||
// Map of {devicekey -> {sessionId -> session pickle}}
|
// Map of {devicekey -> {sessionId -> session pickle}}
|
||||||
this._sessions = {};
|
this._sessions = {};
|
||||||
@@ -255,10 +256,19 @@ export class MemoryCryptoStore {
|
|||||||
func(this._crossSigningKeys);
|
func(this._crossSigningKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCrossSigningPrivateKey(txn, func, type) {
|
||||||
|
const result = this._privateKeys[type];
|
||||||
|
return func(result || null);
|
||||||
|
}
|
||||||
|
|
||||||
storeCrossSigningKeys(txn, keys) {
|
storeCrossSigningKeys(txn, keys) {
|
||||||
this._crossSigningKeys = keys;
|
this._crossSigningKeys = keys;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storeCrossSigningPrivateKey(txn, type, key) {
|
||||||
|
this._privateKeys[type] = key;
|
||||||
|
}
|
||||||
|
|
||||||
// Olm Sessions
|
// Olm Sessions
|
||||||
|
|
||||||
countEndToEndSessions(txn, func) {
|
countEndToEndSessions(txn, func) {
|
||||||
|
|||||||
70
yarn.lock
70
yarn.lock
@@ -1463,6 +1463,11 @@ base-x@^3.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
safe-buffer "^5.0.1"
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
base64-arraybuffer-es6@0.5.0:
|
||||||
|
version "0.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/base64-arraybuffer-es6/-/base64-arraybuffer-es6-0.5.0.tgz#27877d01148bcfb3919c17ecf64ea163d9bdba62"
|
||||||
|
integrity sha512-UCIPaDJrNNj5jG2ZL+nzJ7czvZV/ZYX6LaIRgfVU1k1edJOQg7dkbiSKzwHkNp6aHEHER/PhlFBrMYnlvJJQEw==
|
||||||
|
|
||||||
base64-js@^1.0.2:
|
base64-js@^1.0.2:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
|
||||||
@@ -2059,7 +2064,7 @@ core-js-compat@^3.6.2:
|
|||||||
browserslist "^4.8.3"
|
browserslist "^4.8.3"
|
||||||
semver "7.0.0"
|
semver "7.0.0"
|
||||||
|
|
||||||
core-js@^2.4.0, core-js@^2.6.5:
|
core-js@^2.4.0, core-js@^2.5.3, core-js@^2.6.5:
|
||||||
version "2.6.11"
|
version "2.6.11"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c"
|
||||||
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
|
integrity sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==
|
||||||
@@ -2728,6 +2733,19 @@ fast-deep-equal@^3.1.1:
|
|||||||
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4"
|
||||||
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
|
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
|
||||||
|
|
||||||
|
fake-indexeddb@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fake-indexeddb/-/fake-indexeddb-3.0.0.tgz#1bd0ffce41b0f433409df301d334d8fd7d77da27"
|
||||||
|
integrity sha512-VrnV9dJWlVWvd8hp9MMR+JS4RLC4ZmToSkuCg91ZwpYE5mSODb3n5VEaV62Hf3AusnbrPfwQhukU+rGZm5W8PQ==
|
||||||
|
dependencies:
|
||||||
|
realistic-structured-clone "^2.0.1"
|
||||||
|
setimmediate "^1.0.5"
|
||||||
|
|
||||||
|
fast-deep-equal@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||||
|
integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=
|
||||||
|
|
||||||
fast-json-stable-stringify@^2.0.0:
|
fast-json-stable-stringify@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||||
@@ -3725,6 +3743,11 @@ jest-leak-detector@^24.9.0:
|
|||||||
jest-get-type "^24.9.0"
|
jest-get-type "^24.9.0"
|
||||||
pretty-format "^24.9.0"
|
pretty-format "^24.9.0"
|
||||||
|
|
||||||
|
jest-localstorage-mock@^2.4.0:
|
||||||
|
version "2.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.4.0.tgz#c6073810735dd3af74020ea6c3885ec1cc6d0d13"
|
||||||
|
integrity sha512-/mC1JxnMeuIlAaQBsDMilskC/x/BicsQ/BXQxEOw+5b1aGZkkOAqAF3nu8yq449CpzGtp5jJ5wCmDNxLgA2m6A==
|
||||||
|
|
||||||
jest-matcher-utils@^24.9.0:
|
jest-matcher-utils@^24.9.0:
|
||||||
version "24.9.0"
|
version "24.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
|
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz#f5b3661d5e628dffe6dd65251dfdae0e87c3a073"
|
||||||
@@ -5283,6 +5306,16 @@ readdirp@^2.2.1:
|
|||||||
micromatch "^3.1.10"
|
micromatch "^3.1.10"
|
||||||
readable-stream "^2.0.2"
|
readable-stream "^2.0.2"
|
||||||
|
|
||||||
|
realistic-structured-clone@^2.0.1:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/realistic-structured-clone/-/realistic-structured-clone-2.0.2.tgz#2f8ec225b1f9af20efc79ac96a09043704414959"
|
||||||
|
integrity sha512-5IEvyfuMJ4tjQOuKKTFNvd+H9GSbE87IcendSBannE28PTrbolgaVg5DdEApRKhtze794iXqVUFKV60GLCNKEg==
|
||||||
|
dependencies:
|
||||||
|
core-js "^2.5.3"
|
||||||
|
domexception "^1.0.1"
|
||||||
|
typeson "^5.8.2"
|
||||||
|
typeson-registry "^1.0.0-alpha.20"
|
||||||
|
|
||||||
realpath-native@^1.1.0:
|
realpath-native@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
|
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
|
||||||
@@ -5614,6 +5647,11 @@ set-value@^2.0.0, set-value@^2.0.1:
|
|||||||
is-plain-object "^2.0.3"
|
is-plain-object "^2.0.3"
|
||||||
split-string "^3.0.1"
|
split-string "^3.0.1"
|
||||||
|
|
||||||
|
setimmediate@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
|
||||||
|
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
|
||||||
|
|
||||||
sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4:
|
sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4:
|
||||||
version "2.4.11"
|
version "2.4.11"
|
||||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||||
@@ -6255,6 +6293,20 @@ typescript@^3.2.2, typescript@^3.7.3:
|
|||||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.2.tgz#91d6868aaead7da74f493c553aeff76c0c0b1d5a"
|
||||||
integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==
|
integrity sha512-EgOVgL/4xfVrCMbhYKUQTdF37SQn4Iw73H5BgCrF1Abdun7Kwy/QZsE/ssAy0y4LxBbvua3PIbFsbRczWWnDdQ==
|
||||||
|
|
||||||
|
typeson-registry@^1.0.0-alpha.20:
|
||||||
|
version "1.0.0-alpha.35"
|
||||||
|
resolved "https://registry.yarnpkg.com/typeson-registry/-/typeson-registry-1.0.0-alpha.35.tgz#b86abfe440e6ee69102eebb0e8c5a916dd182ff9"
|
||||||
|
integrity sha512-a/NffrpFswBTyU6w2d6vjk62K1TZ45H64af9AfRbn7LXqNEfL+h+gw3OV2IaG+enfwqgLB5WmbkrNBaQuc/97A==
|
||||||
|
dependencies:
|
||||||
|
base64-arraybuffer-es6 "0.5.0"
|
||||||
|
typeson "5.18.2"
|
||||||
|
whatwg-url "7.1.0"
|
||||||
|
|
||||||
|
typeson@5.18.2, typeson@^5.8.2:
|
||||||
|
version "5.18.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/typeson/-/typeson-5.18.2.tgz#0d217fc0e11184a66aa7ca0076d9aa7707eb7bc2"
|
||||||
|
integrity sha512-Vetd+OGX05P4qHyHiSLdHZ5Z5GuQDrHHwSdjkqho9NSCYVSLSfRMjklD/unpHH8tXBR9Z/R05rwJSuMpMFrdsw==
|
||||||
|
|
||||||
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
uc.micro@^1.0.1, uc.micro@^1.0.5:
|
||||||
version "1.0.6"
|
version "1.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac"
|
||||||
@@ -6495,19 +6547,19 @@ whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
|
||||||
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
|
||||||
|
|
||||||
whatwg-url@^6.4.1:
|
whatwg-url@7.1.0, whatwg-url@^7.0.0:
|
||||||
version "6.5.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
||||||
integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
|
integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.sortby "^4.7.0"
|
lodash.sortby "^4.7.0"
|
||||||
tr46 "^1.0.1"
|
tr46 "^1.0.1"
|
||||||
webidl-conversions "^4.0.2"
|
webidl-conversions "^4.0.2"
|
||||||
|
|
||||||
whatwg-url@^7.0.0:
|
whatwg-url@^6.4.1:
|
||||||
version "7.1.0"
|
version "6.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
|
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.5.0.tgz#f2df02bff176fd65070df74ad5ccbb5a199965a8"
|
||||||
integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
|
integrity sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
lodash.sortby "^4.7.0"
|
lodash.sortby "^4.7.0"
|
||||||
tr46 "^1.0.1"
|
tr46 "^1.0.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user