You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-28 05:03:59 +03:00
Sign & verify SSSS keys
This commit is contained in:
@@ -49,6 +49,18 @@ describe("Secrets", function() {
|
|||||||
const pubkey = decryption.generate_key();
|
const pubkey = decryption.generate_key();
|
||||||
const privkey = decryption.get_private_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 => {
|
const getKey = expect.createSpy().andCall(e => {
|
||||||
expect(Object.keys(e.keys)).toEqual(["abc"]);
|
expect(Object.keys(e.keys)).toEqual(["abc"]);
|
||||||
return ['abc', privkey];
|
return ['abc', privkey];
|
||||||
@@ -58,10 +70,15 @@ describe("Secrets", function() {
|
|||||||
{userId: "@alice:example.com", deviceId: "Osborne2"},
|
{userId: "@alice:example.com", deviceId: "Osborne2"},
|
||||||
{
|
{
|
||||||
cryptoCallbacks: {
|
cryptoCallbacks: {
|
||||||
|
getCrossSigningKey: t => signingKey,
|
||||||
getSecretStorageKey: getKey,
|
getSecretStorageKey: getKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
alice._crypto._crossSigningInfo.setKeys({
|
||||||
|
master: signingkeyInfo,
|
||||||
|
});
|
||||||
|
|
||||||
const secretStorage = alice._crypto._secretStorage;
|
const secretStorage = alice._crypto._secretStorage;
|
||||||
|
|
||||||
alice.setAccountData = async function(eventType, contents, callback) {
|
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([
|
alice.store.storeAccountDataEvents([
|
||||||
new MatrixEvent({
|
new MatrixEvent({
|
||||||
type: "m.secret_storage.key.abc",
|
type: "m.secret_storage.key.abc",
|
||||||
content: {
|
content: keyAccountData,
|
||||||
algorithm: "m.secret_storage.v1.curve25519-aes-sha2",
|
|
||||||
pubkey: pubkey,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -123,8 +143,15 @@ describe("Secrets", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should encrypt with default key if keys is null", async function() {
|
it("should encrypt with default key if keys is null", async function() {
|
||||||
|
let keys = {};
|
||||||
const alice = await makeTestClient(
|
const alice = await makeTestClient(
|
||||||
{userId: "@alice:example.com", deviceId: "Osborne2"},
|
{userId: "@alice:example.com", deviceId: "Osborne2"},
|
||||||
|
{
|
||||||
|
cryptoCallbacks: {
|
||||||
|
getCrossSigningKey: t => keys[t],
|
||||||
|
saveCrossSigningKeys: k => keys = k,
|
||||||
|
},
|
||||||
|
},
|
||||||
);
|
);
|
||||||
alice.setAccountData = async function(eventType, contents, callback) {
|
alice.setAccountData = async function(eventType, contents, callback) {
|
||||||
alice.store.storeAccountDataEvents([
|
alice.store.storeAccountDataEvents([
|
||||||
@@ -134,6 +161,7 @@ describe("Secrets", function() {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
alice.resetCrossSigningKeys();
|
||||||
|
|
||||||
const newKeyId = await alice.addSecretKey(
|
const newKeyId = await alice.addSecretKey(
|
||||||
'm.secret_storage.v1.curve25519-aes-sha2',
|
'm.secret_storage.v1.curve25519-aes-sha2',
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {pkSign, pkVerify} from './olmlib';
|
|||||||
import {EventEmitter} from 'events';
|
import {EventEmitter} from 'events';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
function getPublicKey(keyInfo) {
|
function publicKeyFromKeyInfo(keyInfo) {
|
||||||
return Object.entries(keyInfo.keys)[0];
|
return Object.entries(keyInfo.keys)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +50,14 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
this.firstUse = true;
|
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
|
* Calls the app callback to ask for a private key
|
||||||
* @param {string} type The key type ("master", "self_signing", or "user_signing")
|
* @param {string} type The key type ("master", "self_signing", or "user_signing")
|
||||||
@@ -62,7 +70,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (expectedPubkey === undefined) {
|
if (expectedPubkey === undefined) {
|
||||||
expectedPubkey = getPublicKey(this.keys[type])[1];
|
expectedPubkey = this.getPublicKey(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
const privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
|
const privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
|
||||||
@@ -109,7 +117,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
getId(type) {
|
getId(type) {
|
||||||
type = type || "master";
|
type = type || "master";
|
||||||
return this.keys[type] && getPublicKey(this.keys[type])[1];
|
return this.keys[type] && this.getPublicKey(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetKeys(level) {
|
async resetKeys(level) {
|
||||||
@@ -201,7 +209,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
if (!this.keys.master) {
|
if (!this.keys.master) {
|
||||||
// this is the first key we've seen, so first-use is true
|
// this is the first key we've seen, so first-use is true
|
||||||
this.firstUse = 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 is a different key, so first-use is false
|
||||||
this.firstUse = false;
|
this.firstUse = false;
|
||||||
} // otherwise, same key, so no change
|
} // otherwise, same key, so no change
|
||||||
@@ -211,7 +219,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
} else {
|
} else {
|
||||||
throw new Error("Tried to set cross-signing keys without a master key");
|
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
|
// verify signatures
|
||||||
if (keys.user_signing) {
|
if (keys.user_signing) {
|
||||||
@@ -262,6 +270,11 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async signObject(data, type) {
|
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);
|
const [pubkey, signing] = await this.getCrossSigningKey(type);
|
||||||
try {
|
try {
|
||||||
pkSign(data, signing, this.userId, pubkey);
|
pkSign(data, signing, this.userId, pubkey);
|
||||||
@@ -316,7 +329,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
|
|
||||||
let userTrusted;
|
let userTrusted;
|
||||||
const userMaster = userCrossSigning.keys.master;
|
const userMaster = userCrossSigning.keys.master;
|
||||||
const uskId = getPublicKey(this.keys.user_signing)[1];
|
const uskId = this.getPublicKey('user_signing');
|
||||||
try {
|
try {
|
||||||
pkVerify(userMaster, uskId, this.userId);
|
pkVerify(userMaster, uskId, this.userId);
|
||||||
userTrusted = true;
|
userTrusted = true;
|
||||||
@@ -339,7 +352,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
const deviceObj = deviceToObject(device, userCrossSigning.userId);
|
const deviceObj = deviceToObject(device, userCrossSigning.userId);
|
||||||
try {
|
try {
|
||||||
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
||||||
pkVerify(deviceObj, getPublicKey(userSSK)[1], userCrossSigning.userId);
|
pkVerify(deviceObj, publicKeyFromKeyInfo(userSSK)[1], userCrossSigning.userId);
|
||||||
return userTrust;
|
return userTrust;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
@@ -20,18 +20,20 @@ import olmlib from './olmlib';
|
|||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
import { keyForNewBackup } from './backup_password';
|
import { keyForNewBackup } from './backup_password';
|
||||||
import { encodeRecoveryKey } from './recoverykey';
|
import { encodeRecoveryKey } from './recoverykey';
|
||||||
|
import { pkVerify } from './olmlib';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements secret storage and sharing (MSC-1946)
|
* Implements secret storage and sharing (MSC-1946)
|
||||||
* @module crypto/Secrets
|
* @module crypto/Secrets
|
||||||
*/
|
*/
|
||||||
export default class SecretStorage extends EventEmitter {
|
export default class SecretStorage extends EventEmitter {
|
||||||
constructor(baseApis, cryptoCallbacks) {
|
constructor(baseApis, cryptoCallbacks, crossSigningInfo) {
|
||||||
super();
|
super();
|
||||||
this._baseApis = baseApis;
|
this._baseApis = baseApis;
|
||||||
|
this._cryptoCallbacks = cryptoCallbacks;
|
||||||
|
this._crossSigningInfo = crossSigningInfo;
|
||||||
this._requests = {};
|
this._requests = {};
|
||||||
this._incomingRequests = {};
|
this._incomingRequests = {};
|
||||||
this._cryptoCallbacks = cryptoCallbacks;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultKeyId() {
|
getDefaultKeyId() {
|
||||||
@@ -119,7 +121,7 @@ export default class SecretStorage extends EventEmitter {
|
|||||||
} while (this._baseApis.getAccountData(`m.secret_storage.key.${keyID}`));
|
} while (this._baseApis.getAccountData(`m.secret_storage.key.${keyID}`));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: sign keyData?
|
await this._crossSigningInfo.signObject(keyData, 'master');
|
||||||
|
|
||||||
await this._baseApis.setAccountData(
|
await this._baseApis.setAccountData(
|
||||||
`m.secret_storage.key.${keyID}`, keyData,
|
`m.secret_storage.key.${keyID}`, keyData,
|
||||||
@@ -162,7 +164,14 @@ export default class SecretStorage extends EventEmitter {
|
|||||||
throw new Error("Unknown key: " +keyName);
|
throw new Error("Unknown key: " +keyName);
|
||||||
}
|
}
|
||||||
const keyInfoContent = keyInfo.getContent();
|
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
|
// encrypt secret, based on the algorithm
|
||||||
switch (keyInfoContent.algorithm) {
|
switch (keyInfoContent.algorithm) {
|
||||||
case "m.secret_storage.v1.curve25519-aes-sha2":
|
case "m.secret_storage.v1.curve25519-aes-sha2":
|
||||||
@@ -261,6 +270,8 @@ export default class SecretStorage extends EventEmitter {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkKey === undefined) checkKey = true;
|
||||||
|
|
||||||
const secretContent = secretInfo.getContent();
|
const secretContent = secretInfo.getContent();
|
||||||
|
|
||||||
if (!secretContent.encrypted) {
|
if (!secretContent.encrypted) {
|
||||||
@@ -276,7 +287,11 @@ export default class SecretStorage extends EventEmitter {
|
|||||||
).getContent();
|
).getContent();
|
||||||
const encInfo = secretContent.encrypted[keyName];
|
const encInfo = secretContent.encrypted[keyName];
|
||||||
if (checkKey) {
|
if (checkKey) {
|
||||||
// FIXME: check signature on key
|
pkVerify(
|
||||||
|
keyInfo,
|
||||||
|
this._crossSigningInfo.getPublicKey('master'),
|
||||||
|
this._crossSigningInfo.userId,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
switch (keyInfo.algorithm) {
|
switch (keyInfo.algorithm) {
|
||||||
case "m.secret_storage.v1.curve25519-aes-sha2":
|
case "m.secret_storage.v1.curve25519-aes-sha2":
|
||||||
|
|||||||
@@ -208,7 +208,7 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
);
|
);
|
||||||
|
|
||||||
this._secretStorage = new SecretStorage(
|
this._secretStorage = new SecretStorage(
|
||||||
baseApis, this._baseApis._cryptoCallbacks,
|
baseApis, this._baseApis._cryptoCallbacks, this._crossSigningInfo,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
utils.inherits(Crypto, EventEmitter);
|
utils.inherits(Crypto, EventEmitter);
|
||||||
|
|||||||
Reference in New Issue
Block a user