Minor improvements on self signed certificates

This commit is contained in:
Carlos Monastyrski
2025-11-24 11:20:21 -03:00
parent c0060c1d22
commit d69a2db80c
10 changed files with 491 additions and 237 deletions
@@ -7,11 +7,7 @@ import { EnrollmentType, IssuerType } from "./certificate-profile-types";
export const createCertificateProfileSchema = z
.object({
projectId: z.string().uuid("Project ID must be valid"),
caId: z
.union([z.string().uuid(), z.literal("")])
.optional()
.nullable()
.transform((val) => (val === "" ? null : val)),
caId: z.string().uuid().nullable().optional(),
certificateTemplateId: z.string().uuid(),
slug: z
.string()
@@ -38,60 +34,101 @@ export const createCertificateProfileSchema = z
})
.refine(
(data) => {
// Validate enrollment type configurations
if (data.enrollmentType === EnrollmentType.EST) {
if (!data.estConfig) {
return false;
}
if (data.apiConfig) {
return false;
}
if (data.acmeConfig) {
return false;
}
return !!data.estConfig;
}
if (data.enrollmentType === EnrollmentType.API) {
if (!data.apiConfig) {
return false;
}
if (data.estConfig) {
return false;
}
if (data.acmeConfig) {
return false;
}
}
if (data.enrollmentType === EnrollmentType.ACME) {
if (!data.acmeConfig) {
return false;
}
if (data.estConfig) {
return false;
}
if (data.apiConfig) {
return false;
}
}
if (data.issuerType === IssuerType.CA) {
if (!data.caId) {
return false;
}
}
if (data.issuerType === IssuerType.SELF_SIGNED) {
if (data.caId) {
return false;
}
if (data.enrollmentType !== EnrollmentType.API) {
return false;
}
}
return true;
},
{
message:
"EST enrollment type requires EST configuration and cannot have API configuration. API enrollment type requires API configuration and cannot have EST configuration. CA issuer type requires a CA ID. Self-signed issuer type cannot have a CA ID and only supports API enrollment."
message: "EST enrollment type requires EST configuration"
}
)
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.API) {
return !!data.apiConfig;
}
return true;
},
{
message: "API enrollment type requires API configuration"
}
)
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.ACME) {
return !!data.acmeConfig;
}
return true;
},
{
message: "ACME enrollment type requires ACME configuration"
}
)
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.EST) {
return !data.apiConfig && !data.acmeConfig;
}
return true;
},
{
message: "EST enrollment type cannot have API or ACME configuration"
}
)
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.API) {
return !data.estConfig && !data.acmeConfig;
}
return true;
},
{
message: "API enrollment type cannot have EST or ACME configuration"
}
)
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.ACME) {
return !data.estConfig && !data.apiConfig;
}
return true;
},
{
message: "ACME enrollment type cannot have EST or API configuration"
}
)
.refine(
(data) => {
if (data.issuerType === IssuerType.CA) {
return !!data.caId;
}
return true;
},
{
message: "CA issuer type requires a CA ID"
}
)
.refine(
(data) => {
if (data.issuerType === IssuerType.SELF_SIGNED) {
return !data.caId;
}
return true;
},
{
message: "Self-signed issuer type cannot have a CA ID"
}
)
.refine(
(data) => {
if (data.issuerType === IssuerType.SELF_SIGNED) {
return data.enrollmentType === EnrollmentType.API;
}
return true;
},
{
message: "Self-signed issuer type only supports API enrollment"
}
);
@@ -123,27 +160,34 @@ export const updateCertificateProfileSchema = z
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.EST) {
if (data.apiConfig) {
return false;
}
return !data.apiConfig;
}
if (data.enrollmentType === EnrollmentType.API) {
if (data.estConfig) {
return false;
}
}
if (data.issuerType === IssuerType.SELF_SIGNED) {
if (data.enrollmentType && data.enrollmentType !== EnrollmentType.API) {
return false;
}
}
return true;
},
{
message:
"Cannot have EST config with API enrollment type or API config with EST enrollment type. Self-signed issuer type only supports API enrollment."
message: "EST enrollment type cannot have API configuration"
}
)
.refine(
(data) => {
if (data.enrollmentType === EnrollmentType.API) {
return !data.estConfig;
}
return true;
},
{
message: "API enrollment type cannot have EST configuration"
}
)
.refine(
(data) => {
if (data.issuerType === IssuerType.SELF_SIGNED) {
return !data.enrollmentType || data.enrollmentType === EnrollmentType.API;
}
return true;
},
{
message: "Self-signed issuer type only supports API enrollment"
}
);
@@ -407,11 +407,16 @@ const generateSelfSignedCertificate = async ({
const signatureAlgorithmConfig = signatureAlgorithmToAlgCfg(effectiveSignatureAlgorithm, effectiveKeyAlgorithm);
const notBeforeDate = certificateRequest.notBefore ? new Date(certificateRequest.notBefore) : new Date();
let notAfterDate = new Date(new Date().setFullYear(new Date().getFullYear() + 1));
let notAfterDate: Date;
if (certificateRequest.notAfter) {
notAfterDate = new Date(certificateRequest.notAfter);
} else if (certificateRequest.validity.ttl) {
notAfterDate = new Date(new Date().getTime() + ms(certificateRequest.validity.ttl));
} else {
throw new BadRequestError({
message: "Either notAfter date or TTL must be provided for certificate validity"
});
}
const serialNumber = createSerialNumber();
@@ -458,10 +463,22 @@ const generateSelfSignedCertificate = async ({
...(subjectAlternativeNames
? [
new x509.SubjectAlternativeNameExtension(
certificateRequest.altNames?.map((san) => ({
type: san.type === CertSubjectAlternativeNameType.DNS_NAME ? "dns" : "ip",
value: san.value
})) || [],
certificateRequest.altNames?.map((san) => {
switch (san.type) {
case CertSubjectAlternativeNameType.DNS_NAME:
return { type: "dns" as const, value: san.value };
case CertSubjectAlternativeNameType.IP_ADDRESS:
return { type: "ip" as const, value: san.value };
case CertSubjectAlternativeNameType.EMAIL:
return { type: "email" as const, value: san.value };
case CertSubjectAlternativeNameType.URI:
return { type: "url" as const, value: san.value };
default:
throw new BadRequestError({
message: `Unsupported Subject Alternative Name type: ${san.type as string}`
});
}
}) || [],
false
)
]
@@ -545,7 +562,7 @@ const createSelfSignedCertificateRecord = async ({
(selfSignedResult.certificateSubject.common_name as string) ||
certificateRequest.commonName ||
originalCert?.commonName ||
(isRenewal ? "Renewed Self-signed Certificate" : "Self-signed Certificate");
"";
const altNamesList = selfSignedResult.subjectAlternativeNames.map((san) => san.value).join(",");
@@ -726,8 +743,8 @@ const processSelfSignedCertificate = async ({
await createEncryptedCertificateData({
certificateId: certificateData.id,
certificate: Buffer.from(selfSignedResult.certificate),
privateKey: Buffer.from(selfSignedResult.privateKey),
certificate: selfSignedResult.certificate,
privateKey: selfSignedResult.privateKey,
projectId,
certificateBodyDAL,
certificateSecretDAL,
@@ -1100,10 +1117,25 @@ export const certificateV3ServiceFactory = ({
commonName: certificateOrder.commonName,
keyUsages: certificateOrder.keyUsages,
extendedKeyUsages: certificateOrder.extendedKeyUsages,
subjectAlternativeNames: certificateOrder.altNames.map((san) => ({
type: san.type === "dns" ? CertSubjectAlternativeNameType.DNS_NAME : CertSubjectAlternativeNameType.IP_ADDRESS,
value: san.value
})),
subjectAlternativeNames: certificateOrder.altNames.map((san) => {
let certType: CertSubjectAlternativeNameType;
switch (san.type) {
case "dns":
certType = CertSubjectAlternativeNameType.DNS_NAME;
break;
case "ip":
certType = CertSubjectAlternativeNameType.IP_ADDRESS;
break;
default:
throw new BadRequestError({
message: `Unsupported Subject Alternative Name type: ${san.type as string}`
});
}
return {
type: certType,
value: san.value
};
}),
validity: certificateOrder.validity,
notBefore: certificateOrder.notBefore,
notAfter: certificateOrder.notAfter,
@@ -1216,7 +1248,8 @@ export const certificateV3ServiceFactory = ({
if (profile.enrollmentType !== EnrollmentType.API) {
throw new ForbiddenRequestError({
message: "Certificate is not eligible for renewal: EST certificates cannot be renewed through this endpoint"
message:
"Certificate is not eligible for renewal: Only certificates issued from an API enrollment profile can be renewed through this endpoint"
});
}
}