feat: added support for custom extended key usages

This commit is contained in:
Sheen Capadngan
2024-09-11 22:38:36 +08:00
parent c1f61f2db4
commit a8759e7410
14 changed files with 306 additions and 45 deletions
@@ -7,7 +7,7 @@ import { CERTIFICATE_AUTHORITIES } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types"; import { CaRenewalType, CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types";
import { import {
validateAltNamesField, validateAltNamesField,
@@ -574,7 +574,8 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl), .describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore), notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter), notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional() keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
}) })
.refine( .refine(
(data) => { (data) => {
@@ -655,7 +656,8 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl), .describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore), notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter), notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional() keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
}) })
.refine( .refine(
(data) => { (data) => {
@@ -7,7 +7,7 @@ import { CERTIFICATE_AUTHORITIES, CERTIFICATES } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyUsage, CrlReason } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyUsage, CrlReason } from "@app/services/certificate/certificate-types";
import { import {
validateAltNamesField, validateAltNamesField,
validateCaDateField validateCaDateField
@@ -87,7 +87,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl), .describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.ttl),
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore), notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notBefore),
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter), notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.notAfter),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional() keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
}) })
.refine( .refine(
(data) => { (data) => {
@@ -179,7 +180,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl), .describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl),
notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore), notBefore: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notBefore),
notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter), notAfter: validateCaDateField.optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.notAfter),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional() keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
}) })
.refine( .refine(
(data) => { (data) => {
@@ -7,7 +7,7 @@ import { CERTIFICATE_TEMPLATES } from "@app/lib/api-docs";
import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type"; import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema"; import { sanitizedCertificateTemplate } from "@app/services/certificate-template/certificate-template-schema";
import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators"; import { validateTemplateRegexField } from "@app/services/certificate-template/certificate-template-validators";
@@ -80,7 +80,8 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
.nativeEnum(CertKeyUsage) .nativeEnum(CertKeyUsage)
.array() .array()
.optional() .optional()
.default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]) .default([CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional().default([])
}), }),
response: { response: {
200: sanitizedCertificateTemplate 200: sanitizedCertificateTemplate
@@ -137,7 +138,8 @@ export const registerCertificateTemplateRouter = async (server: FastifyZodProvid
.refine((val) => ms(val) > 0, "TTL must be a positive number") .refine((val) => ms(val) > 0, "TTL must be a positive number")
.optional() .optional()
.describe(CERTIFICATE_TEMPLATES.UPDATE.ttl), .describe(CERTIFICATE_TEMPLATES.UPDATE.ttl),
keyUsages: z.nativeEnum(CertKeyUsage).array().optional() keyUsages: z.nativeEnum(CertKeyUsage).array().optional(),
extendedKeyUsages: z.nativeEnum(CertExtendedKeyUsage).array().optional()
}), }),
params: z.object({ params: z.object({
certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId) certificateTemplateId: z.string().describe(CERTIFICATE_TEMPLATES.UPDATE.certificateTemplateId)
@@ -19,7 +19,13 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { CertKeyAlgorithm, CertKeyUsage, CertStatus } from "../certificate/certificate-types"; import {
CertExtendedKeyUsage,
CertExtendedKeyUsageOIDToName,
CertKeyAlgorithm,
CertKeyUsage,
CertStatus
} 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";
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
@@ -1053,7 +1059,8 @@ export const certificateAuthorityServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actor, actor,
actorOrgId, actorOrgId,
keyUsages keyUsages,
extendedKeyUsages
}: TIssueCertFromCaDTO) => { }: TIssueCertFromCaDTO) => {
let ca: TCertificateAuthorities | undefined; let ca: TCertificateAuthorities | undefined;
let certificateTemplate: TCertificateTemplates | undefined; let certificateTemplate: TCertificateTemplates | undefined;
@@ -1170,7 +1177,14 @@ export const certificateAuthorityServiceFactory = ({
const appCfg = getConfig(); const appCfg = getConfig();
const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`;
const extensions: x509.Extension[] = [
new x509.BasicConstraintsExtension(false),
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
];
// handle key usages
let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? []; let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? [];
if (keyUsages === undefined && !certificateTemplate) { if (keyUsages === undefined && !certificateTemplate) {
selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]; selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT];
@@ -1184,24 +1198,42 @@ export const certificateAuthorityServiceFactory = ({
const validKeyUsages = certificateTemplate.keyUsages || []; const validKeyUsages = certificateTemplate.keyUsages || [];
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) { if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid key usage value for certificate" message: "Invalid key usage value based on template policy"
}); });
} }
selectedKeyUsages = keyUsages; selectedKeyUsages = keyUsages;
} }
const extensions: x509.Extension[] = [
new x509.BasicConstraintsExtension(false),
new x509.CRLDistributionPointsExtension([distributionPointUrl]),
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
];
const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0); const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0);
if (keyUsagesBitValue) { if (keyUsagesBitValue) {
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true)); extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
} }
// handle extended key usages
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
if (extendedKeyUsages === undefined && certificateTemplate) {
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
}
if (extendedKeyUsages?.length && certificateTemplate) {
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
if (extendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
throw new BadRequestError({
message: "Invalid extended key usage value based on template policy"
});
}
selectedExtendedKeyUsages = extendedKeyUsages;
}
if (selectedExtendedKeyUsages.length) {
extensions.push(
new x509.ExtendedKeyUsageExtension(
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
true
)
);
}
let altNamesArray: { let altNamesArray: {
type: "email" | "dns"; type: "email" | "dns";
value: string; value: string;
@@ -1284,7 +1316,8 @@ export const certificateAuthorityServiceFactory = ({
serialNumber, serialNumber,
notBefore: notBeforeDate, notBefore: notBeforeDate,
notAfter: notAfterDate, notAfter: notAfterDate,
keyUsages: selectedKeyUsages keyUsages: selectedKeyUsages,
extendedKeyUsages: selectedExtendedKeyUsages
}, },
tx tx
); );
@@ -1347,7 +1380,8 @@ export const certificateAuthorityServiceFactory = ({
ttl, ttl,
notBefore, notBefore,
notAfter, notAfter,
keyUsages keyUsages,
extendedKeyUsages
} = dto; } = dto;
let collectionId = pkiCollectionId; let collectionId = pkiCollectionId;
@@ -1472,6 +1506,7 @@ export const certificateAuthorityServiceFactory = ({
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
]; ];
// handle key usages
const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension; const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension;
let csrKeyUsages: CertKeyUsage[] = []; let csrKeyUsages: CertKeyUsage[] = [];
if (csrKeyUsageExtension) { if (csrKeyUsageExtension) {
@@ -1494,7 +1529,7 @@ export const certificateAuthorityServiceFactory = ({
const validKeyUsages = certificateTemplate.keyUsages || []; const validKeyUsages = certificateTemplate.keyUsages || [];
if (csrKeyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) { if (csrKeyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid key usage value for certificate" message: "Invalid key usage value based on template policy"
}); });
} }
selectedKeyUsages = csrKeyUsages; selectedKeyUsages = csrKeyUsages;
@@ -1507,7 +1542,7 @@ export const certificateAuthorityServiceFactory = ({
const validKeyUsages = certificateTemplate.keyUsages || []; const validKeyUsages = certificateTemplate.keyUsages || [];
if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) { if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) {
throw new BadRequestError({ throw new BadRequestError({
message: "Invalid key usage value for certificate" message: "Invalid key usage value based on template policy"
}); });
} }
selectedKeyUsages = keyUsages; selectedKeyUsages = keyUsages;
@@ -1518,6 +1553,53 @@ export const certificateAuthorityServiceFactory = ({
extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true)); extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true));
} }
// handle extended key usages
const csrExtendedKeyUsageExtension = csrObj.getExtension("2.5.29.37") as x509.ExtendedKeyUsageExtension;
let csrExtendedKeyUsages: CertExtendedKeyUsage[] = [];
if (csrExtendedKeyUsageExtension) {
csrExtendedKeyUsages = csrExtendedKeyUsageExtension.usages.map(
(ekuOid) => CertExtendedKeyUsageOIDToName[ekuOid as string]
);
}
let selectedExtendedKeyUsages: CertExtendedKeyUsage[] = extendedKeyUsages ?? [];
if (extendedKeyUsages === undefined && !certificateTemplate && csrExtendedKeyUsageExtension) {
selectedExtendedKeyUsages = csrExtendedKeyUsages;
}
if (extendedKeyUsages === undefined && certificateTemplate) {
if (csrExtendedKeyUsageExtension) {
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
if (csrExtendedKeyUsages.some((eku) => !validExtendedKeyUsages.includes(eku))) {
throw new BadRequestError({
message: "Invalid extended key usage value based on template policy"
});
}
selectedExtendedKeyUsages = csrExtendedKeyUsages;
} else {
selectedExtendedKeyUsages = (certificateTemplate.extendedKeyUsages ?? []) as CertExtendedKeyUsage[];
}
}
if (extendedKeyUsages?.length && certificateTemplate) {
const validExtendedKeyUsages = certificateTemplate.extendedKeyUsages || [];
if (extendedKeyUsages.some((keyUsage) => !validExtendedKeyUsages.includes(keyUsage))) {
throw new BadRequestError({
message: "Invalid extended key usage value based on template policy"
});
}
selectedExtendedKeyUsages = extendedKeyUsages;
}
if (selectedExtendedKeyUsages.length) {
extensions.push(
new x509.ExtendedKeyUsageExtension(
selectedExtendedKeyUsages.map((eku) => x509.ExtendedKeyUsage[eku]),
true
)
);
}
let altNamesFromCsr: string = ""; let altNamesFromCsr: string = "";
let altNamesArray: { let altNamesArray: {
type: "email" | "dns"; type: "email" | "dns";
@@ -1614,7 +1696,8 @@ export const certificateAuthorityServiceFactory = ({
serialNumber, serialNumber,
notBefore: notBeforeDate, notBefore: notBeforeDate,
notAfter: notAfterDate, notAfter: notAfterDate,
keyUsages: selectedKeyUsages keyUsages: selectedKeyUsages,
extendedKeyUsages: selectedExtendedKeyUsages
}, },
tx tx
); );
@@ -4,7 +4,7 @@ import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
import { CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types";
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
@@ -98,6 +98,7 @@ export type TIssueCertFromCaDTO = {
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
keyUsages?: CertKeyUsage[]; keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TSignCertFromCaDTO = export type TSignCertFromCaDTO =
@@ -114,6 +115,7 @@ export type TSignCertFromCaDTO =
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
keyUsages?: CertKeyUsage[]; keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} }
| ({ | ({
isInternal: false; isInternal: false;
@@ -128,6 +130,7 @@ export type TSignCertFromCaDTO =
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
keyUsages?: CertKeyUsage[]; keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">); } & Omit<TProjectPermission, "projectId">);
export type TGetCaCertificateTemplatesDTO = { export type TGetCaCertificateTemplatesDTO = {
@@ -10,7 +10,8 @@ export const sanitizedCertificateTemplate = CertificateTemplatesSchema.pick({
subjectAlternativeName: true, subjectAlternativeName: true,
pkiCollectionId: true, pkiCollectionId: true,
ttl: true, ttl: true,
keyUsages: true keyUsages: true,
extendedKeyUsages: true
}).merge( }).merge(
z.object({ z.object({
projectId: z.string(), projectId: z.string(),
@@ -58,7 +58,8 @@ export const certificateTemplateServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actor, actor,
actorOrgId, actorOrgId,
keyUsages keyUsages,
extendedKeyUsages
}: TCreateCertTemplateDTO) => { }: TCreateCertTemplateDTO) => {
const ca = await certificateAuthorityDAL.findById(caId); const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) { if (!ca) {
@@ -88,7 +89,8 @@ export const certificateTemplateServiceFactory = ({
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
ttl, ttl,
keyUsages keyUsages,
extendedKeyUsages
}, },
tx tx
); );
@@ -116,7 +118,8 @@ export const certificateTemplateServiceFactory = ({
actorAuthMethod, actorAuthMethod,
actor, actor,
actorOrgId, actorOrgId,
keyUsages keyUsages,
extendedKeyUsages
}: TUpdateCertTemplateDTO) => { }: TUpdateCertTemplateDTO) => {
const certTemplate = await certificateTemplateDAL.getById(id); const certTemplate = await certificateTemplateDAL.getById(id);
if (!certTemplate) { if (!certTemplate) {
@@ -157,7 +160,8 @@ export const certificateTemplateServiceFactory = ({
subjectAlternativeName, subjectAlternativeName,
name, name,
ttl, ttl,
keyUsages keyUsages,
extendedKeyUsages
}, },
tx tx
); );
@@ -1,5 +1,5 @@
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
import { CertKeyUsage } from "@app/services/certificate/certificate-types"; import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
export type TCreateCertTemplateDTO = { export type TCreateCertTemplateDTO = {
caId: string; caId: string;
@@ -9,6 +9,7 @@ export type TCreateCertTemplateDTO = {
subjectAlternativeName: string; subjectAlternativeName: string;
ttl: string; ttl: string;
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TUpdateCertTemplateDTO = { export type TUpdateCertTemplateDTO = {
@@ -20,6 +21,7 @@ export type TUpdateCertTemplateDTO = {
subjectAlternativeName?: string; subjectAlternativeName?: string;
ttl?: string; ttl?: string;
keyUsages?: CertKeyUsage[]; keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetCertTemplateDTO = { export type TGetCertTemplateDTO = {
@@ -1,3 +1,5 @@
import * as x509 from "@peculiar/x509";
import { TProjectPermission } from "@app/lib/types"; import { TProjectPermission } from "@app/lib/types";
export enum CertStatus { export enum CertStatus {
@@ -24,6 +26,24 @@ export enum CertKeyUsage {
DECIPHER_ONLY = "decipherOnly" DECIPHER_ONLY = "decipherOnly"
} }
export enum CertExtendedKeyUsage {
CLIENT_AUTH = "clientAuth",
SERVER_AUTH = "serverAuth",
CODE_SIGNING = "codeSigning",
EMAIL_PROTECTION = "emailProtection",
TIMESTAMPING = "timeStamping",
OCSP_SIGNING = "ocspSigning"
}
export const CertExtendedKeyUsageOIDToName: Record<string, CertExtendedKeyUsage> = {
[x509.ExtendedKeyUsage.clientAuth]: CertExtendedKeyUsage.CLIENT_AUTH,
[x509.ExtendedKeyUsage.serverAuth]: CertExtendedKeyUsage.SERVER_AUTH,
[x509.ExtendedKeyUsage.codeSigning]: CertExtendedKeyUsage.CODE_SIGNING,
[x509.ExtendedKeyUsage.emailProtection]: CertExtendedKeyUsage.EMAIL_PROTECTION,
[x509.ExtendedKeyUsage.ocspSigning]: CertExtendedKeyUsage.OCSP_SIGNING,
[x509.ExtendedKeyUsage.timeStamping]: CertExtendedKeyUsage.TIMESTAMPING
};
export enum CrlReason { export enum CrlReason {
UNSPECIFIED = "UNSPECIFIED", UNSPECIFIED = "UNSPECIFIED",
KEY_COMPROMISE = "KEY_COMPROMISE", KEY_COMPROMISE = "KEY_COMPROMISE",
+2 -1
View File
@@ -1,5 +1,5 @@
import { CertKeyAlgorithm } from "../certificates/enums"; import { CertKeyAlgorithm } from "../certificates/enums";
import { CertKeyUsage } from "../certificates/types"; import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/types";
import { CaRenewalType, CaStatus, CaType } from "./enums"; import { CaRenewalType, CaStatus, CaType } from "./enums";
export type TCertificateAuthority = { export type TCertificateAuthority = {
@@ -93,6 +93,7 @@ export type TCreateCertificateDTO = {
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
}; };
export type TCreateCertificateResponse = { export type TCreateCertificateResponse = {
@@ -1,4 +1,4 @@
import { CertKeyUsage } from "../certificates/types"; import { CertExtendedKeyUsage, CertKeyUsage } from "../certificates/types";
export type TCertificateTemplate = { export type TCertificateTemplate = {
id: string; id: string;
@@ -11,6 +11,7 @@ export type TCertificateTemplate = {
subjectAlternativeName: string; subjectAlternativeName: string;
ttl: string; ttl: string;
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
}; };
export type TCreateCertificateTemplateDTO = { export type TCreateCertificateTemplateDTO = {
@@ -22,6 +23,7 @@ export type TCreateCertificateTemplateDTO = {
ttl: string; ttl: string;
projectId: string; projectId: string;
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
}; };
export type TUpdateCertificateTemplateDTO = { export type TUpdateCertificateTemplateDTO = {
@@ -34,6 +36,7 @@ export type TUpdateCertificateTemplateDTO = {
ttl?: string; ttl?: string;
projectId: string; projectId: string;
keyUsages?: CertKeyUsage[]; keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
}; };
export type TDeleteCertificateTemplateDTO = { export type TDeleteCertificateTemplateDTO = {
@@ -12,6 +12,7 @@ export type TCertificate = {
notBefore: string; notBefore: string;
notAfter: string; notAfter: string;
keyUsages: CertKeyUsage[]; keyUsages: CertKeyUsage[];
extendedKeyUsages: CertExtendedKeyUsage[];
}; };
export type TDeleteCertDTO = { export type TDeleteCertDTO = {
@@ -48,3 +49,21 @@ export const KEY_USAGES_OPTIONS = [
{ value: CertKeyUsage.ENCIPHER_ONLY, label: "Encipher Only" }, { value: CertKeyUsage.ENCIPHER_ONLY, label: "Encipher Only" },
{ value: CertKeyUsage.DECIPHER_ONLY, label: "Decipher Only" } { value: CertKeyUsage.DECIPHER_ONLY, label: "Decipher Only" }
] as const; ] as const;
export enum CertExtendedKeyUsage {
CLIENT_AUTH = "clientAuth",
SERVER_AUTH = "serverAuth",
CODE_SIGNING = "codeSigning",
EMAIL_PROTECTION = "emailProtection",
TIMESTAMPING = "timeStamping",
OCSP_SIGNING = "ocspSigning"
}
export const EXTENDED_KEY_USAGES_OPTIONS = [
{ value: CertExtendedKeyUsage.CLIENT_AUTH, label: "Client Auth" },
{ value: CertExtendedKeyUsage.SERVER_AUTH, label: "Server Auth" },
{ value: CertExtendedKeyUsage.EMAIL_PROTECTION, label: "Email Protection" },
{ value: CertExtendedKeyUsage.OCSP_SIGNING, label: "OCSP Signing" },
{ value: CertExtendedKeyUsage.CODE_SIGNING, label: "Code Signing" },
{ value: CertExtendedKeyUsage.TIMESTAMPING, label: "Timestamping" }
] as const;
@@ -33,7 +33,12 @@ import {
useListWorkspacePkiCollections useListWorkspacePkiCollections
} from "@app/hooks/api"; } from "@app/hooks/api";
import { caTypeToNameMap } from "@app/hooks/api/ca/constants"; import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
import { CertKeyUsage, KEY_USAGES_OPTIONS } from "@app/hooks/api/certificates/types"; import {
CertExtendedKeyUsage,
CertKeyUsage,
EXTENDED_KEY_USAGES_OPTIONS,
KEY_USAGES_OPTIONS
} from "@app/hooks/api/certificates/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "./CertificateContent"; import { CertificateContent } from "./CertificateContent";
@@ -56,6 +61,14 @@ const schema = z.object({
[CertKeyUsage.CRL_SIGN]: z.boolean().optional(), [CertKeyUsage.CRL_SIGN]: z.boolean().optional(),
[CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(), [CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(),
[CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional() [CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional()
}),
extendedKeyUsages: z.object({
[CertExtendedKeyUsage.CLIENT_AUTH]: z.boolean().optional(),
[CertExtendedKeyUsage.CODE_SIGNING]: z.boolean().optional(),
[CertExtendedKeyUsage.EMAIL_PROTECTION]: z.boolean().optional(),
[CertExtendedKeyUsage.OCSP_SIGNING]: z.boolean().optional(),
[CertExtendedKeyUsage.SERVER_AUTH]: z.boolean().optional(),
[CertExtendedKeyUsage.TIMESTAMPING]: z.boolean().optional()
}) })
}); });
@@ -110,7 +123,8 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
keyUsages: { keyUsages: {
[CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.DIGITAL_SIGNATURE]: true,
[CertKeyUsage.KEY_ENCIPHERMENT]: true [CertKeyUsage.KEY_ENCIPHERMENT]: true
} },
extendedKeyUsages: {}
} }
}); });
@@ -131,7 +145,10 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
altNames: cert.altNames, altNames: cert.altNames,
certificateTemplateId: cert.certificateTemplateId ?? CERT_TEMPLATE_NONE_VALUE, certificateTemplateId: cert.certificateTemplateId ?? CERT_TEMPLATE_NONE_VALUE,
ttl: "", ttl: "",
keyUsages: Object.fromEntries(cert.keyUsages.map((name) => [name, true])) keyUsages: Object.fromEntries((cert.keyUsages || []).map((name) => [name, true])),
extendedKeyUsages: Object.fromEntries(
(cert.extendedKeyUsages || []).map((name) => [name, true])
)
}); });
} else { } else {
reset({ reset({
@@ -144,7 +161,8 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
keyUsages: { keyUsages: {
[CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.DIGITAL_SIGNATURE]: true,
[CertKeyUsage.KEY_ENCIPHERMENT]: true [CertKeyUsage.KEY_ENCIPHERMENT]: true
} },
extendedKeyUsages: {}
}); });
} }
}, [cert]); }, [cert]);
@@ -156,6 +174,10 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
"keyUsages", "keyUsages",
Object.fromEntries(selectedCertTemplate.keyUsages.map((name) => [name, true])) Object.fromEntries(selectedCertTemplate.keyUsages.map((name) => [name, true]))
); );
setValue(
"extendedKeyUsages",
Object.fromEntries(selectedCertTemplate.extendedKeyUsages.map((name) => [name, true]))
);
} }
}, [selectedCertTemplate, cert]); }, [selectedCertTemplate, cert]);
@@ -166,7 +188,8 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
commonName, commonName,
altNames, altNames,
ttl, ttl,
keyUsages keyUsages,
extendedKeyUsages
}: FormData) => { }: FormData) => {
try { try {
if (!currentWorkspace?.slug) return; if (!currentWorkspace?.slug) return;
@@ -182,7 +205,10 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
ttl, ttl,
keyUsages: Object.entries(keyUsages) keyUsages: Object.entries(keyUsages)
.filter(([, value]) => value) .filter(([, value]) => value)
.map(([key]) => key as CertKeyUsage) .map(([key]) => key as CertKeyUsage),
extendedKeyUsages: Object.entries(extendedKeyUsages)
.filter(([, value]) => value)
.map(([key]) => key as CertExtendedKeyUsage)
}); });
reset(); reset();
@@ -440,6 +466,41 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
); );
}} }}
/> />
<Controller
control={control}
name="extendedKeyUsages"
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<FormControl
label="Extended Key Usages"
errorText={error?.message}
isError={Boolean(error)}
>
<div className="mt-2 mb-7 grid grid-cols-2 gap-2">
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
return (
<Checkbox
id={optionValue}
key={optionValue}
className="data-[state=checked]:bg-primary"
isDisabled={Boolean(cert)}
isChecked={value[optionValue]}
onCheckedChange={(state) => {
onChange({
...value,
[optionValue]: state
});
}}
>
{label}
</Checkbox>
);
})}
</div>
</FormControl>
);
}}
/>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
@@ -33,7 +33,12 @@ import {
useUpdateCertTemplate useUpdateCertTemplate
} from "@app/hooks/api"; } from "@app/hooks/api";
import { caTypeToNameMap } from "@app/hooks/api/ca/constants"; import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
import { CertKeyUsage, KEY_USAGES_OPTIONS } from "@app/hooks/api/certificates/types"; import {
CertExtendedKeyUsage,
CertKeyUsage,
EXTENDED_KEY_USAGES_OPTIONS,
KEY_USAGES_OPTIONS
} from "@app/hooks/api/certificates/types";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
const validateTemplateRegexField = z const validateTemplateRegexField = z
@@ -63,6 +68,14 @@ const schema = z.object({
[CertKeyUsage.CRL_SIGN]: z.boolean().optional(), [CertKeyUsage.CRL_SIGN]: z.boolean().optional(),
[CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(), [CertKeyUsage.ENCIPHER_ONLY]: z.boolean().optional(),
[CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional() [CertKeyUsage.DECIPHER_ONLY]: z.boolean().optional()
}),
extendedKeyUsages: z.object({
[CertExtendedKeyUsage.CLIENT_AUTH]: z.boolean().optional(),
[CertExtendedKeyUsage.CODE_SIGNING]: z.boolean().optional(),
[CertExtendedKeyUsage.EMAIL_PROTECTION]: z.boolean().optional(),
[CertExtendedKeyUsage.OCSP_SIGNING]: z.boolean().optional(),
[CertExtendedKeyUsage.SERVER_AUTH]: z.boolean().optional(),
[CertExtendedKeyUsage.TIMESTAMPING]: z.boolean().optional()
}) })
}); });
@@ -122,7 +135,10 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
subjectAlternativeName: certTemplate.subjectAlternativeName, subjectAlternativeName: certTemplate.subjectAlternativeName,
collectionId: certTemplate.pkiCollectionId ?? undefined, collectionId: certTemplate.pkiCollectionId ?? undefined,
ttl: certTemplate.ttl, ttl: certTemplate.ttl,
keyUsages: Object.fromEntries(certTemplate.keyUsages.map((name) => [name, true])) keyUsages: Object.fromEntries(certTemplate.keyUsages.map((name) => [name, true]) ?? []),
extendedKeyUsages: Object.fromEntries(
certTemplate.extendedKeyUsages.map((name) => [name, true]) ?? []
)
}); });
} else { } else {
reset({ reset({
@@ -133,7 +149,8 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
keyUsages: { keyUsages: {
[CertKeyUsage.DIGITAL_SIGNATURE]: true, [CertKeyUsage.DIGITAL_SIGNATURE]: true,
[CertKeyUsage.KEY_ENCIPHERMENT]: true [CertKeyUsage.KEY_ENCIPHERMENT]: true
} },
extendedKeyUsages: {}
}); });
} }
}, [certTemplate, ca]); }, [certTemplate, ca]);
@@ -144,7 +161,8 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
commonName, commonName,
subjectAlternativeName, subjectAlternativeName,
ttl, ttl,
keyUsages keyUsages,
extendedKeyUsages
}: FormData) => { }: FormData) => {
if (!currentWorkspace?.id) { if (!currentWorkspace?.id) {
return; return;
@@ -163,7 +181,10 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
ttl, ttl,
keyUsages: Object.entries(keyUsages) keyUsages: Object.entries(keyUsages)
.filter(([, value]) => value) .filter(([, value]) => value)
.map(([key]) => key as CertKeyUsage) .map(([key]) => key as CertKeyUsage),
extendedKeyUsages: Object.entries(extendedKeyUsages)
.filter(([, value]) => value)
.map(([key]) => key as CertExtendedKeyUsage)
}); });
createNotification({ createNotification({
@@ -181,7 +202,10 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
ttl, ttl,
keyUsages: Object.entries(keyUsages) keyUsages: Object.entries(keyUsages)
.filter(([, value]) => value) .filter(([, value]) => value)
.map(([key]) => key as CertKeyUsage) .map(([key]) => key as CertKeyUsage),
extendedKeyUsages: Object.entries(extendedKeyUsages)
.filter(([, value]) => value)
.map(([key]) => key as CertExtendedKeyUsage)
}); });
createNotification({ createNotification({
@@ -408,6 +432,40 @@ export const CertificateTemplateModal = ({ popUp, handlePopUpToggle, caId }: Pro
); );
}} }}
/> />
<Controller
control={control}
name="extendedKeyUsages"
render={({ field: { onChange, value }, fieldState: { error } }) => {
return (
<FormControl
label="Extended Key Usages"
errorText={error?.message}
isError={Boolean(error)}
>
<div className="mt-2 mb-7 grid grid-cols-2 gap-2">
{EXTENDED_KEY_USAGES_OPTIONS.map(({ label, value: optionValue }) => {
return (
<Checkbox
id={optionValue}
key={optionValue}
className="data-[state=checked]:bg-primary"
isChecked={value[optionValue]}
onCheckedChange={(state) => {
onChange({
...value,
[optionValue]: state
});
}}
>
{label}
</Checkbox>
);
})}
</div>
</FormControl>
);
}}
/>
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>