google / google/authenticode-rs

`info` rejects install4j-signed Windows executables that Windows accepts

Open
#272 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
36
Forks
17
PR merge metrics
No merged PRs in 30d

Description

## Summary

`authenticode-tool info` fails on signed Windows `.exe` files built with install4j, reporting `Error: invalid signature: authenticode signature parse error: InvalidSignedDataVersion(V3)`. Microsoft's `signtool` verifies the same files with zero warnings and zero errors, and they install with no warnings on Windows.

install4j emits `SignedData` version 3, where the 2008 Authenticode specification says the version "must be set to 1". This does not look like an ad-hoc error - it is what a generic CMS (RFC 5652) implementation produces, since CMS §5.1 computes version 3 for any `eContentType` other than `id-data`, and Authenticode's is always `SPC_INDIRECT_DATA_OBJID`. The same files also wrap the `SpcIndirectDataContent` in an ASN.1 OCTET STRING, where the Authenticode specification puts it directly in the `[0] EXPLICIT` content field - again what CMS prescribes, since it defines `eContent` as `[0] EXPLICIT OCTET STRING`. Non-conformant by the Authenticode specification, in other words, but exactly what CMS produces: these signatures come from a CMS library applied to Authenticode without the PKCS #7 carve-out that Authenticode needs.

Worth noting that the two are separate blockers here. The version check fails first; if it passed, `decode_as::()` would then fail on the OCTET STRING. Accepting these files means handling both.

**The install4j installer also fails validation**, so it's not just a quirk of my particular setup. If their signer has always emitted this, every install4j-built Windows installer is affected, from any vendor.

Since Windows and `signtool` accept these files as valid, and the signature format follows the CMS specification, I would suggest that `authenticode-tool` accept them as well, either by default, or behind e.g. a `--lenient` flag.

I have asked the makers of install4j (ej-technologies) if they have information from Microsoft regarding what is officially supported in this regard, but I haven't gotten a reply (yet).

## Reproduction

```
curl -O https://download.ej-technologies.com/install4j/install4j_windows-x64_13_1.exe
authenticode-tool info install4j_windows-x64_13_1.exe
```

The same file verifies on Windows: `signtool verify /pa /v /all` reports `Successfully verified`, 0 warnings, 0 errors. Non-install4j installers I have tried all pass `authenticode-tool info` without complaint.

## Where it fails

The following is Claude's diagnosis, so take it for what it is...

In `AuthenticodeSignature::from_bytes`, `authenticode/src/signature.rs`. The version check comes first, and is what these files trip on:

```rust
if signed_data.version != CmsVersion::V1 {
return Err(
AuthenticodeSignatureParseError::InvalidSignedDataVersion(
signed_data.version,
),
);
}
```

and further down, the content decode that they would fail next:

```rust
.decode_as::()
.map_err(AuthenticodeSignatureParseError::InvalidSpcIndirectDataContent)?;
```

`econtent` is an `Any`. For the strict encoding it holds the `SpcIndirectDataContent` SEQUENCE directly and `decode_as` succeeds; for these files it holds an OCTET STRING containing that SEQUENCE.

## How other implementations handle it

| Encoding | Windows / signtool | JSign | osslsigncode ≤ 2.13 | authenticode-tool |
|------------------------------------------------------------|--------------------|---------|---------------------|-------------------|
| PKCS #7 style: `SignedData` v1, content directly in `[0]` | accepts | accepts | accepts | accepts |
| CMS style: `SignedData` v3, content in an OCTET STRING | accepts | accepts | accepts | **rejects** |

JSign (https://github.com/ebourg/jsign) is worth a closer look here, because it takes a deliberate position on both sides of this. It *produces* the strict encoding: it carries a custom BouncyCastle subclass, `AuthenticodeSignedDataGenerator`, described in its own javadoc as

> CMSSignedDataGenerator suitable for Authenticode signing (generates SignedData
> v1 structures and preserves the sequence structure of the
> SpcIndirectDataContent object instead of turning it into an octet string)

written precisely because stock CMS generators do neither. And yet it *accepts* both forms on verification: `jsign verify` reports `Signature #1 (SHA256 with RSA 4096) by ej-technologies GmbH is valid` for `install4j_windows-x64_13_1.exe`.

This is also, for what it is worth, what RFC 5652 §5.2.1 suggests. Discussing exactly this PKCS #7 / CMS content encoding difference, it says an implementation unable to decode one syntax "MAY attempt to decode the SignedData type using the PKCS #7 SignedData contentInfo content ANY syntax and compute the message digest accordingly", and names Microsoft Authenticode as the motivating example.

### osslsigncode 2.14

`osslsigncode` 2.14 (current at the time of writing) also fails on these files. I have filed a corresponding request there - mtrojnar/osslsigncode#505.

## Requested change

Would you consider adding explicit support for validating these signed files, either by default, or by adding a lenient mode? In the default case, e.g. a `--strict` flag could be reasonable instead of the lenient one. In the `authenticode` crate it would presumably be a parse option rather than a change to `from_bytes`.

The reasoning: most people reach for a tool like this to answer "will Windows trust this binary?", typically from a Linux or macOS build machine where `signtool` is not available. When the answer the tool gives differs from the answer Windows gives, the result is a false negative, and a false negative from a signature verifier is expensive - it is indistinguishable from a tampered file.

Contributor guide

Open the contributing guide

Research direction

Start in authenticode/src/signature.rs at AuthenticodeSignature::from_bytes, the SignedData version check, and the subsequent SpcIndirectDataContent decode. Reproduce the failure with the install4j installer, then verify both the version-3 and OCTET STRING encodings are handled according to the chosen default or lenient-mode behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
security
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
66/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.