diff --git a/README.md b/README.md
index 64621d93..9cfe567f 100644
--- a/README.md
+++ b/README.md
@@ -57,6 +57,7 @@ See https://ebourg.github.io/jsign for more information.
* The Oracle Cloud signing service has been integrated
* Signing of NuGet packages has been implemented (contributed by Sebastian Stamm)
* The intermediate certificates are downloaded if missing from the keystore or the certificate chain file
+* File list files prefixed with `@` are now supported with the command line tool to sign multiple files
* Jsign now checks if the certificate subject matches the app manifest publisher before signing APPX/MSIX packages
* The `extract` command has been added to extract the signature from a signed file, in DER or PEM format
* The `remove` command has been added to remove the signature from a signed file
diff --git a/docs/index.html b/docs/index.html
index 93585f2b..660b4e21 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -482,7 +482,7 @@
Command Line Tool
The parameters expected are the same as those used by the Ant task:
- usage: jsign [OPTIONS] [FILE]...
+ usage: jsign [OPTIONS] [FILE] [@FILELIST]...
Sign and timestamp Windows executable files, Microsoft Installers (MSI), Cabinet
files (CAB), Catalog files (CAT), Windows packages (APPX/MSIX), Microsoft Dynamics
365 extension packages, NuGet packages and scripts (PowerShell, VBScript, JScript, WSF).
@@ -534,6 +534,8 @@ Command Line Tool
-h,--help Print the help
+After the options Jsign accepts one or more files to sign as arguments. If a filename starts with @ it is considered
+as a text file containing a list of files to sign, one per line.
Examples
diff --git a/jsign-cli/src/main/java/net/jsign/JsignCLI.java b/jsign-cli/src/main/java/net/jsign/JsignCLI.java
index 089329d5..c330d297 100644
--- a/jsign-cli/src/main/java/net/jsign/JsignCLI.java
+++ b/jsign-cli/src/main/java/net/jsign/JsignCLI.java
@@ -17,9 +17,14 @@
package net.jsign;
import java.io.File;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.util.Arrays;
+import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -162,8 +167,25 @@ void execute(String... args) throws SignerException, ParseException {
throw new SignerException("No file specified");
}
- for (String filename : cmd.getArgList()) {
- helper.execute(new File(filename));
+ for (String arg : cmd.getArgList()) {
+ for (String filename : expand(arg)) {
+ helper.execute(new File(filename));
+ }
+ }
+ }
+
+ /**
+ * Expands filenames starting with @ to a list of filenames.
+ */
+ private List expand(String filename) {
+ if (filename.startsWith("@")) {
+ try {
+ return Files.readAllLines(Paths.get(filename.substring(1)));
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Failed to read the file list: " + filename.substring(1), e);
+ }
+ } else {
+ return Collections.singletonList(filename);
}
}
@@ -189,7 +211,7 @@ private void printHelp() {
formatter.setDescPadding(1);
PrintWriter out = new PrintWriter(System.out);
- formatter.printUsage(out, formatter.getWidth(), getProgramName() + " [COMMAND] [OPTIONS] [FILE]...");
+ formatter.printUsage(out, formatter.getWidth(), getProgramName() + " [COMMAND] [OPTIONS] [FILE] [@FILELIST]...");
out.println();
formatter.printWrapped(out, formatter.getWidth(), header);
diff --git a/jsign-cli/src/test/java/net/jsign/JsignCLITest.java b/jsign-cli/src/test/java/net/jsign/JsignCLITest.java
index 89d77b35..d922412d 100644
--- a/jsign-cli/src/test/java/net/jsign/JsignCLITest.java
+++ b/jsign-cli/src/test/java/net/jsign/JsignCLITest.java
@@ -18,9 +18,11 @@
import java.io.File;
import java.net.ProxySelector;
+import java.nio.file.Files;
import java.security.InvalidParameterException;
import java.security.Permission;
import java.security.ProviderException;
+import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.handler.codec.http.HttpRequest;
@@ -216,6 +218,20 @@ public void testSigningMultipleFiles() throws Exception {
}
}
+ @Test
+ public void testSigningMultipleFilesWithListFile() throws Exception {
+ File listFile = new File("target/test-classes/files.txt");
+ Files.write(listFile.toPath(), Arrays.asList(targetFile.getAbsolutePath(), targetFile.getAbsolutePath()));
+
+ cli.execute("--name=WinEyes", "--url=http://www.steelblue.com/WinEyes", "--alg=SHA-1", "--keystore=target/test-classes/keystores/" + keystore, "--keypass=" + keypass, "@" + listFile);
+
+ assertTrue("The file " + targetFile + " wasn't changed", SOURCE_FILE_CRC32 != FileUtils.checksumCRC32(targetFile));
+
+ try (PEFile peFile = new PEFile(targetFile)) {
+ SignatureAssert.assertSigned(peFile, SHA1, SHA1);
+ }
+ }
+
@Test
public void testSigningPowerShell() throws Exception {
File sourceFile = new File("target/test-classes/hello-world.ps1");
diff --git a/jsign/src/deb/data/usr/share/man/man1/jsign.1 b/jsign/src/deb/data/usr/share/man/man1/jsign.1
index a66ede2f..cba07ac1 100644
--- a/jsign/src/deb/data/usr/share/man/man1/jsign.1
+++ b/jsign/src/deb/data/usr/share/man/man1/jsign.1
@@ -6,7 +6,7 @@ jsign \- sign and timestamp executable files for Windows, Microsoft Installers (
.SH SYNOPSIS
.B jsign
-[OPTIONS] [FILE]...
+[OPTIONS] [FILE] [@FILELIST]...
.SH DESCRIPTION
Jsign is a Java implementation of Microsoft Authenticode that lets you sign
@@ -178,6 +178,9 @@ If the signature exists it is attached to the file, replacing any existing signa
.B -h, --help
Print the help
+After the options Jsign accepts one or more files to sign as arguments. If a filename starts with @ it is considered
+as a text file containing a list of files to sign, one per line.
+
.SH EXAMPLES