Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

QR code reciprocation #4334

Merged
merged 11 commits into from
Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 33 additions & 9 deletions res/css/views/right_panel/_VerificationPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_VerificationPanel_verified_section,
.mx_VerificationPanel_reciprocate_section {
// center the big shield icon
.mx_E2EIcon {
// Override general user info margin
margin: 20px auto !important;
}
}


.mx_UserInfo {
.mx_EncryptionPanel_cancel {
mask: url('$(res)/img/feather-customised/cancel.svg');
Expand All @@ -30,11 +40,6 @@ limitations under the License.
right: 14px;
}

.mx_VerificationPanel_verified_section .mx_E2EIcon {
// Override general user info margin
margin: 0 auto !important;
}

.mx_VerificationPanel_qrCode {
padding: 4px 4px 0 4px;
background: white;
Expand All @@ -51,6 +56,16 @@ limitations under the License.
max-width: 240px;
}
}

.mx_VerificationPanel_reciprocate_section {
.mx_FormButton {
width: 100%;
box-sizing: border-box;
padding: 10px;
display: block;
margin: 10px 0;
}
}
}

// Special case styling for EncryptionPanel in a Modal dialog
Expand Down Expand Up @@ -109,13 +124,22 @@ limitations under the License.

// EncryptionPanel when verification is done
.mx_VerificationPanel_verified_section {
// center the big shield icon
.mx_E2EIcon {
margin: 0 auto;
}
// right align the "Got it" button
.mx_AccessibleButton {
float: right;
}
}

.mx_VerificationPanel_reciprocate_section {
.mx_AccessibleButton {
margin-left: 10px;
padding: 7px 40px;
}

.mx_VerificationPanel_reciprocateButtons {
display: flex;
flex-direction: row;
justify-content: flex-end;
}
}
}
113 changes: 2 additions & 111 deletions src/components/views/elements/crypto/VerificationQRCode.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,95 +17,17 @@ limitations under the License.
import React from "react";
import PropTypes from "prop-types";
import {replaceableComponent} from "../../../../utils/replaceableComponent";
import {MatrixClientPeg} from "../../../../MatrixClientPeg";
import {VerificationRequest} from "matrix-js-sdk/src/crypto/verification/request/VerificationRequest";
import {ToDeviceChannel} from "matrix-js-sdk/src/crypto/verification/request/ToDeviceChannel";
import {decodeBase64} from "matrix-js-sdk/src/crypto/olmlib";
import Spinner from "../Spinner";
import * as QRCode from "qrcode";

const CODE_VERSION = 0x02; // the version of binary QR codes we support
const BINARY_PREFIX = "MATRIX"; // ASCII, used to prefix the binary format
const MODE_VERIFY_OTHER_USER = 0x00; // Verifying someone who isn't us
const MODE_VERIFY_SELF_TRUSTED = 0x01; // We trust the master key
const MODE_VERIFY_SELF_UNTRUSTED = 0x02; // We do not trust the master key

@replaceableComponent("views.elements.crypto.VerificationQRCode")
export default class VerificationQRCode extends React.PureComponent {
static propTypes = {
prefix: PropTypes.string.isRequired,
version: PropTypes.number.isRequired,
mode: PropTypes.number.isRequired,
transactionId: PropTypes.string.isRequired, // or requestEventId
firstKeyB64: PropTypes.string.isRequired,
secondKeyB64: PropTypes.string.isRequired,
secretB64: PropTypes.string.isRequired,
qrCodeData: PropTypes.object.isRequired,
};

static async getPropsForRequest(verificationRequest: VerificationRequest) {
const cli = MatrixClientPeg.get();
const myUserId = cli.getUserId();
const otherUserId = verificationRequest.otherUserId;

let mode = MODE_VERIFY_OTHER_USER;
if (myUserId === otherUserId) {
// Mode changes depending on whether or not we trust the master cross signing key
const myTrust = cli.checkUserTrust(myUserId);
if (myTrust.isCrossSigningVerified()) {
mode = MODE_VERIFY_SELF_TRUSTED;
} else {
mode = MODE_VERIFY_SELF_UNTRUSTED;
}
}

const requestEvent = verificationRequest.requestEvent;
const transactionId = requestEvent.getId()
? requestEvent.getId()
: ToDeviceChannel.getTransactionId(requestEvent);

const qrProps = {
prefix: BINARY_PREFIX,
version: CODE_VERSION,
mode,
transactionId,
firstKeyB64: '', // worked out shortly
secondKeyB64: '', // worked out shortly
secretB64: verificationRequest.encodedSharedSecret,
};

const myCrossSigningInfo = cli.getStoredCrossSigningForUser(myUserId);
const myDevices = (await cli.getStoredDevicesForUser(myUserId)) || [];

if (mode === MODE_VERIFY_OTHER_USER) {
// First key is our master cross signing key
qrProps.firstKeyB64 = myCrossSigningInfo.getId("master");

// Second key is the other user's master cross signing key
const otherUserCrossSigningInfo = cli.getStoredCrossSigningForUser(otherUserId);
qrProps.secondKeyB64 = otherUserCrossSigningInfo.getId("master");
} else if (mode === MODE_VERIFY_SELF_TRUSTED) {
// First key is our master cross signing key
qrProps.firstKeyB64 = myCrossSigningInfo.getId("master");

// Second key is the other device's device key
const otherDevice = verificationRequest.targetDevice;
const otherDeviceId = otherDevice ? otherDevice.deviceId : null;
const device = myDevices.find(d => d.deviceId === otherDeviceId);
qrProps.secondKeyB64 = device.getFingerprint();
} else if (mode === MODE_VERIFY_SELF_UNTRUSTED) {
// First key is our device's key
qrProps.firstKeyB64 = cli.getDeviceEd25519Key();

// Second key is what we think our master cross signing key is
qrProps.secondKeyB64 = myCrossSigningInfo.getId("master");
}

return qrProps;
}

constructor(props) {
super(props);

this.state = {
dataUri: null,
};
Expand All @@ -119,39 +41,8 @@ export default class VerificationQRCode extends React.PureComponent {
}

async generateQrCode() {
let buf = Buffer.alloc(0); // we'll concat our way through life

const appendByte = (b: number) => {
const tmpBuf = Buffer.from([b]);
buf = Buffer.concat([buf, tmpBuf]);
};
const appendInt = (i: number) => {
const tmpBuf = Buffer.alloc(2);
tmpBuf.writeInt16BE(i, 0);
buf = Buffer.concat([buf, tmpBuf]);
};
const appendStr = (s: string, enc: string, withLengthPrefix = true) => {
const tmpBuf = Buffer.from(s, enc);
if (withLengthPrefix) appendInt(tmpBuf.byteLength);
buf = Buffer.concat([buf, tmpBuf]);
};
const appendEncBase64 = (b64: string) => {
const b = decodeBase64(b64);
const tmpBuf = Buffer.from(b);
buf = Buffer.concat([buf, tmpBuf]);
};

// Actually build the buffer for the QR code
appendStr(this.props.prefix, "ascii", false);
appendByte(this.props.version);
appendByte(this.props.mode);
appendStr(this.props.transactionId, "utf-8");
appendEncBase64(this.props.firstKeyB64);
appendEncBase64(this.props.secondKeyB64);
appendEncBase64(this.props.secretB64);

// Now actually assemble the QR code's data URI
const uri = await QRCode.toDataURL([{data: buf, mode: 'byte'}], {
const uri = await QRCode.toDataURL([{data: this.props.qrCodeData.buffer, mode: 'byte'}], {
errorCorrectionLevel: 'L', // we want it as trivial-looking as possible
});
this.setState({dataUri: uri});
Expand Down
Loading