diff --git a/backend/src/services/certificate-common/certificate-utils.ts b/backend/src/services/certificate-common/certificate-utils.ts index b88f183db..51754955f 100644 --- a/backend/src/services/certificate-common/certificate-utils.ts +++ b/backend/src/services/certificate-common/certificate-utils.ts @@ -196,3 +196,62 @@ export const convertExtendedKeyUsageArrayToLegacy = ( ): CertExtendedKeyUsage[] | undefined => { return usages?.map(convertToLegacyExtendedKeyUsage); }; + +/** + * Parses a PEM-formatted certificate chain and returns individual certificates + * @param certificateChain - PEM-formatted certificate chain + * @returns Array of individual PEM certificates + */ +const parseCertificateChain = (certificateChain: string): string[] => { + if (!certificateChain || typeof certificateChain !== "string") { + return []; + } + + const certRegex = new RE2(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g); + const certificates = certificateChain.match(certRegex); + + return certificates ? certificates.map((cert) => cert.trim()) : []; +}; + +/** + * Removes the root CA certificate from a certificate chain, leaving only intermediate certificates. + * If the chain contains only the root CA certificate, returns an empty string. + * + * @param certificateChain - PEM-formatted certificate chain containing leaf + intermediates + root CA + * @returns PEM-formatted certificate chain with only intermediate certificates (no root CA) + */ +export const removeRootCaFromChain = (certificateChain?: string): string => { + if (!certificateChain || typeof certificateChain !== "string") { + return ""; + } + + const certificates = parseCertificateChain(certificateChain); + + if (certificates.length === 0) { + return ""; + } + + const intermediateCerts = certificates.slice(0, -1); + + return intermediateCerts.join("\n"); +}; + +/** + * Extracts the root CA certificate from a certificate chain. + * + * @param certificateChain - PEM-formatted certificate chain containing leaf + intermediates + root CA + * @returns PEM-formatted root CA certificate, or empty string if not found + */ +export const extractRootCaFromChain = (certificateChain?: string): string => { + if (!certificateChain || typeof certificateChain !== "string") { + return ""; + } + + const certificates = parseCertificateChain(certificateChain); + + if (certificates.length === 0) { + return ""; + } + + return certificates[certificates.length - 1]; +}; diff --git a/backend/src/services/pki-sync/aws-secrets-manager/aws-secrets-manager-pki-sync-fns.ts b/backend/src/services/pki-sync/aws-secrets-manager/aws-secrets-manager-pki-sync-fns.ts index 11e52ec7e..34a0b6e63 100644 --- a/backend/src/services/pki-sync/aws-secrets-manager/aws-secrets-manager-pki-sync-fns.ts +++ b/backend/src/services/pki-sync/aws-secrets-manager/aws-secrets-manager-pki-sync-fns.ts @@ -17,6 +17,7 @@ import { AWSRegion } from "@app/services/app-connection/app-connection-enums"; import { getAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-fns"; import { TAwsConnectionConfig } from "@app/services/app-connection/aws/aws-connection-types"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { removeRootCaFromChain } from "@app/services/certificate-common/certificate-utils"; import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue"; @@ -262,7 +263,10 @@ export const awsSecretsManagerPkiSyncFactory = ({ }; if (certificateChain && certificateChain.trim().length > 0) { - certificateData[fieldMappings.certificateChain] = certificateChain; + const processedCertificateChain = removeRootCaFromChain(certificateChain); + if (processedCertificateChain.trim().length > 0) { + certificateData[fieldMappings.certificateChain] = processedCertificateChain; + } } if (caCertificate && typeof caCertificate === "string" && caCertificate.trim().length > 0) { diff --git a/backend/src/services/pki-sync/chef/chef-pki-sync-fns.ts b/backend/src/services/pki-sync/chef/chef-pki-sync-fns.ts index bf740c660..797bdc85b 100644 --- a/backend/src/services/pki-sync/chef/chef-pki-sync-fns.ts +++ b/backend/src/services/pki-sync/chef/chef-pki-sync-fns.ts @@ -10,6 +10,7 @@ import { import { TChefDataBagItemContent } from "@app/ee/services/secret-sync/chef"; import { logger } from "@app/lib/logger"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { removeRootCaFromChain } from "@app/services/certificate-common/certificate-utils"; import { TCertificateSyncDALFactory } from "@app/services/certificate-sync/certificate-sync-dal"; import { CertificateSyncStatus } from "@app/services/certificate-sync/certificate-sync-enums"; import { createConnectionQueue, RateLimitConfig } from "@app/services/connection-queue"; @@ -264,11 +265,13 @@ export const chefPkiSyncFactory = ({ certificateDAL, certificateSyncDAL }: TChef } = certificateData; try { + const processedCertificateChain = certificateChain ? removeRootCaFromChain(certificateChain) : undefined; + const chefDataBagItem: ChefCertificateDataBagItem = { id: targetItemName, [fieldMappings.certificate]: cert, [fieldMappings.privateKey]: certPrivateKey, - ...(certificateChain && { [fieldMappings.certificateChain]: certificateChain }), + ...(processedCertificateChain && { [fieldMappings.certificateChain]: processedCertificateChain }), ...(caCertificate && { [fieldMappings.caCertificate]: caCertificate }) }; diff --git a/backend/src/services/pki-sync/pki-sync-queue.ts b/backend/src/services/pki-sync/pki-sync-queue.ts index 4f713a33f..04c5c4734 100644 --- a/backend/src/services/pki-sync/pki-sync-queue.ts +++ b/backend/src/services/pki-sync/pki-sync-queue.ts @@ -26,6 +26,7 @@ import { TCertificateSecretDALFactory } from "../certificate/certificate-secret- import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import { getCaCertChain } from "../certificate-authority/certificate-authority-fns"; +import { extractRootCaFromChain } from "../certificate-common/certificate-utils"; import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal"; import { CertificateSyncStatus } from "../certificate-sync/certificate-sync-enums"; import { TPkiSyncDALFactory } from "./pki-sync-dal"; @@ -255,7 +256,7 @@ export const pkiSyncQueueFactory = ({ if (!certBody.encryptedCertificateChain) { certificateChain = `${caCert}\n${caCertChain}`.trim(); } - caCertificate = caCert; + caCertificate = certificateChain ? extractRootCaFromChain(certificateChain) : caCert; } } catch (chainError) { logger.warn(