diff --git a/src/etc/testcases/taskdefs/signjar.xml b/src/etc/testcases/taskdefs/signjar.xml index fd703476a..191532268 100644 --- a/src/etc/testcases/taskdefs/signjar.xml +++ b/src/etc/testcases/taskdefs/signjar.xml @@ -24,6 +24,13 @@ + + + + + + @@ -174,5 +181,24 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/main/org/apache/tools/ant/taskdefs/AbstractJarSignerTask.java b/src/main/org/apache/tools/ant/taskdefs/AbstractJarSignerTask.java new file mode 100644 index 000000000..7b5b6cb9d --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/AbstractJarSignerTask.java @@ -0,0 +1,277 @@ +/* + * Copyright 2000-2005 The Apache Software Foundation + * + * 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 org.apache.tools.ant.taskdefs; + +import org.apache.tools.ant.Task; +import org.apache.tools.ant.util.JavaEnvUtils; +import org.apache.tools.ant.types.FileSet; +import org.apache.tools.ant.types.RedirectorElement; + +import java.io.File; +import java.util.Vector; + +/** + * This is factored out from {@link SignJar}; a base class that can be used + * for both signing and verifying JAR files using jarsigner + */ + +public abstract class AbstractJarSignerTask extends Task { + /** + * The name of the jar file. + */ + protected File jar; + /** + * The alias of signer. + */ + protected String alias; + /** + * The url or path of keystore file. + */ + protected String keystore; + /** + * password for the store + */ + protected String storepass; + /** + * type of store,-storetype param + */ + protected String storetype; + /** + * password for the key in the store + */ + protected String keypass; + /** + * verbose output + */ + protected boolean verbose; + /** + * The maximum amount of memory to use for Jar signer + */ + protected String maxMemory; + /** + * the filesets of the jars to sign + */ + protected Vector filesets = new Vector(); + /** + * name of JDK program we are looking for + */ + protected static final String JARSIGNER_COMMAND = "jarsigner"; + /** + * redirector used to talk to the jarsigner program + */ + private RedirectorElement redirector; + /** + * error string for unit test verification: {@value} + */ + public static final String ERROR_NO_SOURCE = "jar must be set through jar attribute " + + "or nested filesets"; + + /** + * Set the maximum memory to be used by the jarsigner process + * + * @param max a string indicating the maximum memory according to the JVM + * conventions (e.g. 128m is 128 Megabytes) + */ + public void setMaxmemory(String max) { + maxMemory = max; + } + + /** + * the jar file to sign; required + * + * @param jar the jar file to sign + */ + public void setJar(final File jar) { + this.jar = jar; + } + + /** + * the alias to sign under; required + * + * @param alias the alias to sign under + */ + public void setAlias(final String alias) { + this.alias = alias; + } + + /** + * keystore location; required + * + * @param keystore the keystore location + */ + public void setKeystore(final String keystore) { + this.keystore = keystore; + } + + /** + * password for keystore integrity; required + * + * @param storepass the password for the keystore + */ + public void setStorepass(final String storepass) { + this.storepass = storepass; + } + + /** + * keystore type; optional + * + * @param storetype the keystore type + */ + public void setStoretype(final String storetype) { + this.storetype = storetype; + } + + /** + * password for private key (if different); optional + * + * @param keypass the password for the key (if different) + */ + public void setKeypass(final String keypass) { + this.keypass = keypass; + } + + /** + * Enable verbose output when signing ; optional: default false + * + * @param verbose if true enable verbose output + */ + public void setVerbose(final boolean verbose) { + this.verbose = verbose; + } + + /** + * Adds a set of files to sign + * + * @param set a set of files to sign + * @since Ant 1.4 + */ + public void addFileset(final FileSet set) { + filesets.addElement(set); + } + + /** + * init processing logic; this is retained through our execution(s) + */ + protected void beginExecution() { + + redirector = createRedirector(); + } + + /** + * any cleanup logic + */ + protected void endExecution() { + redirector = null; + } + + /** + * Create the redirector to use, if any. + * + * @return a configured RedirectorElement. + */ + private RedirectorElement createRedirector() { + RedirectorElement result = new RedirectorElement(); + StringBuffer input = new StringBuffer(storepass).append('\n'); + if (keypass != null) { + input.append(keypass).append('\n'); + } + result.setInputString(input.toString()); + result.setLogInputString(false); + return result; + } + + /** + * these are options common to signing and verifying + * @param cmd command to configure + */ + protected void setCommonOptions(final ExecTask cmd) { + if (maxMemory != null) { + cmd.createArg().setValue("-J-Xmx" + maxMemory); + } + + if (verbose) { + cmd.createArg().setValue("-verbose"); + } + } + + /** + * bind to a keystore if the attributes are there + * @param cmd command to configure + */ + protected void bindToKeystore(final ExecTask cmd) { + if (null != keystore) { + // is the keystore a file + cmd.createArg().setValue("-keystore"); + String location; + File keystoreFile = getProject().resolveFile(keystore); + if (keystoreFile.exists()) { + location = keystoreFile.getPath(); + } else { + // must be a URL - just pass as is + location = keystore; + } + cmd.createArg().setValue(location); + } + if (null != storetype) { + cmd.createArg().setValue("-storetype"); + cmd.createArg().setValue(storetype); + } + } + + /** + * create the jarsigner executable task + * @return a task set up with the executable of jarsigner, failonerror=true + * and bound to our redirector + */ + protected ExecTask createJarSigner() { + final ExecTask cmd = new ExecTask(this); + cmd.setExecutable(JavaEnvUtils.getJdkExecutable(JARSIGNER_COMMAND)); + cmd.setTaskType(JARSIGNER_COMMAND); + cmd.setFailonerror(true); + cmd.addConfiguredRedirector(redirector); + return cmd; + } + + /** + * clone our filesets vector, and patch in the jar attribute as a new + * fileset, if is defined + * @return a vector of FileSet instances + */ + protected Vector createUnifiedSources() { + Vector sources = (Vector)filesets.clone(); + if (jar != null) { + //we create a fileset with the source file. + //this lets us combine our logic for handling output directories, + //mapping etc. + FileSet sourceJar = new FileSet(); + sourceJar.setFile(jar); + sourceJar.setDir(jar.getParentFile()); + sources.add(sourceJar); + } + return sources; + } + + /** + * add a value argument to a command + * @param cmd command to manipulate + * @param value value to add + */ + protected void addValue(final ExecTask cmd, String value) { + cmd.createArg().setValue(value); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/SignJar.java b/src/main/org/apache/tools/ant/taskdefs/SignJar.java index 8869c6593..92c76e99a 100644 --- a/src/main/org/apache/tools/ant/taskdefs/SignJar.java +++ b/src/main/org/apache/tools/ant/taskdefs/SignJar.java @@ -48,40 +48,10 @@ import org.apache.tools.ant.util.FileNameMapper; * @ant.task category="java" * @since Ant 1.1 */ -public class SignJar extends Task { +public class SignJar extends AbstractJarSignerTask { private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); - /** - * The name of the jar file. - */ - protected File jar; - - /** - * The alias of signer. - */ - protected String alias; - - /** - * The url or path of keystore file. - */ - private String keystore; - - /** - * password for the store - */ - protected String storepass; - - /** - * type of store,-storetype param - */ - protected String storetype; - - /** - * password for the key in the store - */ - protected String keypass; - /** * name to a signature file */ @@ -92,11 +62,6 @@ public class SignJar extends Task { */ protected File signedjar; - /** - * verbose output - */ - protected boolean verbose; - /** * flag for internal sf signing */ @@ -112,21 +77,6 @@ public class SignJar extends Task { */ private boolean preserveLastModified; - /** - * redirector used to talk to the jarsigner program - */ - private RedirectorElement redirector; - - /** - * The maximum amount of memory to use for Jar signer - */ - private String maxMemory; - - /** - * the filesets of the jars to sign - */ - protected Vector filesets = new Vector(); - /** * Whether to assume a jar which has an appropriate .SF file in is already * signed. @@ -166,11 +116,6 @@ public class SignJar extends Task { * error string for unit test verification {@value} */ public static final String ERROR_SIGNEDJAR_AND_FILESETS = "You cannot specify the signed JAR when using filesets"; - /** - * error string for unit test verification: {@value} - */ - public static final String WARN_JAR_AND_FILESET = "nested filesets will be ignored if the jar attribute has" - + " been specified."; /** * error string for unit test verification: {@value} */ @@ -179,11 +124,6 @@ public class SignJar extends Task { * error string for unit test verification: {@value} */ public static final String ERROR_MAPPER_WITHOUT_DEST = "The destDir attribute is required if a mapper is set"; - /** - * error string for unit test verification: {@value} - */ - public static final String ERROR_NO_SOURCE = "jar must be set through jar attribute " - + "or nested filesets"; /** * error string for unit test verification: {@value} */ @@ -193,75 +133,6 @@ public class SignJar extends Task { */ public static final String ERROR_NO_STOREPASS = "storepass attribute must be set"; - /** - * name of JDK program we are looking for - */ - protected static final String JARSIGNER_COMMAND = "jarsigner"; - - /** - * Set the maximum memory to be used by the jarsigner process - * - * @param max a string indicating the maximum memory according to the JVM - * conventions (e.g. 128m is 128 Megabytes) - */ - public void setMaxmemory(String max) { - maxMemory = max; - } - - /** - * the jar file to sign; required - * - * @param jar the jar file to sign - */ - public void setJar(final File jar) { - this.jar = jar; - } - - /** - * the alias to sign under; required - * - * @param alias the alias to sign under - */ - public void setAlias(final String alias) { - this.alias = alias; - } - - /** - * keystore location; required - * - * @param keystore the keystore location - */ - public void setKeystore(final String keystore) { - this.keystore = keystore; - } - - /** - * password for keystore integrity; required - * - * @param storepass the password for the keystore - */ - public void setStorepass(final String storepass) { - this.storepass = storepass; - } - - /** - * keystore type; optional - * - * @param storetype the keystore type - */ - public void setStoretype(final String storetype) { - this.storetype = storetype; - } - - /** - * password for private key (if different); optional - * - * @param keypass the password for the key (if different) - */ - public void setKeypass(final String keypass) { - this.keypass = keypass; - } - /** * name of .SF/.DSA file; optional * @@ -280,15 +151,6 @@ public class SignJar extends Task { this.signedjar = signedjar; } - /** - * Enable verbose output when signing ; optional: default false - * - * @param verbose if true enable verbose output - */ - public void setVerbose(final boolean verbose) { - this.verbose = verbose; - } - /** * Flag to include the .SF file inside the signature; optional; default * false @@ -318,16 +180,6 @@ public class SignJar extends Task { this.lazy = lazy; } - /** - * Adds a set of files to sign - * - * @param set a set of files to sign - * @since Ant 1.4 - */ - public void addFileset(final FileSet set) { - filesets.addElement(set); - } - /** * Optionally sets the output directory to be used. * @@ -436,87 +288,66 @@ public class SignJar extends Task { throw new BuildException(ERROR_MAPPER_WITHOUT_DEST); } - //init processing logic; this is retained through our execution(s) - redirector = createRedirector(); + beginExecution(); - //special case single jar handling with signedjar attribute set - if (hasJar && hasSignedJar) { - // single jar processing - signOneJar(jar, signedjar); - //return here. - return; - } + try { + //special case single jar handling with signedjar attribute set + if (hasJar && hasSignedJar) { + // single jar processing + signOneJar(jar, signedjar); + //return here. + return; + } - //the rest of the method treats single jar like - //a nested fileset with one file - - if (hasJar) { - //we create a fileset with the source file. - //this lets us combine our logic for handling output directories, - //mapping etc. - FileSet sourceJar = new FileSet(); - sourceJar.setFile(jar); - sourceJar.setDir(jar.getParentFile()); - addFileset(sourceJar); - } - //set up our mapping policy - FileNameMapper destMapper; - if (hasMapper) { - destMapper = mapper; - } else { - //no mapper? use the identity policy - destMapper = new IdentityMapper(); - } + //the rest of the method treats single jar like + //a nested fileset with one file + + Vector sources = createUnifiedSources(); + //set up our mapping policy + FileNameMapper destMapper; + if (hasMapper) { + destMapper = mapper; + } else { + //no mapper? use the identity policy + destMapper = new IdentityMapper(); + } - //at this point the filesets are set up with lists of files, - //and the mapper is ready to map from source dirs to dest files - //now we iterate through every JAR giving source and dest names - // deal with the filesets - for (int i = 0; i < filesets.size(); i++) { - FileSet fs = (FileSet) filesets.elementAt(i); - //get all included files in a fileset - DirectoryScanner ds = fs.getDirectoryScanner(getProject()); - String[] jarFiles = ds.getIncludedFiles(); - File baseDir = fs.getDir(getProject()); - - //calculate our destination directory; it is either the destDir - //attribute, or the base dir of the fileset (for in situ updates) - File toDir = hasDestDir ? destDir : baseDir; - - //loop through all jars in the fileset - for (int j = 0; j < jarFiles.length; j++) { - String jarFile = jarFiles[j]; - //determine the destination filename via the mapper - String[] destFilenames = destMapper.mapFileName(jarFile); - if (destFilenames == null || destFilenames.length != 1) { - //we only like simple mappers. - throw new BuildException(ERROR_BAD_MAP + jarFile); + //at this point the filesets are set up with lists of files, + //and the mapper is ready to map from source dirs to dest files + //now we iterate through every JAR giving source and dest names + // deal with the filesets + for (int i = 0; i < sources.size(); i++) { + FileSet fs = (FileSet) sources.elementAt(i); + //get all included files in a fileset + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + String[] jarFiles = ds.getIncludedFiles(); + File baseDir = fs.getDir(getProject()); + + //calculate our destination directory; it is either the destDir + //attribute, or the base dir of the fileset (for in situ updates) + File toDir = hasDestDir ? destDir : baseDir; + + //loop through all jars in the fileset + for (int j = 0; j < jarFiles.length; j++) { + String jarFile = jarFiles[j]; + //determine the destination filename via the mapper + String[] destFilenames = destMapper.mapFileName(jarFile); + if (destFilenames == null || destFilenames.length != 1) { + //we only like simple mappers. + throw new BuildException(ERROR_BAD_MAP + jarFile); + } + File destFile = new File(toDir, destFilenames[0]); + File jarSource = new File(baseDir, jarFile); + signOneJar(jarSource, destFile); } - File destFile = new File(toDir, destFilenames[0]); - File jarSource = new File(baseDir, jarFile); - signOneJar(jarSource, destFile); } + } finally { + endExecution(); } } - /** - * Create the redirector to use, if any. - * - * @return a configured RedirectorElement. - */ - private RedirectorElement createRedirector() { - RedirectorElement result = new RedirectorElement(); - StringBuffer input = new StringBuffer(storepass).append('\n'); - if (keypass != null) { - input.append(keypass).append('\n'); - } - result.setInputString(input.toString()); - result.setLogInputString(false); - return result; - } - /** * Sign one jar. *

@@ -540,71 +371,47 @@ public class SignJar extends Task { } long lastModified = jarSource.lastModified(); - final ExecTask cmd = new ExecTask(this); - cmd.setExecutable(JavaEnvUtils.getJdkExecutable(JARSIGNER_COMMAND)); - cmd.setTaskType(JARSIGNER_COMMAND); + final ExecTask cmd = createJarSigner(); - if (maxMemory != null) { - cmd.createArg().setValue("-J-Xmx" + maxMemory); - } + setCommonOptions(cmd); - if (null != keystore) { - // is the keystore a file - cmd.createArg().setValue("-keystore"); - String location; - File keystoreFile = getProject().resolveFile(keystore); - if (keystoreFile.exists()) { - location = keystoreFile.getPath(); - } else { - // must be a URL - just pass as is - location = keystore; - } - cmd.createArg().setValue(location); - } - if (null != storetype) { - cmd.createArg().setValue("-storetype"); - cmd.createArg().setValue(storetype); - } + bindToKeystore(cmd); if (null != sigfile) { - cmd.createArg().setValue("-sigfile"); - cmd.createArg().setValue(sigfile); + addValue(cmd, "-sigfile"); + String value = this.sigfile; + addValue(cmd, value); } //DO NOT SET THE -signedjar OPTION if source==dest //unless you like fielding hotspot crash reports if (null != target && !jarSource.equals(target)) { - cmd.createArg().setValue("-signedjar"); - cmd.createArg().setValue(target.getPath()); - } - - if (verbose) { - cmd.createArg().setValue("-verbose"); + addValue(cmd, "-signedjar"); + addValue(cmd, target.getPath()); } if (internalsf) { - cmd.createArg().setValue("-internalsf"); + addValue(cmd, "-internalsf"); } if (sectionsonly) { - cmd.createArg().setValue("-sectionsonly"); + addValue(cmd, "-sectionsonly"); } //add -tsa operations if declared addTimestampAuthorityCommands(cmd); //JAR source is required - cmd.createArg().setValue(jarSource.getPath()); + addValue(cmd, jarSource.getPath()); //alias is required for signing - cmd.createArg().setValue(alias); + addValue(cmd, alias); log("Signing JAR: " + jarSource.getAbsolutePath() +" to " + target.getAbsolutePath() + " as " + alias); - cmd.setFailonerror(true); - cmd.addConfiguredRedirector(redirector); + cmd.execute(); // restore the lastModified attribute @@ -621,12 +428,12 @@ public class SignJar extends Task { */ private void addTimestampAuthorityCommands(final ExecTask cmd) { if(tsaurl!=null) { - cmd.createArg().setValue("-tsa"); - cmd.createArg().setValue(tsaurl); + addValue(cmd, "-tsa"); + addValue(cmd, tsaurl); } if (tsacert != null) { - cmd.createArg().setValue("-tsacert"); - cmd.createArg().setValue(tsacert); + addValue(cmd, "-tsacert"); + addValue(cmd, tsacert); } } diff --git a/src/main/org/apache/tools/ant/taskdefs/VerifyJar.java b/src/main/org/apache/tools/ant/taskdefs/VerifyJar.java new file mode 100644 index 000000000..22fd2e7a5 --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/VerifyJar.java @@ -0,0 +1,120 @@ +/* + * Copyright 2000-2005 The Apache Software Foundation + * + * 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 org.apache.tools.ant.taskdefs; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.DirectoryScanner; +import org.apache.tools.ant.types.FileSet; + +import java.util.Vector; +import java.io.File; + +/** + * JAR verification task. + * For every JAR passed in, we fork jarsigner to verify + * that it is correctly signed. This is more rigorous than just checking for + * the existence of a signature; the entire certification chain is tested + * @since Ant 1.7 + */ + +public class VerifyJar extends AbstractJarSignerTask { + /** + * no file message {@value} + */ + public static final String ERROR_NO_FILE = "Not found :"; + + /** + * certification flag + */ + private boolean certificates=false; + + /** + * Ask for certificate information to be printed + * @param certificates + */ + public void setCertificates(boolean certificates) { + this.certificates = certificates; + } + + /** + * verify our jar files + * @throws BuildException + */ + public void execute() throws BuildException { + //validation logic + final boolean hasFileset = filesets.size() > 0; + final boolean hasJar = jar != null; + + if (!hasJar && !hasFileset) { + throw new BuildException(ERROR_NO_SOURCE); + } + + beginExecution(); + try { + Vector sources = createUnifiedSources(); + for (int i = 0; i < sources.size(); i++) { + FileSet fs = (FileSet) sources.elementAt(i); + //get all included files in a fileset + DirectoryScanner ds = fs.getDirectoryScanner(getProject()); + String[] jarFiles = ds.getIncludedFiles(); + File baseDir = fs.getDir(getProject()); + + //loop through all jars in the fileset + for (int j = 0; j < jarFiles.length; j++) { + String jarFile = jarFiles[j]; + File jarSource = new File(baseDir, jarFile); + verifyOneJar(jarSource); + } + } + + } finally { + endExecution(); + } + + } + + /** + * verify a JAR. + * @param jar + * @throws BuildException if the file could not be verified + */ + private void verifyOneJar(File jar) { + if(!jar.exists()) { + throw new BuildException(ERROR_NO_FILE+jar); + } + final ExecTask cmd = createJarSigner(); + + setCommonOptions(cmd); + bindToKeystore(cmd); + + //verify special operations + addValue(cmd, "-verify"); + + if(certificates) { + addValue(cmd, "-certs"); + } + + //JAR is required + addValue(cmd, jar.getPath()); + + log("Verifying JAR: " + + jar.getAbsolutePath()); + + cmd.execute(); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index fc41144f6..94fba5dbc 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -207,6 +207,7 @@ scriptdef=org.apache.tools.ant.taskdefs.optional.script.ScriptDef ildasm=org.apache.tools.ant.taskdefs.optional.dotnet.Ildasm apt=org.apache.tools.ant.taskdefs.Apt schemavalidate=org.apache.tools.ant.taskdefs.optional.SchemaValidate +verifyjar=org.apache.tools.ant.taskdefs.VerifyJar # deprecated ant tasks (kept for back compatibility) starteam=org.apache.tools.ant.taskdefs.optional.scm.AntStarTeamCheckOut diff --git a/src/testcases/org/apache/tools/ant/taskdefs/SignJarTest.java b/src/testcases/org/apache/tools/ant/taskdefs/SignJarTest.java index c69a7283e..9e3ab34c3 100644 --- a/src/testcases/org/apache/tools/ant/taskdefs/SignJarTest.java +++ b/src/testcases/org/apache/tools/ant/taskdefs/SignJarTest.java @@ -17,14 +17,7 @@ package org.apache.tools.ant.taskdefs; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.Date; -import java.util.Vector; -import java.util.Enumeration; import org.apache.tools.ant.BuildFileTest; -import org.apache.tools.ant.Project; import org.apache.tools.ant.util.JavaEnvUtils; /** @@ -156,4 +149,24 @@ public class SignJarTest extends BuildFileTest { assertLogContaining("java.net.ConnectException"); } } + + public void testVerifyJar() { + executeTarget("testVerifyJar"); + } + + public void testVerifyNoArgs() { + expectBuildExceptionContaining("testVerifyNoArgs", + "no args", + AbstractJarSignerTask.ERROR_NO_SOURCE); + } + + public void NotestVerifyJarUnsigned() { + expectBuildException("testVerifyJarUnsigned", + "unsigned JAR file"); + } + + public void testVerifyFileset() { + executeTarget("testVerifyFileset"); + } + }