change files around and wordings

This commit is contained in:
Anthony Stirling 2025-09-04 23:48:35 +01:00
parent bf03a6011b
commit 019fe714c5
12 changed files with 170 additions and 68 deletions

View File

@ -0,0 +1,66 @@
package stirling.software.common.service;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Date;
public interface ServerCertificateService {
boolean isEnabled();
boolean hasServerCertificate();
void initializeServerCertificate();
KeyStore getServerKeyStore() throws Exception;
String getServerCertificatePassword();
X509Certificate getServerCertificate() throws Exception;
byte[] getServerCertificatePublicKey() throws Exception;
void uploadServerCertificate(InputStream p12Stream, String password) throws Exception;
void deleteServerCertificate() throws Exception;
ServerCertificateInfo getServerCertificateInfo() throws Exception;
class ServerCertificateInfo {
private final boolean exists;
private final String subject;
private final String issuer;
private final Date validFrom;
private final Date validTo;
public ServerCertificateInfo(
boolean exists, String subject, String issuer, Date validFrom, Date validTo) {
this.exists = exists;
this.subject = subject;
this.issuer = issuer;
this.validFrom = validFrom;
this.validTo = validTo;
}
public boolean isExists() {
return exists;
}
public String getSubject() {
return subject;
}
public String getIssuer() {
return issuer;
}
public Date getValidFrom() {
return validFrom;
}
public Date getValidTo() {
return validTo;
}
}
}

View File

@ -7,7 +7,7 @@ import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.service.ServerCertificateService; import stirling.software.common.service.ServerCertificateService;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor

View File

@ -72,7 +72,7 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest; import stirling.software.SPDF.model.api.security.SignPDFWithCertRequest;
import stirling.software.SPDF.service.ServerCertificateService; import stirling.software.common.service.ServerCertificateService;
import stirling.software.common.annotations.AutoJobPostMapping; import stirling.software.common.annotations.AutoJobPostMapping;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.ExceptionUtils;

View File

@ -14,7 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import stirling.software.SPDF.service.ServerCertificateService; import stirling.software.common.service.ServerCertificateService;
@RestController @RestController
@RequestMapping("/api/v1/admin/server-certificate") @RequestMapping("/api/v1/admin/server-certificate")

View File

@ -1,4 +1,4 @@
package stirling.software.SPDF.service; package stirling.software.proprietary.service;
import java.io.*; import java.io.*;
import java.math.BigInteger; import java.math.BigInteger;
@ -26,7 +26,7 @@ import stirling.software.common.configuration.InstallationPathConfig;
@Service @Service
@Slf4j @Slf4j
public class ServerCertificateService { public class ServerCertificateService implements stirling.software.common.service.ServerCertificateService {
private static final String KEYSTORE_FILENAME = "server-certificate.p12"; private static final String KEYSTORE_FILENAME = "server-certificate.p12";
private static final String KEYSTORE_ALIAS = "stirling-pdf-server"; private static final String KEYSTORE_ALIAS = "stirling-pdf-server";
@ -213,40 +213,4 @@ public class ServerCertificateService {
} }
} }
public static class ServerCertificateInfo { }
private final boolean exists;
private final String subject;
private final String issuer;
private final Date validFrom;
private final Date validTo;
public ServerCertificateInfo(
boolean exists, String subject, String issuer, Date validFrom, Date validTo) {
this.exists = exists;
this.subject = subject;
this.issuer = issuer;
this.validFrom = validFrom;
this.validTo = validTo;
}
public boolean isExists() {
return exists;
}
public String getSubject() {
return subject;
}
public String getIssuer() {
return issuer;
}
public Date getValidFrom() {
return validFrom;
}
public Date getValidTo() {
return validTo;
}
}
}

View File

@ -1439,7 +1439,55 @@
"manageSignatures": { "manageSignatures": {
"tags": "sign,certificate,PEM,PKCS12,JKS,server,manual,auto", "tags": "sign,certificate,PEM,PKCS12,JKS,server,manual,auto",
"title": "Manage Signatures", "title": "Manage Signatures",
"desc": "Sign PDFs with certificates using manual or server-managed keys" "desc": "Sign PDFs with certificates using manual or server-managed keys",
"signMode": {
"tooltip": {
"header": {
"title": "About PDF Signatures"
},
"overview": {
"title": "How signatures work",
"text": "Both modes seal the document (any edits are flagged as tampering) and record who/when/how for auditing. Viewer trust depends on the certificate chain."
},
"manual": {
"title": "Manual - Bring your certificate",
"text": "Use your own certificate files for brand-aligned identity. Can display <b>Trusted</b> when your CA/chain is recognised.",
"use": "Use for: customer-facing, legal, compliance."
},
"auto": {
"title": "Auto - Zero-setup, instant system seal",
"text": "Signs with a server <b>self-signed</b> certificate. Same <b>tamper-evident seal</b> and <b>audit trail</b>; typically shows <b>Unverified</b> in viewers.",
"use": "Use when: you need speed and consistent internal identity across reviews and records."
},
"rule": {
"title": "Rule of thumb",
"text": "Need recipient <b>Trusted</b> status? <b>Manual</b>. Need a fast, tamper-evident seal and audit trail with no setup? <b>Auto</b>."
}
}
},
"certType": {
"tooltip": {
"header": {
"title": "About Certificate Types"
},
"what": {
"title": "What's a certificate?",
"text": "It's a secure ID for your signature that proves you signed. Unless you're required to sign via certificate, we recommend using another secure method like Type, Draw, or Upload."
},
"which": {
"title": "Which option should I use?",
"text": "Choose the format that matches your certificate file:",
"bullet1": "PKCS12 (.p12) one combined file (most common)",
"bullet2": "PFX (.pfx) Microsoft's version of PKCS12",
"bullet3": "PEM separate private-key and certificate .pem files",
"bullet4": "JKS Java .jks keystore for dev / CI-CD workflows"
},
"convert": {
"title": "Key not listed?",
"text": "Convert your file to a Java keystore (.jks) with keytool, then pick JKS."
}
}
}
}, },
"removeCertSign": { "removeCertSign": {
"tags": "authenticate,PEM,P12,official,decrypt", "tags": "authenticate,PEM,P12,official,decrypt",

View File

@ -24,7 +24,7 @@ const CertificateFormatSettings = ({ parameters, onParameterChange, disabled = f
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
PKCS#12<br />(.p12 file) PKCS12
</div> </div>
</Button> </Button>
<Button <Button
@ -35,7 +35,7 @@ const CertificateFormatSettings = ({ parameters, onParameterChange, disabled = f
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
PFX<br />(.pfx file) PFX
</div> </div>
</Button> </Button>
</div> </div>
@ -49,7 +49,7 @@ const CertificateFormatSettings = ({ parameters, onParameterChange, disabled = f
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
PEM<br />(Key + Cert files) PEM
</div> </div>
</Button> </Button>
<Button <Button
@ -60,18 +60,11 @@ const CertificateFormatSettings = ({ parameters, onParameterChange, disabled = f
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
JKS<br />(Java KeyStore) JKS
</div> </div>
</Button> </Button>
</div> </div>
</div> </div>
<Text size="xs" c="dimmed">
{parameters.certType === 'PKCS12' && "Upload a single .p12 file containing both certificate and private key"}
{parameters.certType === 'PFX' && "Upload a single .pfx file containing both certificate and private key"}
{parameters.certType === 'PEM' && "Upload separate certificate (.pem/.der/.crt/.cer) and private key (.pem/.der/.key) files"}
{parameters.certType === 'JKS' && "Upload a Java KeyStore (.jks) file"}
{!parameters.certType && "Choose the format of your certificate files"}
</Text>
</Stack> </Stack>
); );
}; };

View File

@ -28,7 +28,7 @@ const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = fal
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
Manual<br />(Provide Files) Manual
</div> </div>
</Button> </Button>
<Button <Button
@ -43,15 +43,10 @@ const CertificateTypeSettings = ({ parameters, onParameterChange, disabled = fal
style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }} style={{ flex: 1, height: 'auto', minHeight: '40px', fontSize: '11px' }}
> >
<div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}> <div style={{ textAlign: 'center', lineHeight: '1.1', fontSize: '11px' }}>
Auto<br />(Server Certificate) Auto
</div> </div>
</Button> </Button>
</div> </div>
<Text size="xs" c="dimmed">
{parameters.signMode === 'MANUAL'
? "Upload your own certificate files for signing"
: "Use the server's pre-configured certificate"}
</Text>
</Stack> </Stack>
); );
}; };

View File

@ -15,9 +15,6 @@ const SignatureAppearanceSettings = ({ parameters, onParameterChange, disabled =
<Stack gap="md"> <Stack gap="md">
{/* Signature Visibility */} {/* Signature Visibility */}
<Stack gap="sm"> <Stack gap="sm">
<Text size="sm" fw={500}>
{t('certSign.appearance.title', 'Signature Appearance')}
</Text>
<div style={{ display: 'flex', gap: '4px' }}> <div style={{ display: 'flex', gap: '4px' }}>
<Button <Button
variant={!parameters.showSignature ? 'filled' : 'outline'} variant={!parameters.showSignature ? 'filled' : 'outline'}

View File

@ -17,10 +17,10 @@ export const useCertificateTypeTips = (): TooltipContent => {
title: t("manageSignatures.certType.tooltip.which.title", "Which option should I use?"), title: t("manageSignatures.certType.tooltip.which.title", "Which option should I use?"),
description: t("manageSignatures.certType.tooltip.which.text", "Choose the format that matches your certificate file:"), description: t("manageSignatures.certType.tooltip.which.text", "Choose the format that matches your certificate file:"),
bullets: [ bullets: [
t("manageSignatures.certType.tooltip.which.bullet1", "PKCS#12 (.p12 / .pfx) one combined file (most common)"), t("manageSignatures.certType.tooltip.which.bullet1", "PKCS12 (.p12) one combined file (most common)"),
t("manageSignatures.certType.tooltip.which.bullet2", "PEM separate private-key and certificate .pem files"), t("manageSignatures.certType.tooltip.which.bullet2", "PFX (.pfx) Microsoft's version of PKCS12"),
t("manageSignatures.certType.tooltip.which.bullet3", "JKS Java .jks keystore for dev / CI-CD workflows"), t("manageSignatures.certType.tooltip.which.bullet3", "PEM separate private-key and certificate .pem files"),
t("manageSignatures.certType.tooltip.which.bullet4", "SERVER use server's certificate (no files needed)") t("manageSignatures.certType.tooltip.which.bullet4", "JKS Java .jks keystore for dev / CI-CD workflows")
] ]
}, },
{ {

View File

@ -0,0 +1,36 @@
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
export const useSignModeTips = (): TooltipContent => {
const { t } = useTranslation();
return {
header: {
title: t("manageSignatures.signMode.tooltip.header.title", "About PDF Signatures")
},
tips: [
{
title: t("manageSignatures.signMode.tooltip.overview.title", "How signatures work"),
description: t("manageSignatures.signMode.tooltip.overview.text", "Both modes seal the document (any edits are flagged as tampering) and record who/when/how for auditing. Viewer trust depends on the certificate chain.")
},
{
title: t("manageSignatures.signMode.tooltip.manual.title", "Manual - Bring your certificate"),
description: t("manageSignatures.signMode.tooltip.manual.text", "Use your own certificate files for brand-aligned identity. Can display <b>Trusted</b> when your CA/chain is recognized."),
bullets: [
t("manageSignatures.signMode.tooltip.manual.use", "Use for: customer-facing, legal, compliance.")
]
},
{
title: t("manageSignatures.signMode.tooltip.auto.title", "Auto - Zero-setup, instant system seal"),
description: t("manageSignatures.signMode.tooltip.auto.text", "Signs with a server <b>self-signed</b> certificate. Same <b>tamper-evident seal</b> and <b>audit trail</b>; typically shows <b>Unverified</b> in viewers."),
bullets: [
t("manageSignatures.signMode.tooltip.auto.use", "Use when: you need speed and consistent internal identity across reviews and records.")
]
},
{
title: t("manageSignatures.signMode.tooltip.rule.title", "Rule of thumb"),
description: t("manageSignatures.signMode.tooltip.rule.text", "Need recipient <b>Trusted</b> status? <b>Manual</b>. Need a fast, tamper-evident seal and audit trail with no setup? <b>Auto</b>.")
}
]
};
};

View File

@ -8,6 +8,7 @@ import { useManageSignaturesParameters } from "../hooks/tools/manageSignatures/u
import { useManageSignaturesOperation } from "../hooks/tools/manageSignatures/useManageSignaturesOperation"; import { useManageSignaturesOperation } from "../hooks/tools/manageSignatures/useManageSignaturesOperation";
import { useCertificateTypeTips } from "../components/tooltips/useCertificateTypeTips"; import { useCertificateTypeTips } from "../components/tooltips/useCertificateTypeTips";
import { useSignatureAppearanceTips } from "../components/tooltips/useSignatureAppearanceTips"; import { useSignatureAppearanceTips } from "../components/tooltips/useSignatureAppearanceTips";
import { useSignModeTips } from "../components/tooltips/useSignModeTips";
import { useBaseTool } from "../hooks/tools/shared/useBaseTool"; import { useBaseTool } from "../hooks/tools/shared/useBaseTool";
import { BaseToolProps, ToolComponent } from "../types/tool"; import { BaseToolProps, ToolComponent } from "../types/tool";
@ -23,6 +24,7 @@ const ManageSignatures = (props: BaseToolProps) => {
const certTypeTips = useCertificateTypeTips(); const certTypeTips = useCertificateTypeTips();
const appearanceTips = useSignatureAppearanceTips(); const appearanceTips = useSignatureAppearanceTips();
const signModeTips = useSignModeTips();
// Check if certificate files are configured for appearance step // Check if certificate files are configured for appearance step
const areCertFilesConfigured = () => { const areCertFilesConfigured = () => {
@ -59,6 +61,7 @@ const ManageSignatures = (props: BaseToolProps) => {
title: t("certSign.signMode.stepTitle", "Sign Mode"), title: t("certSign.signMode.stepTitle", "Sign Mode"),
isCollapsed: base.settingsCollapsed, isCollapsed: base.settingsCollapsed,
onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined, onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset : undefined,
tooltip: signModeTips,
content: ( content: (
<CertificateTypeSettings <CertificateTypeSettings
parameters={base.params.parameters} parameters={base.params.parameters}