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,71 +14,158 @@ 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;
}, },
render: function() { _startExport: function(passphrase) {
let content; // extra Promise.resolve() to turn synchronous exceptions into
if (!this.state.collectedPassword) { // asynchronous ones.
content = ( Promise.resolve().then(() => {
<div className="mx_Dialog_content"> return this.props.matrixClient.exportRoomKeys();
<p> }).then((k) => {
This process will allow you to export the keys for messages return MegolmExportEncryption.encryptMegolmKeyFile(
you have received in encrypted rooms to a local file. You JSON.stringify(k), passphrase
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.
</p>
<p>
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.
</p>
<form onSubmit={this._onPassphraseFormSubmit}>
<div className="mx_TextInputDialog_label">
<label htmlFor="passphrase1">Enter passphrase</label>
</div>
<div>
<input ref="passphrase1" id="passphrase1"
className="mx_TextInputDialog_input"
autoFocus={true} size="64" type="password"/>
</div>
<div className="mx_Dialog_buttons">
<input className="mx_Dialog_primary" type="submit" value="Export" />
</div>
</form>
</div>
); );
} }).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 ( return (
<div className="mx_exportE2eKeysDialog"> <BaseDialog className='mx_exportE2eKeysDialog'
<div className="mx_Dialog_title"> onFinished={this.props.onFinished}
Export room keys title="Export room keys"
</div> >
{content} <form onSubmit={this._onPassphraseFormSubmit}>
</div> <div className="mx_Dialog_content">
<p>
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.
</p>
<p>
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.
</p>
<div className='error'>
{this.state.errStr}
</div>
<div className='mx_E2eKeysDialog_inputTable'>
<div className='mx_E2eKeysDialog_inputRow'>
<div className='mx_E2eKeysDialog_inputLabel'>
<label htmlFor='passphrase1'>
Enter passphrase
</label>
</div>
<div className='mx_E2eKeysDialog_inputCell'>
<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>
</form>
</BaseDialog>
); );
}, },
}); });

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);
}); });
}; };
*/