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());
    });
};
import { Convert } from '@enbox/common';
import { CryptoError, CryptoErrorCode } from '@enbox/crypto';
/**
 * Checks if the provided object is a valid JWE (JSON Web Encryption) header.
 *
 * This function evaluates whether the given object adheres to the structure expected for
 * a JWE header, specifically looking for the presence and proper format of the "alg" (algorithm)
 * and "enc" (encryption algorithm) properties, which are essential for defining the JWE's
 * cryptographic operations.
 *
 * @example
 * ```ts
 * const header = {
 *   alg: 'dir',
 *   enc: 'A256GCM'
 * };
 *
 * if (isValidJweHeader(header)) {
 *   console.log('The object is a valid JWE header.');
 * } else {
 *   console.log('The object is not a valid JWE header.');
 * }
 * ```
 *
 * @param obj - The object to be validated as a JWE header.
 * @returns Returns `true` if the object is a valid JWE header, otherwise `false`.
 */
export function isValidJweHeader(obj) {
    return typeof obj === 'object' && obj !== null
        && 'alg' in obj && obj.alg !== undefined
        && 'enc' in obj && obj.enc !== undefined;
}
/**
 * The `JweKeyManagement` class implements the key management aspects of JSON Web Encryption (JWE)
 * as specified in {@link https://datatracker.ietf.org/doc/html/rfc7516 | RFC 7516}.
 *
 * It supports algorithms for encrypting and decrypting keys, thereby enabling the secure
 * transmission of information where the payload is encrypted, and the encryption key is also
 * encrypted or agreed upon using key agreement techniques.
 *
 * The choice of algorithm is determined by the "alg" parameter in the JWE
 * header, and the class is designed to handle the intricacies associated with each algorithm,
 * ensuring the secure handling of the encryption keys.
 *
 * Supported algorithms include:
 * - `"dir"`: Direct Encryption Mode
 * - `"PBES2-HS256+A128KW"`, `"PBES2-HS384+A192KW"`, `"PBES2-HS512+A256KW"`: Password-Based
 *   Encryption Mode with Key Wrapping (PBES2) using HMAC-SHA and AES Key Wrap algorithms for key
 *   wrapping and encryption.
 *
 * @example
 * // To encrypt a key:
 * const keyEncryptionKey = Convert.string(passphrase).toUint8Array()
 * const { cek, encryptedKey: encryptedCek } = await JweKeyManagement.encrypt({
 *   key: keyEncryptionKey,
 *   joseHeader: {
 *     alg: 'PBES2-HS512+A256KW',
 *     enc: 'A256GCM',
 *     p2c : 210_000,
       p2s : Convert.uint8Array(saltInput).toBase64Url()
 *   },
 *   crypto: new AgentCryptoApi(),
 * });
 *
 * // To decrypt a key:
 * const cek = await JweKeyManagement.decrypt({
 *   key: keyEncryptionKey,
 *   encryptedKey: encryptedCek,
 *   joseHeader: {
 *     alg: 'PBES2-HS512+A256KW',
 *     enc: 'A256GCM',
 *     p2c : 210_000,
       p2s : Convert.uint8Array(saltInput).toBase64Url()
 *   },
 *   crypto: new AgentCryptoApi(),
 * });
 */
export class JweKeyManagement {
    /**
     * Decrypts the encrypted key (JWE Encrypted Key) using the specified key encryption algorithm
     * defined in the JWE Header's "alg" parameter.
     *
     * This method supports multiple key management algorithms, including Direct Encryption (dir) and
     * PBES2 schemes with key wrapping.
     *
     * The method takes a key, which can be a Key Identifier, JWK, or raw byte array, and the
     * encrypted key along with the JWE header. It returns the decrypted Content Encryption Key (CEK)
     * which can then be used to decrypt the JWE ciphertext.
     *
     * @example
     * ```ts
     * // Decrypting the CEK with the PBES2-HS512+A256KW algorithm
     * const cek = await JweKeyManagement.decrypt({
     *   key: Convert.string(passphrase).toUint8Array(),
     *   encryptedKey: encryptedCek,
     *   joseHeader: {
     *     alg: 'PBES2-HS512+A256KW',
     *     enc: 'A256GCM',
     *     p2c: 210_000,
     *     p2s: Convert.uint8Array(saltInput).toBase64Url(),
     *   },
     *   crypto: new AgentCryptoApi()
     * });
     * ```
     *
     * @param params - The decryption parameters.
     * @throws Throws an error if the key management algorithm is not supported or if required
     *         parameters are missing or invalid.
     */
    static decrypt(_a, options_1) {
        return __awaiter(this, arguments, void 0, function* ({ key, encryptedKey, joseHeader, crypto }, options) {
            var _b;
            const minP2cCount = (_b = options === null || options === void 0 ? void 0 : options.minP2cCount) !== null && _b !== void 0 ? _b : 1000;
            // Determine the Key Management Mode employed by the algorithm specified by the "alg"
            // (algorithm) Header Parameter.
            switch (joseHeader.alg) {
                case 'dir': {
                    // In Direct Encryption mode, a JWE "Encrypted Key" is not provided. Instead, the
                    // provided key management `key` is directly used as the Content Encryption Key (CEK) to
                    // decrypt the JWE payload.
                    // Verify that the JWE Encrypted Key value is empty.
                    if (encryptedKey !== undefined) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JWE "encrypted_key" is not allowed when using "dir" (Direct Encryption Mode).');
                    }
                    // Verify the key management `key` is a Key Identifier or JWK.
                    if (key instanceof Uint8Array) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'Key management "key" must be a Key URI or JWK when using "dir" (Direct Encryption Mode).');
                    }
                    // return the key management `key` as the CEK.
                    return key;
                }
                case 'PBES2-HS256+A128KW':
                case 'PBES2-HS384+A192KW':
                case 'PBES2-HS512+A256KW': {
                    // In Key Encryption mode (PBES2) with key wrapping (A128KW, A192KW, A256KW), the given
                    // passphrase, salt (p2s), and iteration count (p2c) are used with the PBKDF2 key derivation
                    // function to derive the Key Encryption Key (KEK).  The KEK is then used to decrypt the JWE
                    // Encrypted Key to obtain the Content Encryption Key (CEK).
                    if (typeof joseHeader.p2c !== 'number') {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2c" (PBES2 Count) is missing or not a number.');
                    }
                    // Per RFC 7518, Section 4.8.1.2, a minimum iteration count of 1000 is RECOMMENDED.
                    // Enforce this floor to prevent an attacker from supplying a crafted JWE with a
                    // trivially low iteration count that would weaken key derivation.
                    if (joseHeader.p2c < minP2cCount) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, `JOSE Header "p2c" (PBES2 Count) is ${joseHeader.p2c}, which is below the minimum of ${minP2cCount}.`);
                    }
                    if (typeof joseHeader.p2s !== 'string') {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2s" (PBES2 salt) is missing or not a string.');
                    }
                    // Throw an error if the key management `key` is not a byte array. For PBES2, the key is
                    // expected to be a low-entropy passphrase as a byte array.
                    if (!(key instanceof Uint8Array)) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'Key management "key" must be a Uint8Array when using "PBES2" (Key Encryption Mode).');
                    }
                    // Verify that the JWE Encrypted Key value is present.
                    if (encryptedKey === undefined) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JWE "encrypted_key" is required when using "PBES2" (Key Encryption Mode).');
                    }
                    // Per {@link https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 | RFC 7518, Section 4.8.1.1},
                    // the salt value used with PBES2 should be of the format (UTF8(Alg) || 0x00 || Salt Input),
                    // where Alg is the "alg" (algorithm) Header Parameter value. This reduces the potential for
                    // a precomputed dictionary attack (also known as a rainbow table attack).
                    let salt;
                    try {
                        salt = new Uint8Array([
                            ...Convert.string(joseHeader.alg).toUint8Array(),
                            0x00,
                            ...Convert.base64Url(joseHeader.p2s).toUint8Array()
                        ]);
                    }
                    catch (_c) {
                        throw new CryptoError(CryptoErrorCode.EncodingError, 'Failed to decode the JOSE Header "p2s" (PBES2 salt) value.');
                    }
                    // Derive the Key Encryption Key (KEK) from the given passphrase, salt, and iteration count.
                    const kek = yield crypto.deriveKey({
                        algorithm: joseHeader.alg,
                        baseKeyBytes: key,
                        iterations: joseHeader.p2c,
                        salt
                    });
                    if (!(kek.alg && ['A128KW', 'A192KW', 'A256KW'].includes(kek.alg))) {
                        throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `Unsupported Key Encryption Algorithm (alg) value: ${kek.alg}`);
                    }
                    // Decrypt the Content Encryption Key (CEK) with the derived KEK.
                    return yield crypto.unwrapKey({
                        decryptionKey: kek,
                        wrappedKeyBytes: encryptedKey,
                        wrappedKeyAlgorithm: joseHeader.enc
                    });
                }
                default: {
                    throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `Unsupported "alg" (Algorithm) Header Parameter value: ${joseHeader.alg}`);
                }
            }
        });
    }
    /**
     * Encrypts a Content Encryption Key (CEK) using the key management algorithm specified in the
     * JWE Header's "alg" parameter.
     *
     * This method supports various key management algorithms, including Direct Encryption (dir) and
     * PBES2 with key wrapping.
     *
     * It generates a random CEK for the specified encryption algorithm in the JWE header, which
     * can then be used to encrypt the actual payload. For algorithms that require an encrypted key,
     * it returns the CEK along with the encrypted key.
     *
     * @example
     * ```ts
     * // Encrypting the CEK with the PBES2-HS512+A256KW algorithm
     * const { cek, encryptedKey } = await JweKeyManagement.encrypt({
     *   key: Convert.string(passphrase).toUint8Array(),
     *   joseHeader: {
     *     alg: 'PBES2-HS512+A256KW',
     *     enc: 'A256GCM',
     *     p2c: 210_000,
     *     p2s: Convert.uint8Array(saltInput).toBase64Url(),
     *   },
     *   crypto: crypto: new AgentCryptoApi()
     * });
     * ```
     *
     * @param params - The encryption parameters.
     * @returns The encrypted key result containing the CEK and optionally the encrypted CEK
     *          (JWE Encrypted Key).
     * @throws Throws an error if the key management algorithm is not supported or if required
     *         parameters are missing or invalid.
     */
    static encrypt(_a) {
        return __awaiter(this, arguments, void 0, function* ({ key, joseHeader, crypto }) {
            let cek;
            let encryptedKey;
            // Determine the Key Management Mode employed by the algorithm specified by the "alg"
            // (algorithm) Header Parameter.
            switch (joseHeader.alg) {
                case 'dir': {
                    // In Direct Encryption mode (dir), a JWE "Encrypted Key" is not provided. Instead, the
                    // provided key management `key` is directly used as the Content Encryption Key (CEK) to
                    // decrypt the JWE payload.
                    // Verify that the JWE Encrypted Key value is empty.
                    if (encryptedKey !== undefined) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JWE "encrypted_key" is not allowed when using "dir" (Direct Encryption Mode).');
                    }
                    // Verify the key management `key` is a Key Identifier or JWK.
                    if (key instanceof Uint8Array) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'Key management "key" must be a Key URI or JWK when using "dir" (Direct Encryption Mode).');
                    }
                    // Set the CEK to the key management `key`.
                    cek = key;
                    break;
                }
                case 'PBES2-HS256+A128KW':
                case 'PBES2-HS384+A192KW':
                case 'PBES2-HS512+A256KW': {
                    // In Key Encryption mode (PBES2) with key wrapping (A128KW, A192KW, A256KW), a randomly
                    // generated Content Encryption Key (CEK) is encrypted with a Key Encryption Key (KEK)
                    // derived from the given passphrase, salt (p2s), and iteration count (p2c) using the
                    // PBKDF2 key derivation function.
                    if (typeof joseHeader.p2c !== 'number') {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2c" (PBES2 Count) is missing or not a number.');
                    }
                    if (typeof joseHeader.p2s !== 'string') {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'JOSE Header "p2s" (PBES2 salt) is missing or not a string.');
                    }
                    // Throw an error if the key management `key` is not a byte array.
                    if (!(key instanceof Uint8Array)) {
                        throw new CryptoError(CryptoErrorCode.InvalidJwe, 'Key management "key" must be a Uint8Array when using "PBES2" (Key Encryption Mode).');
                    }
                    // Generate a random Content Encryption Key (CEK) using the algorithm specified by the "enc"
                    // (encryption) Header Parameter.
                    cek = yield crypto.generateKey({ algorithm: joseHeader.enc });
                    // Per {@link https://www.rfc-editor.org/rfc/rfc7518.html#section-4.8.1.1 | RFC 7518, Section 4.8.1.1},
                    // the salt value used with PBES2 should be of the format (UTF8(Alg) || 0x00 || Salt Input),
                    // where Alg is the "alg" (algorithm) Header Parameter value. This reduces the potential for
                    // a precomputed dictionary attack (also known as a rainbow table attack).
                    let salt;
                    try {
                        salt = new Uint8Array([
                            ...Convert.string(joseHeader.alg).toUint8Array(),
                            0x00,
                            ...Convert.base64Url(joseHeader.p2s).toUint8Array()
                        ]);
                    }
                    catch (_b) {
                        throw new CryptoError(CryptoErrorCode.EncodingError, 'Failed to decode the JOSE Header "p2s" (PBES2 salt) value.');
                    }
                    // Derive a Key Encryption Key (KEK) from the given passphrase, salt, and iteration count.
                    const kek = yield crypto.deriveKey({
                        algorithm: joseHeader.alg,
                        baseKeyBytes: key,
                        iterations: joseHeader.p2c,
                        salt
                    });
                    // Encrypt the randomly generated CEK with the derived Key Encryption Key (KEK).
                    encryptedKey = yield crypto.wrapKey({ encryptionKey: kek, unwrappedKey: cek });
                    break;
                }
                default: {
                    throw new CryptoError(CryptoErrorCode.AlgorithmNotSupported, `Unsupported "alg" (Algorithm) Header Parameter value: ${joseHeader.alg}`);
                }
            }
            return { cek, encryptedKey };
        });
    }
}
//# sourceMappingURL=jwe.js.map