mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
misc: addressed review comments
This commit is contained in:
@@ -12,7 +12,7 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
tb.foreign("certificateTemplateId").references("id").inTable(TableName.CertificateTemplate).onDelete("CASCADE");
|
tb.foreign("certificateTemplateId").references("id").inTable(TableName.CertificateTemplate).onDelete("CASCADE");
|
||||||
tb.binary("encryptedCaChain").notNullable();
|
tb.binary("encryptedCaChain").notNullable();
|
||||||
tb.string("hashedPassphrase").notNullable();
|
tb.string("hashedPassphrase").notNullable();
|
||||||
tb.boolean("isEnabled");
|
tb.boolean("isEnabled").notNullable();
|
||||||
tb.timestamps(true, true, true);
|
tb.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1554,9 +1554,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
certificate: leafCert,
|
certificate: leafCert,
|
||||||
// certificate: leafCert.toString("pem"),
|
|
||||||
// certificateObj: leafCert,
|
|
||||||
// rawCertificate: leafCert.rawData,
|
|
||||||
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
|
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
|
||||||
issuingCaCertificate,
|
issuingCaCertificate,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
|
|||||||
22
backend/src/services/certificate-est/certificate-est-fns.ts
Normal file
22
backend/src/services/certificate-est/certificate-est-fns.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs";
|
||||||
|
|
||||||
|
export const convertRawCertsToPkcs7 = (rawCertificate: ArrayBuffer[]) => {
|
||||||
|
const certs = rawCertificate.map((rawCert) => Certificate.fromBER(rawCert));
|
||||||
|
const cmsSigned = new SignedData({
|
||||||
|
encapContentInfo: new EncapsulatedContentInfo({
|
||||||
|
eContentType: "1.2.840.113549.1.7.1" // not encrypted and not compressed data
|
||||||
|
}),
|
||||||
|
certificates: certs
|
||||||
|
});
|
||||||
|
|
||||||
|
const cmsContent = new ContentInfo({
|
||||||
|
contentType: "1.2.840.113549.1.7.2", // SignedData
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
content: cmsSigned.toSchema()
|
||||||
|
});
|
||||||
|
|
||||||
|
const derBuffer = cmsContent.toSchema().toBER(false);
|
||||||
|
const base64Pkcs7 = Buffer.from(derBuffer).toString("base64");
|
||||||
|
|
||||||
|
return base64Pkcs7;
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ import * as x509 from "@peculiar/x509";
|
|||||||
|
|
||||||
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { isCertChainValid } from "../certificate/certificate-fns";
|
||||||
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal";
|
||||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||||
import { getCaCertChain, getCaCertChains } from "../certificate-authority/certificate-authority-fns";
|
import { getCaCertChain, getCaCertChains } from "../certificate-authority/certificate-authority-fns";
|
||||||
@@ -10,6 +11,7 @@ import { TCertificateTemplateDALFactory } from "../certificate-template/certific
|
|||||||
import { TCertificateTemplateServiceFactory } from "../certificate-template/certificate-template-service";
|
import { TCertificateTemplateServiceFactory } from "../certificate-template/certificate-template-service";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
|
import { convertRawCertsToPkcs7 } from "./certificate-est-fns";
|
||||||
|
|
||||||
type TCertificateEstServiceFactoryDep = {
|
type TCertificateEstServiceFactoryDep = {
|
||||||
certificateAuthorityService: Pick<TCertificateAuthorityServiceFactory, "signCertFromCa">;
|
certificateAuthorityService: Pick<TCertificateAuthorityServiceFactory, "signCertFromCa">;
|
||||||
@@ -62,15 +64,7 @@ export const certificateEstServiceFactory = ({
|
|||||||
throw new UnauthorizedError({ message: "Missing client certificate" });
|
throw new UnauthorizedError({ message: "Missing client certificate" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientCertBody = leafCertificate
|
const cert = new x509.X509Certificate(leafCertificate);
|
||||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
|
||||||
.replace("-----END CERTIFICATE-----", "")
|
|
||||||
.replace(/\n/g, "")
|
|
||||||
.replace(/ /g, "")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const cert = new x509.X509Certificate(clientCertBody);
|
|
||||||
|
|
||||||
// We have to assert that the client certificate provided can be traced back to the Root CA
|
// We have to assert that the client certificate provided can be traced back to the Root CA
|
||||||
const caCertChains = await getCaCertChains({
|
const caCertChains = await getCaCertChains({
|
||||||
caId: certTemplate.caId,
|
caId: certTemplate.caId,
|
||||||
@@ -80,21 +74,15 @@ export const certificateEstServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const caChainBuilders = caCertChains.map((chain) => {
|
|
||||||
const caCert = new x509.X509Certificate(chain.certificate);
|
|
||||||
const caChain =
|
|
||||||
chain.certificateChain
|
|
||||||
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
|
|
||||||
?.map((c) => new x509.X509Certificate(c)) || [];
|
|
||||||
return new x509.X509ChainBuilder({
|
|
||||||
certificates: [caCert, ...caChain]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const verifiedChains = await Promise.all(
|
const verifiedChains = await Promise.all(
|
||||||
caChainBuilders.map(async (caChainBuilder) => {
|
caCertChains.map((chain) => {
|
||||||
const chainItems = await caChainBuilder.build(cert);
|
const caCert = new x509.X509Certificate(chain.certificate);
|
||||||
return chainItems.length === caChainBuilder.certificates.length + 1;
|
const caChain =
|
||||||
|
chain.certificateChain
|
||||||
|
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
|
||||||
|
?.map((c) => new x509.X509Certificate(c)) || [];
|
||||||
|
|
||||||
|
return isCertChainValid([cert, caCert, ...caChain]);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -138,8 +126,7 @@ export const certificateEstServiceFactory = ({
|
|||||||
csr
|
csr
|
||||||
});
|
});
|
||||||
|
|
||||||
const certs = new x509.X509Certificates([certificate]);
|
return convertRawCertsToPkcs7([certificate.rawData]);
|
||||||
return certs.export("base64");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const simpleEnroll = async ({
|
const simpleEnroll = async ({
|
||||||
@@ -173,10 +160,6 @@ export const certificateEstServiceFactory = ({
|
|||||||
|
|
||||||
if (!caCerts) throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
if (!caCerts) throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
||||||
|
|
||||||
const caChain = new x509.X509ChainBuilder({
|
|
||||||
certificates: caCerts
|
|
||||||
});
|
|
||||||
|
|
||||||
const leafCertificate = decodeURIComponent(sslClientCert).match(
|
const leafCertificate = decodeURIComponent(sslClientCert).match(
|
||||||
/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g
|
/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g
|
||||||
)?.[0];
|
)?.[0];
|
||||||
@@ -185,17 +168,10 @@ export const certificateEstServiceFactory = ({
|
|||||||
throw new BadRequestError({ message: "Missing client certificate" });
|
throw new BadRequestError({ message: "Missing client certificate" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientCertBody = leafCertificate
|
const certObj = new x509.X509Certificate(leafCertificate);
|
||||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
if (!(await isCertChainValid([certObj, ...caCerts]))) {
|
||||||
.replace("-----END CERTIFICATE-----", "")
|
throw new BadRequestError({ message: "Invalid certificate chain" });
|
||||||
.replace(/\n/g, "")
|
}
|
||||||
.replace(/ /g, "")
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
const certObj = new x509.X509Certificate(clientCertBody);
|
|
||||||
const chainItems = await caChain.build(certObj);
|
|
||||||
|
|
||||||
if (chainItems.length !== caCerts.length + 1) throw new BadRequestError({ message: "Invalid certificate chain" });
|
|
||||||
|
|
||||||
const { certificate } = await certificateAuthorityService.signCertFromCa({
|
const { certificate } = await certificateAuthorityService.signCertFromCa({
|
||||||
isInternal: true,
|
isInternal: true,
|
||||||
@@ -203,8 +179,7 @@ export const certificateEstServiceFactory = ({
|
|||||||
csr
|
csr
|
||||||
});
|
});
|
||||||
|
|
||||||
const certs = new x509.X509Certificates([certificate]);
|
return convertRawCertsToPkcs7([certificate.rawData]);
|
||||||
return certs.export("base64");
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -240,16 +215,13 @@ export const certificateEstServiceFactory = ({
|
|||||||
|
|
||||||
if (!certificates) throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
if (!certificates) throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
||||||
|
|
||||||
const chain = new x509.X509ChainBuilder({
|
const caCertificate = new x509.X509Certificate(caCert);
|
||||||
certificates
|
|
||||||
});
|
|
||||||
|
|
||||||
const chainItems = await chain.build(new x509.X509Certificate(caCert));
|
if (!(await isCertChainValid([caCertificate, ...certificates]))) {
|
||||||
|
|
||||||
if (chainItems.length !== certificates.length + 1)
|
|
||||||
throw new BadRequestError({ message: "Invalid certificate chain" });
|
throw new BadRequestError({ message: "Invalid certificate chain" });
|
||||||
|
}
|
||||||
|
|
||||||
return chainItems.export("base64");
|
return convertRawCertsToPkcs7([caCertificate.rawData, ...certificates.map((cert) => cert.rawData)]);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import * as x509 from "@peculiar/x509";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
import { TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
|
import { TCertificateTemplateEstConfigsUpdate } from "@app/db/schemas";
|
||||||
@@ -7,6 +8,7 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
|
||||||
|
import { isCertChainValid } from "../certificate/certificate-fns";
|
||||||
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal";
|
||||||
import { TKmsServiceFactory } from "../kms/kms-service";
|
import { TKmsServiceFactory } from "../kms/kms-service";
|
||||||
import { TProjectDALFactory } from "../project/project-dal";
|
import { TProjectDALFactory } from "../project/project-dal";
|
||||||
@@ -241,6 +243,19 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// validate CA chain
|
||||||
|
const certificates = caChain
|
||||||
|
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
|
||||||
|
?.map((cert) => new x509.X509Certificate(cert));
|
||||||
|
|
||||||
|
if (!certificates) {
|
||||||
|
throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await isCertChainValid(certificates))) {
|
||||||
|
throw new BadRequestError({ message: "Invalid certificate chain" });
|
||||||
|
}
|
||||||
|
|
||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
kmsId: certificateManagerKmsId
|
kmsId: certificateManagerKmsId
|
||||||
});
|
});
|
||||||
@@ -313,6 +328,18 @@ export const certificateTemplateServiceFactory = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (caChain) {
|
if (caChain) {
|
||||||
|
const certificates = caChain
|
||||||
|
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
|
||||||
|
?.map((cert) => new x509.X509Certificate(cert));
|
||||||
|
|
||||||
|
if (!certificates) {
|
||||||
|
throw new BadRequestError({ message: "Failed to parse certificate chain" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(await isCertChainValid(certificates))) {
|
||||||
|
throw new BadRequestError({ message: "Invalid certificate chain" });
|
||||||
|
}
|
||||||
|
|
||||||
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
const kmsEncryptor = await kmsService.encryptWithKmsKey({
|
||||||
kmsId: certificateManagerKmsId
|
kmsId: certificateManagerKmsId
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,3 +24,24 @@ export const revocationReasonToCrlCode = (crlReason: CrlReason) => {
|
|||||||
return x509.X509CrlReason.unspecified;
|
return x509.X509CrlReason.unspecified;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isCertChainValid = async (certificates: x509.X509Certificate[]) => {
|
||||||
|
if (certificates.length === 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for self-signed
|
||||||
|
if (certificates.length === 2 && certificates[0].equal(certificates[1])) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leafCert = certificates[0];
|
||||||
|
const chain = new x509.X509ChainBuilder({
|
||||||
|
certificates: certificates.slice(1)
|
||||||
|
});
|
||||||
|
|
||||||
|
const chainItems = await chain.build(leafCert);
|
||||||
|
|
||||||
|
// chain.build() implicitly verifies the chain
|
||||||
|
return chainItems.length === certificates.length;
|
||||||
|
};
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const CertificateTemplatesSection = () => {
|
|||||||
<p className="text-xl font-semibold text-mineshaft-100">Certificate Templates</p>
|
<p className="text-xl font-semibold text-mineshaft-100">Certificate Templates</p>
|
||||||
<ProjectPermissionCan
|
<ProjectPermissionCan
|
||||||
I={ProjectPermissionActions.Create}
|
I={ProjectPermissionActions.Create}
|
||||||
a={ProjectPermissionSub.Certificates}
|
a={ProjectPermissionSub.CertificateTemplates}
|
||||||
>
|
>
|
||||||
{(isAllowed) => (
|
{(isAllowed) => (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user