Skip to content

Support multiple timestamps per signature - #511

Closed
mtrojnar wants to merge 2 commits into
masterfrom
multi-timestamp
Closed

mtrojnar wants to merge 2 commits into
masterfrom
multi-timestamp

Conversation

@mtrojnar

@mtrojnar mtrojnar commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Pull Request Type

  • New feature

Related Issue

Follow-up to #510: support multiple timestamps on a single signature, rather than retaining just one timestamp for verification.

Current Behavior

Adding a timestamp replaces existing timestamps. Repeated timestamp URLs are fallback servers, and verification reads only the first value of each timestamp attribute, retaining one timestamp for the final check.

New Behavior

  • add preserves existing timestamps and appends new ones, including timestamps of the other protocol.
  • -timestamp-all requires a timestamp from every specified -t and -ts URL and permits both protocols in one invocation. Any failed request fails the operation without leaving a partial output. At least one -t or -ts URL is required, so it never succeeds without adding a timestamp.
  • Without -timestamp-all, repeated URLs retain their existing fallback behavior: stop after the first successful response.
  • Verification checks every value of every Authenticode/RFC 3161 timestamp attribute, including repeated attributes. One trusted, valid timestamp suffices only if the Authenticode signature also validates at that timestamp's time.
  • Invalid, untrusted, malformed, or incorrectly bound alternatives do not override a successful timestamp/signature pair. If no pair validates, that signature fails. Empty/malformed timestamp attributes are not treated as absent timestamps.
  • -ignore-timestamp retains verification at the requested/current time. Files with multiple signatures still succeed when any eligible signature validates.

Example:

osslsigncode add -timestamp-all \
  -ts https://tsa1.example.com/ -ts https://tsa2.example.com/ \
  -in signed.exe -out multistamped.exe

Scope of Changes

  • Separate signature-attribute printing from per-value timestamp decoding and verification; free each decoded timestamp after use.
  • Share the remote timestamp loop between both protocols and bound the URL arrays.
  • Add a CTest that appends two RFC 3161 timestamps with -timestamp-all to an already timestamped PE file, then verifies the result.
  • Update CLI help, command documentation, README, and changelog.
  • Separate commit, pre-existing bug: RFC 3161 verification compared the messageImprint using its own length, so a TSA-signed token with an empty or truncated imprint matched any signature. Require the imprint length to equal the digest size (reported by @olszomal).

Testing

  • Existing tests
  • New tests added
  • Manual testing

Linux, GCC 16.2.0, OpenSSL 3.6.4:

Check Result
CMake build and full serial ctest 377/377 passed
After rebase onto 6356da1: full serial ctest with ASan+UBSan (-fno-sanitize-recover=undefined), OpenSSL 3.6.5 393/393 passed
Expired-cert signature plus test-TSA-signed token with an empty, 1-byte, 100-byte, or wrong imprint (OpenSSL 3.6.5) Empty and 1-byte verified before the fix; all rejected after
-timestamp-all without -t/-ts Rejected with usage error, no output
git diff --check Passed

Strict UBSan across all formats additionally finds a pre-existing signed-shift error in appx.c:2822 (left shift of 255 by 24 places cannot be represented in type 'int'). Reproduced on base bf7533d using signed_256appx; fixed on master by 6356da1, which this PR is now rebased onto.

Additional Notes

  • Compatibility change: add no longer implicitly replaces existing timestamps. Explicitly remove the signature and sign again to replace them.
  • Built-in timestamping remains separate from remote timestamping; repeated built-in add invocations append normally.
  • The final signature result now summarizes whether a timestamp/signature pair validated; per-timestamp verification results are printed separately.
  • Not tested locally: Windows/Cygwin/macOS, older OpenSSL versions, or interoperability of multi-timestamp output with third-party Authenticode verifiers.

License Declaration

  • I hereby agree to license my contribution under the project's license.

Comment thread osslsigncode.c Fixed
Comment thread osslsigncode.c Fixed
@mtrojnar
mtrojnar force-pushed the multi-timestamp branch 2 times, most recently from f2f6fe4 to 1285301 Compare November 4, 2026 12:16
@mtrojnar mtrojnar mentioned this pull request Nov 4, 2026
3 tasks done
@mtrojnar

mtrojnar commented Nov 4, 2026

Copy link
Copy Markdown
Owner Author

@p-rogalski Can you take a look whether -timestamp-all addresses your concerns?

@mtrojnar mtrojnar assigned olszomal and unassigned olszomal Nov 5, 2026
@olszomal

olszomal commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

The multi-timestamp implementation looks good.

One pre-existing issue in verify_timestamp_token(): memcmp() uses the untrusted hash_len, allowing partial comparisons or out-of-bounds reads.

I suggest checking the digest length first:

if (hash_len != EVP_MD_size(md) ||
    memcmp(mdbuf, hash_data, (size_t)hash_len)) {

Preserve existing timestamps when adding new ones and verify every value
of both Authenticode and RFC 3161 timestamp attributes. Accept a signature
when at least one trusted timestamp and the Authenticode signature both
validate at that timestamp's time. Malformed or untrusted alternatives
must not hide a valid pair or be treated as absent timestamps.

Add -timestamp-all to require every requested TSA, allowing both remote
protocols together without changing the existing URL fallback behavior.
Reject -timestamp-all without any -t or -ts URL, so it never succeeds
without adding a timestamp. Reject excess URLs before writing past the
timestamp server arrays and use a while loop for variable-length argument
consumption.

Document the new verification policy and the change from implicit
timestamp replacement to appending.
verify_timestamp_token() compared the computed digest with the
token's messageImprint using the imprint's own length. An empty
imprint therefore matched any signature, and a short one bound only a
prefix of the digest. A trusted TSA that signs such a request, even
once, produces a token that can be attached to other signatures to
claim an earlier signing time, for example to make a signature with an
expired certificate verify. An imprint longer than the digest read
past the computed value.

Require the imprint length to equal the digest size before comparing.
Also reject an unknown imprint algorithm explicitly and check the
digest computation, whose failure was previously ignored.

Authenticode timestamps are unaffected: CMS_verify() binds them
through the messageDigest attribute.
@mtrojnar

mtrojnar commented Nov 6, 2026

Copy link
Copy Markdown
Owner Author

Closing this in favour of #532, which carries the message imprint length check, the timestamp/time pairing fix and the -t/-ts array bounds check.

The multiple-timestamp feature needs a different design. What Windows accepts and uses is recorded in #533: one attribute with two values is rejected outright, and with the values in separate attributes only the first is used, ordered by DER encoding. A second timestamp therefore adds no redundancy there, so -timestamp-all and append-by-default would not deliver what they promise. Aligning verify with Windows is tracked as follow-up in #533.

@mtrojnar mtrojnar closed this Nov 6, 2026
@mtrojnar
mtrojnar deleted the multi-timestamp branch November 6, 2026 18:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants