You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2026-01-03 23:22:30 +03:00
Merge remote-tracking branch 'origin/develop' into dbkr/prefer_const
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- node # Latest stable version of nodejs.
|
||||
script:
|
||||
- npm run lint
|
||||
- npm run test
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/bin/bash -l
|
||||
|
||||
set -x
|
||||
|
||||
export NVM_DIR="/home/jenkins/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
|
||||
nvm use 6
|
||||
npm install
|
||||
|
||||
nvm use 6 || exit $?
|
||||
npm install || exit $?
|
||||
|
||||
RC=0
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"dist": "npm run build",
|
||||
"watch": "watchify --exclude olm browser-index.js -o dist/browser-matrix-dev.js -v",
|
||||
"lint": "eslint --max-warnings 1860 src spec",
|
||||
"prepublish": "npm run lint && npm run build && git rev-parse HEAD > git-revision.txt"
|
||||
"prepublish": "npm run build && git rev-parse HEAD > git-revision.txt"
|
||||
},
|
||||
"repository": {
|
||||
"url": "https://github.com/matrix-org/matrix-js-sdk"
|
||||
@@ -63,6 +63,6 @@
|
||||
"watchify": "^3.2.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"olm": "https://matrix.org/packages/npm/olm/olm-2.1.0.tgz"
|
||||
"olm": "https://matrix.org/packages/npm/olm/olm-2.2.1.tgz"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -895,4 +895,89 @@ describe("megolm", function() {
|
||||
return q.all([downloadPromise, sendPromise]);
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
|
||||
it("Alice exports megolm keys and imports them to a new device", function(done) {
|
||||
let messageEncrypted;
|
||||
|
||||
return aliceTestClient.start().then(() => {
|
||||
const p2pSession = createOlmSession(
|
||||
testOlmAccount, aliceTestClient
|
||||
);
|
||||
|
||||
const groupSession = new Olm.OutboundGroupSession();
|
||||
groupSession.create();
|
||||
|
||||
// make the room_key event
|
||||
const roomKeyEncrypted = encryptGroupSessionKey({
|
||||
senderKey: testSenderKey,
|
||||
recipient: aliceTestClient,
|
||||
p2pSession: p2pSession,
|
||||
groupSession: groupSession,
|
||||
room_id: ROOM_ID,
|
||||
});
|
||||
|
||||
// encrypt a message with the group session
|
||||
messageEncrypted = encryptMegolmEvent({
|
||||
senderKey: testSenderKey,
|
||||
groupSession: groupSession,
|
||||
room_id: ROOM_ID,
|
||||
});
|
||||
|
||||
// Alice gets both the events in a single sync
|
||||
const syncResponse = {
|
||||
next_batch: 1,
|
||||
to_device: {
|
||||
events: [roomKeyEncrypted],
|
||||
},
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncResponse.rooms.join[ROOM_ID] = {
|
||||
timeline: {
|
||||
events: [messageEncrypted],
|
||||
},
|
||||
};
|
||||
|
||||
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
|
||||
return aliceTestClient.httpBackend.flush("/sync", 1);
|
||||
}).then(function() {
|
||||
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||
const event = room.getLiveTimeline().getEvents()[0];
|
||||
expect(event.getContent().body).toEqual('42');
|
||||
|
||||
return aliceTestClient.client.exportRoomKeys();
|
||||
}).then(function(exported) {
|
||||
// start a new client
|
||||
aliceTestClient.stop();
|
||||
|
||||
aliceTestClient = new TestClient(
|
||||
"@alice:localhost", "device2", "access_token2"
|
||||
);
|
||||
|
||||
aliceTestClient.client.importRoomKeys(exported);
|
||||
|
||||
return aliceTestClient.start();
|
||||
}).then(function() {
|
||||
const syncResponse = {
|
||||
next_batch: 1,
|
||||
rooms: {
|
||||
join: {},
|
||||
},
|
||||
};
|
||||
syncResponse.rooms.join[ROOM_ID] = {
|
||||
timeline: {
|
||||
events: [messageEncrypted],
|
||||
},
|
||||
};
|
||||
|
||||
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
|
||||
return aliceTestClient.httpBackend.flush("/sync", 1);
|
||||
}).then(function() {
|
||||
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||
const event = room.getLiveTimeline().getEvents()[0];
|
||||
expect(event.getContent().body).toEqual('42');
|
||||
}).nodeify(done);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -193,6 +193,12 @@ module.exports.MockStorageApi = function() {
|
||||
this.data = {};
|
||||
};
|
||||
module.exports.MockStorageApi.prototype = {
|
||||
get length() {
|
||||
return Object.keys(this.data).length;
|
||||
},
|
||||
key: function(i) {
|
||||
return Object.keys(this.data)[i];
|
||||
},
|
||||
setItem: function(k, v) {
|
||||
this.data[k] = v;
|
||||
},
|
||||
|
||||
@@ -461,6 +461,33 @@ MatrixClient.prototype.isRoomEncrypted = function(roomId) {
|
||||
return this._crypto.isRoomEncrypted(roomId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a list containing all of the room keys
|
||||
*
|
||||
* This should be encrypted before returning it to the user.
|
||||
*
|
||||
* @return {module:client.Promise} a promise which resolves to a list of
|
||||
* session export objects
|
||||
*/
|
||||
MatrixClient.prototype.exportRoomKeys = function() {
|
||||
if (!this._crypto) {
|
||||
return q.reject(new Error("End-to-end encryption disabled"));
|
||||
}
|
||||
return this._crypto.exportRoomKeys();
|
||||
};
|
||||
|
||||
/**
|
||||
* Import a list of room keys previously exported by exportRoomKeys
|
||||
*
|
||||
* @param {Object[]} keys a list of session export objects
|
||||
*/
|
||||
MatrixClient.prototype.importRoomKeys = function(keys) {
|
||||
if (!this._crypto) {
|
||||
throw new Error("End-to-end encryption disabled");
|
||||
}
|
||||
this._crypto.importRoomKeys(keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt a received event according to the algorithm specified in the event.
|
||||
*
|
||||
|
||||
@@ -47,6 +47,18 @@ function checkPayloadLength(payloadString) {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The type of object we use for importing and exporting megolm session data.
|
||||
*
|
||||
* @typedef {Object} module:crypto/OlmDevice.MegolmSessionData
|
||||
* @property {String} sender_key Sender's Curve25519 device key
|
||||
* @property {Object<string, string>} sender_claimed_keys Other keys the sender claims.
|
||||
* @property {String} room_id Room this session is used in
|
||||
* @property {String} session_id Unique id for the session
|
||||
* @property {String} session_key Base64'ed key data
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Manages the olm cryptography functions. Each OlmDevice has a single
|
||||
* OlmAccount and a number of OlmSessions.
|
||||
@@ -683,6 +695,48 @@ OlmDevice.prototype.addInboundGroupSession = function(
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Add a previously-exported inbound group session to the session store
|
||||
*
|
||||
* @param {module:crypto/OlmDevice.MegolmSessionData} data session data
|
||||
*/
|
||||
OlmDevice.prototype.importInboundGroupSession = function(data) {
|
||||
/* if we already have this session, consider updating it */
|
||||
function updateSession(session) {
|
||||
console.log("Update for megolm session " + data.sender_key + "|" +
|
||||
data.session_id);
|
||||
// for now we just ignore updates. TODO: implement something here
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const r = this._getInboundGroupSession(
|
||||
data.room_id, data.sender_key, data.session_id, updateSession
|
||||
);
|
||||
|
||||
if (r !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// new session.
|
||||
const session = new Olm.InboundGroupSession();
|
||||
try {
|
||||
session.import_session(data.session_key);
|
||||
if (data.session_id != session.session_id()) {
|
||||
throw new Error(
|
||||
"Mismatched group session ID from senderKey: " + data.sender_key
|
||||
);
|
||||
}
|
||||
this._saveInboundGroupSession(
|
||||
data.room_id, data.sender_key, data.session_id, session,
|
||||
data.sender_claimed_keys
|
||||
);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt a received message with an inbound group session
|
||||
*
|
||||
@@ -739,6 +793,41 @@ OlmDevice.prototype.decryptGroupMessage = function(
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Export an inbound group session
|
||||
*
|
||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||
* @param {string} sessionId session identifier
|
||||
* @return {module:crypto/OlmDevice.MegolmSessionData} exported session data
|
||||
*/
|
||||
OlmDevice.prototype.exportInboundGroupSession = function(senderKey, sessionId) {
|
||||
const s = this._sessionStore.getEndToEndInboundGroupSession(
|
||||
senderKey, sessionId
|
||||
);
|
||||
|
||||
if (s === null) {
|
||||
throw new Error("Unknown inbound group session [" + senderKey + "," +
|
||||
sessionId + "]");
|
||||
}
|
||||
const r = JSON.parse(s);
|
||||
|
||||
const session = new Olm.InboundGroupSession();
|
||||
try {
|
||||
session.unpickle(this._pickleKey, r.session);
|
||||
|
||||
const messageIndex = session.first_known_index();
|
||||
|
||||
return {
|
||||
"sender_key": senderKey,
|
||||
"sender_claimed_keys": r.keysClaimed,
|
||||
"room_id": r.room_id,
|
||||
"session_id": sessionId,
|
||||
"session_key": session.export_session(messageIndex),
|
||||
};
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
// Utilities
|
||||
// =========
|
||||
|
||||
@@ -136,6 +136,15 @@ DecryptionAlgorithm.prototype.onRoomKeyEvent = function(params) {
|
||||
// ignore by default
|
||||
};
|
||||
|
||||
/**
|
||||
* Import a room key
|
||||
*
|
||||
* @param {module:crypto/OlmDevice.MegolmSessionData} session
|
||||
*/
|
||||
DecryptionAlgorithm.prototype.importRoomKey = function(session) {
|
||||
// ignore by default
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception thrown when decryption fails
|
||||
*
|
||||
|
||||
@@ -564,19 +564,45 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) {
|
||||
content.session_key, event.getKeysClaimed()
|
||||
);
|
||||
|
||||
let k = event.getSenderKey() + "|" + content.session_id;
|
||||
let pending = this._pendingEvents[k];
|
||||
if (pending) {
|
||||
// have another go at decrypting events sent with this session.
|
||||
delete this._pendingEvents[k];
|
||||
// have another go at decrypting events sent with this session.
|
||||
this._retryDecryption(event.getSenderKey, content.session_id);
|
||||
};
|
||||
|
||||
for (let i = 0; i < pending.length; i++) {
|
||||
try {
|
||||
this.decryptEvent(pending[i]);
|
||||
console.log("successful re-decryption of", pending[i]);
|
||||
} catch (e) {
|
||||
console.log("Still can't decrypt", pending[i], e.stack || e);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {module:crypto/OlmDevice.MegolmSessionData} session
|
||||
*/
|
||||
MegolmDecryption.prototype.importRoomKey = function(session) {
|
||||
this._olmDevice.importInboundGroupSession(session);
|
||||
|
||||
// have another go at decrypting events sent with this session.
|
||||
this._retryDecryption(session.sender_key, session.session_id);
|
||||
};
|
||||
|
||||
/**
|
||||
* Have another go at decrypting events after we receive a key
|
||||
*
|
||||
* @private
|
||||
* @param {String} senderKey
|
||||
* @param {String} sessionId
|
||||
*/
|
||||
MegolmDecryption.prototype._retryDecryption = function(senderKey, sessionId) {
|
||||
const k = senderKey + "|" + sessionId;
|
||||
const pending = this._pendingEvents[k];
|
||||
if (!pending) {
|
||||
return;
|
||||
}
|
||||
|
||||
delete this._pendingEvents[k];
|
||||
|
||||
for (let i = 0; i < pending.length; i++) {
|
||||
try {
|
||||
this.decryptEvent(pending[i]);
|
||||
console.log("successful re-decryption of", pending[i]);
|
||||
} catch (e) {
|
||||
console.log("Still can't decrypt", pending[i], e.stack || e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -897,6 +897,45 @@ Crypto.prototype.isRoomEncrypted = function(roomId) {
|
||||
return Boolean(this._roomEncryptors[roomId]);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Get a list containing all of the room keys
|
||||
*
|
||||
* @return {module:client.Promise} a promise which resolves to a list of
|
||||
* session export objects
|
||||
*/
|
||||
Crypto.prototype.exportRoomKeys = function() {
|
||||
return q(
|
||||
this._sessionStore.getAllEndToEndInboundGroupSessionKeys().map(
|
||||
(s) => {
|
||||
const sess = this._olmDevice.exportInboundGroupSession(
|
||||
s.senderKey, s.sessionId
|
||||
);
|
||||
|
||||
sess.algorithm = olmlib.MEGOLM_ALGORITHM;
|
||||
return sess;
|
||||
}
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Import a list of room keys previously exported by exportRoomKeys
|
||||
*
|
||||
* @param {Object[]} keys a list of session export objects
|
||||
*/
|
||||
Crypto.prototype.importRoomKeys = function(keys) {
|
||||
keys.map((session) => {
|
||||
if (!session.room_id || !session.algorithm) {
|
||||
console.warn("ignoring session entry with missing fields", session);
|
||||
return;
|
||||
}
|
||||
|
||||
const alg = this._getRoomDecryptor(session.room_id, session.algorithm);
|
||||
alg.importRoomKey(session);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Encrypt an event according to the configuration of the room, if necessary.
|
||||
*
|
||||
|
||||
@@ -37,7 +37,10 @@ function WebStorageSessionStore(webStore) {
|
||||
this.store = webStore;
|
||||
if (!utils.isFunction(webStore.getItem) ||
|
||||
!utils.isFunction(webStore.setItem) ||
|
||||
!utils.isFunction(webStore.removeItem)) {
|
||||
!utils.isFunction(webStore.removeItem) ||
|
||||
!utils.isFunction(webStore.key) ||
|
||||
typeof(webStore.length) !== 'number'
|
||||
) {
|
||||
throw new Error(
|
||||
"Supplied webStore does not meet the WebStorage API interface"
|
||||
);
|
||||
@@ -120,6 +123,32 @@ WebStorageSessionStore.prototype = {
|
||||
return getJsonItem(this.store, keyEndToEndSessions(deviceKey));
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve a list of all known inbound group sessions
|
||||
*
|
||||
* @return {{senderKey: string, sessionId: string}}
|
||||
*/
|
||||
getAllEndToEndInboundGroupSessionKeys: function() {
|
||||
const prefix = E2E_PREFIX + 'inboundgroupsessions/';
|
||||
const result = [];
|
||||
for (let i = 0; i < this.store.length; i++) {
|
||||
const key = this.store.key(i);
|
||||
if (!key.startsWith(prefix)) {
|
||||
continue;
|
||||
}
|
||||
// we can't use split, as the components we are trying to split out
|
||||
// might themselves contain '/' characters. We rely on the
|
||||
// senderKey being a (32-byte) curve25519 key, base64-encoded
|
||||
// (hence 43 characters long).
|
||||
|
||||
result.push({
|
||||
senderKey: key.substr(prefix.length, 43),
|
||||
sessionId: key.substr(prefix.length + 44),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
getEndToEndInboundGroupSession: function(senderKey, sessionId) {
|
||||
let key = keyEndToEndInboundGroupSession(senderKey, sessionId);
|
||||
return this.store.getItem(key);
|
||||
|
||||
Reference in New Issue
Block a user