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
Implement megolm encryption/decryption
Very early attempt at encryption/decryption implementation via megolm. You have to c&p the keys manually.
This commit is contained in:
217
lib/OlmDevice.js
217
lib/OlmDevice.js
@@ -61,6 +61,10 @@ function OlmDevice(sessionStore) {
|
||||
|
||||
this.deviceCurve25519Key = e2eKeys.curve25519;
|
||||
this.deviceEd25519Key = e2eKeys.ed25519;
|
||||
|
||||
// we don't bother stashing outboundgroupsessions in the sessionstore -
|
||||
// instead we keep them here.
|
||||
this._outboundGroupSessionStore = {};
|
||||
}
|
||||
|
||||
|
||||
@@ -358,6 +362,219 @@ OlmDevice.prototype.decryptMessage = function(
|
||||
};
|
||||
|
||||
|
||||
// Outbound group session
|
||||
// ======================
|
||||
|
||||
/**
|
||||
* store an OutboundGroupSession in _outboundGroupSessionStore
|
||||
*
|
||||
* @param {Olm.OutboundGroupSession} session
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._saveOutboundGroupSession = function(session) {
|
||||
var pickledSession = session.pickle(this._pickleKey);
|
||||
this._outboundGroupSessionStore[session.session_id()] = pickledSession;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* extract an OutboundGroupSession from _outboundGroupSessionStore and call the
|
||||
* given function
|
||||
*
|
||||
* @param {string} sessionId
|
||||
* @param {function} func
|
||||
* @return {object} result of func
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
|
||||
var pickled = this._outboundGroupSessionStore[sessionId];
|
||||
if (pickled === null) {
|
||||
throw new Error("Unknown outbound group session " + sessionId);
|
||||
}
|
||||
|
||||
var session = new Olm.OutboundGroupSession();
|
||||
try {
|
||||
session.unpickle(this._pickleKey, pickled);
|
||||
return func(session);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Generate a new outbound group session
|
||||
*
|
||||
* @return {string} sessionId for the outbound session.
|
||||
*/
|
||||
OlmDevice.prototype.createOutboundGroupSession = function() {
|
||||
var session = new Olm.OutboundGroupSession();
|
||||
try {
|
||||
session.create();
|
||||
this._saveOutboundGroupSession(session);
|
||||
return session.session_id();
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Encrypt an outgoing message with an outbound group session
|
||||
*
|
||||
* @param {string} sessionId the id of the outboundgroupsession
|
||||
* @param {string} payloadString payload to be encrypted and sent
|
||||
*
|
||||
* @return {string} ciphertext
|
||||
*/
|
||||
OlmDevice.prototype.encryptGroupMessage = function(sessionId, payloadString) {
|
||||
var self = this;
|
||||
|
||||
return this._getOutboundGroupSession(sessionId, function(session) {
|
||||
var res = session.encrypt(payloadString);
|
||||
self._saveOutboundGroupSession(session);
|
||||
return res;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the session keys for an outbound group session
|
||||
*
|
||||
* @param {string} sessionId the id of the outbound group session
|
||||
*
|
||||
* @return {{chain_index: number, key: string}} current chain index, and
|
||||
* base64-encoded secret key.
|
||||
*/
|
||||
OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) {
|
||||
return this._getOutboundGroupSession(sessionId, function(session) {
|
||||
return {
|
||||
chain_index: session.message_index(),
|
||||
key: session.session_key(),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Inbound group session
|
||||
// =====================
|
||||
|
||||
/**
|
||||
* store an InboundGroupSession in the session store
|
||||
*
|
||||
* @param {string} roomId
|
||||
* @param {string} senderKey
|
||||
* @param {string} sessionId
|
||||
* @param {Olm.InboundGroupSession} session
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._saveInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, session
|
||||
) {
|
||||
var r = {
|
||||
room_id: roomId,
|
||||
session: session.pickle(this._pickleKey),
|
||||
};
|
||||
|
||||
this._sessionStore.storeEndToEndInboundGroupSession(
|
||||
senderKey, sessionId, JSON.stringify(r)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* extract an InboundGroupSession from the session store and call the given function
|
||||
*
|
||||
* @param {string} roomId
|
||||
* @param {string} senderKey
|
||||
* @param {string} sessionId
|
||||
* @param {function} func
|
||||
* @return {object} result of func
|
||||
* @private
|
||||
*/
|
||||
OlmDevice.prototype._getInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, func
|
||||
) {
|
||||
var r = this._sessionStore.getEndToEndInboundGroupSession(
|
||||
senderKey, sessionId
|
||||
);
|
||||
|
||||
if (r === null) {
|
||||
throw new Error("Unknown inbound group session id");
|
||||
}
|
||||
|
||||
r = JSON.parse(r);
|
||||
|
||||
// check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (roomId !== r.room_id) {
|
||||
throw new Error(
|
||||
"Mismatched room_id for inbound group session (expected " + r.room_id +
|
||||
", was " + roomId + ")"
|
||||
);
|
||||
}
|
||||
|
||||
var session = new Olm.InboundGroupSession();
|
||||
try {
|
||||
session.unpickle(this._pickleKey, r.session);
|
||||
return func(session);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add an inbound group session to the session store
|
||||
*
|
||||
* @param {string} roomId room in which this session will be used
|
||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||
* @param {string} sessionId session identifier
|
||||
* @param {string} sessionKey base64-encoded secret key at index chainIndex
|
||||
* @param {number} chainIndex index at which sessionKey applies
|
||||
*/
|
||||
OlmDevice.prototype.addInboundGroupSession = function(
|
||||
roomId, senderKey, sessionId, sessionKey, chainIndex
|
||||
) {
|
||||
var self = this;
|
||||
var session = new Olm.InboundGroupSession();
|
||||
try {
|
||||
session.create(chainIndex, sessionKey);
|
||||
self._saveInboundGroupSession(roomId, senderKey, sessionId, session);
|
||||
} finally {
|
||||
session.free();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decrypt a received message with an inbound group session
|
||||
*
|
||||
* @param {string} roomId room in which the message was received
|
||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||
* @param {string} sessionId session identifier
|
||||
* @param {string} body base64-encoded body of the encrypted message
|
||||
*
|
||||
* @return {string} plaintext
|
||||
*/
|
||||
OlmDevice.prototype.decryptGroupMessage = function(
|
||||
roomId, senderKey, sessionId, body
|
||||
) {
|
||||
var self = this;
|
||||
|
||||
function decrypt(session) {
|
||||
var res = session.decrypt(body);
|
||||
self._saveInboundGroupSession(
|
||||
roomId, senderKey, sessionId, session
|
||||
);
|
||||
return res;
|
||||
}
|
||||
|
||||
return this._getInboundGroupSession(
|
||||
roomId, senderKey, sessionId, decrypt
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Utilities
|
||||
// =========
|
||||
|
||||
/**
|
||||
* Verify an ed25519 signature.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,7 @@ limitations under the License.
|
||||
var base = require("./base");
|
||||
|
||||
require("./olm");
|
||||
require("./megolm");
|
||||
|
||||
/**
|
||||
* @see module:crypto-algorithms/base.ENCRYPTION_CLASSES
|
||||
|
||||
171
lib/crypto-algorithms/megolm.js
Normal file
171
lib/crypto-algorithms/megolm.js
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
Copyright 2015, 2016 OpenMarket Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Defines m.olm encryption/decryption
|
||||
*
|
||||
* @module crypto-algorithms/olm
|
||||
*/
|
||||
|
||||
var q = require("q");
|
||||
|
||||
var utils = require("../utils");
|
||||
var base = require("./base");
|
||||
|
||||
var MEGOLM_ALGORITHM = "m.megolm.v1.aes-sha2";
|
||||
|
||||
/**
|
||||
* Megolm encryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto-algorithms/base.EncryptionAlgorithm}
|
||||
*
|
||||
* @param {string} deviceId The identifier for this device.
|
||||
* @param {module:crypto} crypto crypto core
|
||||
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||
*
|
||||
*/
|
||||
function MegolmEncryption(deviceId, crypto, olmDevice) {
|
||||
base.EncryptionAlgorithm.call(this, deviceId, crypto, olmDevice);
|
||||
this._outboundSessionId = null;
|
||||
}
|
||||
utils.inherits(MegolmEncryption, base.EncryptionAlgorithm);
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
* @param {string[]} roomMembers list of currently-joined users in the room
|
||||
* @return {module:client.Promise} Promise which resolves when setup is complete
|
||||
*/
|
||||
MegolmEncryption.prototype.initRoomEncryption = function(roomMembers) {
|
||||
// nothing to do here.
|
||||
return q();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {string} roomId
|
||||
*/
|
||||
MegolmEncryption.prototype._ensureOutboundSession = function(roomId) {
|
||||
if (this._outboundSessionId) {
|
||||
return;
|
||||
}
|
||||
var session_id = this._olmDevice.createOutboundGroupSession();
|
||||
this._outboundSessionId = session_id;
|
||||
|
||||
var key = this._olmDevice.getOutboundGroupSessionKey(session_id);
|
||||
|
||||
// TODO: initiate key-sharing
|
||||
|
||||
console.log(
|
||||
'Created outbound session. Add with window.mxMatrixClientPeg.' +
|
||||
'matrixClient._crypto._olmDevice.addInboundGroupSession("' +
|
||||
[
|
||||
roomId, this._olmDevice.deviceCurve25519Key, session_id,
|
||||
key.key, key.chain_index
|
||||
].join('", "') +
|
||||
'")'
|
||||
);
|
||||
|
||||
this._olmDevice.addInboundGroupSession(
|
||||
roomId, this._olmDevice.deviceCurve25519Key, session_id,
|
||||
key.key, key.chain_index
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {module:models/room} room
|
||||
* @param {string} eventType
|
||||
* @param {object} plaintext event content
|
||||
*
|
||||
* @return {object} new event body
|
||||
*/
|
||||
MegolmEncryption.prototype.encryptMessage = function(room, eventType, content) {
|
||||
this._ensureOutboundSession(room.roomId);
|
||||
|
||||
var payloadJson = {
|
||||
room_id: room.roomId,
|
||||
type: eventType,
|
||||
content: content
|
||||
};
|
||||
|
||||
var ciphertext = this._olmDevice.encryptGroupMessage(
|
||||
this._outboundSessionId, JSON.stringify(payloadJson)
|
||||
);
|
||||
|
||||
var encryptedContent = {
|
||||
algorithm: MEGOLM_ALGORITHM,
|
||||
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||
body: ciphertext,
|
||||
session_id: this._outboundSessionId,
|
||||
signature: "FIXME",
|
||||
};
|
||||
|
||||
return encryptedContent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Megolm decryption implementation
|
||||
*
|
||||
* @constructor
|
||||
* @extends {module:crypto-algorithms/base.DecryptionAlgorithm}
|
||||
*
|
||||
* @param {string} deviceId The identifier for this device.
|
||||
* @param {module:crypto} crypto crypto core
|
||||
* @param {module:OlmDevice} olmDevice olm.js wrapper
|
||||
*
|
||||
*/
|
||||
function MegolmDecryption(deviceId, crypto, olmDevice) {
|
||||
base.DecryptionAlgorithm.call(this, deviceId, crypto, olmDevice);
|
||||
}
|
||||
utils.inherits(MegolmDecryption, base.DecryptionAlgorithm);
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*
|
||||
* @param {object} event raw event
|
||||
*
|
||||
* @return {object} decrypted payload (with properties 'type', 'content')
|
||||
*
|
||||
* @throws {module:crypto-algorithms/base.DecryptionError} if there is a
|
||||
* problem decrypting the event
|
||||
*/
|
||||
MegolmDecryption.prototype.decryptEvent = function(event) {
|
||||
var content = event.content;
|
||||
|
||||
console.log("decrypting " + event.event_id + " with sid " +
|
||||
content.session_id);
|
||||
|
||||
if (!content.sender_key || !content.session_id ||
|
||||
!content.body || !content.signature
|
||||
) {
|
||||
throw new base.DecryptionError("Missing fields in input");
|
||||
}
|
||||
|
||||
try {
|
||||
var res = this._olmDevice.decryptGroupMessage(
|
||||
event.room_id, content.sender_key, content.session_id, content.body
|
||||
);
|
||||
return JSON.parse(res);
|
||||
} catch (e) {
|
||||
throw new base.DecryptionError(e);
|
||||
}
|
||||
};
|
||||
|
||||
base.registerAlgorithm(MEGOLM_ALGORITHM, MegolmEncryption, MegolmDecryption);
|
||||
@@ -104,6 +104,15 @@ WebStorageSessionStore.prototype = {
|
||||
return getJsonItem(this.store, keyEndToEndSessions(deviceKey));
|
||||
},
|
||||
|
||||
getEndToEndInboundGroupSession: function(senderKey, sessionId) {
|
||||
return this.store.getItem(keyEndToEndInboundGroupSession(senderKey, sessionId));
|
||||
},
|
||||
|
||||
storeEndToEndInboundGroupSession: function(senderKey, sessionId, pickledSession) {
|
||||
return this.store.setItem(keyEndToEndInboundGroupSession(senderKey, sessionId),
|
||||
pickledSession);
|
||||
},
|
||||
|
||||
/**
|
||||
* Store the end-to-end state for a room.
|
||||
* @param {string} roomId The room's ID.
|
||||
@@ -133,6 +142,10 @@ function keyEndToEndSessions(deviceKey) {
|
||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||
}
|
||||
|
||||
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
||||
return E2E_PREFIX + "inboundgroupsessions/" + senderKey + "/" + sessionId;
|
||||
}
|
||||
|
||||
function keyEndToEndRoom(roomId) {
|
||||
return E2E_PREFIX + "rooms/" + roomId;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user