You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-29 16:43:09 +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.deviceCurve25519Key = e2eKeys.curve25519;
|
||||||
this.deviceEd25519Key = e2eKeys.ed25519;
|
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.
|
* Verify an ed25519 signature.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ limitations under the License.
|
|||||||
var base = require("./base");
|
var base = require("./base");
|
||||||
|
|
||||||
require("./olm");
|
require("./olm");
|
||||||
|
require("./megolm");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see module:crypto-algorithms/base.ENCRYPTION_CLASSES
|
* @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));
|
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.
|
* Store the end-to-end state for a room.
|
||||||
* @param {string} roomId The room's ID.
|
* @param {string} roomId The room's ID.
|
||||||
@@ -133,6 +142,10 @@ function keyEndToEndSessions(deviceKey) {
|
|||||||
return E2E_PREFIX + "sessions/" + deviceKey;
|
return E2E_PREFIX + "sessions/" + deviceKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keyEndToEndInboundGroupSession(senderKey, sessionId) {
|
||||||
|
return E2E_PREFIX + "inboundgroupsessions/" + senderKey + "/" + sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
function keyEndToEndRoom(roomId) {
|
function keyEndToEndRoom(roomId) {
|
||||||
return E2E_PREFIX + "rooms/" + roomId;
|
return E2E_PREFIX + "rooms/" + roomId;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user