mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-10 14:28:35 +00:00
Address PR comments and improvements
This commit is contained in:
@@ -388,6 +388,9 @@ export enum EventType {
|
|||||||
GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE = "get-certificate-profile-latest-active-bundle",
|
GET_CERTIFICATE_PROFILE_LATEST_ACTIVE_BUNDLE = "get-certificate-profile-latest-active-bundle",
|
||||||
UPDATE_CERTIFICATE_RENEWAL_CONFIG = "update-certificate-renewal-config",
|
UPDATE_CERTIFICATE_RENEWAL_CONFIG = "update-certificate-renewal-config",
|
||||||
DISABLE_CERTIFICATE_RENEWAL_CONFIG = "disable-certificate-renewal-config",
|
DISABLE_CERTIFICATE_RENEWAL_CONFIG = "disable-certificate-renewal-config",
|
||||||
|
CREATE_CERTIFICATE_REQUEST = "create-certificate-request",
|
||||||
|
GET_CERTIFICATE_REQUEST = "get-certificate-request",
|
||||||
|
GET_CERTIFICATE_FROM_REQUEST = "get-certificate-from-request",
|
||||||
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
ATTEMPT_CREATE_SLACK_INTEGRATION = "attempt-create-slack-integration",
|
||||||
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
ATTEMPT_REINSTALL_SLACK_INTEGRATION = "attempt-reinstall-slack-integration",
|
||||||
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
GET_PROJECT_SLACK_CONFIG = "get-project-slack-config",
|
||||||
@@ -2846,7 +2849,6 @@ interface OrderCertificateFromProfile {
|
|||||||
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE;
|
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE;
|
||||||
metadata: {
|
metadata: {
|
||||||
certificateProfileId: string;
|
certificateProfileId: string;
|
||||||
orderId: string;
|
|
||||||
profileName: string;
|
profileName: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -4196,6 +4198,31 @@ interface DisableCertificateRenewalConfigEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface CreateCertificateRequestEvent {
|
||||||
|
type: EventType.CREATE_CERTIFICATE_REQUEST;
|
||||||
|
metadata: {
|
||||||
|
certificateRequestId: string;
|
||||||
|
profileId?: string;
|
||||||
|
caId?: string;
|
||||||
|
commonName?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCertificateRequestEvent {
|
||||||
|
type: EventType.GET_CERTIFICATE_REQUEST;
|
||||||
|
metadata: {
|
||||||
|
certificateRequestId: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCertificateFromRequestEvent {
|
||||||
|
type: EventType.GET_CERTIFICATE_FROM_REQUEST;
|
||||||
|
metadata: {
|
||||||
|
certificateRequestId: string;
|
||||||
|
certificateId?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export type Event =
|
export type Event =
|
||||||
| CreateSubOrganizationEvent
|
| CreateSubOrganizationEvent
|
||||||
| UpdateSubOrganizationEvent
|
| UpdateSubOrganizationEvent
|
||||||
@@ -4575,6 +4602,9 @@ export type Event =
|
|||||||
| PamResourceDeleteEvent
|
| PamResourceDeleteEvent
|
||||||
| UpdateCertificateRenewalConfigEvent
|
| UpdateCertificateRenewalConfigEvent
|
||||||
| DisableCertificateRenewalConfigEvent
|
| DisableCertificateRenewalConfigEvent
|
||||||
|
| CreateCertificateRequestEvent
|
||||||
|
| GetCertificateRequestEvent
|
||||||
|
| GetCertificateFromRequestEvent
|
||||||
| AutomatedRenewCertificate
|
| AutomatedRenewCertificate
|
||||||
| AutomatedRenewCertificateFailed
|
| AutomatedRenewCertificateFailed
|
||||||
| UserLoginEvent
|
| UserLoginEvent
|
||||||
|
|||||||
@@ -2259,7 +2259,8 @@ export const registerRoutes = async (
|
|||||||
kmsService,
|
kmsService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
certificateIssuanceQueue
|
certificateIssuanceQueue,
|
||||||
|
certificateRequestService
|
||||||
});
|
});
|
||||||
|
|
||||||
const certificateV3Queue = certificateV3QueueServiceFactory({
|
const certificateV3Queue = certificateV3QueueServiceFactory({
|
||||||
|
|||||||
@@ -665,8 +665,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
|
const response = await server.services.internalCertificateAuthority.issueCertFromCa({
|
||||||
await server.services.internalCertificateAuthority.issueCertFromCa({
|
|
||||||
caId: req.params.caId,
|
caId: req.params.caId,
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -675,6 +674,8 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
|
|||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } = response;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
|
|||||||
@@ -299,7 +299,7 @@ export const registerCertificateProfilesRouter = async (server: FastifyZodProvid
|
|||||||
certificateAuthority: z
|
certificateAuthority: z
|
||||||
.object({
|
.object({
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
projectId: z.string().optional(),
|
projectId: z.string(),
|
||||||
status: z.string(),
|
status: z.string(),
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
isExternal: z.boolean().optional(),
|
isExternal: z.boolean().optional(),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { CertificatesSchema, TCertificateRequests } from "@app/db/schemas";
|
import { CertificatesSchema } from "@app/db/schemas";
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags, CERTIFICATES } from "@app/lib/api-docs";
|
import { ApiDocsTags, CERTIFICATES } from "@app/lib/api-docs";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
@@ -13,7 +13,6 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
|||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import {
|
import {
|
||||||
ACMESANType,
|
ACMESANType,
|
||||||
CertificateOrderStatus,
|
|
||||||
CertKeyAlgorithm,
|
CertKeyAlgorithm,
|
||||||
CertSignatureAlgorithm,
|
CertSignatureAlgorithm,
|
||||||
CrlReason
|
CrlReason
|
||||||
@@ -30,9 +29,21 @@ import { mapEnumsForValidation } from "@app/services/certificate-common/certific
|
|||||||
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||||
import { CertificateRequestStatus } from "@app/services/certificate-request/certificate-request-types";
|
import { CertificateRequestStatus } from "@app/services/certificate-request/certificate-request-types";
|
||||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
import { TCertificateFromProfileResponse } from "@app/services/certificate-v3/certificate-v3-types";
|
||||||
|
|
||||||
import { booleanSchema } from "../sanitizedSchemas";
|
import { booleanSchema } from "../sanitizedSchemas";
|
||||||
|
|
||||||
|
type CertificateServiceResponse = TCertificateFromProfileResponse | Omit<TCertificateFromProfileResponse, "privateKey">;
|
||||||
|
|
||||||
|
const extractCertificateData = (data: CertificateServiceResponse) => ({
|
||||||
|
certificate: data.certificate,
|
||||||
|
issuingCaCertificate: data.issuingCaCertificate,
|
||||||
|
certificateChain: data.certificateChain,
|
||||||
|
privateKey: "privateKey" in data ? data.privateKey : undefined,
|
||||||
|
serialNumber: data.serialNumber,
|
||||||
|
certificateId: data.certificateId
|
||||||
|
});
|
||||||
|
|
||||||
interface CertificateRequestForService {
|
interface CertificateRequestForService {
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
keyUsages?: CertKeyUsageType[];
|
keyUsages?: CertKeyUsageType[];
|
||||||
@@ -50,13 +61,32 @@ interface CertificateRequestForService {
|
|||||||
keyAlgorithm?: string;
|
keyAlgorithm?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const validateTtlAndDateFields = (data: { notBefore?: string; notAfter?: string; ttl?: string }) => {
|
const validateTtlAndDateFields = (data: {
|
||||||
|
attributes?: { notBefore?: string; notAfter?: string; ttl?: string };
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
ttl?: string;
|
||||||
|
}) => {
|
||||||
|
if (data.attributes) {
|
||||||
|
const hasDateFields = data.attributes.notBefore || data.attributes.notAfter;
|
||||||
|
const hasTtl = data.attributes.ttl;
|
||||||
|
return !(hasDateFields && hasTtl);
|
||||||
|
}
|
||||||
const hasDateFields = data.notBefore || data.notAfter;
|
const hasDateFields = data.notBefore || data.notAfter;
|
||||||
const hasTtl = data.ttl;
|
const hasTtl = data.ttl;
|
||||||
return !(hasDateFields && hasTtl);
|
return !(hasDateFields && hasTtl);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateDateOrder = (data: { notBefore?: string; notAfter?: string }) => {
|
const validateDateOrder = (data: {
|
||||||
|
attributes?: { notBefore?: string; notAfter?: string };
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
}) => {
|
||||||
|
if (data.attributes?.notBefore && data.attributes?.notAfter) {
|
||||||
|
const notBefore = new Date(data.attributes.notBefore);
|
||||||
|
const notAfter = new Date(data.attributes.notAfter);
|
||||||
|
return notBefore < notAfter;
|
||||||
|
}
|
||||||
if (data.notBefore && data.notAfter) {
|
if (data.notBefore && data.notAfter) {
|
||||||
const notBefore = new Date(data.notBefore);
|
const notBefore = new Date(data.notBefore);
|
||||||
const notAfter = new Date(data.notAfter);
|
const notAfter = new Date(data.notAfter);
|
||||||
@@ -65,20 +95,6 @@ const validateDateOrder = (data: { notBefore?: string; notAfter?: string }) => {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const validateCertificateRequestFlow = (data: {
|
|
||||||
csr?: string;
|
|
||||||
subjectAlternativeNames?: Array<{ type: ACMESANType; value: string }>;
|
|
||||||
commonName?: string;
|
|
||||||
altNames?: Array<{ type: CertSubjectAlternativeNameType; value: string }>;
|
|
||||||
}) => {
|
|
||||||
const hasCSR = !!data.csr;
|
|
||||||
const hasSANs = !!data.subjectAlternativeNames?.length;
|
|
||||||
const hasStandardFields = !!(data.commonName || data.altNames?.length);
|
|
||||||
|
|
||||||
const flowCount = Number(hasCSR) + Number(hasSANs) + Number(hasStandardFields);
|
|
||||||
return flowCount === 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -92,7 +108,14 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
body: z
|
body: z
|
||||||
.object({
|
.object({
|
||||||
profileId: z.string().uuid(),
|
profileId: z.string().uuid(),
|
||||||
projectId: z.string().uuid(),
|
csr: z
|
||||||
|
.string()
|
||||||
|
.trim()
|
||||||
|
.min(1, "CSR cannot be empty")
|
||||||
|
.max(4096, "CSR cannot exceed 4096 characters")
|
||||||
|
.optional(),
|
||||||
|
attributes: z
|
||||||
|
.object({
|
||||||
commonName: validateTemplateRegexField.optional(),
|
commonName: validateTemplateRegexField.optional(),
|
||||||
keyUsages: z.nativeEnum(CertKeyUsageType).array().optional(),
|
keyUsages: z.nativeEnum(CertKeyUsageType).array().optional(),
|
||||||
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsageType).array().optional(),
|
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsageType).array().optional(),
|
||||||
@@ -104,14 +127,8 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm),
|
signatureAlgorithm: z.nativeEnum(CertSignatureAlgorithm).optional(),
|
||||||
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
|
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).optional(),
|
||||||
csr: z
|
|
||||||
.string()
|
|
||||||
.trim()
|
|
||||||
.min(1, "CSR cannot be empty")
|
|
||||||
.max(4096, "CSR cannot exceed 4096 characters")
|
|
||||||
.optional(),
|
|
||||||
subjectAlternativeNames: z
|
subjectAlternativeNames: z
|
||||||
.array(
|
.array(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -130,7 +147,9 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
.min(1, "TTL cannot be empty")
|
.min(1, "TTL cannot be empty")
|
||||||
.refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
.refine((val) => ms(val) > 0, "TTL must be a positive number"),
|
||||||
notBefore: validateCaDateField.optional(),
|
notBefore: validateCaDateField.optional(),
|
||||||
notAfter: validateCaDateField.optional(),
|
notAfter: validateCaDateField.optional()
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
removeRootsFromChain: booleanSchema.default(false).optional()
|
removeRootsFromChain: booleanSchema.default(false).optional()
|
||||||
})
|
})
|
||||||
.refine(validateTtlAndDateFields, {
|
.refine(validateTtlAndDateFields, {
|
||||||
@@ -139,130 +158,27 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.refine(validateDateOrder, {
|
.refine(validateDateOrder, {
|
||||||
message: "notBefore must be earlier than notAfter"
|
message: "notBefore must be earlier than notAfter"
|
||||||
})
|
|
||||||
.refine(validateCertificateRequestFlow, {
|
|
||||||
message:
|
|
||||||
"Must specify exactly one of: csr (for signing), subjectAlternativeNames (for ACME), or commonName/altNames (for issuance)"
|
|
||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.union([
|
200: z.object({
|
||||||
z.object({
|
certificate: z
|
||||||
|
.object({
|
||||||
certificate: z.string().trim(),
|
certificate: z.string().trim(),
|
||||||
issuingCaCertificate: z.string().trim(),
|
issuingCaCertificate: z.string().trim(),
|
||||||
certificateChain: z.string().trim(),
|
certificateChain: z.string().trim(),
|
||||||
privateKey: z.string().trim().optional(),
|
privateKey: z.string().trim().optional(),
|
||||||
serialNumber: z.string().trim(),
|
serialNumber: z.string().trim(),
|
||||||
certificateId: z.string(),
|
certificateId: z.string()
|
||||||
|
})
|
||||||
|
.nullable()
|
||||||
|
.optional(),
|
||||||
certificateRequestId: z.string()
|
certificateRequestId: z.string()
|
||||||
}),
|
|
||||||
z.object({
|
|
||||||
certificateRequestId: z.string(),
|
|
||||||
status: z.string(),
|
|
||||||
orderId: z.string().optional(),
|
|
||||||
subjectAlternativeNames: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
type: z.nativeEnum(ACMESANType),
|
|
||||||
value: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus)
|
|
||||||
})
|
})
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
authorizations: z
|
|
||||||
.array(
|
|
||||||
z.object({
|
|
||||||
identifier: z.object({
|
|
||||||
type: z.nativeEnum(ACMESANType),
|
|
||||||
value: z.string()
|
|
||||||
}),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
expires: z.string().optional(),
|
|
||||||
challenges: z.array(
|
|
||||||
z.object({
|
|
||||||
type: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
url: z.string(),
|
|
||||||
token: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
finalize: z.string().optional()
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { csr, subjectAlternativeNames, ...requestBody } = req.body;
|
const { csr, attributes, ...requestBody } = req.body;
|
||||||
|
|
||||||
const certificateRequest = await server.services.certificateRequest.createCertificateRequest({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: req.body.projectId,
|
|
||||||
profileId: requestBody.profileId,
|
|
||||||
csr,
|
|
||||||
commonName: requestBody.commonName,
|
|
||||||
altNames: requestBody.altNames ? JSON.stringify(requestBody.altNames) : undefined,
|
|
||||||
keyUsages: requestBody.keyUsages,
|
|
||||||
extendedKeyUsages: requestBody.extendedKeyUsages,
|
|
||||||
notBefore: requestBody.notBefore ? new Date(requestBody.notBefore) : undefined,
|
|
||||||
notAfter: requestBody.notAfter ? new Date(requestBody.notAfter) : undefined,
|
|
||||||
keyAlgorithm: requestBody.keyAlgorithm,
|
|
||||||
signatureAlgorithm: requestBody.signatureAlgorithm,
|
|
||||||
metadata: JSON.stringify({
|
|
||||||
ttl: requestBody.ttl,
|
|
||||||
removeRootsFromChain: requestBody.removeRootsFromChain,
|
|
||||||
subjectAlternativeNames
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (csr) {
|
|
||||||
const extractedCsrData = extractCertificateRequestFromCSR(csr);
|
|
||||||
|
|
||||||
const data = await server.services.certificateV3.signCertificateFromProfile({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
profileId: requestBody.profileId,
|
|
||||||
csr,
|
|
||||||
validity: { ttl: requestBody.ttl },
|
|
||||||
notBefore: requestBody.notBefore ? new Date(requestBody.notBefore) : undefined,
|
|
||||||
notAfter: requestBody.notAfter ? new Date(requestBody.notAfter) : undefined,
|
|
||||||
enrollmentType: EnrollmentType.API,
|
|
||||||
removeRootsFromChain: requestBody.removeRootsFromChain
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.certificateRequest.attachCertificateToRequest({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
certificateId: data.certificateId
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
|
||||||
...req.auditLogInfo,
|
|
||||||
projectId: data.projectId,
|
|
||||||
event: {
|
|
||||||
type: EventType.SIGN_CERTIFICATE_FROM_PROFILE,
|
|
||||||
metadata: {
|
|
||||||
certificateProfileId: requestBody.profileId,
|
|
||||||
certificateId: data.certificateId,
|
|
||||||
profileName: data.profileName,
|
|
||||||
commonName: extractedCsrData.commonName || ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
...data,
|
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const profile = await server.services.certificateProfile.getProfileById({
|
const profile = await server.services.certificateProfile.getProfileById({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -284,10 +200,10 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
useOrderFlow = caType !== CaType.INTERNAL;
|
useOrderFlow = caType !== CaType.INTERNAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subjectAlternativeNames?.length || useOrderFlow) {
|
if (attributes?.subjectAlternativeNames?.length || useOrderFlow) {
|
||||||
let acmeAltNames = subjectAlternativeNames;
|
let acmeAltNames: Array<{ type: ACMESANType; value: string }> | undefined = attributes?.subjectAlternativeNames;
|
||||||
if (useOrderFlow && !subjectAlternativeNames && requestBody.altNames?.length) {
|
if (useOrderFlow && !attributes?.subjectAlternativeNames && attributes?.altNames?.length) {
|
||||||
acmeAltNames = requestBody.altNames.map((alt) => ({
|
acmeAltNames = attributes.altNames.map((alt: { type: CertSubjectAlternativeNameType; value: string }) => ({
|
||||||
type: (alt.type === CertSubjectAlternativeNameType.DNS_NAME
|
type: (alt.type === CertSubjectAlternativeNameType.DNS_NAME
|
||||||
? ACMESANType.DNS
|
? ACMESANType.DNS
|
||||||
: ACMESANType.IP) as ACMESANType,
|
: ACMESANType.IP) as ACMESANType,
|
||||||
@@ -297,14 +213,15 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const certificateOrderObject = {
|
const certificateOrderObject = {
|
||||||
altNames: acmeAltNames || [],
|
altNames: acmeAltNames || [],
|
||||||
validity: { ttl: requestBody.ttl },
|
validity: { ttl: attributes?.ttl || "" },
|
||||||
commonName: requestBody.commonName,
|
commonName: attributes?.commonName,
|
||||||
keyUsages: requestBody.keyUsages,
|
keyUsages: attributes?.keyUsages,
|
||||||
extendedKeyUsages: requestBody.extendedKeyUsages,
|
extendedKeyUsages: attributes?.extendedKeyUsages,
|
||||||
notBefore: requestBody.notBefore ? new Date(requestBody.notBefore) : undefined,
|
notBefore: attributes?.notBefore ? new Date(attributes.notBefore) : undefined,
|
||||||
notAfter: requestBody.notAfter ? new Date(requestBody.notAfter) : undefined,
|
notAfter: attributes?.notAfter ? new Date(attributes.notAfter) : undefined,
|
||||||
signatureAlgorithm: requestBody.signatureAlgorithm,
|
signatureAlgorithm: attributes?.signatureAlgorithm,
|
||||||
keyAlgorithm: requestBody.keyAlgorithm
|
keyAlgorithm: attributes?.keyAlgorithm,
|
||||||
|
csr
|
||||||
};
|
};
|
||||||
|
|
||||||
const data = await server.services.certificateV3.orderCertificateFromProfile({
|
const data = await server.services.certificateV3.orderCertificateFromProfile({
|
||||||
@@ -314,14 +231,7 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
profileId: requestBody.profileId,
|
profileId: requestBody.profileId,
|
||||||
certificateOrder: certificateOrderObject,
|
certificateOrder: certificateOrderObject,
|
||||||
removeRootsFromChain: requestBody.removeRootsFromChain,
|
removeRootsFromChain: requestBody.removeRootsFromChain
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.certificateRequest.updateCertificateRequestStatus({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
status: CertificateRequestStatus.PENDING,
|
|
||||||
errorMessage: undefined
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
@@ -331,27 +241,63 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
||||||
metadata: {
|
metadata: {
|
||||||
certificateProfileId: requestBody.profileId,
|
certificateProfileId: requestBody.profileId,
|
||||||
orderId: data.orderId,
|
|
||||||
profileName: data.profileName
|
profileName: data.profileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificateRequestId: certificateRequest.id,
|
certificate: null,
|
||||||
...data
|
certificateRequestId: data.certificateRequestId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (csr) {
|
||||||
|
const extractedCsrData = extractCertificateRequestFromCSR(csr);
|
||||||
|
|
||||||
|
const data = await server.services.certificateV3.signCertificateFromProfile({
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
profileId: requestBody.profileId,
|
||||||
|
csr,
|
||||||
|
validity: { ttl: attributes?.ttl || "" },
|
||||||
|
notBefore: attributes?.notBefore ? new Date(attributes.notBefore) : undefined,
|
||||||
|
notAfter: attributes?.notAfter ? new Date(attributes.notAfter) : undefined,
|
||||||
|
enrollmentType: EnrollmentType.API,
|
||||||
|
removeRootsFromChain: requestBody.removeRootsFromChain
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: data.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.SIGN_CERTIFICATE_FROM_PROFILE,
|
||||||
|
metadata: {
|
||||||
|
certificateProfileId: requestBody.profileId,
|
||||||
|
certificateId: data.certificateId,
|
||||||
|
profileName: data.profileName,
|
||||||
|
commonName: extractedCsrData.commonName || ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
certificate: extractCertificateData(data),
|
||||||
|
certificateRequestId: data.certificateRequestId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const certificateRequestForService: CertificateRequestForService = {
|
const certificateRequestForService: CertificateRequestForService = {
|
||||||
commonName: requestBody.commonName,
|
commonName: attributes?.commonName,
|
||||||
keyUsages: requestBody.keyUsages,
|
keyUsages: attributes?.keyUsages,
|
||||||
extendedKeyUsages: requestBody.extendedKeyUsages,
|
extendedKeyUsages: attributes?.extendedKeyUsages,
|
||||||
altNames: requestBody.altNames,
|
altNames: attributes?.altNames,
|
||||||
validity: { ttl: requestBody.ttl },
|
validity: { ttl: attributes?.ttl || "" },
|
||||||
notBefore: requestBody.notBefore ? new Date(requestBody.notBefore) : undefined,
|
notBefore: attributes?.notBefore ? new Date(attributes.notBefore) : undefined,
|
||||||
notAfter: requestBody.notAfter ? new Date(requestBody.notAfter) : undefined,
|
notAfter: attributes?.notAfter ? new Date(attributes.notAfter) : undefined,
|
||||||
signatureAlgorithm: requestBody.signatureAlgorithm,
|
signatureAlgorithm: attributes?.signatureAlgorithm,
|
||||||
keyAlgorithm: requestBody.keyAlgorithm
|
keyAlgorithm: attributes?.keyAlgorithm
|
||||||
};
|
};
|
||||||
|
|
||||||
const mappedCertificateRequest = mapEnumsForValidation(certificateRequestForService);
|
const mappedCertificateRequest = mapEnumsForValidation(certificateRequestForService);
|
||||||
@@ -366,11 +312,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
removeRootsFromChain: requestBody.removeRootsFromChain
|
removeRootsFromChain: requestBody.removeRootsFromChain
|
||||||
});
|
});
|
||||||
|
|
||||||
await server.services.certificateRequest.attachCertificateToRequest({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
certificateId: data.certificateId
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
@@ -379,24 +320,15 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
metadata: {
|
metadata: {
|
||||||
certificateProfileId: requestBody.profileId,
|
certificateProfileId: requestBody.profileId,
|
||||||
certificateId: data.certificateId,
|
certificateId: data.certificateId,
|
||||||
commonName: requestBody.commonName || "",
|
commonName: attributes?.commonName || "",
|
||||||
profileName: data.profileName
|
profileName: data.profileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...data,
|
certificate: extractCertificateData(data),
|
||||||
certificateRequestId: certificateRequest.id
|
certificateRequestId: data.certificateRequestId
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
await server.services.certificateRequest.updateCertificateRequestStatus({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
status: CertificateRequestStatus.FAILED,
|
|
||||||
errorMessage: error instanceof Error ? error.message : "Unknown error"
|
|
||||||
});
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
server.route({
|
server.route({
|
||||||
@@ -437,7 +369,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
certificateRequestId: req.params.requestId
|
certificateRequestId: req.params.requestId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.certificate && data.serialNumber) {
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: (req.query as { projectId: string }).projectId,
|
projectId: (req.query as { projectId: string }).projectId,
|
||||||
@@ -450,8 +381,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -539,28 +468,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
removeRootsFromChain: req.body.removeRootsFromChain
|
removeRootsFromChain: req.body.removeRootsFromChain
|
||||||
});
|
});
|
||||||
|
|
||||||
const certificateRequest = await server.services.certificateRequest.createCertificateRequest({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: data.projectId,
|
|
||||||
profileId: req.body.profileId,
|
|
||||||
commonName: req.body.commonName,
|
|
||||||
altNames: req.body.altNames?.map((altName) => `${altName.type}:${altName.value}`).join(","),
|
|
||||||
keyUsages: req.body.keyUsages,
|
|
||||||
extendedKeyUsages: req.body.extendedKeyUsages,
|
|
||||||
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
|
||||||
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
|
|
||||||
keyAlgorithm: req.body.keyAlgorithm,
|
|
||||||
signatureAlgorithm: req.body.signatureAlgorithm
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.certificateRequest.attachCertificateToRequest({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
certificateId: data.certificateId
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
@@ -575,10 +482,7 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return data;
|
||||||
...data,
|
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -644,29 +548,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
|
|
||||||
const certificateRequestData = extractCertificateRequestFromCSR(req.body.csr);
|
const certificateRequestData = extractCertificateRequestFromCSR(req.body.csr);
|
||||||
|
|
||||||
const certificateRequest = await server.services.certificateRequest.createCertificateRequest({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: data.projectId,
|
|
||||||
profileId: req.body.profileId,
|
|
||||||
csr: req.body.csr,
|
|
||||||
commonName: certificateRequestData.commonName,
|
|
||||||
altNames: certificateRequestData.subjectAlternativeNames?.map((san) => `${san.type}:${san.value}`).join(","),
|
|
||||||
keyUsages: certificateRequestData.keyUsages,
|
|
||||||
extendedKeyUsages: certificateRequestData.extendedKeyUsages,
|
|
||||||
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
|
||||||
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
|
|
||||||
keyAlgorithm: certificateRequestData.keyAlgorithm,
|
|
||||||
signatureAlgorithm: certificateRequestData.signatureAlgorithm
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.certificateRequest.attachCertificateToRequest({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
certificateId: data.certificateId
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
@@ -681,10 +562,7 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return data;
|
||||||
...data,
|
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -735,34 +613,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
orderId: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
subjectAlternativeNames: z.array(
|
|
||||||
z.object({
|
|
||||||
type: z.nativeEnum(ACMESANType),
|
|
||||||
value: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
authorizations: z.array(
|
|
||||||
z.object({
|
|
||||||
identifier: z.object({
|
|
||||||
type: z.nativeEnum(ACMESANType),
|
|
||||||
value: z.string()
|
|
||||||
}),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
expires: z.string().optional(),
|
|
||||||
challenges: z.array(
|
|
||||||
z.object({
|
|
||||||
type: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
url: z.string(),
|
|
||||||
token: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
finalize: z.string(),
|
|
||||||
certificate: z.string().optional(),
|
certificate: z.string().optional(),
|
||||||
certificateRequestId: z.string()
|
certificateRequestId: z.string()
|
||||||
})
|
})
|
||||||
@@ -794,23 +644,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
removeRootsFromChain: req.body.removeRootsFromChain
|
removeRootsFromChain: req.body.removeRootsFromChain
|
||||||
});
|
});
|
||||||
|
|
||||||
const certificateRequest = await server.services.certificateRequest.createCertificateRequest({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: data.projectId,
|
|
||||||
profileId: req.body.profileId,
|
|
||||||
commonName: req.body.commonName,
|
|
||||||
altNames: req.body.subjectAlternativeNames?.map((san) => `${san.type}:${san.value}`).join(","),
|
|
||||||
keyUsages: req.body.keyUsages,
|
|
||||||
extendedKeyUsages: req.body.extendedKeyUsages,
|
|
||||||
notBefore: req.body.notBefore ? new Date(req.body.notBefore) : undefined,
|
|
||||||
notAfter: req.body.notAfter ? new Date(req.body.notAfter) : undefined,
|
|
||||||
signatureAlgorithm: req.body.signatureAlgorithm,
|
|
||||||
keyAlgorithm: req.body.keyAlgorithm
|
|
||||||
});
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
@@ -818,16 +651,12 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
||||||
metadata: {
|
metadata: {
|
||||||
certificateProfileId: req.body.profileId,
|
certificateProfileId: req.body.profileId,
|
||||||
orderId: data.orderId,
|
|
||||||
profileName: data.profileName
|
profileName: data.profileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return data;
|
||||||
...data,
|
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -862,9 +691,6 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
let certificateRequest: TCertificateRequests | undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const originalCertificate = await server.services.certificate.getCert({
|
const originalCertificate = await server.services.certificate.getCert({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -876,38 +702,15 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
throw new NotFoundError({ message: "Original certificate not found" });
|
throw new NotFoundError({ message: "Original certificate not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
certificateRequest = await server.services.certificateRequest.createCertificateRequest({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: originalCertificate.cert.projectId,
|
|
||||||
profileId: originalCertificate.cert.profileId || undefined,
|
|
||||||
caId: originalCertificate.cert.caId ?? undefined,
|
|
||||||
metadata: JSON.stringify({
|
|
||||||
operation: "renewal",
|
|
||||||
originalCertificateId: req.params.id,
|
|
||||||
removeRootsFromChain: req.body?.removeRootsFromChain
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await server.services.certificateV3.renewCertificate({
|
const data = await server.services.certificateV3.renewCertificate({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
certificateId: req.params.id,
|
certificateId: req.params.id,
|
||||||
removeRootsFromChain: req.body?.removeRootsFromChain,
|
removeRootsFromChain: req.body?.removeRootsFromChain
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.certificate && data.certificate.trim() !== "") {
|
|
||||||
await server.services.certificateRequest.attachCertificateToRequest({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
certificateId: data.certificateId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
@@ -922,20 +725,7 @@ export const registerCertificateRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return data;
|
||||||
...data,
|
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (certificateRequest) {
|
|
||||||
await server.services.certificateRequest.updateCertificateRequestStatus({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
status: CertificateRequestStatus.FAILED,
|
|
||||||
errorMessage: error instanceof Error ? error.message : "Unknown error during certificate renewal"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -242,8 +242,7 @@ export const registerDeprecatedCertRouter = async (server: FastifyZodProvider) =
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } =
|
const response = await server.services.internalCertificateAuthority.issueCertFromCa({
|
||||||
await server.services.internalCertificateAuthority.issueCertFromCa({
|
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
@@ -251,6 +250,8 @@ export const registerDeprecatedCertRouter = async (server: FastifyZodProvider) =
|
|||||||
...req.body
|
...req.body
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber, ca } = response;
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { TCertificateRequests } from "@app/db/schemas";
|
|
||||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||||
import { ApiDocsTags } from "@app/lib/api-docs";
|
import { ApiDocsTags } from "@app/lib/api-docs";
|
||||||
import { NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
@@ -8,12 +7,7 @@ import { ms } from "@app/lib/ms";
|
|||||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { AuthMode } from "@app/services/auth/auth-type";
|
import { AuthMode } from "@app/services/auth/auth-type";
|
||||||
import {
|
import { ACMESANType, CertKeyAlgorithm, CertSignatureAlgorithm } from "@app/services/certificate/certificate-types";
|
||||||
ACMESANType,
|
|
||||||
CertificateOrderStatus,
|
|
||||||
CertKeyAlgorithm,
|
|
||||||
CertSignatureAlgorithm
|
|
||||||
} from "@app/services/certificate/certificate-types";
|
|
||||||
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
|
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
|
||||||
import {
|
import {
|
||||||
CertExtendedKeyUsageType,
|
CertExtendedKeyUsageType,
|
||||||
@@ -23,7 +17,6 @@ import {
|
|||||||
import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils";
|
import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils";
|
||||||
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
|
import { mapEnumsForValidation } from "@app/services/certificate-common/certificate-utils";
|
||||||
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||||
import { CertificateRequestStatus } from "@app/services/certificate-request/certificate-request-types";
|
|
||||||
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
|
||||||
|
|
||||||
import { booleanSchema } from "../sanitizedSchemas";
|
import { booleanSchema } from "../sanitizedSchemas";
|
||||||
@@ -340,34 +333,6 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
orderId: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
subjectAlternativeNames: z.array(
|
|
||||||
z.object({
|
|
||||||
type: z.nativeEnum(ACMESANType),
|
|
||||||
value: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
authorizations: z.array(
|
|
||||||
z.object({
|
|
||||||
identifier: z.object({
|
|
||||||
type: z.nativeEnum(ACMESANType),
|
|
||||||
value: z.string()
|
|
||||||
}),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
expires: z.string().optional(),
|
|
||||||
challenges: z.array(
|
|
||||||
z.object({
|
|
||||||
type: z.string(),
|
|
||||||
status: z.nativeEnum(CertificateOrderStatus),
|
|
||||||
url: z.string(),
|
|
||||||
token: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
),
|
|
||||||
finalize: z.string(),
|
|
||||||
certificate: z.string().optional(),
|
certificate: z.string().optional(),
|
||||||
certificateRequestId: z.string()
|
certificateRequestId: z.string()
|
||||||
})
|
})
|
||||||
@@ -423,7 +388,6 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
type: EventType.ORDER_CERTIFICATE_FROM_PROFILE,
|
||||||
metadata: {
|
metadata: {
|
||||||
certificateProfileId: req.body.profileId,
|
certificateProfileId: req.body.profileId,
|
||||||
orderId: data.orderId,
|
|
||||||
profileName: data.profileName
|
profileName: data.profileName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -467,9 +431,6 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
},
|
},
|
||||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
let certificateRequest: TCertificateRequests | undefined;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const originalCertificate = await server.services.certificate.getCert({
|
const originalCertificate = await server.services.certificate.getCert({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
@@ -481,38 +442,15 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
throw new NotFoundError({ message: "Original certificate not found" });
|
throw new NotFoundError({ message: "Original certificate not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
certificateRequest = await server.services.certificateRequest.createCertificateRequest({
|
|
||||||
actor: req.permission.type,
|
|
||||||
actorId: req.permission.id,
|
|
||||||
actorAuthMethod: req.permission.authMethod,
|
|
||||||
actorOrgId: req.permission.orgId,
|
|
||||||
projectId: originalCertificate.cert.projectId,
|
|
||||||
profileId: originalCertificate.cert.profileId || undefined,
|
|
||||||
caId: originalCertificate.cert.caId ?? undefined,
|
|
||||||
metadata: JSON.stringify({
|
|
||||||
operation: "renewal",
|
|
||||||
originalCertificateId: req.params.certificateId,
|
|
||||||
removeRootsFromChain: req.body?.removeRootsFromChain
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await server.services.certificateV3.renewCertificate({
|
const data = await server.services.certificateV3.renewCertificate({
|
||||||
actor: req.permission.type,
|
actor: req.permission.type,
|
||||||
actorId: req.permission.id,
|
actorId: req.permission.id,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorOrgId: req.permission.orgId,
|
actorOrgId: req.permission.orgId,
|
||||||
certificateId: req.params.certificateId,
|
certificateId: req.params.certificateId,
|
||||||
removeRootsFromChain: req.body?.removeRootsFromChain,
|
removeRootsFromChain: req.body?.removeRootsFromChain
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (data.certificate && data.certificate.trim() !== "") {
|
|
||||||
await server.services.certificateRequest.attachCertificateToRequest({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
certificateId: data.certificateId
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await server.services.auditLog.createAuditLog({
|
await server.services.auditLog.createAuditLog({
|
||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: data.projectId,
|
projectId: data.projectId,
|
||||||
@@ -527,20 +465,7 @@ export const registerCertificatesRouter = async (server: FastifyZodProvider) =>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return data;
|
||||||
...data,
|
|
||||||
certificateRequestId: certificateRequest.id
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
if (certificateRequest) {
|
|
||||||
await server.services.certificateRequest.updateCertificateRequestStatus({
|
|
||||||
certificateRequestId: certificateRequest.id,
|
|
||||||
status: CertificateRequestStatus.FAILED,
|
|
||||||
errorMessage: error instanceof Error ? error.message : "Unknown error during certificate renewal"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import acme from "acme-client";
|
import acme from "acme-client";
|
||||||
|
|
||||||
import { getConfig } from "@app/lib/config/env";
|
|
||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { QueueJobs, TQueueServiceFactory } from "@app/queue";
|
import { QueueJobs, TQueueServiceFactory } from "@app/queue";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
||||||
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -15,6 +14,7 @@ import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal";
|
|||||||
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
import { TAppConnectionServiceFactory } from "../app-connection/app-connection-service";
|
||||||
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
|
import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal";
|
||||||
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
|
import { CertKeyAlgorithm } from "../certificate-common/certificate-constants";
|
||||||
import { TCertificateRequestServiceFactory } from "../certificate-request/certificate-request-service";
|
import { TCertificateRequestServiceFactory } from "../certificate-request/certificate-request-service";
|
||||||
import { CertificateRequestStatus } from "../certificate-request/certificate-request-types";
|
import { CertificateRequestStatus } from "../certificate-request/certificate-request-types";
|
||||||
import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal";
|
import { TPkiSubscriberDALFactory } from "../pki-subscriber/pki-subscriber-dal";
|
||||||
@@ -41,6 +41,7 @@ export type TIssueCertificateFromProfileJobData = {
|
|||||||
isRenewal?: boolean;
|
isRenewal?: boolean;
|
||||||
originalCertificateId?: string;
|
originalCertificateId?: string;
|
||||||
certificateRequestId?: string;
|
certificateRequestId?: string;
|
||||||
|
csr?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type TCertificateIssuanceQueueFactoryDep = {
|
type TCertificateIssuanceQueueFactoryDep = {
|
||||||
@@ -86,40 +87,6 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
certificateProfileDAL,
|
certificateProfileDAL,
|
||||||
certificateRequestService
|
certificateRequestService
|
||||||
}: TCertificateIssuanceQueueFactoryDep) => {
|
}: TCertificateIssuanceQueueFactoryDep) => {
|
||||||
const validateKeyUsages = (keyUsages: unknown): CertKeyUsage[] => {
|
|
||||||
if (!keyUsages) return [];
|
|
||||||
const validKeyUsages = Object.values(CertKeyUsage);
|
|
||||||
|
|
||||||
if (Array.isArray(keyUsages)) {
|
|
||||||
return keyUsages.filter(
|
|
||||||
(usage): usage is CertKeyUsage => typeof usage === "string" && validKeyUsages.includes(usage as CertKeyUsage)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateExtendedKeyUsages = (extendedKeyUsages: unknown): CertExtendedKeyUsage[] => {
|
|
||||||
if (!extendedKeyUsages) return [];
|
|
||||||
const validExtendedKeyUsages = Object.values(CertExtendedKeyUsage);
|
|
||||||
|
|
||||||
if (Array.isArray(extendedKeyUsages)) {
|
|
||||||
return extendedKeyUsages.filter(
|
|
||||||
(usage): usage is CertExtendedKeyUsage =>
|
|
||||||
typeof usage === "string" && validExtendedKeyUsages.includes(usage as CertExtendedKeyUsage)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const validateKeyAlgorithm = (keyAlgorithm: unknown): CertKeyAlgorithm | undefined => {
|
|
||||||
if (typeof keyAlgorithm !== "string") return undefined;
|
|
||||||
const validKeyAlgorithms = Object.values(CertKeyAlgorithm);
|
|
||||||
return validKeyAlgorithms.includes(keyAlgorithm as CertKeyAlgorithm)
|
|
||||||
? (keyAlgorithm as CertKeyAlgorithm)
|
|
||||||
: undefined;
|
|
||||||
};
|
|
||||||
const acmeFns = AcmeCertificateAuthorityFns({
|
const acmeFns = AcmeCertificateAuthorityFns({
|
||||||
appConnectionDAL,
|
appConnectionDAL,
|
||||||
appConnectionService,
|
appConnectionService,
|
||||||
@@ -168,7 +135,8 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
extendedKeyUsages,
|
extendedKeyUsages,
|
||||||
isRenewal,
|
isRenewal,
|
||||||
originalCertificateId,
|
originalCertificateId,
|
||||||
certificateRequestId
|
certificateRequestId,
|
||||||
|
csr
|
||||||
}: TIssueCertificateFromProfileJobData) => {
|
}: TIssueCertificateFromProfileJobData) => {
|
||||||
const jobData: TIssueCertificateFromProfileJobData = {
|
const jobData: TIssueCertificateFromProfileJobData = {
|
||||||
certificateId,
|
certificateId,
|
||||||
@@ -183,7 +151,8 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
extendedKeyUsages,
|
extendedKeyUsages,
|
||||||
isRenewal,
|
isRenewal,
|
||||||
originalCertificateId,
|
originalCertificateId,
|
||||||
certificateRequestId
|
certificateRequestId,
|
||||||
|
csr
|
||||||
};
|
};
|
||||||
|
|
||||||
await queueService.queuePg(QueueJobs.CaIssueCertificateFromProfile, jobData, {
|
await queueService.queuePg(QueueJobs.CaIssueCertificateFromProfile, jobData, {
|
||||||
@@ -210,7 +179,8 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
extendedKeyUsages,
|
extendedKeyUsages,
|
||||||
isRenewal,
|
isRenewal,
|
||||||
originalCertificateId,
|
originalCertificateId,
|
||||||
certificateRequestId
|
certificateRequestId,
|
||||||
|
csr
|
||||||
} = data;
|
} = data;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -225,32 +195,36 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId);
|
||||||
|
|
||||||
if (ca.externalCa?.type === CaType.ACME) {
|
if (ca.externalCa?.type === CaType.ACME) {
|
||||||
const validatedKeyAlgorithm = validateKeyAlgorithm(keyAlgorithm);
|
let certificateCsr: string;
|
||||||
if (!validatedKeyAlgorithm) {
|
let skLeaf: string = "";
|
||||||
throw new BadRequestError({ message: `Invalid key algorithm: ${keyAlgorithm}` });
|
|
||||||
}
|
if (csr) {
|
||||||
const keyAlg = keyAlgorithmToAlgCfg(validatedKeyAlgorithm);
|
certificateCsr = csr;
|
||||||
|
} else {
|
||||||
|
const keyAlg = keyAlgorithmToAlgCfg(keyAlgorithm as CertKeyAlgorithm);
|
||||||
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(keyAlg, true, ["sign", "verify"]);
|
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(keyAlg, true, ["sign", "verify"]);
|
||||||
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
|
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
|
||||||
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
|
skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
|
||||||
|
|
||||||
const [, certificateCsr] = await acme.crypto.createCsr(
|
const [, generatedCsr] = await acme.crypto.createCsr(
|
||||||
{
|
{
|
||||||
altNames: altNames || [],
|
altNames: altNames || [],
|
||||||
commonName: commonName || ""
|
commonName: commonName || ""
|
||||||
},
|
},
|
||||||
skLeaf
|
skLeaf
|
||||||
);
|
);
|
||||||
|
certificateCsr = generatedCsr.toString();
|
||||||
|
}
|
||||||
|
|
||||||
const acmeResult = await acmeFns.orderCertificateFromProfile({
|
const acmeResult = await acmeFns.orderCertificateFromProfile({
|
||||||
caId,
|
caId,
|
||||||
profileId,
|
profileId,
|
||||||
commonName: commonName || "",
|
commonName: commonName || "",
|
||||||
altNames: altNames || [],
|
altNames: altNames || [],
|
||||||
csr: certificateCsr,
|
csr: Buffer.from(certificateCsr),
|
||||||
csrPrivateKey: skLeaf,
|
csrPrivateKey: skLeaf,
|
||||||
keyUsages: validateKeyUsages(keyUsages),
|
keyUsages: keyUsages as CertKeyUsage[],
|
||||||
extendedKeyUsages: validateExtendedKeyUsages(extendedKeyUsages),
|
extendedKeyUsages: extendedKeyUsages as CertExtendedKeyUsage[],
|
||||||
ttl,
|
ttl,
|
||||||
signatureAlgorithm,
|
signatureAlgorithm,
|
||||||
keyAlgorithm,
|
keyAlgorithm,
|
||||||
@@ -306,24 +280,20 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedKeyAlgorithm = validateKeyAlgorithm(keyAlgorithm);
|
|
||||||
if (!validatedKeyAlgorithm) {
|
|
||||||
throw new BadRequestError({ message: `Invalid key algorithm: ${keyAlgorithm}` });
|
|
||||||
}
|
|
||||||
|
|
||||||
const azureParams = {
|
const azureParams = {
|
||||||
caId,
|
caId,
|
||||||
profileId,
|
profileId,
|
||||||
commonName: commonName || "",
|
commonName: commonName || "",
|
||||||
altNames: altNames || [],
|
altNames: altNames || [],
|
||||||
keyUsages: validateKeyUsages(keyUsages),
|
keyUsages: keyUsages as CertKeyUsage[],
|
||||||
extendedKeyUsages: validateExtendedKeyUsages(extendedKeyUsages),
|
extendedKeyUsages: extendedKeyUsages as CertExtendedKeyUsage[],
|
||||||
validity: { ttl },
|
validity: { ttl },
|
||||||
signatureAlgorithm,
|
signatureAlgorithm,
|
||||||
keyAlgorithm: validatedKeyAlgorithm,
|
keyAlgorithm: keyAlgorithm as CertKeyAlgorithm,
|
||||||
isRenewal,
|
isRenewal,
|
||||||
originalCertificateId,
|
originalCertificateId,
|
||||||
template
|
template,
|
||||||
|
...(csr && { csr })
|
||||||
};
|
};
|
||||||
|
|
||||||
const azureResult = await azureAdCsFns.orderCertificateFromProfile(azureParams);
|
const azureResult = await azureAdCsFns.orderCertificateFromProfile(azureParams);
|
||||||
@@ -383,8 +353,6 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initializeCertificateIssuanceQueue = async () => {
|
const initializeCertificateIssuanceQueue = async () => {
|
||||||
const appCfg = getConfig();
|
|
||||||
|
|
||||||
await queueService.startPg(
|
await queueService.startPg(
|
||||||
QueueJobs.CaIssueCertificateFromProfile,
|
QueueJobs.CaIssueCertificateFromProfile,
|
||||||
async ([job]) => {
|
async ([job]) => {
|
||||||
@@ -392,8 +360,9 @@ export const certificateIssuanceQueueFactory = ({
|
|||||||
await processCertificateIssuanceJobs(data);
|
await processCertificateIssuanceJobs(data);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
workerCount: appCfg.NODE_ENV === "production" ? 3 : 1,
|
workerCount: 2,
|
||||||
batchSize: 1
|
batchSize: 1,
|
||||||
|
pollingIntervalSeconds: 1
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+13
-8
@@ -67,6 +67,7 @@ import {
|
|||||||
TGetCaDTO,
|
TGetCaDTO,
|
||||||
TImportCertToCaDTO,
|
TImportCertToCaDTO,
|
||||||
TIssueCertFromCaDTO,
|
TIssueCertFromCaDTO,
|
||||||
|
TIssueCertFromCaResponse,
|
||||||
TRenewCaCertDTO,
|
TRenewCaCertDTO,
|
||||||
TSignCertFromCaDTO,
|
TSignCertFromCaDTO,
|
||||||
TSignIntermediateDTO,
|
TSignIntermediateDTO,
|
||||||
@@ -1198,7 +1199,7 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
isFromProfile,
|
isFromProfile,
|
||||||
internal = false,
|
internal = false,
|
||||||
tx
|
tx
|
||||||
}: TIssueCertFromCaDTO) => {
|
}: TIssueCertFromCaDTO): Promise<TIssueCertFromCaResponse> => {
|
||||||
let ca: TCertificateAuthorityWithAssociatedCa | undefined;
|
let ca: TCertificateAuthorityWithAssociatedCa | undefined;
|
||||||
let certificateTemplate: TCertificateTemplates | undefined;
|
let certificateTemplate: TCertificateTemplates | undefined;
|
||||||
let collectionId = pkiCollectionId;
|
let collectionId = pkiCollectionId;
|
||||||
@@ -1532,10 +1533,11 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
return cert;
|
return cert;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cert;
|
||||||
if (tx) {
|
if (tx) {
|
||||||
await executeIssueCertOperations(tx);
|
cert = await executeIssueCertOperations(tx);
|
||||||
} else {
|
} else {
|
||||||
await certificateDAL.transaction(executeIssueCertOperations);
|
cert = await certificateDAL.transaction(executeIssueCertOperations);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1544,6 +1546,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
issuingCaCertificate,
|
issuingCaCertificate,
|
||||||
privateKey: skLeaf,
|
privateKey: skLeaf,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
|
certificateId: cert.id,
|
||||||
|
commonName,
|
||||||
ca: expandInternalCa(ca)
|
ca: expandInternalCa(ca)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1905,8 +1909,8 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
plainText: Buffer.from(certificateChainPem)
|
plainText: Buffer.from(certificateChainPem)
|
||||||
});
|
});
|
||||||
|
|
||||||
await certificateDAL.transaction(async (tx) => {
|
const cert = await certificateDAL.transaction(async (tx) => {
|
||||||
const cert = await certificateDAL.create(
|
const newCert = await certificateDAL.create(
|
||||||
{
|
{
|
||||||
caId: (ca as TCertificateAuthorities).id,
|
caId: (ca as TCertificateAuthorities).id,
|
||||||
caCertId: caCert.id,
|
caCertId: caCert.id,
|
||||||
@@ -1929,7 +1933,7 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
await certificateBodyDAL.create(
|
await certificateBodyDAL.create(
|
||||||
{
|
{
|
||||||
certId: cert.id,
|
certId: newCert.id,
|
||||||
encryptedCertificate,
|
encryptedCertificate,
|
||||||
encryptedCertificateChain
|
encryptedCertificateChain
|
||||||
},
|
},
|
||||||
@@ -1940,13 +1944,13 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
await pkiCollectionItemDAL.create(
|
await pkiCollectionItemDAL.create(
|
||||||
{
|
{
|
||||||
pkiCollectionId: collectionId,
|
pkiCollectionId: collectionId,
|
||||||
certId: cert.id
|
certId: newCert.id
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return cert;
|
return newCert;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1954,6 +1958,7 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
certificateChain: certificateChainPem,
|
certificateChain: certificateChainPem,
|
||||||
issuingCaCertificate,
|
issuingCaCertificate,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
|
certificateId: cert.id,
|
||||||
ca: expandInternalCa(ca),
|
ca: expandInternalCa(ca),
|
||||||
commonName: cn
|
commonName: cn
|
||||||
};
|
};
|
||||||
|
|||||||
+17
@@ -248,3 +248,20 @@ export type TIssueCertWithTemplateDTO = {
|
|||||||
keyUsages?: CertKeyUsage[];
|
keyUsages?: CertKeyUsage[];
|
||||||
extendedKeyUsages?: CertExtendedKeyUsage[];
|
extendedKeyUsages?: CertExtendedKeyUsage[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TCaReference = {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
dn: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TIssueCertFromCaResponse = {
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string;
|
||||||
|
issuingCaCertificate: string;
|
||||||
|
privateKey: string;
|
||||||
|
serialNumber: string;
|
||||||
|
certificateId: string;
|
||||||
|
ca: TCaReference;
|
||||||
|
commonName: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export type TCertificateProfileWithConfigs = TCertificateProfile & {
|
|||||||
};
|
};
|
||||||
certificateAuthority?: {
|
certificateAuthority?: {
|
||||||
id: string;
|
id: string;
|
||||||
projectId?: string;
|
projectId: string;
|
||||||
status: string;
|
status: string;
|
||||||
name: string;
|
name: string;
|
||||||
isExternal?: boolean;
|
isExternal?: boolean;
|
||||||
|
|||||||
@@ -1,36 +1,12 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
|
||||||
import { TDbClient } from "@app/db";
|
import { TDbClient } from "@app/db";
|
||||||
import { TableName, TCertificateRequests } from "@app/db/schemas";
|
import { TableName, TCertificateRequests, TCertificates } from "@app/db/schemas";
|
||||||
import { DatabaseError } from "@app/lib/errors";
|
import { DatabaseError } from "@app/lib/errors";
|
||||||
import { ormify } from "@app/lib/knex";
|
import { ormify, selectAllTableCols } from "@app/lib/knex";
|
||||||
|
|
||||||
type TCertificateRequestWithCertificateFlat = TCertificateRequests & {
|
|
||||||
certificateId?: string | null;
|
|
||||||
certificateSerialNumber?: string | null;
|
|
||||||
certificateFriendlyName?: string | null;
|
|
||||||
certificateCommonName?: string | null;
|
|
||||||
certificateAltNames?: string | null;
|
|
||||||
certificateStatus?: string | null;
|
|
||||||
certificateNotBefore?: Date | null;
|
|
||||||
certificateNotAfter?: Date | null;
|
|
||||||
certificateKeyUsages?: string[] | null;
|
|
||||||
certificateExtendedKeyUsages?: string[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TCertificateInfo = {
|
|
||||||
id: string;
|
|
||||||
serialNumber: string;
|
|
||||||
friendlyName: string | null;
|
|
||||||
commonName: string;
|
|
||||||
altNames: string | null;
|
|
||||||
status: string;
|
|
||||||
notBefore: Date;
|
|
||||||
notAfter: Date;
|
|
||||||
keyUsages: string[] | null;
|
|
||||||
extendedKeyUsages: string[] | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TCertificateRequestWithCertificate = TCertificateRequests & {
|
type TCertificateRequestWithCertificate = TCertificateRequests & {
|
||||||
certificate: TCertificateInfo | null;
|
certificate: TCertificates | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateRequestDALFactory = ReturnType<typeof certificateRequestDALFactory>;
|
export type TCertificateRequestDALFactory = ReturnType<typeof certificateRequestDALFactory>;
|
||||||
@@ -40,61 +16,24 @@ export const certificateRequestDALFactory = (db: TDbClient) => {
|
|||||||
|
|
||||||
const findByIdWithCertificate = async (id: string): Promise<TCertificateRequestWithCertificate | null> => {
|
const findByIdWithCertificate = async (id: string): Promise<TCertificateRequestWithCertificate | null> => {
|
||||||
try {
|
try {
|
||||||
const certificateRequest = (await db(TableName.CertificateRequests)
|
const certificateRequest = await certificateRequestOrm.findById(id);
|
||||||
.leftJoin(
|
|
||||||
TableName.Certificate,
|
|
||||||
`${TableName.CertificateRequests}.certificateId`,
|
|
||||||
`${TableName.Certificate}.id`
|
|
||||||
)
|
|
||||||
.where(`${TableName.CertificateRequests}.id`, id)
|
|
||||||
.select(
|
|
||||||
`${TableName.CertificateRequests}.*`,
|
|
||||||
`${TableName.Certificate}.id as certificateId`,
|
|
||||||
`${TableName.Certificate}.serialNumber as certificateSerialNumber`,
|
|
||||||
`${TableName.Certificate}.friendlyName as certificateFriendlyName`,
|
|
||||||
`${TableName.Certificate}.commonName as certificateCommonName`,
|
|
||||||
`${TableName.Certificate}.altNames as certificateAltNames`,
|
|
||||||
`${TableName.Certificate}.status as certificateStatus`,
|
|
||||||
`${TableName.Certificate}.notBefore as certificateNotBefore`,
|
|
||||||
`${TableName.Certificate}.notAfter as certificateNotAfter`,
|
|
||||||
`${TableName.Certificate}.keyUsages as certificateKeyUsages`,
|
|
||||||
`${TableName.Certificate}.extendedKeyUsages as certificateExtendedKeyUsages`
|
|
||||||
)
|
|
||||||
.first()) as TCertificateRequestWithCertificateFlat | undefined;
|
|
||||||
|
|
||||||
if (!certificateRequest) return null;
|
if (!certificateRequest) return null;
|
||||||
|
|
||||||
// Transform the flat result into nested structure
|
if (!certificateRequest.certificateId) {
|
||||||
const {
|
return {
|
||||||
certificateId,
|
...certificateRequest,
|
||||||
certificateSerialNumber,
|
certificate: null
|
||||||
certificateFriendlyName,
|
};
|
||||||
certificateCommonName,
|
}
|
||||||
certificateAltNames,
|
|
||||||
certificateStatus,
|
const certificate = await db(TableName.Certificate)
|
||||||
certificateNotBefore,
|
.where("id", certificateRequest.certificateId)
|
||||||
certificateNotAfter,
|
.select(selectAllTableCols(TableName.Certificate))
|
||||||
certificateKeyUsages,
|
.first();
|
||||||
certificateExtendedKeyUsages,
|
|
||||||
...certificateRequestData
|
|
||||||
} = certificateRequest;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...certificateRequestData,
|
...certificateRequest,
|
||||||
certificate: certificateId
|
certificate: certificate || null
|
||||||
? {
|
|
||||||
id: certificateId,
|
|
||||||
serialNumber: certificateSerialNumber as string,
|
|
||||||
friendlyName: certificateFriendlyName || null,
|
|
||||||
commonName: certificateCommonName as string,
|
|
||||||
altNames: certificateAltNames || null,
|
|
||||||
status: certificateStatus as string,
|
|
||||||
notBefore: certificateNotBefore as Date,
|
|
||||||
notAfter: certificateNotAfter as Date,
|
|
||||||
keyUsages: certificateKeyUsages || null,
|
|
||||||
extendedKeyUsages: certificateExtendedKeyUsages || null
|
|
||||||
}
|
|
||||||
: null
|
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Find certificate request by ID with certificate" });
|
throw new DatabaseError({ error, name: "Find certificate request by ID with certificate" });
|
||||||
@@ -111,24 +50,33 @@ export const certificateRequestDALFactory = (db: TDbClient) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStatus = async (id: string, status: string, errorMessage?: string): Promise<TCertificateRequests> => {
|
const updateStatus = async (
|
||||||
|
id: string,
|
||||||
|
status: string,
|
||||||
|
errorMessage?: string,
|
||||||
|
tx?: Knex
|
||||||
|
): Promise<TCertificateRequests> => {
|
||||||
try {
|
try {
|
||||||
const updateData: Partial<TCertificateRequests> = { status };
|
const updateData: Partial<TCertificateRequests> = { status };
|
||||||
if (errorMessage !== undefined) {
|
if (errorMessage !== undefined) {
|
||||||
updateData.errorMessage = errorMessage;
|
updateData.errorMessage = errorMessage;
|
||||||
}
|
}
|
||||||
return await certificateRequestOrm.updateById(id, updateData);
|
return await certificateRequestOrm.updateById(id, updateData, tx);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Update certificate request status" });
|
throw new DatabaseError({ error, name: "Update certificate request status" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const attachCertificate = async (id: string, certificateId: string): Promise<TCertificateRequests> => {
|
const attachCertificate = async (id: string, certificateId: string, tx?: Knex): Promise<TCertificateRequests> => {
|
||||||
try {
|
try {
|
||||||
return await certificateRequestOrm.updateById(id, {
|
return await certificateRequestOrm.updateById(
|
||||||
|
id,
|
||||||
|
{
|
||||||
certificateId,
|
certificateId,
|
||||||
status: "issued"
|
status: "issued"
|
||||||
});
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new DatabaseError({ error, name: "Attach certificate to request" });
|
throw new DatabaseError({ error, name: "Attach certificate to request" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
ProjectPermissionSet,
|
ProjectPermissionSet,
|
||||||
ProjectPermissionSub
|
ProjectPermissionSub
|
||||||
} from "@app/ee/services/permission/project-permission";
|
} from "@app/ee/services/permission/project-permission";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { NotFoundError } from "@app/lib/errors";
|
||||||
import { ActorType, AuthMethod } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod } from "@app/services/auth/auth-type";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
import { TCertificateServiceFactory } from "@app/services/certificate/certificate-service";
|
||||||
@@ -202,7 +202,7 @@ describe("CertificateRequestService", () => {
|
|||||||
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
|
(mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission);
|
||||||
(mockCertificateRequestDAL.findById as any).mockResolvedValue(mockRequest);
|
(mockCertificateRequestDAL.findById as any).mockResolvedValue(mockRequest);
|
||||||
|
|
||||||
await expect(service.getCertificateRequest(mockGetData)).rejects.toThrow(BadRequestError);
|
await expect(service.getCertificateRequest(mockGetData)).rejects.toThrow(NotFoundError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -257,14 +257,14 @@ describe("CertificateRequestService", () => {
|
|||||||
"550e8400-e29b-41d4-a716-446655440005"
|
"550e8400-e29b-41d4-a716-446655440005"
|
||||||
);
|
);
|
||||||
expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({
|
expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({
|
||||||
serialNumber: "123456",
|
id: "550e8400-e29b-41d4-a716-446655440006",
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
||||||
actorAuthMethod: AuthMethod.EMAIL,
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
||||||
});
|
});
|
||||||
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
|
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
|
||||||
serialNumber: "123456",
|
id: "550e8400-e29b-41d4-a716-446655440006",
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
||||||
actorAuthMethod: AuthMethod.EMAIL,
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
@@ -354,14 +354,14 @@ describe("CertificateRequestService", () => {
|
|||||||
"550e8400-e29b-41d4-a716-446655440005"
|
"550e8400-e29b-41d4-a716-446655440005"
|
||||||
);
|
);
|
||||||
expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({
|
expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({
|
||||||
serialNumber: "123456",
|
id: "550e8400-e29b-41d4-a716-446655440008",
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
||||||
actorAuthMethod: AuthMethod.EMAIL,
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
actorOrgId: "550e8400-e29b-41d4-a716-446655440002"
|
||||||
});
|
});
|
||||||
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
|
expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({
|
||||||
serialNumber: "123456",
|
id: "550e8400-e29b-41d4-a716-446655440008",
|
||||||
actor: ActorType.USER,
|
actor: ActorType.USER,
|
||||||
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
actorId: "550e8400-e29b-41d4-a716-446655440001",
|
||||||
actorAuthMethod: AuthMethod.EMAIL,
|
actorAuthMethod: AuthMethod.EMAIL,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { Knex } from "knex";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import { ActionProjectType } from "@app/db/schemas";
|
import { ActionProjectType } from "@app/db/schemas";
|
||||||
@@ -30,13 +31,12 @@ type TCertificateRequestServiceFactoryDep = {
|
|||||||
|
|
||||||
export type TCertificateRequestServiceFactory = ReturnType<typeof certificateRequestServiceFactory>;
|
export type TCertificateRequestServiceFactory = ReturnType<typeof certificateRequestServiceFactory>;
|
||||||
|
|
||||||
// Input validation schemas
|
|
||||||
const certificateRequestDataSchema = z
|
const certificateRequestDataSchema = z
|
||||||
.object({
|
.object({
|
||||||
profileId: z.string().uuid().optional(),
|
profileId: z.string().uuid().optional(),
|
||||||
caId: z.string().uuid().optional(),
|
caId: z.string().uuid().optional(),
|
||||||
csr: z.string().min(1).optional(),
|
csr: z.string().min(1).optional(),
|
||||||
commonName: z.string().min(1).max(255).optional(),
|
commonName: z.string().max(255).optional(),
|
||||||
altNames: z.string().max(1000).optional(),
|
altNames: z.string().max(1000).optional(),
|
||||||
keyUsages: z.array(z.string()).max(20).optional(),
|
keyUsages: z.array(z.string()).max(20).optional(),
|
||||||
extendedKeyUsages: z.array(z.string()).max(20).optional(),
|
extendedKeyUsages: z.array(z.string()).max(20).optional(),
|
||||||
@@ -93,8 +93,9 @@ export const certificateRequestServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
projectId,
|
projectId,
|
||||||
|
tx,
|
||||||
...requestData
|
...requestData
|
||||||
}: TCreateCertificateRequestDTO) => {
|
}: TCreateCertificateRequestDTO & { tx?: Knex }) => {
|
||||||
const { permission } = await permissionService.getProjectPermission({
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
@@ -112,7 +113,16 @@ export const certificateRequestServiceFactory = ({
|
|||||||
// Validate input data before creating the request
|
// Validate input data before creating the request
|
||||||
const validatedData = validateCertificateRequestData(requestData);
|
const validatedData = validateCertificateRequestData(requestData);
|
||||||
|
|
||||||
const certificateRequest = await certificateRequestDAL.create({
|
const certificateRequest = tx
|
||||||
|
? await certificateRequestDAL.create(
|
||||||
|
{
|
||||||
|
status: CertificateRequestStatus.PENDING,
|
||||||
|
projectId,
|
||||||
|
...validatedData
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
)
|
||||||
|
: await certificateRequestDAL.create({
|
||||||
status: CertificateRequestStatus.PENDING,
|
status: CertificateRequestStatus.PENDING,
|
||||||
projectId,
|
projectId,
|
||||||
...validatedData
|
...validatedData
|
||||||
@@ -149,7 +159,7 @@ export const certificateRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (certificateRequest.projectId !== projectId) {
|
if (certificateRequest.projectId !== projectId) {
|
||||||
throw new BadRequestError({ message: "Certificate request does not belong to this project" });
|
throw new NotFoundError({ message: "Certificate request not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return certificateRequest;
|
return certificateRequest;
|
||||||
@@ -183,7 +193,7 @@ export const certificateRequestServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (certificateRequest.projectId !== projectId) {
|
if (certificateRequest.projectId !== projectId) {
|
||||||
throw new BadRequestError({ message: "Certificate request does not belong to this project" });
|
throw new NotFoundError({ message: "Certificate request not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no certificate is attached, return basic info
|
// If no certificate is attached, return basic info
|
||||||
@@ -201,7 +211,7 @@ export const certificateRequestServiceFactory = ({
|
|||||||
|
|
||||||
// Get certificate body (PEM data)
|
// Get certificate body (PEM data)
|
||||||
const certBody = await certificateService.getCertBody({
|
const certBody = await certificateService.getCertBody({
|
||||||
serialNumber: certificateRequest.certificate.serialNumber,
|
id: certificateRequest.certificate.id,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
@@ -212,7 +222,7 @@ export const certificateRequestServiceFactory = ({
|
|||||||
let privateKey: string | null = null;
|
let privateKey: string | null = null;
|
||||||
try {
|
try {
|
||||||
const certPrivateKey = await certificateService.getCertPrivateKey({
|
const certPrivateKey = await certificateService.getCertPrivateKey({
|
||||||
serialNumber: certificateRequest.certificate.serialNumber,
|
id: certificateRequest.certificate.id,
|
||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { TPkiAcmeAccountDALFactory } from "@app/ee/services/pki-acme/pki-acme-ac
|
|||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||||
import { ACMESANType, CertificateOrderStatus, CertStatus } from "@app/services/certificate/certificate-types";
|
import { ACMESANType, CertStatus } from "@app/services/certificate/certificate-types";
|
||||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums";
|
import { CaStatus } from "@app/services/certificate-authority/certificate-authority-enums";
|
||||||
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
||||||
@@ -198,6 +198,9 @@ describe("CertificateV3Service", () => {
|
|||||||
} as any,
|
} as any,
|
||||||
certificateIssuanceQueue: {
|
certificateIssuanceQueue: {
|
||||||
queueCertificateIssuance: vi.fn().mockResolvedValue(undefined)
|
queueCertificateIssuance: vi.fn().mockResolvedValue(undefined)
|
||||||
|
},
|
||||||
|
certificateRequestService: {
|
||||||
|
createCertificateRequest: vi.fn().mockResolvedValue({ id: "cert-req-123" })
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -287,6 +290,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "issuing-ca",
|
issuingCaCertificate: "issuing-ca",
|
||||||
privateKey: "key",
|
privateKey: "key",
|
||||||
serialNumber: "123456",
|
serialNumber: "123456",
|
||||||
|
certificateId: "cert-1",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: {
|
ca: {
|
||||||
id: "ca-123",
|
id: "ca-123",
|
||||||
projectId: "project-123",
|
projectId: "project-123",
|
||||||
@@ -346,8 +351,13 @@ describe("CertificateV3Service", () => {
|
|||||||
vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate);
|
vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate);
|
||||||
vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResult as any);
|
vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResult as any);
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord);
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord);
|
||||||
|
vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCertRecord);
|
||||||
vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord);
|
vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord);
|
||||||
|
|
||||||
|
vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
const result = await service.issueCertificateFromProfile({
|
const result = await service.issueCertificateFromProfile({
|
||||||
profileId,
|
profileId,
|
||||||
certificateRequest: mockCertificateRequest,
|
certificateRequest: mockCertificateRequest,
|
||||||
@@ -481,6 +491,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "issuing-ca",
|
issuingCaCertificate: "issuing-ca",
|
||||||
privateKey: "key",
|
privateKey: "key",
|
||||||
serialNumber: "123456",
|
serialNumber: "123456",
|
||||||
|
certificateId: "cert-1",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: {
|
ca: {
|
||||||
id: "ca-123",
|
id: "ca-123",
|
||||||
projectId: "project-123",
|
projectId: "project-123",
|
||||||
@@ -523,8 +535,34 @@ describe("CertificateV3Service", () => {
|
|||||||
vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate);
|
vi.mocked(mockCertificateTemplateV2Service.getTemplateV2ById).mockResolvedValue(mockTemplate);
|
||||||
vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResultWithCa as any);
|
vi.mocked(mockInternalCaService.issueCertFromCa).mockResolvedValue(mockCertificateResultWithCa as any);
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord);
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord);
|
||||||
|
vi.mocked(mockCertificateDAL.findById).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(mockCertRecord);
|
vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord);
|
||||||
|
|
||||||
|
vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
await service.issueCertificateFromProfile({
|
await service.issueCertificateFromProfile({
|
||||||
profileId,
|
profileId,
|
||||||
certificateRequest: camelCaseRequest,
|
certificateRequest: camelCaseRequest,
|
||||||
@@ -749,8 +787,13 @@ describe("CertificateV3Service", () => {
|
|||||||
});
|
});
|
||||||
vi.mocked(mockInternalCaService.signCertFromCa).mockResolvedValue(mockSignResult as any);
|
vi.mocked(mockInternalCaService.signCertFromCa).mockResolvedValue(mockSignResult as any);
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord);
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue(mockCertRecord);
|
||||||
|
vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockCertRecord);
|
||||||
vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord);
|
vi.mocked(mockCertificateDAL.updateById).mockResolvedValue(mockCertRecord);
|
||||||
|
|
||||||
|
vi.mocked(mockCertificateDAL.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
const result = await service.signCertificateFromProfile({
|
const result = await service.signCertificateFromProfile({
|
||||||
profileId,
|
profileId,
|
||||||
csr: mockCSR,
|
csr: mockCSR,
|
||||||
@@ -819,168 +862,6 @@ describe("CertificateV3Service", () => {
|
|||||||
keyAlgorithm: "RSA_2048"
|
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,
|
|
||||||
issuerType: IssuerType.CA,
|
|
||||||
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 () => {
|
it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => {
|
||||||
const profileId = "profile-123";
|
const profileId = "profile-123";
|
||||||
const mockProfile = {
|
const mockProfile = {
|
||||||
@@ -1117,6 +998,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "ca-cert",
|
issuingCaCertificate: "ca-cert",
|
||||||
privateKey: "key",
|
privateKey: "key",
|
||||||
serialNumber: "123456",
|
serialNumber: "123456",
|
||||||
|
certificateId: "cert-1",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: rsaCa as any
|
ca: rsaCa as any
|
||||||
});
|
});
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
||||||
@@ -1163,6 +1046,31 @@ describe("CertificateV3Service", () => {
|
|||||||
pkiSubscriberId: null,
|
pkiSubscriberId: null,
|
||||||
profileId: null
|
profileId: null
|
||||||
});
|
});
|
||||||
|
vi.mocked(mockCertificateDAL.findById).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.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
// Should not throw - RSA CA is compatible with RSA signature algorithms
|
// Should not throw - RSA CA is compatible with RSA signature algorithms
|
||||||
await expect(
|
await expect(
|
||||||
@@ -1249,6 +1157,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "ca-cert",
|
issuingCaCertificate: "ca-cert",
|
||||||
privateKey: "key",
|
privateKey: "key",
|
||||||
serialNumber: "123456",
|
serialNumber: "123456",
|
||||||
|
certificateId: "cert-1",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: ecCa as any
|
ca: ecCa as any
|
||||||
});
|
});
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
||||||
@@ -1295,6 +1205,31 @@ describe("CertificateV3Service", () => {
|
|||||||
pkiSubscriberId: null,
|
pkiSubscriberId: null,
|
||||||
profileId: null
|
profileId: null
|
||||||
});
|
});
|
||||||
|
vi.mocked(mockCertificateDAL.findById).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.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
// Should not throw - EC CA is compatible with ECDSA signature algorithms
|
// Should not throw - EC CA is compatible with ECDSA signature algorithms
|
||||||
await expect(
|
await expect(
|
||||||
@@ -1381,6 +1316,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "ca-cert",
|
issuingCaCertificate: "ca-cert",
|
||||||
privateKey: "key",
|
privateKey: "key",
|
||||||
serialNumber: "123456",
|
serialNumber: "123456",
|
||||||
|
certificateId: "cert-1",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: rsa8192Ca as any
|
ca: rsa8192Ca as any
|
||||||
});
|
});
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
||||||
@@ -1427,6 +1364,31 @@ describe("CertificateV3Service", () => {
|
|||||||
pkiSubscriberId: null,
|
pkiSubscriberId: null,
|
||||||
profileId: null
|
profileId: null
|
||||||
});
|
});
|
||||||
|
vi.mocked(mockCertificateDAL.findById).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.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
// Should not throw - dynamic check supports new RSA key sizes
|
// Should not throw - dynamic check supports new RSA key sizes
|
||||||
await expect(
|
await expect(
|
||||||
@@ -1513,6 +1475,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "ca-cert",
|
issuingCaCertificate: "ca-cert",
|
||||||
privateKey: "key",
|
privateKey: "key",
|
||||||
serialNumber: "123456",
|
serialNumber: "123456",
|
||||||
|
certificateId: "cert-1",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: newEcCa as any
|
ca: newEcCa as any
|
||||||
});
|
});
|
||||||
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
vi.mocked(mockCertificateDAL.findOne).mockResolvedValue({
|
||||||
@@ -1559,6 +1523,31 @@ describe("CertificateV3Service", () => {
|
|||||||
pkiSubscriberId: null,
|
pkiSubscriberId: null,
|
||||||
profileId: null
|
profileId: null
|
||||||
});
|
});
|
||||||
|
vi.mocked(mockCertificateDAL.findById).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.transaction).mockImplementation(async (callback: (tx: any) => Promise<unknown>) => {
|
||||||
|
return callback(undefined as any);
|
||||||
|
});
|
||||||
|
|
||||||
// Should not throw - dynamic check supports new EC curves
|
// Should not throw - dynamic check supports new EC curves
|
||||||
await expect(
|
await expect(
|
||||||
@@ -1692,8 +1681,9 @@ describe("CertificateV3Service", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should successfully renew eligible certificate", async () => {
|
it("should successfully renew eligible certificate", async () => {
|
||||||
// Mock the initial findById call
|
vi.mocked(mockCertificateDAL.findById)
|
||||||
vi.mocked(mockCertificateDAL.findById).mockResolvedValue(mockOriginalCert);
|
.mockResolvedValueOnce(mockOriginalCert)
|
||||||
|
.mockResolvedValueOnce({ ...mockOriginalCert, id: "cert-456", serialNumber: "789012" });
|
||||||
vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any);
|
vi.mocked(mockCertificateSecretDAL.findOne).mockResolvedValue({ id: "secret-123", certId: "cert-123" } as any);
|
||||||
vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile);
|
vi.mocked(mockCertificateProfileDAL.findByIdWithConfigs).mockResolvedValue(mockProfile);
|
||||||
vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA);
|
vi.mocked(mockCertificateAuthorityDAL.findByIdWithAssociatedCa).mockResolvedValue(mockCA);
|
||||||
@@ -1709,6 +1699,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "issuing-ca",
|
issuingCaCertificate: "issuing-ca",
|
||||||
privateKey: "private-key",
|
privateKey: "private-key",
|
||||||
serialNumber: "789012",
|
serialNumber: "789012",
|
||||||
|
certificateId: "cert-456",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: mockCA
|
ca: mockCA
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -2026,6 +2018,8 @@ describe("CertificateV3Service", () => {
|
|||||||
issuingCaCertificate: "issuing-ca",
|
issuingCaCertificate: "issuing-ca",
|
||||||
privateKey: "private-key",
|
privateKey: "private-key",
|
||||||
serialNumber: "789012",
|
serialNumber: "789012",
|
||||||
|
certificateId: "cert-456",
|
||||||
|
commonName: "test.example.com",
|
||||||
ca: mockCA
|
ca: mockCA
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import { TCertificateDALFactory } from "@app/services/certificate/certificate-da
|
|||||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||||
import {
|
import {
|
||||||
CertExtendedKeyUsage,
|
CertExtendedKeyUsage,
|
||||||
CertificateOrderStatus,
|
|
||||||
CertKeyAlgorithm,
|
CertKeyAlgorithm,
|
||||||
CertKeyType,
|
CertKeyType,
|
||||||
CertKeyUsage,
|
CertKeyUsage,
|
||||||
@@ -40,11 +39,7 @@ import {
|
|||||||
} from "@app/services/certificate-authority/certificate-authority-fns";
|
} from "@app/services/certificate-authority/certificate-authority-fns";
|
||||||
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
|
||||||
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
|
||||||
import {
|
import { EnrollmentType, IssuerType } from "@app/services/certificate-profile/certificate-profile-types";
|
||||||
EnrollmentType,
|
|
||||||
IssuerType,
|
|
||||||
TCertificateProfileWithConfigs
|
|
||||||
} from "@app/services/certificate-profile/certificate-profile-types";
|
|
||||||
import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service";
|
import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -71,7 +66,9 @@ import {
|
|||||||
normalizeDateForApi,
|
normalizeDateForApi,
|
||||||
removeRootCaFromChain
|
removeRootCaFromChain
|
||||||
} from "../certificate-common/certificate-utils";
|
} from "../certificate-common/certificate-utils";
|
||||||
|
import { TCertificateRequestServiceFactory } from "../certificate-request/certificate-request-service";
|
||||||
import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal";
|
import { TCertificateSyncDALFactory } from "../certificate-sync/certificate-sync-dal";
|
||||||
|
import { TCertificateRequest } from "../certificate-template-v2/certificate-template-v2-types";
|
||||||
import { TPkiSyncDALFactory } from "../pki-sync/pki-sync-dal";
|
import { TPkiSyncDALFactory } from "../pki-sync/pki-sync-dal";
|
||||||
import { TPkiSyncQueueFactory } from "../pki-sync/pki-sync-queue";
|
import { TPkiSyncQueueFactory } from "../pki-sync/pki-sync-queue";
|
||||||
import { addRenewedCertificateToSyncs, triggerAutoSyncForCertificate } from "../pki-sync/pki-sync-utils";
|
import { addRenewedCertificateToSyncs, triggerAutoSyncForCertificate } from "../pki-sync/pki-sync-utils";
|
||||||
@@ -122,6 +119,7 @@ type TCertificateV3ServiceFactoryDep = {
|
|||||||
import("../certificate-authority/certificate-issuance-queue").TCertificateIssuanceQueueFactory,
|
import("../certificate-authority/certificate-issuance-queue").TCertificateIssuanceQueueFactory,
|
||||||
"queueCertificateIssuance"
|
"queueCertificateIssuance"
|
||||||
>;
|
>;
|
||||||
|
certificateRequestService: Pick<TCertificateRequestServiceFactory, "createCertificateRequest">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateV3ServiceFactory = ReturnType<typeof certificateV3ServiceFactory>;
|
export type TCertificateV3ServiceFactory = ReturnType<typeof certificateV3ServiceFactory>;
|
||||||
@@ -861,7 +859,8 @@ export const certificateV3ServiceFactory = ({
|
|||||||
pkiSyncQueue,
|
pkiSyncQueue,
|
||||||
kmsService,
|
kmsService,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
certificateIssuanceQueue
|
certificateIssuanceQueue,
|
||||||
|
certificateRequestService
|
||||||
}: TCertificateV3ServiceFactoryDep) => {
|
}: TCertificateV3ServiceFactoryDep) => {
|
||||||
const issueCertificateFromProfile = async ({
|
const issueCertificateFromProfile = async ({
|
||||||
profileId,
|
profileId,
|
||||||
@@ -945,7 +944,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
const result = await certificateDAL.transaction(async (tx) => {
|
const result = await certificateDAL.transaction(async (tx) => {
|
||||||
const effectiveAlgorithms = getEffectiveAlgorithms(effectiveSignatureAlgorithm, effectiveKeyAlgorithm);
|
const effectiveAlgorithms = getEffectiveAlgorithms(effectiveSignatureAlgorithm, effectiveKeyAlgorithm);
|
||||||
|
|
||||||
return processSelfSignedCertificate({
|
const selfSignedResult = await processSelfSignedCertificate({
|
||||||
certificateRequest,
|
certificateRequest,
|
||||||
template,
|
template,
|
||||||
profile,
|
profile,
|
||||||
@@ -957,9 +956,29 @@ export const certificateV3ServiceFactory = ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
tx
|
tx
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const certRequestResult = await certificateRequestService.createCertificateRequest({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId: profile.projectId,
|
||||||
|
tx,
|
||||||
|
profileId: profile.id,
|
||||||
|
commonName: certificateRequest.commonName,
|
||||||
|
altNames: certificateRequest.altNames?.map((san) => san.value).join(","),
|
||||||
|
keyUsages: convertKeyUsageArrayToLegacy(certificateRequest.keyUsages),
|
||||||
|
extendedKeyUsages: convertExtendedKeyUsageArrayToLegacy(certificateRequest.extendedKeyUsages),
|
||||||
|
notBefore: certificateRequest.notBefore,
|
||||||
|
notAfter: certificateRequest.notAfter,
|
||||||
|
keyAlgorithm: effectiveKeyAlgorithm,
|
||||||
|
signatureAlgorithm: effectiveSignatureAlgorithm
|
||||||
});
|
});
|
||||||
|
|
||||||
const { selfSignedResult, certificateData } = result;
|
return { ...selfSignedResult, certificateRequestId: certRequestResult.id };
|
||||||
|
});
|
||||||
|
|
||||||
|
const { selfSignedResult, certificateData, certificateRequestId } = result;
|
||||||
|
|
||||||
const subjectCommonName =
|
const subjectCommonName =
|
||||||
(selfSignedResult.certificateSubject.common_name as string) ||
|
(selfSignedResult.certificateSubject.common_name as string) ||
|
||||||
@@ -985,6 +1004,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
privateKey: selfSignedResult.privateKey.toString("utf8"),
|
privateKey: selfSignedResult.privateKey.toString("utf8"),
|
||||||
serialNumber: selfSignedResult.serialNumber,
|
serialNumber: selfSignedResult.serialNumber,
|
||||||
certificateId: certificateData.id,
|
certificateId: certificateData.id,
|
||||||
|
certificateRequestId,
|
||||||
projectId: profile.projectId,
|
projectId: profile.projectId,
|
||||||
profileName: profile.slug,
|
profileName: profile.slug,
|
||||||
commonName: subjectCommonName
|
commonName: subjectCommonName
|
||||||
@@ -1003,8 +1023,16 @@ export const certificateV3ServiceFactory = ({
|
|||||||
validateCaSupport(ca, "direct certificate issuance");
|
validateCaSupport(ca, "direct certificate issuance");
|
||||||
validateAlgorithmCompatibility(ca, template);
|
validateAlgorithmCompatibility(ca, template);
|
||||||
|
|
||||||
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber } =
|
const {
|
||||||
await internalCaService.issueCertFromCa({
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
issuingCaCertificate,
|
||||||
|
privateKey,
|
||||||
|
serialNumber,
|
||||||
|
cert,
|
||||||
|
certificateRequestId
|
||||||
|
} = await certificateDAL.transaction(async (tx) => {
|
||||||
|
const certResult = await internalCaService.issueCertFromCa({
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
friendlyName: certificateSubject.common_name || "Certificate",
|
friendlyName: certificateSubject.common_name || "Certificate",
|
||||||
commonName: certificateSubject.common_name || "",
|
commonName: certificateSubject.common_name || "",
|
||||||
@@ -1023,22 +1051,43 @@ export const certificateV3ServiceFactory = ({
|
|||||||
isFromProfile: true
|
isFromProfile: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const cert = await certificateDAL.findOne({ serialNumber, caId: ca.id });
|
const certificateRecord = await certificateDAL.findById(certResult.certificateId);
|
||||||
if (!cert) {
|
if (!certificateRecord) {
|
||||||
throw new NotFoundError({ message: "Certificate was issued but could not be found in database" });
|
throw new NotFoundError({ message: "Certificate was issued but could not be found in database" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(
|
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(
|
||||||
profile,
|
profile,
|
||||||
certificateRequest.validity.ttl,
|
certificateRequest.validity.ttl,
|
||||||
new Date(cert.notAfter)
|
new Date(certificateRecord.notAfter)
|
||||||
);
|
);
|
||||||
|
|
||||||
const updateData: { profileId: string; renewBeforeDays?: number } = { profileId };
|
const updateData: { profileId: string; renewBeforeDays?: number } = { profileId };
|
||||||
if (finalRenewBeforeDays !== undefined) {
|
if (finalRenewBeforeDays !== undefined) {
|
||||||
updateData.renewBeforeDays = finalRenewBeforeDays;
|
updateData.renewBeforeDays = finalRenewBeforeDays;
|
||||||
}
|
}
|
||||||
await certificateDAL.updateById(cert.id, updateData);
|
await certificateDAL.updateById(certificateRecord.id, updateData);
|
||||||
|
|
||||||
|
const certRequestResult = await certificateRequestService.createCertificateRequest({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId: profile.projectId,
|
||||||
|
tx,
|
||||||
|
profileId: profile.id,
|
||||||
|
commonName: certificateRequest.commonName,
|
||||||
|
altNames: certificateRequest.altNames?.map((san) => san.value).join(","),
|
||||||
|
keyUsages: convertKeyUsageArrayToLegacy(certificateRequest.keyUsages),
|
||||||
|
extendedKeyUsages: convertExtendedKeyUsageArrayToLegacy(certificateRequest.extendedKeyUsages),
|
||||||
|
notBefore: certificateRequest.notBefore,
|
||||||
|
notAfter: certificateRequest.notAfter,
|
||||||
|
keyAlgorithm: effectiveKeyAlgorithm,
|
||||||
|
signatureAlgorithm: effectiveSignatureAlgorithm
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...certResult, cert: certificateRecord, certificateRequestId: certRequestResult.id };
|
||||||
|
});
|
||||||
|
|
||||||
let finalCertificateChain = bufferToString(certificateChain);
|
let finalCertificateChain = bufferToString(certificateChain);
|
||||||
if (removeRootsFromChain) {
|
if (removeRootsFromChain) {
|
||||||
@@ -1052,6 +1101,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
privateKey: bufferToString(privateKey),
|
privateKey: bufferToString(privateKey),
|
||||||
serialNumber,
|
serialNumber,
|
||||||
certificateId: cert.id,
|
certificateId: cert.id,
|
||||||
|
certificateRequestId,
|
||||||
projectId: profile.projectId,
|
projectId: profile.projectId,
|
||||||
profileName: profile.slug,
|
profileName: profile.slug,
|
||||||
commonName: cert.commonName || ""
|
commonName: cert.commonName || ""
|
||||||
@@ -1135,8 +1185,9 @@ export const certificateV3ServiceFactory = ({
|
|||||||
const effectiveSignatureAlgorithm = extractedSignatureAlgorithm;
|
const effectiveSignatureAlgorithm = extractedSignatureAlgorithm;
|
||||||
const effectiveKeyAlgorithm = extractedKeyAlgorithm;
|
const effectiveKeyAlgorithm = extractedKeyAlgorithm;
|
||||||
|
|
||||||
const { certificate, certificateChain, issuingCaCertificate, serialNumber } =
|
const { certificate, certificateChain, issuingCaCertificate, serialNumber, cert, certificateRequestId } =
|
||||||
await internalCaService.signCertFromCa({
|
await certificateDAL.transaction(async (tx) => {
|
||||||
|
const certResult = await internalCaService.signCertFromCa({
|
||||||
isInternal: true,
|
isInternal: true,
|
||||||
caId: ca.id,
|
caId: ca.id,
|
||||||
csr,
|
csr,
|
||||||
@@ -1149,18 +1200,44 @@ export const certificateV3ServiceFactory = ({
|
|||||||
isFromProfile: true
|
isFromProfile: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const cert = await certificateDAL.findOne({ serialNumber, caId: ca.id });
|
const signedCertRecord = await certificateDAL.findById(certResult.certificateId);
|
||||||
if (!cert) {
|
if (!signedCertRecord) {
|
||||||
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
|
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(profile, validity.ttl, new Date(cert.notAfter));
|
const finalRenewBeforeDays = calculateFinalRenewBeforeDays(
|
||||||
|
profile,
|
||||||
|
validity.ttl,
|
||||||
|
new Date(signedCertRecord.notAfter)
|
||||||
|
);
|
||||||
|
|
||||||
const updateData2: { profileId: string; renewBeforeDays?: number } = { profileId };
|
const updateData: { profileId: string; renewBeforeDays?: number } = { profileId };
|
||||||
if (finalRenewBeforeDays !== undefined) {
|
if (finalRenewBeforeDays !== undefined) {
|
||||||
updateData2.renewBeforeDays = finalRenewBeforeDays;
|
updateData.renewBeforeDays = finalRenewBeforeDays;
|
||||||
}
|
}
|
||||||
await certificateDAL.updateById(cert.id, updateData2);
|
await certificateDAL.updateById(signedCertRecord.id, updateData);
|
||||||
|
|
||||||
|
const certRequestResult = await certificateRequestService.createCertificateRequest({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId: profile.projectId,
|
||||||
|
tx,
|
||||||
|
profileId: profile.id,
|
||||||
|
csr,
|
||||||
|
commonName: mappedCertificateRequest.commonName,
|
||||||
|
altNames: mappedCertificateRequest.subjectAlternativeNames?.map((san) => san.value).join(","),
|
||||||
|
keyUsages: convertKeyUsageArrayToLegacy(mappedCertificateRequest.keyUsages),
|
||||||
|
extendedKeyUsages: convertExtendedKeyUsageArrayToLegacy(mappedCertificateRequest.extendedKeyUsages),
|
||||||
|
notBefore,
|
||||||
|
notAfter,
|
||||||
|
keyAlgorithm: effectiveKeyAlgorithm,
|
||||||
|
signatureAlgorithm: effectiveSignatureAlgorithm
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...certResult, cert: signedCertRecord, certificateRequestId: certRequestResult.id };
|
||||||
|
});
|
||||||
|
|
||||||
const certificateString = extractCertificateFromBuffer(certificate as unknown as Buffer);
|
const certificateString = extractCertificateFromBuffer(certificate as unknown as Buffer);
|
||||||
let certificateChainString = extractCertificateFromBuffer(certificateChain as unknown as Buffer);
|
let certificateChainString = extractCertificateFromBuffer(certificateChain as unknown as Buffer);
|
||||||
@@ -1174,6 +1251,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
certificateChain: certificateChainString,
|
certificateChain: certificateChainString,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
certificateId: cert.id,
|
certificateId: cert.id,
|
||||||
|
certificateRequestId,
|
||||||
projectId: profile.projectId,
|
projectId: profile.projectId,
|
||||||
profileName: profile.slug,
|
profileName: profile.slug,
|
||||||
commonName: cert.commonName || ""
|
commonName: cert.commonName || ""
|
||||||
@@ -1186,9 +1264,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
actor,
|
actor,
|
||||||
actorId,
|
actorId,
|
||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId
|
||||||
removeRootsFromChain,
|
|
||||||
certificateRequestId
|
|
||||||
}: TOrderCertificateFromProfileDTO): Promise<TCertificateOrderResponse> => {
|
}: TOrderCertificateFromProfileDTO): Promise<TCertificateOrderResponse> => {
|
||||||
const profile = await validateProfileAndPermissions(
|
const profile = await validateProfileAndPermissions(
|
||||||
profileId,
|
profileId,
|
||||||
@@ -1202,7 +1278,22 @@ export const certificateV3ServiceFactory = ({
|
|||||||
EnrollmentType.API
|
EnrollmentType.API
|
||||||
);
|
);
|
||||||
|
|
||||||
const certificateRequest = {
|
let certificateRequest: TCertificateRequest;
|
||||||
|
let extractedKeyAlgorithm: string | undefined;
|
||||||
|
let extractedSignatureAlgorithm: string | undefined;
|
||||||
|
|
||||||
|
if (certificateOrder.csr) {
|
||||||
|
certificateRequest = extractCertificateRequestFromCSR(certificateOrder.csr);
|
||||||
|
const algorithms = extractAlgorithmsFromCSR(certificateOrder.csr);
|
||||||
|
extractedKeyAlgorithm = algorithms.keyAlgorithm;
|
||||||
|
extractedSignatureAlgorithm = algorithms.signatureAlgorithm;
|
||||||
|
certificateRequest.validity = certificateOrder.validity;
|
||||||
|
if (certificateOrder.notBefore && certificateOrder.notAfter) {
|
||||||
|
certificateRequest.notBefore = certificateOrder.notBefore;
|
||||||
|
certificateRequest.notAfter = certificateOrder.notAfter;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
certificateRequest = {
|
||||||
commonName: certificateOrder.commonName,
|
commonName: certificateOrder.commonName,
|
||||||
keyUsages: certificateOrder.keyUsages,
|
keyUsages: certificateOrder.keyUsages,
|
||||||
extendedKeyUsages: certificateOrder.extendedKeyUsages,
|
extendedKeyUsages: certificateOrder.extendedKeyUsages,
|
||||||
@@ -1231,8 +1322,15 @@ export const certificateV3ServiceFactory = ({
|
|||||||
signatureAlgorithm: certificateOrder.signatureAlgorithm,
|
signatureAlgorithm: certificateOrder.signatureAlgorithm,
|
||||||
keyAlgorithm: certificateOrder.keyAlgorithm
|
keyAlgorithm: certificateOrder.keyAlgorithm
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const mappedCertificateRequest = mapEnumsForValidation(certificateRequest);
|
const mappedCertificateRequest = mapEnumsForValidation(certificateRequest);
|
||||||
|
|
||||||
|
if (certificateOrder.csr) {
|
||||||
|
mappedCertificateRequest.keyAlgorithm = extractedKeyAlgorithm;
|
||||||
|
mappedCertificateRequest.signatureAlgorithm = extractedSignatureAlgorithm;
|
||||||
|
}
|
||||||
|
|
||||||
const validationResult = await certificateTemplateV2Service.validateCertificateRequest(
|
const validationResult = await certificateTemplateV2Service.validateCertificateRequest(
|
||||||
profile.certificateTemplateId,
|
profile.certificateTemplateId,
|
||||||
mappedCertificateRequest
|
mappedCertificateRequest
|
||||||
@@ -1258,64 +1356,53 @@ export const certificateV3ServiceFactory = ({
|
|||||||
const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL;
|
const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL;
|
||||||
|
|
||||||
if (caType === CaType.INTERNAL) {
|
if (caType === CaType.INTERNAL) {
|
||||||
const certificateResult = await issueCertificateFromProfile({
|
throw new BadRequestError({
|
||||||
profileId,
|
message: "Certificate ordering is not supported for the specified CA type"
|
||||||
certificateRequest,
|
|
||||||
actor,
|
|
||||||
actorId,
|
|
||||||
actorAuthMethod,
|
|
||||||
actorOrgId,
|
|
||||||
removeRootsFromChain
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const orderId = randomUUID();
|
|
||||||
|
|
||||||
return {
|
|
||||||
orderId,
|
|
||||||
status: CertificateOrderStatus.VALID,
|
|
||||||
subjectAlternativeNames: certificateOrder.altNames.map((san) => ({
|
|
||||||
type: san.type,
|
|
||||||
value: san.value,
|
|
||||||
status: CertificateOrderStatus.VALID
|
|
||||||
})),
|
|
||||||
authorizations: [],
|
|
||||||
finalize: `/api/v1/cert-manager/certificates/orders/${orderId}/completed`,
|
|
||||||
certificate: certificateResult.certificate,
|
|
||||||
projectId: certificateResult.projectId,
|
|
||||||
profileName: certificateResult.profileName
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (caType === CaType.ACME || caType === CaType.AZURE_AD_CS) {
|
if (caType === CaType.ACME || caType === CaType.AZURE_AD_CS) {
|
||||||
const orderId = randomUUID();
|
const orderId = randomUUID();
|
||||||
|
|
||||||
|
const certRequest = await certificateRequestService.createCertificateRequest({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId: profile.projectId,
|
||||||
|
profileId: profile.id,
|
||||||
|
commonName: certificateOrder.commonName || "",
|
||||||
|
keyUsages: certificateOrder.keyUsages ? convertEnumsToStringArray(certificateOrder.keyUsages) : [],
|
||||||
|
extendedKeyUsages: certificateOrder.extendedKeyUsages
|
||||||
|
? convertEnumsToStringArray(certificateOrder.extendedKeyUsages)
|
||||||
|
: [],
|
||||||
|
keyAlgorithm: certificateOrder.keyAlgorithm || "",
|
||||||
|
signatureAlgorithm: certificateOrder.signatureAlgorithm || "",
|
||||||
|
altNames: certificateOrder.altNames?.map((san) => san.value).join(",") || "",
|
||||||
|
notBefore: certificateOrder.notBefore,
|
||||||
|
notAfter: certificateOrder.notAfter
|
||||||
|
});
|
||||||
|
|
||||||
await certificateIssuanceQueue.queueCertificateIssuance({
|
await certificateIssuanceQueue.queueCertificateIssuance({
|
||||||
certificateId: orderId,
|
certificateId: orderId,
|
||||||
profileId: profile.id,
|
profileId: profile.id,
|
||||||
caId: profile.caId || "",
|
caId: profile.caId || "",
|
||||||
ttl: certificateOrder.validity?.ttl || "1y",
|
ttl: certificateOrder.validity?.ttl || "1y",
|
||||||
signatureAlgorithm: certificateOrder.signatureAlgorithm || "",
|
signatureAlgorithm: certificateOrder.signatureAlgorithm || "",
|
||||||
keyAlgorithm: certificateOrder.keyAlgorithm || "",
|
keyAlgorithm: certificateRequest.keyAlgorithm || "",
|
||||||
commonName: certificateOrder.commonName || "",
|
commonName: certificateRequest.commonName || "",
|
||||||
altNames: certificateOrder.altNames?.map((san) => san.value) || [],
|
altNames: certificateRequest.subjectAlternativeNames?.map((san) => san.value) || [],
|
||||||
keyUsages: certificateOrder.keyUsages ? convertEnumsToStringArray(certificateOrder.keyUsages) : [],
|
keyUsages: certificateRequest.keyUsages ? convertEnumsToStringArray(certificateRequest.keyUsages) : [],
|
||||||
extendedKeyUsages: certificateOrder.extendedKeyUsages
|
extendedKeyUsages: certificateRequest.extendedKeyUsages
|
||||||
? convertEnumsToStringArray(certificateOrder.extendedKeyUsages)
|
? convertEnumsToStringArray(certificateRequest.extendedKeyUsages)
|
||||||
: [],
|
: [],
|
||||||
certificateRequestId
|
certificateRequestId: certRequest.id,
|
||||||
|
csr: certificateOrder.csr
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
orderId,
|
certificateRequestId: certRequest.id,
|
||||||
status: CertificateOrderStatus.PENDING,
|
projectId: certRequest.projectId,
|
||||||
subjectAlternativeNames: certificateOrder.altNames.map((san) => ({
|
|
||||||
type: san.type,
|
|
||||||
value: san.value,
|
|
||||||
status: CertificateOrderStatus.PENDING
|
|
||||||
})),
|
|
||||||
authorizations: [],
|
|
||||||
finalize: `/api/v3/pki/certificates/orders/${orderId}/finalize`,
|
|
||||||
projectId: profile.projectId,
|
|
||||||
profileName: profile.slug
|
profileName: profile.slug
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -1325,30 +1412,6 @@ export const certificateV3ServiceFactory = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Type for internal CA renewal result
|
|
||||||
type TInternalRenewalData = {
|
|
||||||
certificate: string;
|
|
||||||
certificateChain: string;
|
|
||||||
issuingCaCertificate: string;
|
|
||||||
serialNumber: string;
|
|
||||||
newCert: TCertificates;
|
|
||||||
originalCert: TCertificates;
|
|
||||||
profile: TCertificateProfileWithConfigs | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Type for external CA renewal result
|
|
||||||
type TExternalRenewalData = {
|
|
||||||
isExternalCA: true;
|
|
||||||
ca: TCertificateAuthorityWithAssociatedCa;
|
|
||||||
profile: TCertificateProfileWithConfigs | null;
|
|
||||||
originalCert: TCertificates;
|
|
||||||
originalSignatureAlgorithm: CertSignatureAlgorithm;
|
|
||||||
originalKeyAlgorithm: CertKeyAlgorithm;
|
|
||||||
ttl: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type TRenewalTransactionResult = TInternalRenewalData | TExternalRenewalData;
|
|
||||||
|
|
||||||
const renewCertificate = async ({
|
const renewCertificate = async ({
|
||||||
certificateId,
|
certificateId,
|
||||||
actor,
|
actor,
|
||||||
@@ -1356,10 +1419,11 @@ export const certificateV3ServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId,
|
actorOrgId,
|
||||||
internal = false,
|
internal = false,
|
||||||
removeRootsFromChain,
|
removeRootsFromChain
|
||||||
certificateRequestId
|
}: Omit<TRenewCertificateDTO, "certificateRequestId"> & {
|
||||||
}: TRenewCertificateDTO & { internal?: boolean }): Promise<TCertificateFromProfileResponse> => {
|
internal?: boolean;
|
||||||
const renewalResult: TRenewalTransactionResult = await certificateDAL.transaction(async (tx) => {
|
}): Promise<TCertificateFromProfileResponse> => {
|
||||||
|
const renewalResult = await certificateDAL.transaction(async (tx) => {
|
||||||
const originalCert = await certificateDAL.findById(certificateId, tx);
|
const originalCert = await certificateDAL.findById(certificateId, tx);
|
||||||
if (!originalCert) {
|
if (!originalCert) {
|
||||||
throw new NotFoundError({ message: "Certificate not found" });
|
throw new NotFoundError({ message: "Certificate not found" });
|
||||||
@@ -1576,7 +1640,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
issuingCaCertificate = caResult.issuingCaCertificate;
|
issuingCaCertificate = caResult.issuingCaCertificate;
|
||||||
serialNumber = caResult.serialNumber;
|
serialNumber = caResult.serialNumber;
|
||||||
|
|
||||||
const foundCert = await certificateDAL.findOne({ serialNumber, caId: ca.id }, tx);
|
const foundCert = await certificateDAL.findById(caResult.certificateId, tx);
|
||||||
if (!foundCert) {
|
if (!foundCert) {
|
||||||
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
|
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
|
||||||
}
|
}
|
||||||
@@ -1665,6 +1729,27 @@ export const certificateV3ServiceFactory = ({
|
|||||||
|
|
||||||
await addRenewedCertificateToSyncs(originalCert.id, newCert.id, { certificateSyncDAL }, tx);
|
await addRenewedCertificateToSyncs(originalCert.id, newCert.id, { certificateSyncDAL }, tx);
|
||||||
|
|
||||||
|
const certRequestResult = await certificateRequestService.createCertificateRequest({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId: originalCert.projectId,
|
||||||
|
tx,
|
||||||
|
profileId: originalCert.profileId || undefined,
|
||||||
|
commonName: originalCert.commonName || undefined,
|
||||||
|
altNames: originalCert.altNames || undefined,
|
||||||
|
keyUsages: convertKeyUsageArrayFromLegacy(parseKeyUsages(originalCert.keyUsages)),
|
||||||
|
extendedKeyUsages: convertExtendedKeyUsageArrayFromLegacy(
|
||||||
|
parseExtendedKeyUsages(originalCert.extendedKeyUsages)
|
||||||
|
),
|
||||||
|
notBefore: new Date(newCert.notBefore),
|
||||||
|
notAfter: new Date(newCert.notAfter),
|
||||||
|
keyAlgorithm: originalKeyAlgorithm,
|
||||||
|
signatureAlgorithm: originalSignatureAlgorithm,
|
||||||
|
metadata: `Renewed from certificate ID: ${originalCert.id}`
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate,
|
certificate,
|
||||||
certificateChain,
|
certificateChain,
|
||||||
@@ -1672,10 +1757,13 @@ export const certificateV3ServiceFactory = ({
|
|||||||
serialNumber,
|
serialNumber,
|
||||||
newCert,
|
newCert,
|
||||||
originalCert,
|
originalCert,
|
||||||
profile
|
profile,
|
||||||
|
certRequestResult
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let certificateRequestId: string = renewalResult.certRequestResult?.id || "";
|
||||||
|
|
||||||
// Handle external CA renewals separately
|
// Handle external CA renewals separately
|
||||||
if ("isExternalCA" in renewalResult && renewalResult.isExternalCA) {
|
if ("isExternalCA" in renewalResult && renewalResult.isExternalCA) {
|
||||||
const { ca, profile, originalCert, originalSignatureAlgorithm, originalKeyAlgorithm, ttl } = renewalResult;
|
const { ca, profile, originalCert, originalSignatureAlgorithm, originalKeyAlgorithm, ttl } = renewalResult;
|
||||||
@@ -1685,6 +1773,27 @@ export const certificateV3ServiceFactory = ({
|
|||||||
? originalCert.altNames.split(",").map((san: string) => san.trim())
|
? originalCert.altNames.split(",").map((san: string) => san.trim())
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
const certificateRequest = await certificateRequestService.createCertificateRequest({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
projectId: originalCert.projectId,
|
||||||
|
profileId: profile?.id,
|
||||||
|
caId: ca.id,
|
||||||
|
commonName: originalCert.commonName || undefined,
|
||||||
|
altNames: originalCert.altNames || undefined,
|
||||||
|
keyUsages: convertKeyUsageArrayFromLegacy(parseKeyUsages(originalCert.keyUsages)),
|
||||||
|
extendedKeyUsages: convertExtendedKeyUsageArrayFromLegacy(
|
||||||
|
parseExtendedKeyUsages(originalCert.extendedKeyUsages)
|
||||||
|
),
|
||||||
|
keyAlgorithm: originalKeyAlgorithm,
|
||||||
|
signatureAlgorithm: originalSignatureAlgorithm,
|
||||||
|
metadata: `Renewed from certificate ID: ${originalCert.id}`
|
||||||
|
});
|
||||||
|
|
||||||
|
certificateRequestId = certificateRequest.id;
|
||||||
|
|
||||||
await certificateIssuanceQueue.queueCertificateIssuance({
|
await certificateIssuanceQueue.queueCertificateIssuance({
|
||||||
certificateId: renewalOrderId,
|
certificateId: renewalOrderId,
|
||||||
profileId: profile?.id || "",
|
profileId: profile?.id || "",
|
||||||
@@ -1698,7 +1807,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
extendedKeyUsages: convertEnumsToStringArray(parseExtendedKeyUsages(originalCert.extendedKeyUsages)),
|
extendedKeyUsages: convertEnumsToStringArray(parseExtendedKeyUsages(originalCert.extendedKeyUsages)),
|
||||||
isRenewal: true,
|
isRenewal: true,
|
||||||
originalCertificateId: certificateId,
|
originalCertificateId: certificateId,
|
||||||
certificateRequestId
|
certificateRequestId: certificateRequest.id
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -1707,6 +1816,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
issuingCaCertificate: "",
|
issuingCaCertificate: "",
|
||||||
serialNumber: "",
|
serialNumber: "",
|
||||||
certificateId: renewalOrderId,
|
certificateId: renewalOrderId,
|
||||||
|
certificateRequestId: certificateRequest.id,
|
||||||
projectId: originalCert.projectId,
|
projectId: originalCert.projectId,
|
||||||
profileName: profile?.slug || "External CA Profile",
|
profileName: profile?.slug || "External CA Profile",
|
||||||
commonName: originalCert.commonName || ""
|
commonName: originalCert.commonName || ""
|
||||||
@@ -1734,6 +1844,7 @@ export const certificateV3ServiceFactory = ({
|
|||||||
certificateChain: finalCertificateChain,
|
certificateChain: finalCertificateChain,
|
||||||
serialNumber: renewalResult.serialNumber,
|
serialNumber: renewalResult.serialNumber,
|
||||||
certificateId: renewalResult.newCert.id,
|
certificateId: renewalResult.newCert.id,
|
||||||
|
certificateRequestId,
|
||||||
projectId: renewalResult.originalCert.projectId,
|
projectId: renewalResult.originalCert.projectId,
|
||||||
profileName: renewalResult.profile?.slug || "Self-signed Certificate",
|
profileName: renewalResult.profile?.slug || "Self-signed Certificate",
|
||||||
commonName: renewalResult.originalCert.commonName || ""
|
commonName: renewalResult.originalCert.commonName || ""
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { TProjectPermission } from "@app/lib/types";
|
import { TProjectPermission } from "@app/lib/types";
|
||||||
|
|
||||||
import { ACMESANType, CertificateOrderStatus } from "../certificate/certificate-types";
|
import { ACMESANType } from "../certificate/certificate-types";
|
||||||
import {
|
import {
|
||||||
CertExtendedKeyUsageType,
|
CertExtendedKeyUsageType,
|
||||||
CertKeyUsageType,
|
CertKeyUsageType,
|
||||||
@@ -59,9 +59,9 @@ export type TOrderCertificateFromProfileDTO = {
|
|||||||
signatureAlgorithm?: string;
|
signatureAlgorithm?: string;
|
||||||
keyAlgorithm?: string;
|
keyAlgorithm?: string;
|
||||||
template?: string;
|
template?: string;
|
||||||
|
csr?: string;
|
||||||
};
|
};
|
||||||
removeRootsFromChain?: boolean;
|
removeRootsFromChain?: boolean;
|
||||||
certificateRequestId?: string;
|
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TCertificateFromProfileResponse = {
|
export type TCertificateFromProfileResponse = {
|
||||||
@@ -71,34 +71,14 @@ export type TCertificateFromProfileResponse = {
|
|||||||
privateKey?: string;
|
privateKey?: string;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
certificateId: string;
|
certificateId: string;
|
||||||
|
certificateRequestId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
profileName: string;
|
profileName: string;
|
||||||
commonName: string;
|
commonName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateOrderResponse = {
|
export type TCertificateOrderResponse = {
|
||||||
orderId: string;
|
certificateRequestId: string;
|
||||||
status: CertificateOrderStatus;
|
|
||||||
subjectAlternativeNames: Array<{
|
|
||||||
type: ACMESANType;
|
|
||||||
value: string;
|
|
||||||
status: CertificateOrderStatus;
|
|
||||||
}>;
|
|
||||||
authorizations: Array<{
|
|
||||||
identifier: {
|
|
||||||
type: ACMESANType;
|
|
||||||
value: string;
|
|
||||||
};
|
|
||||||
status: CertificateOrderStatus;
|
|
||||||
expires?: string;
|
|
||||||
challenges: Array<{
|
|
||||||
type: string;
|
|
||||||
status: CertificateOrderStatus;
|
|
||||||
url: string;
|
|
||||||
token: string;
|
|
||||||
}>;
|
|
||||||
}>;
|
|
||||||
finalize: string;
|
|
||||||
certificate?: string;
|
certificate?: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
profileName: string;
|
profileName: string;
|
||||||
|
|||||||
@@ -83,32 +83,40 @@ export type TDownloadPkcs12DTO = {
|
|||||||
|
|
||||||
export type TUnifiedCertificateIssuanceDTO = {
|
export type TUnifiedCertificateIssuanceDTO = {
|
||||||
projectSlug: string;
|
projectSlug: string;
|
||||||
|
profileId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
profileId?: string;
|
|
||||||
caId?: string;
|
|
||||||
csr?: string;
|
csr?: string;
|
||||||
|
attributes?: {
|
||||||
commonName?: string;
|
commonName?: string;
|
||||||
altNames?: string;
|
|
||||||
keyUsages?: string[];
|
keyUsages?: string[];
|
||||||
extendedKeyUsages?: string[];
|
extendedKeyUsages?: string[];
|
||||||
notBefore?: Date;
|
altNames?: Array<{
|
||||||
notAfter?: Date;
|
type: string;
|
||||||
keyAlgorithm?: string;
|
value: string;
|
||||||
signatureAlgorithm?: string;
|
}>;
|
||||||
ttl?: string;
|
signatureAlgorithm: string;
|
||||||
friendlyName?: string;
|
keyAlgorithm: string;
|
||||||
pkiCollectionId?: string;
|
subjectAlternativeNames?: Array<{
|
||||||
issuerType?: string;
|
type: string;
|
||||||
|
value: string;
|
||||||
|
}>;
|
||||||
|
ttl: string;
|
||||||
|
notBefore?: string;
|
||||||
|
notAfter?: string;
|
||||||
|
};
|
||||||
|
removeRootsFromChain?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TUnifiedCertificateResponse = {
|
export type TUnifiedCertificateResponse = {
|
||||||
|
certificate: {
|
||||||
certificate: string;
|
certificate: string;
|
||||||
issuingCaCertificate: string;
|
issuingCaCertificate: string;
|
||||||
certificateChain: string;
|
certificateChain: string;
|
||||||
privateKey?: string;
|
privateKey?: string;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
certificateId: string;
|
certificateId: string;
|
||||||
projectId: string;
|
};
|
||||||
|
certificateRequestId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateRequestResponse = {
|
export type TCertificateRequestResponse = {
|
||||||
|
|||||||
+20
-18
@@ -107,6 +107,7 @@ type TCertificateDetails = {
|
|||||||
certificate?: string;
|
certificate?: string;
|
||||||
certificateChain?: string;
|
certificateChain?: string;
|
||||||
privateKey?: string;
|
privateKey?: string;
|
||||||
|
issuingCaCertificate?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }: Props) => {
|
export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }: Props) => {
|
||||||
@@ -280,41 +281,42 @@ export const CertificateIssuanceModal = ({ popUp, handlePopUpToggle, profileId }
|
|||||||
profileId: formProfileId,
|
profileId: formProfileId,
|
||||||
projectSlug: currentProject.slug,
|
projectSlug: currentProject.slug,
|
||||||
projectId: currentProject.id,
|
projectId: currentProject.id,
|
||||||
|
attributes: {
|
||||||
ttl,
|
ttl,
|
||||||
|
signatureAlgorithm: signatureAlgorithm || "",
|
||||||
|
keyAlgorithm: keyAlgorithm || "",
|
||||||
keyUsages: filterUsages(keyUsages) as CertKeyUsage[],
|
keyUsages: filterUsages(keyUsages) as CertKeyUsage[],
|
||||||
extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[]
|
extendedKeyUsages: filterUsages(extendedKeyUsages) as CertExtendedKeyUsage[]
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (constraints.shouldShowSubjectSection && commonName) {
|
if (constraints.shouldShowSubjectSection && commonName) {
|
||||||
request.commonName = commonName;
|
request.attributes.commonName = commonName;
|
||||||
}
|
|
||||||
|
|
||||||
if (signatureAlgorithm) {
|
|
||||||
request.signatureAlgorithm = signatureAlgorithm;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyAlgorithm) {
|
|
||||||
request.keyAlgorithm = keyAlgorithm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) {
|
if (constraints.shouldShowSanSection && subjectAltNames && subjectAltNames.length > 0) {
|
||||||
const formattedSans = formatSubjectAltNames(subjectAltNames);
|
const formattedSans = formatSubjectAltNames(subjectAltNames);
|
||||||
if (formattedSans && formattedSans.length > 0) {
|
if (formattedSans && formattedSans.length > 0) {
|
||||||
request.altNames = formattedSans;
|
request.attributes.altNames = formattedSans;
|
||||||
|
request.attributes.subjectAlternativeNames = formattedSans;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await issueCertificate(request);
|
const response = await issueCertificate(request);
|
||||||
|
|
||||||
// Handle certificate issuance response
|
// Handle certificate issuance response
|
||||||
if ("certificate" in response) {
|
|
||||||
// Immediate certificate issuance
|
if ("certificate" in response && response.certificate) {
|
||||||
setCertificateDetails({
|
const certData = response.certificate;
|
||||||
serialNumber: response.serialNumber,
|
const certificateDetailsToSet = {
|
||||||
certificate: response.certificate,
|
serialNumber: certData.serialNumber || "",
|
||||||
certificateChain: response.certificateChain,
|
certificate: certData.certificate || "",
|
||||||
privateKey: response.privateKey
|
certificateChain: certData.certificateChain || "",
|
||||||
});
|
privateKey: certData.privateKey || "",
|
||||||
|
issuingCaCertificate: certData.issuingCaCertificate || ""
|
||||||
|
};
|
||||||
|
|
||||||
|
setCertificateDetails(certificateDetailsToSet);
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully created certificate",
|
text: "Successfully created certificate",
|
||||||
|
|||||||
Reference in New Issue
Block a user