diff --git a/backend/src/services/certificate-request/certificate-request-service.test.ts b/backend/src/services/certificate-request/certificate-request-service.test.ts index 66d5c613b..10c8b73cf 100644 --- a/backend/src/services/certificate-request/certificate-request-service.test.ts +++ b/backend/src/services/certificate-request/certificate-request-service.test.ts @@ -225,6 +225,10 @@ describe("CertificateRequestService", () => { { action: ProjectPermissionCertificateActions.Read, subject: ProjectPermissionSub.Certificates + }, + { + action: ProjectPermissionCertificateActions.ReadPrivateKey, + subject: ProjectPermissionSub.Certificates } ]) }; @@ -319,7 +323,7 @@ describe("CertificateRequestService", () => { }); }); - it("should get certificate from request successfully when private key access is denied", async () => { + it("should get certificate from request successfully when user lacks private key permission", async () => { const mockPermission = { permission: createMongoAbility([ { @@ -349,7 +353,6 @@ describe("CertificateRequestService", () => { (mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission); (mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert); (mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody); - (mockCertificateService.getCertPrivateKey as any).mockRejectedValue(new Error("Private key access denied")); const result = await service.getCertificateFromRequest(mockGetData); @@ -363,8 +366,68 @@ describe("CertificateRequestService", () => { actorAuthMethod: AuthMethod.EMAIL, actorOrgId: "550e8400-e29b-41d4-a716-446655440002" }); + expect(mockCertificateService.getCertPrivateKey).not.toHaveBeenCalled(); + expect(result).toEqual({ + status: CertificateRequestStatus.ISSUED, + certificate: "-----BEGIN CERTIFICATE-----\nMOCK_CERT_PEM\n-----END CERTIFICATE-----", + privateKey: null, + serialNumber: "123456", + errorMessage: null, + createdAt: mockRequestWithCert.createdAt, + updatedAt: mockRequestWithCert.updatedAt + }); + }); + + it("should get certificate from request successfully when user has private key permission but key retrieval fails", async () => { + const mockPermission = { + permission: createMongoAbility([ + { + action: ProjectPermissionCertificateActions.Read, + subject: ProjectPermissionSub.Certificates + }, + { + action: ProjectPermissionCertificateActions.ReadPrivateKey, + subject: ProjectPermissionSub.Certificates + } + ]) + }; + const mockCertificate = { + id: "550e8400-e29b-41d4-a716-446655440009", + serialNumber: "123456", + commonName: "test.example.com" + }; + const mockRequestWithCert = { + id: "550e8400-e29b-41d4-a716-446655440005", + projectId: "550e8400-e29b-41d4-a716-446655440003", + status: CertificateRequestStatus.ISSUED, + certificate: mockCertificate, + errorMessage: null, + createdAt: new Date(), + updatedAt: new Date() + }; + const mockCertBody = { + certificate: "-----BEGIN CERTIFICATE-----\nMOCK_CERT_PEM\n-----END CERTIFICATE-----" + }; + + (mockPermissionService.getProjectPermission as any).mockResolvedValue(mockPermission); + (mockCertificateRequestDAL.findByIdWithCertificate as any).mockResolvedValue(mockRequestWithCert); + (mockCertificateService.getCertBody as any).mockResolvedValue(mockCertBody); + (mockCertificateService.getCertPrivateKey as any).mockRejectedValue(new Error("Private key not found")); + + const result = await service.getCertificateFromRequest(mockGetData); + + expect(mockCertificateRequestDAL.findByIdWithCertificate).toHaveBeenCalledWith( + "550e8400-e29b-41d4-a716-446655440005" + ); + expect(mockCertificateService.getCertBody).toHaveBeenCalledWith({ + id: "550e8400-e29b-41d4-a716-446655440009", + actor: ActorType.USER, + actorId: "550e8400-e29b-41d4-a716-446655440001", + actorAuthMethod: AuthMethod.EMAIL, + actorOrgId: "550e8400-e29b-41d4-a716-446655440002" + }); expect(mockCertificateService.getCertPrivateKey).toHaveBeenCalledWith({ - id: "550e8400-e29b-41d4-a716-446655440008", + id: "550e8400-e29b-41d4-a716-446655440009", actor: ActorType.USER, actorId: "550e8400-e29b-41d4-a716-446655440001", actorAuthMethod: AuthMethod.EMAIL, diff --git a/backend/src/services/certificate-request/certificate-request-service.ts b/backend/src/services/certificate-request/certificate-request-service.ts index 71129aba7..46cd49476 100644 --- a/backend/src/services/certificate-request/certificate-request-service.ts +++ b/backend/src/services/certificate-request/certificate-request-service.ts @@ -218,20 +218,25 @@ export const certificateRequestServiceFactory = ({ actorOrgId }); - // Try to get private key (may fail if user doesn't have permission) + const canReadPrivateKey = permission.can( + ProjectPermissionCertificateActions.ReadPrivateKey, + ProjectPermissionSub.Certificates + ); + let privateKey: string | null = null; - try { - const certPrivateKey = await certificateService.getCertPrivateKey({ - id: certificateRequest.certificate.id, - actor, - actorId, - actorAuthMethod, - actorOrgId - }); - privateKey = certPrivateKey.certPrivateKey; - } catch (error) { - // Private key access denied - continue without it - privateKey = null; + if (canReadPrivateKey) { + try { + const certPrivateKey = await certificateService.getCertPrivateKey({ + id: certificateRequest.certificate.id, + actor, + actorId, + actorAuthMethod, + actorOrgId + }); + privateKey = certPrivateKey.certPrivateKey; + } catch (error) { + privateKey = null; + } } return { diff --git a/backend/src/services/certificate-v3/certificate-v3-service.ts b/backend/src/services/certificate-v3/certificate-v3-service.ts index af6162d70..c20412537 100644 --- a/backend/src/services/certificate-v3/certificate-v3-service.ts +++ b/backend/src/services/certificate-v3/certificate-v3-service.ts @@ -1001,11 +1001,27 @@ export const certificateV3ServiceFactory = ({ }); } + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: profile.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + const canReadPrivateKey = permission.can( + ProjectPermissionCertificateActions.ReadPrivateKey, + ProjectPermissionSub.Certificates + ); + + const privateKeyForResponse = canReadPrivateKey ? selfSignedResult.privateKey.toString("utf8") : undefined; + return { certificate: selfSignedResult.certificate.toString("utf8"), issuingCaCertificate: "", certificateChain: selfSignedResult.certificate.toString("utf8"), - privateKey: selfSignedResult.privateKey.toString("utf8"), + privateKey: privateKeyForResponse, serialNumber: selfSignedResult.serialNumber, certificateId: certificateData.id, certificateRequestId, @@ -1102,11 +1118,28 @@ export const certificateV3ServiceFactory = ({ finalCertificateChain = removeRootCaFromChain(finalCertificateChain); } + // Check if user has permission to read private key + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: profile.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + const canReadPrivateKey = permission.can( + ProjectPermissionCertificateActions.ReadPrivateKey, + ProjectPermissionSub.Certificates + ); + + const privateKeyForResponse = canReadPrivateKey ? bufferToString(privateKey) : undefined; + return { certificate: bufferToString(certificate), issuingCaCertificate: bufferToString(issuingCaCertificate), certificateChain: finalCertificateChain, - privateKey: bufferToString(privateKey), + privateKey: privateKeyForResponse, serialNumber, certificateId: cert.id, certificateRequestId,