// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { MsalNode } from "./msalNodeCommon";
import { createHash, createPrivateKey } from "crypto";
import { formatError } from "../../util/logging";
import { promisify } from "util";
import { readFile } from "fs";
const readFileAsync = promisify(readFile);
/**
 * Tries to asynchronously load a certificate from the given path.
 *
 * @param configuration - Either the PEM value or the path to the certificate.
 * @param sendCertificateChain - Option to include x5c header for SubjectName and Issuer name authorization.
 * @returns - The certificate parts, or `undefined` if the certificate could not be loaded.
 * @internal
 */
export async function parseCertificate(configuration, sendCertificateChain) {
    const certificateParts = {};
    const certificate = configuration
        .certificate;
    const certificatePath = configuration
        .certificatePath;
    certificateParts.certificateContents =
        certificate || (await readFileAsync(certificatePath, "utf8"));
    if (sendCertificateChain) {
        certificateParts.x5c = certificateParts.certificateContents;
    }
    const certificatePattern = /(-+BEGIN CERTIFICATE-+)(\n\r?|\r\n?)([A-Za-z0-9+/\n\r]+=*)(\n\r?|\r\n?)(-+END CERTIFICATE-+)/g;
    const publicKeys = [];
    // Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c
    let match;
    do {
        match = certificatePattern.exec(certificateParts.certificateContents);
        if (match) {
            publicKeys.push(match[3]);
        }
    } while (match);
    if (publicKeys.length === 0) {
        throw new Error("The file at the specified path does not contain a PEM-encoded certificate.");
    }
    certificateParts.thumbprint = createHash("sha1")
        .update(Buffer.from(publicKeys[0], "base64"))
        .digest("hex")
        .toUpperCase();
    return certificateParts;
}
/**
 * MSAL client certificate client. Calls to MSAL's confidential application's `acquireTokenByClientCredential` during `doGetToken`.
 * @internal
 */
export class MsalClientCertificate extends MsalNode {
    constructor(options) {
        super(options);
        this.requiresConfidential = true;
        this.configuration = options.configuration;
        this.sendCertificateChain = options.sendCertificateChain;
    }
    // Changing the MSAL configuration asynchronously
    async init(options) {
        try {
            const parts = await parseCertificate(this.configuration, this.sendCertificateChain);
            let privateKey;
            if (this.configuration.certificatePassword !== undefined) {
                const privateKeyObject = createPrivateKey({
                    key: parts.certificateContents,
                    passphrase: this.configuration.certificatePassword,
                    format: "pem",
                });
                privateKey = privateKeyObject
                    .export({
                    format: "pem",
                    type: "pkcs8",
                })
                    .toString();
            }
            else {
                privateKey = parts.certificateContents;
            }
            this.msalConfig.auth.clientCertificate = {
                thumbprint: parts.thumbprint,
                privateKey: privateKey,
                x5c: parts.x5c,
            };
        }
        catch (error) {
            this.logger.info(formatError("", error));
            throw error;
        }
        return super.init(options);
    }
    async doGetToken(scopes, options = {}) {
        try {
            const clientCredReq = {
                scopes,
                correlationId: options.correlationId,
                azureRegion: this.azureRegion,
                authority: options.authority,
                claims: options.claims,
            };
            const result = await this.getApp("confidential", options.enableCae).acquireTokenByClientCredential(clientCredReq);
            // Even though we're providing the same default in memory persistence cache that we use for DeviceCodeCredential,
            // The Client Credential flow does not return the account information from the authentication service,
            // so each time getToken gets called, we will have to acquire a new token through the service.
            return this.handleResult(scopes, this.clientId, result || undefined);
        }
        catch (err) {
            throw this.handleError(scopes, err, options);
        }
    }
}
//# sourceMappingURL=msalClientCertificate.js.map