mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 14:34:09 +00:00
Transfer cert endpoints to work with serial numbers
This commit is contained in:
@@ -23,6 +23,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
t.string("commonName").notNullable();
|
||||
t.string("dn").notNullable();
|
||||
t.unique(["dn", "projectId"]);
|
||||
t.string("serialNumber").nullable().unique();
|
||||
t.integer("maxPathLength").nullable();
|
||||
t.datetime("notBefore").nullable();
|
||||
t.datetime("notAfter").nullable();
|
||||
@@ -54,12 +55,14 @@ export async function up(knex: Knex): Promise<void> {
|
||||
}
|
||||
|
||||
if (!(await knex.schema.hasTable(TableName.Certificate))) {
|
||||
// TODO: consider adding name
|
||||
// TODO: consider adding serialNumber
|
||||
await knex.schema.createTable(TableName.Certificate, (t) => {
|
||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||
t.timestamps(true, true, true);
|
||||
t.uuid("caId").notNullable();
|
||||
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
|
||||
t.string("status").notNullable(); // active / pending-certificate
|
||||
t.string("serialNumber").notNullable().unique();
|
||||
t.string("commonName").notNullable();
|
||||
t.datetime("notBefore").notNullable();
|
||||
t.datetime("notAfter").notNullable();
|
||||
|
||||
@@ -22,6 +22,7 @@ export const CertificateAuthoritiesSchema = z.object({
|
||||
locality: z.string(),
|
||||
commonName: z.string(),
|
||||
dn: z.string(),
|
||||
serialNumber: z.string().nullable().optional(),
|
||||
maxPathLength: z.number().nullable().optional(),
|
||||
notBefore: z.date().nullable().optional(),
|
||||
notAfter: z.date().nullable().optional()
|
||||
|
||||
@@ -12,6 +12,8 @@ export const CertificatesSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
caId: z.string().uuid(),
|
||||
status: z.string(),
|
||||
serialNumber: z.string(),
|
||||
commonName: z.string(),
|
||||
notBefore: z.date(),
|
||||
notAfter: z.date()
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AuthMode } from "@app/services/auth/auth-type";
|
||||
export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:certId",
|
||||
url: "/:serialNumber",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
@@ -16,7 +16,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Get certificate",
|
||||
params: z.object({
|
||||
certId: z.string().trim()
|
||||
serialNumber: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -25,8 +25,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const certificate = await server.services.certificate.getCertById({
|
||||
certId: req.params.certId,
|
||||
const certificate = await server.services.certificate.getCert({
|
||||
serialNumber: req.params.serialNumber,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
@@ -38,9 +38,45 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:serialNumber/revoke",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
schema: {
|
||||
description: "Revoke",
|
||||
params: z.object({
|
||||
serialNumber: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string().trim(),
|
||||
serialNumber: z.string().trim(),
|
||||
revokedAt: z.date()
|
||||
})
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
await server.services.certificate.revokeCert({
|
||||
serialNumber: req.params.serialNumber,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId
|
||||
});
|
||||
return {
|
||||
message: "Successfully revoked certificate",
|
||||
serialNumber: req.params.serialNumber,
|
||||
revokedAt: new Date()
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "DELETE",
|
||||
url: "/:certId",
|
||||
url: "/:serialNumber",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
@@ -48,7 +84,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Delete certificate",
|
||||
params: z.object({
|
||||
certId: z.string().trim()
|
||||
serialNumber: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -57,8 +93,8 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
},
|
||||
handler: async (req) => {
|
||||
const certificate = await server.services.certificate.deleteCertById({
|
||||
certId: req.params.certId,
|
||||
const certificate = await server.services.certificate.deleteCert({
|
||||
serialNumber: req.params.serialNumber,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
@@ -72,7 +108,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
server.route({
|
||||
method: "GET",
|
||||
url: "/:certId/certificate",
|
||||
url: "/:serialNumber/certificate",
|
||||
config: {
|
||||
rateLimit: readLimit
|
||||
},
|
||||
@@ -80,7 +116,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "Get certificate of certificate",
|
||||
params: z.object({
|
||||
certId: z.string().trim()
|
||||
serialNumber: z.string().trim()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
@@ -92,7 +128,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
||||
},
|
||||
handler: async (req) => {
|
||||
const { certificate, certificateChain, serialNumber } = await server.services.certificate.getCertCert({
|
||||
certId: req.params.certId,
|
||||
serialNumber: req.params.serialNumber,
|
||||
actor: req.permission.type,
|
||||
actorId: req.permission.id,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
|
||||
@@ -60,8 +60,13 @@ export const registerV1Routes = async (server: FastifyZodProvider) => {
|
||||
{ prefix: "/workspace" }
|
||||
);
|
||||
|
||||
await server.register(registerCaRouter, { prefix: "/ca" });
|
||||
await server.register(registerCertRouter, { prefix: "/certificates" });
|
||||
await server.register(
|
||||
async (pkiRouter) => {
|
||||
await pkiRouter.register(registerCaRouter, { prefix: "/ca" });
|
||||
await pkiRouter.register(registerCertRouter, { prefix: "/certificates" });
|
||||
},
|
||||
{ prefix: "/pki" }
|
||||
);
|
||||
|
||||
await server.register(registerProjectBotRouter, { prefix: "/bot" });
|
||||
await server.register(registerIntegrationRouter, { prefix: "/integration" });
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
import { ForbiddenError } from "@casl/ability";
|
||||
import * as x509 from "@peculiar/x509";
|
||||
import crypto, { KeyObject } from "crypto";
|
||||
@@ -9,6 +10,7 @@ import { TCertificateCertDALFactory } from "@app/services/certificate/certificat
|
||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TCertStatus } from "../certificate/certificate-types";
|
||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||
import { createDistinguishedName } from "./certificate-authority-fns";
|
||||
@@ -118,6 +120,7 @@ export const certificateAuthorityServiceFactory = ({
|
||||
? new Date(notAfter)
|
||||
: new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
||||
|
||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||
const ca = await certificateAuthorityDAL.create(
|
||||
{
|
||||
projectId: project.id,
|
||||
@@ -130,7 +133,7 @@ export const certificateAuthorityServiceFactory = ({
|
||||
commonName,
|
||||
status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE,
|
||||
dn,
|
||||
...(type === CaType.ROOT && { maxPathLength, notBefore: notBeforeDate, notAfter: notAfterDate })
|
||||
...(type === CaType.ROOT && { maxPathLength, notBefore: notBeforeDate, notAfter: notAfterDate, serialNumber })
|
||||
},
|
||||
tx
|
||||
);
|
||||
@@ -140,6 +143,7 @@ export const certificateAuthorityServiceFactory = ({
|
||||
|
||||
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||
name: dn,
|
||||
serialNumber,
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate,
|
||||
signingAlgorithm: alg,
|
||||
@@ -291,7 +295,12 @@ export const certificateAuthorityServiceFactory = ({
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
// eslint-disable-next-line no-bitwise
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment)
|
||||
new x509.KeyUsagesExtension(
|
||||
x509.KeyUsageFlags.keyCertSign |
|
||||
x509.KeyUsageFlags.cRLSign |
|
||||
x509.KeyUsageFlags.digitalSignature |
|
||||
x509.KeyUsageFlags.keyEncipherment
|
||||
)
|
||||
],
|
||||
attributes: [new x509.ChallengePasswordAttribute("password")]
|
||||
});
|
||||
@@ -411,8 +420,9 @@ export const certificateAuthorityServiceFactory = ({
|
||||
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||
}
|
||||
|
||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||
const intermediateCert = await x509.X509CertificateGenerator.create({
|
||||
// serialNumber: "03",
|
||||
serialNumber,
|
||||
subject: csrObj.subject,
|
||||
issuer: certObj.subject,
|
||||
notBefore: notBeforeDate,
|
||||
@@ -421,7 +431,13 @@ export const certificateAuthorityServiceFactory = ({
|
||||
publicKey: csrObj.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.dataEncipherment, true),
|
||||
new x509.KeyUsagesExtension(
|
||||
x509.KeyUsageFlags.keyCertSign |
|
||||
x509.KeyUsageFlags.cRLSign |
|
||||
x509.KeyUsageFlags.digitalSignature |
|
||||
x509.KeyUsageFlags.keyEncipherment,
|
||||
true
|
||||
),
|
||||
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true),
|
||||
await x509.AuthorityKeyIdentifierExtension.create(certObj, false),
|
||||
await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
|
||||
@@ -511,6 +527,7 @@ export const certificateAuthorityServiceFactory = ({
|
||||
maxPathLength: maxPathLength === undefined ? -1 : maxPathLength,
|
||||
notBefore: new Date(certObj.notBefore),
|
||||
notAfter: new Date(certObj.notAfter),
|
||||
serialNumber: certObj.serialNumber,
|
||||
parentCaId: parentCa?.id
|
||||
},
|
||||
tx
|
||||
@@ -601,8 +618,9 @@ export const certificateAuthorityServiceFactory = ({
|
||||
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
||||
}
|
||||
|
||||
const serialNumber = crypto.randomBytes(32).toString("hex");
|
||||
const leafCert = await x509.X509CertificateGenerator.create({
|
||||
// serialNumber: "03",
|
||||
serialNumber,
|
||||
subject: csrObj.subject,
|
||||
issuer: caCertObj.subject,
|
||||
notBefore: notBeforeDate,
|
||||
@@ -611,7 +629,7 @@ export const certificateAuthorityServiceFactory = ({
|
||||
publicKey: csrObj.publicKey,
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.dataEncipherment, true),
|
||||
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)
|
||||
@@ -627,7 +645,9 @@ export const certificateAuthorityServiceFactory = ({
|
||||
const cert = await certificateDAL.create(
|
||||
{
|
||||
caId: ca.id,
|
||||
status: TCertStatus.ACTIVE,
|
||||
commonName,
|
||||
serialNumber,
|
||||
notBefore: notBeforeDate,
|
||||
notAfter: notAfterDate
|
||||
},
|
||||
@@ -651,7 +671,7 @@ export const certificateAuthorityServiceFactory = ({
|
||||
certificateChain: chain.join("\n"),
|
||||
issuingCaCertificate: caCert.certificate,
|
||||
privateKey: skLeaf,
|
||||
serialNumber: leafCert.serialNumber
|
||||
serialNumber
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -11,8 +11,6 @@ export enum CaStatus {
|
||||
PENDING_CERTIFICATE = "pending-certificate"
|
||||
}
|
||||
|
||||
// TODO: attach permissions after draft impl
|
||||
|
||||
export type TCreateCaDTO = {
|
||||
projectSlug: string;
|
||||
type: CaType;
|
||||
|
||||
@@ -7,10 +7,10 @@ import { TCertificateCertDALFactory } from "@app/services/certificate/certificat
|
||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||
|
||||
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO } from "./certificate-types";
|
||||
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types";
|
||||
|
||||
type TCertificateServiceFactoryDep = {
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findById" | "deleteById">;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById">;
|
||||
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
@@ -24,8 +24,8 @@ export const certificateServiceFactory = ({
|
||||
certificateAuthorityDAL,
|
||||
permissionService
|
||||
}: TCertificateServiceFactoryDep) => {
|
||||
const getCertById = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
||||
const cert = await certificateDAL.findById(certId);
|
||||
const getCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@@ -41,8 +41,8 @@ export const certificateServiceFactory = ({
|
||||
return cert;
|
||||
};
|
||||
|
||||
const deleteCertById = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCertDTO) => {
|
||||
const cert = await certificateDAL.findById(certId);
|
||||
const deleteCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TDeleteCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@@ -59,8 +59,31 @@ export const certificateServiceFactory = ({
|
||||
return deletedCert;
|
||||
};
|
||||
|
||||
const getCertCert = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
|
||||
const cert = await certificateDAL.findById(certId);
|
||||
const revokeCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TRevokeCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
actor,
|
||||
actorId,
|
||||
ca.projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId
|
||||
);
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
|
||||
// WIP
|
||||
|
||||
// const revocationDate = new Date();
|
||||
|
||||
// const serialNumber2 = crypto.randomBytes(16).toString("hex");
|
||||
// const crlEntry = new x509.X509CrlEntry(serialNumber2, new Date(), []);
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
|
||||
const cert = await certificateDAL.findOne({ serialNumber });
|
||||
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||
|
||||
const { permission } = await permissionService.getProjectPermission(
|
||||
@@ -73,7 +96,7 @@ export const certificateServiceFactory = ({
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
||||
|
||||
const certCert = await certificateCertDAL.findOne({ certId });
|
||||
const certCert = await certificateCertDAL.findOne({ certId: cert.id });
|
||||
const certObj = new x509.X509Certificate(certCert.certificate);
|
||||
|
||||
return {
|
||||
@@ -84,8 +107,9 @@ export const certificateServiceFactory = ({
|
||||
};
|
||||
|
||||
return {
|
||||
getCertById,
|
||||
deleteCertById,
|
||||
getCert,
|
||||
deleteCert,
|
||||
revokeCert,
|
||||
getCertCert
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
|
||||
export enum TCertStatus {
|
||||
ACTIVE = "active",
|
||||
REVOKED = "revoked"
|
||||
}
|
||||
|
||||
export type TGetCertDTO = {
|
||||
certId: string;
|
||||
serialNumber: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TDeleteCertDTO = {
|
||||
certId: string;
|
||||
serialNumber: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TRevokeCertDTO = {
|
||||
serialNumber: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
export type TGetCertCertDTO = {
|
||||
certId: string;
|
||||
serialNumber: string;
|
||||
} & Omit<TProjectPermission, "projectId">;
|
||||
|
||||
@@ -67,7 +67,7 @@ type TProjectServiceFactoryDep = {
|
||||
projectUserMembershipRoleDAL: Pick<TProjectUserMembershipRoleDALFactory, "create">;
|
||||
secretBlindIndexDAL: Pick<TSecretBlindIndexDALFactory, "create">;
|
||||
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "find">;
|
||||
certificateDAL: TCertificateDALFactory;
|
||||
certificateDAL: Pick<TCertificateDALFactory, "find">;
|
||||
permissionService: TPermissionServiceFactory;
|
||||
orgService: Pick<TOrgServiceFactory, "addGhostUser">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
|
||||
Reference in New Issue
Block a user