How to Implement Digital Signatures Using the CertSignController in Stirling-PDF

The CertSignController exposes a POST endpoint at /api/v1/security/cert-sign that consumes multipart certificate files and PDFs to generate cryptographically signed documents using Apache PDFBox and BouncyCastle, supporting both visible digital signature overlays and invisible certification.

The Stirling-Tools/Stirling-PDF repository provides a Spring Boot-based backend for PDF manipulation. To implement digital signatures using the CertSignController, you interact with the app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java class, which orchestrates keystore creation, certificate chain validation, and PDF cryptographic signing through a standardized multipart HTTP interface.

CertSignController Endpoint Architecture

The controller exposes a single REST endpoint mapped via @AutoJobPostMapping to handle certificate-based signing operations.

Request Mapping and DTO Binding

The endpoint listens at POST /api/v1/security/cert-sign and consumes multipart/form-data. Spring automatically binds incoming requests to the SignPDFWithCertRequest DTO defined in app/core/src/main/java/stirling/software/SPDF/model/api/security/SignPDFWithCertRequest.java.

This DTO extends PDFFile and encapsulates:

  • The source PDF file (fileInput)
  • Certificate files (PEM key pairs, PKCS12/PFX stores, or JKS keystores)
  • Passwords for encrypted private keys
  • Visual signature options (page number, coordinates, logo display, signer metadata)

Certificate Type Handling

The controller supports four certificate input modes via the certType parameter:

  1. PEM: Separate private key and certificate files parsed using BouncyCastle helpers (getPrivateKeyFromPEM, getCertificateFromPEM)
  2. PKCS12/PFX: Standard .p12 or .pfx keystore files
  3. JKS: Java KeyStore format
  4. SERVER: Server-side keystore retrieved via ServerCertificateServiceInterface

Based on the type, the controller constructs a Java KeyStore containing the private key and certificate chain (lines 93-124). The static initializer registers BouncyCastle as a security provider (Security.addProvider(new BouncyCastleProvider())) to enable PEM parsing and cryptographic operations.

PDF Signing Implementation

The actual signing logic resides in the inner class CreateSignature, which extends PDFBox's CreateSignatureBase, and the static sign helper method.

Signature Engine Initialization

When processing a request, the controller instantiates CreateSignature with the prepared KeyStore. The constructor (lines 107-125 in CertSignController.java) loads an optional logo image (signature.png) from resources for visual signatures and initializes the cryptographic parameters.

Document Signing Process

The static sign method (lines 165-199) performs the following operations:

  • Loads the input PDF using CustomPDFDocumentFactory to create a PDDocument instance
  • Constructs a PDSignature object with metadata (reason, location, signer name, date)
  • Determines signature visibility based on the showSignature flag
  • For visible signatures, creates SignatureOptions and calls instance.createVisibleSignature
  • For invisible signatures, applies the cryptographic signature without visual artifacts
  • Writes the signed PDF bytes to an OutputStream

Visual Signature Generation

The createVisibleSignature method (lines 128-170) generates the visual overlay by:

  • Creating a form XObject for the signature appearance on the specified page
  • Rendering the optional logo via PDImageXObject
  • Drawing signer information extracted from the X.509 certificate (name, date, reason)

The final signed PDF bytes are wrapped in a ResponseEntity<byte[]> via WebResponseUtils.bytesToWebResponse (lines 262-264) with the filename pattern *_signed.pdf.

Implementation Examples

cURL HTTP Request

For command-line integration or testing, submit a multipart POST request:

curl -X POST "http://localhost:8080/api/v1/security/cert-sign" \
  -F "fileInput=@sample.pdf" \
  -F "certType=PKCS12" \
  -F "p12File=@mycert.p12" \
  -F "password=secret123" \
  -F "showSignature=true" \
  -F "pageNumber=1" \
  -F "showLogo=true" \
  -F "reason=Approved" \
  -F "location=New York" \
  -F "name=John Doe" \
  -o signed_sample.pdf

The multipart fields map directly to properties of SignPDFWithCertRequest.

Java Client Using Spring RestTemplate

For programmatic integration in Java applications:

MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("fileInput", new FileSystemResource("sample.pdf"));
body.add("certType", "PEM");
body.add("privateKeyFile", new FileSystemResource("private_key.pem"));
body.add("certFile", new FileSystemResource("certificate.pem"));
body.add("password", "myPassword");
body.add("showSignature", true);
body.add("pageNumber", 1);
body.add("showLogo", true);
body.add("reason", "Contract approval");
body.add("location", "Berlin");
body.add("name", "Alice Smith");

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(body, headers);
ResponseEntity<byte[]> response = restTemplate.postForEntity(
        "http://localhost:8080/api/v1/security/cert-sign",
        request,
        byte[].class);

Files.write(Paths.get("sample_signed.pdf"), response.getBody());

Spring automatically populates the SignPDFWithCertRequest DTO from the multipart map.

Unit Testing the Controller

For internal testing or custom extensions, invoke the controller directly:

@MockBean private ServerCertificateServiceInterface serverCertificateService;
@Autowired private CertSignController certSignController;
@Autowired private CustomPDFDocumentFactory pdfFactory;

@Test
void signPdfWithPem() throws Exception {
    MockMultipartFile pdf = new MockMultipartFile("fileInput", "doc.pdf",
            "application/pdf", Files.readAllBytes(Paths.get("src/test/resources/doc.pdf")));
    MockMultipartFile key = new MockMultipartFile("privateKeyFile", "key.pem",
            "application/octet-stream", Files.readAllBytes(Paths.get("src/test/resources/key.pem")));
    MockMultipartFile cert = new MockMultipartFile("certFile", "cert.pem",
            "application/octet-stream", Files.readAllBytes(Paths.get("src/test/resources/cert.pem")));

    SignPDFWithCertRequest req = new SignPDFWithCertRequest();
    req.setFileInput(pdf);
    req.setCertType("PEM");
    req.setPrivateKeyFile(key);
    req.setCertFile(cert);
    req.setPassword("");   // PEM keys may be unencrypted
    req.setShowSignature(true);
    req.setPageNumber(1);
    req.setShowLogo(true);
    req.setReason("Test");
    req.setLocation("Test Lab");
    req.setName("Tester");

    ResponseEntity<byte[]> resp = certSignController.signPDFWithCert(req);
    assertEquals(HttpStatus.OK, resp.getStatusCode());
    assertTrue(resp.getBody().length > 0);
}

This test bypasses the HTTP layer while exercising the same code path used by the REST endpoint.

Key Source Files and Dependencies

Understanding the controller requires familiarity with these components:

Summary

  • The CertSignController exposes POST /api/v1/security/cert-sign for certificate-based PDF signing in Stirling-PDF.
  • It supports PEM, PKCS12/PFX, JKS, and SERVER certificate types through BouncyCastle-backed keystore creation.
  • The CreateSignature inner class extends PDFBox's CreateSignatureBase to handle cryptographic signing and optional visual overlays.
  • Visual signatures include configurable logos, signer names, dates, and reasons rendered via PDFBox form XObjects.
  • The controller returns signed PDF bytes wrapped in a ResponseEntity with standardized headers via WebResponseUtils.

Frequently Asked Questions

What certificate formats does CertSignController support?

The controller supports four certificate formats: PEM (separate key and certificate files), PKCS12/PFX (standard keystores), JKS (Java KeyStore), and SERVER (server-side keystore retrieval). PEM files are parsed using BouncyCastle helpers, while PKCS12 and JKS files are loaded directly into a Java KeyStore object. The SERVER type delegates keystore retrieval to the ServerCertificateServiceInterface implementation.

How does the controller handle visual versus invisible signatures?

The showSignature parameter in the request DTO determines visibility. When true, the controller calls createVisibleSignature to render a signature appearance containing the signer name, date, reason, and optional logo on the specified page. When false, the controller applies a cryptographic signature without visual artifacts, certifying the document without modifying its visible content.

Can I use the CertSignController for batch processing or automated workflows?

Yes, the endpoint is annotated with @AutoJobPostMapping, indicating integration with Stirling-PDF's job automation framework. The multipart interface accepts standard HTTP clients, making it suitable for batch scripts using cURL or automated Java clients using RestTemplate or WebClient. For high-volume server-side signing, configure the SERVER certificate type to avoid transferring certificate files with each request.

What dependencies are required for the cryptographic operations?

The implementation requires Apache PDFBox for PDF manipulation and BouncyCastle for cryptographic operations and PEM parsing. The controller registers BouncyCastle as a security provider during class initialization. The CustomPDFDocumentFactory provides additional application-specific PDF loading configuration, while Spring Boot handles the REST layer and multipart file binding.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →