'use strict';

Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.DeviceTrustLevel = exports.UserTrustLevel = exports.CrossSigningLevel = exports.CrossSigningInfo = undefined;

var _bluebird = require('bluebird');

var _assign = require('babel-runtime/core-js/object/assign');

var _assign2 = _interopRequireDefault(_assign);

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

var _slicedToArray3 = _interopRequireDefault(_slicedToArray2);

var _defineProperty2 = require('babel-runtime/helpers/defineProperty');

var _defineProperty3 = _interopRequireDefault(_defineProperty2);

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

var _keys5 = _interopRequireDefault(_keys4);

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

var _regenerator2 = _interopRequireDefault(_regenerator);

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 _entries = require('babel-runtime/core-js/object/entries');

var _entries2 = _interopRequireDefault(_entries);

var _olmlib = require('./olmlib');

var _events = require('events');

var _logger = require('../logger');

var _logger2 = _interopRequireDefault(_logger);

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

function publicKeyFromKeyInfo(keyInfo) {
    return (0, _entries2.default)(keyInfo.keys)[0];
} /*
  Copyright 2019 New Vector Ltd
  Copyright 2019 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.
  */

/**
 * Cross signing methods
 * @module crypto/CrossSigning
 */

var CrossSigningInfo = exports.CrossSigningInfo = function (_EventEmitter) {
    (0, _inherits3.default)(CrossSigningInfo, _EventEmitter);

    /**
     * Information about a user's cross-signing keys
     *
     * @class
     *
     * @param {string} userId the user that the information is about
     * @param {object} callbacks Callbacks used to interact with the app
     *     Requires getCrossSigningKey and saveCrossSigningKeys
     */
    function CrossSigningInfo(userId, callbacks) {
        (0, _classCallCheck3.default)(this, CrossSigningInfo);

        // you can't change the userId
        var _this = (0, _possibleConstructorReturn3.default)(this, (CrossSigningInfo.__proto__ || (0, _getPrototypeOf2.default)(CrossSigningInfo)).call(this));

        Object.defineProperty(_this, 'userId', {
            enumerable: true,
            value: userId
        });
        _this._callbacks = callbacks || {};
        _this.keys = {};
        _this.firstUse = true;
        return _this;
    }

    /**
     * Calls the app callback to ask for a private key
     * @param {string} type The key type ("master", "self_signing", or "user_signing")
     * @param {Uint8Array} expectedPubkey The matching public key or undefined to use
     *     the stored public key for the given key type.
     */


    (0, _createClass3.default)(CrossSigningInfo, [{
        key: 'getCrossSigningKey',
        value: function () {
            var _ref = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee(type, expectedPubkey) {
                var privkey, signing, gotPubkey;
                return _regenerator2.default.wrap(function _callee$(_context) {
                    while (1) {
                        switch (_context.prev = _context.next) {
                            case 0:
                                if (this._callbacks.getCrossSigningKey) {
                                    _context.next = 2;
                                    break;
                                }

                                throw new Error("No getCrossSigningKey callback supplied");

                            case 2:

                                if (expectedPubkey === undefined) {
                                    expectedPubkey = this.getId(type);
                                }

                                _context.next = 5;
                                return (0, _bluebird.resolve)(this._callbacks.getCrossSigningKey(type, expectedPubkey));

                            case 5:
                                privkey = _context.sent;

                                if (privkey) {
                                    _context.next = 8;
                                    break;
                                }

                                throw new Error("getCrossSigningKey callback for  " + type + " returned falsey");

                            case 8:
                                signing = new global.Olm.PkSigning();
                                gotPubkey = signing.init_with_seed(privkey);

                                if (!(gotPubkey !== expectedPubkey)) {
                                    _context.next = 15;
                                    break;
                                }

                                signing.free();
                                throw new Error("Key type " + type + " from getCrossSigningKey callback did not match");

                            case 15:
                                return _context.abrupt('return', [gotPubkey, signing]);

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

            function getCrossSigningKey(_x, _x2) {
                return _ref.apply(this, arguments);
            }

            return getCrossSigningKey;
        }()
    }, {
        key: 'toStorage',
        value: function toStorage() {
            return {
                keys: this.keys,
                firstUse: this.firstUse
            };
        }
    }, {
        key: 'hasKeys',
        value: function hasKeys() {
            return (0, _keys5.default)(this.keys).length > 0;
        }

        /**
         * Get the ID used to identify the user
         *
         * @param {string} type The type of key to get the ID of.  One of "master",
         * "self_signing", or "user_signing".  Defaults to "master".
         *
         * @return {string} the ID
         */

    }, {
        key: 'getId',
        value: function getId(type) {
            type = type || "master";
            if (!this.keys[type]) return null;
            var keyInfo = this.keys[type];
            return publicKeyFromKeyInfo(keyInfo)[1];
        }
    }, {
        key: 'resetKeys',
        value: function () {
            var _ref2 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee2(level) {
                var privateKeys, keys, masterSigning, masterPub, _ref3, _ref4, sskSigning, sskPub, uskSigning, uskPub;

                return _regenerator2.default.wrap(function _callee2$(_context2) {
                    while (1) {
                        switch (_context2.prev = _context2.next) {
                            case 0:
                                if (this._callbacks.saveCrossSigningKeys) {
                                    _context2.next = 2;
                                    break;
                                }

                                throw new Error("No saveCrossSigningKeys callback supplied");

                            case 2:
                                if (!(level === undefined || level & CrossSigningLevel.MASTER || !this.keys.master)) {
                                    _context2.next = 6;
                                    break;
                                }

                                level = CrossSigningLevel.MASTER | CrossSigningLevel.USER_SIGNING | CrossSigningLevel.SELF_SIGNING;
                                _context2.next = 8;
                                break;

                            case 6:
                                if (!(level === 0)) {
                                    _context2.next = 8;
                                    break;
                                }

                                return _context2.abrupt('return');

                            case 8:
                                privateKeys = {};
                                keys = {};
                                masterSigning = void 0;
                                masterPub = void 0;
                                _context2.prev = 12;

                                if (!(level & CrossSigningLevel.MASTER)) {
                                    _context2.next = 20;
                                    break;
                                }

                                masterSigning = new global.Olm.PkSigning();
                                privateKeys.master = masterSigning.generate_seed();
                                masterPub = masterSigning.init_with_seed(privateKeys.master);
                                keys.master = {
                                    user_id: this.userId,
                                    usage: ['master'],
                                    keys: (0, _defineProperty3.default)({}, 'ed25519:' + masterPub, masterPub)
                                };
                                _context2.next = 26;
                                break;

                            case 20:
                                _context2.next = 22;
                                return (0, _bluebird.resolve)(this.getCrossSigningyKey("master"));

                            case 22:
                                _ref3 = _context2.sent;
                                _ref4 = (0, _slicedToArray3.default)(_ref3, 2);
                                masterPub = _ref4[0];
                                masterSigning = _ref4[1];

                            case 26:

                                if (level & CrossSigningLevel.SELF_SIGNING) {
                                    sskSigning = new global.Olm.PkSigning();

                                    try {
                                        privateKeys.self_signing = sskSigning.generate_seed();
                                        sskPub = sskSigning.init_with_seed(privateKeys.self_signing);

                                        keys.self_signing = {
                                            user_id: this.userId,
                                            usage: ['self_signing'],
                                            keys: (0, _defineProperty3.default)({}, 'ed25519:' + sskPub, sskPub)
                                        };
                                        (0, _olmlib.pkSign)(keys.self_signing, masterSigning, this.userId, masterPub);
                                    } finally {
                                        sskSigning.free();
                                    }
                                }

                                if (level & CrossSigningLevel.USER_SIGNING) {
                                    uskSigning = new global.Olm.PkSigning();

                                    try {
                                        privateKeys.user_signing = uskSigning.generate_seed();
                                        uskPub = uskSigning.init_with_seed(privateKeys.user_signing);

                                        keys.user_signing = {
                                            user_id: this.userId,
                                            usage: ['user_signing'],
                                            keys: (0, _defineProperty3.default)({}, 'ed25519:' + uskPub, uskPub)
                                        };
                                        (0, _olmlib.pkSign)(keys.user_signing, masterSigning, this.userId, masterPub);
                                    } finally {
                                        uskSigning.free();
                                    }
                                }

                                (0, _assign2.default)(this.keys, keys);
                                this._callbacks.saveCrossSigningKeys(privateKeys);

                            case 30:
                                _context2.prev = 30;

                                if (masterSigning) {
                                    masterSigning.free();
                                }
                                return _context2.finish(30);

                            case 33:
                            case 'end':
                                return _context2.stop();
                        }
                    }
                }, _callee2, this, [[12,, 30, 33]]);
            }));

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

            return resetKeys;
        }()
    }, {
        key: 'setKeys',
        value: function setKeys(keys) {
            var signingKeys = {};
            if (keys.master) {
                if (keys.master.user_id !== this.userId) {
                    var error = "Mismatched user ID " + keys.master.user_id + " in master key from " + this.userId;
                    _logger2.default.error(error);
                    throw new Error(error);
                }
                if (!this.keys.master) {
                    // this is the first key we've seen, so first-use is true
                    this.firstUse = true;
                } else if (publicKeyFromKeyInfo(keys.master)[1] !== this.getId()) {
                    // this is a different key, so first-use is false
                    this.firstUse = false;
                } // otherwise, same key, so no change
                signingKeys.master = keys.master;
            } else if (this.keys.master) {
                signingKeys.master = this.keys.master;
            } else {
                throw new Error("Tried to set cross-signing keys without a master key");
            }
            var masterKey = publicKeyFromKeyInfo(signingKeys.master)[1];

            // verify signatures
            if (keys.user_signing) {
                if (keys.user_signing.user_id !== this.userId) {
                    var _error = "Mismatched user ID " + keys.master.user_id + " in user_signing key from " + this.userId;
                    _logger2.default.error(_error);
                    throw new Error(_error);
                }
                try {
                    (0, _olmlib.pkVerify)(keys.user_signing, masterKey, this.userId);
                } catch (e) {
                    _logger2.default.error("invalid signature on user-signing key");
                    // FIXME: what do we want to do here?
                    throw e;
                }
            }
            if (keys.self_signing) {
                if (keys.self_signing.user_id !== this.userId) {
                    var _error2 = "Mismatched user ID " + keys.master.user_id + " in self_signing key from " + this.userId;
                    _logger2.default.error(_error2);
                    throw new Error(_error2);
                }
                try {
                    (0, _olmlib.pkVerify)(keys.self_signing, masterKey, this.userId);
                } catch (e) {
                    _logger2.default.error("invalid signature on self-signing key");
                    // FIXME: what do we want to do here?
                    throw e;
                }
            }

            // if everything checks out, then save the keys
            if (keys.master) {
                this.keys.master = keys.master;
                // if the master key is set, then the old self-signing and
                // user-signing keys are obsolete
                delete this.keys.self_signing;
                delete this.keys.user_signing;
            }
            if (keys.self_signing) {
                this.keys.self_signing = keys.self_signing;
            }
            if (keys.user_signing) {
                this.keys.user_signing = keys.user_signing;
            }
        }
    }, {
        key: 'signObject',
        value: function () {
            var _ref5 = (0, _bluebird.coroutine)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(data, type) {
                var _ref6, _ref7, pubkey, signing;

                return _regenerator2.default.wrap(function _callee3$(_context3) {
                    while (1) {
                        switch (_context3.prev = _context3.next) {
                            case 0:
                                if (this.keys[type]) {
                                    _context3.next = 2;
                                    break;
                                }

                                throw new Error("Attempted to sign with " + type + " key but no such key present");

                            case 2:
                                _context3.next = 4;
                                return (0, _bluebird.resolve)(this.getCrossSigningKey(type));

                            case 4:
                                _ref6 = _context3.sent;
                                _ref7 = (0, _slicedToArray3.default)(_ref6, 2);
                                pubkey = _ref7[0];
                                signing = _ref7[1];
                                _context3.prev = 8;

                                (0, _olmlib.pkSign)(data, signing, this.userId, pubkey);
                                return _context3.abrupt('return', data);

                            case 11:
                                _context3.prev = 11;

                                signing.free();
                                return _context3.finish(11);

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

            function signObject(_x4, _x5) {
                return _ref5.apply(this, arguments);
            }

            return signObject;
        }()
    }, {
        key: 'signUser',
        value: function () {
            var _ref8 = (0, _bluebird.method)(function (key) {
                if (!this.keys.user_signing) {
                    return;
                }
                return this.signObject(key.keys.master, "user_signing");
            });

            function signUser(_x6) {
                return _ref8.apply(this, arguments);
            }

            return signUser;
        }()
    }, {
        key: 'signDevice',
        value: function () {
            var _ref9 = (0, _bluebird.method)(function (userId, device) {
                if (userId !== this.userId) {
                    throw new Error('Trying to sign ' + userId + '\'s device; can only sign our own device');
                }
                if (!this.keys.self_signing) {
                    return;
                }
                return this.signObject({
                    algorithms: device.algorithms,
                    keys: device.keys,
                    device_id: device.deviceId,
                    user_id: userId
                }, "self_signing");
            });

            function signDevice(_x7, _x8) {
                return _ref9.apply(this, arguments);
            }

            return signDevice;
        }()

        /**
         * Check whether a given user is trusted.
         *
         * @param {CrossSigningInfo} userCrossSigning Cross signing info for user
         *
         * @returns {UserTrustLevel}
         */

    }, {
        key: 'checkUserTrust',
        value: function checkUserTrust(userCrossSigning) {
            // if we're checking our own key, then it's trusted if the master key
            // and self-signing key match
            if (this.userId === userCrossSigning.userId && this.getId() && this.getId() === userCrossSigning.getId() && this.getId("self_signing") && this.getId("self_signing") === userCrossSigning.getId("self_signing")) {
                return new UserTrustLevel(true, this.firstUse);
            }

            if (!this.keys.user_signing) {
                // If there's no user signing key, they can't possibly be verified.
                // They may be TOFU trusted though.
                return new UserTrustLevel(false, userCrossSigning.firstUse);
            }

            var userTrusted = void 0;
            var userMaster = userCrossSigning.keys.master;
            var uskId = this.getId('user_signing');
            try {
                (0, _olmlib.pkVerify)(userMaster, uskId, this.userId);
                userTrusted = true;
            } catch (e) {
                userTrusted = false;
            }
            return new UserTrustLevel(userTrusted, userCrossSigning.firstUse);
        }

        /**
         * Check whether a given device is trusted.
         *
         * @param {CrossSigningInfo} userCrossSigning Cross signing info for user
         * @param {module:crypto/deviceinfo} device The device to check
         * @param {bool} localTrust Whether the device is trusted locally
         *
         * @returns {DeviceTrustLevel}
         */

    }, {
        key: 'checkDeviceTrust',
        value: function checkDeviceTrust(userCrossSigning, device, localTrust) {
            var userTrust = this.checkUserTrust(userCrossSigning);

            var userSSK = userCrossSigning.keys.self_signing;
            if (!userSSK) {
                // if the user has no self-signing key then we cannot make any
                // trust assertions about this device from cross-signing
                return new DeviceTrustLevel(false, false, localTrust);
            }

            var deviceObj = deviceToObject(device, userCrossSigning.userId);
            try {
                // if we can verify the user's SSK from their master key...
                (0, _olmlib.pkVerify)(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
                // ...and this device's key from their SSK...
                (0, _olmlib.pkVerify)(deviceObj, publicKeyFromKeyInfo(userSSK)[1], userCrossSigning.userId);
                // ...then we trust this device as much as far as we trust the user
                return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust);
            } catch (e) {
                return new DeviceTrustLevel(false, false, localTrust);
            }
        }
    }], [{
        key: 'fromStorage',
        value: function fromStorage(obj, userId) {
            var res = new CrossSigningInfo(userId);
            for (var prop in obj) {
                if (obj.hasOwnProperty(prop)) {
                    res[prop] = obj[prop];
                }
            }
            return res;
        }
    }]);
    return CrossSigningInfo;
}(_events.EventEmitter);

function deviceToObject(device, userId) {
    return {
        algorithms: device.algorithms,
        keys: device.keys,
        device_id: device.deviceId,
        user_id: userId,
        signatures: device.signatures
    };
}

var CrossSigningLevel = exports.CrossSigningLevel = {
    MASTER: 4,
    USER_SIGNING: 2,
    SELF_SIGNING: 1
};

/**
 * Represents the ways in which we trust a user
 */

var UserTrustLevel = exports.UserTrustLevel = function () {
    function UserTrustLevel(crossSigningVerified, tofu) {
        (0, _classCallCheck3.default)(this, UserTrustLevel);

        this._crossSigningVerified = crossSigningVerified;
        this._tofu = tofu;
    }

    /**
     * @returns {bool} true if this user is verified via any means
     */


    (0, _createClass3.default)(UserTrustLevel, [{
        key: 'isVerified',
        value: function isVerified() {
            return this.isCrossSigningVerified();
        }

        /**
         * @returns {bool} true if this user is verified via cross signing
         */

    }, {
        key: 'isCrossSigningVerified',
        value: function isCrossSigningVerified() {
            return this._crossSigningVerified;
        }

        /**
         * @returns {bool} true if this user's key is trusted on first use
         */

    }, {
        key: 'isTofu',
        value: function isTofu() {
            return this._tofu;
        }
    }]);
    return UserTrustLevel;
}();

/**
 * Represents the ways in which we trust a device
 */


var DeviceTrustLevel = exports.DeviceTrustLevel = function () {
    function DeviceTrustLevel(crossSigningVerified, tofu, localVerified) {
        (0, _classCallCheck3.default)(this, DeviceTrustLevel);

        this._crossSigningVerified = crossSigningVerified;
        this._tofu = tofu;
        this._localVerified = localVerified;
    }

    (0, _createClass3.default)(DeviceTrustLevel, [{
        key: 'isVerified',


        /**
         * @returns {bool} true if this device is verified via any means
         */
        value: function isVerified() {
            return this.isCrossSigningVerified() || this.isLocallyVerified();
        }

        /**
         * @returns {bool} true if this device is verified via cross signing
         */

    }, {
        key: 'isCrossSigningVerified',
        value: function isCrossSigningVerified() {
            return this._crossSigningVerified;
        }

        /**
         * @returns {bool} true if this device is verified locally
         */

    }, {
        key: 'isLocallyVerified',
        value: function isLocallyVerified() {
            return this._localVerified;
        }

        /**
         * @returns {bool} true if this device is trusted from a user's key
         * that is trusted on first use
         */

    }, {
        key: 'isTofu',
        value: function isTofu() {
            return this._tofu;
        }
    }], [{
        key: 'fromUserTrustLevel',
        value: function fromUserTrustLevel(userTrustLevel, localVerified) {
            return new DeviceTrustLevel(userTrustLevel._crossSigningVerified, userTrustLevel._tofu, localVerified);
        }
    }]);
    return DeviceTrustLevel;
}();
//# sourceMappingURL=CrossSigning.js.map