Files
infisical/backend/src/services/certificate-v3/certificate-v3-service.test.ts
T

1464 lines
47 KiB
TypeScript

/* 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 { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { ACMESANType, CertificateOrderStatus } from "@app/services/certificate/certificate-types";
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
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 { certificateV3ServiceFactory, TCertificateV3ServiceFactory } from "./certificate-v3-service";
describe("CertificateV3Service", () => {
let service: TCertificateV3ServiceFactory;
const mockCertificateDAL: Pick<TCertificateDALFactory, "findOne" | "updateById"> = {
findOne: vi.fn(),
updateById: vi.fn()
};
const mockCertificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa"> = {
findByIdWithAssociatedCa: vi.fn()
};
const mockCertificateProfileDAL: Pick<TCertificateProfileDALFactory, "findByIdWithConfigs"> = {
findByIdWithConfigs: vi.fn()
};
const mockCertificateTemplateV2Service: Pick<
TCertificateTemplateV2ServiceFactory,
"validateCertificateRequest" | "getTemplateV2ById"
> = {
validateCertificateRequest: vi.fn(),
getTemplateV2ById: vi.fn()
};
const mockInternalCaService: Pick<TInternalCertificateAuthorityServiceFactory, "signCertFromCa" | "issueCertFromCa"> =
{
signCertFromCa: vi.fn(),
issueCertFromCa: vi.fn()
};
const mockPermissionService: Pick<TPermissionServiceFactory, "getProjectPermission"> = {
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.clearAllMocks();
// 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()
}
});
service = certificateV3ServiceFactory({
certificateDAL: mockCertificateDAL,
certificateAuthorityDAL: mockCertificateAuthorityDAL,
certificateProfileDAL: mockCertificateProfileDAL,
certificateTemplateV2Service: mockCertificateTemplateV2Service,
internalCaService: mockInternalCaService,
permissionService: mockPermissionService
});
});
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(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();
});
});
});