Skip to content

Commit

Permalink
Preliminary support for multiple commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ebourg committed Apr 15, 2024
1 parent 620616e commit c31c942
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 29 deletions.
8 changes: 6 additions & 2 deletions jsign-ant/src/main/java/net/jsign/JsignTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ public class JsignTask extends Task {
/** The set of files to be signed. */
private FileSet fileset;

/** The operation to execute */
private String command = "sign";

/** The program name embedded in the signature. */
private String name;

Expand Down Expand Up @@ -169,6 +172,7 @@ public void execute() throws BuildException {
SignerHelper helper = new SignerHelper(new AntConsole(this), "attribute");
helper.setBaseDir(getProject().getBaseDir());

helper.command(command);
helper.name(name);
helper.url(url);
helper.alg(algorithm);
Expand All @@ -189,10 +193,10 @@ public void execute() throws BuildException {

if (fileset != null) {
for(String fileElement : fileset.getDirectoryScanner().getIncludedFiles()) {
helper.sign(new File(fileset.getDir(), fileElement));
helper.execute(new File(fileset.getDir(), fileElement));
}
} else {
helper.sign(file);
helper.execute(file);
}
} catch (Exception e) {
throw new BuildException(e.getMessage(), e, getLocation());
Expand Down
44 changes: 40 additions & 4 deletions jsign-cli/src/main/java/net/jsign/JsignCLI.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
package net.jsign;

import java.io.File;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
Expand Down Expand Up @@ -48,10 +53,11 @@ public static void main(String... args) {
}
}

private final Options options;
/** The options for each operation */
private final Map<String, Options> options = new LinkedHashMap<>();

JsignCLI() {
options = new Options();
Options options = new Options();
options.addOption(Option.builder("s").hasArg().longOpt(PARAM_KEYSTORE).argName("FILE").desc("The keystore file, the SunPKCS11 configuration file, the cloud keystore name, or the card/token name").type(File.class).build());
options.addOption(Option.builder().hasArg().longOpt(PARAM_STOREPASS).argName("PASSWORD").desc("The password to open the keystore").build());
options.addOption(Option.builder().hasArg().longOpt(PARAM_STORETYPE).argName("TYPE")
Expand Down Expand Up @@ -92,11 +98,24 @@ public static void main(String... args) {
options.addOption(Option.builder("e").hasArg().longOpt(PARAM_ENCODING).argName("ENCODING").desc("The encoding of the script to be signed (UTF-8 by default, or the encoding specified by the byte order mark if there is one).").build());
options.addOption(Option.builder().longOpt(PARAM_DETACHED).desc("Tells if a detached signature should be generated or reused.").build());
options.addOption(Option.builder("h").longOpt("help").desc("Print the help").build());

this.options.put("sign", options);
}

void execute(String... args) throws SignerException, ParseException {
DefaultParser parser = new DefaultParser();

String command = "sign";
if (args.length >= 1 && !args[0].startsWith("-")) {
command = args[0];
args = Arrays.copyOfRange(args, 1, args.length);
}

Options options = this.options.get(command);
if (options == null) {
throw new ParseException("Unknown command '" + command + "'");
}

CommandLine cmd = parser.parse(options, args);

if (cmd.hasOption("help") || args.length == 0) {
Expand All @@ -105,6 +124,7 @@ void execute(String... args) throws SignerException, ParseException {
}

SignerHelper helper = new SignerHelper(new StdOutConsole(1), "option");
helper.command(command);

setOption(PARAM_KEYSTORE, helper, cmd);
setOption(PARAM_STOREPASS, helper, cmd);
Expand Down Expand Up @@ -132,7 +152,7 @@ void execute(String... args) throws SignerException, ParseException {
}

for (String filename : cmd.getArgList()) {
helper.sign(new File(filename));
helper.execute(new File(filename));
}
}

Expand All @@ -156,7 +176,23 @@ private void printHelp() {
formatter.setOptionComparator(null);
formatter.setWidth(85);
formatter.setDescPadding(1);
formatter.printHelp(getProgramName() + " [OPTIONS] [FILE]...", header, options, footer);

PrintWriter out = new PrintWriter(System.out);
formatter.printUsage(out, formatter.getWidth(), getProgramName() + " [COMMAND] [OPTIONS] [FILE]...");
out.println();
formatter.printWrapped(out, formatter.getWidth(), header);

out.println("commands: " + options.keySet().stream().map(s -> "sign".equals(s) ? s + " (default)" : s).collect(Collectors.joining(", ")));

for (String command : options.keySet()) {
if (!options.get(command).getOptions().isEmpty()) {
out.println();
out.println(command + ":");
formatter.printOptions(out, formatter.getWidth(), options.get(command), formatter.getLeftPadding(), formatter.getDescPadding());
}
}
formatter.printWrapped(out, formatter.getWidth(), footer);
out.flush();
}

private static String getProgramName() {
Expand Down
12 changes: 11 additions & 1 deletion jsign-cli/src/test/java/net/jsign/JsignCLITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public void testPrintHelp() {

@Test(expected = SignerException.class)
public void testMissingKeyStore() throws Exception {
cli.execute("" + targetFile);
cli.execute("sign", "" + targetFile);
}

@Test(expected = IllegalArgumentException.class)
Expand Down Expand Up @@ -533,4 +533,14 @@ public void testExplicitCertificateChainOnlyOnSingleEntryWhenFirst() throws Exce
assertEquals("exception message", "The certificate chain in target/test-classes/keystores/jsign-test-certificate-partial-chain-reversed.pem does not match the chain from the keystore", e.getMessage().replace('\\', '/'));
}
}

@Test
public void testUnknownCommand() throws Exception {
try {
cli.execute("unsign", "" + targetFile);
fail("No exception thrown");
} catch (ParseException e) {
assertEquals("exception message", "Unknown command 'unsign'", e.getMessage());
}
}
}
22 changes: 22 additions & 0 deletions jsign-core/src/main/java/net/jsign/SignerHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
* @since 2.0
*/
class SignerHelper {
public static final String PARAM_COMMAND = "command";
public static final String PARAM_KEYSTORE = "keystore";
public static final String PARAM_STOREPASS = "storepass";
public static final String PARAM_STORETYPE = "storetype";
Expand All @@ -81,6 +82,7 @@ class SignerHelper {
/** The name used to refer to a configuration parameter */
private final String parameterName;

private String command = "sign";
private final KeyStoreBuilder ksparams;
private String alias;
private String tsaurl;
Expand All @@ -105,6 +107,11 @@ public SignerHelper(Console console, String parameterName) {
this.ksparams = new KeyStoreBuilder(parameterName);
}

public SignerHelper command(String command) {
this.command = command;
return this;
}

public SignerHelper keystore(String keystore) {
ksparams.keystore(keystore);
return this;
Expand Down Expand Up @@ -221,6 +228,7 @@ public SignerHelper param(String key, String value) {
}

switch (key) {
case PARAM_COMMAND: return command(value);
case PARAM_KEYSTORE: return keystore(value);
case PARAM_STOREPASS: return storepass(value);
case PARAM_STORETYPE: return storetype(value);
Expand Down Expand Up @@ -250,6 +258,20 @@ void setBaseDir(File basedir) {
ksparams.setBaseDir(basedir);
}

public void execute(String file) throws SignerException {
execute(ksparams.createFile(file));
}

public void execute(File file) throws SignerException {
switch (command) {
case "sign":
sign(file);
break;
default:
throw new SignerException("Unknown command '" + command + "'");
}
}

private AuthenticodeSigner build() throws SignerException {
KeyStore ks;
try {
Expand Down
49 changes: 30 additions & 19 deletions jsign-core/src/test/java/net/jsign/SignerHelperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,12 @@ public void testDetachedSignature() throws Exception {
.keypass("password");

// sign and detach
signer.sign(targetFile);
signer.execute(targetFile);

assertFalse("Signature was detached", detachedSignatureFile.exists());

signer.alg("SHA-512").detached(true);
signer.sign(targetFile);
signer.execute(targetFile);

assertTrue("Signature wasn't detached", detachedSignatureFile.exists());

Expand All @@ -71,7 +71,7 @@ public void testDetachedSignature() throws Exception {
detachedSignatureFile.renameTo(detachedSignatureFile2);

signer = new SignerHelper(new StdOutConsole(2), "parameter").detached(true);
signer.sign(targetFile2);
signer.execute(targetFile2);

assertEquals(FileUtils.checksum(targetFile, new CRC32()).getValue(), FileUtils.checksum(targetFile2, new CRC32()).getValue());
}
Expand Down Expand Up @@ -101,7 +101,7 @@ public void testDetachedSignatureWithNotPaddedFile() throws Exception {
.detached(true);

// sign and detach
signer.sign(targetFile);
signer.execute(targetFile);

assertTrue("Signature wasn't detached", detachedSignatureFile.exists());

Expand All @@ -113,7 +113,7 @@ public void testDetachedSignatureWithNotPaddedFile() throws Exception {
detachedSignatureFile.renameTo(detachedSignatureFile2);

signer = new SignerHelper(new StdOutConsole(2), "parameter").detached(true);
signer.sign(targetFile2);
signer.execute(targetFile2);

assertEquals(FileUtils.checksum(targetFile, new CRC32()).getValue(), FileUtils.checksum(targetFile2, new CRC32()).getValue());
}
Expand All @@ -131,7 +131,7 @@ public void testPasswordFromFile() throws Exception {
.keystore("target/test-classes/keystores/keystore.jks")
.keypass("file:target/test-classes/storepass.txt");

signer.sign(targetFile);
signer.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -148,7 +148,7 @@ public void testPasswordFromFileFailed() throws Exception {
.keypass("file:/path/to/missing/file");

try {
signer.sign(targetFile);
signer.execute(targetFile);
} catch (SignerException e) {
assertEquals("message", "Failed to read the keypass parameter from the file '/path/to/missing/file'", e.getMessage());
}
Expand All @@ -167,7 +167,7 @@ public void testPasswordFromEnvironment() throws Exception {
.keystore("target/test-classes/keystores/keystore.jks")
.keypass("env:STOREPASS");

signer.sign(targetFile);
signer.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -186,7 +186,7 @@ public void testPasswordFromEnvironmentFailed() throws Exception {
.keypass("env:MISSING_VAR");

try {
signer.sign(targetFile);
signer.execute(targetFile);
} catch (SignerException e) {
assertEquals("message", "Failed to read the keypass parameter, the 'MISSING_VAR' environment variable is not defined", e.getMessage());
}
Expand All @@ -207,7 +207,7 @@ public void testAWS() throws Exception {
.certfile("src/test/resources/keystores/jsign-test-certificate-full-chain.pem")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -226,7 +226,7 @@ public void testAzureKeyVault() throws Exception {
.alias("jsign")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -246,7 +246,7 @@ public void testGoogleCloud() throws Exception {
.certfile("src/test/resources/keystores/jsign-test-certificate-full-chain-reversed.pem")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -266,7 +266,7 @@ public void testDigiCertONE() throws Exception {
.alias("Tomcat-PMC-cert-2021-11")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -286,7 +286,7 @@ public void testESigner() throws Exception {
.keypass("RDXYgV9qju+6/7GnMf1vCbKexXVJmUVr+86Wq/8aIGg=")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -306,7 +306,7 @@ public void testOracleCloud() throws Exception {
.certfile("src/test/resources/keystores/jsign-test-certificate-full-chain.pem")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -328,7 +328,7 @@ public void testPIV() throws Exception {
.certfile("src/test/resources/keystores/jsign-test-certificate-full-chain.pem")
.alg("SHA-256");

helper.sign(targetFile);
helper.execute(targetFile);

SignatureAssert.assertSigned(new PEFile(targetFile), SHA256);
}
Expand All @@ -346,7 +346,7 @@ public void testSignWithMismatchedKeyAlgorithms() throws Exception {
.certfile("target/test-classes/keystores/jsign-test-certificate-full-chain.pem");

try {
signer.sign(targetFile);
signer.execute(targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("message", "Signature verification failed, the private key doesn't match the certificate", e.getCause().getMessage());
Expand All @@ -368,7 +368,7 @@ public void testSignWithMismatchedKeyLengths() throws Exception {
.certfile("target/test-classes/keystores/jsign-root-ca.pem");

try {
signer.sign(targetFile);
signer.execute(targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("message", "Signature verification failed, the certificate is a root or intermediate CA certificate (CN=Jsign Root Certificate Authority 2022)", e.getCause().getMessage());
Expand All @@ -390,7 +390,7 @@ public void testSignWithMismatchedRSAKeys() throws Exception {
.certfile("target/test-classes/keystores/jsign-test-certificate-partial-chain.pem");

try {
signer.sign(targetFile);
signer.execute(targetFile);
fail("No exception thrown");
} catch (SignerException e) {
assertEquals("message", "Signature verification failed, the certificate is a root or intermediate CA certificate (CN=Jsign Code Signing CA 2022)", e.getCause().getMessage());
Expand All @@ -410,4 +410,15 @@ public void testMissingPKCS12KeyStorePassword() {
assertEquals("message", "The keystore password must be specified", e.getMessage());
}
}

@Test
public void testUnknownCommand() {
SignerHelper signer = new SignerHelper(new StdOutConsole(2), "parameter");
signer.command("unsign");
try {
signer.execute("target/test-classes/wineyes.exe");
} catch (SignerException e) {
assertEquals("message", "Unknown command 'unsign'", e.getMessage());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public void doCall(Map<String, String> params) throws SignerException {
for (Map.Entry<String, String> param : params.entrySet()) {
helper.param(param.getKey(), param.getValue());
}
helper.sign(file);
helper.execute(file);
}

public void doCall(kotlin.Pair<String, String>... pairs) throws SignerException {
Expand Down
Loading

0 comments on commit c31c942

Please sign in to comment.