"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.notConcurrent = notConcurrent;
exports.jwk2pkcs8 = jwk2pkcs8;
exports.jwk2spki = jwk2spki;
exports.spkiToJwk = spkiToJwk;
exports.pkcs8ToJwk = pkcs8ToJwk;
exports.truncateTrailingNulls = truncateTrailingNulls;
exports.appendBuffer = appendBuffer;
exports.after = after;
exports.before = before;
exports.parseEncryptedFields = parseEncryptedFields;
exports.encryptObject = encryptObject;
exports.decryptObject = decryptObject;
exports.keyPairFromPrivateKeyJwk = keyPairFromPrivateKeyJwk;
const date_fns_1 = require("date-fns");
const binary_utils_1 = require("../utils/binary-utils");
const asn1_packer_1 = require("./asn1-packer");
const asn1_parser_1 = require("./asn1-parser");
function notConcurrent(concurrencyMap, key, proc) {
    const inFlight = concurrencyMap[key];
    if (!inFlight) {
        const newJob = (() => __awaiter(this, void 0, void 0, function* () {
            try {
                return yield proc();
            }
            finally {
                delete concurrencyMap[key];
            }
        }))();
        concurrencyMap[key] = newJob;
        return newJob;
    }
    else {
        return concurrencyMap[key].then(() => notConcurrent(concurrencyMap, key, proc));
    }
}
function jwk2pkcs8(jwk) {
    return (0, asn1_packer_1.pack)([
        0x30,
        [
            [0x02, '00'],
            [0x30, [[0x06, '2a864886f70d010101'], [0x05]]], // pragma: allowlist secret
            [
                0x04,
                [
                    [
                        0x30,
                        [
                            [0x02, '00'],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.n))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.e))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.d))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.p))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.q))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.dp))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.dq))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.qi))],
                        ],
                    ],
                ],
            ],
        ],
    ]);
}
function jwk2spki(jwk) {
    return (0, asn1_packer_1.pack)([
        0x30,
        [
            [0x30, [[0x06, '2a864886f70d010101'], [0x05]]], // pragma: allowlist secret
            [
                0x03,
                [
                    [
                        0x30,
                        [
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.n))],
                            [0x02, (0, binary_utils_1.ua2hex)((0, binary_utils_1.b64Url2ua)(jwk.e))],
                        ],
                    ],
                ],
            ],
        ],
    ]);
}
function spkiToJwk(buf, shaVersion) {
    var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
    const asn1 = (0, asn1_parser_1.parseAsn1)(new Uint8Array(buf));
    var modulus = undefined;
    var exponent = undefined;
    if (asn1.type === 0x30 &&
        ((_b = (_a = asn1.children) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.type) === 0x30 &&
        ((_f = (_e = (_d = (_c = asn1.children) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.children) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.type) === 0x06 &&
        (0, binary_utils_1.ua2hex)((_l = (_k = (_j = (_h = (_g = asn1.children) === null || _g === void 0 ? void 0 : _g[0]) === null || _h === void 0 ? void 0 : _h.children) === null || _j === void 0 ? void 0 : _j[0]) === null || _k === void 0 ? void 0 : _k.value) !== null && _l !== void 0 ? _l : new Uint8Array(0)) === '2a864886f70d010101' // pragma: allowlist secret
    ) {
        modulus = (_s = (_r = (_q = (_p = (_o = (_m = asn1.children) === null || _m === void 0 ? void 0 : _m[1]) === null || _o === void 0 ? void 0 : _o.children) === null || _p === void 0 ? void 0 : _p[0]) === null || _q === void 0 ? void 0 : _q.children) === null || _r === void 0 ? void 0 : _r[0]) === null || _s === void 0 ? void 0 : _s.value;
        exponent = (_y = (_x = (_w = (_v = (_u = (_t = asn1.children) === null || _t === void 0 ? void 0 : _t[1]) === null || _u === void 0 ? void 0 : _u.children) === null || _v === void 0 ? void 0 : _v[0]) === null || _w === void 0 ? void 0 : _w.children) === null || _x === void 0 ? void 0 : _x[1]) === null || _y === void 0 ? void 0 : _y.value;
    }
    else if (asn1.type === 0x30 && ((_0 = (_z = asn1.children) === null || _z === void 0 ? void 0 : _z[0]) === null || _0 === void 0 ? void 0 : _0.type) === 0x02 && ((_2 = (_1 = asn1.children) === null || _1 === void 0 ? void 0 : _1[1]) === null || _2 === void 0 ? void 0 : _2.type) === 0x02) {
        modulus = (_4 = (_3 = asn1.children) === null || _3 === void 0 ? void 0 : _3[0]) === null || _4 === void 0 ? void 0 : _4.value;
        exponent = (_6 = (_5 = asn1.children) === null || _5 === void 0 ? void 0 : _5[1]) === null || _6 === void 0 ? void 0 : _6.value;
    }
    if (!modulus || !exponent) {
        throw new Error('Invalid spki format');
    }
    return {
        kty: 'RSA',
        alg: shaVersion === 'sha-256' ? 'RSA-OAEP-256' : 'RSA-OAEP',
        ext: true,
        n: (0, binary_utils_1.ua2b64Url)(minimalRep(modulus)),
        e: (0, binary_utils_1.ua2b64Url)(minimalRep(exponent)),
    };
}
function pkcs8ToJwk(buf) {
    var _a, _b;
    const parsed = (0, asn1_parser_1.parseAsn1)(new Uint8Array(buf));
    const seq = ((_a = parsed.children) === null || _a === void 0 ? void 0 : _a.length) === 3 && parsed.children[2].type === 0x04 && ((_b = parsed.children[2].children) === null || _b === void 0 ? void 0 : _b.length) === 1
        ? parsed.children[2].children[0]
        : parsed;
    return {
        kty: 'RSA',
        n: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[1].value)),
        e: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[2].value)),
        d: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[3].value)),
        p: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[4].value)),
        q: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[5].value)),
        dp: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[6].value)),
        dq: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[7].value)),
        qi: (0, binary_utils_1.ua2b64Url)(minimalRep(seq.children[8].value)),
    };
}
function minimalRep(b) {
    let i = 0;
    while (b[i] === 0) {
        i++;
    }
    return b.slice(i);
}
/**
 * Provide a view over the given Uint8Array where any trailing null bytes at
 * the end are truncated.
 *
 * This can be used to ignore null bytes at the end of a padded UTF-8 string
 * without needing to copy that string, assuming code point U+0000 is encoded
 * in one null byte according to standards rather than in a multi-byte
 * overlong form.
 */
function truncateTrailingNulls(a) {
    let end = a.byteLength - 1;
    while (a[end] === 0 && end >= 0) {
        end--;
    }
    // end is now either the last non-null position in a or -1; in the latter
    // case the returned array will have length 0.
    return a.subarray(0, end + 1);
}
/**
 *
 * @param buffer1 {Uint8Array}
 * @param buffer2{ Uint8Array}
 * @returns {ArrayBuffer}
 */
function appendBuffer(buffer1, buffer2) {
    const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
    tmp.set(new Uint8Array(buffer1), 0);
    tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
    return tmp.buffer;
}
//Convenience methods for dates management
function after(d1, d2) {
    return d1 === null || d2 === null || d1 === undefined || d2 === undefined || (0, date_fns_1.isAfter)(parseDateNumber(d1), parseDateNumber(d2));
}
function before(d1, d2) {
    return d1 === null || d2 === null || d1 === undefined || d2 === undefined || (0, date_fns_1.isBefore)(parseDateNumber(d1), parseDateNumber(d2));
}
function parseDateNumber(epochOrLongCalendar) {
    if (!epochOrLongCalendar && epochOrLongCalendar !== 0) {
        return null;
    }
    if (epochOrLongCalendar >= 18000101 && epochOrLongCalendar < 25400000) {
        return (0, date_fns_1.parse)('' + epochOrLongCalendar, 'yyyyMMdd', new Date());
    }
    else if (epochOrLongCalendar >= 18000101000000) {
        return (0, date_fns_1.parse)('' + epochOrLongCalendar, 'yyyyMMddHHmmss', new Date());
    }
    else {
        return new Date(epochOrLongCalendar);
    }
}
/**
 * @internal this function is for internal use only and may be changed without notice
 *
 * Parse the encrypted fields configuration for a specific entity type.
 *
 * ## Grammar
 *
 * The grammar for each encrypted field is the following:
 * ```
 * fieldName :=
 *   regex([a-zA-Z_][a-zA-Z0-9_]+)
 * encryptedField :=
 *   fieldName
 *   | fieldName + ("." | ".*." | "[].") + encryptedField
 * ```
 *
 * This grammar allows you to specify the fields to encrypt for the object and recursively for nested objects.
 * - A string containing only a single `fieldName` will encrypt the field with the given name.
 * - A string starting with `fieldName.` allows to specify the encrypted fields of a nested object. The encrypted values of the
 *   fields in the nested object will be saved in the nested object.
 * - A string starting with `fieldName.*.` treats `fieldName` as a map/dictionary data structure and allows to specify the encrypted fields of the
 *   values of the map. Note that the values of the map must be objects as well. The encrypted content of each map value is stored in that value.
 * - A string starting with `fieldName[].` treats `fieldName` as an array and allows to specify the encrypted fields of the values of the array.
 *   Note that the values of the array must be objects as well. The encrypted content of each array element is stored in that element.
 *
 * ## Example
 *
 * Consider the following object and encryption keys:
 * ```javascript
 * const obj = {
 *   a: { x: 0, y: 1 },
 *   b: "hello",
 *   c: [ { public: "a", secret: "b" }, { public: "c", secret: "d" } ],
 *   d: "ok",
 *   e: {
 *     info: "something",
 *     private: "secret",
 *     dataMap: {
 *       "en": {
 *         a: 1,
 *         b: 2
 *       },
 *       "fr": {
 *         a: 3,
 *         b: 4
 *       }
 *     }
 *   }
 * }
 * const encryptedFields = [
 *   "a",
 *   "c[].secret",
 *   "d",
 *   "e.private",
 *   "e.datamap.*.a"
 * ]
 * ```
 * If you use them with the crypt method you will get the following result:
 * ```json
 * {
 *   b: "hello",
 *   c: [
 *     { public: "a", encryptedSelf: "...encrypted data of c[0]" },
 *     { public: "c", encryptedSelf: "...encrypted data of c[1]" }
 *   ],
 *   e: {
 *     info: "something",
 *     dataMap: {
 *       "en": { b: 2, encryptedSelf: "...encrypted data of e.dataMap['en']" },
 *       "fr": { b: 4, encryptedSelf: "...encrypted data of e.dataMap['fr']" }
 *     },
 *     encryptedSelf: "...encrypted data of e"
 *   },
 *   encryptedSelf: "...encrypted data of obj"
 * }
 * ```
 *
 * ## Shortened representation
 *
 * You can also group encrypted fields having the same prefix by concatenating to the prefix the JSON representation of an array of all the postfixes.
 * For example the following encrypted fields:
 * ```javascript
 * const encryptedFields = ["a.b.c.d.e.f1", "a.b.c.d.e.f2", "a.b.c.d.e.f3", "a.b.c.d.e.f4"]
 * ```
 * can be shortened to
 * ```javascript
 * const encryptedFields = ['a.b.c.d.e.["f1","f2","f3","f4"]'] // Note the use of single quotes to avoid escaping the double quotes
 * ```
 * If you use the shortened representation you may need to escape nested json representations. In that case the use of `JSON.stringify` is
 * recommended.
 *
 * @param encryptedFields
 * @param path
 */
function parseEncryptedFields(encryptedFields, path) {
    const groupedData = {
        topLevelFields: new Set(),
        nestedObjectsKeys: {},
        mapsValuesKeys: {},
        arraysValuesKeys: {},
    };
    const encryptedFieldRegex = /^([_a-zA-Z][_a-zA-Z0-9]*)(?:(\.\*\.|\[]\.|\.)(?:[_a-zA-Z].*|\[.*]))?$/;
    const addSubkeyToGroupedData = (currFieldName, currFieldSeparator, currEncryptedField, groupedDataKey) => {
        var _a;
        const existingOrNew = (_a = groupedData[groupedDataKey][currFieldName]) !== null && _a !== void 0 ? _a : (groupedData[groupedDataKey][currFieldName] = []);
        const subKey = currEncryptedField.slice(currFieldName.length + currFieldSeparator.length);
        if (subKey.startsWith('[')) {
            let parsedJson;
            try {
                parsedJson = JSON.parse(subKey);
            }
            catch (_b) {
                throw new Error(`Invalid encrypted field ${path}${currEncryptedField} (not a valid JSON subkey)`);
            }
            if (Array.isArray(parsedJson) && parsedJson.every((x) => typeof x == 'string')) {
                parsedJson.forEach((x) => existingOrNew.push(x));
            }
            else
                throw new Error(`Invalid encrypted field ${path}${currEncryptedField} (not an array of strings)`);
        }
        else {
            existingOrNew.push(subKey);
        }
    };
    for (const currEncryptedField of encryptedFields) {
        const currFieldMatch = currEncryptedField.match(encryptedFieldRegex);
        if (!!currFieldMatch) {
            const currFieldName = currFieldMatch[1];
            const currFieldSeparator = currFieldMatch[2];
            if (!currFieldSeparator) {
                if (groupedData.topLevelFields.has(currFieldName))
                    throw new Error(`Duplicate encrypted field ${path}${currFieldName}`);
                if (!!groupedData.nestedObjectsKeys[currFieldName] ||
                    !!groupedData.mapsValuesKeys[currFieldName] ||
                    !!groupedData.arraysValuesKeys[currFieldName])
                    throw new Error(`Encrypted field appears multiple times as different nested types and or top-level-field: ${path}${currFieldName}`);
                groupedData.topLevelFields.add(currFieldName);
            }
            else if (currFieldSeparator == '.') {
                if (groupedData.topLevelFields.has(currFieldName) ||
                    !!groupedData.mapsValuesKeys[currFieldName] ||
                    !!groupedData.arraysValuesKeys[currFieldName])
                    throw new Error(`Encrypted field appears multiple times as different nested types and or top-level-field: ${path}${currFieldName}`);
                addSubkeyToGroupedData(currFieldName, currFieldSeparator, currEncryptedField, 'nestedObjectsKeys');
            }
            else if (currFieldSeparator == '.*.') {
                if (groupedData.topLevelFields.has(currFieldName) ||
                    !!groupedData.nestedObjectsKeys[currFieldName] ||
                    !!groupedData.arraysValuesKeys[currFieldName])
                    throw new Error(`Encrypted field appears multiple times as different nested types and or top-level-field: ${path}${currFieldName}`);
                addSubkeyToGroupedData(currFieldName, currFieldSeparator, currEncryptedField, 'mapsValuesKeys');
            }
            else if (currFieldSeparator == '[].') {
                if (groupedData.topLevelFields.has(currFieldName) ||
                    !!groupedData.nestedObjectsKeys[currFieldName] ||
                    !!groupedData.mapsValuesKeys[currFieldName])
                    throw new Error(`Encrypted field appears multiple times as different nested types and or top-level-field: ${path}${currFieldName}`);
                addSubkeyToGroupedData(currFieldName, currFieldSeparator, currEncryptedField, 'arraysValuesKeys');
            }
            else
                throw new Error(`Internal error: unknown separator ${currFieldSeparator} passed regex validation in ${path}${currEncryptedField}`);
        }
        else
            throw new Error(`Invalid encrypted field ${path}${currEncryptedField}`);
    }
    return {
        topLevelFields: Array.from(groupedData.topLevelFields).map((fieldName) => ({ fieldName, fieldPath: path + fieldName })),
        nestedObjectsKeys: Object.fromEntries(Object.entries(groupedData.nestedObjectsKeys).map(([fieldName, fieldNames]) => [
            fieldName,
            parseEncryptedFields(fieldNames, path + fieldName + '.'),
        ])),
        mapsValuesKeys: Object.fromEntries(Object.entries(groupedData.mapsValuesKeys).map(([fieldName, fieldNames]) => [
            fieldName,
            parseEncryptedFields(fieldNames, path + fieldName + '.*.'),
        ])),
        arraysValuesKeys: Object.fromEntries(Object.entries(groupedData.arraysValuesKeys).map(([fieldName, fieldNames]) => [
            fieldName,
            parseEncryptedFields(fieldNames, path + fieldName + '[].'),
        ])),
    };
}
function encryptedObjectDeepEqual(l, r) {
    if (l === null || r === null) {
        return l === r;
    }
    if (Array.isArray(l)) {
        if (!Array.isArray(r) || l.length !== r.length) {
            return false;
        }
        for (let i = 0; i < l.length; i++) {
            if (!encryptedObjectDeepEqual(l[i], r[i])) {
                return false;
            }
        }
    }
    else if (typeof l === 'object') {
        if (typeof r !== 'object' || Array.isArray(r)) {
            return false;
        }
        const toEncryptKeys = Object.keys(l);
        const encryptedSelfContentKeys = Object.keys(r);
        if (toEncryptKeys.length !== encryptedSelfContentKeys.length) {
            return false;
        }
        const allKeys = new Set([...toEncryptKeys, ...encryptedSelfContentKeys]);
        if (allKeys.size !== toEncryptKeys.length) {
            return false;
        }
        for (const key of toEncryptKeys) {
            if (!encryptedObjectDeepEqual(l[key], r[key])) {
                return false;
            }
        }
    }
    else {
        return l === r;
    }
    return true;
}
/**
 * @internal this function is for internal use only and may be changed without notice
 * Encrypt the object graph recursively. Generally return an updated SHALLOW copy, although some fields may be deep copied if they also needed to be
 * recursively encrypted.
 * @param obj the object to encrypt
 * @param cryptor takes in input an object consisting only of the fields to encrypt of obj, and returns the encrypted object.
 * @param decryptor takes in input an "encrytpedSelf" value and returns the decrypted content. Can return null or throw exception if the decryption fails.
 * @param keys the keys to encrypt for the
 * @param path path of the current object, used for error messages.
 * @return a shallow copy of the object with a new encryptedSelfField and without the encrypted fields.
 */
function encryptObject(obj_1, cryptor_1, decryptor_1, keys_1) {
    return __awaiter(this, arguments, void 0, function* (obj, cryptor, decryptor, keys, path = 'obj') {
        const shallowClone = Object.assign({}, obj);
        const currEncryptedFields = {};
        for (const { fieldName } of keys.topLevelFields) {
            const fieldValue = shallowClone[fieldName];
            if (fieldValue !== undefined) {
                // we keep null values
                currEncryptedFields[fieldName] = fieldValue;
                delete shallowClone[fieldName];
            }
        }
        let existingEncryptedSelfMatch = false;
        if (shallowClone.encryptedSelf != undefined) {
            let decrypted = undefined;
            try {
                decrypted = yield decryptor((0, binary_utils_1.string2ua)((0, binary_utils_1.a2b)(shallowClone.encryptedSelf)));
            }
            catch (e) {
                // ignore
            }
            if (decrypted != undefined /* ignore null and undefined */ && encryptedObjectDeepEqual(currEncryptedFields, decrypted)) {
                existingEncryptedSelfMatch = true;
            }
        }
        if (!existingEncryptedSelfMatch) {
            shallowClone['encryptedSelf'] = (0, binary_utils_1.b2a)((0, binary_utils_1.ua2string)(yield cryptor(currEncryptedFields)));
        }
        for (const [fieldName, subKeys] of Object.entries(keys.nestedObjectsKeys)) {
            const fieldValue = shallowClone[fieldName];
            if (fieldValue !== undefined && fieldValue !== null) {
                if (!isPojo(fieldValue))
                    throw new Error(`Expected field ${path}.${fieldName} to be a non-array object`);
                shallowClone[fieldName] = yield encryptObject(fieldValue, cryptor, decryptor, subKeys, path + '.' + fieldName);
            }
        }
        for (const [mapFieldName, subKeys] of Object.entries(keys.mapsValuesKeys)) {
            const fieldValue = shallowClone[mapFieldName];
            if (fieldValue !== undefined && fieldValue !== null) {
                if (!isPojo(fieldValue))
                    throw new Error(`Expected field ${path}.${mapFieldName} to be a non-array object`);
                const newMap = {};
                for (const [key, value] of Object.entries(fieldValue)) {
                    if (value === null || value === undefined) {
                        newMap[key] = value;
                    }
                    else {
                        if (!isPojo(value))
                            throw new Error(`Expected field ${path}.${mapFieldName}.${key} to be a non-array object`);
                        newMap[key] = yield encryptObject(value, cryptor, decryptor, subKeys, path + '.' + mapFieldName + '.' + key);
                    }
                }
                shallowClone[mapFieldName] = newMap;
            }
        }
        for (const [arrayFieldName, subKeys] of Object.entries(keys.arraysValuesKeys)) {
            const fieldValue = shallowClone[arrayFieldName];
            if (fieldValue !== undefined && fieldValue !== null) {
                if (!Array.isArray(fieldValue))
                    throw new Error(`Expected field ${path}.${arrayFieldName} to be an array`);
                const newArray = Array(fieldValue.length);
                for (let i = 0; i < fieldValue.length; i++) {
                    const value = fieldValue[i];
                    if (value === null || value === undefined) {
                        newArray[i] = value;
                    }
                    else {
                        if (!isPojo(value))
                            throw new Error(`Expected field ${path}.${arrayFieldName}[${i}] to be a non-array object`);
                        newArray[i] = yield encryptObject(value, cryptor, decryptor, subKeys, path + '.' + arrayFieldName + '[' + i + ']');
                    }
                }
                shallowClone[arrayFieldName] = newArray;
            }
        }
        return shallowClone;
    });
}
/**
 * Check if value is a non-null object that is not an array.
 */
function isPojo(value) {
    // This break if for some reason we have object versions of String/Number/Boolean
    return value !== null && typeof value === 'object' && !Array.isArray(value) && !(value instanceof ArrayBuffer) && !ArrayBuffer.isView(value);
}
/**
 * @internal this function is for internal use only and may be changed without notice
 * Decrypt object graph recursively.
 *
 * @param obj the object to decrypt
 * @param decryptor the decryptor function (returns a promise); the decryptor returns the decrypted input or null if
 * the decryption fails
 * @return a deep copy of the object with the decrypted fields and removed encryptedSelf field, or null if the
 * decryption failed
 */
function decryptObject(obj, decryptor) {
    return __awaiter(this, void 0, void 0, function* () {
        const copy = {};
        for (const [key, value] of Object.entries(obj)) {
            if (key === 'encryptedSelf') {
                copy[key] = value;
            }
            else if (typeof value === 'object' && value !== null) {
                if (Array.isArray(value)) {
                    // Note: nested arrays (and primitives) are returned as is, and they aren't recursively decrypted.
                    // This is because we currently don't support encryption of elements from arrays in arrays (we only support
                    // arrays in objects in arrays).
                    // In future this may change.
                    const decryptedElements = [];
                    for (const element of value) {
                        if (isPojo(element)) {
                            const decrypted = yield decryptObject(element, decryptor);
                            if (decrypted == null)
                                return null;
                            decryptedElements.push(decrypted);
                        }
                        else {
                            decryptedElements.push(element);
                        }
                    }
                    copy[key] = decryptedElements;
                }
                else if (value instanceof ArrayBuffer || ArrayBuffer.isView(value)) {
                    copy[key] = value; // No direct decryption of array buffer data. Array buffer should be converted to b64 string and encrypted as json data.
                }
                else {
                    const decrypted = yield decryptObject(value, decryptor);
                    if (decrypted == null)
                        return null;
                    copy[key] = decrypted;
                }
            }
            else {
                copy[key] = value;
            }
        }
        if (obj.encryptedSelf) {
            const decrypted = yield decryptor((0, binary_utils_1.string2ua)((0, binary_utils_1.a2b)(obj.encryptedSelf)));
            if (decrypted == null)
                return null;
            return Object.assign(Object.assign({}, copy), decrypted);
        }
        else
            return copy;
    });
}
/**
 * Extracts the full jwk key pair from the jwk representation of the private key.
 * @param privateKeyJwk private key in jwk representation
 * @param shaVersion the SHA algorithm version
 * @throws if the key is missing the public modulus or public exponent.
 */
function keyPairFromPrivateKeyJwk(privateKeyJwk, shaVersion) {
    if (!privateKeyJwk.n || !privateKeyJwk.e)
        throw new Error('Incomplete private JsonWebKey: missing public modulus and/or exponent');
    return {
        privateKey: privateKeyJwk,
        publicKey: spkiToJwk((0, binary_utils_1.hex2ua)(jwk2spki(privateKeyJwk)), shaVersion),
    };
}
//# sourceMappingURL=crypto-utils.js.map