diff --git a/backend/src/services/certificate-authority/certificate-authority-validators.ts b/backend/src/services/certificate-authority/certificate-authority-validators.ts index fab62ddbf..3fa7e8d5e 100644 --- a/backend/src/services/certificate-authority/certificate-authority-validators.ts +++ b/backend/src/services/certificate-authority/certificate-authority-validators.ts @@ -15,10 +15,15 @@ export const validateAltNameField = z .trim() .refine( (name) => { - return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name); + return ( + isFQDN(name, { allow_wildcard: true, require_tld: false }) || + z.string().url().safeParse(name).success || + z.string().email().safeParse(name).success || + isValidIp(name) + ); }, { - message: "SAN must be a valid hostname, email address, or IP address" + message: "SAN must be a valid hostname, email address, IP address or URL" } ); @@ -39,10 +44,15 @@ export const validateAltNamesField = z if (data === "") return true; // Split and validate each alt name return data.split(", ").every((name) => { - return isFQDN(name, { allow_wildcard: true }) || z.string().email().safeParse(name).success || isValidIp(name); + return ( + isFQDN(name, { allow_wildcard: true, require_tld: false }) || + z.string().url().safeParse(name).success || + z.string().email().safeParse(name).success || + isValidIp(name) + ); }); }, { - message: "Each alt name must be a valid hostname or email address" + message: "Each alt name must be a valid hostname, email address, IP address or URL" } ); diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts index 80d2842fa..eace4e47a 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-fns.ts @@ -152,7 +152,7 @@ export const InternalCertificateAuthorityFns = ({ extensions.push(extendedKeyUsagesExtension); } - let altNamesArray: { type: "email" | "dns"; value: string }[] = []; + let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = []; if (subscriber.subjectAlternativeNames?.length) { altNamesArray = subscriber.subjectAlternativeNames.map((altName) => { @@ -160,10 +160,18 @@ export const InternalCertificateAuthorityFns = ({ return { type: "email", value: altName }; } - if (isFQDN(altName, { allow_wildcard: true })) { + if (isFQDN(altName, { allow_wildcard: true, require_tld: false })) { return { type: "dns", value: altName }; } + if (z.string().url().safeParse(altName).success) { + return { type: "url", value: altName }; + } + + if (z.string().ip().safeParse(altName).success) { + return { type: "ip", value: altName }; + } + throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` }); }); @@ -418,7 +426,7 @@ export const InternalCertificateAuthorityFns = ({ ); } - let altNamesArray: { type: "email" | "dns"; value: string }[] = []; + let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = []; if (altNames) { altNamesArray = altNames.split(",").map((altName) => { @@ -426,10 +434,18 @@ export const InternalCertificateAuthorityFns = ({ return { type: "email", value: altName }; } - if (isFQDN(altName, { allow_wildcard: true })) { + if (isFQDN(altName, { allow_wildcard: true, require_tld: false })) { return { type: "dns", value: altName }; } + if (z.string().url().safeParse(altName).success) { + return { type: "url", value: altName }; + } + + if (z.string().ip().safeParse(altName).success) { + return { type: "ip", value: altName }; + } + throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` }); });