"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.AES = exports.AESUtilsImpl = void 0;
const utils_1 = require("../utils");
class AESUtilsImpl {
    set debug(value) {
        this._debug = value;
    }
    constructor(crypto = typeof window !== 'undefined' ? window.crypto : typeof self !== 'undefined' ? self.crypto : {}) {
        /********* AES Config **********/
        this.ivLength = 16;
        this.aesAlgorithmEncryptName = 'AES-CBC';
        this.aesKeyGenParams = {
            name: 'AES-CBC',
            length: 256,
        };
        this._debug = false;
        this.crypto = crypto;
    }
    encryptWithRawKey(rawKey, plainData) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.encrypt(yield this.importKey('raw', (0, utils_1.hex2ua)(rawKey.replace(/-/g, ''))), plainData, rawKey // Used for logging only if debug is enabled, ok to always pass
            );
        });
    }
    encrypt(cryptoKey, plainData, rawKey = '<NA>') {
        return new Promise((resolve, reject) => {
            if (plainData instanceof Uint8Array) {
                const buffer = plainData.buffer;
                plainData = (buffer.byteLength > plainData.byteLength ? buffer.slice(0, plainData.byteLength) : buffer);
            }
            const aesAlgorithmEncrypt = {
                name: this.aesAlgorithmEncryptName,
                iv: this.generateIV(this.ivLength),
            };
            this._debug && console.log(`encrypt ${(0, utils_1.ua2hex)(plainData)} with ${rawKey}`);
            this.crypto.subtle
                .encrypt(Object.assign({}, aesAlgorithmEncrypt), cryptoKey, plainData)
                .then((cipherData) => {
                this._debug && console.log(`cipherData: ${(0, utils_1.ua2hex)(cipherData)}`);
                return resolve((0, utils_1.appendBuffer)(aesAlgorithmEncrypt.iv.buffer, cipherData));
            }, (err) => reject('AES encryption failed: ' + err));
        });
    }
    decryptWithRawKey(rawKey, plainData) {
        return __awaiter(this, void 0, void 0, function* () {
            return this.decrypt(yield this.importKey('raw', (0, utils_1.hex2ua)(rawKey.replace(/-/g, ''))), plainData, rawKey // Used for logging only if debug is enabled, ok to always pass
            );
        });
    }
    /**
     *
     * @param cryptoKey (CryptoKey)
     * @param encryptedData (ArrayBuffer)
     * @param rawKey
     * @returns {Promise} will be ArrayBuffer
     */
    decrypt(cryptoKey, encryptedData, rawKey) {
        return new Promise((resolve, reject) => {
            if (!cryptoKey) {
                return reject('No crypto key provided for decryption');
            }
            const encryptedDataUint8 = encryptedData instanceof ArrayBuffer ? new Uint8Array(encryptedData) : encryptedData;
            const aesAlgorithmEncrypt = {
                name: this.aesAlgorithmEncryptName,
                iv: encryptedDataUint8.subarray(0, this.ivLength),
                /*
                 * IF THIS BIT OF CODE PRODUCES A DOMEXCEPTION CODE 0 ERROR, IT MIGHT BE RELATED TO THIS:
                 *
                 * NOTOK:
                 * if (!hcparty.hcPartyKeys && !hcparty.hcPartyKeys[hcpartyId] && hcparty.hcPartyKeys[hcpartyId].length !== 2) {
                 *   throw 'No hcPartyKey for this Healthcare party(' + hcpartyId + ').';
                 * }
                 * var delegateHcPartyKey = hcparty.hcPartyKeys[hcpartyId][1];
                 *
                 * SHOULD BE:
                 * var delegatorId = patient.delegations[hcpartyId][0].owner;
                 * if (!hcparty.hcPartyKeys && !hcparty.hcPartyKeys[delegatorId] && hcparty.hcPartyKeys[delegatorId].length !== 2) {
                 *   throw 'No hcPartyKey for this Healthcare party(' + delegatorId + ').';
                 * }
                 * var delegateHcPartyKey = hcparty.hcPartyKeys[delegatorId][1];
                 */
            };
            this._debug && console.log(`decrypt ${(0, utils_1.ua2hex)(encryptedData)} with ${rawKey}`);
            this.crypto.subtle.decrypt(aesAlgorithmEncrypt, cryptoKey, (0, utils_1.ua2ab)(encryptedDataUint8.subarray(this.ivLength, encryptedDataUint8.length))).then((decipheredData) => {
                this._debug && console.log(`decipheredData: ${(0, utils_1.ua2hex)(decipheredData)}`);
                resolve(decipheredData);
            }, (err) => {
                reject(new Error('AES decryption failed: ' + err));
            });
        });
    }
    decryptSome(cryptoKeys, uint8Array) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                return this.decrypt(cryptoKeys[0], uint8Array);
            }
            catch (e) {
                if (cryptoKeys.length > 1) {
                    return this.decryptSome(cryptoKeys.slice(1), uint8Array);
                }
                else {
                    throw e;
                }
            }
        });
    }
    generateCryptoKey(toHex) {
        return new Promise((resolve, reject) => {
            const extractable = true;
            const keyUsages = ['decrypt', 'encrypt'];
            const cryptoKeyPromise = this.crypto.subtle.generateKey(this.aesKeyGenParams, extractable, keyUsages);
            return toHex === undefined || !toHex
                ? cryptoKeyPromise.then(resolve, reject)
                : cryptoKeyPromise.then((k) => this.exportKey(k, 'raw'), reject).then((raw) => resolve((0, utils_1.ua2hex)(raw)), reject);
        });
    }
    // noinspection JSMethodCanBeStatic
    generateIV(ivByteLength) {
        return new Uint8Array(this.crypto.getRandomValues(new Uint8Array(ivByteLength)));
    }
    exportKey(cryptoKey, format) {
        return new Promise((resolve, reject) => {
            return this.crypto.subtle.exportKey(format, cryptoKey).then(resolve, reject);
        });
    }
    /**
     * the ability to import a key that have already been created elsewhere, for use within the web
     * application that is invoking the import function, for use within the importing web application's
     * origin. This necessiates an interoperable key format, such as JSON Web Key [JWK] which may be
     * represented as octets.
     *
     * https://chromium.googlesource.com/chromium/blink.git/+/6b902997e3ca0384c8fa6fe56f79ecd7589d3ca6/LayoutTests/crypto/resources/common.js
     *
     * @param format 'raw' or 'jwk'
     * @param aesKey
     * @returns {*}
     */
    importKey(format, aesKey) {
        return new Promise((resolve, reject) => {
            const extractable = true;
            const keyUsages = ['decrypt', 'encrypt'];
            return this.crypto.subtle
                .importKey(format, aesKey, this.aesKeyGenParams, extractable, keyUsages)
                .catch((err) => {
                if (format == 'raw' && (aesKey instanceof ArrayBuffer || ArrayBuffer.isView(aesKey))) {
                    console.warn(`Import of key ${(0, utils_1.ua2hex)(aesKey)} failed`);
                }
                throw err;
            })
                .then(resolve, reject);
        });
    }
}
exports.AESUtilsImpl = AESUtilsImpl;
exports.AES = new AESUtilsImpl();
//# sourceMappingURL=AES.js.map