"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RecoveryDataEncryption = exports.RecoveryDataUseFailureReason = void 0;
const RecoveryData_1 = require("../../icc-api/model/internal/RecoveryData");
const utils_1 = require("../utils");
const XHR_1 = require("../../icc-api/api/XHR");
var XHRError = XHR_1.XHR.XHRError;
var RecoveryDataUseFailureReason;
(function (RecoveryDataUseFailureReason) {
    /**
     * The recovery data matching the provided recovery key does not exist. It could have been deleted, or it could have been expired.
     */
    RecoveryDataUseFailureReason["Missing"] = "MISSING";
    /**
     * The recovery data exists but it is not available to the current user. The user may have used a recovery key that he created while logged in as
     * a different user.
     */
    RecoveryDataUseFailureReason["Unauthorized"] = "UNAUTHORIZED";
    /**
     * The recovery data exists and is available to the current user, but it is not of the expected type.
     */
    RecoveryDataUseFailureReason["InvalidType"] = "INVALID_TYPE";
    /**
     * The recovery data exists and is available to the current user, but its content could not be interpreted. The data may have been created
     * with an unsupported SDK version.
     */
    RecoveryDataUseFailureReason["InvalidContent"] = "INVALID_CONTENT";
})(RecoveryDataUseFailureReason || (exports.RecoveryDataUseFailureReason = RecoveryDataUseFailureReason = {}));
/**
 * @internal this class is for internal use only and may change without notice.
 */
class RecoveryDataEncryption {
    constructor(primitives, baseRecoveryApi) {
        this.primitives = primitives;
        this.baseRecoveryApi = baseRecoveryApi;
    }
    recoveryKeyToId(recoveryKey) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2hex)(yield this.primitives.sha256((0, utils_1.hex2ua)(recoveryKey)));
        });
    }
    createAndSaveKeyPairsRecoveryDataFor(recipient, keyPairs, lifetimeSeconds) {
        return __awaiter(this, void 0, void 0, function* () {
            const content = {};
            for (const [delegateId, pairs] of Object.entries(keyPairs)) {
                const entries = [];
                for (const { pair, algorithm } of pairs) {
                    entries.push({
                        pair: {
                            privateKey: (0, utils_1.ua2b64)(yield this.primitives.RSA.exportKey(pair.privateKey, 'pkcs8')),
                            publicKey: (0, utils_1.ua2b64)(yield this.primitives.RSA.exportKey(pair.publicKey, 'spki')),
                        },
                        algorithm,
                    });
                }
                content[delegateId] = entries;
            }
            return yield this.createRecoveryData(recipient, RecoveryData_1.RecoveryData.Type.KEYPAIR_RECOVERY, lifetimeSeconds, content);
        });
    }
    getAndDecryptKeyPairsRecoveryData(recoveryKey, autoDeleteOnSuccess) {
        return __awaiter(this, void 0, void 0, function* () {
            const getRecoveryDataResult = yield this.getRecoveryDataAndDecrypt(recoveryKey, RecoveryData_1.RecoveryData.Type.KEYPAIR_RECOVERY);
            if ('failure' in getRecoveryDataResult) {
                return getRecoveryDataResult;
            }
            else {
                const recoveredKeysData = getRecoveryDataResult.decryptedJson;
                const recoveredKeys = {};
                for (const [delegateId, pairs] of Object.entries(recoveredKeysData)) {
                    const delegateKeys = {};
                    for (const { pair, algorithm } of pairs) {
                        delegateKeys[(0, utils_1.ua2hex)((0, utils_1.b64_2ua)(pair.publicKey))] = {
                            privateKey: yield this.primitives.RSA.importKey('pkcs8', (0, utils_1.ua2ab)((0, utils_1.b64_2ua)(pair.privateKey)), ['decrypt'], algorithm),
                            publicKey: yield this.primitives.RSA.importKey('spki', (0, utils_1.ua2ab)((0, utils_1.b64_2ua)(pair.publicKey)), ['encrypt'], algorithm),
                        };
                    }
                    recoveredKeys[delegateId] = delegateKeys;
                }
                if (autoDeleteOnSuccess) {
                    yield this.baseRecoveryApi.deleteRecoveryData(yield this.recoveryKeyToId(recoveryKey)).catch((e) => {
                        console.warn(`Failed to auto-delete recovery data, ignoring. ${e}`);
                    });
                }
                return { success: recoveredKeys };
            }
        });
    }
    createAndSaveExchangeDataRecoveryData(recipient, exchangeDataInfo, lifetimeSeconds) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.createRecoveryData(recipient, RecoveryData_1.RecoveryData.Type.EXCHANGE_KEY_RECOVERY, lifetimeSeconds, exchangeDataInfo.map((x) => ({
                exchangeDataId: x.exchangeDataId,
                rawAccessControlSecret: (0, utils_1.ua2b64)(x.rawAccessControlSecret),
                rawSharedSignatureKey: (0, utils_1.ua2b64)(x.rawSharedSignatureKey),
                rawExchangeKey: (0, utils_1.ua2b64)(x.rawExchangeKey),
            })));
        });
    }
    getAndDecryptExchangeDataRecoveryData(recoveryKey) {
        return __awaiter(this, void 0, void 0, function* () {
            const getRecoveryDataResult = yield this.getRecoveryDataAndDecrypt(recoveryKey, RecoveryData_1.RecoveryData.Type.EXCHANGE_KEY_RECOVERY);
            if ('failure' in getRecoveryDataResult)
                return getRecoveryDataResult;
            return {
                success: getRecoveryDataResult.decryptedJson.map((x) => ({
                    exchangeDataId: x.exchangeDataId,
                    rawAccessControlSecret: (0, utils_1.ua2ab)((0, utils_1.b64_2ua)(x.rawAccessControlSecret)),
                    rawSharedSignatureKey: (0, utils_1.ua2ab)((0, utils_1.b64_2ua)(x.rawSharedSignatureKey)),
                    rawExchangeKey: (0, utils_1.ua2ab)((0, utils_1.b64_2ua)(x.rawExchangeKey)),
                })),
            };
        });
    }
    getRecoveryDataAndDecrypt(recoveryKey, expectedType) {
        return __awaiter(this, void 0, void 0, function* () {
            const id = yield this.recoveryKeyToId(recoveryKey);
            const getResult = yield this.baseRecoveryApi.getRecoveryData(id).then((r) => ({ recoveryData: r }), (e) => {
                if (e instanceof XHRError) {
                    if (e.statusCode === 404) {
                        return { failure: RecoveryDataUseFailureReason.Missing };
                    }
                    else if (e.statusCode === 403) {
                        return { failure: RecoveryDataUseFailureReason.Unauthorized };
                    }
                }
                throw e;
            });
            if ('failure' in getResult)
                return getResult;
            const { recoveryData } = getResult;
            if (recoveryData.type !== expectedType)
                return { failure: RecoveryDataUseFailureReason.InvalidType };
            const decryptionKey = yield this.primitives.AES.importKey('raw', (0, utils_1.hex2ua)(recoveryKey));
            const decryptionResult = yield this.primitives.AES.decrypt(decryptionKey, (0, utils_1.string2ua)((0, utils_1.a2b)(recoveryData.encryptedSelf)))
                .then((d) => JSON.parse((0, utils_1.ua2utf8)(d)))
                .then((r) => ({ success: r }), () => ({ failure: RecoveryDataUseFailureReason.InvalidContent }));
            if ('failure' in decryptionResult)
                return decryptionResult;
            return { recoveryData, decryptedJson: decryptionResult.success };
        });
    }
    createRecoveryData(recipient, type, lifetimeSeconds, content) {
        return __awaiter(this, void 0, void 0, function* () {
            const recoveryKey = yield this.primitives.AES.generateCryptoKey(false);
            const recoveryKeyHex = (0, utils_1.ua2hex)(yield this.primitives.AES.exportKey(recoveryKey, 'raw'));
            const id = yield this.recoveryKeyToId(recoveryKeyHex);
            const encryptedSelf = (0, utils_1.b2a)((0, utils_1.ua2string)(yield this.primitives.AES.encrypt(recoveryKey, (0, utils_1.utf8_2ua)(JSON.stringify(content)))));
            const expirationInstant = lifetimeSeconds ? Date.now() + lifetimeSeconds * 1000 : undefined;
            const data = new RecoveryData_1.RecoveryData({
                id,
                encryptedSelf,
                expirationInstant,
                recipient,
                type,
            });
            yield this.baseRecoveryApi.createRecoveryData(data);
            return recoveryKeyHex;
        });
    }
}
exports.RecoveryDataEncryption = RecoveryDataEncryption;
//# sourceMappingURL=RecoveryDataEncryption.js.map