PKI revamp: frontend implementation

This commit is contained in:
Carlos Monastyrski
2025-10-10 01:50:10 -03:00
parent 4ddce06887
commit 15a6b8da04
57 changed files with 5271 additions and 298 deletions
@@ -1,16 +1,6 @@
import { z } from "zod";
const attributeTypeSchema = z.enum([
"common_name",
"organization_name",
"organization_unit",
"locality",
"state",
"country",
"email",
"street_address",
"postal_code"
]);
const attributeTypeSchema = z.enum(["common_name"]);
const includeTypeSchema = z.enum(["mandatory", "optional", "prohibit"]);
@@ -137,14 +127,6 @@ export const deleteCertificateTemplateV2Schema = z.object({
export const certificateRequestSchema = z.object({
commonName: z.string().optional(),
organization: z.string().optional(),
organizationUnit: z.string().optional(),
locality: z.string().optional(),
state: z.string().optional(),
country: z.string().length(2).optional(),
email: z.string().email().optional(),
streetAddress: z.string().optional(),
postalCode: z.string().optional(),
keyUsages: z.array(z.string()).optional(),
extendedKeyUsages: z.array(z.string()).optional(),
subjectAlternativeNames: z
@@ -60,14 +60,6 @@ describe("CertificateTemplateV2Service", () => {
type: "common_name",
include: "mandatory",
value: ["example.com"]
},
{
type: "organization_name",
include: "optional"
},
{
type: "country",
include: "prohibit"
}
],
keyUsages: {
@@ -416,7 +408,6 @@ describe("CertificateTemplateV2Service", () => {
describe("validateCertificateRequest", () => {
const validRequest: TCertificateRequest = {
commonName: "example.com",
organization: "Example Corp",
keyUsages: ["digital_signature", "key_encipherment"],
extendedKeyUsages: ["server_auth"],
subjectAlternativeNames: [
@@ -458,15 +449,6 @@ describe("CertificateTemplateV2Service", () => {
expect(result.errors).toContain("common_name is mandatory but not provided in request");
});
it("should detect prohibited attributes", async () => {
const invalidRequest = { ...validRequest, country: "US" };
const result = await service.validateCertificateRequest("template-123", invalidRequest);
expect(result.isValid).toBe(false);
expect(result.errors).toContain("country is prohibited by template policy");
});
it("should validate attribute values against allowed list", async () => {
const invalidRequest = { ...validRequest, commonName: "forbidden.com" };
@@ -614,14 +596,6 @@ describe("CertificateTemplateV2Service", () => {
}
});
it("should allow optional attributes when not provided", async () => {
const requestWithoutOrg = { ...validRequest, organization: undefined };
const result = await service.validateCertificateRequest("template-123", requestWithoutOrg);
expect(result.isValid).toBe(true);
});
it("should allow optional key usages and extended key usages", async () => {
const requestWithOptionalUsages = {
...validRequest,
@@ -805,21 +779,14 @@ describe("CertificateTemplateV2Service", () => {
expect(result.isValid).toBe(true);
});
it("should handle template with all fields prohibited", async () => {
it("should handle template with SAN fields prohibited", async () => {
const prohibitTemplate = {
...sampleTemplate,
attributes: [
{
type: "organization_name",
include: "prohibit" as const
},
{
type: "locality",
include: "prohibit" as const
},
{
type: "country",
include: "prohibit" as const
type: "common_name",
include: "mandatory" as const,
value: ["example.com"]
}
],
keyUsages: {
@@ -845,9 +812,6 @@ describe("CertificateTemplateV2Service", () => {
const requestWithProhibited = {
commonName: "example.com",
organization: "Test Org",
locality: "Test City",
country: "US",
keyUsages: ["digital_signature"],
extendedKeyUsages: ["server_auth"],
subjectAlternativeNames: [
@@ -859,9 +823,6 @@ describe("CertificateTemplateV2Service", () => {
const result = await service.validateCertificateRequest("template-123", requestWithProhibited);
expect(result.isValid).toBe(false);
expect(result.errors).toContain("organization_name is prohibited by template policy");
expect(result.errors).toContain("locality is prohibited by template policy");
expect(result.errors).toContain("country is prohibited by template policy");
expect(result.errors).toContain("email SAN is prohibited by template policy");
expect(result.errors).toContain("uri SAN is prohibited by template policy");
});
@@ -874,16 +835,6 @@ describe("CertificateTemplateV2Service", () => {
type: "common_name",
include: "mandatory" as const,
value: ["example.com", "test.com"]
},
{
type: "organization_name",
include: "optional" as const,
value: ["Example Corp", "Test Corp"]
},
{
type: "country",
include: "mandatory" as const,
value: ["US", "CA"]
}
],
subjectAlternativeNames: [
@@ -898,8 +849,6 @@ describe("CertificateTemplateV2Service", () => {
const validConstrainedRequest = {
commonName: "example.com",
organization: "Example Corp",
country: "US",
keyUsages: ["digital_signature", "key_encipherment"],
extendedKeyUsages: ["server_auth"],
validity: { ttl: "30d" }
@@ -910,8 +859,6 @@ describe("CertificateTemplateV2Service", () => {
const invalidConstrainedRequest = {
commonName: "forbidden.com",
organization: "Forbidden Corp",
country: "FR",
keyUsages: ["digital_signature", "key_encipherment"],
extendedKeyUsages: ["server_auth"],
validity: { ttl: "30d" }
@@ -920,10 +867,6 @@ describe("CertificateTemplateV2Service", () => {
const invalidResult = await service.validateCertificateRequest("template-123", invalidConstrainedRequest);
expect(invalidResult.isValid).toBe(false);
expect(invalidResult.errors).toContain("common_name value 'forbidden.com' is not in allowed values list");
expect(invalidResult.errors).toContain(
"organization_name value 'Forbidden Corp' does not match allowed patterns: Example Corp, Test Corp"
);
expect(invalidResult.errors).toContain("country value 'FR' is not in allowed values list");
});
it("should validate SAN value constraints with multiple types", async () => {
@@ -72,22 +72,6 @@ export const certificateTemplateV2ServiceFactory = ({
switch (attrType) {
case "common_name":
return request.commonName;
case "organization_name":
return request.organization;
case "organization_unit":
return request.organizationUnit;
case "locality":
return request.locality;
case "state":
return request.state;
case "country":
return request.country;
case "email":
return request.email;
case "street_address":
return request.streetAddress;
case "postal_code":
return request.postalCode;
default:
return undefined;
}
@@ -145,8 +129,25 @@ export const certificateTemplateV2ServiceFactory = ({
if (!requestValue) {
errors.push(`${attrPolicy.type} is mandatory but not provided in request`);
} else if (attrPolicy.value && attrPolicy.value.length > 0) {
if (!attrPolicy.value.includes(requestValue)) {
errors.push(`${attrPolicy.type} value '${requestValue}' is not in allowed values list`);
// Check if the request value matches any allowed pattern
const hasWildcards = attrPolicy.value.some((val) => val.includes("*"));
const isValidValue = attrPolicy.value.some((allowedValue) => {
if (allowedValue.includes("*")) {
// Handle wildcard patterns
const pattern = allowedValue.replace(/\./g, "\\.").replace(/\*/g, ".*");
const regex = new RE2(`^${pattern}$`);
return regex.test(requestValue);
}
return allowedValue === requestValue;
});
if (!isValidValue) {
if (hasWildcards) {
errors.push(
`${attrPolicy.type} value '${requestValue}' does not match allowed patterns: ${attrPolicy.value.join(", ")}`
);
} else {
errors.push(`${attrPolicy.type} value '${requestValue}' is not in allowed values list`);
}
}
}
}
@@ -156,18 +157,24 @@ export const certificateTemplateV2ServiceFactory = ({
}
if (attrPolicy.include === "optional" && requestValue && attrPolicy.value && attrPolicy.value.length > 0) {
const hasWildcards = attrPolicy.value.some((val) => val.includes("*"));
const isValidValue = attrPolicy.value.some((allowedValue) => {
if (allowedValue.includes("*")) {
const pattern = allowedValue.replace(/\*/g, "[^.]*");
// Handle wildcard patterns - escape dots and replace * with .*
const pattern = allowedValue.replace(/\./g, "\\.").replace(/\*/g, ".*");
const regex = new RE2(`^${pattern}$`);
return regex.test(requestValue);
}
return allowedValue === requestValue;
});
if (!isValidValue) {
errors.push(
`${attrPolicy.type} value '${requestValue}' does not match allowed patterns: ${attrPolicy.value.join(", ")}`
);
if (hasWildcards) {
errors.push(
`${attrPolicy.type} value '${requestValue}' does not match allowed patterns: ${attrPolicy.value.join(", ")}`
);
} else {
errors.push(`${attrPolicy.type} value '${requestValue}' is not in allowed values list`);
}
}
}
});
@@ -216,9 +223,25 @@ export const certificateTemplateV2ServiceFactory = ({
if (requestSans.length === 0) {
errors.push(`${sanPolicy.type} SAN is mandatory but not provided in request`);
} else if (sanPolicy.value && sanPolicy.value.length > 0) {
const hasWildcards = sanPolicy.value.some((val) => val.includes("*"));
requestSans.forEach((san) => {
if (!sanPolicy.value!.includes(san.value)) {
errors.push(`${sanPolicy.type} SAN value '${san.value}' is not in allowed values list`);
const isValidValue = sanPolicy.value!.some((allowedValue) => {
if (allowedValue.includes("*")) {
// Handle wildcard patterns - escape dots and replace * with .*
const pattern = allowedValue.replace(/\./g, "\\.").replace(/\*/g, ".*");
const regex = new RE2(`^${pattern}$`);
return regex.test(san.value);
}
return allowedValue === san.value;
});
if (!isValidValue) {
if (hasWildcards) {
errors.push(
`${sanPolicy.type} SAN value '${san.value}' does not match allowed patterns: ${sanPolicy.value!.join(", ")}`
);
} else {
errors.push(`${sanPolicy.type} SAN value '${san.value}' is not in allowed values list`);
}
}
});
}
@@ -229,19 +252,25 @@ export const certificateTemplateV2ServiceFactory = ({
}
if (sanPolicy.include === "optional" && sanPolicy.value && sanPolicy.value.length > 0) {
const hasWildcards = sanPolicy.value.some((val) => val.includes("*"));
requestSans.forEach((san) => {
const isValidValue = sanPolicy.value!.some((allowedValue) => {
if (allowedValue.includes("*")) {
const pattern = allowedValue.replace(/\*/g, "[^.]*");
// Handle wildcard patterns - escape dots and replace * with .*
const pattern = allowedValue.replace(/\./g, "\\.").replace(/\*/g, ".*");
const regex = new RE2(`^${pattern}$`);
return regex.test(san.value);
}
return allowedValue === san.value;
});
if (!isValidValue) {
errors.push(
`${sanPolicy.type} SAN value '${san.value}' does not match allowed patterns: ${sanPolicy.value!.join(", ")}`
);
if (hasWildcards) {
errors.push(
`${sanPolicy.type} SAN value '${san.value}' does not match allowed patterns: ${sanPolicy.value!.join(", ")}`
);
} else {
errors.push(`${sanPolicy.type} SAN value '${san.value}' is not in allowed values list`);
}
}
});
}
@@ -2,16 +2,7 @@ import { TCertificateTemplatesV2, TCertificateTemplatesV2Insert } from "@app/db/
export interface TTemplateV2Policy {
attributes: Array<{
type:
| "common_name"
| "organization_name"
| "organization_unit"
| "locality"
| "state"
| "country"
| "email"
| "street_address"
| "postal_code";
type: "common_name";
include: "mandatory" | "optional" | "prohibit";
value?: string[];
}>;
@@ -97,14 +88,6 @@ export type TCertificateTemplateV2Update = Partial<
export interface TCertificateRequest {
commonName?: string;
organization?: string;
organizationUnit?: string;
locality?: string;
state?: string;
country?: string;
email?: string;
streetAddress?: string;
postalCode?: string;
keyUsages?: string[];
extendedKeyUsages?: string[];
subjectAlternativeNames?: Array<{