mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 06:29:25 +00:00
835 lines
27 KiB
TypeScript
835 lines
27 KiB
TypeScript
import { ForbiddenError } from "@casl/ability";
|
|
import * as x509 from "@peculiar/x509";
|
|
|
|
import { ActionProjectType, OrganizationActionScope } from "@app/db/schemas";
|
|
import { crypto } from "@app/lib/crypto/cryptography";
|
|
import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/errors";
|
|
import { isValidIp } from "@app/lib/ip";
|
|
import { ms } from "@app/lib/ms";
|
|
import { isFQDN } from "@app/lib/validator/validate-url";
|
|
import { constructPemChainFromCerts } from "@app/services/certificate/certificate-fns";
|
|
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "@app/services/certificate/certificate-types";
|
|
import {
|
|
createSerialNumber,
|
|
keyAlgorithmToAlgCfg
|
|
} from "@app/services/certificate-authority/certificate-authority-fns";
|
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|
import { KmsDataKey } from "@app/services/kms/kms-types";
|
|
|
|
import { TLicenseServiceFactory } from "../license/license-service";
|
|
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
|
|
import { TPermissionServiceFactory } from "../permission/permission-service-types";
|
|
import { ProjectPermissionKmipActions, ProjectPermissionSub } from "../permission/project-permission";
|
|
import { TKmipClientCertificateDALFactory } from "./kmip-client-certificate-dal";
|
|
import { TKmipClientDALFactory } from "./kmip-client-dal";
|
|
import { TKmipOrgConfigDALFactory } from "./kmip-org-config-dal";
|
|
import { TKmipOrgServerCertificateDALFactory } from "./kmip-org-server-certificate-dal";
|
|
import {
|
|
TCreateKmipClientCertificateDTO,
|
|
TCreateKmipClientDTO,
|
|
TDeleteKmipClientDTO,
|
|
TGenerateOrgKmipServerCertificateDTO,
|
|
TGetKmipClientDTO,
|
|
TGetOrgKmipDTO,
|
|
TListKmipClientsByProjectIdDTO,
|
|
TRegisterServerDTO,
|
|
TSetupOrgKmipDTO,
|
|
TUpdateKmipClientDTO
|
|
} from "./kmip-types";
|
|
|
|
type TKmipServiceFactoryDep = {
|
|
kmipClientDAL: TKmipClientDALFactory;
|
|
kmipClientCertificateDAL: TKmipClientCertificateDALFactory;
|
|
kmipOrgServerCertificateDAL: TKmipOrgServerCertificateDALFactory;
|
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
|
|
kmsService: Pick<TKmsServiceFactory, "createCipherPairWithDataKey">;
|
|
kmipOrgConfigDAL: TKmipOrgConfigDALFactory;
|
|
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
|
};
|
|
|
|
export type TKmipServiceFactory = ReturnType<typeof kmipServiceFactory>;
|
|
|
|
export const kmipServiceFactory = ({
|
|
kmipClientDAL,
|
|
permissionService,
|
|
kmipClientCertificateDAL,
|
|
kmipOrgConfigDAL,
|
|
kmsService,
|
|
kmipOrgServerCertificateDAL,
|
|
licenseService
|
|
}: TKmipServiceFactoryDep) => {
|
|
const createKmipClient = async ({
|
|
actor,
|
|
actorId,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
projectId,
|
|
name,
|
|
description,
|
|
permissions
|
|
}: TCreateKmipClientDTO) => {
|
|
if (crypto.isFipsModeEnabled()) {
|
|
throw new BadRequestError({
|
|
message: "KMIP is currently not supported in FIPS mode of operation."
|
|
});
|
|
}
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.KMS
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionKmipActions.CreateClients,
|
|
ProjectPermissionSub.Kmip
|
|
);
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to create KMIP client. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const kmipClient = await kmipClientDAL.create({
|
|
projectId,
|
|
name,
|
|
description,
|
|
permissions
|
|
});
|
|
|
|
return kmipClient;
|
|
};
|
|
|
|
const updateKmipClient = async ({
|
|
actor,
|
|
actorId,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
name,
|
|
description,
|
|
permissions,
|
|
id
|
|
}: TUpdateKmipClientDTO) => {
|
|
const kmipClient = await kmipClientDAL.findById(id);
|
|
|
|
if (!kmipClient) {
|
|
throw new NotFoundError({
|
|
message: `KMIP client with ID ${id} does not exist`
|
|
});
|
|
}
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to update KMIP client. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: kmipClient.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.KMS
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionKmipActions.UpdateClients,
|
|
ProjectPermissionSub.Kmip
|
|
);
|
|
|
|
const updatedKmipClient = await kmipClientDAL.updateById(id, {
|
|
name,
|
|
description,
|
|
permissions
|
|
});
|
|
|
|
return updatedKmipClient;
|
|
};
|
|
|
|
const deleteKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TDeleteKmipClientDTO) => {
|
|
const kmipClient = await kmipClientDAL.findById(id);
|
|
|
|
if (!kmipClient) {
|
|
throw new NotFoundError({
|
|
message: `KMIP client with ID ${id} does not exist`
|
|
});
|
|
}
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: kmipClient.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.KMS
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionKmipActions.DeleteClients,
|
|
ProjectPermissionSub.Kmip
|
|
);
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to delete KMIP client. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const deletedKmipClient = await kmipClientDAL.deleteById(id);
|
|
|
|
return deletedKmipClient;
|
|
};
|
|
|
|
const getKmipClient = async ({ actor, actorId, actorOrgId, actorAuthMethod, id }: TGetKmipClientDTO) => {
|
|
const kmipClient = await kmipClientDAL.findById(id);
|
|
|
|
if (!kmipClient) {
|
|
throw new NotFoundError({
|
|
message: `KMIP client with ID ${id} does not exist`
|
|
});
|
|
}
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: kmipClient.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.KMS
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
|
|
|
return kmipClient;
|
|
};
|
|
|
|
const listKmipClientsByProjectId = async ({
|
|
actor,
|
|
actorId,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
projectId,
|
|
...rest
|
|
}: TListKmipClientsByProjectIdDTO) => {
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.KMS
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionKmipActions.ReadClients, ProjectPermissionSub.Kmip);
|
|
|
|
return kmipClientDAL.findByProjectId({ projectId, ...rest });
|
|
};
|
|
|
|
const createKmipClientCertificate = async ({
|
|
actor,
|
|
actorId,
|
|
actorOrgId,
|
|
actorAuthMethod,
|
|
ttl,
|
|
keyAlgorithm,
|
|
clientId
|
|
}: TCreateKmipClientCertificateDTO) => {
|
|
const kmipClient = await kmipClientDAL.findById(clientId);
|
|
|
|
if (!kmipClient) {
|
|
throw new NotFoundError({
|
|
message: `KMIP client with ID ${clientId} does not exist`
|
|
});
|
|
}
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to create KMIP client. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor,
|
|
actorId,
|
|
projectId: kmipClient.projectId,
|
|
actorAuthMethod,
|
|
actorOrgId,
|
|
actionProjectType: ActionProjectType.KMS
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(
|
|
ProjectPermissionKmipActions.GenerateClientCertificates,
|
|
ProjectPermissionSub.Kmip
|
|
);
|
|
|
|
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
if (!kmipConfig) {
|
|
throw new InternalServerError({
|
|
message: "KMIP has not been configured for the organization"
|
|
});
|
|
}
|
|
|
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
const caCertObj = new x509.X509Certificate(
|
|
decryptor({ cipherTextBlob: kmipConfig.encryptedClientIntermediateCaCertificate })
|
|
);
|
|
|
|
const notBeforeDate = new Date();
|
|
const notAfterDate = new Date(new Date().getTime() + ms(ttl));
|
|
|
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
|
|
|
// check not before constraint
|
|
if (notBeforeDate < caCertNotBeforeDate) {
|
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
|
}
|
|
|
|
if (notBeforeDate > notAfterDate) throw new BadRequestError({ message: "notBefore date is after notAfter date" });
|
|
|
|
// check not after constraint
|
|
if (notAfterDate > caCertNotAfterDate) {
|
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
|
}
|
|
|
|
const alg = keyAlgorithmToAlgCfg(keyAlgorithm);
|
|
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
|
|
const extensions: x509.Extension[] = [
|
|
new x509.BasicConstraintsExtension(false),
|
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(leafKeys.publicKey),
|
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy
|
|
new x509.KeyUsagesExtension(
|
|
// eslint-disable-next-line no-bitwise
|
|
x509.KeyUsageFlags[CertKeyUsage.DIGITAL_SIGNATURE] |
|
|
x509.KeyUsageFlags[CertKeyUsage.KEY_ENCIPHERMENT] |
|
|
x509.KeyUsageFlags[CertKeyUsage.KEY_AGREEMENT],
|
|
true
|
|
),
|
|
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.CLIENT_AUTH]], true)
|
|
];
|
|
|
|
const caAlg = keyAlgorithmToAlgCfg(kmipConfig.caKeyAlgorithm as CertKeyAlgorithm);
|
|
|
|
const caSkObj = crypto.nativeCrypto.createPrivateKey({
|
|
key: decryptor({ cipherTextBlob: kmipConfig.encryptedClientIntermediateCaPrivateKey }),
|
|
format: "der",
|
|
type: "pkcs8"
|
|
});
|
|
|
|
const caPrivateKey = await crypto.nativeCrypto.subtle.importKey(
|
|
"pkcs8",
|
|
caSkObj.export({ format: "der", type: "pkcs8" }),
|
|
caAlg,
|
|
true,
|
|
["sign"]
|
|
);
|
|
|
|
const serialNumber = createSerialNumber();
|
|
const leafCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber,
|
|
subject: `OU=${kmipClient.projectId},CN=${clientId}`,
|
|
issuer: caCertObj.subject,
|
|
notBefore: notBeforeDate,
|
|
notAfter: notAfterDate,
|
|
signingKey: caPrivateKey,
|
|
publicKey: leafKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions
|
|
});
|
|
|
|
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
|
|
|
|
const rootCaCert = new x509.X509Certificate(decryptor({ cipherTextBlob: kmipConfig.encryptedRootCaCertificate }));
|
|
const serverIntermediateCaCert = new x509.X509Certificate(
|
|
decryptor({ cipherTextBlob: kmipConfig.encryptedServerIntermediateCaCertificate })
|
|
);
|
|
|
|
await kmipClientCertificateDAL.create({
|
|
kmipClientId: clientId,
|
|
keyAlgorithm,
|
|
issuedAt: notBeforeDate,
|
|
expiration: notAfterDate,
|
|
serialNumber
|
|
});
|
|
|
|
return {
|
|
serialNumber,
|
|
privateKey: skLeafObj.export({ format: "pem", type: "pkcs8" }) as string,
|
|
certificate: leafCert.toString("pem"),
|
|
certificateChain: constructPemChainFromCerts([serverIntermediateCaCert, rootCaCert]),
|
|
projectId: kmipClient.projectId
|
|
};
|
|
};
|
|
|
|
const getServerCertificateBySerialNumber = async (orgId: string, serialNumber: string) => {
|
|
const serverCert = await kmipOrgServerCertificateDAL.findOne({
|
|
serialNumber,
|
|
orgId
|
|
});
|
|
|
|
if (!serverCert) {
|
|
throw new NotFoundError({
|
|
message: "Server certificate not found"
|
|
});
|
|
}
|
|
|
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId
|
|
});
|
|
|
|
const parsedCertificate = new x509.X509Certificate(decryptor({ cipherTextBlob: serverCert.encryptedCertificate }));
|
|
|
|
return {
|
|
publicKey: parsedCertificate.publicKey.toString("pem"),
|
|
keyAlgorithm: serverCert.keyAlgorithm as CertKeyAlgorithm
|
|
};
|
|
};
|
|
|
|
const setupOrgKmip = async ({ caKeyAlgorithm, actorOrgId, actor, actorId, actorAuthMethod }: TSetupOrgKmipDTO) => {
|
|
const { permission } = await permissionService.getOrgPermission({
|
|
scope: OrganizationActionScope.Any,
|
|
actor,
|
|
actorId,
|
|
orgId: actorOrgId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Setup, OrgPermissionSubjects.Kmip);
|
|
|
|
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
if (kmipConfig) {
|
|
throw new BadRequestError({
|
|
message: "KMIP has already been configured for the organization"
|
|
});
|
|
}
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to setup KMIP. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const alg = keyAlgorithmToAlgCfg(caKeyAlgorithm);
|
|
|
|
// generate root CA
|
|
const rootCaSerialNumber = createSerialNumber();
|
|
const rootCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const rootCaSkObj = crypto.nativeCrypto.KeyObject.from(rootCaKeys.privateKey);
|
|
const rootCaIssuedAt = new Date();
|
|
const rootCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 20));
|
|
|
|
const rootCaCert = await x509.X509CertificateGenerator.createSelfSigned({
|
|
name: `CN=KMIP Root CA,OU=${actorOrgId}`,
|
|
serialNumber: rootCaSerialNumber,
|
|
notBefore: rootCaIssuedAt,
|
|
notAfter: rootCaExpiration,
|
|
signingAlgorithm: alg,
|
|
keys: rootCaKeys,
|
|
extensions: [
|
|
// eslint-disable-next-line no-bitwise
|
|
new x509.KeyUsagesExtension(x509.KeyUsageFlags.keyCertSign | x509.KeyUsageFlags.cRLSign, true),
|
|
await x509.SubjectKeyIdentifierExtension.create(rootCaKeys.publicKey)
|
|
]
|
|
});
|
|
|
|
// generate intermediate server CA
|
|
const serverIntermediateCaSerialNumber = createSerialNumber();
|
|
const serverIntermediateCaIssuedAt = new Date();
|
|
const serverIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
|
const serverIntermediateCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const serverIntermediateCaSkObj = crypto.nativeCrypto.KeyObject.from(serverIntermediateCaKeys.privateKey);
|
|
|
|
const serverIntermediateCaCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber: serverIntermediateCaSerialNumber,
|
|
subject: `CN=KMIP Server Intermediate CA,OU=${actorOrgId}`,
|
|
issuer: rootCaCert.subject,
|
|
notBefore: serverIntermediateCaIssuedAt,
|
|
notAfter: serverIntermediateCaExpiration,
|
|
signingKey: rootCaKeys.privateKey,
|
|
publicKey: serverIntermediateCaKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions: [
|
|
new x509.KeyUsagesExtension(
|
|
// eslint-disable-next-line no-bitwise
|
|
x509.KeyUsageFlags.keyCertSign |
|
|
x509.KeyUsageFlags.cRLSign |
|
|
x509.KeyUsageFlags.digitalSignature |
|
|
x509.KeyUsageFlags.keyEncipherment,
|
|
true
|
|
),
|
|
new x509.BasicConstraintsExtension(true, 0, true),
|
|
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(serverIntermediateCaKeys.publicKey)
|
|
]
|
|
});
|
|
|
|
// generate intermediate client CA
|
|
const clientIntermediateCaSerialNumber = createSerialNumber();
|
|
const clientIntermediateCaIssuedAt = new Date();
|
|
const clientIntermediateCaExpiration = new Date(new Date().setFullYear(new Date().getFullYear() + 10));
|
|
const clientIntermediateCaKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
const clientIntermediateCaSkObj = crypto.nativeCrypto.KeyObject.from(clientIntermediateCaKeys.privateKey);
|
|
|
|
const clientIntermediateCaCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber: clientIntermediateCaSerialNumber,
|
|
subject: `CN=KMIP Client Intermediate CA,OU=${actorOrgId}`,
|
|
issuer: rootCaCert.subject,
|
|
notBefore: clientIntermediateCaIssuedAt,
|
|
notAfter: clientIntermediateCaExpiration,
|
|
signingKey: rootCaKeys.privateKey,
|
|
publicKey: clientIntermediateCaKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions: [
|
|
new x509.KeyUsagesExtension(
|
|
// eslint-disable-next-line no-bitwise
|
|
x509.KeyUsageFlags.keyCertSign |
|
|
x509.KeyUsageFlags.cRLSign |
|
|
x509.KeyUsageFlags.digitalSignature |
|
|
x509.KeyUsageFlags.keyEncipherment,
|
|
true
|
|
),
|
|
new x509.BasicConstraintsExtension(true, 0, true),
|
|
await x509.AuthorityKeyIdentifierExtension.create(rootCaCert, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(clientIntermediateCaKeys.publicKey)
|
|
]
|
|
});
|
|
|
|
const { encryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
await kmipOrgConfigDAL.create({
|
|
orgId: actorOrgId,
|
|
caKeyAlgorithm,
|
|
rootCaIssuedAt,
|
|
rootCaExpiration,
|
|
rootCaSerialNumber,
|
|
encryptedRootCaCertificate: encryptor({ plainText: Buffer.from(rootCaCert.rawData) }).cipherTextBlob,
|
|
encryptedRootCaPrivateKey: encryptor({
|
|
plainText: rootCaSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob,
|
|
serverIntermediateCaIssuedAt,
|
|
serverIntermediateCaExpiration,
|
|
serverIntermediateCaSerialNumber,
|
|
encryptedServerIntermediateCaCertificate: encryptor({
|
|
plainText: Buffer.from(new Uint8Array(serverIntermediateCaCert.rawData))
|
|
}).cipherTextBlob,
|
|
encryptedServerIntermediateCaChain: encryptor({ plainText: Buffer.from(rootCaCert.toString("pem")) })
|
|
.cipherTextBlob,
|
|
encryptedServerIntermediateCaPrivateKey: encryptor({
|
|
plainText: serverIntermediateCaSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob,
|
|
clientIntermediateCaIssuedAt,
|
|
clientIntermediateCaExpiration,
|
|
clientIntermediateCaSerialNumber,
|
|
encryptedClientIntermediateCaCertificate: encryptor({
|
|
plainText: Buffer.from(new Uint8Array(clientIntermediateCaCert.rawData))
|
|
}).cipherTextBlob,
|
|
encryptedClientIntermediateCaChain: encryptor({ plainText: Buffer.from(rootCaCert.toString("pem")) })
|
|
.cipherTextBlob,
|
|
encryptedClientIntermediateCaPrivateKey: encryptor({
|
|
plainText: clientIntermediateCaSkObj.export({
|
|
type: "pkcs8",
|
|
format: "der"
|
|
})
|
|
}).cipherTextBlob
|
|
});
|
|
|
|
return {
|
|
serverCertificateChain: constructPemChainFromCerts([serverIntermediateCaCert, rootCaCert]),
|
|
clientCertificateChain: constructPemChainFromCerts([clientIntermediateCaCert, rootCaCert])
|
|
};
|
|
};
|
|
|
|
const getOrgKmip = async ({ actorOrgId, actor, actorId, actorAuthMethod }: TGetOrgKmipDTO) => {
|
|
await permissionService.getOrgPermission({
|
|
scope: OrganizationActionScope.Any,
|
|
actor,
|
|
actorId,
|
|
orgId: actorOrgId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
});
|
|
|
|
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
if (!kmipConfig) {
|
|
throw new BadRequestError({
|
|
message: "KMIP has not been configured for the organization"
|
|
});
|
|
}
|
|
|
|
const { decryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
const rootCaCert = new x509.X509Certificate(decryptor({ cipherTextBlob: kmipConfig.encryptedRootCaCertificate }));
|
|
const serverIntermediateCaCert = new x509.X509Certificate(
|
|
decryptor({ cipherTextBlob: kmipConfig.encryptedServerIntermediateCaCertificate })
|
|
);
|
|
|
|
const clientIntermediateCaCert = new x509.X509Certificate(
|
|
decryptor({ cipherTextBlob: kmipConfig.encryptedClientIntermediateCaCertificate })
|
|
);
|
|
|
|
return {
|
|
id: kmipConfig.id,
|
|
serverCertificateChain: constructPemChainFromCerts([serverIntermediateCaCert, rootCaCert]),
|
|
clientCertificateChain: constructPemChainFromCerts([clientIntermediateCaCert, rootCaCert])
|
|
};
|
|
};
|
|
|
|
const generateOrgKmipServerCertificate = async ({
|
|
orgId,
|
|
ttl,
|
|
commonName,
|
|
altNames,
|
|
keyAlgorithm
|
|
}: TGenerateOrgKmipServerCertificateDTO) => {
|
|
const kmipOrgConfig = await kmipOrgConfigDAL.findOne({
|
|
orgId
|
|
});
|
|
|
|
if (!kmipOrgConfig) {
|
|
throw new BadRequestError({
|
|
message: "KMIP has not been configured for the organization"
|
|
});
|
|
}
|
|
|
|
const plan = await licenseService.getPlan(orgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to generate KMIP server certificate. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const { decryptor, encryptor } = await kmsService.createCipherPairWithDataKey({
|
|
type: KmsDataKey.Organization,
|
|
orgId
|
|
});
|
|
|
|
const caCertObj = new x509.X509Certificate(
|
|
decryptor({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaCertificate })
|
|
);
|
|
|
|
const notBeforeDate = new Date();
|
|
const notAfterDate = new Date(new Date().getTime() + ms(ttl));
|
|
|
|
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
|
|
const caCertNotAfterDate = new Date(caCertObj.notAfter);
|
|
|
|
// check not before constraint
|
|
if (notBeforeDate < caCertNotBeforeDate) {
|
|
throw new BadRequestError({ message: "notBefore date is before CA certificate's notBefore date" });
|
|
}
|
|
|
|
if (notBeforeDate > notAfterDate) throw new BadRequestError({ message: "notBefore date is after notAfter date" });
|
|
|
|
// check not after constraint
|
|
if (notAfterDate > caCertNotAfterDate) {
|
|
throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" });
|
|
}
|
|
|
|
const alg = keyAlgorithmToAlgCfg(keyAlgorithm);
|
|
|
|
const leafKeys = await crypto.nativeCrypto.subtle.generateKey(alg, true, ["sign", "verify"]);
|
|
|
|
const extensions: x509.Extension[] = [
|
|
new x509.BasicConstraintsExtension(false),
|
|
await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),
|
|
await x509.SubjectKeyIdentifierExtension.create(leafKeys.publicKey),
|
|
new x509.CertificatePolicyExtension(["2.5.29.32.0"]), // anyPolicy
|
|
new x509.KeyUsagesExtension(
|
|
// eslint-disable-next-line no-bitwise
|
|
x509.KeyUsageFlags[CertKeyUsage.DIGITAL_SIGNATURE] | x509.KeyUsageFlags[CertKeyUsage.KEY_ENCIPHERMENT],
|
|
true
|
|
),
|
|
new x509.ExtendedKeyUsageExtension([x509.ExtendedKeyUsage[CertExtendedKeyUsage.SERVER_AUTH]], true)
|
|
];
|
|
|
|
const altNamesArray: {
|
|
type: "email" | "dns" | "ip";
|
|
value: string;
|
|
}[] = altNames
|
|
.split(",")
|
|
.map((name) => name.trim())
|
|
.map((altName) => {
|
|
if (isFQDN(altName, { allow_wildcard: true })) {
|
|
return {
|
|
type: "dns",
|
|
value: altName
|
|
};
|
|
}
|
|
|
|
if (isValidIp(altName)) {
|
|
return {
|
|
type: "ip",
|
|
value: altName
|
|
};
|
|
}
|
|
|
|
throw new Error(`Invalid altName: ${altName}`);
|
|
});
|
|
|
|
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
|
|
extensions.push(altNamesExtension);
|
|
|
|
const caAlg = keyAlgorithmToAlgCfg(kmipOrgConfig.caKeyAlgorithm as CertKeyAlgorithm);
|
|
|
|
const decryptedCaCertChain = decryptor({
|
|
cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaChain
|
|
}).toString("utf-8");
|
|
|
|
const caSkObj = crypto.nativeCrypto.createPrivateKey({
|
|
key: decryptor({ cipherTextBlob: kmipOrgConfig.encryptedServerIntermediateCaPrivateKey }),
|
|
format: "der",
|
|
type: "pkcs8"
|
|
});
|
|
|
|
const caPrivateKey = await crypto.nativeCrypto.subtle.importKey(
|
|
"pkcs8",
|
|
caSkObj.export({ format: "der", type: "pkcs8" }),
|
|
caAlg,
|
|
true,
|
|
["sign"]
|
|
);
|
|
|
|
const serialNumber = createSerialNumber();
|
|
const leafCert = await x509.X509CertificateGenerator.create({
|
|
serialNumber,
|
|
subject: `CN=${commonName}`,
|
|
issuer: caCertObj.subject,
|
|
notBefore: notBeforeDate,
|
|
notAfter: notAfterDate,
|
|
signingKey: caPrivateKey,
|
|
publicKey: leafKeys.publicKey,
|
|
signingAlgorithm: alg,
|
|
extensions
|
|
});
|
|
|
|
const skLeafObj = crypto.nativeCrypto.KeyObject.from(leafKeys.privateKey);
|
|
const certificateChain = `${caCertObj.toString("pem")}\n${decryptedCaCertChain}`.trim();
|
|
|
|
await kmipOrgServerCertificateDAL.create({
|
|
orgId,
|
|
keyAlgorithm,
|
|
issuedAt: notBeforeDate,
|
|
expiration: notAfterDate,
|
|
serialNumber,
|
|
commonName,
|
|
altNames,
|
|
encryptedCertificate: encryptor({ plainText: Buffer.from(new Uint8Array(leafCert.rawData)) }).cipherTextBlob,
|
|
encryptedChain: encryptor({ plainText: Buffer.from(certificateChain) }).cipherTextBlob
|
|
});
|
|
|
|
return {
|
|
serialNumber,
|
|
privateKey: skLeafObj.export({ format: "pem", type: "pkcs8" }) as string,
|
|
certificate: leafCert.toString("pem"),
|
|
certificateChain
|
|
};
|
|
};
|
|
|
|
const registerServer = async ({
|
|
actorOrgId,
|
|
actor,
|
|
actorId,
|
|
actorAuthMethod,
|
|
ttl,
|
|
commonName,
|
|
keyAlgorithm,
|
|
hostnamesOrIps
|
|
}: TRegisterServerDTO) => {
|
|
const { permission } = await permissionService.getOrgPermission({
|
|
scope: OrganizationActionScope.Any,
|
|
actor,
|
|
actorId,
|
|
orgId: actorOrgId,
|
|
actorAuthMethod,
|
|
actorOrgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionKmipActions.Proxy, OrgPermissionSubjects.Kmip);
|
|
|
|
const kmipConfig = await kmipOrgConfigDAL.findOne({
|
|
orgId: actorOrgId
|
|
});
|
|
|
|
if (!kmipConfig) {
|
|
throw new BadRequestError({
|
|
message: "KMIP has not been configured for the organization"
|
|
});
|
|
}
|
|
|
|
const plan = await licenseService.getPlan(actorOrgId);
|
|
if (!plan.kmip)
|
|
throw new BadRequestError({
|
|
message: "Failed to register KMIP server. Upgrade your plan to enterprise."
|
|
});
|
|
|
|
const { privateKey, certificate, certificateChain, serialNumber } = await generateOrgKmipServerCertificate({
|
|
orgId: actorOrgId,
|
|
commonName: commonName ?? "kmip-server",
|
|
altNames: hostnamesOrIps,
|
|
keyAlgorithm: keyAlgorithm ?? (kmipConfig.caKeyAlgorithm as CertKeyAlgorithm),
|
|
ttl
|
|
});
|
|
|
|
const { clientCertificateChain } = await getOrgKmip({
|
|
actor,
|
|
actorAuthMethod,
|
|
actorId,
|
|
actorOrgId
|
|
});
|
|
|
|
return {
|
|
serverCertificateSerialNumber: serialNumber,
|
|
clientCertificateChain,
|
|
privateKey,
|
|
certificate,
|
|
certificateChain
|
|
};
|
|
};
|
|
|
|
return {
|
|
createKmipClient,
|
|
updateKmipClient,
|
|
deleteKmipClient,
|
|
getKmipClient,
|
|
listKmipClientsByProjectId,
|
|
createKmipClientCertificate,
|
|
setupOrgKmip,
|
|
generateOrgKmipServerCertificate,
|
|
getOrgKmip,
|
|
getServerCertificateBySerialNumber,
|
|
registerServer
|
|
};
|
|
};
|