From 7218e31a9c252dafc5a8812ab66d45608fa754e7 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 13 Nov 2019 17:52:24 +0000 Subject: [PATCH] Sign & verify SSSS keys --- spec/unit/crypto/secrets.spec.js | 36 ++++++++++++++++++++++++++++---- src/crypto/CrossSigning.js | 27 +++++++++++++++++------- src/crypto/Secrets.js | 25 +++++++++++++++++----- src/crypto/index.js | 2 +- 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/spec/unit/crypto/secrets.spec.js b/spec/unit/crypto/secrets.spec.js index fcf8f605e..0fa1aa56e 100644 --- a/spec/unit/crypto/secrets.spec.js +++ b/spec/unit/crypto/secrets.spec.js @@ -49,6 +49,18 @@ describe("Secrets", function() { const pubkey = decryption.generate_key(); const privkey = decryption.get_private_key(); + const signing = new global.Olm.PkSigning(); + const signingKey = signing.generate_seed(); + const signingPubKey = signing.init_with_seed(signingKey); + + const signingkeyInfo = { + user_id: "@alice:example.com", + usage: ['master'], + keys: { + ['ed25519:' + signingPubKey]: signingPubKey, + }, + }; + const getKey = expect.createSpy().andCall(e => { expect(Object.keys(e.keys)).toEqual(["abc"]); return ['abc', privkey]; @@ -58,10 +70,15 @@ describe("Secrets", function() { {userId: "@alice:example.com", deviceId: "Osborne2"}, { cryptoCallbacks: { + getCrossSigningKey: t => signingKey, getSecretStorageKey: getKey, }, }, ); + alice._crypto._crossSigningInfo.setKeys({ + master: signingkeyInfo, + }); + const secretStorage = alice._crypto._secretStorage; alice.setAccountData = async function(eventType, contents, callback) { @@ -76,13 +93,16 @@ describe("Secrets", function() { } }; + const keyAccountData = { + algorithm: "m.secret_storage.v1.curve25519-aes-sha2", + pubkey: pubkey, + }; + await alice._crypto._crossSigningInfo.signObject(keyAccountData, 'master'); + alice.store.storeAccountDataEvents([ new MatrixEvent({ type: "m.secret_storage.key.abc", - content: { - algorithm: "m.secret_storage.v1.curve25519-aes-sha2", - pubkey: pubkey, - }, + content: keyAccountData, }), ]); @@ -123,8 +143,15 @@ describe("Secrets", function() { }); it("should encrypt with default key if keys is null", async function() { + let keys = {}; const alice = await makeTestClient( {userId: "@alice:example.com", deviceId: "Osborne2"}, + { + cryptoCallbacks: { + getCrossSigningKey: t => keys[t], + saveCrossSigningKeys: k => keys = k, + }, + }, ); alice.setAccountData = async function(eventType, contents, callback) { alice.store.storeAccountDataEvents([ @@ -134,6 +161,7 @@ describe("Secrets", function() { }), ]); }; + alice.resetCrossSigningKeys(); const newKeyId = await alice.addSecretKey( 'm.secret_storage.v1.curve25519-aes-sha2', diff --git a/src/crypto/CrossSigning.js b/src/crypto/CrossSigning.js index 880931d00..cd0d2930f 100644 --- a/src/crypto/CrossSigning.js +++ b/src/crypto/CrossSigning.js @@ -23,7 +23,7 @@ import {pkSign, pkVerify} from './olmlib'; import {EventEmitter} from 'events'; import logger from '../logger'; -function getPublicKey(keyInfo) { +function publicKeyFromKeyInfo(keyInfo) { return Object.entries(keyInfo.keys)[0]; } @@ -50,6 +50,14 @@ export class CrossSigningInfo extends EventEmitter { this.firstUse = true; } + getPublicKey(type) { + if (!this.keys[type]) { + throw new Error("No " + type + " key present"); + } + const keyInfo = this.keys[type]; + return publicKeyFromKeyInfo(keyInfo)[1]; + } + /** * Calls the app callback to ask for a private key * @param {string} type The key type ("master", "self_signing", or "user_signing") @@ -62,7 +70,7 @@ export class CrossSigningInfo extends EventEmitter { } if (expectedPubkey === undefined) { - expectedPubkey = getPublicKey(this.keys[type])[1]; + expectedPubkey = this.getPublicKey(type); } const privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey); @@ -109,7 +117,7 @@ export class CrossSigningInfo extends EventEmitter { */ getId(type) { type = type || "master"; - return this.keys[type] && getPublicKey(this.keys[type])[1]; + return this.keys[type] && this.getPublicKey(type); } async resetKeys(level) { @@ -201,7 +209,7 @@ export class CrossSigningInfo extends EventEmitter { if (!this.keys.master) { // this is the first key we've seen, so first-use is true this.firstUse = true; - } else if (getPublicKey(keys.master)[1] !== this.getId()) { + } else if (publicKeyFromKeyInfo(keys.master)[1] !== this.getId()) { // this is a different key, so first-use is false this.firstUse = false; } // otherwise, same key, so no change @@ -211,7 +219,7 @@ export class CrossSigningInfo extends EventEmitter { } else { throw new Error("Tried to set cross-signing keys without a master key"); } - const masterKey = getPublicKey(signingKeys.master)[1]; + const masterKey = publicKeyFromKeyInfo(signingKeys.master)[1]; // verify signatures if (keys.user_signing) { @@ -262,6 +270,11 @@ export class CrossSigningInfo extends EventEmitter { } async signObject(data, type) { + if (!this.keys[type]) { + throw new Error( + "Attempted to sign with " + type + " key but no such key present", + ); + } const [pubkey, signing] = await this.getCrossSigningKey(type); try { pkSign(data, signing, this.userId, pubkey); @@ -316,7 +329,7 @@ export class CrossSigningInfo extends EventEmitter { let userTrusted; const userMaster = userCrossSigning.keys.master; - const uskId = getPublicKey(this.keys.user_signing)[1]; + const uskId = this.getPublicKey('user_signing'); try { pkVerify(userMaster, uskId, this.userId); userTrusted = true; @@ -339,7 +352,7 @@ export class CrossSigningInfo extends EventEmitter { const deviceObj = deviceToObject(device, userCrossSigning.userId); try { pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId); - pkVerify(deviceObj, getPublicKey(userSSK)[1], userCrossSigning.userId); + pkVerify(deviceObj, publicKeyFromKeyInfo(userSSK)[1], userCrossSigning.userId); return userTrust; } catch (e) { return 0; diff --git a/src/crypto/Secrets.js b/src/crypto/Secrets.js index cadaa3c85..e6fafa1d5 100644 --- a/src/crypto/Secrets.js +++ b/src/crypto/Secrets.js @@ -20,18 +20,20 @@ import olmlib from './olmlib'; import { randomString } from '../randomstring'; import { keyForNewBackup } from './backup_password'; import { encodeRecoveryKey } from './recoverykey'; +import { pkVerify } from './olmlib'; /** * Implements secret storage and sharing (MSC-1946) * @module crypto/Secrets */ export default class SecretStorage extends EventEmitter { - constructor(baseApis, cryptoCallbacks) { + constructor(baseApis, cryptoCallbacks, crossSigningInfo) { super(); this._baseApis = baseApis; + this._cryptoCallbacks = cryptoCallbacks; + this._crossSigningInfo = crossSigningInfo; this._requests = {}; this._incomingRequests = {}; - this._cryptoCallbacks = cryptoCallbacks; } getDefaultKeyId() { @@ -119,7 +121,7 @@ export default class SecretStorage extends EventEmitter { } while (this._baseApis.getAccountData(`m.secret_storage.key.${keyID}`)); } - // FIXME: sign keyData? + await this._crossSigningInfo.signObject(keyData, 'master'); await this._baseApis.setAccountData( `m.secret_storage.key.${keyID}`, keyData, @@ -162,7 +164,14 @@ export default class SecretStorage extends EventEmitter { throw new Error("Unknown key: " +keyName); } const keyInfoContent = keyInfo.getContent(); - // FIXME: check signature of key info + + // check signature of key info + pkVerify( + keyInfoContent, + this._crossSigningInfo.getPublicKey('master'), + this._crossSigningInfo.userId, + ); + // encrypt secret, based on the algorithm switch (keyInfoContent.algorithm) { case "m.secret_storage.v1.curve25519-aes-sha2": @@ -261,6 +270,8 @@ export default class SecretStorage extends EventEmitter { return false; } + if (checkKey === undefined) checkKey = true; + const secretContent = secretInfo.getContent(); if (!secretContent.encrypted) { @@ -276,7 +287,11 @@ export default class SecretStorage extends EventEmitter { ).getContent(); const encInfo = secretContent.encrypted[keyName]; if (checkKey) { - // FIXME: check signature on key + pkVerify( + keyInfo, + this._crossSigningInfo.getPublicKey('master'), + this._crossSigningInfo.userId, + ); } switch (keyInfo.algorithm) { case "m.secret_storage.v1.curve25519-aes-sha2": diff --git a/src/crypto/index.js b/src/crypto/index.js index d90a5b9cb..03848553f 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -208,7 +208,7 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId, ); this._secretStorage = new SecretStorage( - baseApis, this._baseApis._cryptoCallbacks, + baseApis, this._baseApis._cryptoCallbacks, this._crossSigningInfo, ); } utils.inherits(Crypto, EventEmitter);