LibreSign / LibreSign/libresign

PhpNative engine: non-ASCII characters in the signature stamp are rendered as mojibake

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

Nobody has claimed this yet.

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

Description

Describe the bug

With the PhpNative signature engine, non-ASCII characters in the visible signature stamp are
rendered as mojibake. The stored signature_text_template is correct — the corruption happens at
render time.

A French template produced this on the signed PDF:

Template (stored, correct) Rendered in the PDF
Signé avec LibreSign Sign^ avec LibreSign
Émetteur : Phenix Tech ^ metteur : Phenix Tech
To reproduce
  1. Set signature_engine to PhpNative.
  2. Set a signature_text_template containing accented characters, e.g.
    Signé avec LibreSign\n{{SignerCommonName}}\nÉmetteur : {{IssuerCommonName}}.
  3. Sign a document and open the result — the accented characters are mangled.

occ config:app:get libresign signature_text_template still returns the correct UTF-8, confirming
the problem is in the rendering path and not in storage.

Expected behavior

The visible stamp should render the template as stored, at least for the Latin-1 range.

Root cause

Two things combine in lib/Handler/SignEngine/PhpNativeHandler.php:

1. No encoding conversion. escapePdfText() (line 398) only escapes PDF delimiters:

private function escapePdfText(string $value): string {
    $value = str_replace('\\', '\\\\', $value);
    $value = str_replace('(', '\\(', $value);
    $value = str_replace(')', '\\)', $value);
    return $value;
}

The raw UTF-8 bytes are then written straight into a PDF literal string via
sprintf("(%s) Tj\n", $escaped). é (0xC3 0xA9) therefore reaches the content stream as two
separate byte codes.

2. A single-byte base font with no declared encoding (line 336):

resources: [
    'Font' => [
        'F1' => [
            'Type' => '/Font',
            'Subtype' => '/Type1',
            'BaseFont' => '/Helvetica',
        ],
    ],
],

There is no /Encoding entry, so the viewer falls back to Helvetica's built-in StandardEncoding.
Each UTF-8 byte is mapped independently, which is exactly the observed output.

Possible directions
  • Minimal: convert the string to WinAnsi and declare /Encoding /WinAnsiEncoding in the font
    dictionary. Covers Latin-1, so most Western European templates, and is a small change. Characters
    outside Latin-1 would still need a fallback rather than silent corruption.
  • Complete: embed a TrueType subset with a proper /Encoding (or a Type0/Identity-H font) to
    support the full Unicode range, at the cost of a larger PDF and a font to ship.

Either way, it would be worth failing loudly — or transliterating explicitly — rather than emitting
bytes that render as mojibake, since the result lands on a signed document that cannot be corrected
afterwards without invalidating the signature.

Why this matters more than it looks

This is engine-specific: JSignPdf renders the same template correctly through iText. The two
engines are therefore not interchangeable in both directions.

It compounds with #8145: JSignPdf currently cannot timestamp against any TSA that rejects SHA-1,
so instances that need RFC 3161 timestamping are pushed towards PhpNative — and there they must
restrict their signature stamp to ASCII. Right now there is no engine that does both correct
timestamping and correct non-ASCII rendering.

Workaround

Restrict signature_text_template to ASCII. In French this is doable with some care — Signature,
Certificat, Date carry no accents — but it constrains the wording, and nothing warns the
administrator that the template will be corrupted.

Environment
  • LibreSign 13.3.0
  • Nextcloud 33.0.8 (All-in-One)
  • Signature engine: PhpNative (jeidison/signer-php)
  • Certificate engine: OpenSSL, self-signed internal root CA

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 in lib/Handler/SignEngine/PhpNativeHandler.php, reading escapePdfText() around line 398 and the font dictionary around line 336. Reproduce the issue with the provided French template, then compare the PhpNative rendering path with JSignPdf. Done means the visible stamp preserves supported non-ASCII text and handles characters outside that range without silent mojibake.

Written by the indexing model from the issue text.

Assessment

Tech stack
php
Domain
backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
56/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.