jsign: Signing certain MSI files corrupts them

Hi, My team has encountered an issue while using the programmatic API to sign some MSI files. We sign many files every day including other MSIs and have no issues with the vast majority. We’re not sure why these couple of MSIs are the exception. The signing doesn’t fail or throw any errors at all but immediately after the MSI is signed it becomes corrupt and attempting to run the MSI displays the following error: image

The code we’re using to sign is as follows:

KeyStore keystore;
AuthenticodeSigner signer;
Signable file;
File certFile = null;

try {
	certFile = new File(certFilePath);
	keystore = KeyStoreUtils.load(certFile, "PKCS12", certPassphrase, null);
	
	signer = new AuthenticodeSigner(keystore, certKeystore, certPassphrase);

	File[] filesToSign = listFiles(regex);
	
	for(File targetFile : filesToSign) {		
		signer.withTimestamping(true)
			.withDigestAlgorithm(DigestAlgorithm.SHA256)
			.withSignaturesReplaced(true)
			.withTimestampingMode(TimestampingMode.RFC3161)
		       .withTimestampingAuthority(timestampURL);

		file = Signable.of(targetFile);
		
		signer.sign(file);				
	}
} catch (Exception e) {
	// ...
}

It’s pretty straightforward so I’m not sure what’s going on or additional spots to look.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 18 (13 by maintainers)

Commits related to this issue

Most upvoted comments

After a few hours ingesting the CFB file format and writing a homemade parser I’ve eventually figured out the issue: Apache POI doesn’t update the number of directory sectors in the MSI header.

The test file sent by @griggsrl has 32 entries which fill exactly one directory sector. The addition of a new entry (such as the signature entry created by Jsign) generates a second directory sector, but the number of directory sectors in the header remains 1. It looks like msiexec.exe, the program processing the MSI files, reads only the number of directory sectors specified in the header, instead of reading the full stream as specified by the FAT. Depending on the name of the entry added, the binary tree of the entry nodes is reshaped, a part of the tree is grafted to the new entry located on the second directory sector and becomes unavailable. If this affects a critical entry such as the SummaryInformation entry, the installation fails. Increasing the number of directory sectors by one in the header (offset 0x28) fixes the issue.