Merge pull request #4867 from Infisical/feat/ENG-3969

Add PKI template presets
This commit is contained in:
carlosmonastyrski
2025-11-14 16:10:16 -03:00
committed by GitHub
5 changed files with 605 additions and 40 deletions
@@ -10,7 +10,7 @@ import {
ProjectPermissionSub ProjectPermissionSub
} from "@app/context/ProjectPermissionContext/types"; } from "@app/context/ProjectPermissionContext/types";
import { useDeleteCertificateTemplateV2WithPolicies } from "@app/hooks/api/certificateTemplates/mutations"; 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 { CreateTemplateModal } from "./CreateTemplateModal";
import { TemplateList } from "./TemplateList"; import { TemplateList } from "./TemplateList";
@@ -84,7 +84,12 @@ export const CertificateTemplatesV2Tab = () => {
<TemplateList onEditTemplate={handleEditTemplate} onDeleteTemplate={handleDeleteTemplate} /> <TemplateList onEditTemplate={handleEditTemplate} onDeleteTemplate={handleDeleteTemplate} />
<CreateTemplateModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} /> <CreateTemplateModal
isOpen={isCreateModalOpen}
onClose={() => {
setIsCreateModalOpen(false);
}}
/>
{selectedTemplate && ( {selectedTemplate && (
<> <>
@@ -36,13 +36,18 @@ import {
CertDurationUnit, CertDurationUnit,
CertExtendedKeyUsageType, CertExtendedKeyUsageType,
CertKeyUsageType, CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType, CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType, CertSubjectAttributeType,
SAN_INCLUDE_OPTIONS, SAN_INCLUDE_OPTIONS,
SAN_TYPE_OPTIONS, SAN_TYPE_OPTIONS,
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS, SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS,
SUBJECT_ATTRIBUTE_TYPE_OPTIONS SUBJECT_ATTRIBUTE_TYPE_OPTIONS,
TEMPLATE_PRESET_IDS,
type TemplatePresetId
} from "./shared/certificate-constants"; } from "./shared/certificate-constants";
import { CERTIFICATE_TEMPLATE_PRESETS } from "./shared/template-presets";
import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared"; import { KeyUsagesSection, TemplateFormData, templateSchema } from "./shared";
export type FormData = TemplateFormData; export type FormData = TemplateFormData;
@@ -91,7 +96,7 @@ const SIGNATURE_ALGORITHMS = [
"SHA256-ECDSA", "SHA256-ECDSA",
"SHA384-ECDSA", "SHA384-ECDSA",
"SHA512-ECDSA" "SHA512-ECDSA"
]; ] as const;
const KEY_ALGORITHMS = [ const KEY_ALGORITHMS = [
"RSA-2048", "RSA-2048",
@@ -100,7 +105,7 @@ const KEY_ALGORITHMS = [
"ECDSA-P256", "ECDSA-P256",
"ECDSA-P384", "ECDSA-P384",
"ECDSA-P521" "ECDSA-P521"
]; ] as const;
export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => { export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create" }: Props) => {
const { currentProject } = useProject(); const { currentProject } = useProject();
@@ -117,7 +122,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
subj.allowed.forEach((allowedValue) => { subj.allowed.forEach((allowedValue) => {
attributes.push({ attributes.push({
type: subj.type as CertSubjectAttributeType, type: subj.type as CertSubjectAttributeType,
include: "optional", include: CertSubjectAttributeInclude.OPTIONAL,
value: [allowedValue] value: [allowedValue]
}); });
}); });
@@ -126,7 +131,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
subj.denied.forEach((deniedValue) => { subj.denied.forEach((deniedValue) => {
attributes.push({ attributes.push({
type: subj.type as CertSubjectAttributeType, type: subj.type as CertSubjectAttributeType,
include: "prohibit", include: CertSubjectAttributeInclude.PROHIBIT,
value: [deniedValue] value: [deniedValue]
}); });
}); });
@@ -141,7 +146,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.required.forEach((requiredValue) => { san.required.forEach((requiredValue) => {
subjectAlternativeNames.push({ subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType, type: san.type as CertSubjectAlternativeNameType,
include: "mandatory", include: CertSanInclude.MANDATORY,
value: [requiredValue] value: [requiredValue]
}); });
}); });
@@ -150,7 +155,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.allowed.forEach((allowedValue) => { san.allowed.forEach((allowedValue) => {
subjectAlternativeNames.push({ subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType, type: san.type as CertSubjectAlternativeNameType,
include: "optional", include: CertSanInclude.OPTIONAL,
value: [allowedValue] value: [allowedValue]
}); });
}); });
@@ -159,7 +164,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
san.denied.forEach((deniedValue) => { san.denied.forEach((deniedValue) => {
subjectAlternativeNames.push({ subjectAlternativeNames.push({
type: san.type as CertSubjectAlternativeNameType, type: san.type as CertSubjectAlternativeNameType,
include: "prohibit", include: CertSanInclude.PROHIBIT,
value: [deniedValue] value: [deniedValue]
}); });
}); });
@@ -219,6 +224,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
}; };
return { return {
preset: TEMPLATE_PRESET_IDS.CUSTOM,
name: templateData.name || "", name: templateData.name || "",
description: templateData.description || "", description: templateData.description || "",
attributes, attributes,
@@ -231,25 +237,30 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
}; };
}; };
const getDefaultValues = (): FormData => ({ const getDefaultValues = (): FormData & { preset: TemplatePresetId } => {
name: "", return {
description: "", preset: TEMPLATE_PRESET_IDS.CUSTOM,
attributes: [], name: "",
keyUsages: { requiredUsages: [], optionalUsages: [] }, description: "",
extendedKeyUsages: { requiredUsages: [], optionalUsages: [] }, attributes: [],
subjectAlternativeNames: [], keyUsages: { requiredUsages: [], optionalUsages: [] },
validity: { extendedKeyUsages: { requiredUsages: [], optionalUsages: [] },
maxDuration: { value: 365, unit: CertDurationUnit.DAYS } subjectAlternativeNames: [],
}, validity: {
signatureAlgorithm: { maxDuration: { value: 365, unit: CertDurationUnit.DAYS }
allowedAlgorithms: [] },
}, signatureAlgorithm: {
keyAlgorithm: { allowedAlgorithms: []
allowedKeyTypes: [] },
} keyAlgorithm: {
}); allowedKeyTypes: []
}
};
};
const { control, handleSubmit, reset, watch, setValue, formState } = useForm<FormData>({ const { control, handleSubmit, reset, watch, setValue, formState } = useForm<
FormData & { preset: TemplatePresetId }
>({
resolver: zodResolver(templateSchema), resolver: zodResolver(templateSchema),
defaultValues: getDefaultValues(), defaultValues: getDefaultValues(),
mode: "onChange", mode: "onChange",
@@ -260,7 +271,7 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
useEffect(() => { useEffect(() => {
if (isEdit && template) { if (isEdit && template) {
const convertedData = convertApiToUiFormat(template); const convertedData = convertApiToUiFormat(template);
reset(convertedData); reset({ ...convertedData, preset: TEMPLATE_PRESET_IDS.CUSTOM });
} else if (!isEdit) { } else if (!isEdit) {
reset(getDefaultValues()); reset(getDefaultValues());
} }
@@ -273,6 +284,37 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: [], requiredUsages: [],
optionalUsages: [] 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 = < const consolidateByType = <
T extends { type: string; allowed?: string[]; required?: string[]; denied?: string[] } 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) => { data.attributes?.map((attr) => {
const result: AttributeTransform = { type: attr.type }; 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; 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; result.denied = attr.value;
} }
@@ -322,11 +372,11 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
data.subjectAlternativeNames?.map((san) => { data.subjectAlternativeNames?.map((san) => {
const result: SanTransform = { type: san.type }; 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; 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; 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; result.denied = san.value;
} }
@@ -464,11 +514,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: ["*"] value: ["*"]
}; };
setValue("attributes", [...watchedAttributes, newAttribute]); setValue("attributes", [...watchedAttributes, newAttribute]);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}; };
const removeAttribute = (index: number) => { const removeAttribute = (index: number) => {
const newAttributes = watchedAttributes.filter((_, i) => i !== index); const newAttributes = watchedAttributes.filter((_, i) => i !== index);
setValue("attributes", newAttributes); setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}; };
const addSan = () => { const addSan = () => {
@@ -478,11 +536,19 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: ["*"] value: ["*"]
}; };
setValue("subjectAlternativeNames", [...watchedSans, newSan]); setValue("subjectAlternativeNames", [...watchedSans, newSan]);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}; };
const removeSan = (index: number) => { const removeSan = (index: number) => {
const newSans = watchedSans.filter((_, i) => i !== index); const newSans = watchedSans.filter((_, i) => i !== index);
setValue("subjectAlternativeNames", newSans); setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}; };
const handleKeyUsagesChange = (usages: { const handleKeyUsagesChange = (usages: {
@@ -493,6 +559,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: usages.requiredUsages, requiredUsages: usages.requiredUsages,
optionalUsages: usages.optionalUsages optionalUsages: usages.optionalUsages
}); });
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}; };
const handleExtendedKeyUsagesChange = (usages: { const handleExtendedKeyUsagesChange = (usages: {
@@ -503,6 +573,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
requiredUsages: usages.requiredUsages, requiredUsages: usages.requiredUsages,
optionalUsages: usages.optionalUsages optionalUsages: usages.optionalUsages
}); });
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}; };
return ( return (
@@ -555,6 +629,33 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="preset"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Template Preset"
isError={Boolean(error)}
errorText={error?.message}
>
<Select
value={field.value}
onValueChange={handlePresetChange}
className="w-full"
position="popper"
dropdownContainerClassName="max-w-none"
>
<SelectItem value={TEMPLATE_PRESET_IDS.CUSTOM}>Custom</SelectItem>
{CERTIFICATE_TEMPLATE_PRESETS.map((preset) => (
<SelectItem key={preset.id} value={preset.id}>
{preset.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
</div> </div>
<AccordionItem value="attributes"> <AccordionItem value="attributes">
<AccordionTrigger>Subject Attributes</AccordionTrigger> <AccordionTrigger>Subject Attributes</AccordionTrigger>
@@ -595,6 +696,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
type: value as CertSubjectAttributeType type: value as CertSubjectAttributeType
}; };
setValue("attributes", newAttributes); setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}} }}
className="w-48" className="w-48"
> >
@@ -615,6 +720,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number] value as (typeof SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS)[number]
}; };
setValue("attributes", newAttributes); setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}} }}
className="w-32" className="w-32"
> >
@@ -635,6 +744,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: e.target.value.trim() ? [e.target.value.trim()] : [] value: e.target.value.trim() ? [e.target.value.trim()] : []
}; };
setValue("attributes", newAttributes); setValue("attributes", newAttributes);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}} }}
className={`flex-1 ${ className={`flex-1 ${
attr.value && attr.value.length > 0 && attr.value[0] === "" 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 type: value as CertSubjectAlternativeNameType
}; };
setValue("subjectAlternativeNames", newSans); setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}} }}
className="w-36" className="w-36"
> >
@@ -720,6 +837,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
include: value as (typeof SAN_INCLUDE_OPTIONS)[number] include: value as (typeof SAN_INCLUDE_OPTIONS)[number]
}; };
setValue("subjectAlternativeNames", newSans); setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}} }}
className="w-32" className="w-32"
> >
@@ -740,6 +861,10 @@ export const CreateTemplateModal = ({ isOpen, onClose, template, mode = "create"
value: e.target.value.trim() ? [e.target.value.trim()] : [] value: e.target.value.trim() ? [e.target.value.trim()] : []
}; };
setValue("subjectAlternativeNames", newSans); setValue("subjectAlternativeNames", newSans);
if (watchedPreset !== TEMPLATE_PRESET_IDS.CUSTOM) {
setValue("preset", TEMPLATE_PRESET_IDS.CUSTOM);
}
}} }}
className={`flex-1 ${ className={`flex-1 ${
san.value && san.value.length > 0 && san.value[0] === "" san.value && san.value.length > 0 && san.value[0] === ""
@@ -150,8 +150,19 @@ export const SUBJECT_ATTRIBUTE_TYPE_OPTIONS = Object.values(CertSubjectAttribute
export const ATTRIBUTE_RULE_OPTIONS = Object.values(CertAttributeRule); export const ATTRIBUTE_RULE_OPTIONS = Object.values(CertAttributeRule);
export const SAN_EFFECT_OPTIONS = Object.values(CertSanEffect); export const SAN_EFFECT_OPTIONS = Object.values(CertSanEffect);
export const SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS = ["optional", "prohibit"] as const; export enum CertSubjectAttributeInclude {
export const SAN_INCLUDE_OPTIONS = ["mandatory", "optional", "prohibit"] as const; 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 = { export const USAGE_STATES = {
REQUIRED: "required", REQUIRED: "required",
@@ -239,3 +250,27 @@ export const mapTemplateKeyAlgorithmToApi = (templateFormat: string): string =>
}; };
return mapping[templateFormat] || templateFormat; 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;
@@ -4,21 +4,22 @@ import {
CertDurationUnit, CertDurationUnit,
CertExtendedKeyUsageType, CertExtendedKeyUsageType,
CertKeyUsageType, CertKeyUsageType,
CertSanInclude,
CertSubjectAlternativeNameType, CertSubjectAlternativeNameType,
CertSubjectAttributeInclude,
CertSubjectAttributeType, CertSubjectAttributeType,
SAN_INCLUDE_OPTIONS, TEMPLATE_PRESET_IDS
SUBJECT_ATTRIBUTE_INCLUDE_OPTIONS
} from "./certificate-constants"; } from "./certificate-constants";
export const uiAttributeSchema = z.object({ export const uiAttributeSchema = z.object({
type: z.nativeEnum(CertSubjectAttributeType), 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")) value: z.array(z.string().min(1, "Value cannot be empty"))
}); });
export const uiSanSchema = z.object({ export const uiSanSchema = z.object({
type: z.nativeEnum(CertSubjectAlternativeNameType), 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")) 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") .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({ export const templateSchema = z.object({
preset: uiPresetSchema,
name: z name: z
.string() .string()
.trim() .trim()
@@ -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<TemplateFormData, "preset">;
}
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]
}
}
}
];