"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.NativeCryptoPrimitivesBridge = void 0;
const shamir_1 = require("./shamir");
const utils_1 = require("../utils");
/**
 * Allows to use the expo-kryptom module as crypto primitives. This is necessary when building expo (react native) apps.
 */
class NativeCryptoPrimitivesBridge {
    constructor(expoKryptomModule) {
        this.strongRandom = expoKryptomModule.StrongRandom;
        this.digest = expoKryptomModule.Digest;
        this.shamir = new StrongRandomShamir(this.strongRandom);
        this.AES = new NativeAesBridge(expoKryptomModule.Aes, this.strongRandom);
        this.HMAC = new NativeHmacBridge(expoKryptomModule.Hmac);
        this.RSA = new NativeRsaBridge(expoKryptomModule.Rsa);
    }
    randomBytes(n) {
        return this.strongRandom.randomBytes(n);
    }
    randomUuid() {
        return this.strongRandom.randomUUID();
    }
    sha256(data) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.digest.sha256(data instanceof ArrayBuffer ? new Uint8Array(data) : data));
        });
    }
    sha512(data) {
        return __awaiter(this, void 0, void 0, function* () {
            throw new Error('Not yet implemented');
        });
    }
}
exports.NativeCryptoPrimitivesBridge = NativeCryptoPrimitivesBridge;
class StrongRandomShamir extends shamir_1.ShamirClass {
    constructor(strongRandom) {
        super();
        this.strongRandom = strongRandom;
    }
    fillRandom(arr) {
        const random = this.strongRandom.randomBytes(arr.byteLength);
        // Note that the new Uint32Array does not copy the random array
        arr.set(new Uint32Array(random.buffer));
    }
}
class NativeAesBridge {
    constructor(aes, random) {
        this.aes = aes;
        this.random = random;
    }
    decrypt(cryptoKey, encryptedData) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.aes.decrypt(new Uint8Array(encryptedData), getKryptomKey(cryptoKey)));
        });
    }
    decryptSome(cryptoKeys, uint8Array) {
        try {
            return this.decrypt(cryptoKeys[0], uint8Array);
        }
        catch (e) {
            if (cryptoKeys.length > 1) {
                return this.decryptSome(cryptoKeys.slice(1), uint8Array);
            }
            else {
                throw e;
            }
        }
    }
    decryptWithRawKey(rawKey, plainData) {
        return __awaiter(this, void 0, void 0, function* () {
            const imported = yield this.importKey('raw', (0, utils_1.hex2ua)(rawKey));
            return this.decrypt(imported, plainData);
        });
    }
    encrypt(cryptoKey, plainData) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.aes.encrypt(new Uint8Array(plainData), getKryptomKey(cryptoKey), null));
        });
    }
    encryptWithRawKey(rawKey, plainData) {
        return __awaiter(this, void 0, void 0, function* () {
            const imported = yield this.importKey('raw', (0, utils_1.hex2ua)(rawKey));
            return this.encrypt(imported, plainData);
        });
    }
    exportKey(cryptoKey, format) {
        return __awaiter(this, void 0, void 0, function* () {
            const rawKey = yield this.aes.exportRawKey(getKryptomKey(cryptoKey));
            if (format == 'raw')
                return (0, utils_1.ua2ab)(rawKey);
            return {
                kty: 'oct',
                alg: rawKey.byteLength == 32 ? 'A256CBC' : 'A128CBC',
                ext: true,
                key_ops: ['encrypt', 'decrypt'],
                k: (0, utils_1.ua2b64)(rawKey).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'),
            };
        });
    }
    generateCryptoKey(toHex) {
        return __awaiter(this, void 0, void 0, function* () {
            if (toHex) {
                return Promise.resolve((0, utils_1.ua2hex)(this.random.randomBytes(32)));
            }
            else {
                const aesKey = yield this.aes.generateKey(AesAlgorithm.AesCbcPkcs7, 256);
                return new AesCryptoKey(aesKey, 256);
            }
        });
    }
    generateIV(ivByteLength) {
        return this.random.randomBytes(ivByteLength);
    }
    importKey(format, aesKey) {
        return __awaiter(this, void 0, void 0, function* () {
            var _a, _b;
            let keyBytes;
            if (format === 'jwk') {
                if (aesKey.kty !== 'oct' ||
                    (aesKey.alg !== 'A256CBC' && aesKey.alg !== 'A128CBC') ||
                    !aesKey.ext ||
                    !((_a = aesKey.key_ops) === null || _a === void 0 ? void 0 : _a.includes('encrypt')) ||
                    !((_b = aesKey.key_ops) === null || _b === void 0 ? void 0 : _b.includes('decrypt')) ||
                    !aesKey.k) {
                    throw new Error('Invalid JWK - must have kty=oct, alg=(A256CBC||A128CBC), ext=true, key_ops=[encrypt, decrypt], and must have non-empty k field');
                }
                // b64_2ua works also with url-safe base64
                keyBytes = new Uint8Array((0, utils_1.b64_2ua)(aesKey.k));
            }
            else if (format === 'raw') {
                if (aesKey instanceof ArrayBuffer && !ArrayBuffer.isView(aesKey))
                    throw new Error("aesKey must be an ArrayBuffer or an ArrayBufferView when format is 'raw'");
                keyBytes = new Uint8Array(aesKey);
            }
            else
                throw new Error(`Invalid format ${format}`);
            const imported = yield this.aes.importRawKey(keyBytes, AesAlgorithm.AesCbcPkcs7);
            return new AesCryptoKey(imported, keyBytes.length);
        });
    }
}
function getKryptomKey(cryptoKey) {
    if ('kryptomKey' in cryptoKey) {
        return cryptoKey.kryptomKey;
    }
    else
        throw new Error('Invalid crypto key: only crypto keys generated or loaded with this implementation of crypto primitives are supported');
}
class AesCryptoKey {
    constructor(kryptomKey, size) {
        this.kryptomKey = kryptomKey;
        this.size = size;
    }
    get algorithm() {
        return { name: 'AES-CBC', length: this.size };
    }
    get extractable() {
        return true;
    }
    get type() {
        return 'secret';
    }
    get usages() {
        return ['encrypt', 'decrypt'];
    }
    toJSON() {
        return {};
    }
}
class NativeHmacBridge {
    constructor(hmac) {
        this.hmac = hmac;
    }
    exportKey(key) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.hmac.exportRawKey(getKryptomKey(key)));
        });
    }
    generateKey() {
        return __awaiter(this, void 0, void 0, function* () {
            const kryptomKey = yield this.hmac.generateKey(HmacAlgorithm.HmacSha512);
            return new HmacCryptoKey(kryptomKey);
        });
    }
    importKey(key) {
        return __awaiter(this, void 0, void 0, function* () {
            const kryptomKey = yield this.hmac.importRawKey(new Uint8Array(key), HmacAlgorithm.HmacSha512);
            return new HmacCryptoKey(kryptomKey);
        });
    }
    sign(key, data) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.hmac.sign(new Uint8Array(data), getKryptomKey(key)));
        });
    }
    verify(key, data, signature) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.hmac.verify(new Uint8Array(signature), new Uint8Array(data), getKryptomKey(key));
        });
    }
}
class HmacCryptoKey {
    constructor(kryptomKey) {
        this.kryptomKey = kryptomKey;
    }
    get algorithm() {
        return { name: 'HMAC', hash: { name: 'SHA-512' }, length: 1024 };
    }
    get extractable() {
        return true;
    }
    get type() {
        return 'secret';
    }
    get usages() {
        return ['sign', 'verify'];
    }
    toJSON() {
        return {};
    }
}
class NativeRsaBridge {
    constructor(rsa) {
        this.rsa = rsa;
    }
    checkKeyPairValidity(keyPair) {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                const text = 'shibboleth';
                const encryptedText = yield this.encrypt(keyPair.publicKey, (0, utils_1.utf8_2ua)(text));
                const decryptedText = (0, utils_1.ua2utf8)(yield this.decrypt(keyPair.privateKey, new Uint8Array(encryptedText)));
                return decryptedText === text;
            }
            catch (e) {
                return false;
            }
        });
    }
    decrypt(privateKey, encryptedData) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.rsa.decrypt(encryptedData, getKryptomKey(privateKey)));
        });
    }
    encrypt(publicKey, plainData) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.rsa.encrypt(plainData, getKryptomKey(publicKey)));
        });
    }
    exportKey(cryptoKey, format) {
        return __awaiter(this, void 0, void 0, function* () {
            if (format === 'jwk') {
                if (cryptoKey.type === 'private') {
                    return yield this.rsa.exportPrivateKeyJwk(getKryptomKey(cryptoKey));
                }
                else {
                    return yield this.rsa.exportPublicKeyJwk(getKryptomKey(cryptoKey));
                }
            }
            else if (format === 'spki') {
                return (0, utils_1.ua2ab)(yield this.rsa.exportPublicKeySpki(getKryptomKey(cryptoKey)));
            }
            else if (format === 'pkcs8') {
                return (0, utils_1.ua2ab)(yield this.rsa.exportPrivateKeyPkcs8(getKryptomKey(cryptoKey)));
            }
            else
                throw new Error(`Invalid format ${format}`);
        });
    }
    exportKeys(keyPair, privKeyFormat, pubKeyFormat) {
        return __awaiter(this, void 0, void 0, function* () {
            let privateKey;
            if (privKeyFormat === 'jwk') {
                privateKey = yield this.rsa.exportPrivateKeyJwk(getKryptomKey(keyPair.privateKey));
            }
            else {
                privateKey = (0, utils_1.ua2ab)(yield this.rsa.exportPrivateKeyPkcs8(getKryptomKey(keyPair.privateKey)));
            }
            let publicKey;
            if (pubKeyFormat === 'jwk') {
                publicKey = yield this.rsa.exportPublicKeyJwk(getKryptomKey(keyPair.publicKey));
            }
            else {
                publicKey = (0, utils_1.ua2ab)(yield this.rsa.exportPublicKeySpki(getKryptomKey(keyPair.publicKey)));
            }
            return { privateKey, publicKey };
        });
    }
    generateKeyPair(shaVersion) {
        return __awaiter(this, void 0, void 0, function* () {
            const generated = yield this.rsa.generateKey(this.kryptomAlgorithm(shaVersion), 2048);
            // In expo-kryptom a private keys and public keys are supertypes of keypair.
            return {
                privateKey: new PrivateRsaCryptoKey(generated, this.encryptionKeysAlgorithm(shaVersion)),
                publicKey: new PublicRsaCryptoKey(generated, this.encryptionKeysAlgorithm(shaVersion)),
            };
        });
    }
    generateSignatureKeyPair() {
        return __awaiter(this, void 0, void 0, function* () {
            const generated = yield this.rsa.generateKey(RsaSignatureAlgorithm.PssWithSha256, 2048);
            return {
                privateKey: new PrivateRsaCryptoKey(generated, this.signatureKeysAlgorithm()),
                publicKey: new PublicRsaCryptoKey(generated, this.signatureKeysAlgorithm()),
            };
        });
    }
    importKey(format, keydata, keyUsages, hashAlgorithm) {
        if (keyUsages[0] == 'encrypt' && (format == 'jwk' || format == 'spki')) {
            return this.importPublicKey(format, keydata, hashAlgorithm);
        }
        else if (keyUsages[0] == 'decrypt' && (format == 'jwk' || format == 'pkcs8')) {
            return this.importPrivateKey(format, keydata, hashAlgorithm);
        }
        else
            throw new Error(`Combination of key usages and key format not supported: ${keyUsages} ${format}`);
    }
    importKeyPair(privateKeyFormat, privateKeydata, publicKeyFormat, publicKeyData, hashAlgorithm) {
        return __awaiter(this, void 0, void 0, function* () {
            let importedPrivate;
            let importedPublic;
            const algorithm = this.kryptomAlgorithm(hashAlgorithm);
            if (privateKeyFormat == 'jwk') {
                importedPrivate = yield this.rsa.importPrivateKeyJwk(privateKeydata, algorithm);
            }
            else {
                importedPrivate = yield this.rsa.importPrivateKeyPkcs8(new Uint8Array(privateKeydata), algorithm);
            }
            if (publicKeyFormat == 'jwk') {
                importedPublic = yield this.rsa.importPublicKeyJwk(publicKeyData, algorithm);
            }
            else {
                importedPublic = yield this.rsa.importPublicKeySpki(new Uint8Array(publicKeyData), algorithm);
            }
            return {
                privateKey: new PrivateRsaCryptoKey(importedPrivate, this.encryptionKeysAlgorithm(hashAlgorithm)),
                publicKey: new PublicRsaCryptoKey(importedPublic, this.encryptionKeysAlgorithm(hashAlgorithm)),
            };
        });
    }
    importPrivateKey(format, keydata, hashAlgorithm) {
        return __awaiter(this, void 0, void 0, function* () {
            let imported;
            if (format == 'jwk') {
                imported = yield this.rsa.importPrivateKeyJwk(keydata, this.kryptomAlgorithm(hashAlgorithm));
            }
            else {
                imported = yield this.rsa.importPrivateKeyPkcs8(new Uint8Array(keydata), this.kryptomAlgorithm(hashAlgorithm));
            }
            return new PrivateRsaCryptoKey(imported, this.encryptionKeysAlgorithm(hashAlgorithm));
        });
    }
    importPublicKey(format, keydata, hashAlgorithm) {
        return __awaiter(this, void 0, void 0, function* () {
            let imported;
            if (format == 'jwk') {
                imported = yield this.rsa.importPublicKeyJwk(keydata, this.kryptomAlgorithm(hashAlgorithm));
            }
            else {
                imported = yield this.rsa.importPublicKeySpki(new Uint8Array(keydata), this.kryptomAlgorithm(hashAlgorithm));
            }
            return new PublicRsaCryptoKey(imported, this.encryptionKeysAlgorithm(hashAlgorithm));
        });
    }
    importSignatureKey(format, keydata) {
        return __awaiter(this, void 0, void 0, function* () {
            let importedKey;
            if (format == 'jwk') {
                importedKey = yield this.rsa.importPrivateKeyJwk(keydata, RsaSignatureAlgorithm.PssWithSha256);
            }
            else {
                importedKey = yield this.rsa.importPrivateKeyPkcs8(new Uint8Array(keydata), RsaSignatureAlgorithm.PssWithSha256);
            }
            return new PrivateRsaCryptoKey(importedKey, this.signatureKeysAlgorithm());
        });
    }
    importVerificationKey(format, keydata) {
        return __awaiter(this, void 0, void 0, function* () {
            let importedKey;
            if (format == 'jwk') {
                importedKey = yield this.rsa.importPublicKeyJwk(keydata, RsaSignatureAlgorithm.PssWithSha256);
            }
            else {
                importedKey = yield this.rsa.importPublicKeySpki(new Uint8Array(keydata), RsaSignatureAlgorithm.PssWithSha256);
            }
            return new PublicRsaCryptoKey(importedKey, this.signatureKeysAlgorithm());
        });
    }
    sign(privateKey, data) {
        return __awaiter(this, void 0, void 0, function* () {
            return (0, utils_1.ua2ab)(yield this.rsa.signature(new Uint8Array(data), getKryptomKey(privateKey)));
        });
    }
    verifySignature(publicKey, signature, data) {
        return __awaiter(this, void 0, void 0, function* () {
            return yield this.rsa.verify(new Uint8Array(signature), new Uint8Array(data), getKryptomKey(publicKey));
        });
    }
    kryptomAlgorithm(shaVersion) {
        return shaVersion == 'sha-256' ? RsaEncryptionAlgorithm.OaepWithSha256 : RsaEncryptionAlgorithm.OaepWithSha1;
    }
    encryptionKeysAlgorithm(shaVersion) {
        return {
            name: 'RSA-OAEP',
            modulusLength: 2048,
            publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // Equivalent to 65537 (Fermat F4), read http://en.wikipedia.org/wiki/65537_(number)
            hash: { name: shaVersion == 'sha-256' ? 'SHA-256' : 'SHA-1' },
        };
    }
    signatureKeysAlgorithm() {
        return {
            name: 'RSA-PSS',
            modulusLength: 2048,
            publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // Equivalent to 65537 (Fermat F4), read http://en.wikipedia.org/wiki/65537_(number)
            hash: { name: 'SHA-256' },
        };
    }
}
class PrivateRsaCryptoKey {
    constructor(kryptomKey, algorithm) {
        this.kryptomKey = kryptomKey;
        this.algorithm = algorithm;
    }
    get extractable() {
        return true;
    }
    get type() {
        return 'private';
    }
    get usages() {
        return this.algorithm.name != 'RSA-PSS' ? ['decrypt'] : ['sign'];
    }
    toJSON() {
        return {};
    }
}
class PublicRsaCryptoKey {
    constructor(kryptomKey, algorithm) {
        this.kryptomKey = kryptomKey;
        this.algorithm = algorithm;
    }
    get extractable() {
        return true;
    }
    get type() {
        return 'public';
    }
    get usages() {
        return this.algorithm.name != 'RSA-PSS' ? ['encrypt'] : ['verify'];
    }
    toJSON() {
        return {};
    }
}
var AesAlgorithm;
(function (AesAlgorithm) {
    AesAlgorithm["AesCbcPkcs7"] = "AesCbcPkcs7";
})(AesAlgorithm || (AesAlgorithm = {}));
var RsaEncryptionAlgorithm;
(function (RsaEncryptionAlgorithm) {
    RsaEncryptionAlgorithm["OaepWithSha1"] = "OaepWithSha1";
    RsaEncryptionAlgorithm["OaepWithSha256"] = "OaepWithSha256";
})(RsaEncryptionAlgorithm || (RsaEncryptionAlgorithm = {}));
var RsaSignatureAlgorithm;
(function (RsaSignatureAlgorithm) {
    RsaSignatureAlgorithm["PssWithSha256"] = "PssWithSha256";
})(RsaSignatureAlgorithm || (RsaSignatureAlgorithm = {}));
var HmacAlgorithm;
(function (HmacAlgorithm) {
    HmacAlgorithm["HmacSha512"] = "HmacSha512";
})(HmacAlgorithm || (HmacAlgorithm = {}));
//# sourceMappingURL=NativeCryptoPrimitivesBridge.js.map