/* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import { ForbiddenError } from "@casl/ability"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal"; import { ACMESANType, CertificateOrderStatus, CertStatus } from "@app/services/certificate/certificate-types"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums"; import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service"; import { CertExtendedKeyUsageType, CertIncludeType, CertKeyUsageType, CertSubjectAttributeType } from "@app/services/certificate-common/certificate-constants"; import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types"; import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service"; import { ActorType, AuthMethod } from "../auth/auth-type"; import { extractAlgorithmsFromCSR, extractCertificateRequestFromCSR } from "../certificate-common/certificate-csr-utils"; import { certificateV3ServiceFactory, TCertificateV3ServiceFactory } from "./certificate-v3-service"; vi.mock("../certificate-common/certificate-csr-utils", () => ({ extractCertificateRequestFromCSR: vi.fn(), extractAlgorithmsFromCSR: vi.fn() })); describe("CertificateV3Service", () => { let service: TCertificateV3ServiceFactory; const mockCertificateDAL: Pick = { findOne: vi.fn(), findById: vi.fn(), updateById: vi.fn(), transaction: vi.fn().mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }) }; const mockCertificateSecretDAL: Pick = { findOne: vi.fn() }; const mockCertificateAuthorityDAL: Pick = { findByIdWithAssociatedCa: vi.fn() }; const mockCertificateProfileDAL: Pick = { findByIdWithConfigs: vi.fn() }; const mockCertificateTemplateV2Service: Pick< TCertificateTemplateV2ServiceFactory, "validateCertificateRequest" | "getTemplateV2ById" > = { validateCertificateRequest: vi.fn(), getTemplateV2ById: vi.fn() }; const mockInternalCaService: Pick = { signCertFromCa: vi.fn(), issueCertFromCa: vi.fn() }; const mockPermissionService: Pick = { getProjectPermission: vi.fn().mockResolvedValue({ permission: { throwUnlessCan: vi.fn(), can: vi.fn().mockReturnValue(true), cannot: vi.fn().mockReturnValue(false), relevantRuleFor: vi.fn(), rules: [] } }) }; const mockActor = { actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123" }; beforeEach(() => { // Reset all mocks before each test vi.resetAllMocks(); // Mock ForbiddenError.from static method vi.spyOn(ForbiddenError, "from").mockReturnValue({ throwUnlessCan: vi.fn() } as any); // Ensure the permission service mock is properly set up (mockPermissionService.getProjectPermission as any).mockResolvedValue({ permission: { throwUnlessCan: vi.fn(), can: vi.fn().mockReturnValue(true), cannot: vi.fn().mockReturnValue(false), relevantRuleFor: vi.fn(), rules: [], detectSubjectType: vi.fn() } }); vi.mocked(extractCertificateRequestFromCSR).mockReturnValue({ commonName: "test.example.com", keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], extendedKeyUsages: [CertExtendedKeyUsageType.SERVER_AUTH] }); vi.mocked(extractAlgorithmsFromCSR).mockReturnValue({ keyAlgorithm: "RSA_2048" as any, signatureAlgorithm: "RSA-SHA256" as any }); service = certificateV3ServiceFactory({ certificateDAL: mockCertificateDAL, certificateSecretDAL: mockCertificateSecretDAL, certificateAuthorityDAL: mockCertificateAuthorityDAL, certificateProfileDAL: mockCertificateProfileDAL, certificateTemplateV2Service: mockCertificateTemplateV2Service, internalCaService: mockInternalCaService, permissionService: mockPermissionService, certificateSyncDAL: { findPkiSyncIdsByCertificateId: vi.fn().mockResolvedValue([]), addCertificates: vi.fn().mockResolvedValue([]), findByPkiSyncAndCertificate: vi.fn().mockResolvedValue(null) }, pkiSyncDAL: { find: vi.fn().mockResolvedValue([]) }, pkiSyncQueue: { queuePkiSyncSyncCertificatesById: vi.fn().mockResolvedValue(undefined) } }); }); afterEach(() => { vi.clearAllMocks(); vi.restoreAllMocks(); // Ensure static method mocks are properly restored }); describe("issueCertificateFromProfile", () => { const mockCertificateRequest = { commonName: "test.example.com", keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], extendedKeyUsages: [CertExtendedKeyUsageType.SERVER_AUTH], validity: { ttl: "30d" }, signatureAlgorithm: "RSA-SHA256", keyAlgorithm: "RSA_2048" }; it("should issue certificate successfully for API enrollment profile", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.API, caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile", description: "Test profile" }; const mockCA = { id: "ca-123", projectId: "project-123", externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-123" }, name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true }; const mockTemplate = { id: "template-123", name: "Test Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", description: "Test template", signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" }, keyAlgorithm: { defaultKeyType: "RSA_2048" }, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ] }; const mockCertificateResult = { certificate: "cert", certificateChain: "chain", issuingCaCertificate: "issuing-ca", privateKey: "key", serialNumber: "123456", ca: { id: "ca-123", projectId: "project-123", name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: null, notAfter: null, activeCaCertId: "cert-123", caId: "ca-123" } } }; const mockCertRecord = { id: "cert-123", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", commonName: "test.example.com", friendlyName: "Test Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-123", certificateTemplateId: "template-123", revokedAt: null, revokedBy: null }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResult as any); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord); const result = await service.issueCertificateFromProfile({ profileId, certificateRequest: mockCertificateRequest, ...mockActor }); expect(result).toHaveProperty("certificate"); expect(result).toHaveProperty("issuingCaCertificate"); expect(result).toHaveProperty("certificateChain"); expect(result).toHaveProperty("privateKey"); expect(result).toHaveProperty("serialNumber", "123456"); expect(result).toHaveProperty("certificateId", "cert-123"); }); it("should correctly map camelCase key usages to snake_case before validation", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.API, caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile-camel", description: "Test camelCase profile", estConfigId: null, apiConfigId: null }; const mockCA = { id: "ca-123", projectId: "project-123", externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-123" }, name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true }; const mockTemplate = { id: "template-123", name: "Test Template for CamelCase", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", description: "Test template for camelCase validation", signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" }, keyAlgorithm: { defaultKeyType: "RSA_2048" }, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; const mockCertRecord = { id: "cert-123", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", commonName: "test.example.com", friendlyName: "Test Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-123", certificateTemplateId: "template-123", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }; const camelCaseRequest = { commonName: "test.example.com", keyUsages: [ CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION, CertKeyUsageType.KEY_AGREEMENT, CertKeyUsageType.CRL_SIGN, CertKeyUsageType.DECIPHER_ONLY ], extendedKeyUsages: [ CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.CODE_SIGNING, CertExtendedKeyUsageType.OCSP_SIGNING, CertExtendedKeyUsageType.SERVER_AUTH ], validity: { ttl: "10d" } }; const mockCertificateResultWithCa = { certificate: "cert", certificateChain: "chain", issuingCaCertificate: "issuing-ca", privateKey: "key", serialNumber: "123456", ca: { id: "ca-123", projectId: "project-123", name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: null, notAfter: null, activeCaCertId: "cert-123", caId: "ca-123" } } }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResultWithCa as any); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord); await service.issueCertificateFromProfile({ profileId, certificateRequest: camelCaseRequest, ...mockActor }); // Verify that the template validation service was called with mapped snake_case values expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith( "template-123", expect.objectContaining({ keyUsages: [ CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION, CertKeyUsageType.KEY_AGREEMENT, CertKeyUsageType.CRL_SIGN, CertKeyUsageType.DECIPHER_ONLY ], extendedKeyUsages: [ CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.CODE_SIGNING, CertExtendedKeyUsageType.OCSP_SIGNING, CertExtendedKeyUsageType.SERVER_AUTH ] }) ); }); it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.EST, // Wrong enrollment type caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile-est", description: "Test EST profile", estConfigId: null, apiConfigId: null }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); await expect( service.issueCertificateFromProfile({ profileId, certificateRequest: mockCertificateRequest, ...mockActor }) ).rejects.toThrow(ForbiddenRequestError); await expect( service.issueCertificateFromProfile({ profileId, certificateRequest: mockCertificateRequest, ...mockActor }) ).rejects.toThrow("Profile is not configured for api enrollment"); }); it("should throw NotFoundError when profile doesn't exist", async () => { const profileId = "non-existent-profile"; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(undefined); await expect( service.issueCertificateFromProfile({ profileId, certificateRequest: mockCertificateRequest, ...mockActor }) ).rejects.toThrow(NotFoundError); }); }); describe("signCertificateFromProfile", () => { const mockCSR = "-----BEGIN CERTIFICATE REQUEST-----\nMIIC..."; const mockValidity = { ttl: "30d" }; it("should sign certificate successfully for API enrollment profile", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.API, caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile-sign", description: "Test signing profile", estConfigId: null, apiConfigId: null }; const mockCA = { id: "ca-123", projectId: "project-123", externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-123" }, name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true }; const mockSignResult = { certificate: "signed-cert", certificateChain: "chain", issuingCaCertificate: "issuing-ca", serialNumber: "789012", commonName: "test.example.com", ca: { id: "ca-123", projectId: "project-123", name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: null, notAfter: null, activeCaCertId: "cert-123", caId: "ca-123" } } }; const mockCertRecord = { id: "cert-456", serialNumber: "789012", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", commonName: "test.example.com", friendlyName: "Test Signing Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-123", certificateTemplateId: "template-123", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }; const mockTemplate = { id: "template-123", name: "Test Signing Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", description: "Test template for signing certificates", signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" }, keyAlgorithm: { defaultKeyType: "RSA_2048" }, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockInternalCaService.signCertFromCa).mockResolvedValue(mockSignResult as any); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord); const result = await service.signCertificateFromProfile({ profileId, csr: mockCSR, validity: mockValidity, ...mockActor }); expect(result).toHaveProperty("certificate"); expect(result).toHaveProperty("issuingCaCertificate"); expect(result).toHaveProperty("certificateChain"); expect(result).toHaveProperty("serialNumber", "789012"); expect(result).toHaveProperty("certificateId", "cert-456"); expect(result).not.toHaveProperty("privateKey"); }); it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.EST, // Wrong enrollment type caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile-est-sign", description: "Test EST signing profile", estConfigId: null, apiConfigId: null }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); await expect( service.signCertificateFromProfile({ profileId, csr: mockCSR, validity: mockValidity, ...mockActor }) ).rejects.toThrow(ForbiddenRequestError); await expect( service.signCertificateFromProfile({ profileId, csr: mockCSR, validity: mockValidity, ...mockActor }) ).rejects.toThrow("Profile is not configured for api enrollment"); }); }); describe("orderCertificateFromProfile", () => { const mockCertificateOrder = { altNames: [{ type: ACMESANType.DNS, value: "example.com" }], validity: { ttl: "30d" }, commonName: "example.com", keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], extendedKeyUsages: [CertExtendedKeyUsageType.SERVER_AUTH], signatureAlgorithm: "RSA-SHA256", keyAlgorithm: "RSA_2048" }; it("should create order successfully for API enrollment profile", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.API, caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile-order", description: "Test order profile", estConfigId: null, apiConfigId: null }; const mockCA = { id: "ca-123", projectId: "project-123", externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-123" }, name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true }; const mockTemplate = { id: "template-123", name: "Test Order Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", description: "Test template for ordering certificates", signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" }, keyAlgorithm: { defaultKeyType: "RSA_2048" }, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; const mockCertificateResult = { certificate: "cert", certificateChain: "chain", issuingCaCertificate: "issuing-ca", privateKey: "key", serialNumber: "123456", ca: { id: "ca-123", projectId: "project-123", name: "Test CA", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", dn: "CN=Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: null, notAfter: null, activeCaCertId: "cert-123", caId: "ca-123" } } }; const mockCertRecord = { id: "cert-123", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-123", commonName: "example.com", friendlyName: "Test Order Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-123", certificateTemplateId: "template-123", revokedAt: null, altNames: JSON.stringify([{ type: "DNS", value: "example.com" }]), caCertId: null, keyUsages: ["DIGITAL_SIGNATURE"], extendedKeyUsages: ["SERVER_AUTH"], revocationReason: null, pkiSubscriberId: null, profileId: null }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResult as any); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord); const result = await service.orderCertificateFromProfile({ profileId, certificateOrder: mockCertificateOrder, ...mockActor }); expect(result).toHaveProperty("orderId"); expect(result).toHaveProperty("status", "valid"); expect(result).toHaveProperty("certificate"); expect(result.subjectAlternativeNames).toHaveLength(1); expect(result.subjectAlternativeNames[0]).toEqual({ type: ACMESANType.DNS, value: "example.com", status: CertificateOrderStatus.VALID }); }); it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => { const profileId = "profile-123"; const mockProfile = { id: profileId, projectId: "project-123", enrollmentType: EnrollmentType.EST, // Wrong enrollment type caId: "ca-123", certificateTemplateId: "template-123", createdAt: new Date(), updatedAt: new Date(), slug: "test-profile-est-order", description: "Test EST order profile", estConfigId: null, apiConfigId: null }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); await expect( service.orderCertificateFromProfile({ profileId, certificateOrder: mockCertificateOrder, ...mockActor }) ).rejects.toThrow(ForbiddenRequestError); await expect( service.orderCertificateFromProfile({ profileId, certificateOrder: mockCertificateOrder, ...mockActor }) ).rejects.toThrow("Profile is not configured for api enrollment"); }); }); describe("algorithm compatibility (integration tests)", () => { const mockProfile = { id: "profile-1", slug: "test-profile", projectId: "project-1", caId: "ca-1", certificateTemplateId: "template-1", enrollmentType: EnrollmentType.API, createdAt: new Date(), updatedAt: new Date(), description: "Test profile for algorithm compatibility", estConfigId: null, apiConfigId: null }; const mockCertificateRequest = { commonName: "test.example.com", validity: { ttl: "30d" }, keyUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], extendedKeyUsages: [CertExtendedKeyUsageType.SERVER_AUTH] }; beforeEach(() => { vi.clearAllMocks(); }); it("should successfully process RSA algorithms with RSA CAs", async () => { const rsaCa = { id: "ca-1", projectId: "project-1", status: "active", name: "RSA Test CA", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-1", parentCaId: null, type: "ROOT", friendlyName: "RSA Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "RSA Test CA", dn: "CN=RSA Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_2048", notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-1" } }; const rsaTemplate = { id: "template-1", name: "RSA Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", description: "RSA template for algorithm compatibility", signatureAlgorithm: { allowedAlgorithms: ["SHA256-RSA", "SHA384-RSA"] }, keyAlgorithm: null, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(rsaCa); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(rsaTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ certificate: "cert", certificateChain: "chain", issuingCaCertificate: "ca-cert", privateKey: "key", serialNumber: "123456", ca: rsaCa as any }); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); // Should not throw - RSA CA is compatible with RSA signature algorithms await expect( service.issueCertificateFromProfile({ profileId: mockProfile.id, certificateRequest: { ...mockCertificateRequest, signatureAlgorithm: "RSA-SHA256" }, ...mockActor }) ).resolves.toBeDefined(); }); it("should successfully process ECDSA algorithms with EC CAs", async () => { const ecCa = { id: "ca-1", projectId: "project-1", status: "active", name: "EC Test CA", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-1", parentCaId: null, type: "ROOT", friendlyName: "EC Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "EC Test CA", dn: "CN=EC Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "EC_prime256v1", notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-1" } }; const ecdsaTemplate = { id: "template-1", name: "ECDSA Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", description: "ECDSA template for algorithm compatibility", signatureAlgorithm: { allowedAlgorithms: ["SHA256-ECDSA", "SHA384-ECDSA"] }, keyAlgorithm: null, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(ecCa); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(ecdsaTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ certificate: "cert", certificateChain: "chain", issuingCaCertificate: "ca-cert", privateKey: "key", serialNumber: "123456", ca: ecCa as any }); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); // Should not throw - EC CA is compatible with ECDSA signature algorithms await expect( service.issueCertificateFromProfile({ profileId: mockProfile.id, certificateRequest: { ...mockCertificateRequest, signatureAlgorithm: "ECDSA-SHA256" }, ...mockActor }) ).resolves.toBeDefined(); }); it("should dynamically support new RSA key sizes", async () => { const rsa8192Ca = { id: "ca-1", projectId: "project-1", status: "active", name: "RSA 8192 Test CA", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-1", parentCaId: null, type: "ROOT", friendlyName: "RSA 8192 Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "RSA 8192 Test CA", dn: "CN=RSA 8192 Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "RSA_8192", // Future RSA key size notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-1" } }; const rsaTemplate = { id: "template-1", name: "RSA 8192 Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", description: "RSA 8192 template for future key sizes", signatureAlgorithm: { allowedAlgorithms: ["SHA256-RSA"] }, keyAlgorithm: null, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(rsa8192Ca); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(rsaTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ certificate: "cert", certificateChain: "chain", issuingCaCertificate: "ca-cert", privateKey: "key", serialNumber: "123456", ca: rsa8192Ca as any }); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); // Should not throw - dynamic check supports new RSA key sizes await expect( service.issueCertificateFromProfile({ profileId: mockProfile.id, certificateRequest: { ...mockCertificateRequest, signatureAlgorithm: "RSA-SHA256" }, ...mockActor }) ).resolves.toBeDefined(); }); it("should dynamically support new EC curve types", async () => { const newEcCa = { id: "ca-1", projectId: "project-1", status: "active", name: "EC secp521r1 Test CA", createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, externalCa: undefined, internalCa: { id: "internal-ca-1", parentCaId: null, type: "ROOT", friendlyName: "EC secp521r1 Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "EC secp521r1 Test CA", dn: "CN=EC secp521r1 Test CA", serialNumber: "123", maxPathLength: null, keyAlgorithm: "EC_secp521r1", // Future EC curve notBefore: undefined, notAfter: undefined, activeCaCertId: "cert-123", caId: "ca-1" } }; const ecdsaTemplate = { id: "template-1", name: "ECDSA secp521r1 Template", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", description: "ECDSA secp521r1 template for future EC curves", signatureAlgorithm: { allowedAlgorithms: ["SHA384-ECDSA"] }, keyAlgorithm: null, attributes: [ { type: CertSubjectAttributeType.COMMON_NAME, include: CertIncludeType.OPTIONAL, value: ["example.com"] } ], subject: undefined, sans: undefined, keyUsages: undefined, extendedKeyUsages: undefined, algorithms: undefined, validity: undefined }; vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(newEcCa); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(ecdsaTemplate); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ certificate: "cert", certificateChain: "chain", issuingCaCertificate: "ca-cert", privateKey: "key", serialNumber: "123456", ca: newEcCa as any }); vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue({ id: "cert-1", serialNumber: "123456", status: "ACTIVE", createdAt: new Date(), updatedAt: new Date(), projectId: "project-1", commonName: "test.example.com", friendlyName: "Test Algorithm Cert", notBefore: new Date(), notAfter: new Date(), caId: "ca-1", certificateTemplateId: "template-1", revokedAt: null, altNames: null, caCertId: null, keyUsages: null, extendedKeyUsages: null, revocationReason: null, pkiSubscriberId: null, profileId: null }); // Should not throw - dynamic check supports new EC curves await expect( service.issueCertificateFromProfile({ profileId: mockProfile.id, certificateRequest: { ...mockCertificateRequest, signatureAlgorithm: "ECDSA-SHA384" }, ...mockActor }) ).resolves.toBeDefined(); }); }); describe("renewCertificate", () => { const mockOriginalCert = { id: "cert-123", status: CertStatus.ACTIVE, serialNumber: "123456", friendlyName: "Test Certificate", commonName: "test.example.com", notBefore: new Date("2024-01-01"), notAfter: new Date("2024-02-01"), // 31 days revokedAt: null, renewedByCertificateId: null, profileId: "profile-123", renewBeforeDays: 7, caId: "ca-123", pkiSubscriberId: null, keyUsages: ["digital_signature", "key_agreement"], extendedKeyUsages: ["server_auth"], altNames: "test.example.com,api.example.com", projectId: "project-123", createdAt: new Date(), updatedAt: new Date(), certificateTemplateId: "template-123", revocationReason: null, caCertId: null, renewedFromCertificateId: null, renewalError: null, keyAlgorithm: "RSA_2048", signatureAlgorithm: "RSA-SHA256" }; const mockProfile = { id: "profile-123", projectId: "project-123", enrollmentType: EnrollmentType.API, caId: "ca-123", certificateTemplateId: "template-123", apiConfig: { id: "api-config-123", autoRenew: true, renewBeforeDays: 14 }, createdAt: new Date(), updatedAt: new Date(), slug: "test-profile", description: "Test profile" }; const mockCA = { id: "ca-123", projectId: "project-123", status: CaStatus.ACTIVE, createdAt: new Date(), updatedAt: new Date(), enableDirectIssuance: true, name: "Test CA", requireTemplateForIssuance: false, externalCa: undefined, parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", keyAlgorithm: "RSA_2048", notAfter: "2025-01-01T00:00:00Z", notBefore: "2024-01-01T00:00:00Z", maxPathLength: -1, activeCaCertId: "cert-123", dn: "CN=Test CA,O=Test Org,OU=Test OU,C=US", serialNumber: "123456789", internalCa: { id: "internal-ca-123", parentCaId: null, type: "ROOT", friendlyName: "Test CA", organization: "Test Org", ou: "Test OU", country: "US", province: "CA", locality: "SF", commonName: "Test CA", keyAlgorithm: "RSA_2048", notAfter: "2025-01-01T00:00:00Z", notBefore: "2024-01-01T00:00:00Z", maxPathLength: -1, activeCaCertId: "cert-123", dn: "CN=Test CA,O=Test Org,OU=Test OU,C=US", serialNumber: "123456789" } }; const mockTemplate = { id: "template-123", projectId: "project-123", name: "Test Template", createdAt: new Date(), updatedAt: new Date(), algorithms: { signature: ["SHA256-RSA", "SHA384-RSA"], keyType: ["RSA_2048", "RSA_4096"] } }; beforeEach(() => { // Mock current date to be within renewal window vi.useFakeTimers(); vi.setSystemTime(new Date("2024-01-26")); // 6 days before cert expires, within renewal window }); afterEach(() => { vi.useRealTimers(); }); it("should successfully renew eligible certificate", async () => { // Mock the initial findById call vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ certificate: "renewed-cert", certificateChain: "renewed-chain", issuingCaCertificate: "issuing-ca", privateKey: "private-key", serialNumber: "789012", ca: mockCA }); const newCert = { ...mockOriginalCert, id: "cert-456", serialNumber: "789012" }; vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(newCert); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(newCert); // Mock the transaction to return the expected structure vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; const result = await callback(mockTx); return result; }); const result = await service.renewCertificate({ certificateId: "cert-123", ...mockActor }); expect(result).toHaveProperty("certificate", "renewed-cert"); expect(result).toHaveProperty("certificateId", "cert-456"); expect(mockCertificateDAL.updateById).toHaveBeenCalledWith( "cert-456", { profileId: "profile-123", renewBeforeDays: 14, renewedFromCertificateId: "cert-123" }, {} ); expect(mockCertificateDAL.updateById).toHaveBeenCalledWith( "cert-123", { renewedByCertificateId: "cert-456", renewalError: null }, {} ); }); it("should validate certificate against current template during renewal", async () => { vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: false, errors: ["Subject alternative name not allowed"], warnings: [] }); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); // Mock updateById to handle the renewal error logging vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockOriginalCert); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(BadRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("Certificate renewal failed. Errors: Subject alternative name not allowed"); // Should store template validation error expect(mockCertificateDAL.updateById).toHaveBeenCalledWith("cert-123", { renewalError: "Template validation failed: Subject alternative name not allowed" }); }); it("should reject renewal if certificate is not from a profile", async () => { const certWithoutProfile = { ...mockOriginalCert, profileId: null }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(certWithoutProfile); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(ForbiddenRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("Only certificates issued from a profile can be renewed"); }); it("should reject renewal if certificate was issued from CSR (external private key)", async () => { vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue(null as any); vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(ForbiddenRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("certificates issued from CSR (external private key) cannot be renewed"); }); it("should reject renewal if certificate is already renewed", async () => { const alreadyRenewedCert = { ...mockOriginalCert, renewedByCertificateId: "cert-456" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(alreadyRenewedCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); // Mock updateById to handle the renewal error logging vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(alreadyRenewedCert); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(BadRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("Certificate has already been renewed"); }); it("should reject renewal if certificate is expired", async () => { const expiredCert = { ...mockOriginalCert, notAfter: new Date("2024-01-20") // Expired 6 days ago }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(expiredCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); // Mock updateById to handle the renewal error logging vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(expiredCert); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(BadRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("Certificate is already expired"); }); it("should reject renewal if certificate is revoked", async () => { const revokedCert = { ...mockOriginalCert, revokedAt: new Date("2024-01-15") }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(revokedCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); // Mock updateById to handle the renewal error logging vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(revokedCert); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(BadRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("Certificate is revoked and cannot be renewed"); }); it("should reject renewal if CA is inactive", async () => { const inactiveCA = { ...mockCA, status: CaStatus.DISABLED }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(inactiveCA); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); // Mock updateById to handle the renewal error logging vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockOriginalCert); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(BadRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow("Certificate is not eligible for renewal: Certificate Authority is disabled, must be active"); }); it("should reject renewal if new certificate would outlive CA", async () => { const shortLivedCA = { ...mockCA, internalCa: { ...mockCA.internalCa, notAfter: "2024-01-28T00:00:00Z" } }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(shortLivedCA); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); // Mock updateById to handle the renewal error logging vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockOriginalCert); // Set up transaction mock to properly handle errors vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(BadRequestError); await expect( service.renewCertificate({ certificateId: "cert-123", ...mockActor }) ).rejects.toThrow(/New certificate would expire \(.+\) after its issuing CA \(.+\)/); }); it("should allow manual renewal outside window (manual renewal always bypasses window)", async () => { vi.setSystemTime(new Date("2024-01-15")); // 17 days before expiry, outside 7-day window vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile); vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate); vi.mocked(mockCertificateTemplateV2Service.validateCertificateRequest).mockResolvedValue({ isValid: true, errors: [], warnings: [] }); vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue({ certificate: "renewed-cert", certificateChain: "renewed-chain", issuingCaCertificate: "issuing-ca", privateKey: "private-key", serialNumber: "789012", ca: mockCA }); const newCert = { ...mockOriginalCert, id: "cert-456", serialNumber: "789012" }; vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(newCert); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(newCert); // Set up transaction mock to properly handle the renewal process vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise) => { const mockTx = {}; return callback(mockTx); }); const result = await service.renewCertificate({ certificateId: "cert-123", ...mockActor }); expect(result).toHaveProperty("certificate", "renewed-cert"); }); }); describe("updateRenewalConfig", () => { it("should update renewal configuration successfully", async () => { const mockCert = { id: "cert-123", profileId: "profile-123", renewedByCertificateId: null, notBefore: new Date("2026-01-01"), notAfter: new Date("2026-02-01"), projectId: "project-123", status: CertStatus.ACTIVE, revokedAt: null, commonName: "" }; const mockProfile = { id: "profile-123", enrollmentType: EnrollmentType.API, projectId: "project-123" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCert as any); const result = await service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 7 }); expect(result).toEqual({ projectId: "project-123", renewBeforeDays: 7, commonName: "" }); expect(mockCertificateDAL.updateById).toHaveBeenCalledWith("cert-123", { renewBeforeDays: 7 }); }); it("should reject update if certificate is not from profile", async () => { const mockCert = { id: "cert-123", profileId: null, renewedByCertificateId: null, projectId: "project-123" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); await expect( service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 7 }) ).rejects.toThrow(BadRequestError); await expect( service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 7 }) ).rejects.toThrow("Certificate is not eligible for auto-renewal: certificate was not issued from a profile"); }); it("should reject update if certificate is already renewed", async () => { const mockCert = { id: "cert-123", profileId: "profile-123", renewedByCertificateId: "cert-456", projectId: "project-123", status: CertStatus.ACTIVE, revokedAt: null, notBefore: new Date("2026-01-01"), notAfter: new Date("2026-02-01") }; const mockProfile = { id: "profile-123", enrollmentType: EnrollmentType.API, projectId: "project-123" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); await expect( service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 7 }) ).rejects.toThrow(BadRequestError); await expect( service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 7 }) ).rejects.toThrow("Certificate is not eligible for auto-renewal: certificate has already been renewed"); }); it("should reject update if renewBeforeDays >= certificate TTL", async () => { const mockCert = { id: "cert-123", profileId: "profile-123", renewedByCertificateId: null, notBefore: new Date("2026-01-01"), notAfter: new Date("2026-01-08"), projectId: "project-123", status: CertStatus.ACTIVE, revokedAt: null }; const mockProfile = { id: "profile-123", enrollmentType: EnrollmentType.API, projectId: "project-123" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any); await expect( service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 8 // Greater than 7-day TTL }) ).rejects.toThrow(BadRequestError); await expect( service.updateRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123", renewBeforeDays: 8 }) ).rejects.toThrow("Invalid renewal configuration: renewal threshold exceeds certificate validity period"); }); }); describe("disableRenewalConfig", () => { it("should disable renewal configuration successfully", async () => { const mockCert = { id: "cert-123", profileId: "profile-123", projectId: "project-123", commonName: "" }; const mockProfile = { id: "profile-123", enrollmentType: EnrollmentType.API, projectId: "project-123" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile as any); vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCert as any); const result = await service.disableRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123" }); expect(result).toEqual({ projectId: "project-123", commonName: "" }); expect(mockCertificateDAL.updateById).toHaveBeenCalledWith("cert-123", { renewBeforeDays: null }); }); it("should reject disable if certificate is not from profile", async () => { const mockCert = { id: "cert-123", profileId: null, projectId: "project-123" }; vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCert as any); await expect( service.disableRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123" }) ).rejects.toThrow(BadRequestError); await expect( service.disableRenewalConfig({ actor: ActorType.USER, actorId: "user-123", actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "org-123", certificateId: "cert-123" }) ).rejects.toThrow("Certificate is not eligible for auto-renewal: certificate was not issued from a profile"); }); }); });