From 2172d11376a0092e29af8eba8bd192cfc4242f0c Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Fri, 14 Nov 2025 00:58:51 -0300 Subject: [PATCH] Add PKI template presets --- .../CertificateTemplatesV2Tab.tsx | 9 +- .../CreateTemplateModal.tsx | 189 +++++++-- .../shared/certificate-constants.ts | 39 +- .../shared/schemas.ts | 23 +- .../shared/template-presets.ts | 385 ++++++++++++++++++ 5 files changed, 605 insertions(+), 40 deletions(-) create mode 100644 frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx index ec660b339..33f92a122 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CertificateTemplatesV2Tab.tsx @@ -10,7 +10,7 @@ import { ProjectPermissionSub } from "@app/context/ProjectPermissionContext/types"; import { useDeleteCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/mutations"; -import { TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types"; +import { type TCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/types"; import { CreateTemplateModal } from "./CreateTemplateModal"; import { TemplateList } from "./TemplateList"; @@ -84,7 +84,12 @@ export const CertificateTemplatesV2Tab = () => { - setIsCreateModalOpen(false)} /> + { + setIsCreateModalOpen(false); + }} + /> {selectedTemplate && ( <> diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx index b929ce36e..d55c1b5ef 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/CreateTemplateModal.tsx @@ -36,13 +36,18 @@ import { CertDurationUnit, CertExtendedKeyUsageType, CertKeyUsageType, + CertSanInclude, CertSubjectAlternativeNameType, + CertSubjectAttributeInclude, CertSubjectAttributeType, SAN_INCLUDE_OPTIONS, SAN_TYPE_OPTIONS, SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS, - SUBJECT_ATTRIBUTE_TYPE_OPTIONS + SUBJECT_ATTRIBUTE_TYPE_OPTIONS, + TEMPLATE_PRESET_IDS, + type TemplatePresetId } from "./shared/certificate-constants"; +import { CERTIFICATE_TEMPLATE_PRESETS } from "./shared/template-presets"; import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared"; export type FormData = TemplateFormData; @@ -91,7 +96,7 @@ const SIGNATURE_ALGORITHMS = [ "SHA256-ECDSA", "SHA384-ECDSA", "SHA512-ECDSA" -]; +] as const; const KEY_ALGORITHMS = [ "RSA-2048", @@ -100,7 +105,7 @@ const KEY_ALGORITHMS = [ "ECDSA-P256", "ECDSA-P384", "ECDSA-P521" -]; +] as const; export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => { const { currentProject } = useProject(); @@ -117,7 +122,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" subj.allowed.forEach((allowedValue) => { attributes.push({ type: subj.type as CertSubjectAttributeType, - include: "optional", + include: CertSubjectAttributeInclude.OPTIONAL, value: [allowedValue] }); }); @@ -126,7 +131,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" subj.denied.forEach((deniedValue) => { attributes.push({ type: subj.type as CertSubjectAttributeType, - include: "prohibit", + include: CertSubjectAttributeInclude.PROHIBIT, value: [deniedValue] }); }); @@ -141,7 +146,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" san.required.forEach((requiredValue) => { subjectAlternativeNames.push({ type: san.type as CertSubjectAlternativeNameType, - include: "mandatory", + include: CertSanInclude.MANDATORY, value: [requiredValue] }); }); @@ -150,7 +155,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" san.allowed.forEach((allowedValue) => { subjectAlternativeNames.push({ type: san.type as CertSubjectAlternativeNameType, - include: "optional", + include: CertSanInclude.OPTIONAL, value: [allowedValue] }); }); @@ -159,7 +164,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" san.denied.forEach((deniedValue) => { subjectAlternativeNames.push({ type: san.type as CertSubjectAlternativeNameType, - include: "prohibit", + include: CertSanInclude.PROHIBIT, value: [deniedValue] }); }); @@ -219,6 +224,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }; return { + preset: TEMPLATE_PRESET_IDS.CUSTOM, name: templateData.name || "", description: templateData.description || "", attributes, @@ -231,25 +237,30 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }; }; - const getDefaultValues = (): FormData => ({ - name: "", - description: "", - attributes: [], - keyUsages: { requiredUsages: [], optionalUsages: [] }, - extendedKeyUsages: { requiredUsages: [], optionalUsages: [] }, - subjectAlternativeNames: [], - validity: { - maxDuration: { value: 365, unit: CertDurationUnit.DAYS } - }, - signatureAlgorithm: { - allowedAlgorithms: [] - }, - keyAlgorithm: { - allowedKeyTypes: [] - } - }); + const getDefaultValues = (): FormData & { preset: TemplatePresetId } => { + return { + preset: TEMPLATE_PRESET_IDS.CUSTOM, + name: "", + description: "", + attributes: [], + keyUsages: { requiredUsages: [], optionalUsages: [] }, + extendedKeyUsages: { requiredUsages: [], optionalUsages: [] }, + subjectAlternativeNames: [], + validity: { + maxDuration: { value: 365, unit: CertDurationUnit.DAYS } + }, + signatureAlgorithm: { + allowedAlgorithms: [] + }, + keyAlgorithm: { + allowedKeyTypes: [] + } + }; + }; - const { control, handleSubmit, reset, watch, setValue, formState } = useForm({ + const { control, handleSubmit, reset, watch, setValue, formState } = useForm< + FormData & { preset: TemplatePresetId } + >({ resolver: zodResolver(templateSchema), defaultValues: getDefaultValues(), mode: "onChange", @@ -260,7 +271,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" useEffect(() => { if (isEdit && template) { const convertedData = convertApiToUiFormat(template); - reset(convertedData); + reset({ ...convertedData, preset: TEMPLATE_PRESET_IDS.CUSTOM }); } else if (!isEdit) { reset(getDefaultValues()); } @@ -273,6 +284,37 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" requiredUsages: [], optionalUsages: [] }; + const watchedPreset = watch("preset") || TEMPLATE_PRESET_IDS.CUSTOM; + + const handlePresetChange = (presetId: TemplatePresetId) => { + setValue("preset", presetId); + + if (presetId === TEMPLATE_PRESET_IDS.CUSTOM) { + return; + } + + const selectedPreset = CERTIFICATE_TEMPLATE_PRESETS.find((p) => p.id === presetId); + if (selectedPreset) { + if (selectedPreset.formData.keyUsages) { + setValue("keyUsages", selectedPreset.formData.keyUsages); + } + if (selectedPreset.formData.extendedKeyUsages) { + setValue("extendedKeyUsages", selectedPreset.formData.extendedKeyUsages); + } + if (selectedPreset.formData.attributes) { + setValue("attributes", selectedPreset.formData.attributes); + } + if (selectedPreset.formData.subjectAlternativeNames) { + setValue("subjectAlternativeNames", selectedPreset.formData.subjectAlternativeNames); + } + if (selectedPreset.formData.signatureAlgorithm) { + setValue("signatureAlgorithm", selectedPreset.formData.signatureAlgorithm); + } + if (selectedPreset.formData.keyAlgorithm) { + setValue("keyAlgorithm", selectedPreset.formData.keyAlgorithm); + } + } + }; const consolidateByType = < T extends { type: string; allowed?: string[]; required?: string[]; denied?: string[] } @@ -309,9 +351,17 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" data.attributes?.map((attr) => { const result: AttributeTransform = { type: attr.type }; - if (attr.include === "optional" && attr.value && attr.value.length > 0) { + if ( + attr.include === CertSubjectAttributeInclude.OPTIONAL && + attr.value && + attr.value.length > 0 + ) { result.allowed = attr.value; - } else if (attr.include === "prohibit" && attr.value && attr.value.length > 0) { + } else if ( + attr.include === CertSubjectAttributeInclude.PROHIBIT && + attr.value && + attr.value.length > 0 + ) { result.denied = attr.value; } @@ -322,11 +372,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" data.subjectAlternativeNames?.map((san) => { const result: SanTransform = { type: san.type }; - if (san.include === "mandatory" && san.value && san.value.length > 0) { + if (san.include === CertSanInclude.MANDATORY && san.value && san.value.length > 0) { result.required = san.value; - } else if (san.include === "optional" && san.value && san.value.length > 0) { + } else if (san.include === CertSanInclude.OPTIONAL && san.value && san.value.length > 0) { result.allowed = san.value; - } else if (san.include === "prohibit" && san.value && san.value.length > 0) { + } else if (san.include === CertSanInclude.PROHIBIT && san.value && san.value.length > 0) { result.denied = san.value; } @@ -464,11 +514,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: ["*"] }; setValue("attributes", [...watchedAttributes, newAttribute]); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const removeAttribute = (index: number) => { const newAttributes = watchedAttributes.filter((_, i) => i !== index); setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const addSan = () => { @@ -478,11 +536,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: ["*"] }; setValue("subjectAlternativeNames", [...watchedSans, newSan]); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const removeSan = (index: number) => { const newSans = watchedSans.filter((_, i) => i !== index); setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const handleKeyUsagesChange = (usages: { @@ -493,6 +559,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" requiredUsages: usages.requiredUsages, optionalUsages: usages.optionalUsages }); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; const handleExtendedKeyUsagesChange = (usages: { @@ -503,6 +573,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" requiredUsages: usages.requiredUsages, optionalUsages: usages.optionalUsages }); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }; return ( @@ -555,6 +629,33 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" )} /> + + ( + + + + )} + /> Subject Attributes @@ -595,6 +696,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" type: value as CertSubjectAttributeType }; setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-48" > @@ -615,6 +720,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number] }; setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-32" > @@ -635,6 +744,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: e.target.value.trim() ? [e.target.value.trim()] : [] }; setValue("attributes", newAttributes); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className={`flex-1 ${ attr.value && attr.value.length > 0 && attr.value[0] === "" @@ -701,6 +814,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" type: value as CertSubjectAlternativeNameType }; setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-36" > @@ -720,6 +837,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" include: value as (typeof SAN_INCLUDE_OPTIONS)[number] }; setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className="w-32" > @@ -740,6 +861,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" value: e.target.value.trim() ? [e.target.value.trim()] : [] }; setValue("subjectAlternativeNames", newSans); + + if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) { + setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM); + } }} className={`flex-1 ${ san.value && san.value.length > 0 && san.value[0] === "" diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts index 50b69a2e2..47fdcd5a2 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/certificate-constants.ts @@ -150,8 +150,19 @@ export const SUBJECT_ATTRIBUTE_TYPE_OPTIONS = Object.values(CertSubjectAttribute export const ATTRIBUTE_RULE_OPTIONS = Object.values(CertAttributeRule); export const SAN_EFFECT_OPTIONS = Object.values(CertSanEffect); -export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = ["optional", "prohibit"] as const; -export const SAN_INCLUDE_OPTIONS = ["mandatory", "optional", "prohibit"] as const; +export enum CertSubjectAttributeInclude { + OPTIONAL = "optional", + PROHIBIT = "prohibit" +} + +export enum CertSanInclude { + MANDATORY = "mandatory", + OPTIONAL = "optional", + PROHIBIT = "prohibit" +} + +export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = Object.values(CertSubjectAttributeInclude); +export const SAN_INCLUDE_OPTIONS = Object.values(CertSanInclude); export const USAGE_STATES = { REQUIRED: "required", @@ -239,3 +250,27 @@ export const mapTemplateKeyAlgorithmToApi = (templateFormat: string): string => }; return mapping[templateFormat] || templateFormat; }; + +export const TEMPLATE_PRESET_IDS = { + CUSTOM: "custom", + TLS_SERVER: "tls-server", + TLS_CLIENT: "tls-client", + CODE_SIGNING: "code-signing", + DEVICE: "device", + USER: "user", + EMAIL_PROTECTION: "email-protection", + DUAL_PURPOSE_SERVER: "dual-purpose-server" +} as const; + +export type TemplatePresetId = (typeof TEMPLATE_PRESET_IDS)[keyof typeof TEMPLATE_PRESET_IDS]; + +export const ALGORITHM_FAMILIES = { + ECDSA: { + signature: ["SHA256-ECDSA", "SHA384-ECDSA", "SHA512-ECDSA"] as const, + key: ["ECDSA-P256", "ECDSA-P384", "ECDSA-P521"] as const + }, + RSA: { + signature: ["SHA256-RSA", "SHA384-RSA", "SHA512-RSA"] as const, + key: ["RSA-2048", "RSA-3072", "RSA-4096"] as const + } +} as const; diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts index 0590c4160..137ca262c 100644 --- a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/schemas.ts @@ -4,21 +4,22 @@ import { CertDurationUnit, CertExtendedKeyUsageType, CertKeyUsageType, + CertSanInclude, CertSubjectAlternativeNameType, + CertSubjectAttributeInclude, CertSubjectAttributeType, - SAN_INCLUDE_OPTIONS, - SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS + TEMPLATE_PRESET_IDS } from "./certificate-constants"; export const uiAttributeSchema = z.object({ type: z.nativeEnum(CertSubjectAttributeType), - include: z.enum(SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS), + include: z.nativeEnum(CertSubjectAttributeInclude), value: z.array(z.string().min(1, "Value cannot be empty")) }); export const uiSanSchema = z.object({ type: z.nativeEnum(CertSubjectAlternativeNameType), - include: z.enum(SAN_INCLUDE_OPTIONS), + include: z.nativeEnum(CertSanInclude), value: z.array(z.string().min(1, "Value cannot be empty")) }); @@ -51,7 +52,21 @@ export const uiKeyAlgorithmSchema = z.object({ .min(1, "At least one key type must be selected") }); +export const uiPresetSchema = z + .enum([ + TEMPLATE_PRESET_IDS.CUSTOM, + TEMPLATE_PRESET_IDS.TLS_SERVER, + TEMPLATE_PRESET_IDS.TLS_CLIENT, + TEMPLATE_PRESET_IDS.CODE_SIGNING, + TEMPLATE_PRESET_IDS.DEVICE, + TEMPLATE_PRESET_IDS.USER, + TEMPLATE_PRESET_IDS.EMAIL_PROTECTION, + TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER + ]) + .default(TEMPLATE_PRESET_IDS.CUSTOM); + export const templateSchema = z.object({ + preset: uiPresetSchema, name: z .string() .trim() diff --git a/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts new file mode 100644 index 000000000..9edad21ea --- /dev/null +++ b/frontend/src/pages/cert-manager/PoliciesPage/components/CertificateTemplatesV2Tab/shared/template-presets.ts @@ -0,0 +1,385 @@ +import { + ALGORITHM_FAMILIES, + CertDurationUnit, + CertExtendedKeyUsageType, + CertKeyUsageType, + CertSanInclude, + CertSubjectAlternativeNameType, + CertSubjectAttributeInclude, + CertSubjectAttributeType, + TEMPLATE_PRESET_IDS, + type TemplatePresetId +} from "./certificate-constants"; +import { TemplateFormData } from "."; + +export interface CertificateTemplatePreset { + readonly id: TemplatePresetId; + readonly name: string; + readonly description: string; + readonly useCase: string; + readonly formData: Omit; +} + +export const CERTIFICATE_TEMPLATE_PRESETS: CertificateTemplatePreset[] = [ + { + id: TEMPLATE_PRESET_IDS.TLS_SERVER, + name: "TLS Server Certificate", + description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.", + useCase: "Web servers, API endpoints, HTTPS services", + formData: { + name: "TLS Server Certificate", + description: "Standard TLS/SSL server certificate for HTTPS services and API endpoints.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_ENCIPHERMENT] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.SERVER_AUTH], + optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.TLS_CLIENT, + name: "TLS Client Certificate", + description: "Client certificate for mutual TLS authentication and API access.", + useCase: "Client authentication, mTLS, API authentication", + formData: { + name: "TLS Client Certificate", + description: "Client certificate for mutual TLS authentication and API access.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH], + optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.CODE_SIGNING, + name: "Code Signing Certificate", + description: + "Certificate for signing software, executables, and packages. Requires hardware security modules.", + useCase: "Software signing, executable authentication, package validation", + formData: { + name: "Code Signing Certificate", + description: + "Certificate for signing software, executables, and packages. Requires hardware security modules.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.NON_REPUDIATION] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.CODE_SIGNING], + optionalUsages: [ + CertExtendedKeyUsageType.CODE_SIGNING, + CertExtendedKeyUsageType.TIME_STAMPING + ] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.DEVICE, + name: "Device Certificate", + description: + "Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.", + useCase: "Device authentication, IoT security, embedded systems", + formData: { + name: "Device Certificate", + description: + "Certificate for IoT devices and embedded systems authentication. IEEE 802.1AR compliant.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [CertKeyUsageType.DIGITAL_SIGNATURE, CertKeyUsageType.KEY_AGREEMENT] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.CLIENT_AUTH], + optionalUsages: [CertExtendedKeyUsageType.CLIENT_AUTH, CertExtendedKeyUsageType.SERVER_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.USER, + name: "User Certificate", + description: + "Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.", + useCase: "Personal authentication, smart cards, email protection", + formData: { + name: "User Certificate", + description: + "Personal certificate for user authentication and email signing. FIPS 201 PIV compliant.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [ + CertKeyUsageType.DIGITAL_SIGNATURE, + CertKeyUsageType.KEY_ENCIPHERMENT, + CertKeyUsageType.KEY_AGREEMENT + ] + }, + extendedKeyUsages: { + requiredUsages: [ + CertExtendedKeyUsageType.CLIENT_AUTH, + CertExtendedKeyUsageType.EMAIL_PROTECTION + ], + optionalUsages: [ + CertExtendedKeyUsageType.CLIENT_AUTH, + CertExtendedKeyUsageType.EMAIL_PROTECTION + ] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.EMAIL_PROTECTION, + name: "Email Protection Certificate", + description: "S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.", + useCase: "Email encryption, digital signing, secure messaging", + formData: { + name: "Email Protection Certificate", + description: + "S/MIME certificate for email encryption and digital signing. RFC 8550 compliant.", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [ + CertKeyUsageType.DIGITAL_SIGNATURE, + CertKeyUsageType.KEY_ENCIPHERMENT, + CertKeyUsageType.KEY_AGREEMENT + ] + }, + extendedKeyUsages: { + requiredUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION], + optionalUsages: [CertExtendedKeyUsageType.EMAIL_PROTECTION] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.EMAIL, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.RSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.RSA.key] + } + } + }, + { + id: TEMPLATE_PRESET_IDS.DUAL_PURPOSE_SERVER, + name: "Dual-Purpose Server Certificate", + description: + "Certificate for services requiring both server and client authentication capabilities", + useCase: "Microservices, service mesh, dual authentication", + formData: { + name: "Dual-Purpose Server Certificate", + description: + "Certificate for services requiring both server and client authentication capabilities", + keyUsages: { + requiredUsages: [CertKeyUsageType.DIGITAL_SIGNATURE], + optionalUsages: [ + CertKeyUsageType.DIGITAL_SIGNATURE, + CertKeyUsageType.KEY_ENCIPHERMENT, + CertKeyUsageType.KEY_AGREEMENT + ] + }, + extendedKeyUsages: { + requiredUsages: [ + CertExtendedKeyUsageType.SERVER_AUTH, + CertExtendedKeyUsageType.CLIENT_AUTH + ], + optionalUsages: [CertExtendedKeyUsageType.SERVER_AUTH, CertExtendedKeyUsageType.CLIENT_AUTH] + }, + validity: { + maxDuration: { + value: 365, + unit: CertDurationUnit.DAYS + } + }, + attributes: [ + { + type: CertSubjectAttributeType.COMMON_NAME, + include: CertSubjectAttributeInclude.OPTIONAL, + value: ["*"] + } + ], + subjectAlternativeNames: [ + { + type: CertSubjectAlternativeNameType.DNS_NAME, + include: CertSanInclude.OPTIONAL, + value: ["*"] + }, + { + type: CertSubjectAlternativeNameType.IP_ADDRESS, + include: CertSanInclude.OPTIONAL, + value: ["*"] + } + ], + signatureAlgorithm: { + allowedAlgorithms: [...ALGORITHM_FAMILIES.ECDSA.signature] + }, + keyAlgorithm: { + allowedKeyTypes: [...ALGORITHM_FAMILIES.ECDSA.key] + } + } + } +];