You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
add methods for signing and checking users and devices with cross-signing
This commit is contained in:
@@ -762,6 +762,29 @@ MatrixClient.prototype.getGlobalBlacklistUnverifiedDevices = function() {
|
|||||||
return this._crypto.getGlobalBlacklistUnverifiedDevices();
|
return this._crypto.getGlobalBlacklistUnverifiedDevices();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a function that just calls the corresponding function from this._crypto.
|
||||||
|
*
|
||||||
|
* @param {string} name the function to call
|
||||||
|
*
|
||||||
|
* @return {Function} a wrapper function
|
||||||
|
*/
|
||||||
|
function wrapCryptoFunc(name) {
|
||||||
|
return function(...args) {
|
||||||
|
if (!this._crypto) { // eslint-disable-line no-invalid-this
|
||||||
|
throw new Error("End-to-end encryption disabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._crypto[name](...args); // eslint-disable-line no-invalid-this
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
MatrixClient.prototype.checkUserTrust
|
||||||
|
= wrapCryptoFunc("checkUserTrust");
|
||||||
|
|
||||||
|
MatrixClient.prototype.checkDeviceTrust
|
||||||
|
= wrapCryptoFunc("checkDeviceTrust");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get e2e information on the device that sent an event
|
* Get e2e information on the device that sent an event
|
||||||
*
|
*
|
||||||
@@ -793,6 +816,12 @@ MatrixClient.prototype.isEventSenderVerified = async function(event) {
|
|||||||
return device.isVerified();
|
return device.isVerified();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MatrixClient.prototype.resetCrossSigningKeys
|
||||||
|
= wrapCryptoFunc("resetCrossSigningKeys");
|
||||||
|
|
||||||
|
MatrixClient.prototype.setCrossSigningKeys
|
||||||
|
= wrapCryptoFunc("setCrossSigningKeys");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel a room key request for this event if one is ongoing and resend the
|
* Cancel a room key request for this event if one is ongoing and resend the
|
||||||
* request.
|
* request.
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import {EventEmitter} from 'events';
|
|||||||
|
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import DeviceInfo from './deviceinfo';
|
import DeviceInfo from './deviceinfo';
|
||||||
import SskInfo from './sskinfo';
|
import {CrossSigningInfo, CrossSigningVerification} from './CrossSigning';
|
||||||
import olmlib from './olmlib';
|
import olmlib from './olmlib';
|
||||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ export default class DeviceList extends EventEmitter {
|
|||||||
// userId -> {
|
// userId -> {
|
||||||
// [key info]
|
// [key info]
|
||||||
// }
|
// }
|
||||||
this._ssks = {};
|
this._crossSigningInfo = {};
|
||||||
|
|
||||||
// map of identity keys to the user who owns it
|
// map of identity keys to the user who owns it
|
||||||
this._userByIdentityKey = {};
|
this._userByIdentityKey = {};
|
||||||
@@ -345,18 +345,18 @@ export default class DeviceList extends EventEmitter {
|
|||||||
return this._devices[userId];
|
return this._devices[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
getRawStoredSskForUser(userId) {
|
getRawStoredCrossSigningForUser(userId) {
|
||||||
return this._ssks[userId];
|
return this._crossSigningInfo[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
getStoredSskForUser(userId) {
|
getStoredCrossSigningForUser(userId) {
|
||||||
if (!this._ssks[userId]) return null;
|
if (!this._crossSigningInfo[userId]) return null;
|
||||||
|
|
||||||
return SskInfo.fromStorage(this._ssks[userId]);
|
return CrossSigningInfo.fromStorage(this._crossSigningInfo[userId], userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
storeSskForUser(userId, ssk) {
|
storeCrossSigningForUser(userId, info) {
|
||||||
this._ssks[userId] = ssk;
|
this._crossSigningInfo[userId] = info;
|
||||||
this._dirty = true;
|
this._dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,8 +587,8 @@ export default class DeviceList extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setRawStoredSskForUser(userId, ssk) {
|
setRawStoredCrossSigningForUser(userId, info) {
|
||||||
this._ssks[userId] = ssk;
|
this._crossSigningInfo[userId] = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -838,6 +838,7 @@ class DeviceListUpdateSerialiser {
|
|||||||
|
|
||||||
async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
|
async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
|
||||||
userResult) {
|
userResult) {
|
||||||
|
// FIXME: this isn't correct any more
|
||||||
let updated = false;
|
let updated = false;
|
||||||
|
|
||||||
// remove any devices in the store which aren't in the response
|
// remove any devices in the store which aren't in the response
|
||||||
@@ -885,6 +886,7 @@ async function _updateStoredDeviceKeysForUser(_olmDevice, userId, userStore,
|
|||||||
async function _updateStoredSelfSigningKeyForUser(
|
async function _updateStoredSelfSigningKeyForUser(
|
||||||
_olmDevice, userId, userStore, userResult,
|
_olmDevice, userId, userStore, userResult,
|
||||||
) {
|
) {
|
||||||
|
// FIXME: this function may need modifying
|
||||||
let updated = false;
|
let updated = false;
|
||||||
|
|
||||||
if (userResult.user_id !== userId) {
|
if (userResult.user_id !== userId) {
|
||||||
@@ -925,8 +927,6 @@ async function _updateStoredSelfSigningKeyForUser(
|
|||||||
userStore.user_id = userResult.user_id;
|
userStore.user_id = userResult.user_id;
|
||||||
userStore.usage = userResult.usage;
|
userStore.usage = userResult.usage;
|
||||||
userStore.keys = userResult.keys;
|
userStore.keys = userResult.keys;
|
||||||
// reset verification status since its now a new key
|
|
||||||
userStore.verified = SskInfo.SskVerification.UNVERIFIED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return updated;
|
return updated;
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import SskInfo from './sskinfo';
|
|||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
const DeviceVerification = DeviceInfo.DeviceVerification;
|
||||||
const DeviceList = require('./DeviceList').default;
|
const DeviceList = require('./DeviceList').default;
|
||||||
import { randomString } from '../randomstring';
|
import { randomString } from '../randomstring';
|
||||||
|
import { CrossSigningInfo, CrossSigningLevel, CrossSigningVerification } from './CrossSigning';
|
||||||
|
|
||||||
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
|
import OutgoingRoomKeyRequestManager from './OutgoingRoomKeyRequestManager';
|
||||||
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
import IndexedDBCryptoStore from './store/indexeddb-crypto-store';
|
||||||
@@ -196,6 +197,14 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
this._lastNewSessionForced = {};
|
this._lastNewSessionForced = {};
|
||||||
|
|
||||||
this._verificationTransactions = new Map();
|
this._verificationTransactions = new Map();
|
||||||
|
|
||||||
|
this._crossSigningInfo = new CrossSigningInfo(userId);
|
||||||
|
this._crossSigningInfo.on("cross-signing:savePrivateKeys", (...args) => {
|
||||||
|
this._baseApis.emit("cross-signing:savePrivateKeys", ...args);
|
||||||
|
});
|
||||||
|
this._crossSigningInfo.on("cross-signing:getKey", (...args) => {
|
||||||
|
this._baseApis.emit("cross-signing:getKey", ...args);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
utils.inherits(Crypto, EventEmitter);
|
utils.inherits(Crypto, EventEmitter);
|
||||||
|
|
||||||
@@ -251,6 +260,74 @@ Crypto.prototype.init = async function() {
|
|||||||
this._checkAndStartKeyBackup();
|
this._checkAndStartKeyBackup();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate new cross-signing keys.
|
||||||
|
*
|
||||||
|
* @param {CrossSigningLevel} level the level of cross-signing to reset. New
|
||||||
|
* keys will be created for the given level and below. Defaults to
|
||||||
|
* regenerating all keys.
|
||||||
|
*/
|
||||||
|
Crypto.prototype.resetCrossSigningKeys = async function(level) {
|
||||||
|
await this._crossSigningInfo.resetKeys(level);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the user's cross-signing keys to use.
|
||||||
|
*
|
||||||
|
* @param {object} keys A mapping of key type to key data.
|
||||||
|
*/
|
||||||
|
Crypto.prototype.setCrossSigningKeys = function(keys) {
|
||||||
|
this._crossSigningInfo.setKeys(keys);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a given user is trusted.
|
||||||
|
*
|
||||||
|
* @param {string} userId The ID of the user to check.
|
||||||
|
*
|
||||||
|
* @returns {integer} a bit mask indicating how the user is trusted (if at all)
|
||||||
|
* - returnValue & 1: unused
|
||||||
|
* - returnValue & 2: trust-on-first-use cross-signing key
|
||||||
|
* - returnValue & 4: user's cross-signing key is verified
|
||||||
|
*
|
||||||
|
* TODO: is this a good way of representing it? Or we could return an object
|
||||||
|
* with different keys, or a set? The advantage of doing it this way is that
|
||||||
|
* you can define which methods you want to use, "&" with the appopriate mask,
|
||||||
|
* then test for truthiness. Or if you want to just trust everything, then use
|
||||||
|
* the value alone. However, I wonder if bit masks are too obscure...
|
||||||
|
*/
|
||||||
|
Crypto.prototype.checkUserTrust = function(userId) {
|
||||||
|
const userCrossSigning = this._deviceList.getStoredCrossSigningForUser(userId);
|
||||||
|
if (!userCrossSigning) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return this._crossSigningInfo.checkUserTrust(userCrossSigning) << 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether a given device is trusted.
|
||||||
|
*
|
||||||
|
* @param {string} userId The ID of the user whose devices is to be checked.
|
||||||
|
* @param {string} deviceId The ID of the device to check
|
||||||
|
*
|
||||||
|
* @returns {integer} a bit mask indicating how the user is trusted (if at all)
|
||||||
|
* - returnValue & 1: device marked as verified
|
||||||
|
* - returnValue & 2: trust-on-first-use cross-signing key
|
||||||
|
* - returnValue & 4: user's cross-signing key is verified and device is signed
|
||||||
|
*
|
||||||
|
* TODO: see checkUserTrust
|
||||||
|
*/
|
||||||
|
Crypto.prototype.checkDeviceTrust = function(userId, deviceId) {
|
||||||
|
const userCrossSigning = this._deviceList.getStoredCrossSigningForUser(userId);
|
||||||
|
const device = this._deviceList.getStoredDevice(userId, deviceId);
|
||||||
|
let rv = 0;
|
||||||
|
if (device.isVerified()) {
|
||||||
|
rv |= 1;
|
||||||
|
}
|
||||||
|
rv |= this._crossSigningInfo.checkDeviceTrust(userCrossSigning, device) << 1;
|
||||||
|
return rv;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Event handler for DeviceList's userNewDevices event
|
* Event handler for DeviceList's userNewDevices event
|
||||||
*/
|
*/
|
||||||
@@ -906,6 +983,24 @@ Crypto.prototype.setSskVerification = async function(userId, verified) {
|
|||||||
Crypto.prototype.setDeviceVerification = async function(
|
Crypto.prototype.setDeviceVerification = async function(
|
||||||
userId, deviceId, verified, blocked, known,
|
userId, deviceId, verified, blocked, known,
|
||||||
) {
|
) {
|
||||||
|
const xsk = this._deviceList.getStoredCrossSigningForUser(userId);
|
||||||
|
if (xsk.getId() === deviceId) {
|
||||||
|
if (verified) {
|
||||||
|
xsk.verified = CrossSigningVerification.VERIFIED;
|
||||||
|
const device = await this._crossSigningInfo.signUser(xsk);
|
||||||
|
// FIXME: mark xsk as dirty in device list
|
||||||
|
this._baseApis.uploadKeySignatures({
|
||||||
|
[userId]: {
|
||||||
|
[deviceId]: device,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return device;
|
||||||
|
} else {
|
||||||
|
// FIXME: ???
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const devices = this._deviceList.getRawStoredDevicesForUser(userId);
|
const devices = this._deviceList.getRawStoredDevicesForUser(userId);
|
||||||
if (!devices || !devices[deviceId]) {
|
if (!devices || !devices[deviceId]) {
|
||||||
throw new Error("Unknown device " + userId + ":" + deviceId);
|
throw new Error("Unknown device " + userId + ":" + deviceId);
|
||||||
@@ -937,6 +1032,18 @@ Crypto.prototype.setDeviceVerification = async function(
|
|||||||
this._deviceList.storeDevicesForUser(userId, devices);
|
this._deviceList.storeDevicesForUser(userId, devices);
|
||||||
this._deviceList.saveIfDirty();
|
this._deviceList.saveIfDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do cross-signing
|
||||||
|
if (verified && userId === this._userId) {
|
||||||
|
const device = await this._crossSigningInfo.signDevice(userId, dev);
|
||||||
|
// FIXME: mark device as dirty in device list
|
||||||
|
this._baseApis.uploadKeySignatures({
|
||||||
|
[userId]: {
|
||||||
|
[deviceId]: device,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return DeviceInfo.fromStorage(dev, deviceId);
|
return DeviceInfo.fromStorage(dev, deviceId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -337,3 +337,65 @@ const _verifySignature = module.exports.verifySignature = async function(
|
|||||||
signingKey, json, signature,
|
signingKey, json, signature,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sign a JSON object using public key cryptography
|
||||||
|
* @param {Object} obj Object to sign. The object will be modified to include
|
||||||
|
* the new signature
|
||||||
|
* @param {Olm.PkSigning|Uint8Array} key the signing object or the private key
|
||||||
|
* seed
|
||||||
|
* @param {string} userId The user ID who owns the signing key
|
||||||
|
* @param {string} pubkey The public key (ignored if key is a seed)
|
||||||
|
* @returns {string} the signature for the object
|
||||||
|
*/
|
||||||
|
module.exports.pkSign = function(obj, key, userId, pubkey) {
|
||||||
|
let createdKey = false;
|
||||||
|
if (key instanceof Uint8Array) {
|
||||||
|
const keyObj = new global.Olm.PkSigning();
|
||||||
|
pubkey = keyObj.init_with_seed(key);
|
||||||
|
key = keyObj;
|
||||||
|
createdKey = true;
|
||||||
|
}
|
||||||
|
const sigs = obj.signatures || {};
|
||||||
|
delete obj.signatures;
|
||||||
|
const unsigned = obj.unsigned;
|
||||||
|
if (obj.unsigned) delete obj.unsigned;
|
||||||
|
try {
|
||||||
|
const mysigs = sigs[userId] || {};
|
||||||
|
sigs[userId] = mysigs;
|
||||||
|
|
||||||
|
return mysigs['ed25519:' + pubkey] = key.sign(anotherjson.stringify(obj));
|
||||||
|
} finally {
|
||||||
|
obj.signatures = sigs;
|
||||||
|
if (unsigned) obj.unsigned = unsigned;
|
||||||
|
if (createdKey) {
|
||||||
|
key.free();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify a signed JSON object
|
||||||
|
* @param {Object} obj Object to verify
|
||||||
|
* @param {string} pubkey The public key to use to verify
|
||||||
|
* @param {string} userId The user ID who signed the object
|
||||||
|
*/
|
||||||
|
module.exports.pkVerify = function(obj, pubkey, userId) {
|
||||||
|
const keyId = "ed25519:" + pubkey;
|
||||||
|
if (!(obj.signatures && obj.signatures[userId] && obj.signatures[userId][keyId])) {
|
||||||
|
throw new Error("No signature");
|
||||||
|
}
|
||||||
|
const signature = obj.signatures[userId][keyId];
|
||||||
|
const util = new global.Olm.Utility();
|
||||||
|
const sigs = obj.signatures;
|
||||||
|
delete obj.signatures;
|
||||||
|
const unsigned = obj.unsigned;
|
||||||
|
if (obj.unsigned) delete obj.unsigned;
|
||||||
|
try {
|
||||||
|
util.ed25519_verify(pubkey, anotherjson.stringify(obj), signature);
|
||||||
|
} finally {
|
||||||
|
obj.signatures = sigs;
|
||||||
|
if (unsigned) obj.unsigned = unsigned;
|
||||||
|
util.free();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ export default class IndexedDBCryptoStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the account keys fort cross-signing (eg. self-signing key,
|
* Get the account keys for cross-signing (eg. self-signing key,
|
||||||
* user signing key).
|
* user signing key).
|
||||||
*
|
*
|
||||||
* @param {*} txn An active transaction. See doTxn().
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
|
|||||||
Reference in New Issue
Block a user