From e23deac1bb52d644c5b40149943685b92c321d04 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Fri, 20 Jan 2017 15:12:50 +0000 Subject: [PATCH] Implement e2e export --- package.json | 3 +- .../views/dialogs/ExportE2eKeysDialog.js | 175 +++++++++++++----- src/components/structures/UserSettings.js | 21 +++ src/index.js | 16 +- 4 files changed, 156 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 0a8c09f984..dabac0a060 100644 --- a/package.json +++ b/package.json @@ -47,10 +47,12 @@ "browser-encrypt-attachment": "^0.3.0", "browser-request": "^0.3.3", "classnames": "^2.1.2", + "commonmark": "^0.27.0", "draft-js": "^0.8.1", "draft-js-export-html": "^0.5.0", "draft-js-export-markdown": "^0.2.0", "emojione": "2.2.3", + "file-saver": "^1.3.3", "filesize": "^3.1.2", "flux": "^2.0.3", "fuse.js": "^2.2.0", @@ -59,7 +61,6 @@ "isomorphic-fetch": "^2.2.1", "linkifyjs": "^2.1.3", "lodash": "^4.13.1", - "commonmark": "^0.27.0", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "optimist": "^0.6.1", "q": "^1.4.1", diff --git a/src/async-components/views/dialogs/ExportE2eKeysDialog.js b/src/async-components/views/dialogs/ExportE2eKeysDialog.js index 284d299f4b..816b8eb73d 100644 --- a/src/async-components/views/dialogs/ExportE2eKeysDialog.js +++ b/src/async-components/views/dialogs/ExportE2eKeysDialog.js @@ -14,71 +14,158 @@ See the License for the specific language governing permissions and limitations under the License. */ +import FileSaver from 'file-saver'; import React from 'react'; +import * as Matrix from 'matrix-js-sdk'; +import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; import sdk from '../../../index'; -import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; +const PHASE_EDIT = 1; +const PHASE_EXPORTING = 2; export default React.createClass({ displayName: 'ExportE2eKeysDialog', + propTypes: { + matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired, + onFinished: React.PropTypes.func.isRequired, + }, + getInitialState: function() { return { - collectedPassword: false, + phase: PHASE_EDIT, + errStr: null, }; }, + componentWillMount: function() { + this._unmounted = false; + }, + + componentWillUnmount: function() { + this._unmounted = true; + }, + _onPassphraseFormSubmit: function(ev) { ev.preventDefault(); - console.log(this.refs.passphrase1.value); + + const passphrase = this.refs.passphrase1.value; + if (passphrase !== this.refs.passphrase2.value) { + this.setState({errStr: 'Passphrases must match'}); + return false; + } + if (!passphrase) { + this.setState({errStr: 'Passphrase must not be empty'}); + return false; + } + + this._startExport(passphrase); return false; }, - render: function() { - let content; - if (!this.state.collectedPassword) { - content = ( -
-

- This process will allow you to export the keys for messages - you have received in encrypted rooms to a local file. You - will then be able to import the file into another Matrix - client in the future, so that client will also be able to - decrypt these messages. -

-

- The exported file will allow anyone who can read it to decrypt - any encrypted messages that you can see, so you should be - careful to keep it secure. To help with this, you should enter - a passphrase below, which will be used to encrypt the exported - data. It will only be possible to import the data by using the - same passphrase. -

-
-
- -
-
- -
-
- -
-
-
+ _startExport: function(passphrase) { + // extra Promise.resolve() to turn synchronous exceptions into + // asynchronous ones. + Promise.resolve().then(() => { + return this.props.matrixClient.exportRoomKeys(); + }).then((k) => { + return MegolmExportEncryption.encryptMegolmKeyFile( + JSON.stringify(k), passphrase ); - } + }).then((f) => { + const blob = new Blob([f], { + type: 'text/plain;charset=us-ascii', + }); + FileSaver.saveAs(blob, 'riot-keys.txt'); + this.props.onFinished(true); + }).catch((e) => { + if (this._unmounted) { + return; + } + this.setState({ + errStr: e.message, + phase: PHASE_EDIT, + }); + }); + + this.setState({ + errStr: null, + phase: PHASE_EXPORTING, + }); + }, + + render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton'); + + const disableForm = (this.state.phase === PHASE_EXPORTING); return ( -
-
- Export room keys -
- {content} -
+ +
+
+

+ This process allows you to export the keys for messages + you have received in encrypted rooms to a local file. You + will then be able to import the file into another Matrix + client in the future, so that client will also be able to + decrypt these messages. +

+

+ The exported file will allow anyone who can read it to decrypt + any encrypted messages that you can see, so you should be + careful to keep it secure. To help with this, you should enter + a passphrase below, which will be used to encrypt the exported + data. It will only be possible to import the data by using the + same passphrase. +

+
+ {this.state.errStr} +
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + + Cancel + +
+
+
); }, }); diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js index ba5ab49bbc..6edf572f6c 100644 --- a/src/components/structures/UserSettings.js +++ b/src/components/structures/UserSettings.js @@ -393,6 +393,16 @@ module.exports = React.createClass({ }).done(); }, + _onExportE2eKeysClicked: function() { + Modal.createDialogAsync( + (cb) => { + require(['../../async-components/views/dialogs/ExportE2eKeysDialog'], cb); + }, { + matrixClient: MatrixClientPeg.get(), + } + ); + }, + _renderUserInterfaceSettings: function() { var client = MatrixClientPeg.get(); @@ -463,6 +473,16 @@ module.exports = React.createClass({ const deviceId = client.deviceId; const identityKey = client.getDeviceEd25519Key() || ""; + let exportButton = null; + + if (client.isCryptoEnabled) { + exportButton = ( + + Export E2E room keys + + ); + } return (

Cryptography

@@ -471,6 +491,7 @@ module.exports = React.createClass({
  • {deviceId}
  • {identityKey}
  • + {exportButton}
    ); diff --git a/src/index.js b/src/index.js index 5d4145a39b..0e3e90aed6 100644 --- a/src/index.js +++ b/src/index.js @@ -29,20 +29,7 @@ module.exports.getComponent = function(componentName) { }; -/* hacky functions for megolm import/export until we give it a UI */ -import * as MegolmExportEncryption from './utils/MegolmExportEncryption'; -import MatrixClientPeg from './MatrixClientPeg'; - -window.exportKeys = function(password) { - return MatrixClientPeg.get().exportRoomKeys().then((k) => { - return MegolmExportEncryption.encryptMegolmKeyFile( - JSON.stringify(k), password - ); - }).then((f) => { - console.log(new TextDecoder().decode(new Uint8Array(f))); - }).done(); -}; - +/* window.importKeys = function(password, data) { const arrayBuffer = new TextEncoder().encode(data).buffer; return MegolmExportEncryption.decryptMegolmKeyFile( @@ -52,3 +39,4 @@ window.importKeys = function(password, data) { return MatrixClientPeg.get().importRoomKeys(k); }); }; +*/