Implement e2e export

This commit is contained in:
Richard van der Hoff 2017-01-20 15:12:50 +00:00
parent 86276450f6
commit e23deac1bb
4 changed files with 156 additions and 59 deletions

View file

@ -47,10 +47,12 @@
"browser-encrypt-attachment": "^0.3.0", "browser-encrypt-attachment": "^0.3.0",
"browser-request": "^0.3.3", "browser-request": "^0.3.3",
"classnames": "^2.1.2", "classnames": "^2.1.2",
"commonmark": "^0.27.0",
"draft-js": "^0.8.1", "draft-js": "^0.8.1",
"draft-js-export-html": "^0.5.0", "draft-js-export-html": "^0.5.0",
"draft-js-export-markdown": "^0.2.0", "draft-js-export-markdown": "^0.2.0",
"emojione": "2.2.3", "emojione": "2.2.3",
"file-saver": "^1.3.3",
"filesize": "^3.1.2", "filesize": "^3.1.2",
"flux": "^2.0.3", "flux": "^2.0.3",
"fuse.js": "^2.2.0", "fuse.js": "^2.2.0",
@ -59,7 +61,6 @@
"isomorphic-fetch": "^2.2.1", "isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3", "linkifyjs": "^2.1.3",
"lodash": "^4.13.1", "lodash": "^4.13.1",
"commonmark": "^0.27.0",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop", "matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1", "optimist": "^0.6.1",
"q": "^1.4.1", "q": "^1.4.1",

View file

@ -14,34 +14,102 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
import FileSaver from 'file-saver';
import React from 'react'; import React from 'react';
import * as Matrix from 'matrix-js-sdk';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption';
import sdk from '../../../index'; import sdk from '../../../index';
import * as MegolmExportEncryption from '../../../utils/MegolmExportEncryption'; const PHASE_EDIT = 1;
const PHASE_EXPORTING = 2;
export default React.createClass({ export default React.createClass({
displayName: 'ExportE2eKeysDialog', displayName: 'ExportE2eKeysDialog',
propTypes: {
matrixClient: React.PropTypes.instanceOf(Matrix.MatrixClient).isRequired,
onFinished: React.PropTypes.func.isRequired,
},
getInitialState: function() { getInitialState: function() {
return { return {
collectedPassword: false, phase: PHASE_EDIT,
errStr: null,
}; };
}, },
componentWillMount: function() {
this._unmounted = false;
},
componentWillUnmount: function() {
this._unmounted = true;
},
_onPassphraseFormSubmit: function(ev) { _onPassphraseFormSubmit: function(ev) {
ev.preventDefault(); 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; return false;
}, },
_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() { render: function() {
let content; const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog');
if (!this.state.collectedPassword) { const AccessibleButton = sdk.getComponent('views.elements.AccessibleButton');
content = (
const disableForm = (this.state.phase === PHASE_EXPORTING);
return (
<BaseDialog className='mx_exportE2eKeysDialog'
onFinished={this.props.onFinished}
title="Export room keys"
>
<form onSubmit={this._onPassphraseFormSubmit}>
<div className="mx_Dialog_content"> <div className="mx_Dialog_content">
<p> <p>
This process will allow you to export the keys for messages This process allows you to export the keys for messages
you have received in encrypted rooms to a local file. You you have received in encrypted rooms to a local file. You
will then be able to import the file into another Matrix will then be able to import the file into another Matrix
client in the future, so that client will also be able to client in the future, so that client will also be able to
@ -55,30 +123,49 @@ export default React.createClass({
data. It will only be possible to import the data by using the data. It will only be possible to import the data by using the
same passphrase. same passphrase.
</p> </p>
<form onSubmit={this._onPassphraseFormSubmit}> <div className='error'>
<div className="mx_TextInputDialog_label"> {this.state.errStr}
<label htmlFor="passphrase1">Enter passphrase</label>
</div> </div>
<div> <div className='mx_E2eKeysDialog_inputTable'>
<input ref="passphrase1" id="passphrase1" <div className='mx_E2eKeysDialog_inputRow'>
className="mx_TextInputDialog_input" <div className='mx_E2eKeysDialog_inputLabel'>
autoFocus={true} size="64" type="password"/> <label htmlFor='passphrase1'>
Enter passphrase
</label>
</div> </div>
<div className="mx_Dialog_buttons"> <div className='mx_E2eKeysDialog_inputCell'>
<input className="mx_Dialog_primary" type="submit" value="Export" /> <input ref='passphrase1' id='passphrase1'
autoFocus={true} size='64' type='password'
disabled={disableForm}
/>
</div>
</div>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase2'>
Confirm passphrase
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<input ref='passphrase2' id='passphrase2'
size='64' type='password'
disabled={disableForm}
/>
</div>
</div>
</div>
</div>
<div className='mx_Dialog_buttons'>
<input className='mx_Dialog_primary' type='submit' value='Export'
disabled={disableForm}
/>
<AccessibleButton element='button' onClick={this.props.onFinished}
disabled={disableForm}>
Cancel
</AccessibleButton>
</div> </div>
</form> </form>
</div> </BaseDialog>
);
}
return (
<div className="mx_exportE2eKeysDialog">
<div className="mx_Dialog_title">
Export room keys
</div>
{content}
</div>
); );
}, },
}); });

View file

@ -393,6 +393,16 @@ module.exports = React.createClass({
}).done(); }).done();
}, },
_onExportE2eKeysClicked: function() {
Modal.createDialogAsync(
(cb) => {
require(['../../async-components/views/dialogs/ExportE2eKeysDialog'], cb);
}, {
matrixClient: MatrixClientPeg.get(),
}
);
},
_renderUserInterfaceSettings: function() { _renderUserInterfaceSettings: function() {
var client = MatrixClientPeg.get(); var client = MatrixClientPeg.get();
@ -463,6 +473,16 @@ module.exports = React.createClass({
const deviceId = client.deviceId; const deviceId = client.deviceId;
const identityKey = client.getDeviceEd25519Key() || "<not supported>"; const identityKey = client.getDeviceEd25519Key() || "<not supported>";
let exportButton = null;
if (client.isCryptoEnabled) {
exportButton = (
<AccessibleButton className="mx_UserSettings_button"
onClick={this._onExportE2eKeysClicked}>
Export E2E room keys
</AccessibleButton>
);
}
return ( return (
<div> <div>
<h3>Cryptography</h3> <h3>Cryptography</h3>
@ -471,6 +491,7 @@ module.exports = React.createClass({
<li><label>Device ID:</label> <span><code>{deviceId}</code></span></li> <li><label>Device ID:</label> <span><code>{deviceId}</code></span></li>
<li><label>Device key:</label> <span><code><b>{identityKey}</b></code></span></li> <li><label>Device key:</label> <span><code><b>{identityKey}</b></code></span></li>
</ul> </ul>
{exportButton}
</div> </div>
</div> </div>
); );

View file

@ -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) { window.importKeys = function(password, data) {
const arrayBuffer = new TextEncoder().encode(data).buffer; const arrayBuffer = new TextEncoder().encode(data).buffer;
return MegolmExportEncryption.decryptMegolmKeyFile( return MegolmExportEncryption.decryptMegolmKeyFile(
@ -52,3 +39,4 @@ window.importKeys = function(password, data) {
return MatrixClientPeg.get().importRoomKeys(k); return MatrixClientPeg.get().importRoomKeys(k);
}); });
}; };
*/