wangzhibo
2026-05-09 53418655c061ac26f66058253f39200aced1ab1d
1
{"version":3,"file":"clientCertificateCredential.js","sourceRoot":"","sources":["../../../src/credentials/clientCertificateCredential.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport type { AccessToken, GetTokenOptions, TokenCredential } from \"@azure/core-auth\";\nimport type { MsalClient } from \"../msal/nodeFlows/msalClient.js\";\nimport { createMsalClient } from \"../msal/nodeFlows/msalClient.js\";\nimport { createHash, createPrivateKey } from \"node:crypto\";\nimport {\n  processMultiTenantRequest,\n  resolveAdditionallyAllowedTenantIds,\n} from \"../util/tenantIdUtils.js\";\n\nimport type { CertificateParts } from \"../msal/types.js\";\nimport type { ClientCertificateCredentialOptions } from \"./clientCertificateCredentialOptions.js\";\nimport { credentialLogger } from \"../util/logging.js\";\nimport { readFile } from \"node:fs/promises\";\nimport { tracingClient } from \"../util/tracing.js\";\nimport type {\n  ClientCertificateCredentialPEMConfiguration,\n  ClientCertificatePEMCertificate,\n  ClientCertificatePEMCertificatePath,\n} from \"./clientCertificateCredentialModels.js\";\n\nconst credentialName = \"ClientCertificateCredential\";\nconst logger = credentialLogger(credentialName);\n\n/**\n * Enables authentication to Microsoft Entra ID using a PEM-encoded\n * certificate that is assigned to an App Registration. More information\n * on how to configure certificate authentication can be found here:\n *\n * https://learn.microsoft.com/azure/active-directory/develop/active-directory-certificate-credentials#register-your-certificate-with-azure-ad\n *\n */\nexport class ClientCertificateCredential implements TokenCredential {\n  private tenantId: string;\n  private additionallyAllowedTenantIds: string[];\n  private certificateConfiguration: ClientCertificateCredentialPEMConfiguration;\n  private sendCertificateChain?: boolean;\n  private msalClient: MsalClient;\n\n  /**\n   * Creates an instance of the ClientCertificateCredential with the details\n   * needed to authenticate against Microsoft Entra ID with a certificate.\n   *\n   * @param tenantId - The Microsoft Entra tenant (directory) ID.\n   * @param clientId - The client (application) ID of an App Registration in the tenant.\n   * @param certificatePath - The path to a PEM-encoded public/private key certificate on the filesystem.\n   * Ensure that certificate is in PEM format and contains both the public and private keys.\n   * @param options - Options for configuring the client which makes the authentication request.\n   */\n  constructor(\n    tenantId: string,\n    clientId: string,\n    certificatePath: string,\n    options?: ClientCertificateCredentialOptions,\n  );\n  /**\n   * Creates an instance of the ClientCertificateCredential with the details\n   * needed to authenticate against Microsoft Entra ID with a certificate.\n   *\n   * @param tenantId - The Microsoft Entra tenant (directory) ID.\n   * @param clientId - The client (application) ID of an App Registration in the tenant.\n   * @param configuration - Other parameters required, including the path of the certificate on the filesystem.\n   *                        If the type is ignored, we will throw the value of the path to a PEM certificate.\n   * @param options - Options for configuring the client which makes the authentication request.\n   */\n  constructor(\n    tenantId: string,\n    clientId: string,\n    configuration: ClientCertificatePEMCertificatePath,\n    options?: ClientCertificateCredentialOptions,\n  );\n  /**\n   * Creates an instance of the ClientCertificateCredential with the details\n   * needed to authenticate against Microsoft Entra ID with a certificate.\n   *\n   * @param tenantId - The Microsoft Entra tenant (directory) ID.\n   * @param clientId - The client (application) ID of an App Registration in the tenant.\n   * @param configuration - Other parameters required, including the PEM-encoded certificate as a string.\n   *                        If the type is ignored, we will throw the value of the PEM-encoded certificate.\n   * @param options - Options for configuring the client which makes the authentication request.\n   */\n  constructor(\n    tenantId: string,\n    clientId: string,\n    configuration: ClientCertificatePEMCertificate,\n    options?: ClientCertificateCredentialOptions,\n  );\n  constructor(\n    tenantId: string,\n    clientId: string,\n    certificatePathOrConfiguration: string | ClientCertificateCredentialPEMConfiguration,\n    options: ClientCertificateCredentialOptions = {},\n  ) {\n    if (!tenantId || !clientId) {\n      throw new Error(`${credentialName}: tenantId and clientId are required parameters.`);\n    }\n\n    this.tenantId = tenantId;\n    this.additionallyAllowedTenantIds = resolveAdditionallyAllowedTenantIds(\n      options?.additionallyAllowedTenants,\n    );\n\n    this.sendCertificateChain = options.sendCertificateChain;\n\n    this.certificateConfiguration = {\n      ...(typeof certificatePathOrConfiguration === \"string\"\n        ? {\n            certificatePath: certificatePathOrConfiguration,\n          }\n        : certificatePathOrConfiguration),\n    };\n    const certificate = (this.certificateConfiguration as ClientCertificatePEMCertificate)\n      .certificate;\n    const certificatePath = (this.certificateConfiguration as ClientCertificatePEMCertificatePath)\n      .certificatePath;\n    if (!this.certificateConfiguration || !(certificate || certificatePath)) {\n      throw new Error(\n        `${credentialName}: Provide either a PEM certificate in string form, or the path to that certificate in the filesystem. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`,\n      );\n    }\n    if (certificate && certificatePath) {\n      throw new Error(\n        `${credentialName}: To avoid unexpected behaviors, providing both the contents of a PEM certificate and the path to a PEM certificate is forbidden. To troubleshoot, visit https://aka.ms/azsdk/js/identity/serviceprincipalauthentication/troubleshoot.`,\n      );\n    }\n    this.msalClient = createMsalClient(clientId, tenantId, {\n      ...options,\n      logger,\n      tokenCredentialOptions: options,\n    });\n  }\n\n  /**\n   * Authenticates with Microsoft Entra ID and returns an access token if successful.\n   * If authentication fails, a {@link CredentialUnavailableError} will be thrown with the details of the failure.\n   *\n   * @param scopes - The list of scopes for which the token will have access.\n   * @param options - The options used to configure any requests this\n   *                TokenCredential implementation might make.\n   */\n  async getToken(scopes: string | string[], options: GetTokenOptions = {}): Promise<AccessToken> {\n    return tracingClient.withSpan(`${credentialName}.getToken`, options, async (newOptions) => {\n      newOptions.tenantId = processMultiTenantRequest(\n        this.tenantId,\n        newOptions,\n        this.additionallyAllowedTenantIds,\n        logger,\n      );\n\n      const arrayScopes = Array.isArray(scopes) ? scopes : [scopes];\n      const certificate = await this.buildClientCertificate();\n      return this.msalClient.getTokenByClientCertificate(arrayScopes, certificate, newOptions);\n    });\n  }\n\n  private async buildClientCertificate(): Promise<CertificateParts> {\n    const parts = await parseCertificate(\n      this.certificateConfiguration,\n      this.sendCertificateChain ?? false,\n    );\n\n    let privateKey: string;\n    if (this.certificateConfiguration.certificatePassword !== undefined) {\n      privateKey = createPrivateKey({\n        key: parts.certificateContents,\n        passphrase: this.certificateConfiguration.certificatePassword,\n        format: \"pem\",\n      })\n        .export({\n          format: \"pem\",\n          type: \"pkcs8\",\n        })\n        .toString();\n    } else {\n      privateKey = parts.certificateContents;\n    }\n\n    return {\n      thumbprint: parts.thumbprint,\n      thumbprintSha256: parts.thumbprintSha256,\n      privateKey,\n      x5c: parts.x5c,\n    };\n  }\n}\n\n/**\n * Parses a certificate into its relevant parts\n *\n * @param certificateConfiguration - The certificate contents or path to the certificate\n * @param sendCertificateChain - true if the entire certificate chain should be sent for SNI, false otherwise\n * @returns The parsed certificate parts and the certificate contents\n */\nexport async function parseCertificate(\n  certificateConfiguration: ClientCertificateCredentialPEMConfiguration,\n  sendCertificateChain: boolean,\n): Promise<Omit<CertificateParts, \"privateKey\"> & { certificateContents: string }> {\n  const certificate = (certificateConfiguration as ClientCertificatePEMCertificate).certificate;\n  const certificatePath = (certificateConfiguration as ClientCertificatePEMCertificatePath)\n    .certificatePath;\n  const certificateContents = certificate || (await readFile(certificatePath!, \"utf8\"));\n  const x5c = sendCertificateChain ? certificateContents : undefined;\n\n  const certificatePattern =\n    /(-+BEGIN CERTIFICATE-+)(\\n\\r?|\\r\\n?)([A-Za-z0-9+/\\n\\r]+=*)(\\n\\r?|\\r\\n?)(-+END CERTIFICATE-+)/g;\n  const publicKeys: string[] = [];\n\n  // Match all possible certificates, in the order they are in the file. These will form the chain that is used for x5c\n  let match;\n  do {\n    match = certificatePattern.exec(certificateContents);\n    if (match) {\n      publicKeys.push(match[3]);\n    }\n  } while (match);\n\n  if (publicKeys.length === 0) {\n    throw new Error(\"The file at the specified path does not contain a PEM-encoded certificate.\");\n  }\n\n  const thumbprint = createHash(\"sha1\") // CodeQL [SM04514] Needed for backward compatibility reason\n    .update(Buffer.from(publicKeys[0], \"base64\"))\n    .digest(\"hex\")\n    .toUpperCase();\n\n  const thumbprintSha256 = createHash(\"sha256\")\n    .update(Buffer.from(publicKeys[0], \"base64\"))\n    .digest(\"hex\")\n    .toUpperCase();\n\n  return {\n    certificateContents,\n    thumbprintSha256,\n    thumbprint,\n    x5c,\n  };\n}\n"]}