misc: initial instance KMIP PKI setup

This commit is contained in:
Sheen Capadngan
2025-02-06 02:57:40 +08:00
parent 8b315c946c
commit 9f9ded5102
20 changed files with 873 additions and 8 deletions
+16
View File
@@ -146,6 +146,12 @@ import {
TKmipClients,
TKmipClientsInsert,
TKmipClientsUpdate,
TKmipInstanceConfigs,
TKmipInstanceConfigsInsert,
TKmipInstanceConfigsUpdate,
TKmipInstanceServerCertificates,
TKmipInstanceServerCertificatesInsert,
TKmipInstanceServerCertificatesUpdate,
TKmsKeys,
TKmsKeysInsert,
TKmsKeysUpdate,
@@ -906,5 +912,15 @@ declare module "knex/types/tables" {
>;
[TableName.SecretSync]: KnexOriginal.CompositeTableType<TSecretSyncs, TSecretSyncsInsert, TSecretSyncsUpdate>;
[TableName.KmipClient]: KnexOriginal.CompositeTableType<TKmipClients, TKmipClientsInsert, TKmipClientsUpdate>;
[TableName.KmipInstanceConfig]: KnexOriginal.CompositeTableType<
TKmipInstanceConfigs,
TKmipInstanceConfigsInsert,
TKmipInstanceConfigsUpdate
>;
[TableName.KmipInstanceServerCertificates]: KnexOriginal.CompositeTableType<
TKmipInstanceServerCertificates,
TKmipInstanceServerCertificatesInsert,
TKmipInstanceServerCertificatesUpdate
>;
}
}
@@ -1,6 +1,7 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> {
const hasKmipClientTable = await knex.schema.hasTable(TableName.KmipClient);
@@ -14,6 +15,55 @@ export async function up(knex: Knex): Promise<void> {
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
});
}
const hasKmipInstanceConfigTable = await knex.schema.hasTable(TableName.KmipInstanceConfig);
if (!hasKmipInstanceConfigTable) {
await knex.schema.createTable(TableName.KmipInstanceConfig, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("caKeyAlgorithm").notNullable();
t.datetime("rootCaIssuedAt").notNullable();
t.datetime("rootCaExpiration").notNullable();
t.string("rootCaSerialNumber").notNullable();
t.binary("encryptedRootCaCertificate").notNullable();
t.binary("encryptedRootCaPrivateKey").notNullable();
t.datetime("serverIntermediateCaIssuedAt").notNullable();
t.datetime("serverIntermediateCaExpiration").notNullable();
t.string("serverIntermediateCaSerialNumber");
t.binary("encryptedServerIntermediateCaCertificate").notNullable();
t.binary("encryptedServerIntermediateCaChain").notNullable();
t.binary("encryptedServerIntermediateCaPrivateKey").notNullable();
t.datetime("clientIntermediateCaIssuedAt").notNullable();
t.datetime("clientIntermediateCaExpiration").notNullable();
t.string("clientIntermediateCaSerialNumber").notNullable();
t.binary("encryptedClientIntermediateCaCertificate").notNullable();
t.binary("encryptedClientIntermediateCaChain").notNullable();
t.binary("encryptedClientIntermediateCaPrivateKey").notNullable();
t.timestamps(true, true, true);
});
await createOnUpdateTrigger(knex, TableName.KmipInstanceConfig);
}
const hasKmipInstanceServerCertTable = await knex.schema.hasTable(TableName.KmipInstanceServerCertificates);
if (!hasKmipInstanceServerCertTable) {
await knex.schema.createTable(TableName.KmipInstanceServerCertificates, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.string("commonName").notNullable();
t.string("altNames").notNullable();
t.string("serialNumber").notNullable();
t.string("keyAlgorithm").notNullable();
t.datetime("issuedAt").notNullable();
t.datetime("expiration").notNullable();
t.binary("encryptedCertificate").notNullable();
t.binary("encryptedChain").notNullable();
t.binary("encryptedPrivateKey").notNullable();
});
}
}
export async function down(knex: Knex): Promise<void> {
@@ -21,4 +71,15 @@ export async function down(knex: Knex): Promise<void> {
if (hasKmipClientTable) {
await knex.schema.dropTable(TableName.KmipClient);
}
const hasKmipInstanceConfigTable = await knex.schema.hasTable(TableName.KmipInstanceConfig);
if (hasKmipInstanceConfigTable) {
await knex.schema.dropTable(TableName.KmipInstanceConfig);
await dropOnUpdateTrigger(knex, TableName.KmipInstanceConfig);
}
const hasKmipInstanceServerCertTable = await knex.schema.hasTable(TableName.KmipInstanceServerCertificates);
if (hasKmipInstanceServerCertTable) {
await knex.schema.dropTable(TableName.KmipInstanceServerCertificates);
}
}
+2
View File
@@ -46,6 +46,8 @@ export * from "./integration-auths";
export * from "./integrations";
export * from "./internal-kms";
export * from "./kmip-clients";
export * from "./kmip-instance-configs";
export * from "./kmip-instance-server-certificates";
export * from "./kms-key-versions";
export * from "./kms-keys";
export * from "./kms-root-config";
@@ -0,0 +1,38 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const KmipInstanceConfigsSchema = z.object({
id: z.string().uuid(),
caKeyAlgorithm: z.string(),
rootCaIssuedAt: z.date(),
rootCaExpiration: z.date(),
rootCaSerialNumber: z.string(),
encryptedRootCaCertificate: zodBuffer,
encryptedRootCaPrivateKey: zodBuffer,
serverIntermediateCaIssuedAt: z.date(),
serverIntermediateCaExpiration: z.date(),
serverIntermediateCaSerialNumber: z.string().nullable().optional(),
encryptedServerIntermediateCaCertificate: zodBuffer,
encryptedServerIntermediateCaChain: zodBuffer,
encryptedServerIntermediateCaPrivateKey: zodBuffer,
clientIntermediateCaIssuedAt: z.date(),
clientIntermediateCaExpiration: z.date(),
clientIntermediateCaSerialNumber: z.string(),
encryptedClientIntermediateCaCertificate: zodBuffer,
encryptedClientIntermediateCaChain: zodBuffer,
encryptedClientIntermediateCaPrivateKey: zodBuffer,
createdAt: z.date(),
updatedAt: z.date()
});
export type TKmipInstanceConfigs = z.infer<typeof KmipInstanceConfigsSchema>;
export type TKmipInstanceConfigsInsert = Omit<z.input<typeof KmipInstanceConfigsSchema>, TImmutableDBKeys>;
export type TKmipInstanceConfigsUpdate = Partial<Omit<z.input<typeof KmipInstanceConfigsSchema>, TImmutableDBKeys>>;
@@ -0,0 +1,32 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const KmipInstanceServerCertificatesSchema = z.object({
id: z.string().uuid(),
commonName: z.string(),
altNames: z.string(),
serialNumber: z.string(),
keyAlgorithm: z.string(),
issuedAt: z.date(),
expiration: z.date(),
encryptedCertificate: zodBuffer,
encryptedChain: zodBuffer,
encryptedPrivateKey: zodBuffer
});
export type TKmipInstanceServerCertificates = z.infer<typeof KmipInstanceServerCertificatesSchema>;
export type TKmipInstanceServerCertificatesInsert = Omit<
z.input<typeof KmipInstanceServerCertificatesSchema>,
TImmutableDBKeys
>;
export type TKmipInstanceServerCertificatesUpdate = Partial<
Omit<z.input<typeof KmipInstanceServerCertificatesSchema>, TImmutableDBKeys>
>;
+3 -1
View File
@@ -133,7 +133,9 @@ export enum TableName {
ProjectSlackConfigs = "project_slack_configs",
AppConnection = "app_connections",
SecretSync = "secret_syncs",
KmipClient = "kmip_clients"
KmipClient = "kmip_clients",
KmipInstanceConfig = "kmip_instance_configs",
KmipInstanceServerCertificates = "kmip_instance_server_certificates"
}
export type TImmutableDBKeys = "id" | "createdAt" | "updatedAt";
@@ -0,0 +1,13 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TKmipInstanceConfigDALFactory = ReturnType<typeof kmipInstanceConfigDALFactory>;
export const kmipInstanceConfigDALFactory = (db: TDbClient) => {
const kmipInstanceConfigOrm = ormify(db, TableName.KmipInstanceConfig);
return {
...kmipInstanceConfigOrm
};
};
@@ -0,0 +1,13 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TKmipInstanceServerCertificateDALFactory = ReturnType<typeof kmipInstanceServerCertificateDALFactory>;
export const kmipInstanceServerCertificateDALFactory = (db: TDbClient) => {
const kmipInstanceServerCertificateOrm = ormify(db, TableName.KmipInstanceServerCertificates);
return {
...kmipInstanceServerCertificateOrm
};
};
+7 -1
View File
@@ -36,6 +36,8 @@ import { identityProjectAdditionalPrivilegeDALFactory } from "@app/ee/services/i
import { identityProjectAdditionalPrivilegeServiceFactory } from "@app/ee/services/identity-project-additional-privilege/identity-project-additional-privilege-service";
import { identityProjectAdditionalPrivilegeV2ServiceFactory } from "@app/ee/services/identity-project-additional-privilege-v2/identity-project-additional-privilege-v2-service";
import { kmipClientDALFactory } from "@app/ee/services/kmip/kmip-client-dal";
import { kmipInstanceConfigDALFactory } from "@app/ee/services/kmip/kmip-instance-config-dal";
import { kmipInstanceServerCertificateDALFactory } from "@app/ee/services/kmip/kmip-instance-server-certificate-dal";
import { kmipServiceFactory } from "@app/ee/services/kmip/kmip-service";
import { ldapConfigDALFactory } from "@app/ee/services/ldap-config/ldap-config-dal";
import { ldapConfigServiceFactory } from "@app/ee/services/ldap-config/ldap-config-service";
@@ -383,6 +385,8 @@ export const registerRoutes = async (
const projectTemplateDAL = projectTemplateDALFactory(db);
const resourceMetadataDAL = resourceMetadataDALFactory(db);
const kmipClientDAL = kmipClientDALFactory(db);
const kmipInstanceConfigDAL = kmipInstanceConfigDALFactory(db);
const kmipInstanceServerCertificateDAL = kmipInstanceServerCertificateDALFactory(db);
const permissionService = permissionServiceFactory({
permissionDAL,
@@ -623,7 +627,9 @@ export const registerRoutes = async (
orgService,
keyStore,
licenseService,
kmsService
kmsService,
kmipInstanceConfigDAL,
kmipInstanceServerCertificateDAL
});
const orgAdminService = orgAdminServiceFactory({
@@ -1,3 +1,4 @@
import ms from "ms";
import { z } from "zod";
import { OrganizationsSchema, SuperAdminSchema, UsersSchema } from "@app/db/schemas";
@@ -7,6 +8,8 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
import { verifySuperAdmin } from "@app/server/plugins/auth/superAdmin";
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
import { AuthMode } from "@app/services/auth/auth-type";
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
import { validateAltNamesField } from "@app/services/certificate-authority/certificate-authority-validators";
import { RootKeyEncryptionStrategy } from "@app/services/kms/kms-types";
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { LoginMethod } from "@app/services/super-admin/super-admin-types";
@@ -316,4 +319,88 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
};
}
});
server.route({
method: "POST",
url: "/kmip",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
caKeyAlgorithm: z.nativeEnum(CertKeyAlgorithm)
}),
response: {
200: z.object({
serverCertificateChain: z.string()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
return server.services.superAdmin.setupInstanceKmip({
...req.body
});
}
});
server.route({
method: "GET",
url: "/kmip",
config: {
rateLimit: readLimit
},
schema: {
response: {
200: z.object({
serverCertificateChain: z.string()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async () => {
return server.services.superAdmin.getInstanceKmip();
}
});
server.route({
method: "POST",
url: "/kmip/server-certificates",
config: {
rateLimit: writeLimit
},
schema: {
body: z.object({
commonName: z.string().trim().min(1),
altNames: validateAltNamesField,
keyAlgorithm: z.nativeEnum(CertKeyAlgorithm),
ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number")
}),
response: {
200: z.object({
certificateChain: z.string(),
certificate: z.string(),
privateKey: z.string()
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
return server.services.superAdmin.generateInstanceKmipServerCertificate({
...req.body
});
}
});
};
@@ -1,6 +1,12 @@
import * as x509 from "@peculiar/x509";
import bcrypt from "bcrypt";
import crypto, { KeyObject } from "crypto";
import ms from "ms";
import z from "zod";
import { TSuperAdmin, TSuperAdminUpdate } from "@app/db/schemas";
import { TKmipInstanceConfigDALFactory } from "@app/ee/services/kmip/kmip-instance-config-dal";
import { TKmipInstanceServerCertificateDALFactory } from "@app/ee/services/kmip/kmip-instance-server-certificate-dal";
import { TLicenseServiceFactory } from "@app/ee/services/license/license-service";
import { TKeyStoreFactory } from "@app/keystore/keystore";
import { getConfig } from "@app/lib/config/env";
@@ -10,6 +16,9 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
import { TAuthLoginFactory } from "../auth/auth-login-service";
import { AuthMethod } from "../auth/auth-type";
import { CertExtendedKeyUsage, CertKeyAlgorithm, CertKeyUsage } from "../certificate/certificate-types";
import { createSerialNumber, keyAlgorithmToAlgCfg } from "../certificate-authority/certificate-authority-fns";
import { hostnameRegex } from "../certificate-authority/certificate-authority-validators";
import { KMS_ROOT_CONFIG_UUID } from "../kms/kms-fns";
import { TKmsRootConfigDALFactory } from "../kms/kms-root-config-dal";
import { TKmsServiceFactory } from "../kms/kms-service";
@@ -19,7 +28,13 @@ import { TUserDALFactory } from "../user/user-dal";
import { TUserAliasDALFactory } from "../user-alias/user-alias-dal";
import { UserAliasType } from "../user-alias/user-alias-types";
import { TSuperAdminDALFactory } from "./super-admin-dal";
import { LoginMethod, TAdminGetUsersDTO, TAdminSignUpDTO } from "./super-admin-types";
import {
LoginMethod,
TAdminGetUsersDTO,
TAdminSignUpDTO,
TGenerateInstanceKmipServerCertificateDTO,
TSetupInstanceKmipDTO
} from "./super-admin-types";
type TSuperAdminServiceFactoryDep = {
serverCfgDAL: TSuperAdminDALFactory;
@@ -31,6 +46,8 @@ type TSuperAdminServiceFactoryDep = {
orgService: Pick<TOrgServiceFactory, "createOrganization">;
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
licenseService: Pick<TLicenseServiceFactory, "onPremFeatures">;
kmipInstanceConfigDAL: TKmipInstanceConfigDALFactory;
kmipInstanceServerCertificateDAL: TKmipInstanceServerCertificateDALFactory;
};
export type TSuperAdminServiceFactory = ReturnType<typeof superAdminServiceFactory>;
@@ -57,7 +74,9 @@ export const superAdminServiceFactory = ({
keyStore,
kmsRootConfigDAL,
kmsService,
licenseService
licenseService,
kmipInstanceConfigDAL,
kmipInstanceServerCertificateDAL
}: TSuperAdminServiceFactoryDep) => {
const initServerCfg = async () => {
// TODO(akhilmhdh): bad pattern time less change this later to me itself
@@ -369,6 +388,302 @@ export const superAdminServiceFactory = ({
await kmsService.updateEncryptionStrategy(strategy);
};
const setupInstanceKmip = async ({ caKeyAlgorithm }: TSetupInstanceKmipDTO) => {
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
if (kmipInstanceConfig) {
throw new BadRequestError({
message: "KMIP has already been configured for the instance"
});
}
const alg = keyAlgorithmToAlgCfg(caKeyAlgorithm);
// generate root CA
const rootCaSerialNumber = createSerialNumber();
const rootCaKeys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
const rootCaSkObj = 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",
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.subtle.generateKey(alg, true, ["sign", "verify"]);
const serverIntermediateCaSkObj = KeyObject.from(serverIntermediateCaKeys.privateKey);
const serverIntermediateCaCert = await x509.X509CertificateGenerator.create({
serialNumber: serverIntermediateCaSerialNumber,
subject: "CN=KMIP Server Intermediate CA",
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.subtle.generateKey(alg, true, ["sign", "verify"]);
const clientIntermediateCaSkObj = KeyObject.from(clientIntermediateCaKeys.privateKey);
const clientIntermediateCaCert = await x509.X509CertificateGenerator.create({
serialNumber: clientIntermediateCaSerialNumber,
subject: "CN=KMIP Client Intermediate CA",
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 encryptWithRoot = kmsService.encryptWithRootKey();
await kmipInstanceConfigDAL.create({
// @ts-expect-error id is kept as fixed for idempotence and to avoid race condition
id: ADMIN_CONFIG_DB_UUID,
caKeyAlgorithm,
rootCaIssuedAt,
rootCaExpiration,
rootCaSerialNumber,
encryptedRootCaCertificate: encryptWithRoot(Buffer.from(rootCaCert.rawData)),
encryptedRootCaPrivateKey: encryptWithRoot(
rootCaSkObj.export({
type: "pkcs8",
format: "der"
})
),
serverIntermediateCaIssuedAt,
serverIntermediateCaExpiration,
serverIntermediateCaSerialNumber,
encryptedServerIntermediateCaCertificate: encryptWithRoot(
Buffer.from(new Uint8Array(serverIntermediateCaCert.rawData))
),
encryptedServerIntermediateCaChain: encryptWithRoot(Buffer.from(rootCaCert.toString("pem"))),
encryptedServerIntermediateCaPrivateKey: encryptWithRoot(
serverIntermediateCaSkObj.export({
type: "pkcs8",
format: "der"
})
),
clientIntermediateCaIssuedAt,
clientIntermediateCaExpiration,
clientIntermediateCaSerialNumber,
encryptedClientIntermediateCaCertificate: encryptWithRoot(
Buffer.from(new Uint8Array(clientIntermediateCaCert.rawData))
),
encryptedClientIntermediateCaChain: encryptWithRoot(Buffer.from(rootCaCert.toString("pem"))),
encryptedClientIntermediateCaPrivateKey: encryptWithRoot(
clientIntermediateCaSkObj.export({
type: "pkcs8",
format: "der"
})
)
});
return {
// the order of the cert is intentional - for client chains, ordering should be from intermediate to root
serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim()
};
};
const getInstanceKmip = async () => {
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
if (!kmipInstanceConfig) {
throw new BadRequestError({
message: "KMIP has not been configured for the instance"
});
}
const decryptWithRoot = kmsService.decryptWithRootKey();
const rootCaCert = new x509.X509Certificate(decryptWithRoot(kmipInstanceConfig.encryptedRootCaCertificate));
const serverIntermediateCaCert = new x509.X509Certificate(
decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaCertificate)
);
return {
// the order of the cert is intentional - for client chains, ordering should be from intermediate to root
serverCertificateChain: `${serverIntermediateCaCert.toString("pem")}\n${rootCaCert.toString("pem")}`.trim()
};
};
const generateInstanceKmipServerCertificate = async ({
ttl,
commonName,
altNames,
keyAlgorithm
}: TGenerateInstanceKmipServerCertificateDTO) => {
const kmipInstanceConfig = await kmipInstanceConfigDAL.findById(ADMIN_CONFIG_DB_UUID);
if (!kmipInstanceConfig) {
throw new BadRequestError({
message: "KMIP has not been configured for the instance"
});
}
const decryptWithRoot = kmsService.decryptWithRootKey();
const caCertObj = new x509.X509Certificate(
decryptWithRoot(kmipInstanceConfig.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.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";
value: string;
}[] = altNames
.split(",")
.map((name) => name.trim())
.map((altName) => {
// check if the altName is a valid email
if (z.string().email().safeParse(altName).success) {
return {
type: "email",
value: altName
};
}
// check if the altName is a valid hostname
if (hostnameRegex.test(altName)) {
return {
type: "dns",
value: altName
};
}
// If altName is neither a valid email nor a valid hostname, throw an error or handle it accordingly
throw new Error(`Invalid altName: ${altName}`);
});
const altNamesExtension = new x509.SubjectAlternativeNameExtension(altNamesArray, false);
extensions.push(altNamesExtension);
const caAlg = keyAlgorithmToAlgCfg(kmipInstanceConfig.caKeyAlgorithm as CertKeyAlgorithm);
const decryptedCaCertChain = decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaChain).toString(
"utf-8"
);
const caSkObj = crypto.createPrivateKey({
key: decryptWithRoot(kmipInstanceConfig.encryptedServerIntermediateCaPrivateKey),
format: "der",
type: "pkcs8"
});
const caPrivateKey = await crypto.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 encryptWithRoot = kmsService.encryptWithRootKey();
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const serverCert = await kmipInstanceServerCertificateDAL.create({
keyAlgorithm,
issuedAt: notBeforeDate,
expiration: notAfterDate,
serialNumber,
commonName,
altNames,
encryptedCertificate: encryptWithRoot(Buffer.from(new Uint8Array(leafCert.rawData))),
encryptedPrivateKey: encryptWithRoot(skLeafObj.export({ format: "der", type: "pkcs8" })),
encryptedChain: encryptWithRoot(Buffer.from(`${decryptedCaCertChain}\n${caCertObj.toString("pem")}`.trim()))
});
return serverCert;
};
return {
initServerCfg,
updateServerCfg,
@@ -377,6 +692,9 @@ export const superAdminServiceFactory = ({
deleteUser,
getAdminSlackConfig,
updateRootEncryptionStrategy,
getConfiguredEncryptionStrategies
getConfiguredEncryptionStrategies,
setupInstanceKmip,
getInstanceKmip,
generateInstanceKmipServerCertificate
};
};
@@ -1,3 +1,5 @@
import { CertKeyAlgorithm } from "../certificate/certificate-types";
export type TAdminSignUpDTO = {
email: string;
password: string;
@@ -31,3 +33,14 @@ export enum LoginMethod {
LDAP = "ldap",
OIDC = "oidc"
}
export type TSetupInstanceKmipDTO = {
caKeyAlgorithm: CertKeyAlgorithm;
};
export type TGenerateInstanceKmipServerCertificateDTO = {
commonName: string;
altNames: string;
keyAlgorithm: CertKeyAlgorithm;
ttl: string;
};