'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});

var _slicedToArray2 = require('babel-runtime/helpers/slicedToArray');

var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);

var _promise = require('babel-runtime/core-js/promise');

var _promise2 = _interopRequireDefault(_promise);

var _regenerator = require('babel-runtime/regenerator');

var _regenerator2 = _interopRequireDefault(_regenerator);

var _bluebird = require('bluebird');

var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of');

var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf);

var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');

var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);

var _createClass2 = require('babel-runtime/helpers/createClass');

var _createClass3 = _interopRequireDefault(_createClass2);

var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn');

var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2);

var _inherits2 = require('babel-runtime/helpers/inherits');

var _inherits3 = _interopRequireDefault(_inherits2);

var _set = require('babel-runtime/core-js/set');

var _set2 = _interopRequireDefault(_set);

var _keys = require('babel-runtime/core-js/object/keys');

var _keys2 = _interopRequireDefault(_keys);

var _getIterator2 = require('babel-runtime/core-js/get-iterator');

var _getIterator3 = _interopRequireDefault(_getIterator2);

var _Base2 = require('./Base');

var _Base3 = _interopRequireDefault(_Base2);

var _anotherJson = require('another-json');

var _anotherJson2 = _interopRequireDefault(_anotherJson);

var _Error = require('./Error');

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var EVENTS = ["m.key.verification.accept", "m.key.verification.key", "m.key.verification.mac"]; /*
                                                                                                Copyright 2018 New Vector Ltd
                                                                                                
                                                                                                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.
                                                                                                */

/**
 * Short Authentication String (SAS) verification.
 * @module crypto/verification/SAS
 */

var olmutil = void 0;

var newMismatchedSASError = (0, _Error.errorFactory)("m.mismatched_sas", "Mismatched short authentication string");

var newMismatchedCommitmentError = (0, _Error.errorFactory)("m.mismatched_commitment", "Mismatched commitment");

function generateDecimalSas(sasBytes) {
    /**
     *      +--------+--------+--------+--------+--------+
     *      | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
     *      +--------+--------+--------+--------+--------+
     * bits: 87654321 87654321 87654321 87654321 87654321
     *       \____________/\_____________/\____________/
     *         1st number    2nd number     3rd number
     */
    return [(sasBytes[0] << 5 | sasBytes[1] >> 3) + 1000, ((sasBytes[1] & 0x7) << 10 | sasBytes[2] << 2 | sasBytes[3] >> 6) + 1000, ((sasBytes[3] & 0x3f) << 7 | sasBytes[4] >> 1) + 1000];
}

var emojiMapping = [["🐶", "dog"], //  0
["🐱", "cat"], //  1
["🦁", "lion"], //  2
["🐎", "horse"], //  3
["🦄", "unicorn"], //  4
["🐷", "pig"], //  5
["🐘", "elephant"], //  6
["🐰", "rabbit"], //  7
["🐼", "panda"], //  8
["🐓", "rooster"], //  9
["🐧", "penguin"], // 10
["🐢", "turtle"], // 11
["🐟", "fish"], // 12
["🐙", "octopus"], // 13
["🦋", "butterfly"], // 14
["🌷", "flower"], // 15
["🌳", "tree"], // 16
["🌵", "cactus"], // 17
["🍄", "mushroom"], // 18
["🌏", "globe"], // 19
["🌙", "moon"], // 20
["☁️", "cloud"], // 21
["🔥", "fire"], // 22
["🍌", "banana"], // 23
["🍎", "apple"], // 24
["🍓", "strawberry"], // 25
["🌽", "corn"], // 26
["🍕", "pizza"], // 27
["🎂", "cake"], // 28
["❤️", "heart"], // 29
["🙂", "smiley"], // 30
["🤖", "robot"], // 31
["🎩", "hat"], // 32
["👓", "glasses"], // 33
["🔧", "spanner"], // 34
["🎅", "santa"], // 35
["👍", "thumbs up"], // 36
["☂️", "umbrella"], // 37
["⌛", "hourglass"], // 38
["⏰", "clock"], // 39
["🎁", "gift"], // 40
["💡", "light bulb"], // 41
["📕", "book"], // 42
["✏️", "pencil"], // 43
["📎", "paperclip"], // 44
["✂️", "scissors"], // 45
["🔒", "padlock"], // 46
["🔑", "key"], // 47
["🔨", "hammer"], // 48
["☎️", "telephone"], // 49
["🏁", "flag"], // 50
["🚂", "train"], // 51
["🚲", "bicycle"], // 52
["✈️", "aeroplane"], // 53
["🚀", "rocket"], // 54
["🏆", "trophy"], // 55
["⚽", "ball"], // 56
["🎸", "guitar"], // 57
["🎺", "trumpet"], // 58
["🔔", "bell"], // 59
["⚓️", "anchor"], // 60
["🎧", "headphones"], // 61
["📁", "folder"], // 62
["📌", "pin"]];

function generateEmojiSas(sasBytes) {
    var emojis = [
    // just like base64 encoding
    sasBytes[0] >> 2, (sasBytes[0] & 0x3) << 4 | sasBytes[1] >> 4, (sasBytes[1] & 0xf) << 2 | sasBytes[2] >> 6, sasBytes[2] & 0x3f, sasBytes[3] >> 2, (sasBytes[3] & 0x3) << 4 | sasBytes[4] >> 4, (sasBytes[4] & 0xf) << 2 | sasBytes[5] >> 6];

    return emojis.map(function (num) {
        return emojiMapping[num];
    });
}

var sasGenerators = {
    decimal: generateDecimalSas,
    emoji: generateEmojiSas
};

function generateSas(sasBytes, methods) {
    var sas = {};
    var _iteratorNormalCompletion = true;
    var _didIteratorError = false;
    var _iteratorError = undefined;

    try {
        for (var _iterator = (0, _getIterator3.default)(methods), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
            var method = _step.value;

            if (method in sasGenerators) {
                sas[method] = sasGenerators[method](sasBytes);
            }
        }
    } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
    } finally {
        try {
            if (!_iteratorNormalCompletion && _iterator.return) {
                _iterator.return();
            }
        } finally {
            if (_didIteratorError) {
                throw _iteratorError;
            }
        }
    }

    return sas;
}

var macMethods = {
    "hkdf-hmac-sha256": "calculate_mac",
    "hmac-sha256": "calculate_mac_long_kdf"
};

/* lists of algorithms/methods that are supported.  The key agreement, hashes,
 * and MAC lists should be sorted in order of preference (most preferred
 * first).
 */
var KEY_AGREEMENT_LIST = ["curve25519"];
var HASHES_LIST = ["sha256"];
var MAC_LIST = ["hkdf-hmac-sha256", "hmac-sha256"];
var SAS_LIST = (0, _keys2.default)(sasGenerators);

var KEY_AGREEMENT_SET = new _set2.default(KEY_AGREEMENT_LIST);
var HASHES_SET = new _set2.default(HASHES_LIST);
var MAC_SET = new _set2.default(MAC_LIST);
var SAS_SET = new _set2.default(SAS_LIST);

function intersection(anArray, aSet) {
    return anArray instanceof Array ? anArray.filter(function (x) {
        return aSet.has(x);
    }) : [];
}

/**
 * @alias module:crypto/verification/SAS
 * @extends {module:crypto/verification/Base}
 */

var SAS = function (_Base) {
    (0, _inherits3.default)(SAS, _Base);

    function SAS() {
        (0, _classCallCheck3.default)(this, SAS);
        return (0, _possibleConstructorReturn3.default)(this, (SAS.__proto__ || (0, _getPrototypeOf2.default)(SAS)).apply(this, arguments));
    }

    (0, _createClass3.default)(SAS, [{
        key: '_doVerification',
        value: function () {
            var _ref = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee() {
                return _regenerator2.default.wrap(function _callee$(_context) {
                    while (1) {
                        switch (_context.prev = _context.next) {
                            case 0:
                                _context.next = 2;
                                return (0, _bluebird.resolve)(global.Olm.init());

                            case 2:
                                olmutil = olmutil || new global.Olm.Utility();

                                // make sure user's keys are downloaded
                                _context.next = 5;
                                return (0, _bluebird.resolve)(this._baseApis.downloadKeys([this.userId]));

                            case 5:
                                if (!this.startEvent) {
                                    _context.next = 11;
                                    break;
                                }

                                _context.next = 8;
                                return (0, _bluebird.resolve)(this._doRespondVerification());

                            case 8:
                                return _context.abrupt('return', _context.sent);

                            case 11:
                                _context.next = 13;
                                return (0, _bluebird.resolve)(this._doSendVerification());

                            case 13:
                                return _context.abrupt('return', _context.sent);

                            case 14:
                            case 'end':
                                return _context.stop();
                        }
                    }
                }, _callee, this);
            }));

            function _doVerification() {
                return _ref.apply(this, arguments);
            }

            return _doVerification;
        }()
    }, {
        key: '_doSendVerification',
        value: function () {
            var _ref2 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() {
                var _this2 = this;

                var initialMessage, e, content, sasMethods, macMethod, hashCommitment, olmSAS, commitmentStr, sasInfo, sasBytes, verifySAS, _ref3, _ref4;

                return _regenerator2.default.wrap(function _callee2$(_context2) {
                    while (1) {
                        switch (_context2.prev = _context2.next) {
                            case 0:
                                initialMessage = this._contentWithTxnId({
                                    method: SAS.NAME,
                                    from_device: this._baseApis.deviceId,
                                    key_agreement_protocols: KEY_AGREEMENT_LIST,
                                    hashes: HASHES_LIST,
                                    message_authentication_codes: MAC_LIST,
                                    // FIXME: allow app to specify what SAS methods can be used
                                    short_authentication_string: SAS_LIST
                                });
                                // add the transaction id to the message beforehand because
                                // it needs to be included in the commitment hash later on

                                this._sendWithTxnId("m.key.verification.start", initialMessage);

                                _context2.next = 4;
                                return (0, _bluebird.resolve)(this._waitForEvent("m.key.verification.accept"));

                            case 4:
                                e = _context2.sent;
                                content = e.getContent();
                                sasMethods = intersection(content.short_authentication_string, SAS_SET);

                                if (KEY_AGREEMENT_SET.has(content.key_agreement_protocol) && HASHES_SET.has(content.hash) && MAC_SET.has(content.message_authentication_code) && sasMethods.length) {
                                    _context2.next = 9;
                                    break;
                                }

                                throw (0, _Error.newUnknownMethodError)();

                            case 9:
                                if (!(typeof content.commitment !== "string")) {
                                    _context2.next = 11;
                                    break;
                                }

                                throw (0, _Error.newInvalidMessageError)();

                            case 11:
                                macMethod = content.message_authentication_code;
                                hashCommitment = content.commitment;
                                olmSAS = new global.Olm.SAS();
                                _context2.prev = 14;

                                this._send("m.key.verification.key", {
                                    key: olmSAS.get_pubkey()
                                });

                                _context2.next = 18;
                                return (0, _bluebird.resolve)(this._waitForEvent("m.key.verification.key"));

                            case 18:
                                e = _context2.sent;

                                // FIXME: make sure event is properly formed
                                content = e.getContent();
                                commitmentStr = content.key + _anotherJson2.default.stringify(initialMessage);
                                // TODO: use selected hash function (when we support multiple)

                                if (!(olmutil.sha256(commitmentStr) !== hashCommitment)) {
                                    _context2.next = 23;
                                    break;
                                }

                                throw newMismatchedCommitmentError();

                            case 23:
                                olmSAS.set_their_key(content.key);

                                sasInfo = "MATRIX_KEY_VERIFICATION_SAS" + this._baseApis.getUserId() + this._baseApis.deviceId + this.userId + this.deviceId + this.transactionId;
                                sasBytes = olmSAS.generate_bytes(sasInfo, 6);
                                verifySAS = new _promise2.default(function (resolve, reject) {
                                    _this2.emit("show_sas", {
                                        sas: generateSas(sasBytes, sasMethods),
                                        confirm: function confirm() {
                                            _this2._sendMAC(olmSAS, macMethod);
                                            resolve();
                                        },
                                        cancel: function cancel() {
                                            return reject((0, _Error.newUserCancelledError)());
                                        },
                                        mismatch: function mismatch() {
                                            return reject(newMismatchedSASError());
                                        }
                                    });
                                });
                                _context2.next = 29;
                                return (0, _bluebird.resolve)(_promise2.default.all([this._waitForEvent("m.key.verification.mac"), verifySAS]));

                            case 29:
                                _ref3 = _context2.sent;
                                _ref4 = (0, _slicedToArray3.default)(_ref3, 1);
                                e = _ref4[0];

                                content = e.getContent();
                                _context2.next = 35;
                                return (0, _bluebird.resolve)(this._checkMAC(olmSAS, content, macMethod));

                            case 35:
                                _context2.prev = 35;

                                olmSAS.free();
                                return _context2.finish(35);

                            case 38:
                            case 'end':
                                return _context2.stop();
                        }
                    }
                }, _callee2, this, [[14,, 35, 38]]);
            }));

            function _doSendVerification() {
                return _ref2.apply(this, arguments);
            }

            return _doSendVerification;
        }()
    }, {
        key: '_doRespondVerification',
        value: function () {
            var _ref5 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee3() {
                var _this3 = this;

                var content, keyAgreement, hashMethod, macMethod, sasMethods, olmSAS, commitmentStr, e, sasInfo, sasBytes, verifySAS, _ref6, _ref7;

                return _regenerator2.default.wrap(function _callee3$(_context3) {
                    while (1) {
                        switch (_context3.prev = _context3.next) {
                            case 0:
                                // as m.related_to is not included in the encrypted content in e2e rooms,
                                // we need to make sure it is added
                                content = this._contentFromEventWithTxnId(this.startEvent);

                                // Note: we intersect using our pre-made lists, rather than the sets,
                                // so that the result will be in our order of preference.  Then
                                // fetching the first element from the array will give our preferred
                                // method out of the ones offered by the other party.

                                keyAgreement = intersection(KEY_AGREEMENT_LIST, new _set2.default(content.key_agreement_protocols))[0];
                                hashMethod = intersection(HASHES_LIST, new _set2.default(content.hashes))[0];
                                macMethod = intersection(MAC_LIST, new _set2.default(content.message_authentication_codes))[0];
                                // FIXME: allow app to specify what SAS methods can be used

                                sasMethods = intersection(content.short_authentication_string, SAS_SET);

                                if (keyAgreement !== undefined && hashMethod !== undefined && macMethod !== undefined && sasMethods.length) {
                                    _context3.next = 7;
                                    break;
                                }

                                throw (0, _Error.newUnknownMethodError)();

                            case 7:
                                olmSAS = new global.Olm.SAS();
                                _context3.prev = 8;
                                commitmentStr = olmSAS.get_pubkey() + _anotherJson2.default.stringify(content);

                                this._send("m.key.verification.accept", {
                                    key_agreement_protocol: keyAgreement,
                                    hash: hashMethod,
                                    message_authentication_code: macMethod,
                                    short_authentication_string: sasMethods,
                                    // TODO: use selected hash function (when we support multiple)
                                    commitment: olmutil.sha256(commitmentStr)
                                });

                                _context3.next = 13;
                                return (0, _bluebird.resolve)(this._waitForEvent("m.key.verification.key"));

                            case 13:
                                e = _context3.sent;

                                // FIXME: make sure event is properly formed
                                content = e.getContent();
                                olmSAS.set_their_key(content.key);
                                this._send("m.key.verification.key", {
                                    key: olmSAS.get_pubkey()
                                });

                                sasInfo = "MATRIX_KEY_VERIFICATION_SAS" + this.userId + this.deviceId + this._baseApis.getUserId() + this._baseApis.deviceId + this.transactionId;
                                sasBytes = olmSAS.generate_bytes(sasInfo, 6);
                                verifySAS = new _promise2.default(function (resolve, reject) {
                                    _this3.emit("show_sas", {
                                        sas: generateSas(sasBytes, sasMethods),
                                        confirm: function confirm() {
                                            _this3._sendMAC(olmSAS, macMethod);
                                            resolve();
                                        },
                                        cancel: function cancel() {
                                            return reject((0, _Error.newUserCancelledError)());
                                        },
                                        mismatch: function mismatch() {
                                            return reject(newMismatchedSASError());
                                        }
                                    });
                                });
                                _context3.next = 22;
                                return (0, _bluebird.resolve)(_promise2.default.all([this._waitForEvent("m.key.verification.mac"), verifySAS]));

                            case 22:
                                _ref6 = _context3.sent;
                                _ref7 = (0, _slicedToArray3.default)(_ref6, 1);
                                e = _ref7[0];

                                content = e.getContent();
                                _context3.next = 28;
                                return (0, _bluebird.resolve)(this._checkMAC(olmSAS, content, macMethod));

                            case 28:
                                _context3.prev = 28;

                                olmSAS.free();
                                return _context3.finish(28);

                            case 31:
                            case 'end':
                                return _context3.stop();
                        }
                    }
                }, _callee3, this, [[8,, 28, 31]]);
            }));

            function _doRespondVerification() {
                return _ref5.apply(this, arguments);
            }

            return _doRespondVerification;
        }()
    }, {
        key: '_sendMAC',
        value: function _sendMAC(olmSAS, method) {
            var mac = {};
            var keyList = [];
            var baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + this._baseApis.getUserId() + this._baseApis.deviceId + this.userId + this.deviceId + this.transactionId;

            var deviceKeyId = 'ed25519:' + this._baseApis.deviceId;
            mac[deviceKeyId] = olmSAS[macMethods[method]](this._baseApis.getDeviceEd25519Key(), baseInfo + deviceKeyId);
            keyList.push(deviceKeyId);

            var crossSigningId = this._baseApis.getCrossSigningId();
            if (crossSigningId) {
                var crossSigningKeyId = 'ed25519:' + crossSigningId;
                mac[crossSigningKeyId] = olmSAS[macMethods[method]](crossSigningId, baseInfo + crossSigningKeyId);
                keyList.push(crossSigningKeyId);
            }

            var keys = olmSAS[macMethods[method]](keyList.sort().join(","), baseInfo + "KEY_IDS");
            this._send("m.key.verification.mac", { mac: mac, keys: keys });
        }
    }, {
        key: '_checkMAC',
        value: function () {
            var _ref8 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(olmSAS, content, method) {
                var baseInfo;
                return _regenerator2.default.wrap(function _callee4$(_context4) {
                    while (1) {
                        switch (_context4.prev = _context4.next) {
                            case 0:
                                baseInfo = "MATRIX_KEY_VERIFICATION_MAC" + this.userId + this.deviceId + this._baseApis.getUserId() + this._baseApis.deviceId + this.transactionId;

                                if (!(content.keys !== olmSAS[macMethods[method]]((0, _keys2.default)(content.mac).sort().join(","), baseInfo + "KEY_IDS"))) {
                                    _context4.next = 3;
                                    break;
                                }

                                throw (0, _Error.newKeyMismatchError)();

                            case 3:
                                _context4.next = 5;
                                return (0, _bluebird.resolve)(this._verifyKeys(this.userId, content.mac, function (keyId, device, keyInfo) {
                                    if (keyInfo !== olmSAS[macMethods[method]](device.keys[keyId], baseInfo + keyId)) {
                                        throw (0, _Error.newKeyMismatchError)();
                                    }
                                }));

                            case 5:
                            case 'end':
                                return _context4.stop();
                        }
                    }
                }, _callee4, this);
            }));

            function _checkMAC(_x, _x2, _x3) {
                return _ref8.apply(this, arguments);
            }

            return _checkMAC;
        }()
    }, {
        key: 'events',
        get: function get() {
            return EVENTS;
        }
    }]);
    return SAS;
}(_Base3.default);

exports.default = SAS;


SAS.NAME = "m.sas.v1";
//# sourceMappingURL=SAS.js.map