You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			256 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			256 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			TypeScript
		
	
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';
 | 
						|
import { DefaultTheme, withTheme } from 'styled-components';
 | 
						|
 | 
						|
interface Props {
 | 
						|
  conversation: ConversationModel;
 | 
						|
  theme: DefaultTheme;
 | 
						|
}
 | 
						|
 | 
						|
interface State {
 | 
						|
  loading: boolean;
 | 
						|
  error?: 'verificationKeysLoadFail';
 | 
						|
  securityNumber?: string;
 | 
						|
  isVerified?: boolean;
 | 
						|
}
 | 
						|
 | 
						|
class SessionKeyVerificationInner extends React.Component<Props, State> {
 | 
						|
  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 (
 | 
						|
        <div className="key-verification">
 | 
						|
          <SessionSpinner loading={this.state.loading} />
 | 
						|
        </div>
 | 
						|
      );
 | 
						|
    }
 | 
						|
 | 
						|
    const verificationIconColor = isVerified
 | 
						|
      ? Constants.UI.COLORS.GREEN
 | 
						|
      : Constants.UI.COLORS.DANGER;
 | 
						|
    const verificationButtonColor = isVerified
 | 
						|
      ? SessionButtonColor.Warning
 | 
						|
      : SessionButtonColor.Success;
 | 
						|
    const verificationButton = (
 | 
						|
      <SessionButton
 | 
						|
        buttonType={SessionButtonType.DefaultOutline}
 | 
						|
        buttonColor={verificationButtonColor}
 | 
						|
        onClick={this.toggleVerification}
 | 
						|
      >
 | 
						|
        {window.i18n(isVerified ? 'unverify' : 'verify')}
 | 
						|
      </SessionButton>
 | 
						|
    );
 | 
						|
 | 
						|
    return (
 | 
						|
      <div className="key-verification">
 | 
						|
        {this.state.error ? (
 | 
						|
          <h3>{window.i18n(this.state.error)}</h3>
 | 
						|
        ) : (
 | 
						|
          <>
 | 
						|
            <div className={classNames('key-verification__header')}>
 | 
						|
              <h2>{window.i18n('safetyNumber')}</h2>
 | 
						|
              <small>{theirPubkey}</small>
 | 
						|
            </div>
 | 
						|
 | 
						|
            <div
 | 
						|
              className={classNames(
 | 
						|
                'key-verification__key',
 | 
						|
                'session-info-box'
 | 
						|
              )}
 | 
						|
            >
 | 
						|
              {this.renderSecurityNumber()}
 | 
						|
            </div>
 | 
						|
 | 
						|
            <div className="key-verification__help">
 | 
						|
              {window.i18n('verifyHelp', theirName)}
 | 
						|
            </div>
 | 
						|
 | 
						|
            <div className="key-verification__is-verified">
 | 
						|
              <span>
 | 
						|
                <SessionIcon
 | 
						|
                  iconType={SessionIconType.Lock}
 | 
						|
                  iconSize={SessionIconSize.Huge}
 | 
						|
                  iconColor={verificationIconColor}
 | 
						|
                  theme={this.props.theme}
 | 
						|
                />
 | 
						|
                {window.i18n(
 | 
						|
                  isVerified ? 'isVerified' : 'isNotVerified',
 | 
						|
                  theirName
 | 
						|
                )}
 | 
						|
              </span>
 | 
						|
 | 
						|
              {verificationButton}
 | 
						|
            </div>
 | 
						|
          </>
 | 
						|
        )}
 | 
						|
      </div>
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  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<string | undefined> {
 | 
						|
    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<JSX.Element> | 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 => (
 | 
						|
      <div key={line}>{line}</div>
 | 
						|
    ));
 | 
						|
    return securityNumberElement;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
export const SessionKeyVerification = withTheme(SessionKeyVerificationInner);
 |