Skip to content

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

Description

@bast69

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend taskbugSomething isn't workingphpPull requests that update Php code

    Type

    Projects

    • Status
      0. Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions