"use strict";
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.increment = exports.rawDerive = exports.kdfCounterMode = exports.INT32_MAX_LIMIT = void 0;
/*
 * Implementation of the https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-108r1.pdf
 * Key Derivation in Counter Mode Using Pseudorandom Functions. This
 * implementation mirrors the Dafny one: https://github.com/aws/aws-cryptographic-material-providers-library/blob/main/AwsCryptographyPrimitives/src/KDF/KdfCtr.dfy
 */
const crypto_1 = require("crypto");
const material_management_1 = require("@aws-crypto/material-management");
const serialize_1 = require("@aws-crypto/serialize");
const SEPARATION_INDICATOR = Buffer.from([0x00]);
const COUNTER_START_VALUE = 1;
exports.INT32_MAX_LIMIT = 2147483647;
const SUPPORTED_IKM_LENGTHS = [32, 48, 66];
const SUPPORTED_NONCE_LENGTHS = [16, 32];
const SUPPORTED_DERIVED_KEY_LENGTHS = [32, 64];
const SUPPORTED_DIGEST_ALGORITHMS = ['sha256', 'sha384'];
function kdfCounterMode({ digestAlgorithm, ikm, nonce, purpose, expectedLength, }) {
    /* Precondition: the ikm must be 32, 48, 66 bytes long */
    (0, material_management_1.needs)(SUPPORTED_IKM_LENGTHS.includes(ikm.length), `Unsupported IKM length ${ikm.length}`);
    /* Precondition: the nonce is required */
    (0, material_management_1.needs)(nonce, 'The nonce must be provided');
    /* Precondition: the nonce must be 16, 32 bytes long */
    (0, material_management_1.needs)(SUPPORTED_NONCE_LENGTHS.includes(nonce.length), `Unsupported nonce length ${nonce.length}`);
    /* Precondition: the expected length must be 32, 64 bytes */
    /* Precondition: the expected length * 8 must be under the max 32-bit signed integer */
    (0, material_management_1.needs)(SUPPORTED_DERIVED_KEY_LENGTHS.includes(expectedLength) &&
        expectedLength * 8 < exports.INT32_MAX_LIMIT &&
        expectedLength * 8 > 0, `Unsupported requested length ${expectedLength}`);
    const label = purpose || Buffer.alloc(0);
    const info = nonce;
    const internalLength = 8 + SEPARATION_INDICATOR.length;
    /* Precondition: the input length must be under the max 32-bit signed integer */
    (0, material_management_1.needs)(internalLength + label.length + info.length < exports.INT32_MAX_LIMIT, `Input Length ${internalLength + label.length + info.length} must be under ${exports.INT32_MAX_LIMIT} bytes`);
    const lengthBits = Buffer.from((0, serialize_1.uInt32BE)(expectedLength * 8));
    const explicitInfo = Buffer.concat([
        label,
        SEPARATION_INDICATOR,
        info,
        lengthBits,
    ]);
    return rawDerive(ikm, explicitInfo, expectedLength, digestAlgorithm);
}
exports.kdfCounterMode = kdfCounterMode;
function rawDerive(ikm, explicitInfo, length, 
// omit offset as a parameter because it is unused, causing compile errors due
// to configured project settings
digestAlgorithm) {
    const h = (0, crypto_1.createHash)(digestAlgorithm).digest().length;
    /* Precondition: expected length must be positive */
    (0, material_management_1.needs)(length > 0, `Requested length ${length} must be positive`);
    /* Precondition: length of explicit info + 4 bytes should be under the max 32-bit signed integer */
    (0, material_management_1.needs)(4 + explicitInfo.length < exports.INT32_MAX_LIMIT, `Explicit info length ${explicitInfo.length} must be under ${exports.INT32_MAX_LIMIT - 4} bytes`);
    /* Precondition: the digest algorithm should be sha256 */
    (0, material_management_1.needs)(SUPPORTED_DIGEST_ALGORITHMS.includes(digestAlgorithm), `Unsupported digest algorithm ${digestAlgorithm}`);
    /* Precondition: the expected length + digest hash length should be under the max 32-bit signed integer - 1 */
    (0, material_management_1.needs)(length + h < exports.INT32_MAX_LIMIT - 1, `The combined requested and digest hash length ${length + h} must be under ${exports.INT32_MAX_LIMIT - 1} bytes`);
    // number of iterations calculated in accordance with SP800-108
    const iterations = Math.floor((length + h - 1) / h);
    let buffer = Buffer.alloc(0);
    let i = Buffer.from((0, serialize_1.uInt32BE)(COUNTER_START_VALUE));
    for (let iteration = 1; iteration <= iterations; iteration++) {
        const digest = (0, crypto_1.createHmac)(digestAlgorithm, ikm)
            .update(i)
            .update(explicitInfo)
            .digest();
        buffer = Buffer.concat([buffer, digest]);
        i = increment(i);
    }
    (0, material_management_1.needs)(buffer.length >= length, 'Failed to derive key of requested length');
    return buffer.subarray(0, length);
}
exports.rawDerive = rawDerive;
function increment(x) {
    /* Precondition: buffer length must be 4 bytes */
    (0, material_management_1.needs)(x.length === 4, `Buffer length ${x.length} must be 4 bytes`);
    let output;
    if (x[3] < 255) {
        output = Buffer.from([x[0], x[1], x[2], x[3] + 1]);
    }
    else if (x[2] < 255) {
        output = Buffer.from([x[0], x[1], x[2] + 1, 0]);
    }
    else if (x[1] < 255) {
        output = Buffer.from([x[0], x[1] + 1, 0, 0]);
    }
    else if (x[0] < 255) {
        output = Buffer.from([x[0] + 1, 0, 0, 0]);
    }
    else {
        throw new Error('Unable to derive key material; may have exceeded limit.');
    }
    /* Postcondition: incremented buffer length must be 4 bytes */
    (0, material_management_1.needs)(output.length === 4, `Incremented buffer length ${output.length} must be 4 bytes`);
    return output;
}
exports.increment = increment;
//# sourceMappingURL=data:application/json;base64, 