import React from 'react'; import * as _ from 'lodash'; import { SessionButton, SessionButtonColor, SessionButtonType, } from './SessionButton'; import { UserUtil } from '../../util'; import { MultiDeviceProtocol } from '../../session/protocols'; import { PubKey } from '../../session/types'; import { ConversationModel } from '../../../js/models/conversations'; import { SessionSpinner } from './SessionSpinner'; import classNames from 'classnames'; import { SessionIcon, SessionIconSize, SessionIconType } from './icon'; import { Constants } from '../../session'; interface Props { conversation: ConversationModel; } interface State { loading: boolean; error?: 'verificationKeysLoadFail'; securityNumber?: string; isVerified?: boolean; } export class SessionKeyVerification extends React.Component { constructor(props: any) { super(props); this.state = { loading: true, error: undefined, securityNumber: undefined, isVerified: this.props.conversation.isVerified(), }; this.toggleVerification = this.toggleVerification.bind(this); this.onSafetyNumberChanged = this.onSafetyNumberChanged.bind(this); } public async componentWillMount() { const securityNumber = await this.generateSecurityNumber(); if (!securityNumber) { this.setState({ error: 'verificationKeysLoadFail', }); return; } // Finished loading this.setState({ loading: false, securityNumber, }); } public render() { const theirName = this.props.conversation.attributes.profileName; const theirPubkey = this.props.conversation.id; const isVerified = this.props.conversation.isVerified(); if (this.state.loading) { return (
); } const verificationIconColor = isVerified ? Constants.UI.COLORS.GREEN : Constants.UI.COLORS.DANGER; const verificationButtonColor = isVerified ? SessionButtonColor.Warning : SessionButtonColor.Success; const verificationButton = ( {window.i18n(isVerified ? 'unverify' : 'verify')} ); return (
{this.state.error ? (

{window.i18n(this.state.error)}

) : ( <>

{window.i18n('safetyNumber')}

{theirPubkey}
{this.renderSecurityNumber()}
{window.i18n('verifyHelp', theirName)}
{window.i18n( isVerified ? 'isVerified' : 'isNotVerified', theirName )} {verificationButton}
)}
); } public async onSafetyNumberChanged() { const conversationModel = this.props.conversation; await conversationModel.getProfiles(); const securityNumber = await this.generateSecurityNumber(); this.setState({ securityNumber }); window.confirmationDialog({ title: window.i18n('changedSinceVerifiedTitle'), message: window.i18n('changedRightAfterVerify', [ conversationModel.attributes.profileName, conversationModel.attributes.profileName, ]), hideCancel: true, }); } private async generateSecurityNumber(): Promise { const ourDeviceKey = await UserUtil.getCurrentDevicePubKey(); if (!ourDeviceKey) { this.setState({ error: 'verificationKeysLoadFail', }); return; } const conversationId = this.props.conversation.id; const ourPrimaryKey = ( await MultiDeviceProtocol.getPrimaryDevice(PubKey.cast(ourDeviceKey)) ).key; // Grab identity keys const ourIdentityKey = await window.textsecure.storage.protocol.loadIdentityKey( ourPrimaryKey ); const theirIdentityKey = await window.textsecure.storage.protocol.loadIdentityKey( this.props.conversation.id ); if (!ourIdentityKey || !theirIdentityKey) { return; } // Generate security number const fingerprintGenerator = new window.libsignal.FingerprintGenerator( 5200 ); return fingerprintGenerator.createFor( ourPrimaryKey, ourIdentityKey, conversationId, theirIdentityKey ); } private async toggleVerification() { const conversationModel = this.props.conversation; try { await conversationModel.toggleVerified(); this.setState({ isVerified: !this.state.isVerified }); await conversationModel.getProfiles(); } catch (e) { if (e instanceof Error) { if (e.name === 'OutgoingIdentityKeyError') { await this.onSafetyNumberChanged(); } else { window.log.error( 'failed to toggle verified:', e && e.stack ? e.stack : e ); } } else { const keyError = _.some( e.errors, error => error.name === 'OutgoingIdentityKeyError' ); if (keyError) { await this.onSafetyNumberChanged(); } else { _.forEach(e.errors, error => { window.log.error( 'failed to toggle verified:', error && error.stack ? error.stack : error ); }); } } } } private renderSecurityNumber(): Array | undefined { // Turns 32813902154726601686003948952478 ... // into 32813 90215 47266 ... const { loading, securityNumber } = this.state; if (loading) { return; } const securityNumberChunks = _.chunk( Array.from(securityNumber ?? []), 5 ).map(chunk => chunk.join('')); const securityNumberLines = _.chunk(securityNumberChunks, 4).map(chunk => chunk.join(' ') ); const securityNumberElement = securityNumberLines.map(line => (
{line}
)); return securityNumberElement; } }