From c1f61f2db4d13723ed8124c41124795c49fee092 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Wed, 11 Sep 2024 20:26:33 +0800 Subject: [PATCH] feat: added custom key usages support for sign endpoint --- .../routes/v1/certificate-authority-router.ts | 3 +- .../server/routes/v1/certificate-router.ts | 3 +- .../certificate-authority-service.ts | 53 +++++++++++++++++-- .../certificate-authority-types.ts | 2 + 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index f85b37eee..ea779be86 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -654,7 +654,8 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { .refine((val) => ms(val) > 0, "TTL must be a positive number") .describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl), 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() }) .refine( (data) => { diff --git a/backend/src/server/routes/v1/certificate-router.ts b/backend/src/server/routes/v1/certificate-router.ts index ba8b6150e..dcc3ddf06 100644 --- a/backend/src/server/routes/v1/certificate-router.ts +++ b/backend/src/server/routes/v1/certificate-router.ts @@ -178,7 +178,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { .refine((val) => ms(val) > 0, "TTL must be a positive number") .describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.ttl), 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() }) .refine( (data) => { diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 2a6354d35..53255c0a2 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -1346,7 +1346,8 @@ export const certificateAuthorityServiceFactory = ({ altNames, ttl, notBefore, - notAfter + notAfter, + keyUsages } = dto; let collectionId = pkiCollectionId; @@ -1466,12 +1467,57 @@ export const certificateAuthorityServiceFactory = ({ }); const extensions: x509.Extension[] = [ - new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true), new x509.BasicConstraintsExtension(false), await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) ]; + const csrKeyUsageExtension = csrObj.getExtension("2.5.29.15") as x509.KeyUsagesExtension; + let csrKeyUsages: CertKeyUsage[] = []; + if (csrKeyUsageExtension) { + csrKeyUsages = Object.values(CertKeyUsage).filter( + (keyUsage) => (x509.KeyUsageFlags[keyUsage] & csrKeyUsageExtension.usages) !== 0 + ); + } + + let selectedKeyUsages: CertKeyUsage[] = keyUsages ?? []; + if (keyUsages === undefined && !certificateTemplate) { + if (csrKeyUsageExtension) { + selectedKeyUsages = csrKeyUsages; + } else { + selectedKeyUsages = [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT]; + } + } + + if (keyUsages === undefined && certificateTemplate) { + if (csrKeyUsageExtension) { + const validKeyUsages = certificateTemplate.keyUsages || []; + if (csrKeyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) { + throw new BadRequestError({ + message: "Invalid key usage value for certificate" + }); + } + selectedKeyUsages = csrKeyUsages; + } else { + selectedKeyUsages = (certificateTemplate.keyUsages ?? []) as CertKeyUsage[]; + } + } + + if (keyUsages?.length && certificateTemplate) { + const validKeyUsages = certificateTemplate.keyUsages || []; + if (keyUsages.some((keyUsage) => !validKeyUsages.includes(keyUsage))) { + throw new BadRequestError({ + message: "Invalid key usage value for certificate" + }); + } + selectedKeyUsages = keyUsages; + } + + const keyUsagesBitValue = selectedKeyUsages.reduce((accum, keyUsage) => accum | x509.KeyUsageFlags[keyUsage], 0); + if (keyUsagesBitValue) { + extensions.push(new x509.KeyUsagesExtension(keyUsagesBitValue, true)); + } + let altNamesFromCsr: string = ""; let altNamesArray: { type: "email" | "dns"; @@ -1567,7 +1613,8 @@ export const certificateAuthorityServiceFactory = ({ altNames: altNamesFromCsr || altNames, serialNumber, notBefore: notBeforeDate, - notAfter: notAfterDate + notAfter: notAfterDate, + keyUsages: selectedKeyUsages }, tx ); diff --git a/backend/src/services/certificate-authority/certificate-authority-types.ts b/backend/src/services/certificate-authority/certificate-authority-types.ts index 124c6aea2..f2a584576 100644 --- a/backend/src/services/certificate-authority/certificate-authority-types.ts +++ b/backend/src/services/certificate-authority/certificate-authority-types.ts @@ -113,6 +113,7 @@ export type TSignCertFromCaDTO = ttl?: string; notBefore?: string; notAfter?: string; + keyUsages?: CertKeyUsage[]; } | ({ isInternal: false; @@ -126,6 +127,7 @@ export type TSignCertFromCaDTO = ttl: string; notBefore?: string; notAfter?: string; + keyUsages?: CertKeyUsage[]; } & Omit); export type TGetCaCertificateTemplatesDTO = {