Skip to content

Commit

Permalink
NAVX signing - work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
ebourg committed Aug 4, 2023
1 parent fd9e8b1 commit 0dc04dc
Show file tree
Hide file tree
Showing 11 changed files with 374 additions and 26 deletions.
9 changes: 5 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ Jsign - Java implementation of Microsoft Authenticode

Jsign is a Java implementation of Microsoft Authenticode that lets you sign
and timestamp executable files for Windows, Microsoft Installers (MSI), Cabinet
files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) and scripts.
Jsign is platform independent and provides an alternative to native tools like
signcode/signtool on Windows or the Mono development tools on Unix systems.
files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft
Dynamics 365 extension packages and scripts. Jsign is platform independent and
provides an alternative to native tools like signcode/signtool on Windows or
the Mono development tools on Unix systems.

Jsign comes as an easy-to-use task/plugin for the main build systems (Maven,
Gradle, Ant). It's especially suitable for signing executable wrappers and
Expand All @@ -20,7 +21,7 @@ Jsign can also be used programmatically or standalone as a command line tool.
Jsign is free to use and licensed under the [Apache License version 2.0](https://www.apache.org/licenses/LICENSE-2.0).

## Features
* Platform independent signing of Windows executables, DLLs, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) and scripts (PowerShell, VBScript, JScript, WSF)
* Platform independent signing of Windows executables, DLLs, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension packages and scripts (PowerShell, VBScript, JScript, WSF)
* Timestamping with retries and fallback on alternative servers (RFC 3161 and Authenticode protocols supported)
* Supports multiple signatures per file, for all file types
* Extracts and embeds detached signatures to support [reproducible builds](https://reproducible-builds.org/docs/embedded-signatures/)
Expand Down
19 changes: 10 additions & 9 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<head>
<meta charset='utf-8' />
<meta http-equiv="X-UA-Compatible" content="chrome=1" />
<meta name="description" content="Jsign : Pure Java implementation of Microsoft Authenticode for signing Windows executable files, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) and scripts (PowerShell, VBScript, JScript, WSF)" />
<meta name="description" content="Jsign : Pure Java implementation of Microsoft Authenticode for signing Windows executable files, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension packages and scripts (PowerShell, VBScript, JScript, WSF)" />
<meta name="google-site-verification" content="p912kgAnTBOzVbswrU43k3FXUbPnxLHdeW6xsVcq1uU" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

Expand Down Expand Up @@ -37,9 +37,10 @@ <h2 id="project_tagline">Java implementation of Microsoft Authenticode <br class

<p>Jsign is a Java implementation of Microsoft Authenticode that lets you sign
and timestamp executable files for Windows, Microsoft Installers (MSI), Cabinet files (CAB),
Catalog files (CAT), Windows packages (APPX/MSIX) and scripts (PowerShell, VBScript, JScript, WSF).
Jsign is platform independent and provides an alternative to native tools like <em>signcode/signtool</em>
on Windows or the Mono development tools on Unix systems.</p>
Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension packages
and scripts (PowerShell, VBScript, JScript, WSF). Jsign is platform independent and provides
an alternative to native tools like <em>signcode/signtool</em> on Windows or the Mono
development tools on Unix systems.</p>

<p>Jsign comes as an easy to use task/plugin for the main build systems (Maven,
Gradle, Ant). It's especially suitable for signing executable wrappers and installers
Expand All @@ -56,7 +57,7 @@ <h2 id="project_tagline">Java implementation of Microsoft Authenticode <br class
<h3>Features</h3>

<ul>
<li>Platform independent signing of Windows executables, DLLs, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) and scripts (PowerShell, VBScript, JScript, WSF)</li>
<li>Platform independent signing of Windows executables, DLLs, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension packages and scripts (PowerShell, VBScript, JScript, WSF)</li>
<li>Timestamping with retries and fallback on alternative servers (RFC 3161 and Authenticode protocols supported)</li>
<li>Supports multiple signatures per file, for all file types</li>
<li>Extracts and embeds detached signatures to support <a href="https://reproducible-builds.org/docs/embedded-signatures/">reproducible builds</a></li>
Expand Down Expand Up @@ -132,8 +133,8 @@ <h4 class="mobile-only">Attributes</h4>
<td class="attribute">file</td>
<td class="description">
The file to be signed. The supported files are Windows executables (EXE), DLLs, Microsoft Installers (MSI),
Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) and scripts
(PowerShell, VBScript, JScript, WSF)</td>
Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension
packages and scripts (PowerShell, VBScript, JScript, WSF)</td>
<td class="required">Yes, unless a fileset is specified.</td>
</tr>
<tr>
Expand Down Expand Up @@ -441,8 +442,8 @@ <h3>Command Line Tool</h3>
<pre>
usage: jsign [OPTIONS] [FILE]...
Sign and timestamp Windows executable files, Microsoft Installers (MSI), Cabinet
files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) or scripts
(PowerShell, VBScript, JScript, WSF).
files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics
365 extension packages or scripts (PowerShell, VBScript, JScript, WSF).

-s,--keystore &lt;FILE> The keystore file, the SunPKCS11 configuration file or
the cloud keystore name
Expand Down
2 changes: 1 addition & 1 deletion jsign-cli/src/main/java/net/jsign/JsignCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ private void setOption(String key, SignerHelper helper, CommandLine cmd) {
}

private void printHelp() {
String header = "Sign and timestamp Windows executable files, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX) or scripts (PowerShell, VBScript, JScript, WSF).\n\n";
String header = "Sign and timestamp Windows executable files, Microsoft Installers (MSI), Cabinet files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics 365 extension packages or scripts (PowerShell, VBScript, JScript, WSF).\n\n";
String footer ="\n" +
"Examples:\n\n" +
" Signing with a PKCS#12 keystore and timestamping:\n\n" +
Expand Down
4 changes: 4 additions & 0 deletions jsign-core/src/main/java/net/jsign/Signable.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import net.jsign.mscab.MSCabinetFile;
import net.jsign.msi.MSIFile;
import net.jsign.appx.APPXFile;
import net.jsign.navx.NAVXFile;
import net.jsign.pe.PEFile;
import net.jsign.script.JScript;
import net.jsign.script.PowerShellScript;
Expand Down Expand Up @@ -149,6 +150,9 @@ static Signable of(File file, Charset encoding) throws IOException {
} else if (CatalogFile.isCatalogFile(file)) {
return new CatalogFile(file);

} else if (NAVXFile.isNAVXFile(file)) {
return new NAVXFile(file);

} else if (file.getName().toLowerCase().endsWith(".ps1")
|| file.getName().toLowerCase().endsWith(".psd1")
|| file.getName().toLowerCase().endsWith(".psm1")) {
Expand Down
190 changes: 190 additions & 0 deletions jsign-core/src/main/java/net/jsign/navx/NAVXFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
/**
* Copyright 2023 Emmanuel Bourg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.jsign.navx;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1Object;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.cms.CMSProcessable;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;

import net.jsign.DigestAlgorithm;
import net.jsign.Signable;
import net.jsign.asn1.authenticode.AuthenticodeObjectIdentifiers;
import net.jsign.asn1.authenticode.SpcAttributeTypeAndOptionalValue;
import net.jsign.asn1.authenticode.SpcIndirectDataContent;
import net.jsign.asn1.authenticode.SpcSipInfo;
import net.jsign.asn1.authenticode.SpcUuid;

import static net.jsign.ChannelUtils.*;

/**
* Microsoft Dynamics 365 extension package (NAVX)
*
* @author Emmanuel Bourg
* @since 5.1
*/
public class NAVXFile implements Signable {

/** The channel used for in-memory signing */
private final SeekableByteChannel channel;

/** The underlying file */
private File file;

/** The file header */
private final NAVXHeader header = new NAVXHeader();

/**
* Tells if the specified file is a NAVX file.
*
* @param file the file to check
* @return <code>true</code> if the file is a NAVX file, <code>false</code> otherwise
* @throws IOException if an I/O error occurs
*/
public static boolean isNAVXFile(File file) throws IOException {
if (file.length() < NAVXHeader.SIZE) {
return false;
}

// read the signature
try (SeekableByteChannel channel = Files.newByteChannel(file.toPath(), StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
return buffer.getInt() == NAVXHeader.SIGNATURE;
}
}

/**
* Create a NAVXFile from the specified file.
*
* @param file the file to open
* @throws IOException if an I/O error occurs
*/
public NAVXFile(File file) throws IOException {
this(Files.newByteChannel(file.toPath(), StandardOpenOption.READ, StandardOpenOption.WRITE));
}

/**
* Create a NAVXFile from the specified channel.
*
* @param channel the channel to read the file from
* @throws IOException if an I/O error occurs
*/
public NAVXFile(SeekableByteChannel channel) throws IOException {
this.channel = channel;

channel.position(0);
header.read(channel);

if (header.contentSize + NAVXHeader.SIZE > channel.size()) {
throw new IOException("NAVX file is corrupt: invalid size in the header");
}
}

@Override
public byte[] computeDigest(DigestAlgorithm digestAlgorithm) throws IOException {
MessageDigest digest = digestAlgorithm.getMessageDigest();
updateDigest(channel, digest, 0, getSignatureOffset());
return digest.digest();
}

@Override
public ASN1Object createIndirectData(DigestAlgorithm digestAlgorithm) throws IOException {
AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(digestAlgorithm.oid, DERNull.INSTANCE);
DigestInfo digestInfo = new DigestInfo(algorithmIdentifier, computeDigest(digestAlgorithm));

SpcUuid uuid = new SpcUuid("C8EAD600-FC04-0000-5828-F30400000000");
SpcAttributeTypeAndOptionalValue data = new SpcAttributeTypeAndOptionalValue(AuthenticodeObjectIdentifiers.SPC_SIPINFO_OBJID, new SpcSipInfo(1, uuid));

return new SpcIndirectDataContent(data, digestInfo);
}

private int getSignatureOffset() {
return NAVXHeader.SIZE + header.contentSize;
}

@Override
public List<CMSSignedData> getSignatures() throws IOException {
List<CMSSignedData> signatures = new ArrayList<>();

try {
channel.position(getSignatureOffset());
NAVXSignatureBlock signatureBlock = new NAVXSignatureBlock();
signatureBlock.read(channel);
CMSSignedData signedData = signatureBlock.signedData;
if (signedData != null) {
signatures.add(signedData);

// look for nested signatures
SignerInformation signerInformation = signedData.getSignerInfos().getSigners().iterator().next();
AttributeTable unsignedAttributes = signerInformation.getUnsignedAttributes();
if (unsignedAttributes != null) {
Attribute nestedSignatures = unsignedAttributes.get(AuthenticodeObjectIdentifiers.SPC_NESTED_SIGNATURE_OBJID);
if (nestedSignatures != null) {
for (ASN1Encodable nestedSignature : nestedSignatures.getAttrValues()) {
signatures.add(new CMSSignedData((CMSProcessable) null, ContentInfo.getInstance(nestedSignature)));
}
}
}
}
} catch (UnsupportedOperationException e) {
// unsupported type, just skip
} catch (Exception e) {
e.printStackTrace();
}

return signatures;
}

@Override
public void setSignature(CMSSignedData signature) throws IOException {
NAVXSignatureBlock signatureBlock = new NAVXSignatureBlock();
signatureBlock.signedData = signature;

channel.position(getSignatureOffset());
signatureBlock.write(channel);
channel.truncate(channel.position());
}

@Override
public void save() throws IOException {
}

@Override
public void close() throws IOException {
channel.close();
}
}
61 changes: 61 additions & 0 deletions jsign-core/src/main/java/net/jsign/navx/NAVXHeader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/**
* Copyright 2023 Emmanuel Bourg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package net.jsign.navx;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ReadableByteChannel;

import static java.nio.ByteOrder.*;

/**
* NAVX file header
*
* <pre>
* signature 4 bytes (NAVX)
* unknown 24 bytes
* content size 4 bytes
* unknown 4 bytes
* signature 4 bytes (NAVX)
* </pre>
*
* @since 5.1
*/
class NAVXHeader {

public static final int SIGNATURE = 0x5856414E; // NAVX;
public static final int SIZE = 40;

public int contentSize;

public void read(ReadableByteChannel channel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(SIZE).order(LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();

int signature = buffer.getInt();
if (signature != SIGNATURE) {
throw new IOException("Invalid NAVX header signature");
}
contentSize = buffer.getInt(28);

signature = buffer.getInt(36);
if (signature != SIGNATURE) {
throw new IOException("Invalid NAVX header signature");
}
}
}
Loading

0 comments on commit 0dc04dc

Please sign in to comment.