PKI revamp - backend initial changes

This commit is contained in:
Carlos Monastyrski
2025-10-09 00:29:55 -03:00
parent c1833b5cbd
commit c4dcc3dd83
47 changed files with 9624 additions and 26 deletions
@@ -0,0 +1,372 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-call */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { ForbiddenError } from "@casl/ability";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { CertExtendedKeyUsage, CertKeyUsage } from "@app/services/certificate/certificate-types";
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
import { ActorType, AuthMethod } from "../auth/auth-type";
import { certificateV3ServiceFactory, TCertificateV3ServiceFactory } from "./certificate-v3-service";
describe("CertificateV3Service", () => {
let service: TCertificateV3ServiceFactory;
const mockCertificateDAL = {
findOne: vi.fn(),
updateById: vi.fn()
} as any;
const mockCertificateAuthorityDAL = {
findByIdWithAssociatedCa: vi.fn()
} as any;
const mockCertificateProfileDAL = {
findByIdWithConfigs: vi.fn()
} as any;
const mockCertificateTemplateV2Service = {
validateCertificateRequest: vi.fn(),
getTemplateV2ById: vi.fn()
} as any;
const mockInternalCaService = {
signCertFromCa: vi.fn(),
issueCertFromCa: vi.fn()
} as any;
const mockPermissionService = {
getProjectPermission: vi.fn().mockResolvedValue({
permission: {
throwUnlessCan: vi.fn()
}
})
} as any;
const mockActor = {
actor: ActorType.USER,
actorId: "user-123",
actorAuthMethod: AuthMethod.EMAIL as any,
actorOrgId: "org-123"
};
beforeEach(() => {
vi.spyOn(ForbiddenError, "from").mockReturnValue({
throwUnlessCan: vi.fn()
} as any);
service = certificateV3ServiceFactory({
certificateDAL: mockCertificateDAL,
certificateAuthorityDAL: mockCertificateAuthorityDAL,
certificateProfileDAL: mockCertificateProfileDAL,
certificateTemplateV2Service: mockCertificateTemplateV2Service,
internalCaService: mockInternalCaService,
permissionService: mockPermissionService
});
});
afterEach(() => {
vi.clearAllMocks();
});
describe("issueCertificateFromProfile", () => {
const mockCertificateRequest = {
commonName: "test.example.com",
organization: "Test Org",
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE],
extendedKeyUsages: [CertExtendedKeyUsage.SERVER_AUTH],
validity: { ttl: "30d" },
signatureAlgorithm: "RSA-SHA256",
keyAlgorithm: "RSA_2048"
};
it("should issue certificate successfully for API enrollment profile", async () => {
const profileId = "profile-123";
const mockProfile = {
id: profileId,
projectId: "project-123",
enrollmentType: EnrollmentType.API,
caId: "ca-123",
certificateTemplateId: "template-123"
};
const mockCA = {
id: "ca-123",
externalCa: null
};
const mockTemplate = {
id: "template-123",
signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" },
keyAlgorithm: { defaultKeyType: "RSA_2048" },
attributes: []
};
const mockCertificateResult = {
certificate: Buffer.from("cert"),
certificateChain: Buffer.from("chain"),
privateKey: Buffer.from("key"),
serialNumber: "123456"
};
const mockCertRecord = {
id: "cert-123",
serialNumber: "123456"
};
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
isValid: true,
errors: [],
warnings: []
});
mockCertificateAuthorityDAL.findByIdWithAssociatedCa.mockResolvedValue(mockCA);
mockCertificateTemplateV2Service.getTemplateV2ById.mockResolvedValue(mockTemplate);
mockInternalCaService.issueCertFromCa.mockResolvedValue(mockCertificateResult);
mockCertificateDAL.findOne.mockResolvedValue(mockCertRecord);
mockCertificateDAL.updateById.mockResolvedValue({});
const result = await service.issueCertificateFromProfile({
profileId,
certificateRequest: mockCertificateRequest,
...mockActor
});
expect(result).toHaveProperty("certificate");
expect(result).toHaveProperty("privateKey");
expect(result).toHaveProperty("serialNumber", "123456");
expect(result).toHaveProperty("certificateId", "cert-123");
});
it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => {
const profileId = "profile-123";
const mockProfile = {
id: profileId,
projectId: "project-123",
enrollmentType: EnrollmentType.EST, // Wrong enrollment type
caId: "ca-123",
certificateTemplateId: "template-123"
};
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
await expect(
service.issueCertificateFromProfile({
profileId,
certificateRequest: mockCertificateRequest,
...mockActor
})
).rejects.toThrow(ForbiddenRequestError);
await expect(
service.issueCertificateFromProfile({
profileId,
certificateRequest: mockCertificateRequest,
...mockActor
})
).rejects.toThrow("Profile is not configured for api enrollment");
});
it("should throw NotFoundError when profile doesn't exist", async () => {
const profileId = "non-existent-profile";
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(null);
await expect(
service.issueCertificateFromProfile({
profileId,
certificateRequest: mockCertificateRequest,
...mockActor
})
).rejects.toThrow(NotFoundError);
});
});
describe("signCertificateFromProfile", () => {
const mockCSR = "-----BEGIN CERTIFICATE REQUEST-----\nMIIC...";
const mockValidity = { ttl: "30d" };
it("should sign certificate successfully for API enrollment profile", async () => {
const profileId = "profile-123";
const mockProfile = {
id: profileId,
projectId: "project-123",
enrollmentType: EnrollmentType.API,
caId: "ca-123",
certificateTemplateId: "template-123"
};
const mockCA = {
id: "ca-123",
externalCa: null
};
const mockSignResult = {
certificate: Buffer.from("signed-cert"),
certificateChain: Buffer.from("chain"),
serialNumber: "789012"
};
const mockCertRecord = {
id: "cert-456",
serialNumber: "789012"
};
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
mockCertificateAuthorityDAL.findByIdWithAssociatedCa.mockResolvedValue(mockCA);
mockInternalCaService.signCertFromCa.mockResolvedValue(mockSignResult);
mockCertificateDAL.findOne.mockResolvedValue(mockCertRecord);
mockCertificateDAL.updateById.mockResolvedValue({});
const result = await service.signCertificateFromProfile({
profileId,
csr: mockCSR,
validity: mockValidity,
...mockActor
});
expect(result).toHaveProperty("certificate");
expect(result).toHaveProperty("serialNumber", "789012");
expect(result).toHaveProperty("certificateId", "cert-456");
expect(result).not.toHaveProperty("privateKey");
});
it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => {
const profileId = "profile-123";
const mockProfile = {
id: profileId,
projectId: "project-123",
enrollmentType: EnrollmentType.EST, // Wrong enrollment type
caId: "ca-123",
certificateTemplateId: "template-123"
};
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
await expect(
service.signCertificateFromProfile({
profileId,
csr: mockCSR,
validity: mockValidity,
...mockActor
})
).rejects.toThrow(ForbiddenRequestError);
await expect(
service.signCertificateFromProfile({
profileId,
csr: mockCSR,
validity: mockValidity,
...mockActor
})
).rejects.toThrow("Profile is not configured for api enrollment");
});
});
describe("orderCertificateFromProfile", () => {
const mockCertificateOrder = {
identifiers: [{ type: "dns" as const, value: "example.com" }],
validity: { ttl: "30d" },
commonName: "example.com",
keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE],
extendedKeyUsages: [CertExtendedKeyUsage.SERVER_AUTH],
signatureAlgorithm: "RSA-SHA256",
keyAlgorithm: "RSA_2048"
};
it("should create order successfully for API enrollment profile", async () => {
const profileId = "profile-123";
const mockProfile = {
id: profileId,
projectId: "project-123",
enrollmentType: EnrollmentType.API,
caId: "ca-123",
certificateTemplateId: "template-123"
};
const mockCA = {
id: "ca-123",
externalCa: null
};
const mockTemplate = {
id: "template-123",
signatureAlgorithm: { defaultAlgorithm: "RSA-SHA256" },
keyAlgorithm: { defaultKeyType: "RSA_2048" },
attributes: []
};
const mockCertificateResult = {
certificate: Buffer.from("cert"),
certificateChain: Buffer.from("chain"),
privateKey: Buffer.from("key"),
serialNumber: "123456"
};
const mockCertRecord = {
id: "cert-123",
serialNumber: "123456"
};
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
mockCertificateTemplateV2Service.validateCertificateRequest.mockResolvedValue({
isValid: true,
errors: [],
warnings: []
});
mockCertificateAuthorityDAL.findByIdWithAssociatedCa.mockResolvedValue(mockCA);
mockCertificateTemplateV2Service.getTemplateV2ById.mockResolvedValue(mockTemplate);
mockInternalCaService.issueCertFromCa.mockResolvedValue(mockCertificateResult);
mockCertificateDAL.findOne.mockResolvedValue(mockCertRecord);
mockCertificateDAL.updateById.mockResolvedValue({});
const result = await service.orderCertificateFromProfile({
profileId,
certificateOrder: mockCertificateOrder,
...mockActor
});
expect(result).toHaveProperty("orderId");
expect(result).toHaveProperty("status", "valid");
expect(result).toHaveProperty("certificate");
expect(result.identifiers).toHaveLength(1);
expect(result.identifiers[0]).toEqual({
type: "dns",
value: "example.com",
status: "valid"
});
});
it("should throw ForbiddenRequestError when profile is not configured for API enrollment", async () => {
const profileId = "profile-123";
const mockProfile = {
id: profileId,
projectId: "project-123",
enrollmentType: EnrollmentType.EST, // Wrong enrollment type
caId: "ca-123",
certificateTemplateId: "template-123"
};
mockCertificateProfileDAL.findByIdWithConfigs.mockResolvedValue(mockProfile);
await expect(
service.orderCertificateFromProfile({
profileId,
certificateOrder: mockCertificateOrder,
...mockActor
})
).rejects.toThrow(ForbiddenRequestError);
await expect(
service.orderCertificateFromProfile({
profileId,
certificateOrder: mockCertificateOrder,
...mockActor
})
).rejects.toThrow("Profile is not configured for api enrollment");
});
});
});
@@ -0,0 +1,395 @@
import { ForbiddenError } from "@casl/ability";
import { randomUUID } from "crypto";
import { ActionProjectType } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import {
ProjectPermissionCertificateProfileActions,
ProjectPermissionSub
} from "@app/ee/services/permission/project-permission";
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import {
TCertificateAuthorityDALFactory,
TCertificateAuthorityWithAssociatedCa
} from "@app/services/certificate-authority/certificate-authority-dal";
import { CaType } from "@app/services/certificate-authority/certificate-authority-enums";
import { TInternalCertificateAuthorityServiceFactory } from "@app/services/certificate-authority/internal/internal-certificate-authority-service";
import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal";
import { EnrollmentType } from "@app/services/certificate-profile/certificate-profile-types";
import { TCertificateTemplateV2ServiceFactory } from "@app/services/certificate-template-v2/certificate-template-v2-service";
import {
bufferToString,
buildCertificateSubjectFromTemplate,
buildSubjectAlternativeNamesFromTemplate,
mapEnumsForValidation,
normalizeDateForApi
} from "../certificate-common/certificate-utils";
import {
TCertificateFromProfileResponse,
TCertificateOrderResponse,
TIssueCertificateFromProfileDTO,
TOrderCertificateFromProfileDTO,
TSignCertificateFromProfileDTO
} from "./certificate-v3-types";
type TCertificateV3ServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "updateById">;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findByIdWithAssociatedCa">;
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findByIdWithConfigs">;
certificateTemplateV2Service: Pick<
TCertificateTemplateV2ServiceFactory,
"validateCertificateRequest" | "getTemplateV2ById"
>;
internalCaService: Pick<TInternalCertificateAuthorityServiceFactory, "signCertFromCa" | "issueCertFromCa">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
};
export type TCertificateV3ServiceFactory = ReturnType<typeof certificateV3ServiceFactory>;
const validateProfileAndPermissions = async (
profileId: string,
actor: ActorType,
actorId: string,
actorAuthMethod: ActorAuthMethod,
actorOrgId: string,
certificateProfileDAL: Pick<TCertificateProfileDALFactory, "findByIdWithConfigs">,
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">,
requiredEnrollmentType: EnrollmentType
) => {
const profile = await certificateProfileDAL.findByIdWithConfigs(profileId);
if (!profile) {
throw new NotFoundError({ message: "Certificate profile not found" });
}
if (profile.enrollmentType !== requiredEnrollmentType) {
throw new ForbiddenRequestError({
message: `Profile is not configured for ${requiredEnrollmentType} enrollment`
});
}
const { permission } = await permissionService.getProjectPermission({
actor,
actorId,
projectId: profile.projectId,
actorAuthMethod,
actorOrgId,
actionProjectType: ActionProjectType.CertificateManager
});
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionCertificateProfileActions.IssueCert,
ProjectPermissionSub.CertificateProfiles
);
return profile;
};
const validateCaSupport = (ca: TCertificateAuthorityWithAssociatedCa, operation: string) => {
const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL;
if (caType !== CaType.INTERNAL) {
throw new BadRequestError({ message: `Only internal CAs support ${operation}` });
}
return caType;
};
const extractCertificateFromBuffer = (certData: Buffer | { rawData: Buffer } | string): string => {
if (typeof certData === "string") return certData;
if (Buffer.isBuffer(certData)) return bufferToString(certData);
if (certData && typeof certData === "object" && "rawData" in certData && Buffer.isBuffer(certData.rawData)) {
return bufferToString(certData.rawData);
}
return bufferToString(certData as unknown as Buffer);
};
export const certificateV3ServiceFactory = ({
certificateDAL,
certificateAuthorityDAL,
certificateProfileDAL,
certificateTemplateV2Service,
internalCaService,
permissionService
}: TCertificateV3ServiceFactoryDep) => {
const issueCertificateFromProfile = async ({
profileId,
certificateRequest,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TIssueCertificateFromProfileDTO): Promise<TCertificateFromProfileResponse> => {
const profile = await validateProfileAndPermissions(
profileId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
certificateProfileDAL,
permissionService,
EnrollmentType.API
);
const mappedCertificateRequest = mapEnumsForValidation(certificateRequest);
const validationResult = await certificateTemplateV2Service.validateCertificateRequest(
profile.certificateTemplateId,
mappedCertificateRequest
);
if (!validationResult.isValid) {
throw new BadRequestError({
message: `Certificate request validation failed: ${validationResult.errors.join(", ")}`
});
}
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
if (!ca) {
throw new NotFoundError({ message: "Certificate Authority not found" });
}
validateCaSupport(ca, "direct certificate issuance");
if (!actorAuthMethod) {
throw new BadRequestError({ message: "Authentication method is required for certificate issuance" });
}
const template = await certificateTemplateV2Service.getTemplateV2ById({
actor,
actorId,
actorAuthMethod,
actorOrgId,
templateId: profile.certificateTemplateId
});
if (!template) {
throw new NotFoundError({ message: "Certificate template not found for this profile" });
}
const effectiveSignatureAlgorithm =
certificateRequest.signatureAlgorithm || template.signatureAlgorithm?.defaultAlgorithm;
const effectiveKeyAlgorithm = certificateRequest.keyAlgorithm || template.keyAlgorithm?.defaultKeyType;
const certificateSubject = buildCertificateSubjectFromTemplate(certificateRequest, template.attributes);
const subjectAlternativeNames = buildSubjectAlternativeNamesFromTemplate(
certificateRequest,
template.subjectAlternativeNames
);
const { certificate, certificateChain, privateKey, serialNumber } = await internalCaService.issueCertFromCa({
caId: ca.id,
friendlyName: certificateSubject.common_name || "Certificate",
commonName: certificateSubject.common_name || "",
altNames: subjectAlternativeNames,
ttl: certificateRequest.validity.ttl,
keyUsages: certificateRequest.keyUsages,
extendedKeyUsages: certificateRequest.extendedKeyUsages,
notBefore: normalizeDateForApi(certificateRequest.notBefore),
notAfter: normalizeDateForApi(certificateRequest.notAfter),
signatureAlgorithm: effectiveSignatureAlgorithm,
keyAlgorithm: effectiveKeyAlgorithm,
actor,
actorId,
actorAuthMethod,
actorOrgId
});
const cert = await certificateDAL.findOne({ serialNumber, caId: ca.id });
if (!cert) {
throw new NotFoundError({ message: "Certificate was issued but could not be found in database" });
}
await certificateDAL.updateById(cert.id, { profileId });
const certificateChainString = bufferToString(certificateChain);
return {
certificate: bufferToString(certificate),
issuingCaCertificate: certificateChainString.split("\n").pop() || bufferToString(certificate),
certificateChain: certificateChainString,
privateKey: bufferToString(privateKey),
serialNumber,
certificateId: cert.id
};
};
const signCertificateFromProfile = async ({
profileId,
csr,
validity,
notBefore,
notAfter,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TSignCertificateFromProfileDTO): Promise<Omit<TCertificateFromProfileResponse, "privateKey">> => {
const profile = await validateProfileAndPermissions(
profileId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
certificateProfileDAL,
permissionService,
EnrollmentType.API
);
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
if (!ca) {
throw new NotFoundError({ message: "Certificate Authority not found" });
}
validateCaSupport(ca, "CSR signing");
const { certificate, certificateChain, serialNumber } = await internalCaService.signCertFromCa({
isInternal: true,
caId: ca.id,
csr,
ttl: validity.ttl,
altNames: "",
notBefore: normalizeDateForApi(notBefore),
notAfter: normalizeDateForApi(notAfter)
});
const cert = await certificateDAL.findOne({ serialNumber, caId: ca.id });
if (!cert) {
throw new NotFoundError({ message: "Certificate was signed but could not be found in database" });
}
await certificateDAL.updateById(cert.id, { profileId });
const certificateString = extractCertificateFromBuffer(certificate as unknown as Buffer);
const certificateChainString = extractCertificateFromBuffer(certificateChain as unknown as Buffer);
return {
certificate: certificateString,
issuingCaCertificate: certificateChainString.split("\n").pop() || certificateString,
certificateChain: certificateChainString,
serialNumber,
certificateId: cert.id
};
};
const orderCertificateFromProfile = async ({
profileId,
certificateOrder,
actor,
actorId,
actorAuthMethod,
actorOrgId
}: TOrderCertificateFromProfileDTO): Promise<TCertificateOrderResponse> => {
const profile = await validateProfileAndPermissions(
profileId,
actor,
actorId,
actorAuthMethod,
actorOrgId,
certificateProfileDAL,
permissionService,
EnrollmentType.API
);
const certificateRequest = {
commonName: certificateOrder.commonName,
organization: certificateOrder.organization,
organizationUnit: certificateOrder.organizationUnit,
locality: certificateOrder.locality,
state: certificateOrder.state,
country: certificateOrder.country,
email: certificateOrder.email,
streetAddress: certificateOrder.streetAddress,
postalCode: certificateOrder.postalCode,
keyUsages: certificateOrder.keyUsages,
extendedKeyUsages: certificateOrder.extendedKeyUsages,
subjectAlternativeNames: certificateOrder.identifiers.map((id) => ({
type: id.type === "dns" ? ("dns_name" as const) : ("ip_address" as const),
value: id.value
})),
validity: certificateOrder.validity,
notBefore: certificateOrder.notBefore,
notAfter: certificateOrder.notAfter,
signatureAlgorithm: certificateOrder.signatureAlgorithm,
keyAlgorithm: certificateOrder.keyAlgorithm
};
const mappedCertificateRequest = mapEnumsForValidation(certificateRequest);
const validationResult = await certificateTemplateV2Service.validateCertificateRequest(
profile.certificateTemplateId,
mappedCertificateRequest
);
if (!validationResult.isValid) {
throw new BadRequestError({
message: `Certificate order validation failed: ${validationResult.errors.join(", ")}`
});
}
const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId);
if (!ca) {
throw new NotFoundError({ message: "Certificate Authority not found" });
}
const caType = (ca.externalCa?.type as CaType) ?? CaType.INTERNAL;
if (caType === CaType.INTERNAL) {
const certificateResult = await issueCertificateFromProfile({
profileId,
certificateRequest,
actor,
actorId,
actorAuthMethod,
actorOrgId
});
const orderId = randomUUID();
const identifiers = certificateOrder.identifiers.map((id) => ({
type: id.type,
value: id.value,
status: "valid" as const
}));
const authorizations = certificateOrder.identifiers.map((id) => ({
identifier: {
type: id.type,
value: id.value
},
status: "valid" as const,
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
challenges: [
{
type: "internal-validation",
status: "valid" as const,
url: `/api/v3/certificates/orders/${orderId}/internal`,
token: "internal-ca-validation"
}
]
}));
return {
orderId,
status: "valid",
identifiers,
authorizations,
finalize: `/api/v3/certificates/orders/${orderId}/finalize`,
certificate: certificateResult.certificate
};
}
if (caType === CaType.ACME) {
throw new BadRequestError({
message:
"ACME certificate ordering via profiles is not yet implemented. Use direct certificate issuance for ACME CAs."
});
}
throw new BadRequestError({
message: `Certificate ordering is not supported for CA type: ${caType}`
});
};
return {
issueCertificateFromProfile,
signCertificateFromProfile,
orderCertificateFromProfile
};
};
@@ -0,0 +1,104 @@
import { TProjectPermission } from "@app/lib/types";
import { CertExtendedKeyUsage, CertKeyUsage } from "../certificate/certificate-types";
export type TIssueCertificateFromProfileDTO = {
profileId: string;
certificateRequest: {
commonName?: string;
organization?: string;
organizationUnit?: string;
locality?: string;
state?: string;
country?: string;
email?: string;
streetAddress?: string;
postalCode?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
subjectAlternativeNames?: Array<{
type: "dns_name" | "ip_address" | "email" | "uri";
value: string;
}>;
validity: {
ttl: string;
};
notBefore?: Date;
notAfter?: Date;
signatureAlgorithm?: string;
keyAlgorithm?: string;
};
} & Omit<TProjectPermission, "projectId">;
export type TSignCertificateFromProfileDTO = {
profileId: string;
csr: string;
validity: {
ttl: string;
};
notBefore?: Date;
notAfter?: Date;
} & Omit<TProjectPermission, "projectId">;
export type TOrderCertificateFromProfileDTO = {
profileId: string;
certificateOrder: {
identifiers: Array<{
type: "dns" | "ip";
value: string;
}>;
validity: {
ttl: string;
};
commonName?: string;
organization?: string;
organizationUnit?: string;
locality?: string;
state?: string;
country?: string;
email?: string;
streetAddress?: string;
postalCode?: string;
keyUsages?: CertKeyUsage[];
extendedKeyUsages?: CertExtendedKeyUsage[];
notBefore?: Date;
notAfter?: Date;
signatureAlgorithm?: string;
keyAlgorithm?: string;
};
} & Omit<TProjectPermission, "projectId">;
export type TCertificateFromProfileResponse = {
certificate: string;
issuingCaCertificate: string;
certificateChain: string;
privateKey?: string;
serialNumber: string;
certificateId: string;
};
export type TCertificateOrderResponse = {
orderId: string;
status: "pending" | "processing" | "valid" | "invalid";
identifiers: Array<{
type: "dns" | "ip";
value: string;
status: "pending" | "processing" | "valid" | "invalid";
}>;
authorizations: Array<{
identifier: {
type: "dns" | "ip";
value: string;
};
status: "pending" | "processing" | "valid" | "invalid";
expires?: string;
challenges: Array<{
type: string;
status: "pending" | "processing" | "valid" | "invalid";
url: string;
token: string;
}>;
}>;
finalize: string;
certificate?: string;
};