mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-18 10:48:36 +00:00
738 lines
22 KiB
TypeScript
738 lines
22 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
/* eslint-disable no-bitwise */
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|
|
|
import { EnrollmentType } from "../certificate-profile/certificate-profile-types";
|
|
import { certificateEstV3ServiceFactory, TCertificateEstV3ServiceFactory } from "./certificate-est-v3-service";
|
|
|
|
// Mock the x509 module
|
|
vi.mock("@peculiar/x509", () => ({
|
|
Pkcs10CertificateRequest: vi.fn(),
|
|
GeneralNames: vi.fn(),
|
|
KeyUsagesExtension: vi.fn(),
|
|
ExtendedKeyUsageExtension: vi.fn(),
|
|
X509Certificate: vi.fn(),
|
|
KeyUsageFlags: {
|
|
digitalSignature: 1,
|
|
nonRepudiation: 2,
|
|
keyEncipherment: 4,
|
|
dataEncipherment: 8,
|
|
keyAgreement: 16,
|
|
keyCertSign: 32,
|
|
cRLSign: 64,
|
|
encipherOnly: 128,
|
|
decipherOnly: 256
|
|
}
|
|
}));
|
|
|
|
// Mock other dependencies
|
|
vi.mock("@app/services/certificate-authority/certificate-authority-fns", () => ({
|
|
parseDistinguishedName: vi.fn((subject: string) => {
|
|
const parts = subject.split(",");
|
|
const result: any = {};
|
|
parts.forEach((part) => {
|
|
const [key, value] = part.split("=");
|
|
switch (key.trim()) {
|
|
case "CN":
|
|
result.commonName = value;
|
|
break;
|
|
case "O":
|
|
result.organization = value;
|
|
break;
|
|
case "OU":
|
|
result.ou = value;
|
|
break;
|
|
case "L":
|
|
result.locality = value;
|
|
break;
|
|
case "ST":
|
|
result.province = value;
|
|
break;
|
|
case "C":
|
|
result.country = value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
return result;
|
|
})
|
|
}));
|
|
|
|
vi.mock("@app/services/certificate-authority/certificate-authority-validators", () => ({
|
|
validateAndMapAltNameType: vi.fn((value: string) => {
|
|
if (value.includes(".") && !value.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
|
return { type: "dns", value };
|
|
}
|
|
if (value.match(/^\d+\.\d+\.\d+\.\d+$/)) {
|
|
return { type: "ip", value };
|
|
}
|
|
return null;
|
|
})
|
|
}));
|
|
|
|
vi.mock("@app/services/certificate-common/certificate-constants", () => ({
|
|
mapLegacyKeyUsageToStandard: vi.fn((usage: string) => {
|
|
const mapping: Record<string, string> = {
|
|
digitalSignature: "digital_signature",
|
|
keyEncipherment: "key_encipherment",
|
|
keyCertSign: "key_cert_sign"
|
|
};
|
|
return mapping[usage] || usage;
|
|
}),
|
|
mapLegacyExtendedKeyUsageToStandard: vi.fn((usage: string) => {
|
|
const mapping: Record<string, string> = {
|
|
clientAuth: "client_auth",
|
|
serverAuth: "server_auth",
|
|
codeSigning: "code_signing"
|
|
};
|
|
return mapping[usage] || usage;
|
|
}),
|
|
CertKeyUsageType: {
|
|
DIGITAL_SIGNATURE: "digital_signature",
|
|
KEY_ENCIPHERMENT: "key_encipherment",
|
|
KEY_CERT_SIGN: "key_cert_sign"
|
|
},
|
|
CertExtendedKeyUsageType: {
|
|
CLIENT_AUTH: "client_auth",
|
|
SERVER_AUTH: "server_auth",
|
|
CODE_SIGNING: "code_signing"
|
|
},
|
|
CertSubjectAlternativeNameType: {
|
|
DNS_NAME: "dns_name",
|
|
IP_ADDRESS: "ip_address",
|
|
RFC822_NAME: "rfc822_name",
|
|
UNIFORM_RESOURCE_IDENTIFIER: "uniform_resource_identifier"
|
|
}
|
|
}));
|
|
|
|
vi.mock("@app/services/certificate/certificate-types", () => ({
|
|
mapLegacyAltNameType: vi.fn((type: string) => {
|
|
const mapping: Record<string, string> = {
|
|
dns: "dns_name",
|
|
ip: "ip_address",
|
|
email: "rfc822_name",
|
|
url: "uniform_resource_identifier"
|
|
};
|
|
return mapping[type] || type;
|
|
}),
|
|
TAltNameType: {
|
|
EMAIL: "email",
|
|
DNS: "dns",
|
|
IP: "ip",
|
|
URL: "url"
|
|
},
|
|
CertExtendedKeyUsageOIDToName: {
|
|
"1.3.6.1.5.5.7.3.1": "serverAuth",
|
|
"1.3.6.1.5.5.7.3.2": "clientAuth",
|
|
"1.3.6.1.5.5.7.3.3": "codeSigning"
|
|
},
|
|
CertKeyUsage: {
|
|
DIGITAL_SIGNATURE: "digitalSignature",
|
|
KEY_ENCIPHERMENT: "keyEncipherment",
|
|
KEY_CERT_SIGN: "keyCertSign",
|
|
NON_REPUDIATION: "nonRepudiation",
|
|
DATA_ENCIPHERMENT: "dataEncipherment",
|
|
KEY_AGREEMENT: "keyAgreement",
|
|
CRL_SIGN: "cRLSign",
|
|
ENCIPHER_ONLY: "encipherOnly",
|
|
DECIPHER_ONLY: "decipherOnly"
|
|
},
|
|
CertExtendedKeyUsage: {
|
|
CLIENT_AUTH: "clientAuth",
|
|
SERVER_AUTH: "serverAuth",
|
|
CODE_SIGNING: "codeSigning"
|
|
}
|
|
}));
|
|
|
|
vi.mock("@app/services/certificate-common/certificate-utils", () => ({
|
|
mapEnumsForValidation: vi.fn((req: any) => req)
|
|
}));
|
|
|
|
vi.mock("../../ee/services/certificate-est/certificate-est-fns", () => ({
|
|
convertRawCertsToPkcs7: vi.fn(() => "mocked-pkcs7-response")
|
|
}));
|
|
|
|
describe("CertificateEstV3Service Security Fix", () => {
|
|
let service: TCertificateEstV3ServiceFactory;
|
|
|
|
const mockInternalCertificateAuthorityService = {
|
|
signCertFromCa: vi.fn()
|
|
};
|
|
|
|
const mockCertificateTemplateV2Service = {
|
|
validateCertificateRequest: vi.fn()
|
|
};
|
|
|
|
const mockCertificateAuthorityDAL = {
|
|
findById: vi.fn(),
|
|
findByIdWithAssociatedCa: vi.fn()
|
|
};
|
|
|
|
const mockCertificateAuthorityCertDAL = {
|
|
find: vi.fn(),
|
|
findById: vi.fn()
|
|
};
|
|
|
|
const mockProjectDAL = {
|
|
findOne: vi.fn(),
|
|
updateById: vi.fn(),
|
|
transaction: vi.fn()
|
|
};
|
|
|
|
const mockKmsService = {
|
|
decryptWithKmsKey: vi.fn(),
|
|
generateKmsKey: vi.fn()
|
|
};
|
|
|
|
const mockLicenseService = {
|
|
getPlan: vi.fn()
|
|
};
|
|
|
|
const mockCertificateProfileDAL = {
|
|
findByIdWithConfigs: vi.fn()
|
|
};
|
|
|
|
const mockEstEnrollmentConfigDAL = {
|
|
findById: vi.fn()
|
|
};
|
|
|
|
const mockProfile = {
|
|
id: "profile-123",
|
|
projectId: "project-123",
|
|
caId: "ca-123",
|
|
certificateTemplateId: "template-v2-123",
|
|
enrollmentType: EnrollmentType.EST,
|
|
estConfigId: "est-config-123"
|
|
};
|
|
|
|
const mockEstConfig = {
|
|
id: "est-config-123",
|
|
disableBootstrapCaValidation: true
|
|
};
|
|
|
|
const mockProject = {
|
|
id: "project-123",
|
|
orgId: "org-123"
|
|
};
|
|
|
|
const mockPlan = {
|
|
pkiEst: true
|
|
};
|
|
|
|
beforeEach(async () => {
|
|
const { Pkcs10CertificateRequest, GeneralNames } = await import("@peculiar/x509");
|
|
|
|
service = certificateEstV3ServiceFactory({
|
|
internalCertificateAuthorityService: mockInternalCertificateAuthorityService,
|
|
certificateTemplateV2Service: mockCertificateTemplateV2Service,
|
|
certificateAuthorityDAL: mockCertificateAuthorityDAL,
|
|
certificateAuthorityCertDAL: mockCertificateAuthorityCertDAL,
|
|
projectDAL: mockProjectDAL,
|
|
kmsService: mockKmsService,
|
|
licenseService: mockLicenseService,
|
|
certificateProfileDAL: mockCertificateProfileDAL,
|
|
estEnrollmentConfigDAL: mockEstEnrollmentConfigDAL
|
|
});
|
|
|
|
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
|
|
mockEstEnrollmentConfigDAL.findById.mockResolvedValue(mockEstConfig);
|
|
mockProjectDAL.findOne.mockResolvedValue(mockProject);
|
|
mockLicenseService.getPlan.mockResolvedValue(mockPlan);
|
|
|
|
// Set up the default CSR parsing behavior
|
|
(Pkcs10CertificateRequest as any).mockImplementation((csr: string) => {
|
|
const parsed = JSON.parse(csr);
|
|
const mockExtensions: any[] = [];
|
|
|
|
if (parsed.sans && parsed.sans.length > 0) {
|
|
mockExtensions.push({ type: "2.5.29.17", value: "mock-san-value" });
|
|
}
|
|
|
|
return {
|
|
subject: parsed.subject,
|
|
extensions: mockExtensions,
|
|
getExtension: vi.fn((oid: string) => {
|
|
if (oid === "2.5.29.15" && parsed.keyUsages && parsed.keyUsages.length > 0) {
|
|
// Calculate usages as bitwise OR of key usage flags
|
|
let usages = 0;
|
|
parsed.keyUsages.forEach((usage: string) => {
|
|
switch (usage) {
|
|
case "digital_signature":
|
|
usages |= 1; // KeyUsageFlags.digitalSignature
|
|
break;
|
|
case "key_encipherment":
|
|
usages |= 4; // KeyUsageFlags.keyEncipherment
|
|
break;
|
|
case "key_cert_sign":
|
|
usages |= 32; // KeyUsageFlags.keyCertSign
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
});
|
|
return { usages };
|
|
}
|
|
if (oid === "2.5.29.37" && parsed.extendedKeyUsages && parsed.extendedKeyUsages.length > 0) {
|
|
const ekuOids = parsed.extendedKeyUsages.map((eku: string) => {
|
|
switch (eku) {
|
|
case "client_auth":
|
|
return "1.3.6.1.5.5.7.3.2";
|
|
case "server_auth":
|
|
return "1.3.6.1.5.5.7.3.1";
|
|
case "code_signing":
|
|
return "1.3.6.1.5.5.7.3.3";
|
|
default:
|
|
return "1.3.6.1.5.5.7.3.1";
|
|
}
|
|
});
|
|
return { usages: ekuOids };
|
|
}
|
|
return undefined;
|
|
})
|
|
};
|
|
});
|
|
|
|
(GeneralNames as any).mockImplementation(() => ({
|
|
items: [
|
|
{ type: "dns", value: "test.example.com" },
|
|
{ type: "ip", value: "192.168.1.1" }
|
|
]
|
|
}));
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
const createMockCSR = (
|
|
options: {
|
|
subject?: string;
|
|
keyUsages?: string[];
|
|
extendedKeyUsages?: string[];
|
|
sans?: Array<{ type: string; value: string }>;
|
|
} = {}
|
|
) => {
|
|
const {
|
|
subject = "CN=test.example.com,O=Test Org,C=US",
|
|
keyUsages = [],
|
|
extendedKeyUsages = [],
|
|
sans = []
|
|
} = options;
|
|
|
|
return JSON.stringify({
|
|
subject,
|
|
keyUsages,
|
|
extendedKeyUsages,
|
|
sans
|
|
});
|
|
};
|
|
|
|
describe("CSR Extraction and Template Validation", () => {
|
|
it("should extract subject attributes from CSR", async () => {
|
|
const csr = createMockCSR({
|
|
subject: "CN=test.example.com,O=Test Organization,OU=IT Department,L=San Francisco,ST=California,C=US"
|
|
});
|
|
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: true,
|
|
errors: [],
|
|
warnings: []
|
|
});
|
|
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.objectContaining({
|
|
commonName: "test.example.com",
|
|
organization: "Test Organization",
|
|
organizationUnit: "IT Department",
|
|
locality: "San Francisco",
|
|
state: "California",
|
|
country: "US"
|
|
})
|
|
);
|
|
});
|
|
|
|
it("should extract key usages from CSR", async () => {
|
|
const csr = createMockCSR({
|
|
keyUsages: ["digital_signature", "key_encipherment"]
|
|
});
|
|
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: true,
|
|
errors: [],
|
|
warnings: []
|
|
});
|
|
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.objectContaining({
|
|
keyUsages: expect.arrayContaining(["digital_signature", "key_encipherment"])
|
|
})
|
|
);
|
|
});
|
|
|
|
it("should extract extended key usages from CSR", async () => {
|
|
const csr = createMockCSR({
|
|
extendedKeyUsages: ["client_auth", "server_auth"]
|
|
});
|
|
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: true,
|
|
errors: [],
|
|
warnings: []
|
|
});
|
|
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.objectContaining({
|
|
extendedKeyUsages: expect.arrayContaining(["client_auth", "server_auth"])
|
|
})
|
|
);
|
|
});
|
|
|
|
it("should extract Subject Alternative Names from CSR", async () => {
|
|
const { GeneralNames } = await import("@peculiar/x509");
|
|
|
|
const csr = createMockCSR({
|
|
sans: [
|
|
{ type: "dns", value: "test.example.com" },
|
|
{ type: "ip", value: "192.168.1.1" }
|
|
]
|
|
});
|
|
|
|
(GeneralNames as any).mockImplementation(() => ({
|
|
items: [
|
|
{ type: "dns", value: "test.example.com" },
|
|
{ type: "ip", value: "192.168.1.1" }
|
|
]
|
|
}));
|
|
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: true,
|
|
errors: [],
|
|
warnings: []
|
|
});
|
|
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.objectContaining({
|
|
subjectAlternativeNames: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
type: "dns_name",
|
|
value: "test.example.com"
|
|
}),
|
|
expect.objectContaining({
|
|
type: "ip_address",
|
|
value: "192.168.1.1"
|
|
})
|
|
])
|
|
})
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("Template Validation Enforcement", () => {
|
|
const basicCSR = createMockCSR();
|
|
|
|
it("should enforce template validation and reject invalid requests", async () => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: false,
|
|
errors: ["Common name 'test.example.com' is not allowed", "Key usage 'digital_signature' is denied"],
|
|
warnings: []
|
|
});
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(BadRequestError);
|
|
|
|
expect(mockInternalCertificateAuthorityService.signCertFromCa).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("should allow valid requests that pass template validation", async () => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: true,
|
|
errors: [],
|
|
warnings: []
|
|
});
|
|
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.any(Object)
|
|
);
|
|
expect(mockInternalCertificateAuthorityService.signCertFromCa).toHaveBeenCalledWith({
|
|
isInternal: true,
|
|
caId: "ca-123",
|
|
csr: basicCSR,
|
|
isFromProfile: true
|
|
});
|
|
});
|
|
|
|
it("should validate template for both simpleEnrollByProfile and simpleReenrollByProfile", async () => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: false,
|
|
errors: ["SAN value 'evil.com' is denied"],
|
|
warnings: []
|
|
});
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(BadRequestError);
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalled();
|
|
expect(mockInternalCertificateAuthorityService.signCertFromCa).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe("Policy Bypass Prevention", () => {
|
|
const maliciousCSR = createMockCSR({
|
|
subject: "CN=evil.com,O=Evil Corp,C=XX",
|
|
keyUsages: ["key_cert_sign"],
|
|
sans: [
|
|
{ type: "dns", value: "*.example.com" },
|
|
{ type: "ip", value: "127.0.0.1" }
|
|
]
|
|
});
|
|
|
|
it("should block attempts to bypass subject attribute policies", async () => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: false,
|
|
errors: ["Organization 'Evil Corp' is denied", "Country 'XX' is not allowed"],
|
|
warnings: []
|
|
});
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: maliciousCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(BadRequestError);
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.objectContaining({
|
|
commonName: "evil.com",
|
|
organization: "Evil Corp",
|
|
country: "XX"
|
|
})
|
|
);
|
|
});
|
|
|
|
it("should block attempts to bypass key usage policies", async () => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: false,
|
|
errors: ["Key usage 'key_cert_sign' is denied - certificate authority privileges not allowed"],
|
|
warnings: []
|
|
});
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: maliciousCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(BadRequestError);
|
|
|
|
expect(mockCertificateTemplateV2Service.validateCertificateRequest).toHaveBeenCalledWith(
|
|
"template-v2-123",
|
|
expect.objectContaining({
|
|
keyUsages: expect.arrayContaining(["key_cert_sign"])
|
|
})
|
|
);
|
|
});
|
|
|
|
it("should block attempts to bypass SAN policies", async () => {
|
|
const { GeneralNames } = await import("@peculiar/x509");
|
|
|
|
(GeneralNames as any).mockImplementation(() => ({
|
|
items: [
|
|
{ type: "dns", value: "*.example.com" },
|
|
{ type: "ip", value: "127.0.0.1" }
|
|
]
|
|
}));
|
|
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: false,
|
|
errors: ["SAN value '*.example.com' matches denied wildcard pattern", "SAN value '127.0.0.1' is denied"],
|
|
warnings: []
|
|
});
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: maliciousCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(BadRequestError);
|
|
});
|
|
});
|
|
|
|
describe("Error Handling", () => {
|
|
const basicCSR = createMockCSR();
|
|
|
|
it("should handle profile not found", async () => {
|
|
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(null);
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "nonexistent",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(NotFoundError);
|
|
});
|
|
|
|
it("should handle non-EST enrollment type", async () => {
|
|
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue({
|
|
...mockProfile,
|
|
enrollmentType: EnrollmentType.API
|
|
});
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow(BadRequestError);
|
|
});
|
|
|
|
it("should handle template validation service errors", async () => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockRejectedValue(
|
|
new Error("Template validation service unavailable")
|
|
);
|
|
|
|
await expect(
|
|
service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
})
|
|
).rejects.toThrow("Template validation service unavailable");
|
|
});
|
|
});
|
|
|
|
describe("Integration with existing flow", () => {
|
|
const basicCSR = createMockCSR();
|
|
|
|
beforeEach(() => {
|
|
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
|
|
isValid: true,
|
|
errors: [],
|
|
warnings: []
|
|
});
|
|
});
|
|
|
|
it("should call internal CA service with correct parameters after validation", async () => {
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
expect(mockInternalCertificateAuthorityService.signCertFromCa).toHaveBeenCalledWith({
|
|
isInternal: true,
|
|
caId: "ca-123",
|
|
isFromProfile: true,
|
|
csr: basicCSR
|
|
});
|
|
});
|
|
|
|
it("should use profile's CA ID instead of template ID to avoid v1/v2 mismatch", async () => {
|
|
mockInternalCertificateAuthorityService.signCertFromCa.mockResolvedValue({
|
|
certificate: { rawData: new ArrayBuffer(0) }
|
|
});
|
|
|
|
await service.simpleEnrollByProfile({
|
|
csr: basicCSR,
|
|
profileId: "profile-123",
|
|
sslClientCert: ""
|
|
});
|
|
|
|
// Verify it uses caId from profile, not certificateTemplateId
|
|
expect(mockInternalCertificateAuthorityService.signCertFromCa).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
caId: "ca-123"
|
|
})
|
|
);
|
|
|
|
// Verify it does NOT pass certificateTemplateId to avoid v1/v2 confusion
|
|
expect(mockInternalCertificateAuthorityService.signCertFromCa).toHaveBeenCalledWith(
|
|
expect.not.objectContaining({
|
|
certificateTemplateId: expect.anything()
|
|
})
|
|
);
|
|
});
|
|
});
|
|
});
|