mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-04 23:26:20 +00:00
Merge pull request #4313 from JuliusMieliauskas/fix-san-extension-contents
FIX: SAN extension field in certificate issuance
This commit is contained in:
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { isValidIp } from "@app/lib/ip";
|
import { isValidIp } from "@app/lib/ip";
|
||||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
import { isFQDN } from "@app/lib/validator/validate-url";
|
||||||
|
import { TAltNameMapping, TAltNameType } from "@app/services/certificate/certificate-types";
|
||||||
|
|
||||||
const isValidDate = (dateString: string) => {
|
const isValidDate = (dateString: string) => {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@@ -56,3 +57,19 @@ export const validateAltNamesField = z
|
|||||||
message: "Each alt name must be a valid hostname, email address, IP address or URL"
|
message: "Each alt name must be a valid hostname, email address, IP address or URL"
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const validateAndMapAltNameType = (name: string): TAltNameMapping | null => {
|
||||||
|
if (isFQDN(name, { allow_wildcard: true, require_tld: false })) {
|
||||||
|
return { type: TAltNameType.DNS, value: name };
|
||||||
|
}
|
||||||
|
if (z.string().url().safeParse(name).success) {
|
||||||
|
return { type: TAltNameType.URL, value: name };
|
||||||
|
}
|
||||||
|
if (z.string().email().safeParse(name).success) {
|
||||||
|
return { type: TAltNameType.EMAIL, value: name };
|
||||||
|
}
|
||||||
|
if (isValidIp(name)) {
|
||||||
|
return { type: TAltNameType.IP, value: name };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|||||||
+13
-37
@@ -1,7 +1,6 @@
|
|||||||
/* eslint-disable no-bitwise */
|
/* eslint-disable no-bitwise */
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import RE2 from "re2";
|
import RE2 from "re2";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas";
|
import { TCertificateTemplates, TPkiSubscribers } from "@app/db/schemas";
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "@app/ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
@@ -9,7 +8,6 @@ import { getConfig } from "@app/lib/config/env";
|
|||||||
import { crypto } from "@app/lib/crypto/cryptography";
|
import { crypto } from "@app/lib/crypto/cryptography";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
|
||||||
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||||
@@ -17,7 +15,8 @@ import {
|
|||||||
CertExtendedKeyUsage,
|
CertExtendedKeyUsage,
|
||||||
CertKeyAlgorithm,
|
CertKeyAlgorithm,
|
||||||
CertKeyUsage,
|
CertKeyUsage,
|
||||||
CertStatus
|
CertStatus,
|
||||||
|
TAltNameMapping
|
||||||
} from "@app/services/certificate/certificate-types";
|
} from "@app/services/certificate/certificate-types";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -34,6 +33,7 @@ import {
|
|||||||
} from "../certificate-authority-fns";
|
} from "../certificate-authority-fns";
|
||||||
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
import { TCertificateAuthoritySecretDALFactory } from "../certificate-authority-secret-dal";
|
||||||
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
|
import { TIssueCertWithTemplateDTO } from "./internal-certificate-authority-types";
|
||||||
|
import { validateAndMapAltNameType } from "../certificate-authority-validators";
|
||||||
|
|
||||||
type TInternalCertificateAuthorityFnsDeps = {
|
type TInternalCertificateAuthorityFnsDeps = {
|
||||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa" | "findById">;
|
||||||
@@ -152,27 +152,15 @@ export const InternalCertificateAuthorityFns = ({
|
|||||||
extensions.push(extendedKeyUsagesExtension);
|
extensions.push(extendedKeyUsagesExtension);
|
||||||
}
|
}
|
||||||
|
|
||||||
let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = [];
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
|
|
||||||
if (subscriber.subjectAlternativeNames?.length) {
|
if (subscriber.subjectAlternativeNames?.length) {
|
||||||
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
|
altNamesArray = subscriber.subjectAlternativeNames.map((altName) => {
|
||||||
if (z.string().email().safeParse(altName).success) {
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
return { type: "email", value: altName };
|
if (!altNameType) {
|
||||||
|
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
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}` });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
@@ -426,27 +414,15 @@ export const InternalCertificateAuthorityFns = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let altNamesArray: { type: "email" | "dns" | "ip" | "url"; value: string }[] = [];
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
altNamesArray = altNames.split(",").map((altName) => {
|
altNamesArray = altNames.split(",").map((altName) => {
|
||||||
if (z.string().email().safeParse(altName).success) {
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
return { type: "email", value: altName };
|
if (!altNameType) {
|
||||||
|
throw new BadRequestError({ message: `Invalid SAN entry: ${altName}` });
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
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}` });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
|||||||
+29
-52
@@ -2,7 +2,6 @@
|
|||||||
import { ForbiddenError, subject } from "@casl/ability";
|
import { ForbiddenError, subject } from "@casl/ability";
|
||||||
import * as x509 from "@peculiar/x509";
|
import * as x509 from "@peculiar/x509";
|
||||||
import slugify from "@sindresorhus/slugify";
|
import slugify from "@sindresorhus/slugify";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
import { ActionProjectType, TableName, TCertificateAuthorities, TCertificateTemplates } from "@app/db/schemas";
|
||||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
||||||
@@ -18,7 +17,6 @@ import { crypto } from "@app/lib/crypto/cryptography";
|
|||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||||
import { isFQDN } from "@app/lib/validator/validate-url";
|
|
||||||
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||||
@@ -34,7 +32,8 @@ import {
|
|||||||
CertExtendedKeyUsageOIDToName,
|
CertExtendedKeyUsageOIDToName,
|
||||||
CertKeyAlgorithm,
|
CertKeyAlgorithm,
|
||||||
CertKeyUsage,
|
CertKeyUsage,
|
||||||
CertStatus
|
CertStatus,
|
||||||
|
TAltNameMapping
|
||||||
} from "../../certificate/certificate-types";
|
} from "../../certificate/certificate-types";
|
||||||
import { TCertificateTemplateDALFactory } from "../../certificate-template/certificate-template-dal";
|
import { TCertificateTemplateDALFactory } from "../../certificate-template/certificate-template-dal";
|
||||||
import { validateCertificateDetailsAgainstTemplate } from "../../certificate-template/certificate-template-fns";
|
import { validateCertificateDetailsAgainstTemplate } from "../../certificate-template/certificate-template-fns";
|
||||||
@@ -69,6 +68,7 @@ import {
|
|||||||
TSignIntermediateDTO,
|
TSignIntermediateDTO,
|
||||||
TUpdateCaDTO
|
TUpdateCaDTO
|
||||||
} from "./internal-certificate-authority-types";
|
} from "./internal-certificate-authority-types";
|
||||||
|
import { validateAndMapAltNameType } from "../certificate-authority-validators";
|
||||||
|
|
||||||
type TInternalCertificateAuthorityServiceFactoryDep = {
|
type TInternalCertificateAuthorityServiceFactoryDep = {
|
||||||
certificateAuthorityDAL: Pick<
|
certificateAuthorityDAL: Pick<
|
||||||
@@ -1364,34 +1364,18 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let altNamesArray: {
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
type: "email" | "dns";
|
|
||||||
value: string;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
altNamesArray = altNames
|
altNamesArray = altNames
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((name) => name.trim())
|
.map((name) => name.trim())
|
||||||
.map((altName) => {
|
.map((altName): TAltNameMapping => {
|
||||||
// check if the altName is a valid email
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
if (z.string().email().safeParse(altName).success) {
|
if (!altNameType) {
|
||||||
return {
|
throw new Error(`Invalid altName: ${altName}`);
|
||||||
type: "email",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
// check if the altName is a valid hostname
|
|
||||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
||||||
return {
|
|
||||||
type: "dns",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
|
|
||||||
throw new Error(`Invalid altName: ${altName}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
@@ -1766,34 +1750,22 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
let altNamesFromCsr: string = "";
|
let altNamesFromCsr: string = "";
|
||||||
let altNamesArray: {
|
let altNamesArray: TAltNameMapping[] = [];
|
||||||
type: "email" | "dns";
|
|
||||||
value: string;
|
|
||||||
}[] = [];
|
|
||||||
if (altNames) {
|
if (altNames) {
|
||||||
altNamesArray = altNames
|
altNamesArray = altNames
|
||||||
.split(",")
|
.split(",")
|
||||||
.map((name) => name.trim())
|
.map((name) => name.trim())
|
||||||
.map((altName) => {
|
.map((altName): TAltNameMapping => {
|
||||||
// check if the altName is a valid email
|
const altNameType = validateAndMapAltNameType(altName);
|
||||||
if (z.string().email().safeParse(altName).success) {
|
if (!altNameType) {
|
||||||
return {
|
throw new Error(`Invalid altName: ${altName}`);
|
||||||
type: "email",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
return altNameType;
|
||||||
// check if the altName is a valid hostname
|
|
||||||
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
||||||
return {
|
|
||||||
type: "dns",
|
|
||||||
value: altName
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
|
|
||||||
throw new Error(`Invalid altName: ${altName}`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
||||||
|
extensions.push(altNamesExtension);
|
||||||
} else {
|
} else {
|
||||||
// attempt to read from CSR if altNames is not explicitly provided
|
// attempt to read from CSR if altNames is not explicitly provided
|
||||||
const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17");
|
const sanExtension = csrObj.extensions.find((ext) => ext.type === "2.5.29.17");
|
||||||
@@ -1801,11 +1773,16 @@ export const internalCertificateAuthorityServiceFactory = ({
|
|||||||
const sanNames = new x509.GeneralNames(sanExtension.value);
|
const sanNames = new x509.GeneralNames(sanExtension.value);
|
||||||
|
|
||||||
altNamesArray = sanNames.items
|
altNamesArray = sanNames.items
|
||||||
.filter((value) => value.type === "email" || value.type === "dns")
|
.filter(
|
||||||
.map((name) => ({
|
(value) => value.type === "email" || value.type === "dns" || value.type === "url" || value.type === "ip"
|
||||||
type: name.type as "email" | "dns",
|
)
|
||||||
value: name.value
|
.map((name): TAltNameMapping => {
|
||||||
}));
|
const altNameType = validateAndMapAltNameType(name.value);
|
||||||
|
if (!altNameType) {
|
||||||
|
throw new Error(`Invalid altName from CSR: ${name.value}`);
|
||||||
|
}
|
||||||
|
return altNameType;
|
||||||
|
});
|
||||||
|
|
||||||
altNamesFromCsr = sanNames.items.map((item) => item.value).join(",");
|
altNamesFromCsr = sanNames.items.map((item) => item.value).join(",");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,3 +104,14 @@ export type TGetCertificateCredentialsDTO = {
|
|||||||
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
|
||||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum TAltNameType {
|
||||||
|
EMAIL = "email",
|
||||||
|
DNS = "dns",
|
||||||
|
IP = "ip",
|
||||||
|
URL = "url"
|
||||||
|
}
|
||||||
|
export type TAltNameMapping = {
|
||||||
|
type: TAltNameType;
|
||||||
|
value: string;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user