LibreSign / LibreSign/libresign

Implement the external PDF signing lifecycle

Open
#8,354 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

backend php
Dominant language
PHP
Stars
818
Forks
146
Avg merge
11h 31m
Merged PRs (30d)
326

Description

This is part of #8335.

LibreSign currently completes PDF digital signing in one operation where the signing implementation has direct access to the private key.

External signing requires a different lifecycle:

prepare PDF/PAdES signature
    -> produce the cryptographic input required by the signer
    -> perform the private-key operation outside LibreSign
    -> receive the signature result
    -> finalize the PDF/PAdES signature

The private-key operation may later be performed by a CSC provider, an ICP-Brasil PSC provider, an HSM integration or a local-device bridge.

This issue introduces that lifecycle together with its first real PDF/PAdES implementation.

It must not add production interfaces or value objects that are used only by tests or reserved only for future work.

Goal

Implement a real external-signing lifecycle for PDF documents where:

  1. LibreSign prepares a PDF/PAdES signature without having the private key;
  2. the prepared result exposes the cryptographic input and algorithm information required by an external signer;
  3. an externally produced signature result can be returned to the PDF/PAdES implementation;
  4. LibreSign finalizes the document with that signature;
  5. the resulting PDF can be validated as a correctly signed document.

The private key must never be passed to the PDF/PAdES preparation/finalization component.

Architecture

The intended responsibility boundary is:

sequenceDiagram
    participant L as LibreSign
    participant E as PDF/PAdES signing engine
    participant S as External signer

    L->>E: Prepare external signature
    E-->>L: Prepared signing operation

    L->>S: Cryptographic signing input
    S-->>L: Signature result

    L->>E: Finalize prepared operation
    E-->>L: Signed PDF
PDF/PAdES signing engine owns
  • PDF signature preparation;
  • incremental PDF updates;
  • ByteRange;
  • CMS/PAdES structures;
  • signed attributes;
  • use of the public signer certificate and certificate chain;
  • algorithm information required by the signature format;
  • state required to finalize the prepared operation;
  • application of the returned cryptographic signature;
  • finalization of the signed PDF.
External signer owns
  • access to the private key;
  • credential discovery;
  • authorization required to use the key;
  • the private-key cryptographic operation;
  • returning the resulting signature.

The external signer must not need PDF/PAdES knowledge.

The PDF/PAdES implementation must not contain CSC, ICP-Brasil PSC, OAuth or provider-specific behavior.

Production code must have a real consumer

Any interface or value object introduced by this issue must be used by the production external PDF signing implementation added by the same issue.

Do not add:

  • a production interface with only test implementations;
  • a value object used only by tests;
  • a fake production or reusable test implementation;
  • abstractions that exist only for a possible future provider.

Create only the abstractions required by the real prepare/finalize implementation.

Tests may use PHPUnit mocks, anonymous classes or other test-local doubles where necessary.

Public certificate information

The prepare operation must receive the public signer certificate information required to construct the PDF/PAdES signature.

The private key must not be included.

The production implementation may introduce a focused immutable value object if the real implementation requires one.

If introduced, it must contain only public certificate information such as:

  • the signer X.509 certificate;
  • additional certificates required for the signer certificate chain.

Use one documented certificate representation consistently.

Do not store:

  • private keys;
  • PKCS#12/PFX credentials;
  • passwords;
  • provider credentials;
  • OAuth tokens;
  • provider secrets.

Do not introduce this value object unless it is consumed by the production implementation in this issue.

Signing algorithms

Do not model the external signing operation as one arbitrary signatureAlgorithm string unless the real implementation proves that this is sufficient.

The implementation must explicitly represent the cryptographic information required by both:

  • the PDF/CMS/PAdES preparation; and
  • the external private-key operation.

This may include separate digest and signature algorithm information and, when required, algorithm parameters.

The representation must be based on the requirements of the implemented signing flow, not on assumptions about one future provider.

Provider capability negotiation remains outside this issue.

Prepared signing state

The prepare operation must return everything required to:

  1. perform the external cryptographic operation; and
  2. later finalize the same prepared PDF signature.

The prepared state must support the fact that preparation and finalization may happen in different HTTP requests or PHP processes.

It must therefore not depend on keeping runtime-only objects alive.

Do not store objects such as:

  • service instances;
  • PHP resources;
  • closures;
  • provider clients;
  • other process-local state.

The internal finalization state must remain opaque outside the PDF/PAdES implementation.

The caller must not need to understand ByteRange, CMS, PDF objects or other PDF/PAdES internals.

This issue does not define the database model that will later persist an external signing session.

External signing result

The result returned by the external private-key operation must be treated according to the cryptographic format expected by the implemented PDF/PAdES flow.

Do not introduce a wrapper class only for architectural symmetry.

Create a value object only if the production implementation needs more than one value to safely represent the result.

Relationship with the current signing flow

The existing signing methods must continue working without behavior changes.

Do not migrate all current signing methods to the new lifecycle in this issue.

The existing synchronous flow may continue to use:

ISignEngineHandler
    -> sign()
    -> File

External signing introduces an additional path for PDF signing.

The implementation must not require existing signing methods to use the external lifecycle.

Relationship with signer-php

The implementation path for PDF/PAdES external signing is being discussed in:

https://github.com/jeidison/signer-php/issues/25

The preferred direction is to implement the prepare/finalize capability in the PDF/PAdES layer used by LibreSign instead of duplicating CMS/PAdES logic inside LibreSign.

This issue should not introduce a temporary LibreSign-specific PDF/PAdES implementation only to bypass that architectural decision.

Implementation of this issue should wait until that path is defined.

If signer-php provides the required prepare/finalize API, LibreSign should adapt that real API rather than creating a competing abstraction with no production need.

Tests

Tests must cover the real external PDF signing lifecycle.

At minimum:

  1. prepare a PDF signature using a controlled test certificate;
  2. obtain the real cryptographic input required by the implementation;
  3. perform the private-key operation using a controlled test key;
  4. return the resulting signature to the finalize operation;
  5. finalize the PDF;
  6. validate that the resulting PDF contains a valid digital signature.

Also verify that:

  • the private key is not provided to the prepare/finalize component;
  • existing PDF signatures are preserved when another signature is added;
  • the prepared operation can be resumed without keeping the original PHP process alive;
  • algorithm information used during preparation matches the external private-key operation;
  • no network or real external signing provider is required by the automated tests.

Prefer DataProviders where multiple algorithms or relevant signing cases can be covered without duplicating tests.

Do not add standalone fake engine classes only to satisfy the test suite.

Scope boundaries

Please keep this issue focused on the real PDF/PAdES external-signing lifecycle.

Do not:

  • implement CSC;
  • implement ICP-Brasil PSC;
  • add OAuth or provider authorization;
  • add provider configuration;
  • add provider assignment;
  • add signing-session persistence;
  • change frontend behavior;
  • add controllers or public API routes;
  • add a browser/native bridge;
  • add PKCS#11 or hardware integration;
  • migrate all existing signing methods;
  • create speculative abstractions without a production consumer.

If the real PDF/PAdES implementation requires a contract or value object, add it as part of this implementation and cover it through the real lifecycle tests.

Why this helps #8335

This provides the first functional building block required by external digital signing.

After this issue, LibreSign will have a tested PDF/PAdES lifecycle that can prepare a signature, delegate only the private-key operation and later finalize the document.

Future provider work can then focus on:

prepared cryptographic input
    -> provider authorization
    -> private-key operation
    -> signature result

without implementing PDF/PAdES logic.

This keeps provider protocols and document-signing internals independent.

Done when

  • A production PDF external-signing implementation exists.
  • It provides separate prepare and finalize operations.
  • The private key is never provided to the prepare/finalize component.
  • Public signer certificate information is available when required by PDF/PAdES preparation.
  • Digest, signature algorithm and required algorithm parameters are represented correctly for the implemented lifecycle.
  • Prepared state can cross a PHP request/process boundary.
  • PDF/PAdES internals remain hidden from the future provider layer.
  • A controlled test key can perform the external private-key operation between prepare and finalize.
  • The resulting PDF validates with a valid digital signature.
  • Existing signatures are preserved when adding another signature.
  • Existing LibreSign signing behavior remains unchanged.
  • Automated tests require no real remote provider or network access.
  • No production abstraction exists only for tests or hypothetical future use.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the existing ISignEngineHandler sign() flow and the prepare/finalize direction discussed in signer-php issue 25. Wait until that PDF/PAdES API path is defined, then implement the production lifecycle and its real end-to-end tests with a controlled certificate and key. Done means the finalized PDF validates, existing signatures remain, state crosses processes, and current signing behavior is unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, cryptography, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.