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
Merge pull request #832 from matrix-org/dbkr/cross_signing
Cross Signing Support
This commit is contained in:
334
src/client.js
334
src/client.js
@@ -51,7 +51,7 @@ import logger from './logger';
|
||||
import Crypto from './crypto';
|
||||
import { isCryptoAvailable } from './crypto';
|
||||
import { encodeRecoveryKey, decodeRecoveryKey } from './crypto/recoverykey';
|
||||
import { keyForNewBackup, keyForExistingBackup } from './crypto/backup_password';
|
||||
import { keyFromPassphrase, keyFromAuthData } from './crypto/key_passphrase';
|
||||
import { randomString } from './randomstring';
|
||||
|
||||
// Disable warnings for now: we use deprecated bluebird functions
|
||||
@@ -175,6 +175,68 @@ function keyFromRecoverySession(session, decryptionKey) {
|
||||
* @param {boolean} [opts.fallbackICEServerAllowed]
|
||||
* Optional. Whether to allow a fallback ICE server should be used for negotiating a
|
||||
* WebRTC connection if the homeserver doesn't provide any servers. Defaults to false.
|
||||
*
|
||||
* @param {object} opts.cryptoCallbacks Optional. Callbacks for crypto and cross-signing.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.getCrossSigningKey]
|
||||
* Optional (required for cross-signing). Function to call when a cross-signing private key is needed.
|
||||
* Args:
|
||||
* {string} type The type of key needed. Will be one of "master",
|
||||
* "self_signing", or "user_signing"
|
||||
* {Uint8Array} publicKey The public key matching the expected private key.
|
||||
* This can be passed to checkPrivateKey() along with the private key
|
||||
* in order to check that a given private key matches what is being
|
||||
* requested.
|
||||
* Should return a promise that resolves with the private key as a
|
||||
* UInt8Array or rejects with an error.
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.saveCrossSigningKeys]
|
||||
* Optional (required for cross-signing). Called when new private keys
|
||||
* for cross-signing need to be saved.
|
||||
* Args:
|
||||
* {object} keys the private keys to save. Map of key name to private key
|
||||
* as a UInt8Array. The getPrivateKey callback above will be called
|
||||
* with the corresponding key name when the keys are required again.
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.shouldUpgradeDeviceVerifications]
|
||||
* Optional. Called when there are device-to-device verifications that can be
|
||||
* upgraded into cross-signing verifications.
|
||||
* Args:
|
||||
* {object} users The users whose device verifications can be
|
||||
* upgraded to cross-signing verifications. This will be a map of user IDs
|
||||
* to objects with the properties `devices` (array of the user's devices
|
||||
* that verified their cross-signing key), and `crossSigningInfo` (the
|
||||
* user's cross-signing information)
|
||||
* Should return a promise which resolves with an array of the user IDs who
|
||||
* should be cross-signed.
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.getSecretStorageKey]
|
||||
* Optional. Function called when an encryption key for secret storage
|
||||
* is required. One or more keys will be described in the keys object.
|
||||
* The callback function should return with an array of:
|
||||
* [<key name>, <UInt8Array private key>] or null if it cannot provide
|
||||
* any of the keys.
|
||||
* Args:
|
||||
* {object} keys Information about the keys:
|
||||
* {
|
||||
* <key name>: {
|
||||
* pubkey: {UInt8Array}
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* @param {function} [opts.cryptoCallbacks.onSecretRequested]
|
||||
* Optional. Function called when a request for a secret is received from another
|
||||
* device.
|
||||
* Args:
|
||||
* {string} name The name of the secret being requested.
|
||||
* {string} user_id (string) The user ID of the client requesting
|
||||
* {string} device_id The device ID of the client requesting the secret.
|
||||
* {string} request_id The ID of the request. Used to match a
|
||||
* corresponding `crypto.secrets.request_cancelled`. The request ID will be
|
||||
* unique per sender, device pair.
|
||||
* {DeviceTrustLevel} device_trust: The trust status of the device requesting
|
||||
* the secret as returned by {@link module:client~MatrixClient#checkDeviceTrust}.
|
||||
*/
|
||||
function MatrixClient(opts) {
|
||||
opts.baseUrl = utils.ensureNoTrailingSlash(opts.baseUrl);
|
||||
@@ -235,6 +297,7 @@ function MatrixClient(opts) {
|
||||
this._cryptoStore = opts.cryptoStore;
|
||||
this._sessionStore = opts.sessionStore;
|
||||
this._verificationMethods = opts.verificationMethods;
|
||||
this._cryptoCallbacks = opts.cryptoCallbacks;
|
||||
|
||||
this._forceTURN = opts.forceTURN || false;
|
||||
this._fallbackICEServerAllowed = opts.fallbackICEServerAllowed || false;
|
||||
@@ -611,6 +674,10 @@ MatrixClient.prototype.initCrypto = async function() {
|
||||
"crypto.roomKeyRequest",
|
||||
"crypto.roomKeyRequestCancellation",
|
||||
"crypto.warning",
|
||||
"crypto.devicesUpdated",
|
||||
"deviceVerificationChanged",
|
||||
"userVerificationChanged",
|
||||
"crossSigning.keysChanged",
|
||||
]);
|
||||
|
||||
logger.log("Crypto: initialising crypto object...");
|
||||
@@ -779,10 +846,9 @@ async function _setDeviceVerification(
|
||||
if (!client._crypto) {
|
||||
throw new Error("End-to-End encryption disabled");
|
||||
}
|
||||
const dev = await client._crypto.setDeviceVerification(
|
||||
await client._crypto.setDeviceVerification(
|
||||
userId, deviceId, verified, blocked, known,
|
||||
);
|
||||
client.emit("deviceVerificationChanged", userId, deviceId, dev);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -880,6 +946,189 @@ MatrixClient.prototype.getGlobalBlacklistUnverifiedDevices = function() {
|
||||
return this._crypto.getGlobalBlacklistUnverifiedDevices();
|
||||
};
|
||||
|
||||
/**
|
||||
* Add methods that call the corresponding method in this._crypto
|
||||
*
|
||||
* @param {class} MatrixClient the class to add the method to
|
||||
* @param {string} names the names of the methods to call
|
||||
*/
|
||||
function wrapCryptoFuncs(MatrixClient, names) {
|
||||
for (const name of names) {
|
||||
MatrixClient.prototype[name] = 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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new cross-signing keys.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#resetCrossSigningKeys
|
||||
* @param {object} authDict Auth data to supply for User-Interactive auth.
|
||||
* @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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the user's cross-signing key ID.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#getCrossSigningId
|
||||
* @param {string} [type=master] The type of key to get the ID of. One of
|
||||
* "master", "self_signing", or "user_signing". Defaults to "master".
|
||||
*
|
||||
* @returns {string} the key ID
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the cross signing information for a given user.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#getStoredCrossSigningForUser
|
||||
* @param {string} userId the user ID to get the cross-signing info for.
|
||||
*
|
||||
* @returns {CrossSigningInfo} the cross signing information for the user.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether a given user is trusted.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#checkUserTrust
|
||||
* @param {string} userId The ID of the user to check.
|
||||
*
|
||||
* @returns {UserTrustLevel}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check whether a given device is trusted.
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#checkDeviceTrust
|
||||
* @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 {DeviceTrustLevel}
|
||||
*/
|
||||
|
||||
wrapCryptoFuncs(MatrixClient, [
|
||||
"resetCrossSigningKeys",
|
||||
"getCrossSigningId",
|
||||
"getStoredCrossSigningForUser",
|
||||
"checkUserTrust",
|
||||
"checkDeviceTrust",
|
||||
"checkOwnCrossSigningTrust",
|
||||
"checkPrivateKey",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if the sender of an event is verified
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @param {MatrixEvent} event event to be checked
|
||||
*
|
||||
* @returns {DeviceTrustLevel}
|
||||
*/
|
||||
MatrixClient.prototype.checkEventSenderTrust = async function(event) {
|
||||
const device = await this.getEventSenderDeviceInfo(event);
|
||||
if (!device) {
|
||||
return 0;
|
||||
}
|
||||
return await this._crypto.checkDeviceTrust(event.getSender(), device.deviceId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a key for encrypting secrets.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#addSecretKey
|
||||
* @param {string} algorithm the algorithm used by the key
|
||||
* @param {object} opts the options for the algorithm. The properties used
|
||||
* depend on the algorithm given. This object may be modified to pass
|
||||
* information back about the key.
|
||||
* @param {string} [keyName] the name of the key. If not given, a random
|
||||
* name will be generated.
|
||||
*
|
||||
* @return {string} the name of the key
|
||||
*/
|
||||
|
||||
/**
|
||||
* Store an encrypted secret on the server
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#storeSecret
|
||||
* @param {string} name The name of the secret
|
||||
* @param {string} secret The secret contents.
|
||||
* @param {Array} keys The IDs of the keys to use to encrypt the secret or null/undefined
|
||||
* to use the default (will throw if no default key is set).
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get a secret from storage.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#getSecret
|
||||
* @param {string} name the name of the secret
|
||||
*
|
||||
* @return {string} the contents of the secret
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if a secret is stored on the server.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#isSecretStored
|
||||
* @param {string} name the name of the secret
|
||||
* @param {boolean} checkKey check if the secret is encrypted by a trusted
|
||||
* key
|
||||
*
|
||||
* @return {boolean} whether or not the secret is stored
|
||||
*/
|
||||
|
||||
/**
|
||||
* Request a secret from another device.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#requestSecret
|
||||
* @param {string} name the name of the secret to request
|
||||
* @param {string[]} devices the devices to request the secret from
|
||||
*
|
||||
* @return {string} the contents of the secret
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the current default key ID for encrypting secrets.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#getDefaultKeyId
|
||||
*
|
||||
* @return {string} The default key ID or null if no default key ID is set
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the current default key ID for encrypting secrets.
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @function module:client~MatrixClient#setDefaultKeyId
|
||||
* @param {string} keyId The new default key ID
|
||||
*/
|
||||
|
||||
wrapCryptoFuncs(MatrixClient, [
|
||||
"addSecretKey",
|
||||
"storeSecret",
|
||||
"getSecret",
|
||||
"isSecretStored",
|
||||
"requestSecret",
|
||||
"getDefaultKeyId",
|
||||
"setDefaultKeyId",
|
||||
]);
|
||||
|
||||
/**
|
||||
* Get e2e information on the device that sent an event
|
||||
*
|
||||
@@ -1131,7 +1380,7 @@ MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {
|
||||
let publicKey;
|
||||
const authData = {};
|
||||
if (password) {
|
||||
const keyInfo = await keyForNewBackup(password);
|
||||
const keyInfo = await keyFromPassphrase(password);
|
||||
publicKey = decryption.init_with_private_key(keyInfo.key);
|
||||
authData.private_key_salt = keyInfo.salt;
|
||||
authData.private_key_iterations = keyInfo.iterations;
|
||||
@@ -1158,7 +1407,7 @@ MatrixClient.prototype.prepareKeyBackupVersion = async function(password) {
|
||||
* @param {object} info Info object from prepareKeyBackupVersion
|
||||
* @returns {Promise<object>} Object with 'version' param indicating the version created
|
||||
*/
|
||||
MatrixClient.prototype.createKeyBackupVersion = function(info) {
|
||||
MatrixClient.prototype.createKeyBackupVersion = async function(info) {
|
||||
if (this._crypto === null) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
@@ -1167,19 +1416,27 @@ MatrixClient.prototype.createKeyBackupVersion = function(info) {
|
||||
algorithm: info.algorithm,
|
||||
auth_data: info.auth_data,
|
||||
};
|
||||
return this._crypto._signObject(data.auth_data).then(() => {
|
||||
return this._http.authedRequest(
|
||||
undefined, "POST", "/room_keys/version", undefined, data,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
);
|
||||
}).then((res) => {
|
||||
this.enableKeyBackup({
|
||||
algorithm: info.algorithm,
|
||||
auth_data: info.auth_data,
|
||||
version: res.version,
|
||||
});
|
||||
return res;
|
||||
|
||||
// Now sign the backup auth data. Do it as this device first because crypto._signObject
|
||||
// is dumb and bluntly replaces the whole signatures block...
|
||||
// this can probably go away very soon in favour of just signing with the SSK.
|
||||
await this._crypto._signObject(data.auth_data);
|
||||
|
||||
if (this._crypto._crossSigningInfo.getId()) {
|
||||
// now also sign the auth data with the master key
|
||||
await this._crypto._crossSigningInfo.signObject(data.auth_data, "master");
|
||||
}
|
||||
|
||||
const res = await this._http.authedRequest(
|
||||
undefined, "POST", "/room_keys/version", undefined, data,
|
||||
{prefix: httpApi.PREFIX_UNSTABLE},
|
||||
);
|
||||
this.enableKeyBackup({
|
||||
algorithm: info.algorithm,
|
||||
auth_data: info.auth_data,
|
||||
version: res.version,
|
||||
});
|
||||
return res;
|
||||
};
|
||||
|
||||
MatrixClient.prototype.deleteKeyBackupVersion = function(version) {
|
||||
@@ -1285,7 +1542,7 @@ MatrixClient.RESTORE_BACKUP_ERROR_BAD_KEY = 'RESTORE_BACKUP_ERROR_BAD_KEY';
|
||||
MatrixClient.prototype.restoreKeyBackupWithPassword = async function(
|
||||
password, targetRoomId, targetSessionId, backupInfo,
|
||||
) {
|
||||
const privKey = await keyForExistingBackup(backupInfo, password);
|
||||
const privKey = await keyFromAuthData(backupInfo.auth_data, password);
|
||||
return this._restoreKeyBackup(
|
||||
privKey, targetRoomId, targetSessionId, backupInfo,
|
||||
);
|
||||
@@ -4870,6 +5127,32 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* @param {module:crypto/deviceinfo} deviceInfo updated device information
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires when the trust status of a user changes
|
||||
* If userId is the userId of the logged in user, this indicated a change
|
||||
* in the trust status of the cross-signing data on the account.
|
||||
*
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @event module:client~MatrixClient#"userTrustStatusChanged"
|
||||
* @param {string} userId the userId of the user in question
|
||||
* @param {UserTrustLevel} trustLevel The new trust level of the user
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires when the user's cross-signing keys have changed or cross-signing
|
||||
* has been enabled/disabled. The client can use getStoredCrossSigningForUser
|
||||
* with the user ID of the logged in user to check if cross-signing is
|
||||
* enabled on the account. If enabled, it can test whether the current key
|
||||
* is trusted using with checkUserTrust with the user ID of the logged
|
||||
* in user. The checkOwnCrossSigningTrust function may be used to reconcile
|
||||
* the trust in the account key.
|
||||
*
|
||||
* The cross-signing API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @event module:client~MatrixClient#"crossSigning.keysChanged"
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires whenever new user-scoped account_data is added.
|
||||
* @event module:client~MatrixClient#"accountData"
|
||||
@@ -4927,6 +5210,21 @@ module.exports.CRYPTO_ENABLED = CRYPTO_ENABLED;
|
||||
* perform the key verification
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fires when a secret request has been cancelled. If the client is prompting
|
||||
* the user to ask whether they want to share a secret, the prompt can be
|
||||
* dismissed.
|
||||
*
|
||||
* The Secure Secret Storage API is currently UNSTABLE and may change without notice.
|
||||
*
|
||||
* @event module:client~MatrixClient#"crypto.secrets.requestCancelled"
|
||||
* @param {object} data
|
||||
* @param {string} data.user_id The user ID of the client that had requested the secret.
|
||||
* @param {string} data.device_id The device ID of the client that had requested the
|
||||
* secret.
|
||||
* @param {string} data.request_id The ID of the original request.
|
||||
*/
|
||||
|
||||
// EventEmitter JSDocs
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user