"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.SHOW_QR_CODE_METHOD = exports.SCAN_QR_CODE_METHOD = exports.ReciprocateQRCode = exports.QrCodeEvent = exports.QRCodeData = void 0;
var _Base = require("./Base");
var _Error = require("./Error");
var _olmlib = require("../olmlib");
var _logger = require("../../logger");
var _verification = require("../../crypto-api/verification");
function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return typeof key === "symbol" ? key : String(key); }
function _toPrimitive(input, hint) { if (typeof input !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (typeof res !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } /*
                                                                                                                                                                                                                                                                                                                                                                                          Copyright 2018 - 2021 The Matrix.org Foundation C.I.C.
                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                          Licensed under the Apache License, Version 2.0 (the "License");
                                                                                                                                                                                                                                                                                                                                                                                          you may not use this file except in compliance with the License.
                                                                                                                                                                                                                                                                                                                                                                                          You may obtain a copy of the License at
                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                              http://www.apache.org/licenses/LICENSE-2.0
                                                                                                                                                                                                                                                                                                                                                                                          
                                                                                                                                                                                                                                                                                                                                                                                          Unless required by applicable law or agreed to in writing, software
                                                                                                                                                                                                                                                                                                                                                                                          distributed under the License is distributed on an "AS IS" BASIS,
                                                                                                                                                                                                                                                                                                                                                                                          WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                                                                                                                                                                                                                                                                                                                                                                                          See the License for the specific language governing permissions and
                                                                                                                                                                                                                                                                                                                                                                                          limitations under the License.
                                                                                                                                                                                                                                                                                                                                                                                          */ /**
                                                                                                                                                                                                                                                                                                                                                                                              * QR code key verification.
                                                                                                                                                                                                                                                                                                                                                                                              */
const SHOW_QR_CODE_METHOD = "m.qr_code.show.v1";
exports.SHOW_QR_CODE_METHOD = SHOW_QR_CODE_METHOD;
const SCAN_QR_CODE_METHOD = "m.qr_code.scan.v1";

/** @deprecated use VerifierEvent */
exports.SCAN_QR_CODE_METHOD = SCAN_QR_CODE_METHOD;
/** @deprecated use VerifierEvent */
const QrCodeEvent = _verification.VerifierEvent;
exports.QrCodeEvent = QrCodeEvent;
class ReciprocateQRCode extends _Base.VerificationBase {
  constructor(...args) {
    super(...args);
    _defineProperty(this, "reciprocateQREvent", void 0);
    _defineProperty(this, "doVerification", async () => {
      if (!this.startEvent) {
        // TODO: Support scanning QR codes
        throw new Error("It is not currently possible to start verification" + "with this method yet.");
      }
      const {
        qrCodeData
      } = this.request;
      // 1. check the secret
      if (this.startEvent.getContent()["secret"] !== qrCodeData?.encodedSharedSecret) {
        throw (0, _Error.newKeyMismatchError)();
      }

      // 2. ask if other user shows shield as well
      await new Promise((resolve, reject) => {
        this.reciprocateQREvent = {
          confirm: resolve,
          cancel: () => reject((0, _Error.newUserCancelledError)())
        };
        this.emit(QrCodeEvent.ShowReciprocateQr, this.reciprocateQREvent);
      });

      // 3. determine key to sign / mark as trusted
      const keys = {};
      switch (qrCodeData?.mode) {
        case Mode.VerifyOtherUser:
          {
            // add master key to keys to be signed, only if we're not doing self-verification
            const masterKey = qrCodeData.otherUserMasterKey;
            keys[`ed25519:${masterKey}`] = masterKey;
            break;
          }
        case Mode.VerifySelfTrusted:
          {
            const deviceId = this.request.targetDevice.deviceId;
            keys[`ed25519:${deviceId}`] = qrCodeData.otherDeviceKey;
            break;
          }
        case Mode.VerifySelfUntrusted:
          {
            const masterKey = qrCodeData.myMasterKey;
            keys[`ed25519:${masterKey}`] = masterKey;
            break;
          }
      }

      // 4. sign the key (or mark own MSK as verified in case of MODE_VERIFY_SELF_TRUSTED)
      await this.verifyKeys(this.userId, keys, (keyId, device, keyInfo) => {
        // make sure the device has the expected keys
        const targetKey = keys[keyId];
        if (!targetKey) throw (0, _Error.newKeyMismatchError)();
        if (keyInfo !== targetKey) {
          _logger.logger.error("key ID from key info does not match");
          throw (0, _Error.newKeyMismatchError)();
        }
        for (const deviceKeyId in device.keys) {
          if (!deviceKeyId.startsWith("ed25519")) continue;
          const deviceTargetKey = keys[deviceKeyId];
          if (!deviceTargetKey) throw (0, _Error.newKeyMismatchError)();
          if (device.keys[deviceKeyId] !== deviceTargetKey) {
            _logger.logger.error("master key does not match");
            throw (0, _Error.newKeyMismatchError)();
          }
        }
      });
    });
  }
  static factory(channel, baseApis, userId, deviceId, startEvent, request) {
    return new ReciprocateQRCode(channel, baseApis, userId, deviceId, startEvent, request);
  }

  // eslint-disable-next-line @typescript-eslint/naming-convention
  static get NAME() {
    return "m.reciprocate.v1";
  }
}
exports.ReciprocateQRCode = ReciprocateQRCode;
const CODE_VERSION = 0x02; // the version of binary QR codes we support
const BINARY_PREFIX = "MATRIX"; // ASCII, used to prefix the binary format
var Mode = /*#__PURE__*/function (Mode) {
  Mode[Mode["VerifyOtherUser"] = 0] = "VerifyOtherUser";
  Mode[Mode["VerifySelfTrusted"] = 1] = "VerifySelfTrusted";
  Mode[Mode["VerifySelfUntrusted"] = 2] = "VerifySelfUntrusted";
  return Mode;
}(Mode || {}); // We do not trust the master key
class QRCodeData {
  constructor(mode, sharedSecret,
  // only set when mode is MODE_VERIFY_OTHER_USER, master key of other party at time of generating QR code
  otherUserMasterKey,
  // only set when mode is MODE_VERIFY_SELF_TRUSTED, device key of other party at time of generating QR code
  otherDeviceKey,
  // only set when mode is MODE_VERIFY_SELF_UNTRUSTED, own master key at time of generating QR code
  myMasterKey, buffer) {
    this.mode = mode;
    this.sharedSecret = sharedSecret;
    this.otherUserMasterKey = otherUserMasterKey;
    this.otherDeviceKey = otherDeviceKey;
    this.myMasterKey = myMasterKey;
    this.buffer = buffer;
  }
  static async create(request, client) {
    const sharedSecret = QRCodeData.generateSharedSecret();
    const mode = QRCodeData.determineMode(request, client);
    let otherUserMasterKey = null;
    let otherDeviceKey = null;
    let myMasterKey = null;
    if (mode === Mode.VerifyOtherUser) {
      const otherUserCrossSigningInfo = client.getStoredCrossSigningForUser(request.otherUserId);
      otherUserMasterKey = otherUserCrossSigningInfo.getId("master");
    } else if (mode === Mode.VerifySelfTrusted) {
      otherDeviceKey = await QRCodeData.getOtherDeviceKey(request, client);
    } else if (mode === Mode.VerifySelfUntrusted) {
      const myUserId = client.getUserId();
      const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId);
      myMasterKey = myCrossSigningInfo.getId("master");
    }
    const qrData = QRCodeData.generateQrData(request, client, mode, sharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey);
    const buffer = QRCodeData.generateBuffer(qrData);
    return new QRCodeData(mode, sharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey, buffer);
  }

  /**
   * The unpadded base64 encoded shared secret.
   */
  get encodedSharedSecret() {
    return this.sharedSecret;
  }
  getBuffer() {
    return this.buffer;
  }
  static generateSharedSecret() {
    const secretBytes = new Uint8Array(11);
    global.crypto.getRandomValues(secretBytes);
    return (0, _olmlib.encodeUnpaddedBase64)(secretBytes);
  }
  static async getOtherDeviceKey(request, client) {
    const myUserId = client.getUserId();
    const otherDevice = request.targetDevice;
    const device = otherDevice.deviceId ? client.getStoredDevice(myUserId, otherDevice.deviceId) : undefined;
    if (!device) {
      throw new Error("could not find device " + otherDevice?.deviceId);
    }
    return device.getFingerprint();
  }
  static determineMode(request, client) {
    const myUserId = client.getUserId();
    const otherUserId = request.otherUserId;
    let mode = Mode.VerifyOtherUser;
    if (myUserId === otherUserId) {
      // Mode changes depending on whether or not we trust the master cross signing key
      const myTrust = client.checkUserTrust(myUserId);
      if (myTrust.isCrossSigningVerified()) {
        mode = Mode.VerifySelfTrusted;
      } else {
        mode = Mode.VerifySelfUntrusted;
      }
    }
    return mode;
  }
  static generateQrData(request, client, mode, encodedSharedSecret, otherUserMasterKey, otherDeviceKey, myMasterKey) {
    const myUserId = client.getUserId();
    const transactionId = request.channel.transactionId;
    const qrData = {
      prefix: BINARY_PREFIX,
      version: CODE_VERSION,
      mode,
      transactionId,
      firstKeyB64: "",
      // worked out shortly
      secondKeyB64: "",
      // worked out shortly
      secretB64: encodedSharedSecret
    };
    const myCrossSigningInfo = client.getStoredCrossSigningForUser(myUserId);
    if (mode === Mode.VerifyOtherUser) {
      // First key is our master cross signing key
      qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
      // Second key is the other user's master cross signing key
      qrData.secondKeyB64 = otherUserMasterKey;
    } else if (mode === Mode.VerifySelfTrusted) {
      // First key is our master cross signing key
      qrData.firstKeyB64 = myCrossSigningInfo.getId("master");
      qrData.secondKeyB64 = otherDeviceKey;
    } else if (mode === Mode.VerifySelfUntrusted) {
      // First key is our device's key
      qrData.firstKeyB64 = client.getDeviceEd25519Key();
      // Second key is what we think our master cross signing key is
      qrData.secondKeyB64 = myMasterKey;
    }
    return qrData;
  }
  static generateBuffer(qrData) {
    let buf = Buffer.alloc(0); // we'll concat our way through life

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

    // Actually build the buffer for the QR code
    appendStr(qrData.prefix, "ascii", false);
    appendByte(qrData.version);
    appendByte(qrData.mode);
    appendStr(qrData.transactionId, "utf-8");
    appendEncBase64(qrData.firstKeyB64);
    appendEncBase64(qrData.secondKeyB64);
    appendEncBase64(qrData.secretB64);
    return buf;
  }
}
exports.QRCodeData = QRCodeData;