Browse Source

Factor out a base classs and add a verification task, a verification which cannot rely on return codes as a success metric, as the program returns 0 even for invalid jars. Hence the disabled test

git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@278052 13f79535-47bb-0310-9956-ffa450edef68
master
Steve Loughran 20 years ago
parent
commit
450c0f1f2f
6 changed files with 512 additions and 268 deletions
  1. +26
    -0
      src/etc/testcases/taskdefs/signjar.xml
  2. +277
    -0
      src/main/org/apache/tools/ant/taskdefs/AbstractJarSignerTask.java
  3. +68
    -261
      src/main/org/apache/tools/ant/taskdefs/SignJar.java
  4. +120
    -0
      src/main/org/apache/tools/ant/taskdefs/VerifyJar.java
  5. +1
    -0
      src/main/org/apache/tools/ant/taskdefs/defaults.properties
  6. +20
    -7
      src/testcases/org/apache/tools/ant/taskdefs/SignJarTest.java

+ 26
- 0
src/etc/testcases/taskdefs/signjar.xml View File

@@ -24,6 +24,13 @@
<signjar alias="testonly" keystore="testkeystore"
storepass="apacheant"/>
</presetdef>

<presetdef name="verify-base">
<verifyjar keystore="testkeystore"
storepass="apacheant"/>
</presetdef>


<presetdef name="sign">
<sign-base jar="${test.jar}" />
@@ -174,5 +181,24 @@
<sign tsaurl="http://localhost:0/" />
</target>
<target name="testVerifyJar" depends="basic">
<verify-base jar="${test.jar}"/>
</target>

<target name="testVerifyJarUnsigned" depends="jar">
<verify-base jar="${test.jar}"/>
</target>
<target name="testVerifyFileset" depends="basic">
<verify-base >
<fileset file="${test.jar}" />
</verify-base>
</target>

<target name="testVerifyNoArgs">
<verify-base />
</target>
</project>


+ 277
- 0
src/main/org/apache/tools/ant/taskdefs/AbstractJarSignerTask.java View File

@@ -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);
}
}

+ 68
- 261
src/main/org/apache/tools/ant/taskdefs/SignJar.java View File

@@ -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.
* <p/>
@@ -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);
}
}



+ 120
- 0
src/main/org/apache/tools/ant/taskdefs/VerifyJar.java View File

@@ -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();
}
}

+ 1
- 0
src/main/org/apache/tools/ant/taskdefs/defaults.properties View File

@@ -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


+ 20
- 7
src/testcases/org/apache/tools/ant/taskdefs/SignJarTest.java View File

@@ -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");
}

}

Loading…
Cancel
Save