Author: evenisse Date: Thu Nov 17 06:26:06 2005 New Revision: 345249 URL: http://svn.apache.org/viewcvs?rev=345249&view=rev Log: PR: MNG-1130 Submitted bu Jerome Lacoste Reviewed by: Emmanuel Venisse
Add verification of signed jars Added: maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java (with props) maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java (with props) Modified: maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignMojo.java maven/plugins/trunk/maven-jar-plugin/src/site/apt/howto.apt maven/plugins/trunk/maven-jar-plugin/src/site/apt/introduction.apt Modified: maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignMojo.java URL: http://svn.apache.org/viewcvs/maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignMojo.java?rev=345249&r1=345248&r2=345249&view=diff ============================================================================== --- maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignMojo.java (original) +++ maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignMojo.java Thu Nov 17 06:26:06 2005 @@ -75,7 +75,7 @@ * @parameter alias="jarpath" * @required */ - private String jarPath; + private File jarPath; /** * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>. @@ -131,6 +131,15 @@ private String alias; /** + * Automatically verify a jar after signing it. + * + * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>. + * + * @parameter expression="${verify}" default-value="false" + */ + private boolean verify; + + /** * Enable verbose * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>. * @@ -142,6 +151,27 @@ throws MojoExecutionException { + signJar(); + + if ( verify ) { + JarSignVerifyMojo verify = new JarSignVerifyMojo(); + verify.setWorkingDir( workingDirectory ); + verify.setBasedir( basedir ); + verify.setJarPath( getJarFile() ); + verify.setVerbose( verbose ); + verify.execute(); + } + } + + File getJarFile() { + if ( jarPath != null ) { + return jarPath; + } else { + return AbstractJarMojo.getJarFile( basedir, finalName, null); + } + } + + void signJar() throws MojoExecutionException { List arguments = new ArrayList(); Commandline commandLine = new Commandline(); @@ -160,14 +190,7 @@ addArgIfNotEmpty( arguments, "-storetype", this.type ); addArgIfNotEmpty( arguments, "-sigfile", this.sigfile ); - if ( jarPath != null ) - { - arguments.add( new File( jarPath ) ); - } - else - { - arguments.add( AbstractJarMojo.getJarFile( basedir, finalName, null ) ); - } + arguments.add( getJarFile() ); addArgIf( arguments, alias != null, this.alias ); @@ -214,8 +237,7 @@ if ( result != 0 ) { - throw new MojoExecutionException( "Result of " + commandLine + " execution is: \'" + result + "\': " - + errBuffer.toString() + "." ); + throw new MojoExecutionException( "Result of " + commandLine + " execution is: \'" + result + "\'." ); } } catch ( CommandLineException e ) @@ -398,7 +420,7 @@ } */ - public void setJarPath( String jarPath ) + public void setJarPath( File jarPath ) { this.jarPath = jarPath; } @@ -421,5 +443,9 @@ public void setVerbose( boolean verbose ) { this.verbose = verbose; + } + + public void setVerify( boolean verify ) { + this.verify = verify; } } Added: maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java URL: http://svn.apache.org/viewcvs/maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java?rev=345249&view=auto ============================================================================== --- maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java (added) +++ maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java Thu Nov 17 06:26:06 2005 @@ -0,0 +1,346 @@ +package org.apache.maven.plugin.jar; + +/* + * Copyright 2001-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. + */ + +import org.apache.maven.plugin.logging.Log; +import org.apache.commons.lang.SystemUtils; + +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; + +import org.codehaus.plexus.util.cli.CommandLineException; +import org.codehaus.plexus.util.cli.CommandLineUtils; +import org.codehaus.plexus.util.cli.Commandline; +import org.codehaus.plexus.util.cli.DefaultConsumer; +import org.codehaus.plexus.util.cli.StreamConsumer; + +import org.codehaus.plexus.util.StringUtils; + +import java.io.File; +import java.io.InputStream; +import java.util.List; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.ArrayList; + +/** + * Checks the signature of a signed jar using jarsigner. + * + * @author <a href="[EMAIL PROTECTED]">Jerome Lacoste</a> + * @version $Id$ + * @goal sign-verify + * @phase package + * @requiresProject + * @todo refactor the common code with javadoc plugin + */ +public class JarSignVerifyMojo + extends AbstractMojo +{ + /** + * @parameter expression="${workingdir}" default-value="${basedir}" + * @required + */ + private File workingDirectory; + + /** + * Directory containing the generated JAR. + * + * @parameter expression="${project.build.directory}" + * @required + * @readonly + */ + private File basedir; + + /** + * Name of the generated JAR (without classifier and extension). + * + * @parameter alias="jarname" expression="${project.build.finalName}" + * @required + */ + private String finalName; + + /** + * Path of the jar to sign. When specified, the finalName is ignored. + * + * @parameter expression="${jarpath}" + */ + private File jarPath; + + /** + * Check certificates. Requires [EMAIL PROTECTED] #setVerbose()}. + * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>. + * + * @parameter expression="${checkcerts}" default-value="false" + */ + private boolean checkCerts; + + /** + * Enable verbose + * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>. + * + * @parameter expression="${verbose}" default-value="false" + */ + private boolean verbose; + + File getJarFile() { + if ( jarPath != null ) { + return jarPath; + } else { + return AbstractJarMojo.getJarFile( basedir, finalName, null); + } + } + + public void execute() throws MojoExecutionException { + + List arguments = new ArrayList(); + + Commandline commandLine = new Commandline(); + + commandLine.setExecutable( getJarsignerPath() ); + + arguments.add( "-verify" ); + + addArgIf( arguments, this.verbose, "-verbose" ); + addArgIf( arguments, this.checkCerts, "-certs" ); + + arguments.add( getJarFile() ); + + for ( Iterator it = arguments.iterator() ; it.hasNext() ; ) { + commandLine.createArgument().setValue( it.next().toString() ); + } + + commandLine.setWorkingDirectory( workingDirectory.getAbsolutePath() ); + + getLog().debug("Executing: " + commandLine ); + + LineMatcherStreamConsumer outConsumer = new LineMatcherStreamConsumer( "jar verified." ); + + final StringBuffer errBuffer = new StringBuffer(); + StreamConsumer errConsumer = new StreamConsumer() { + public void consumeLine(String line) + { + errBuffer.append( line ); + getLog().warn( line ); + } + }; + + + try + { + int result = executeCommandLine( commandLine, null, outConsumer, errConsumer ); + + if ( result != 0 ) + { + throw new MojoExecutionException("Result of " + commandLine + + " execution is: \'" + result + "\'." ); + } + + if (! outConsumer.matched ) { + throw new MojoExecutionException( "Verify failed: " + outConsumer.firstOutLine ); + } + } + catch ( CommandLineException e ) + { + throw new MojoExecutionException( "command execution failed", e ); + } + } + + private void createParentDirIfNecessary(final String file) { + if ( file != null) + { + final File fileDir = new File( file ).getParentFile(); + + if ( fileDir != null) { // not a relative path + boolean mkdirs = fileDir.mkdirs(); + getLog().debug("mdkirs: " + mkdirs + " " + fileDir); + } + } + } + + // checks if a consumed line matches + // also keeps track of the first consumed line. + class LineMatcherStreamConsumer implements StreamConsumer + { + private String toMatch; + private boolean matched; + private String firstOutLine; + // private String lastOutLine = ""; + + LineMatcherStreamConsumer( String toMatch ) { + this.toMatch = toMatch; + } + + public void consumeLine(String line) + { + if ( firstOutLine == null ) { + firstOutLine = line; + } + matched = matched || toMatch.equals( line ); + // lastOutLine = line; + getLog().info( line ); + } + } + + // taken from JavadocReport then slightly refactored + // should probably share with other plugins that use $JAVA_HOME/bin tools + + /** + * Get the path of jarsigner tool depending the OS. + * + * @return the path of the jarsigner tool + */ + private String getJarsignerPath() + { + return getJDKCommandPath( "jarsigner", getLog() ); + } + + private static String getJDKCommandPath( String command, Log logger ) { + String path = getJDKCommandExe(command).getAbsolutePath(); + logger.debug( command + " executable=[" + path + "]" ); + return path; + } + + private static File getJDKCommandExe( String command ) + { + String fullCommand = command + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" ); + + File exe; + + // For IBM's JDK 1.2 + if ( SystemUtils.IS_OS_AIX ) + { + exe = new File( SystemUtils.getJavaHome() + "/../sh", fullCommand ); + } + else if ( SystemUtils.IS_OS_MAC_OSX ) + { + exe = new File( SystemUtils.getJavaHome() + "/bin", fullCommand ); + } + else + { + exe = new File( SystemUtils.getJavaHome() + "/../bin", fullCommand ); + } + + return exe; + } + + + // Helper methods. Could/should be shared e.g. with JavadocReport + + /** + * Convenience method to add an argument to the <code>command line</code> + * conditionally based on the given flag. + * + * @param arguments + * @param b the flag which controls if the argument is added or not. + * @param value the argument value to be added. + */ + private void addArgIf( List arguments, boolean b, String value ) + { + if ( b ) + { + arguments.add( value ); + } + } + + /** + * Convenience method to add an argument to the <code>command line</code> + * if the the value is not null or empty. + * <p> + * Moreover, the value could be comma separated. + * + * @param arguments + * @param key the argument name. + * @param value the argument value to be added. + * @see #addArgIfNotEmpty(java.util.List,String,String,boolean) + */ + private void addArgIfNotEmpty( List arguments, String key, String value ) + { + addArgIfNotEmpty( arguments, key, value, false ); + } + + /** + * Convenience method to add an argument to the <code>command line</code> + * if the the value is not null or empty. + * <p> + * Moreover, the value could be comma separated. + * + * @param arguments + * @param key the argument name. + * @param value the argument value to be added. + * @param repeatKey repeat or not the key in the command line + */ + private void addArgIfNotEmpty( List arguments, String key, String value, boolean repeatKey ) + { + if ( !StringUtils.isEmpty( value ) ) + { + arguments.add( key ); + + StringTokenizer token = new StringTokenizer( value, "," ); + while ( token.hasMoreTokens() ) + { + String current = token.nextToken().trim(); + + if ( !StringUtils.isEmpty( current ) ) + { + arguments.add( current ); + + if ( token.hasMoreTokens() && repeatKey ) + { + arguments.add( key ); + } + } + } + } + } + + // + // methods used for tests purposes - allow mocking and simulate automatic setters + // + + protected int executeCommandLine( Commandline commandLine, InputStream inputStream, + StreamConsumer systemOut, StreamConsumer systemErr ) + throws CommandLineException { + return CommandLineUtils.executeCommandLine( commandLine, inputStream, systemOut, systemErr ); + } + + public void setWorkingDir( File workingDir ) { + this.workingDirectory = workingDir; + } + + public void setBasedir( File basedir ) { + this.basedir = basedir; + } + + // hiding for now - I don't think this is required to be seen + /* + public void setFinalName( String finalName ) { + this.finalName = finalName; + } + */ + + public void setJarPath( File jarPath ) { + this.jarPath = jarPath; + } + + public void setCheckCerts( boolean checkCerts) { + this.checkCerts = checkCerts; + } + + public void setVerbose( boolean verbose ) { + this.verbose = verbose; + } +} Propchange: maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: maven/plugins/trunk/maven-jar-plugin/src/main/java/org/apache/maven/plugin/jar/JarSignVerifyMojo.java ------------------------------------------------------------------------------ svn:keywords = "Author Date Id Revision" Modified: maven/plugins/trunk/maven-jar-plugin/src/site/apt/howto.apt URL: http://svn.apache.org/viewcvs/maven/plugins/trunk/maven-jar-plugin/src/site/apt/howto.apt?rev=345249&r1=345248&r2=345249&view=diff ============================================================================== --- maven/plugins/trunk/maven-jar-plugin/src/site/apt/howto.apt (original) +++ maven/plugins/trunk/maven-jar-plugin/src/site/apt/howto.apt Thu Nov 17 06:26:06 2005 @@ -4,7 +4,7 @@ How to use - Brief examples on how to use the jar:jar and jar:sign goals. + Brief examples on how to use the jar:jar, jar:sign and jar:sign-verify goals. * How to use jar:jar @@ -24,6 +24,8 @@ If you need to sign a jar, when using the 'jar' packaging, you just need to configure the sign goal appropriately for the signing to occur automatically during the package phase. + Note that you can automatically verify a jar after signing it. + ------------------- <project> ... @@ -46,6 +48,7 @@ <alias>youralias</alias> <storepass>yourstorepassword</storepass> <!--signedjar>${project.build.directory}/signed/${project.build.finalName}.jar</signedjar--> + <verify>true</verify> </configuration> </plugin> </plugins> @@ -57,6 +60,12 @@ ------------------- m2 jar:sign -Dkeystore=/path/to/your/keystore -Dstorepass=yourstorepassword -Dalias=youralias +------------------- + +* How to use jar:sign-verify specifying parameters on the command line + +------------------- + m2 jar:sign-verify [-Djarpath=/path/to/your/signedjar] [-Dverbose=true [-Dcheckcerts=true] ] ------------------- For full documentation, click {{{index.html}here}}. Modified: maven/plugins/trunk/maven-jar-plugin/src/site/apt/introduction.apt URL: http://svn.apache.org/viewcvs/maven/plugins/trunk/maven-jar-plugin/src/site/apt/introduction.apt?rev=345249&r1=345248&r2=345249&view=diff ============================================================================== --- maven/plugins/trunk/maven-jar-plugin/src/site/apt/introduction.apt (original) +++ maven/plugins/trunk/maven-jar-plugin/src/site/apt/introduction.apt Thu Nov 17 06:26:06 2005 @@ -7,7 +7,7 @@ This plugin provides the capability to manipulate jars. Currently it can create jars for your project sources or tests classes. To achieve this use "jar:jar" or "jar:test-jar". - It also provides the capability to sign a jar file with the goal "jar:sign". + It also provides the capability to sign and verify a signed jar file with the goals "jar:sign" and "jar:sign-verify". To learn how to use the plugin, click {{{howto.html}here}}. Added: maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java URL: http://svn.apache.org/viewcvs/maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java?rev=345249&view=auto ============================================================================== --- maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java (added) +++ maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java Thu Nov 17 06:26:06 2005 @@ -0,0 +1,204 @@ +package org.apache.maven.plugin.jar; + +/* + * Copyright 2005 The Codehaus. + * + * 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. + */ + +import org.apache.maven.plugin.MojoExecutionException; + +import org.codehaus.plexus.util.StringUtils; +import org.codehaus.plexus.util.cli.Commandline; +import org.codehaus.plexus.util.cli.CommandLineException; +import org.codehaus.plexus.util.cli.StreamConsumer; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import java.io.File; +import java.io.InputStream; +import java.io.IOException; + +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * These unit tests only check whether the generated command lines are correct. + * Really running the command would mean checking the results, which is too painful and not really a unit test. + * It would probably require to 'jarsigner -verify' the resulting signed jar and I believe it would make the code + * too complex with very few benefits. + * + * @author Jerome Lacoste <[EMAIL PROTECTED]> + * @version $Id$ + */ +public class JarSignVerifyMojoTest extends TestCase +{ + private MockJarSignVerifyMojo mojo; + + static class MockJarSignVerifyMojo extends JarSignVerifyMojo { + public int executeResult; + public List commandLines = new ArrayList(); + public String failureMsg; + public Map systemProperties = new HashMap(); + public String lastOutLine; + + protected int executeCommandLine( Commandline commandLine, InputStream inputStream, + StreamConsumer systemOut, StreamConsumer systemErr ) + throws CommandLineException + { + commandLines.add( commandLine ); + if ( failureMsg != null ) { + throw new CommandLineException( failureMsg ) ; + } + if ( lastOutLine != null ) { + systemOut.consumeLine( lastOutLine ); + } + return executeResult; + } + + protected String getSystemProperty( String key ) { + return (String) systemProperties.get( key ); + } + } + + + public void setUp() throws IOException { + mojo = new MockJarSignVerifyMojo(); + mojo.executeResult = 0; + // it doesn't really matter if the paths are not cross-platform, we don't execute the command lines anyway + File basedir = new File( System.getProperty( "java.io.tmpdir" ) ) ; + mojo.setBasedir( basedir ); + mojo.setWorkingDir( basedir ); + mojo.setJarPath( new File( "/tmp/signed/file-version.jar" ) ); + } + + public void tearDown() { + mojo = null; + } + + public void testPleaseMaven() { + assertTrue( true ); + } + + /** + */ + public void testRunOK() throws MojoExecutionException + { + mojo.lastOutLine = "jar verified."; + + mojo.execute(); + + String[] expectedArguments = + { "-verify", "/tmp/signed/file-version.jar" }; + + checkMojo( expectedArguments ); + } + + /** + */ + public void testRunOKAllArguments() throws MojoExecutionException + { + mojo.lastOutLine = "jar verified."; + + mojo.setVerbose( true ); + mojo.setCheckCerts( true ); + + mojo.execute(); + + String[] expectedArguments = + { "-verify", "-verbose", "-certs", "/tmp/signed/file-version.jar" }; + + checkMojo( expectedArguments ); + } + + /** + */ + public void testRunFailureNeverHappens() + { + mojo.executeResult = 1; + + try { + mojo.execute(); + fail( "expected failure" ); + } catch ( MojoExecutionException e ) { + assertTrue( e.getMessage().startsWith( "Result of " ) ); + } + + String[] expectedArguments = + { "-verify", "/tmp/signed/file-version.jar" }; + + checkMojo( expectedArguments ); + } + + /** + */ + public void testRunFailureVerifyFailed() + { + mojo.lastOutLine = "jar is unsigned."; + + try { + mojo.execute(); + fail( "expected failure" ); + } catch ( MojoExecutionException e ) { + assertTrue( e.getMessage().startsWith( "Verify failed: jar is unsigned." ) ); + } + + String[] expectedArguments = + { "-verify", "/tmp/signed/file-version.jar" }; + + checkMojo( expectedArguments ); + } + + /** + */ + public void testRunError() + { + mojo.failureMsg = "simulated failure"; + + try { + mojo.execute(); + fail( "expected failure" ); + } catch ( MojoExecutionException e ) { + assertEquals( "command execution failed", e.getMessage() ); + } + + String[] expectedArguments = + { "-verify", "/tmp/signed/file-version.jar" }; + + checkMojo( expectedArguments ); + } + + private void checkMojo( String[] expectedCommandLineArguments ) { + assertEquals( 1, mojo.commandLines.size() ); + Commandline commandline = (Commandline) mojo.commandLines.get(0); + String[] arguments = commandline.getArguments(); + // isn't there an assertEquals for arrays? + /* + for (int i = 0; i < arguments.length; i++ ) { + System.out.println( arguments[ i ] ); + } + */ + assertEquals( "Differing number of arguments", + expectedCommandLineArguments.length, + arguments.length ); + for (int i = 0; i < arguments.length; i++ ) { + expectedCommandLineArguments[ i ] = StringUtils.replace( expectedCommandLineArguments[ i ], "/", File.separator ); + assertEquals( expectedCommandLineArguments[ i ], arguments[ i ] ); + } + } +} Propchange: maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: maven/plugins/trunk/maven-jar-plugin/src/test/java/org/apache/maven/plugin/jar/JarSignVerifyMojoTest.java ------------------------------------------------------------------------------ svn:keywords = "Author Date Id Revision"