mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-20 00:37:35 +00:00
Preliminary scaffolding for CA structures
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
import * as x509 from "@peculiar/x509";
|
||||
import crypto, { KeyObject } from "crypto";
|
||||
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
|
||||
import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal";
|
||||
import {
|
||||
// TIssueCertFromCaDTO,
|
||||
CAType,
|
||||
TCreateCaDTO,
|
||||
TGetCaCertDTO,
|
||||
TGetCaCsrDTO
|
||||
} from "./certificate-authority-types";
|
||||
|
||||
type TCertificateAuthorityServiceFactoryDep = {
|
||||
// TODO: Pick
|
||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
||||
certificateAuthorityCertDAL: TCertificateAuthorityCertDALFactory;
|
||||
certificateAuthoritySkDAL: TCertificateAuthoritySkDALFactory;
|
||||
projectDAL: TProjectDALFactory;
|
||||
};
|
||||
|
||||
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
|
||||
|
||||
type DNParts = {
|
||||
commonName?: string;
|
||||
organization?: string;
|
||||
ou?: string;
|
||||
country?: string;
|
||||
province?: string;
|
||||
locality?: string;
|
||||
};
|
||||
|
||||
function createDistinguishedName(parts: DNParts) {
|
||||
const dnParts = [];
|
||||
if (parts.country) dnParts.push(`C=${parts.country}`);
|
||||
if (parts.organization) dnParts.push(`O=${parts.organization}`);
|
||||
if (parts.ou) dnParts.push(`OU=${parts.ou}`);
|
||||
if (parts.province) dnParts.push(`ST=${parts.province}`);
|
||||
if (parts.commonName) dnParts.push(`CN=${parts.commonName}`);
|
||||
if (parts.locality) dnParts.push(`L=${parts.locality}`);
|
||||
return dnParts.join(", ");
|
||||
}
|
||||
|
||||
export const certificateAuthorityServiceFactory = ({
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCertDAL,
|
||||
certificateAuthoritySkDAL,
|
||||
projectDAL
|
||||
}: TCertificateAuthorityServiceFactoryDep) => {
|
||||
/**
|
||||
* Generates a new root or intermediate CA
|
||||
* @param param0
|
||||
* @returns
|
||||
*/
|
||||
const createCa = async ({
|
||||
projectSlug,
|
||||
type,
|
||||
commonName,
|
||||
organization,
|
||||
ou,
|
||||
country,
|
||||
province,
|
||||
locality,
|
||||
// actorId,
|
||||
// actorAuthMethod,
|
||||
// actor,
|
||||
actorOrgId
|
||||
}: TCreateCaDTO) => {
|
||||
const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId);
|
||||
if (!project) throw new BadRequestError({ message: "Project not found" });
|
||||
|
||||
const dn = createDistinguishedName({
|
||||
commonName,
|
||||
organization,
|
||||
ou,
|
||||
country,
|
||||
province,
|
||||
locality
|
||||
});
|
||||
|
||||
const alg = {
|
||||
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"]);
|
||||
|
||||
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
|
||||
const skObj = KeyObject.from(keys.privateKey);
|
||||
const sk = skObj.export({ format: "pem", type: "pkcs8" }) as string;
|
||||
const pkObj = KeyObject.from(keys.publicKey);
|
||||
const pk = pkObj.export({ format: "pem", type: "spki" }) as string;
|
||||
|
||||
const newCa = await certificateAuthorityDAL.transaction(async (tx) => {
|
||||
const ca = await certificateAuthorityDAL.create(
|
||||
{
|
||||
projectId: project.id,
|
||||
type,
|
||||
dn
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
if (type === CAType.ROOT) {
|
||||
// note: self-signed cert only applicable for root CA
|
||||
const cert = await x509.X509CertificateGenerator.createSelfSigned({
|
||||
name: dn,
|
||||
notBefore: new Date("2020/01/01"),
|
||||
notAfter: new Date("2020/01/02"),
|
||||
signingAlgorithm: alg,
|
||||
keys,
|
||||
extensions: [
|
||||
new x509.BasicConstraintsExtension(true, 2, true),
|
||||
new x509.ExtendedKeyUsageExtension(["1.2.3.4.5.6.7", "2.3.4.5.6.7.8"], true),
|
||||
// eslint-disable-next-line no-bitwise
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
||||
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey)
|
||||
]
|
||||
});
|
||||
const certificate = cert.toString("pem");
|
||||
await certificateAuthorityCertDAL.create(
|
||||
{
|
||||
caId: ca.id,
|
||||
certificate, // TODO: encrypt
|
||||
certificateChain: "" // TODO: encrypt
|
||||
},
|
||||
tx
|
||||
);
|
||||
}
|
||||
|
||||
await certificateAuthoritySkDAL.create(
|
||||
{
|
||||
caId: ca.id,
|
||||
pk, // TODO: encrypt
|
||||
sk // TODO: encrypt
|
||||
},
|
||||
tx
|
||||
);
|
||||
|
||||
return ca;
|
||||
});
|
||||
|
||||
return newCa;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a CSR for a CA
|
||||
*/
|
||||
const getCaCsr = async ({ caId }: TGetCaCsrDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findById(caId);
|
||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||
// TODO: permissioning
|
||||
|
||||
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
|
||||
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });
|
||||
|
||||
const alg = {
|
||||
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 pkObj = crypto.createPublicKey({ key: caKeys.pk, format: "pem", type: "spki" });
|
||||
|
||||
const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
|
||||
"sign"
|
||||
]);
|
||||
const pk = await crypto.subtle.importKey("spki", pkObj.export({ format: "der", type: "spki" }), alg, true, [
|
||||
"verify"
|
||||
]);
|
||||
|
||||
const cert = new x509.X509Certificate(caCert.certificate);
|
||||
|
||||
const csr = await x509.Pkcs10CertificateRequestGenerator.create({
|
||||
name: cert.subject || ca.dn,
|
||||
keys: { privateKey: sk, publicKey: pk },
|
||||
signingAlgorithm: alg,
|
||||
extensions: [
|
||||
// eslint-disable-next-line no-bitwise
|
||||
new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment)
|
||||
],
|
||||
attributes: [new x509.ChallengePasswordAttribute("password")]
|
||||
});
|
||||
|
||||
return csr.toString("base64");
|
||||
};
|
||||
|
||||
/**
|
||||
* Return certificate and certificate chain for CA
|
||||
*/
|
||||
const getCaCert = async ({ caId }: TGetCaCertDTO) => {
|
||||
const ca = await certificateAuthorityDAL.findById(caId);
|
||||
if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||
// TODO: permissioning
|
||||
|
||||
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
|
||||
|
||||
return {
|
||||
certificate: caCert.certificate,
|
||||
certificateChain: caCert.certificateChain
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Issue new certificate
|
||||
*/
|
||||
const issueCertFromCa = async () => {
|
||||
// WIP: parse publicKey from CSR
|
||||
// const csrArrayBuffer = Uint8Array.from(atob(csr), (c) => c.charCodeAt(0));
|
||||
// const csrR = new x509.Pkcs10CertificateRequest(csrArrayBuffer);
|
||||
|
||||
// const ca = await certificateAuthorityDAL.findById(caId);
|
||||
// if (!ca) throw new BadRequestError({ message: "CA not found" });
|
||||
// // TODO: permissioning
|
||||
|
||||
// const alg = {
|
||||
// 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 caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id });
|
||||
|
||||
// const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" });
|
||||
// const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
|
||||
// "sign"
|
||||
// ]);
|
||||
|
||||
// const cert = new x509.X509Certificate(caCert.certificate);
|
||||
|
||||
// const leafCert = await x509.X509CertificateGenerator.create({
|
||||
// // serialNumber: "03",
|
||||
// subject: "CN=Leaf", // TODO: make dynamic
|
||||
// issuer: cert.subject,
|
||||
// notBefore: new Date(notBefore),
|
||||
// notAfter: new Date(notAfter),
|
||||
// signingKey: sk,
|
||||
// publicKey: leafKeys.publicKey,
|
||||
// signingAlgorithm: alg,
|
||||
// extensions: [
|
||||
// new x509.KeyUsagesExtension(x509.KeyUsageFlags.dataEncipherment, true),
|
||||
// new x509.BasicConstraintsExtension(false),
|
||||
// await x509.AuthorityKeyIdentifierExtension.create(cert, false),
|
||||
// await x509.SubjectKeyIdentifierExtension.create(leafKeys.publicKey)
|
||||
// ]
|
||||
// });
|
||||
|
||||
// console.log("leafCert: ", leafCert);
|
||||
|
||||
return "";
|
||||
};
|
||||
|
||||
return {
|
||||
createCa,
|
||||
getCaCsr,
|
||||
getCaCert,
|
||||
issueCertFromCa
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user