mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-19 22:20:36 +00:00
Add self-sign certs support
This commit is contained in:
@@ -67,6 +67,12 @@ export const certificateEstV3ServiceFactory = ({
|
||||
throw new BadRequestError({ message: "EST enrollment not configured for this profile" });
|
||||
}
|
||||
|
||||
if (!profile.caId) {
|
||||
throw new BadRequestError({
|
||||
message: "Self-signed certificates are not supported for EST enrollment"
|
||||
});
|
||||
}
|
||||
|
||||
const estConfig = await estEnrollmentConfigDAL.findById(profile.estConfigId);
|
||||
if (!estConfig) {
|
||||
throw new NotFoundError({ message: "EST configuration not found" });
|
||||
@@ -169,6 +175,12 @@ export const certificateEstV3ServiceFactory = ({
|
||||
throw new BadRequestError({ message: "EST enrollment not configured for this profile" });
|
||||
}
|
||||
|
||||
if (!profile.caId) {
|
||||
throw new BadRequestError({
|
||||
message: "Self-signed certificates are not supported for EST enrollment"
|
||||
});
|
||||
}
|
||||
|
||||
const estConfig = await estEnrollmentConfigDAL.findById(profile.estConfigId);
|
||||
if (!estConfig) {
|
||||
throw new NotFoundError({ message: "EST configuration not found" });
|
||||
@@ -281,6 +293,12 @@ export const certificateEstV3ServiceFactory = ({
|
||||
throw new BadRequestError({ message: "EST enrollment not configured for this profile" });
|
||||
}
|
||||
|
||||
if (!profile.caId) {
|
||||
throw new BadRequestError({
|
||||
message: "Self-signed certificates are not supported for EST enrollment"
|
||||
});
|
||||
}
|
||||
|
||||
const estConfig = await estEnrollmentConfigDAL.findById(profile.estConfigId);
|
||||
if (!estConfig) {
|
||||
throw new NotFoundError({ message: "EST configuration not found" });
|
||||
|
||||
@@ -7,6 +7,7 @@ import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||
|
||||
import {
|
||||
EnrollmentType,
|
||||
IssuerType,
|
||||
TCertificateProfile,
|
||||
TCertificateProfileCertificate,
|
||||
TCertificateProfileInsert,
|
||||
@@ -198,6 +199,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
slug: result.slug,
|
||||
description: result.description,
|
||||
enrollmentType: result.enrollmentType as EnrollmentType,
|
||||
issuerType: result.issuerType as IssuerType,
|
||||
estConfigId: result.estConfigId,
|
||||
apiConfigId: result.apiConfigId,
|
||||
acmeConfigId: result.acmeConfigId,
|
||||
@@ -239,12 +241,13 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
limit?: number;
|
||||
search?: string;
|
||||
enrollmentType?: EnrollmentType;
|
||||
issuerType?: IssuerType;
|
||||
caId?: string;
|
||||
} = {},
|
||||
tx?: Knex
|
||||
): Promise<TCertificateProfile[] | TCertificateProfileWithConfigs[]> => {
|
||||
try {
|
||||
const { offset = 0, limit = 20, search, enrollmentType, caId } = options;
|
||||
const { offset = 0, limit = 20, search, enrollmentType, issuerType, caId } = options;
|
||||
|
||||
let baseQuery = (tx || db)(TableName.PkiCertificateProfile).where(
|
||||
`${TableName.PkiCertificateProfile}.projectId`,
|
||||
@@ -269,6 +272,10 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.caId`, caId);
|
||||
}
|
||||
|
||||
if (issuerType) {
|
||||
baseQuery = baseQuery.where(`${TableName.PkiCertificateProfile}.issuerType`, issuerType);
|
||||
}
|
||||
|
||||
const query = baseQuery
|
||||
.leftJoin(
|
||||
TableName.PkiEstEnrollmentConfig,
|
||||
@@ -338,8 +345,10 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
slug: result.slug,
|
||||
description: result.description,
|
||||
enrollmentType: result.enrollmentType as EnrollmentType,
|
||||
issuerType: result.issuerType as IssuerType,
|
||||
estConfigId: result.estConfigId,
|
||||
apiConfigId: result.apiConfigId,
|
||||
acmeConfigId: result.acmeConfigId,
|
||||
createdAt: result.createdAt,
|
||||
updatedAt: result.updatedAt,
|
||||
estConfig,
|
||||
@@ -359,12 +368,13 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
options: {
|
||||
search?: string;
|
||||
enrollmentType?: EnrollmentType;
|
||||
issuerType?: IssuerType;
|
||||
caId?: string;
|
||||
} = {},
|
||||
tx?: Knex
|
||||
): Promise<number> => {
|
||||
try {
|
||||
const { search, enrollmentType, caId } = options;
|
||||
const { search, enrollmentType, issuerType, caId } = options;
|
||||
|
||||
let query = (tx || db)(TableName.PkiCertificateProfile).where({ projectId });
|
||||
|
||||
@@ -384,6 +394,10 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
query = query.where({ caId });
|
||||
}
|
||||
|
||||
if (issuerType) {
|
||||
query = query.where({ issuerType });
|
||||
}
|
||||
|
||||
const result = await query.count("*").first();
|
||||
return parseInt((result as unknown as { count: string }).count || "0", 10);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import RE2 from "re2";
|
||||
import { z } from "zod";
|
||||
|
||||
import { EnrollmentType } from "./certificate-profile-types";
|
||||
import { CertStatus } from "../certificate/certificate-types";
|
||||
import { EnrollmentType, IssuerType } from "./certificate-profile-types";
|
||||
|
||||
export const createCertificateProfileSchema = z
|
||||
.object({
|
||||
projectId: z.string().uuid("Project ID must be valid"),
|
||||
caId: z.string().uuid(),
|
||||
caId: z
|
||||
.union([z.string().uuid(), z.literal("")])
|
||||
.optional()
|
||||
.nullable()
|
||||
.transform((val) => (val === "" ? null : val)),
|
||||
certificateTemplateId: z.string().uuid(),
|
||||
slug: z
|
||||
.string()
|
||||
@@ -15,6 +20,7 @@ export const createCertificateProfileSchema = z
|
||||
.regex(new RE2("^[a-z0-9-]+$"), "Slug must contain only lowercase letters, numbers, and hyphens"),
|
||||
description: z.string().max(1000).optional(),
|
||||
enrollmentType: z.nativeEnum(EnrollmentType),
|
||||
issuerType: z.nativeEnum(IssuerType).default(IssuerType.CA),
|
||||
estConfig: z
|
||||
.object({
|
||||
disableBootstrapCaValidation: z.boolean().default(false),
|
||||
@@ -32,6 +38,7 @@ export const createCertificateProfileSchema = z
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
// Validate enrollment type configurations
|
||||
if (data.enrollmentType === EnrollmentType.EST) {
|
||||
if (!data.estConfig) {
|
||||
return false;
|
||||
@@ -65,11 +72,26 @@ export const createCertificateProfileSchema = z
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.issuerType === IssuerType.CA) {
|
||||
if (!data.caId) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (data.issuerType === IssuerType.SELF_SIGNED) {
|
||||
if (data.caId) {
|
||||
return false;
|
||||
}
|
||||
if (data.enrollmentType !== EnrollmentType.API) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"EST enrollment type requires EST configuration and cannot have API configuration. API enrollment type requires API configuration and cannot have EST configuration."
|
||||
"EST enrollment type requires EST configuration and cannot have API configuration. API enrollment type requires API configuration and cannot have EST configuration. CA issuer type requires a CA ID. Self-signed issuer type cannot have a CA ID and only supports API enrollment."
|
||||
}
|
||||
);
|
||||
|
||||
@@ -83,6 +105,7 @@ export const updateCertificateProfileSchema = z
|
||||
.optional(),
|
||||
description: z.string().max(1000).optional(),
|
||||
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
|
||||
issuerType: z.nativeEnum(IssuerType).optional(),
|
||||
estConfig: z
|
||||
.object({
|
||||
disableBootstrapCaValidation: z.boolean().default(false),
|
||||
@@ -109,10 +132,18 @@ export const updateCertificateProfileSchema = z
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.issuerType === IssuerType.SELF_SIGNED) {
|
||||
if (data.enrollmentType && data.enrollmentType !== EnrollmentType.API) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Cannot have EST config with API enrollment type or API config with EST enrollment type."
|
||||
message:
|
||||
"Cannot have EST config with API enrollment type or API config with EST enrollment type. Self-signed issuer type only supports API enrollment."
|
||||
}
|
||||
);
|
||||
|
||||
@@ -131,6 +162,7 @@ export const listCertificateProfilesSchema = z.object({
|
||||
limit: z.coerce.number().min(1).max(100).default(20),
|
||||
search: z.string().optional(),
|
||||
enrollmentType: z.nativeEnum(EnrollmentType).optional(),
|
||||
issuerType: z.nativeEnum(IssuerType).optional(),
|
||||
caId: z.string().uuid().optional()
|
||||
});
|
||||
|
||||
@@ -142,6 +174,6 @@ export const listCertificatesByProfileSchema = z.object({
|
||||
profileId: z.string().uuid(),
|
||||
offset: z.coerce.number().min(0).default(0),
|
||||
limit: z.coerce.number().min(1).max(100).default(20),
|
||||
status: z.enum(["active", "expired", "revoked"]).optional(),
|
||||
status: z.nativeEnum(CertStatus).optional(),
|
||||
search: z.string().optional()
|
||||
});
|
||||
|
||||
@@ -22,7 +22,12 @@ import type { TKmsServiceFactory } from "../kms/kms-service";
|
||||
import type { TProjectDALFactory } from "../project/project-dal";
|
||||
import type { TCertificateProfileDALFactory } from "./certificate-profile-dal";
|
||||
import { certificateProfileServiceFactory, TCertificateProfileServiceFactory } from "./certificate-profile-service";
|
||||
import { EnrollmentType, TCertificateProfile, TCertificateProfileWithConfigs } from "./certificate-profile-types";
|
||||
import {
|
||||
EnrollmentType,
|
||||
IssuerType,
|
||||
TCertificateProfile,
|
||||
TCertificateProfileWithConfigs
|
||||
} from "./certificate-profile-types";
|
||||
|
||||
vi.mock("@app/lib/crypto/cryptography", () => ({
|
||||
crypto: {
|
||||
@@ -90,6 +95,7 @@ describe("CertificateProfileService", () => {
|
||||
description: "Test certificate profile",
|
||||
slug: "test-profile",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfigId: "api-config-123",
|
||||
@@ -272,6 +278,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "new-profile",
|
||||
description: "New test profile",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
@@ -312,6 +319,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "new-profile",
|
||||
description: "New test profile",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfigId: "api-config-123",
|
||||
@@ -383,6 +391,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "invalid-profile",
|
||||
description: "Invalid test profile",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123"
|
||||
};
|
||||
@@ -401,6 +410,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "api-profile",
|
||||
description: "Profile with API enrollment",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
@@ -726,6 +736,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "est-profile",
|
||||
description: "Profile with EST enrollment",
|
||||
enrollmentType: EnrollmentType.EST,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
estConfig: {
|
||||
@@ -776,6 +787,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "different-profile-name",
|
||||
description: "Profile with duplicate slug",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
@@ -801,6 +813,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "auto-renew-profile",
|
||||
description: "Profile with auto-renewal",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
@@ -965,6 +978,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "invalid-template-profile",
|
||||
description: "Profile with invalid template",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "nonexistent-template",
|
||||
apiConfig: {
|
||||
@@ -990,6 +1004,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "concurrent-profile",
|
||||
description: "Profile created concurrently",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
@@ -1018,6 +1033,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "cross-project-profile",
|
||||
description: "Profile using template from different project",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-456",
|
||||
apiConfig: {
|
||||
@@ -1047,6 +1063,7 @@ describe("CertificateProfileService", () => {
|
||||
slug: "invalid-slug-profile",
|
||||
description: "Profile with invalid slug format",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
|
||||
@@ -32,6 +32,7 @@ import { getProjectKmsCertificateKeyId } from "../project/project-fns";
|
||||
import { TCertificateProfileDALFactory } from "./certificate-profile-dal";
|
||||
import {
|
||||
EnrollmentType,
|
||||
IssuerType,
|
||||
TCertificateProfile,
|
||||
TCertificateProfileCertificate,
|
||||
TCertificateProfileInsert,
|
||||
@@ -39,6 +40,34 @@ import {
|
||||
TCertificateProfileWithConfigs
|
||||
} from "./certificate-profile-types";
|
||||
|
||||
const validateIssuerTypeConstraints = (
|
||||
issuerType: IssuerType,
|
||||
enrollmentType: EnrollmentType,
|
||||
caId: string | null,
|
||||
existingCaId?: string | null
|
||||
) => {
|
||||
if (issuerType === IssuerType.CA) {
|
||||
if (!caId && !existingCaId) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "CA issuer type requires a Certificate Authority to be selected"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (issuerType === IssuerType.SELF_SIGNED) {
|
||||
if (caId) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Self-signed issuer type cannot have a Certificate Authority"
|
||||
});
|
||||
}
|
||||
if (enrollmentType !== EnrollmentType.API) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Self-signed issuer type only supports API enrollment"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const generateAndEncryptAcmeEabSecret = async (
|
||||
projectId: string,
|
||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey">,
|
||||
@@ -163,7 +192,8 @@ export type TCertificateProfileServiceFactory = ReturnType<typeof certificatePro
|
||||
const convertDalToService = (dalResult: Record<string, unknown>): TCertificateProfile => {
|
||||
return {
|
||||
...dalResult,
|
||||
enrollmentType: dalResult.enrollmentType as EnrollmentType
|
||||
enrollmentType: dalResult.enrollmentType as EnrollmentType,
|
||||
issuerType: dalResult.issuerType as IssuerType
|
||||
} as TCertificateProfile;
|
||||
};
|
||||
|
||||
@@ -240,6 +270,8 @@ export const certificateProfileServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
validateIssuerTypeConstraints(data.issuerType, data.enrollmentType, data.caId ?? null);
|
||||
|
||||
// Validate enrollment configuration requirements
|
||||
if (data.enrollmentType === EnrollmentType.EST && !data.estConfig) {
|
||||
throw new ForbiddenRequestError({
|
||||
@@ -376,7 +408,18 @@ export const certificateProfileServiceFactory = ({
|
||||
}
|
||||
}
|
||||
|
||||
const { estConfig, apiConfig, ...profileUpdateData } = data;
|
||||
const finalIssuerType = data.issuerType || existingProfile.issuerType;
|
||||
const finalEnrollmentType = data.enrollmentType || existingProfile.enrollmentType;
|
||||
const finalCaId = data.caId !== undefined ? data.caId : existingProfile.caId;
|
||||
|
||||
validateIssuerTypeConstraints(finalIssuerType, finalEnrollmentType, finalCaId ?? null, existingProfile.caId);
|
||||
|
||||
const updatedData =
|
||||
finalIssuerType === IssuerType.SELF_SIGNED && existingProfile.caId && data.issuerType === IssuerType.SELF_SIGNED
|
||||
? { ...data, caId: null }
|
||||
: data;
|
||||
|
||||
const { estConfig, apiConfig, ...profileUpdateData } = updatedData;
|
||||
|
||||
const updatedProfile = await certificateProfileDAL.transaction(async (tx) => {
|
||||
if (estConfig && existingProfile.estConfigId) {
|
||||
@@ -569,6 +612,7 @@ export const certificateProfileServiceFactory = ({
|
||||
limit = 20,
|
||||
search,
|
||||
enrollmentType,
|
||||
issuerType,
|
||||
caId
|
||||
}: {
|
||||
actor: ActorType;
|
||||
@@ -580,6 +624,7 @@ export const certificateProfileServiceFactory = ({
|
||||
limit?: number;
|
||||
search?: string;
|
||||
enrollmentType?: EnrollmentType;
|
||||
issuerType?: IssuerType;
|
||||
caId?: string;
|
||||
}): Promise<{
|
||||
profiles: TCertificateProfileWithConfigs[];
|
||||
@@ -603,12 +648,14 @@ export const certificateProfileServiceFactory = ({
|
||||
limit,
|
||||
search,
|
||||
enrollmentType,
|
||||
issuerType,
|
||||
caId
|
||||
});
|
||||
|
||||
const totalCount = await certificateProfileDAL.countByProjectId(projectId, {
|
||||
search,
|
||||
enrollmentType,
|
||||
issuerType,
|
||||
caId
|
||||
});
|
||||
|
||||
|
||||
@@ -10,16 +10,24 @@ export enum EnrollmentType {
|
||||
ACME = "acme"
|
||||
}
|
||||
|
||||
export type TCertificateProfile = Omit<TPkiCertificateProfiles, "enrollmentType"> & {
|
||||
export enum IssuerType {
|
||||
CA = "ca",
|
||||
SELF_SIGNED = "self-signed"
|
||||
}
|
||||
|
||||
export type TCertificateProfile = Omit<TPkiCertificateProfiles, "enrollmentType" | "issuerType"> & {
|
||||
enrollmentType: EnrollmentType;
|
||||
issuerType: IssuerType;
|
||||
};
|
||||
|
||||
export type TCertificateProfileInsert = Omit<TPkiCertificateProfilesInsert, "enrollmentType"> & {
|
||||
export type TCertificateProfileInsert = Omit<TPkiCertificateProfilesInsert, "enrollmentType" | "issuerType"> & {
|
||||
enrollmentType: EnrollmentType;
|
||||
issuerType: IssuerType;
|
||||
};
|
||||
|
||||
export type TCertificateProfileUpdate = Omit<TPkiCertificateProfilesUpdate, "enrollmentType"> & {
|
||||
export type TCertificateProfileUpdate = Omit<TPkiCertificateProfilesUpdate, "enrollmentType" | "issuerType"> & {
|
||||
enrollmentType?: EnrollmentType;
|
||||
issuerType?: IssuerType;
|
||||
estConfig?: {
|
||||
disableBootstrapCaValidation?: boolean;
|
||||
passphrase?: string;
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
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 { EnrollmentType, IssuerType } 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";
|
||||
@@ -40,18 +40,29 @@ vi.mock("../certificate-common/certificate-csr-utils", () => ({
|
||||
describe("CertificateV3Service", () => {
|
||||
let service: TCertificateV3ServiceFactory;
|
||||
|
||||
const mockCertificateDAL: Pick<TCertificateDALFactory, "findOne" | "findById" | "updateById" | "transaction"> = {
|
||||
const mockCertificateDAL: Pick<
|
||||
TCertificateDALFactory,
|
||||
"findOne" | "findById" | "updateById" | "transaction" | "create"
|
||||
> = {
|
||||
findOne: vi.fn(),
|
||||
findById: vi.fn(),
|
||||
updateById: vi.fn(),
|
||||
create: vi.fn().mockResolvedValue({
|
||||
id: "new-cert-id",
|
||||
serialNumber: "123456789",
|
||||
friendlyName: "Test Certificate",
|
||||
commonName: "test.example.com",
|
||||
status: "ACTIVE"
|
||||
}),
|
||||
transaction: vi.fn().mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||
const mockTx = {};
|
||||
return callback(mockTx);
|
||||
})
|
||||
};
|
||||
|
||||
const mockCertificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne"> = {
|
||||
findOne: vi.fn()
|
||||
const mockCertificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne" | "create"> = {
|
||||
findOne: vi.fn(),
|
||||
create: vi.fn()
|
||||
};
|
||||
|
||||
const mockCertificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa"> = {
|
||||
@@ -150,7 +161,24 @@ describe("CertificateV3Service", () => {
|
||||
},
|
||||
pkiSyncQueue: {
|
||||
queuePkiSyncSyncCertificatesById: vi.fn().mockResolvedValue(undefined)
|
||||
}
|
||||
},
|
||||
certificateBodyDAL: {
|
||||
create: vi.fn().mockResolvedValue({ id: "body-123" })
|
||||
},
|
||||
kmsService: {
|
||||
generateKmsKey: vi.fn().mockResolvedValue("kms-key-123"),
|
||||
encryptWithKmsKey: vi.fn().mockResolvedValue(vi.fn().mockResolvedValue(Buffer.from("encrypted"))),
|
||||
decryptWithKmsKey: vi.fn().mockResolvedValue(vi.fn().mockResolvedValue(Buffer.from("decrypted")))
|
||||
},
|
||||
projectDAL: {
|
||||
findOne: vi.fn().mockResolvedValue({ id: "project-123" }),
|
||||
findById: vi.fn().mockResolvedValue({ id: "project-123" }),
|
||||
updateById: vi.fn().mockResolvedValue({ id: "project-123" }),
|
||||
transaction: vi.fn().mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||
const mockTx = {};
|
||||
return callback(mockTx);
|
||||
})
|
||||
} as any
|
||||
});
|
||||
});
|
||||
|
||||
@@ -175,6 +203,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -319,6 +348,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -508,6 +538,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.EST, // Wrong enrollment type
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -561,6 +592,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -721,6 +753,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.EST, // Wrong enrollment type
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -772,6 +805,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -933,6 +967,7 @@ describe("CertificateV3Service", () => {
|
||||
id: profileId,
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.EST, // Wrong enrollment type
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
createdAt: new Date(),
|
||||
@@ -971,6 +1006,7 @@ describe("CertificateV3Service", () => {
|
||||
caId: "ca-1",
|
||||
certificateTemplateId: "template-1",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
description: "Test profile for algorithm compatibility",
|
||||
@@ -1552,6 +1588,7 @@ describe("CertificateV3Service", () => {
|
||||
id: "profile-123",
|
||||
projectId: "project-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
caId: "ca-123",
|
||||
certificateTemplateId: "template-123",
|
||||
apiConfig: {
|
||||
@@ -1733,9 +1770,9 @@ describe("CertificateV3Service", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("should reject renewal if certificate is not from a profile", async () => {
|
||||
const certWithoutProfile = { ...mockOriginalCert, profileId: null };
|
||||
vi.mocked(mockCertificateDAL.findById).mockResolvedValue(certWithoutProfile);
|
||||
it("should reject renewal if certificate has no profile and no CA", async () => {
|
||||
const certWithoutProfileAndCA = { ...mockOriginalCert, profileId: null, caId: null };
|
||||
vi.mocked(mockCertificateDAL.findById).mockResolvedValue(certWithoutProfileAndCA);
|
||||
|
||||
// Set up transaction mock to properly handle errors
|
||||
vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||
@@ -1755,7 +1792,7 @@ describe("CertificateV3Service", () => {
|
||||
certificateId: "cert-123",
|
||||
...mockActor
|
||||
})
|
||||
).rejects.toThrow("Only certificates issued from a profile can be renewed");
|
||||
).rejects.toThrow("Only certificates issued from a profile or self-signed certificates can be renewed");
|
||||
});
|
||||
|
||||
it("should reject renewal if certificate was issued from CSR (external private key)", async () => {
|
||||
@@ -1989,6 +2026,44 @@ describe("CertificateV3Service", () => {
|
||||
|
||||
expect(result).toHaveProperty("certificate", "renewed-cert");
|
||||
});
|
||||
|
||||
it("should successfully renew self-signed certificate", async () => {
|
||||
// Self-signed certificate has no caId and no profileId
|
||||
const selfSignedCert = {
|
||||
...mockOriginalCert,
|
||||
profileId: null,
|
||||
certificateTemplateId: null,
|
||||
caId: null
|
||||
};
|
||||
|
||||
vi.mocked(mockCertificateDAL.findById).mockResolvedValue(selfSignedCert);
|
||||
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);
|
||||
|
||||
const newCert = {
|
||||
...selfSignedCert,
|
||||
id: "cert-456",
|
||||
serialNumber: "self-signed-789012"
|
||||
};
|
||||
vi.mocked(mockCertificateDAL.create).mockResolvedValue(newCert);
|
||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(newCert);
|
||||
vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(newCert);
|
||||
|
||||
// Set up transaction mock
|
||||
vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||
const mockTx = {};
|
||||
return callback(mockTx);
|
||||
});
|
||||
|
||||
const result = await service.renewCertificate({
|
||||
certificateId: "cert-123",
|
||||
...mockActor
|
||||
});
|
||||
|
||||
expect(result).toHaveProperty("certificate");
|
||||
expect(result).toHaveProperty("serialNumber");
|
||||
});
|
||||
});
|
||||
|
||||
describe("updateRenewalConfig", () => {
|
||||
@@ -2008,6 +2083,7 @@ describe("CertificateV3Service", () => {
|
||||
const mockProfile = {
|
||||
id: "profile-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
projectId: "project-123"
|
||||
};
|
||||
|
||||
@@ -2084,6 +2160,7 @@ describe("CertificateV3Service", () => {
|
||||
const mockProfile = {
|
||||
id: "profile-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
projectId: "project-123"
|
||||
};
|
||||
|
||||
@@ -2129,6 +2206,7 @@ describe("CertificateV3Service", () => {
|
||||
const mockProfile = {
|
||||
id: "profile-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
projectId: "project-123"
|
||||
};
|
||||
|
||||
@@ -2172,6 +2250,7 @@ describe("CertificateV3Service", () => {
|
||||
const mockProfile = {
|
||||
id: "profile-123",
|
||||
enrollmentType: EnrollmentType.API,
|
||||
issuerType: IssuerType.CA,
|
||||
projectId: "project-123"
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
import { randomUUID } from "crypto";
|
||||
import RE2 from "re2";
|
||||
|
||||
import { ActionProjectType } from "@app/db/schemas";
|
||||
import { ActionProjectType, TCertificates } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||
import {
|
||||
ProjectPermissionCertificateActions,
|
||||
@@ -10,8 +11,11 @@ import {
|
||||
ProjectPermissionSub
|
||||
} from "@app/ee/services/permission/project-permission";
|
||||
import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-account-dal";
|
||||
import { crypto } from "@app/lib/crypto/cryptography";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { ms } from "@app/lib/ms";
|
||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||
import {
|
||||
@@ -28,12 +32,25 @@ import {
|
||||
TCertificateAuthorityWithAssociatedCa
|
||||
} from "@app/services/certificate-authority/certificate-authority-dal";
|
||||
import { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||
import {
|
||||
createDistinguishedName,
|
||||
createSerialNumber,
|
||||
keyAlgorithmToAlgCfg,
|
||||
signatureAlgorithmToAlgCfg
|
||||
} from "@app/services/certificate-authority/certificate-authority-fns";
|
||||
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
||||
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
||||
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||
import { EnrollmentType, IssuerType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||
import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
import { CertSubjectAlternativeNameType } from "../certificate-common/certificate-constants";
|
||||
import {
|
||||
CertExtendedKeyUsageType,
|
||||
CertKeyUsageType,
|
||||
CertSubjectAlternativeNameType
|
||||
} from "../certificate-common/certificate-constants";
|
||||
import {
|
||||
extractAlgorithmsFromCSR,
|
||||
extractCertificateRequestFromCSR
|
||||
@@ -68,8 +85,9 @@ import {
|
||||
} from "./certificate-v3-types";
|
||||
|
||||
type TCertificateV3ServiceFactoryDep = {
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "findById" | "updateById" | "transaction">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "findById" | "updateById" | "transaction" | "create">;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "findOne" | "create">;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
|
||||
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findByIdWithConfigs">;
|
||||
acmeAccountDAL: Pick<TPkiAcmeAccountDALFactory, "findById">;
|
||||
@@ -85,6 +103,8 @@ type TCertificateV3ServiceFactoryDep = {
|
||||
>;
|
||||
pkiSyncDAL: Pick<TPkiSyncDALFactory, "find">;
|
||||
pkiSyncQueue: Pick<TPkiSyncQueueFactory, "queuePkiSyncSyncCertificatesById">;
|
||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
||||
projectDAL: TProjectDALFactory;
|
||||
};
|
||||
|
||||
export type TCertificateV3ServiceFactory = ReturnType<typeof certificateV3ServiceFactory>;
|
||||
@@ -329,6 +349,141 @@ const parseTtlToDays = (ttl: string): number => {
|
||||
}
|
||||
};
|
||||
|
||||
const generateSelfSignedCertificate = async ({
|
||||
certificateRequest,
|
||||
template,
|
||||
effectiveSignatureAlgorithm,
|
||||
effectiveKeyAlgorithm
|
||||
}: {
|
||||
certificateRequest: {
|
||||
commonName?: string;
|
||||
keyUsages?: CertKeyUsageType[];
|
||||
extendedKeyUsages?: CertExtendedKeyUsageType[];
|
||||
altNames?: Array<{
|
||||
type: CertSubjectAlternativeNameType;
|
||||
value: string;
|
||||
}>;
|
||||
validity: { ttl: string };
|
||||
notBefore?: Date;
|
||||
notAfter?: Date;
|
||||
};
|
||||
template?: {
|
||||
subject?: Array<{
|
||||
type: string;
|
||||
allowed?: string[];
|
||||
required?: string[];
|
||||
denied?: string[];
|
||||
}>;
|
||||
sans?: Array<{
|
||||
type: string;
|
||||
allowed?: string[];
|
||||
required?: string[];
|
||||
denied?: string[];
|
||||
}>;
|
||||
} | null;
|
||||
effectiveSignatureAlgorithm: CertSignatureAlgorithm;
|
||||
effectiveKeyAlgorithm: CertKeyAlgorithm;
|
||||
}): Promise<{
|
||||
certificate: Buffer;
|
||||
privateKey: Buffer;
|
||||
serialNumber: string;
|
||||
notBefore: Date;
|
||||
notAfter: Date;
|
||||
certificateSubject: Record<string, unknown>;
|
||||
subjectAlternativeNames: Array<{
|
||||
type: CertSubjectAlternativeNameType;
|
||||
value: string;
|
||||
}>;
|
||||
}> => {
|
||||
const certificateSubject = buildCertificateSubjectFromTemplate(certificateRequest, template?.subject);
|
||||
const subjectAlternativeNames = buildSubjectAlternativeNamesFromTemplate(
|
||||
{ subjectAlternativeNames: certificateRequest.altNames },
|
||||
template?.sans
|
||||
);
|
||||
|
||||
const keyGenAlg = keyAlgorithmToAlgCfg(effectiveKeyAlgorithm);
|
||||
const keyPair = await crypto.nativeCrypto.subtle.generateKey(keyGenAlg, true, ["sign", "verify"]);
|
||||
|
||||
const signatureAlgorithmConfig = signatureAlgorithmToAlgCfg(effectiveSignatureAlgorithm, effectiveKeyAlgorithm);
|
||||
|
||||
const notBeforeDate = certificateRequest.notBefore ? new Date(certificateRequest.notBefore) : new Date();
|
||||
let notAfterDate = new Date(new Date().setFullYear(new Date().getFullYear() + 1));
|
||||
if (certificateRequest.notAfter) {
|
||||
notAfterDate = new Date(certificateRequest.notAfter);
|
||||
} else if (certificateRequest.validity.ttl) {
|
||||
notAfterDate = new Date(new Date().getTime() + ms(certificateRequest.validity.ttl));
|
||||
}
|
||||
|
||||
const serialNumber = createSerialNumber();
|
||||
const dn = createDistinguishedName({
|
||||
commonName: certificateSubject.common_name,
|
||||
organization: certificateSubject.organization,
|
||||
ou: certificateSubject.organizational_unit,
|
||||
country: certificateSubject.country,
|
||||
province: certificateSubject.state_or_province_name,
|
||||
locality: certificateSubject.locality_name
|
||||
});
|
||||
|
||||
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||
name: dn,
|
||||
serialNumber,
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate,
|
||||
signingAlgorithm: signatureAlgorithmConfig,
|
||||
keys: keyPair,
|
||||
extensions: [
|
||||
new x509.BasicConstraintsExtension(false, undefined, false),
|
||||
...(certificateRequest.keyUsages?.length
|
||||
? [
|
||||
new x509.KeyUsagesExtension(
|
||||
(convertKeyUsageArrayToLegacy(certificateRequest.keyUsages) || []).reduce(
|
||||
// eslint-disable-next-line no-bitwise
|
||||
(acc: number, usage) => acc | x509.KeyUsageFlags[usage],
|
||||
0
|
||||
),
|
||||
false
|
||||
)
|
||||
]
|
||||
: []),
|
||||
...(certificateRequest.extendedKeyUsages?.length
|
||||
? [
|
||||
new x509.ExtendedKeyUsageExtension(
|
||||
(convertExtendedKeyUsageArrayToLegacy(certificateRequest.extendedKeyUsages) || []).map(
|
||||
(eku) => x509.ExtendedKeyUsage[eku]
|
||||
),
|
||||
false
|
||||
)
|
||||
]
|
||||
: []),
|
||||
...(subjectAlternativeNames
|
||||
? [
|
||||
new x509.SubjectAlternativeNameExtension(
|
||||
certificateRequest.altNames?.map((san) => ({
|
||||
type: san.type === CertSubjectAlternativeNameType.DNS_NAME ? "dns" : "ip",
|
||||
value: san.value
|
||||
})) || [],
|
||||
false
|
||||
)
|
||||
]
|
||||
: [])
|
||||
]
|
||||
});
|
||||
|
||||
const certificatePem = cert.toString("pem");
|
||||
const privateKeyObj = crypto.nativeCrypto.KeyObject.from(keyPair.privateKey);
|
||||
const privateKeyPem = privateKeyObj.export({ format: "pem", type: "pkcs8" }) as string;
|
||||
|
||||
return {
|
||||
certificate: Buffer.from(certificatePem),
|
||||
privateKey: Buffer.from(privateKeyPem),
|
||||
serialNumber,
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate,
|
||||
certificateSubject,
|
||||
subjectAlternativeNames: certificateRequest.altNames || []
|
||||
};
|
||||
};
|
||||
|
||||
const calculateFinalRenewBeforeDays = (
|
||||
profile: { apiConfig?: { autoRenew?: boolean; renewBeforeDays?: number } },
|
||||
ttl: string,
|
||||
@@ -348,8 +503,248 @@ const calculateFinalRenewBeforeDays = (
|
||||
return isValidRenewalTiming(renewBeforeDays, certificateExpiryDate) ? renewBeforeDays : undefined;
|
||||
};
|
||||
|
||||
const getEffectiveAlgorithms = (
|
||||
requestSignatureAlgorithm?: CertSignatureAlgorithm,
|
||||
requestKeyAlgorithm?: CertKeyAlgorithm,
|
||||
originalSignatureAlgorithm?: CertSignatureAlgorithm,
|
||||
originalKeyAlgorithm?: CertKeyAlgorithm
|
||||
) => {
|
||||
return {
|
||||
signatureAlgorithm: requestSignatureAlgorithm || originalSignatureAlgorithm || CertSignatureAlgorithm.RSA_SHA256,
|
||||
keyAlgorithm: requestKeyAlgorithm || originalKeyAlgorithm || CertKeyAlgorithm.RSA_2048
|
||||
};
|
||||
};
|
||||
|
||||
const createSelfSignedCertificateRecord = async ({
|
||||
selfSignedResult,
|
||||
certificateRequest,
|
||||
profile,
|
||||
originalCert,
|
||||
certificateDAL,
|
||||
tx,
|
||||
isRenewal = false
|
||||
}: {
|
||||
selfSignedResult: Awaited<ReturnType<typeof generateSelfSignedCertificate>>;
|
||||
certificateRequest: {
|
||||
commonName?: string;
|
||||
keyUsages?: CertKeyUsageType[];
|
||||
extendedKeyUsages?: CertExtendedKeyUsageType[];
|
||||
};
|
||||
profile?: { id: string; projectId: string } | null;
|
||||
originalCert?: {
|
||||
id: string;
|
||||
friendlyName?: string | null;
|
||||
commonName?: string | null;
|
||||
projectId: string;
|
||||
};
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "updateById">;
|
||||
tx: Parameters<TCertificateDALFactory["create"]>[1];
|
||||
isRenewal?: boolean;
|
||||
}) => {
|
||||
const subjectCommonName =
|
||||
(selfSignedResult.certificateSubject.common_name as string) ||
|
||||
certificateRequest.commonName ||
|
||||
originalCert?.commonName ||
|
||||
(isRenewal ? "Renewed Self-signed Certificate" : "Self-signed Certificate");
|
||||
|
||||
const altNamesList = selfSignedResult.subjectAlternativeNames.map((san) => san.value).join(",");
|
||||
|
||||
const projectId = originalCert?.projectId || profile?.projectId;
|
||||
if (!projectId) {
|
||||
throw new BadRequestError({ message: "Project ID is required for certificate creation" });
|
||||
}
|
||||
|
||||
const baseRecord = {
|
||||
serialNumber: selfSignedResult.serialNumber,
|
||||
friendlyName: originalCert?.friendlyName || subjectCommonName,
|
||||
commonName: subjectCommonName,
|
||||
altNames: altNamesList,
|
||||
status: CertStatus.ACTIVE,
|
||||
notBefore: selfSignedResult.notBefore,
|
||||
notAfter: selfSignedResult.notAfter,
|
||||
projectId,
|
||||
keyUsages: convertKeyUsageArrayToLegacy(certificateRequest.keyUsages) || [],
|
||||
extendedKeyUsages: convertExtendedKeyUsageArrayToLegacy(certificateRequest.extendedKeyUsages) || [],
|
||||
profileId: profile?.id || null
|
||||
};
|
||||
|
||||
const renewalRecord =
|
||||
isRenewal && originalCert
|
||||
? {
|
||||
renewedFromCertificateId: originalCert.id
|
||||
}
|
||||
: {};
|
||||
|
||||
return certificateDAL.create(
|
||||
{
|
||||
...baseRecord,
|
||||
...renewalRecord
|
||||
},
|
||||
tx
|
||||
);
|
||||
};
|
||||
|
||||
const createEncryptedCertificateData = async ({
|
||||
certificateId,
|
||||
certificate,
|
||||
privateKey,
|
||||
projectId,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL,
|
||||
tx
|
||||
}: {
|
||||
certificateId: string;
|
||||
certificate: Buffer;
|
||||
privateKey: Buffer;
|
||||
projectId: string;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||
kmsService: Pick<TKmsServiceFactory, "encryptWithKmsKey" | "generateKmsKey">;
|
||||
projectDAL: TProjectDALFactory;
|
||||
tx: Parameters<TCertificateBodyDALFactory["create"]>[1];
|
||||
}) => {
|
||||
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||
projectId,
|
||||
projectDAL,
|
||||
kmsService
|
||||
});
|
||||
|
||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({ kmsId: certificateManagerKeyId });
|
||||
|
||||
const encryptedCertificate = await kmsEncryptor({
|
||||
plainText: certificate
|
||||
});
|
||||
|
||||
await certificateBodyDAL.create(
|
||||
{
|
||||
certId: certificateId,
|
||||
encryptedCertificate: encryptedCertificate.cipherTextBlob
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
const encryptedPrivateKey = await kmsEncryptor({
|
||||
plainText: privateKey
|
||||
});
|
||||
|
||||
await certificateSecretDAL.create(
|
||||
{
|
||||
certId: certificateId,
|
||||
encryptedPrivateKey: encryptedPrivateKey.cipherTextBlob
|
||||
},
|
||||
tx
|
||||
);
|
||||
};
|
||||
|
||||
const processSelfSignedCertificate = async ({
|
||||
certificateRequest,
|
||||
template,
|
||||
profile,
|
||||
originalCert,
|
||||
effectiveAlgorithms,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL,
|
||||
tx,
|
||||
isRenewal = false
|
||||
}: {
|
||||
certificateRequest: {
|
||||
commonName?: string;
|
||||
keyUsages?: CertKeyUsageType[];
|
||||
extendedKeyUsages?: CertExtendedKeyUsageType[];
|
||||
validity: { ttl: string };
|
||||
notBefore?: Date;
|
||||
notAfter?: Date;
|
||||
};
|
||||
template?: {
|
||||
subject?: Array<{
|
||||
type: string;
|
||||
allowed?: string[];
|
||||
required?: string[];
|
||||
denied?: string[];
|
||||
}>;
|
||||
sans?: Array<{
|
||||
type: string;
|
||||
allowed?: string[];
|
||||
required?: string[];
|
||||
denied?: string[];
|
||||
}>;
|
||||
} | null;
|
||||
profile?: { id: string; projectId: string } | null;
|
||||
originalCert?: {
|
||||
id: string;
|
||||
friendlyName?: string | null;
|
||||
commonName?: string | null;
|
||||
projectId: string;
|
||||
};
|
||||
effectiveAlgorithms: {
|
||||
signatureAlgorithm: CertSignatureAlgorithm;
|
||||
keyAlgorithm: CertKeyAlgorithm;
|
||||
};
|
||||
certificateDAL: Pick<TCertificateDALFactory, "create" | "updateById">;
|
||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||
kmsService: Pick<TKmsServiceFactory, "encryptWithKmsKey" | "generateKmsKey">;
|
||||
projectDAL: TProjectDALFactory;
|
||||
tx: Parameters<TCertificateDALFactory["create"]>[1];
|
||||
isRenewal?: boolean;
|
||||
}) => {
|
||||
const projectId = originalCert?.projectId || profile?.projectId;
|
||||
if (!projectId) {
|
||||
throw new BadRequestError({ message: "Project ID is required for certificate creation" });
|
||||
}
|
||||
|
||||
const selfSignedResult = await generateSelfSignedCertificate({
|
||||
certificateRequest,
|
||||
template,
|
||||
effectiveSignatureAlgorithm: effectiveAlgorithms.signatureAlgorithm,
|
||||
effectiveKeyAlgorithm: effectiveAlgorithms.keyAlgorithm
|
||||
});
|
||||
|
||||
const certificateData = await createSelfSignedCertificateRecord({
|
||||
selfSignedResult,
|
||||
certificateRequest,
|
||||
profile,
|
||||
originalCert,
|
||||
certificateDAL,
|
||||
tx,
|
||||
isRenewal
|
||||
});
|
||||
|
||||
await certificateDAL.updateById(
|
||||
certificateData.id,
|
||||
{
|
||||
signatureAlgorithm: effectiveAlgorithms.signatureAlgorithm,
|
||||
keyAlgorithm: effectiveAlgorithms.keyAlgorithm
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
await createEncryptedCertificateData({
|
||||
certificateId: certificateData.id,
|
||||
certificate: Buffer.from(selfSignedResult.certificate),
|
||||
privateKey: Buffer.from(selfSignedResult.privateKey),
|
||||
projectId,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL,
|
||||
tx
|
||||
});
|
||||
|
||||
return {
|
||||
selfSignedResult,
|
||||
certificateData
|
||||
};
|
||||
};
|
||||
|
||||
export const certificateV3ServiceFactory = ({
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
certificateAuthorityDAL,
|
||||
certificateProfileDAL,
|
||||
@@ -359,7 +754,9 @@ export const certificateV3ServiceFactory = ({
|
||||
permissionService,
|
||||
certificateSyncDAL,
|
||||
pkiSyncDAL,
|
||||
pkiSyncQueue
|
||||
pkiSyncQueue,
|
||||
kmsService,
|
||||
projectDAL
|
||||
}: TCertificateV3ServiceFactoryDep) => {
|
||||
const issueCertificateFromProfile = async ({
|
||||
profileId,
|
||||
@@ -416,15 +813,6 @@ export const certificateV3ServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found" });
|
||||
}
|
||||
|
||||
validateCaSupport(ca, "direct certificate issuance");
|
||||
|
||||
validateAlgorithmCompatibility(ca, template);
|
||||
|
||||
const effectiveSignatureAlgorithm = certificateRequest.signatureAlgorithm as CertSignatureAlgorithm | undefined;
|
||||
const effectiveKeyAlgorithm = certificateRequest.keyAlgorithm as CertKeyAlgorithm | undefined;
|
||||
|
||||
@@ -440,12 +828,76 @@ export const certificateV3ServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const certificateSubject = buildCertificateSubjectFromTemplate(certificateRequest, template.subject);
|
||||
const certificateSubject = buildCertificateSubjectFromTemplate(certificateRequest, template?.subject);
|
||||
const subjectAlternativeNames = buildSubjectAlternativeNamesFromTemplate(
|
||||
{ subjectAlternativeNames: certificateRequest.altNames },
|
||||
template.sans
|
||||
template?.sans
|
||||
);
|
||||
|
||||
const issuerType = profile?.issuerType || (profile?.caId ? IssuerType.CA : IssuerType.SELF_SIGNED);
|
||||
|
||||
if (issuerType === IssuerType.SELF_SIGNED) {
|
||||
const result = await certificateDAL.transaction(async (tx) => {
|
||||
const effectiveAlgorithms = getEffectiveAlgorithms(effectiveSignatureAlgorithm, effectiveKeyAlgorithm);
|
||||
|
||||
return processSelfSignedCertificate({
|
||||
certificateRequest,
|
||||
template,
|
||||
profile,
|
||||
effectiveAlgorithms,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL,
|
||||
tx
|
||||
});
|
||||
});
|
||||
|
||||
const { selfSignedResult, certificateData } = result;
|
||||
|
||||
const subjectCommonName =
|
||||
(selfSignedResult.certificateSubject.common_name as string) ||
|
||||
certificateRequest.commonName ||
|
||||
"Self-signed Certificate";
|
||||
|
||||
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(
|
||||
profile,
|
||||
certificateRequest.validity.ttl,
|
||||
selfSignedResult.notAfter
|
||||
);
|
||||
|
||||
if (finalRenewBeforeDays !== undefined) {
|
||||
await certificateDAL.updateById(certificateData.id, {
|
||||
renewBeforeDays: finalRenewBeforeDays
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
certificate: selfSignedResult.certificate.toString("utf8"),
|
||||
issuingCaCertificate: "",
|
||||
certificateChain: selfSignedResult.certificate.toString("utf8"),
|
||||
privateKey: selfSignedResult.privateKey.toString("utf8"),
|
||||
serialNumber: selfSignedResult.serialNumber,
|
||||
certificateId: certificateData.id,
|
||||
projectId: profile.projectId,
|
||||
profileName: profile.slug,
|
||||
commonName: subjectCommonName
|
||||
};
|
||||
}
|
||||
|
||||
if (!profile.caId) {
|
||||
throw new NotFoundError({ message: "Certificate Authority ID not found" });
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found" });
|
||||
}
|
||||
|
||||
validateCaSupport(ca, "direct certificate issuance");
|
||||
validateAlgorithmCompatibility(ca, template);
|
||||
|
||||
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber } =
|
||||
await internalCaService.issueCertFromCa({
|
||||
caId: ca.id,
|
||||
@@ -477,10 +929,11 @@ export const certificateV3ServiceFactory = ({
|
||||
new Date(cert.notAfter)
|
||||
);
|
||||
|
||||
await certificateDAL.updateById(cert.id, {
|
||||
profileId,
|
||||
renewBeforeDays: finalRenewBeforeDays
|
||||
});
|
||||
const updateData: { profileId: string; renewBeforeDays?: number } = { profileId };
|
||||
if (finalRenewBeforeDays !== undefined) {
|
||||
updateData.renewBeforeDays = finalRenewBeforeDays;
|
||||
}
|
||||
await certificateDAL.updateById(cert.id, updateData);
|
||||
|
||||
let finalCertificateChain = bufferToString(certificateChain);
|
||||
if (removeRootsFromChain) {
|
||||
@@ -525,6 +978,12 @@ export const certificateV3ServiceFactory = ({
|
||||
enrollmentType
|
||||
);
|
||||
|
||||
if (!profile.caId) {
|
||||
throw new BadRequestError({
|
||||
message: "Self-signed certificates are not supported for CSR signing"
|
||||
});
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found" });
|
||||
@@ -592,10 +1051,11 @@ export const certificateV3ServiceFactory = ({
|
||||
|
||||
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(profile, validity.ttl, new Date(cert.notAfter));
|
||||
|
||||
await certificateDAL.updateById(cert.id, {
|
||||
profileId,
|
||||
renewBeforeDays: finalRenewBeforeDays
|
||||
});
|
||||
const updateData2: { profileId: string; renewBeforeDays?: number } = { profileId };
|
||||
if (finalRenewBeforeDays !== undefined) {
|
||||
updateData2.renewBeforeDays = finalRenewBeforeDays;
|
||||
}
|
||||
await certificateDAL.updateById(cert.id, updateData2);
|
||||
|
||||
const certificateString = extractCertificateFromBuffer(certificate as unknown as Buffer);
|
||||
let certificateChainString = extractCertificateFromBuffer(certificateChain as unknown as Buffer);
|
||||
@@ -663,6 +1123,12 @@ export const certificateV3ServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (!profile.caId) {
|
||||
throw new BadRequestError({
|
||||
message: "Self-signed certificates are not supported for certificate ordering"
|
||||
});
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found" });
|
||||
@@ -725,9 +1191,10 @@ export const certificateV3ServiceFactory = ({
|
||||
throw new NotFoundError({ message: "Certificate not found" });
|
||||
}
|
||||
|
||||
if (!originalCert.profileId) {
|
||||
const isSelfSigned = !originalCert.profileId && !originalCert.caId && originalCert.certificateTemplateId === null;
|
||||
if (!originalCert.profileId && !originalCert.caId && !isSelfSigned) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Only certificates issued from a profile can be renewed"
|
||||
message: "Only certificates issued from a profile or self-signed certificates can be renewed"
|
||||
});
|
||||
}
|
||||
|
||||
@@ -741,15 +1208,18 @@ export const certificateV3ServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const profile = await certificateProfileDAL.findByIdWithConfigs(originalCert.profileId);
|
||||
if (!profile) {
|
||||
throw new NotFoundError({ message: "Certificate profile not found" });
|
||||
}
|
||||
let profile = null;
|
||||
if (originalCert.profileId) {
|
||||
profile = await certificateProfileDAL.findByIdWithConfigs(originalCert.profileId);
|
||||
if (!profile) {
|
||||
throw new NotFoundError({ message: "Certificate profile not found" });
|
||||
}
|
||||
|
||||
if (profile.enrollmentType !== EnrollmentType.API) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Certificate is not eligible for renewal: EST certificates cannot be renewed through this endpoint"
|
||||
});
|
||||
if (profile.enrollmentType !== EnrollmentType.API) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Certificate is not eligible for renewal: EST certificates cannot be renewed through this endpoint"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const certificateSecret = await certificateSecretDAL.findOne({ certId: originalCert.id }, tx);
|
||||
@@ -761,10 +1231,11 @@ export const certificateV3ServiceFactory = ({
|
||||
}
|
||||
|
||||
if (!internal) {
|
||||
const projectId = profile?.projectId || originalCert.projectId;
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId: profile.projectId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.CertificateManager
|
||||
@@ -776,33 +1247,46 @@ export const certificateV3ServiceFactory = ({
|
||||
);
|
||||
}
|
||||
|
||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found" });
|
||||
const issuerType = profile?.issuerType || (originalCert.caId ? IssuerType.CA : IssuerType.SELF_SIGNED);
|
||||
|
||||
let ca;
|
||||
if (issuerType === IssuerType.CA) {
|
||||
const caId = profile?.caId || originalCert.caId;
|
||||
if (!caId) {
|
||||
throw new NotFoundError({ message: "Certificate Authority ID not found" });
|
||||
}
|
||||
|
||||
ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found" });
|
||||
}
|
||||
|
||||
const eligibilityCheck = validateRenewalEligibility(originalCert, ca);
|
||||
if (!eligibilityCheck.isEligible) {
|
||||
await certificateDAL.updateById(originalCert.id, {
|
||||
renewalError: `Certificate is not eligible for renewal: ${eligibilityCheck.errors.join(", ")}`
|
||||
});
|
||||
throw new BadRequestError({
|
||||
message: `Certificate is not eligible for renewal: ${eligibilityCheck.errors.join(", ")}`
|
||||
});
|
||||
}
|
||||
|
||||
validateCaSupport(ca, "direct certificate issuance");
|
||||
}
|
||||
|
||||
const eligibilityCheck = validateRenewalEligibility(originalCert, ca);
|
||||
if (!eligibilityCheck.isEligible) {
|
||||
await certificateDAL.updateById(originalCert.id, {
|
||||
renewalError: `Certificate is not eligible for renewal: ${eligibilityCheck.errors.join(", ")}`
|
||||
});
|
||||
throw new BadRequestError({
|
||||
message: `Certificate is not eligible for renewal: ${eligibilityCheck.errors.join(", ")}`
|
||||
});
|
||||
}
|
||||
const templateId = profile?.certificateTemplateId || originalCert.certificateTemplateId;
|
||||
const template = templateId
|
||||
? await certificateTemplateV2Service.getTemplateV2ById({
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
templateId,
|
||||
internal
|
||||
})
|
||||
: null;
|
||||
|
||||
validateCaSupport(ca, "direct certificate issuance");
|
||||
|
||||
const template = await certificateTemplateV2Service.getTemplateV2ById({
|
||||
actor,
|
||||
actorId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
templateId: profile.certificateTemplateId,
|
||||
internal
|
||||
});
|
||||
|
||||
if (!template) {
|
||||
if (!template && profile) {
|
||||
throw new NotFoundError({ message: "Certificate template not found for this profile" });
|
||||
}
|
||||
|
||||
@@ -857,10 +1341,13 @@ export const certificateV3ServiceFactory = ({
|
||||
keyAlgorithm: originalCert.keyAlgorithm || undefined
|
||||
};
|
||||
|
||||
const validationResult = await certificateTemplateV2Service.validateCertificateRequest(
|
||||
profile.certificateTemplateId,
|
||||
certificateRequest
|
||||
);
|
||||
let validationResult: { isValid: boolean; errors: string[] } = { isValid: true, errors: [] };
|
||||
if (profile?.certificateTemplateId) {
|
||||
validationResult = await certificateTemplateV2Service.validateCertificateRequest(
|
||||
profile.certificateTemplateId,
|
||||
certificateRequest
|
||||
);
|
||||
}
|
||||
|
||||
if (!validationResult.isValid) {
|
||||
await certificateDAL.updateById(originalCert.id, {
|
||||
@@ -872,14 +1359,28 @@ export const certificateV3ServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
validateAlgorithmCompatibility(ca, template);
|
||||
const notBefore = new Date();
|
||||
const notAfter = new Date(Date.now() + parseTtlToDays(ttl) * 24 * 60 * 60 * 1000);
|
||||
|
||||
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(profile, ttl, notAfter);
|
||||
const finalRenewBeforeDays = profile ? calculateFinalRenewBeforeDays(profile, ttl, notAfter) : undefined;
|
||||
|
||||
const { certificate, certificateChain, issuingCaCertificate, serialNumber } =
|
||||
await internalCaService.issueCertFromCa({
|
||||
let certificate: string;
|
||||
let certificateChain: string;
|
||||
let issuingCaCertificate: string;
|
||||
let serialNumber: string;
|
||||
let newCert: TCertificates;
|
||||
|
||||
if (issuerType === IssuerType.CA) {
|
||||
// CA-signed certificate renewal
|
||||
if (!ca) {
|
||||
throw new NotFoundError({ message: "Certificate Authority not found for CA-signed certificate renewal" });
|
||||
}
|
||||
|
||||
validateAlgorithmCompatibility(ca, {
|
||||
algorithms: template?.algorithms
|
||||
} as { algorithms?: { signature?: string[] } });
|
||||
|
||||
const caResult = await internalCaService.issueCertFromCa({
|
||||
caId: ca.id,
|
||||
friendlyName: originalCert.friendlyName || originalCert.commonName || "Renewed Certificate",
|
||||
commonName: originalCert.commonName || "",
|
||||
@@ -900,20 +1401,72 @@ export const certificateV3ServiceFactory = ({
|
||||
tx
|
||||
});
|
||||
|
||||
const newCert = await certificateDAL.findOne({ serialNumber, caId: ca.id }, tx);
|
||||
certificate = caResult.certificate;
|
||||
certificateChain = caResult.certificateChain;
|
||||
issuingCaCertificate = caResult.issuingCaCertificate;
|
||||
serialNumber = caResult.serialNumber;
|
||||
|
||||
const foundCert = await certificateDAL.findOne({ serialNumber, caId: ca.id }, tx);
|
||||
if (!foundCert) {
|
||||
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
|
||||
}
|
||||
newCert = foundCert;
|
||||
} else {
|
||||
// Self-signed certificate renewal
|
||||
const effectiveAlgorithms = getEffectiveAlgorithms(
|
||||
undefined,
|
||||
undefined,
|
||||
originalSignatureAlgorithm,
|
||||
originalKeyAlgorithm
|
||||
);
|
||||
|
||||
const selfSignedRenewalResult = await processSelfSignedCertificate({
|
||||
certificateRequest,
|
||||
template,
|
||||
profile,
|
||||
originalCert,
|
||||
effectiveAlgorithms,
|
||||
certificateDAL,
|
||||
certificateBodyDAL,
|
||||
certificateSecretDAL,
|
||||
kmsService,
|
||||
projectDAL,
|
||||
tx,
|
||||
isRenewal: true
|
||||
});
|
||||
|
||||
certificate = selfSignedRenewalResult.selfSignedResult.certificate.toString("utf8");
|
||||
certificateChain = selfSignedRenewalResult.selfSignedResult.certificate.toString("utf8"); // Self-signed has no chain
|
||||
issuingCaCertificate = ""; // No issuing CA for self-signed
|
||||
serialNumber = selfSignedRenewalResult.selfSignedResult.serialNumber;
|
||||
newCert = selfSignedRenewalResult.certificateData;
|
||||
}
|
||||
|
||||
if (!newCert) {
|
||||
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
|
||||
}
|
||||
|
||||
await certificateDAL.updateById(
|
||||
newCert.id,
|
||||
{
|
||||
profileId: originalCert.profileId,
|
||||
renewBeforeDays: finalRenewBeforeDays,
|
||||
// For self-signed certificates, we already set the renewal data during creation
|
||||
// For CA-signed certificates, we need to set it now
|
||||
if (issuerType === IssuerType.CA) {
|
||||
const renewalUpdateData: {
|
||||
profileId: string | null;
|
||||
renewedFromCertificateId: string;
|
||||
renewBeforeDays?: number;
|
||||
} = {
|
||||
profileId: originalCert.profileId || null,
|
||||
renewedFromCertificateId: originalCert.id
|
||||
},
|
||||
tx
|
||||
);
|
||||
};
|
||||
|
||||
if (finalRenewBeforeDays !== undefined) {
|
||||
renewalUpdateData.renewBeforeDays = finalRenewBeforeDays;
|
||||
}
|
||||
|
||||
await certificateDAL.updateById(newCert.id, renewalUpdateData, tx);
|
||||
} else if (finalRenewBeforeDays !== undefined) {
|
||||
// For self-signed certificates, just update the renewBeforeDays if needed
|
||||
await certificateDAL.updateById(newCert.id, { renewBeforeDays: finalRenewBeforeDays }, tx);
|
||||
}
|
||||
|
||||
await certificateDAL.updateById(
|
||||
originalCert.id,
|
||||
@@ -953,8 +1506,8 @@ export const certificateV3ServiceFactory = ({
|
||||
certificateChain: finalCertificateChain,
|
||||
serialNumber: renewalResult.serialNumber,
|
||||
certificateId: renewalResult.newCert.id,
|
||||
projectId: renewalResult.profile.projectId,
|
||||
profileName: renewalResult.profile.slug,
|
||||
projectId: renewalResult.originalCert.projectId,
|
||||
profileName: renewalResult.profile?.slug || "Self-signed Certificate",
|
||||
commonName: renewalResult.originalCert.commonName || ""
|
||||
};
|
||||
};
|
||||
|
||||
@@ -309,6 +309,14 @@ export const certificateServiceFactory = ({
|
||||
|
||||
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||
|
||||
if (!certBody) {
|
||||
throw new NotFoundError({ message: "Certificate body not found" });
|
||||
}
|
||||
|
||||
if (!certBody.encryptedCertificate) {
|
||||
throw new BadRequestError({ message: "Certificate data not available" });
|
||||
}
|
||||
|
||||
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||
projectId: cert.projectId,
|
||||
projectDAL,
|
||||
@@ -599,6 +607,14 @@ export const certificateServiceFactory = ({
|
||||
|
||||
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||
|
||||
if (!certBody) {
|
||||
throw new NotFoundError({ message: "Certificate body not found" });
|
||||
}
|
||||
|
||||
if (!certBody.encryptedCertificate) {
|
||||
throw new BadRequestError({ message: "Certificate data not available" });
|
||||
}
|
||||
|
||||
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||
projectId: cert.projectId,
|
||||
projectDAL,
|
||||
|
||||
Reference in New Issue
Block a user