Added key algorithm opts to CA generation

This commit is contained in:
Tuan Dang
2024-05-30 10:12:28 -07:00
parent 14229931ac
commit 45fdd4ebc2
12 changed files with 145 additions and 43 deletions

View File

@@ -25,6 +25,7 @@ export async function up(knex: Knex): Promise<void> {
t.unique(["dn", "projectId"]); t.unique(["dn", "projectId"]);
t.string("serialNumber").nullable().unique(); t.string("serialNumber").nullable().unique();
t.integer("maxPathLength").nullable(); t.integer("maxPathLength").nullable();
t.string("keyAlgorithm").notNullable();
t.datetime("notBefore").nullable(); t.datetime("notBefore").nullable();
t.datetime("notAfter").nullable(); t.datetime("notAfter").nullable();
}); });

View File

@@ -24,6 +24,7 @@ export const CertificateAuthoritiesSchema = z.object({
dn: z.string(), dn: z.string(),
serialNumber: z.string().nullable().optional(), serialNumber: z.string().nullable().optional(),
maxPathLength: z.number().nullable().optional(), maxPathLength: z.number().nullable().optional(),
keyAlgorithm: z.string(),
notBefore: z.date().nullable().optional(), notBefore: z.date().nullable().optional(),
notAfter: z.date().nullable().optional() notAfter: z.date().nullable().optional()
}); });

View File

@@ -23,8 +23,8 @@ export const UsersSchema = z.object({
isGhost: z.boolean().default(false), isGhost: z.boolean().default(false),
username: z.string(), username: z.string(),
isEmailVerified: z.boolean().default(false).nullable().optional(), isEmailVerified: z.boolean().default(false).nullable().optional(),
consecutiveFailedMfaAttempts: z.number().optional(), consecutiveFailedMfaAttempts: z.number().default(0).nullable().optional(),
isLocked: z.boolean().optional(), isLocked: z.boolean().default(false).nullable().optional(),
temporaryLockDateEnd: z.date().nullable().optional() temporaryLockDateEnd: z.date().nullable().optional()
}); });

View File

@@ -4,7 +4,7 @@ import { CertificateAuthoritiesSchema } from "@app/db/schemas";
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 { CaStatus, CaType } from "@app/services/certificate-authority/certificate-authority-types"; import { CaStatus, CaType, CertKeyAlgorithm } from "@app/services/certificate-authority/certificate-authority-types";
import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators"; import { validateCaDateField } from "@app/services/certificate-authority/certificate-authority-validators";
export const registerCaRouter = async (server: FastifyZodProvider) => { export const registerCaRouter = async (server: FastifyZodProvider) => {
@@ -30,7 +30,15 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
// format: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format // format: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format
notBefore: validateCaDateField.optional(), notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(), notAfter: validateCaDateField.optional(),
maxPathLength: z.number().min(-1).optional() maxPathLength: z.number().min(-1).default(-1),
keyAlgorithm: z
.enum([
CertKeyAlgorithm.RSA_2048,
CertKeyAlgorithm.RSA_4096,
CertKeyAlgorithm.ECDSA_P256,
CertKeyAlgorithm.ECDSA_P384
])
.default(CertKeyAlgorithm.RSA_2048)
}) })
.refine( .refine(
(data) => { (data) => {

View File

@@ -1,4 +1,4 @@
import { TDNParts } from "./certificate-authority-types"; import { CertKeyAlgorithm, TDNParts } from "./certificate-authority-types";
export const createDistinguishedName = (parts: TDNParts) => { export const createDistinguishedName = (parts: TDNParts) => {
const dnParts = []; const dnParts = [];
@@ -10,3 +10,36 @@ export const createDistinguishedName = (parts: TDNParts) => {
if (parts.locality) dnParts.push(`L=${parts.locality}`); if (parts.locality) dnParts.push(`L=${parts.locality}`);
return dnParts.join(", "); return dnParts.join(", ");
}; };
export const keyAlgorithmToAlgCfg = (keyAlgorithm: CertKeyAlgorithm) => {
switch (keyAlgorithm) {
case CertKeyAlgorithm.RSA_4096:
return {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 4096
};
case CertKeyAlgorithm.ECDSA_P256:
return {
name: "ECDSA",
namedCurve: "P-256",
hash: "SHA-256"
};
case CertKeyAlgorithm.ECDSA_P384:
return {
name: "ECDSA",
namedCurve: "P-384",
hash: "SHA-384"
};
default: {
// RSA_2048
return {
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048
};
}
}
};

View File

@@ -13,11 +13,12 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TCertStatus } from "../certificate/certificate-types"; import { TCertStatus } 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 { createDistinguishedName } from "./certificate-authority-fns"; import { createDistinguishedName, keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal"; import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal";
import { import {
CaStatus, CaStatus,
CaType, CaType,
CertKeyAlgorithm,
TCreateCaDTO, TCreateCaDTO,
TDeleteCaDTO, TDeleteCaDTO,
TGetCaCertDTO, TGetCaCertDTO,
@@ -68,6 +69,7 @@ export const certificateAuthorityServiceFactory = ({
notBefore, notBefore,
notAfter, notAfter,
maxPathLength, maxPathLength,
keyAlgorithm,
actorId, actorId,
actorAuthMethod, actorAuthMethod,
actor, actor,
@@ -98,12 +100,7 @@ export const certificateAuthorityServiceFactory = ({
locality locality
}); });
const alg = { const alg = keyAlgorithmToAlgCfg(keyAlgorithm);
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048
};
const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey // https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
@@ -133,7 +130,13 @@ export const certificateAuthorityServiceFactory = ({
commonName, commonName,
status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE, status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE,
dn, dn,
...(type === CaType.ROOT && { maxPathLength, notBefore: notBeforeDate, notAfter: notAfterDate, serialNumber }) keyAlgorithm,
...(type === CaType.ROOT && {
maxPathLength,
notBefore: notBeforeDate,
notAfter: notAfterDate,
serialNumber
})
}, },
tx tx
); );
@@ -272,12 +275,7 @@ export const certificateAuthorityServiceFactory = ({
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id }); const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });
const alg = { const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048
};
const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" }); const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" });
const pkObj = crypto.createPublicKey({ key: caKeys.pk, format: "pem", type: "spki" }); const pkObj = crypto.createPublicKey({ key: caKeys.pk, format: "pem", type: "spki" });
@@ -371,12 +369,7 @@ export const certificateAuthorityServiceFactory = ({
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
const alg = { const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048
};
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id }); const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });
@@ -566,12 +559,7 @@ export const certificateAuthorityServiceFactory = ({
const caCertObj = new x509.X509Certificate(caCert.certificate); const caCertObj = new x509.X509Certificate(caCert.certificate);
const alg = { const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
name: "RSASSA-PKCS1-v1_5",
hash: "SHA-256",
publicExponent: new Uint8Array([1, 0, 1]),
modulusLength: 2048
};
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id }); const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });

View File

@@ -11,6 +11,13 @@ export enum CaStatus {
PENDING_CERTIFICATE = "pending-certificate" PENDING_CERTIFICATE = "pending-certificate"
} }
export enum CertKeyAlgorithm {
RSA_2048 = "RSA_2048",
RSA_4096 = "RSA_4096",
ECDSA_P256 = "EC_prime256v1",
ECDSA_P384 = "EC_secp384r1"
}
export type TCreateCaDTO = { export type TCreateCaDTO = {
projectSlug: string; projectSlug: string;
type: CaType; type: CaType;
@@ -22,7 +29,8 @@ export type TCreateCaDTO = {
locality: string; locality: string;
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
maxPathLength?: number; maxPathLength: number;
keyAlgorithm: CertKeyAlgorithm;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
export type TGetCaDTO = { export type TGetCaDTO = {

View File

@@ -55,6 +55,7 @@ consisting of a root CA and an intermediate CA using the Infisical UI.
- Valid Until: The date until which the CA is valid in the date time string format specified [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format). For example, the following formats would be valid: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss.sssZ`. - Valid Until: The date until which the CA is valid in the date time string format specified [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date#date_time_string_format). For example, the following formats would be valid: `YYYY`, `YYYY-MM`, `YYYY-MM-DD`, `YYYY-MM-DDTHH:mm:ss.sssZ`.
- Path Length: The maximum number of intermediate CAs that can be chained to this CA. A path of `-1` implies no limit; a path of `0` implies no intermediate CAs can be chained. - Path Length: The maximum number of intermediate CAs that can be chained to this CA. A path of `-1` implies no limit; a path of `0` implies no intermediate CAs can be chained.
- Key Algorithm: The type of public key algorithm and size, in bits, of the key pair that the CA creates when it issues a certificate. Supported key algorithms are `RSA 2048`, `RSA 4096`, `ECDSA P-256`, and `ECDSA P-384` with the default being `RSA 2048`.
- Organization (O): The organization name. - Organization (O): The organization name.
- Country (C): The country code. - Country (C): The country code.
- State or Province Name: The state or province. - State or Province Name: The state or province.
@@ -98,9 +99,8 @@ consisting of a root CA and an intermediate CA using the Infisical UI.
## FAQ ## FAQ
<AccordionGroup> <AccordionGroup>
<Accordion title="What algorithms are supported as part of private key generation and certificate signing?"> <Accordion title="What key algorithms are supported as part of private key generation and certificate signing?">
Infisical currently only supports `RSA_2048` and `SHA256WITHRSA` for the Infisical supports `RSA 2048`, `RSA 4096`, `ECDSA P-256`, `ECDSA P-384` key
private key and signing algorithm. We are working to add support for more algorithms specified at the time of creating a CA.
algorithms in the future.
</Accordion> </Accordion>
</AccordionGroup> </AccordionGroup>

View File

@@ -1,3 +1,4 @@
import { CertKeyAlgorithm } from "../certificates/enums";
import { CaStatus, CaType } from "./enums"; import { CaStatus, CaType } from "./enums";
export type TCertificateAuthority = { export type TCertificateAuthority = {
@@ -16,6 +17,7 @@ export type TCertificateAuthority = {
maxPathLength?: number; maxPathLength?: number;
notAfter?: string; notAfter?: string;
notBefore?: string; notBefore?: string;
keyAlgorithm: CertKeyAlgorithm;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
}; };
@@ -31,6 +33,7 @@ export type TCreateCaDTO = {
commonName: string; commonName: string;
notAfter?: string; notAfter?: string;
maxPathLength: number; maxPathLength: number;
keyAlgorithm: CertKeyAlgorithm;
}; };
export type TUpdateCaDTO = { export type TUpdateCaDTO = {

View File

@@ -1,6 +1,20 @@
import { CertStatus } from "./enums"; import { CertKeyAlgorithm,CertStatus } from "./enums";
export const certStatusToNameMap: { [K in CertStatus]: string } = { export const certStatusToNameMap: { [K in CertStatus]: string } = {
[CertStatus.ACTIVE]: "Active", [CertStatus.ACTIVE]: "Active",
[CertStatus.REVOKED]: "Revoked" [CertStatus.REVOKED]: "Revoked"
}; };
export const certKeyAlgorithmToNameMap: { [K in CertKeyAlgorithm]: string } = {
[CertKeyAlgorithm.RSA_2048]: "RSA 2048",
[CertKeyAlgorithm.RSA_4096]: "RSA 4096",
[CertKeyAlgorithm.ECDSA_P256]: "ECDSA P256",
[CertKeyAlgorithm.ECDSA_P384]: "ECDSA P384"
};
export const certKeyAlgorithms = [
{ label: "RSA 2048", value: CertKeyAlgorithm.RSA_2048 },
{ label: "RSA 4096", value: CertKeyAlgorithm.RSA_4096 },
{ label: "ECDSA P256", value: CertKeyAlgorithm.ECDSA_P256 },
{ label: "ECDSA P384", value: CertKeyAlgorithm.ECDSA_P384 }
];

View File

@@ -2,3 +2,10 @@ export enum CertStatus {
ACTIVE = "active", ACTIVE = "active",
REVOKED = "revoked" REVOKED = "revoked"
} }
export enum CertKeyAlgorithm {
RSA_2048 = "RSA_2048",
RSA_4096 = "RSA_4096",
ECDSA_P256 = "EC_prime256v1",
ECDSA_P384 = "EC_secp384r1"
}

View File

@@ -17,6 +17,8 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { CaType, useCreateCa, useGetCaById } from "@app/hooks/api/ca"; import { CaType, useCreateCa, useGetCaById } from "@app/hooks/api/ca";
import { certKeyAlgorithms } from "@app/hooks/api/certificates/constants";
import { CertKeyAlgorithm } from "@app/hooks/api/certificates/enums";
import { UsePopUpState } from "@app/hooks/usePopUp"; import { UsePopUpState } from "@app/hooks/usePopUp";
const isValidDate = (dateString: string) => { const isValidDate = (dateString: string) => {
@@ -40,7 +42,13 @@ const schema = z
locality: z.string(), locality: z.string(),
commonName: z.string(), commonName: z.string(),
notAfter: z.string().trim().refine(isValidDate, { message: "Invalid date format" }), notAfter: z.string().trim().refine(isValidDate, { message: "Invalid date format" }),
maxPathLength: z.string() maxPathLength: z.string(),
keyAlgorithm: z.enum([
CertKeyAlgorithm.RSA_2048,
CertKeyAlgorithm.RSA_4096,
CertKeyAlgorithm.ECDSA_P256,
CertKeyAlgorithm.ECDSA_P384
])
}) })
.required(); .required();
@@ -80,7 +88,8 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
locality: "", locality: "",
commonName: "", commonName: "",
notAfter: getDateTenYearsFromToday(), notAfter: getDateTenYearsFromToday(),
maxPathLength: "-1" maxPathLength: "-1",
keyAlgorithm: CertKeyAlgorithm.RSA_2048
} }
}); });
@@ -97,7 +106,8 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
locality: ca.locality, locality: ca.locality,
commonName: ca.commonName, commonName: ca.commonName,
notAfter: ca.notAfter ? format(new Date(ca.notAfter), "yyyy-MM-dd") : "", notAfter: ca.notAfter ? format(new Date(ca.notAfter), "yyyy-MM-dd") : "",
maxPathLength: ca.maxPathLength ? String(ca.maxPathLength) : "" maxPathLength: ca.maxPathLength ? String(ca.maxPathLength) : "",
keyAlgorithm: ca.keyAlgorithm
}); });
} else { } else {
reset({ reset({
@@ -109,7 +119,8 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
locality: "", locality: "",
commonName: "", commonName: "",
notAfter: getDateTenYearsFromToday(), notAfter: getDateTenYearsFromToday(),
maxPathLength: "-1" maxPathLength: "-1",
keyAlgorithm: CertKeyAlgorithm.RSA_2048
}); });
} }
}, [ca]); }, [ca]);
@@ -123,7 +134,8 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
locality, locality,
province, province,
notAfter, notAfter,
maxPathLength maxPathLength,
keyAlgorithm
}: FormData) => { }: FormData) => {
try { try {
if (!currentWorkspace?.slug) return; if (!currentWorkspace?.slug) return;
@@ -138,7 +150,8 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
province, province,
locality, locality,
notAfter, notAfter,
maxPathLength: Number(maxPathLength) maxPathLength: Number(maxPathLength),
keyAlgorithm
}); });
reset(); reset();
@@ -269,6 +282,32 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
/> />
</> </>
)} )}
<Controller
control={control}
name="keyAlgorithm"
defaultValue={CertKeyAlgorithm.RSA_2048}
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Key Algorithm"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
isDisabled={Boolean(ca)}
>
{certKeyAlgorithms.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""