1 package org.apache.maven.plugin.jar;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.File;
23 import java.io.InputStream;
24 import java.util.ArrayList;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.StringTokenizer;
28
29 import org.apache.commons.lang.SystemUtils;
30 import org.apache.maven.plugin.AbstractMojo;
31 import org.apache.maven.plugin.MojoExecutionException;
32 import org.apache.maven.plugin.logging.Log;
33 import org.codehaus.plexus.util.StringUtils;
34 import org.codehaus.plexus.util.cli.CommandLineException;
35 import org.codehaus.plexus.util.cli.CommandLineUtils;
36 import org.codehaus.plexus.util.cli.Commandline;
37 import org.codehaus.plexus.util.cli.StreamConsumer;
38
39 /**
40 * Checks the signature of a signed jar using jarsigner.
41 *
42 * @author <a href="jerome@coffeebreaks.org">Jerome Lacoste</a>
43 * @version $Id: JarSignVerifyMojo.java 802529 2009-08-09 12:05:35Z bentmann $
44 * @goal sign-verify
45 * @phase package
46 * @requiresProject
47 * @todo refactor the common code with javadoc plugin
48 * @requiresDependencyResolution runtime
49 * @deprecated As of version 2.3, this goal is no longer supported in favor of the dedicated maven-jarsigner-plugin.
50 */
51 public class JarSignVerifyMojo
52 extends AbstractMojo
53 {
54 /**
55 * The working directory in which the jarsigner executable will be run.
56 *
57 * @parameter expression="${workingdir}" default-value="${basedir}"
58 * @required
59 */
60 private File workingDirectory;
61
62 /**
63 * Directory containing the generated JAR.
64 *
65 * @parameter expression="${project.build.directory}"
66 * @required
67 * @readonly
68 */
69 private File basedir;
70
71 /**
72 * Name of the generated JAR (without classifier and extension).
73 *
74 * @parameter alias="jarname" expression="${project.build.finalName}"
75 * @required
76 */
77 private String finalName;
78
79 /**
80 * Path of the signed jar. When specified, the finalName is ignored.
81 *
82 * @parameter expression="${jarpath}"
83 */
84 private File jarPath;
85
86 /**
87 * Check certificates. Requires {@link #setVerbose(boolean)}.
88 * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
89 *
90 * @parameter expression="${checkcerts}" default-value="false"
91 */
92 private boolean checkCerts;
93
94 /**
95 * Enable verbose
96 * See <a href="http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/jarsigner.html#Options">options</a>.
97 *
98 * @parameter expression="${verbose}" default-value="false"
99 */
100 private boolean verbose;
101
102 /** When <code>true</code> this will make the execute() operation fail,
103 * throwing an exception, when verifying a non signed jar.
104 * Primarily to keep backwards compatibility with existing code, and allow reusing the
105 * bean in unattended operations when set to <code>false</code>.
106 *
107 * @parameter expression="${errorWhenNotSigned}" default-value="true"
108 **/
109 private boolean errorWhenNotSigned = true;
110
111 /**
112 * Is the jar signed ? output property set by the execute call. The value will be accessible
113 * when execute() ends and if errorWhenNotSigned has been set to false.
114 **/
115 private boolean signed;
116
117 File getJarFile()
118 {
119 if ( jarPath != null )
120 {
121 return jarPath;
122 }
123 else
124 {
125 return AbstractJarMojo.getJarFile( basedir, finalName, null);
126 }
127 }
128
129 public void execute()
130 throws MojoExecutionException
131 {
132 List arguments = new ArrayList();
133
134 Commandline commandLine = new Commandline();
135
136 commandLine.setExecutable( getJarsignerPath() );
137
138 arguments.add( "-verify" );
139
140 addArgIf( arguments, this.verbose, "-verbose" );
141 addArgIf( arguments, this.checkCerts, "-certs" );
142
143 arguments.add( getJarFile() );
144
145 for ( Iterator it = arguments.iterator() ; it.hasNext() ; )
146 {
147 commandLine.createArgument().setValue( it.next().toString() );
148 }
149
150 commandLine.setWorkingDirectory( workingDirectory.getAbsolutePath() );
151
152 getLog().debug("Executing: " + commandLine );
153
154 LineMatcherStreamConsumer outConsumer = new LineMatcherStreamConsumer( "jar verified." );
155
156 final StringBuffer errBuffer = new StringBuffer();
157 StreamConsumer errConsumer = new StreamConsumer()
158 {
159 public void consumeLine(String line)
160 {
161 errBuffer.append( line );
162 getLog().warn( line );
163 }
164 };
165
166
167 try
168 {
169 int result = executeCommandLine( commandLine, null, outConsumer, errConsumer );
170
171 if ( result != 0 )
172 {
173 throw new MojoExecutionException("Result of " + commandLine
174 + " execution is: \'" + result + "\'." );
175 }
176
177 signed = outConsumer.matched;
178
179 if ( !signed && errorWhenNotSigned )
180 {
181 throw new MojoExecutionException( "Verify failed: " + outConsumer.firstOutLine );
182 }
183 }
184 catch ( CommandLineException e )
185 {
186 throw new MojoExecutionException( "command execution failed", e );
187 }
188 }
189
190 // checks if a consumed line matches
191 // also keeps track of the first consumed line.
192 class LineMatcherStreamConsumer
193 implements StreamConsumer
194 {
195 private String toMatch;
196 private boolean matched;
197 private String firstOutLine;
198
199 LineMatcherStreamConsumer( String toMatch )
200 {
201 this.toMatch = toMatch;
202 }
203
204 public void consumeLine(String line)
205 {
206 if ( firstOutLine == null )
207 {
208 firstOutLine = line;
209 }
210 matched = matched || toMatch.equals( line );
211
212 getLog().info( line );
213 }
214 }
215
216 // taken from JavadocReport then slightly refactored
217 // should probably share with other plugins that use $JAVA_HOME/bin tools
218
219 /**
220 * Get the path of jarsigner tool depending the OS.
221 *
222 * @return the path of the jarsigner tool
223 */
224 private String getJarsignerPath()
225 {
226 return getJDKCommandPath( "jarsigner", getLog() );
227 }
228
229 private static String getJDKCommandPath( String command, Log logger )
230 {
231 String path = getJDKCommandExe(command).getAbsolutePath();
232 logger.debug( command + " executable=[" + path + "]" );
233 return path;
234 }
235
236 private static File getJDKCommandExe( String command )
237 {
238 String fullCommand = command + ( SystemUtils.IS_OS_WINDOWS ? ".exe" : "" );
239
240 File exe;
241
242 // For IBM's JDK 1.2
243 if ( SystemUtils.IS_OS_AIX )
244 {
245 exe = new File( SystemUtils.getJavaHome() + "/../sh", fullCommand );
246 }
247 else if ( SystemUtils.IS_OS_MAC_OSX )
248 {
249 exe = new File( SystemUtils.getJavaHome() + "/bin", fullCommand );
250 }
251 else
252 {
253 exe = new File( SystemUtils.getJavaHome() + "/../bin", fullCommand );
254 }
255
256 return exe;
257 }
258
259
260 // Helper methods. Could/should be shared e.g. with JavadocReport
261
262 /**
263 * Convenience method to add an argument to the <code>command line</code>
264 * conditionally based on the given flag.
265 *
266 * @param arguments
267 * @param b the flag which controls if the argument is added or not.
268 * @param value the argument value to be added.
269 */
270 private void addArgIf( List arguments, boolean b, String value )
271 {
272 if ( b )
273 {
274 arguments.add( value );
275 }
276 }
277
278 /**
279 * Convenience method to add an argument to the <code>command line</code>
280 * if the the value is not null or empty.
281 * <p>
282 * Moreover, the value could be comma separated.
283 *
284 * @param arguments
285 * @param key the argument name.
286 * @param value the argument value to be added.
287 * @see #addArgIfNotEmpty(java.util.List,String,String,boolean)
288 */
289 private void addArgIfNotEmpty( List arguments, String key, String value )
290 {
291 addArgIfNotEmpty( arguments, key, value, false );
292 }
293
294 /**
295 * Convenience method to add an argument to the <code>command line</code>
296 * if the the value is not null or empty.
297 * <p>
298 * Moreover, the value could be comma separated.
299 *
300 * @param arguments
301 * @param key the argument name.
302 * @param value the argument value to be added.
303 * @param repeatKey repeat or not the key in the command line
304 */
305 private void addArgIfNotEmpty( List arguments, String key, String value, boolean repeatKey )
306 {
307 if ( !StringUtils.isEmpty( value ) )
308 {
309 arguments.add( key );
310
311 StringTokenizer token = new StringTokenizer( value, "," );
312 while ( token.hasMoreTokens() )
313 {
314 String current = token.nextToken().trim();
315
316 if ( !StringUtils.isEmpty( current ) )
317 {
318 arguments.add( current );
319
320 if ( token.hasMoreTokens() && repeatKey )
321 {
322 arguments.add( key );
323 }
324 }
325 }
326 }
327 }
328
329 //
330 // methods used for tests purposes - allow mocking and simulate automatic setters
331 //
332
333 protected int executeCommandLine( Commandline commandLine, InputStream inputStream,
334 StreamConsumer systemOut, StreamConsumer systemErr )
335 throws CommandLineException
336 {
337 return CommandLineUtils.executeCommandLine( commandLine, inputStream, systemOut, systemErr );
338 }
339
340 public void setWorkingDir( File workingDir )
341 {
342 this.workingDirectory = workingDir;
343 }
344
345 public void setBasedir( File basedir )
346 {
347 this.basedir = basedir;
348 }
349
350 // hiding for now - I don't think this is required to be seen
351 /*
352 public void setFinalName( String finalName )
353 {
354 this.finalName = finalName;
355 }
356 */
357
358 public void setJarPath( File jarPath )
359 {
360 this.jarPath = jarPath;
361 }
362
363 public void setCheckCerts( boolean checkCerts )
364 {
365 this.checkCerts = checkCerts;
366 }
367
368 public void setVerbose( boolean verbose )
369 {
370 this.verbose = verbose;
371 }
372
373 /**
374 * Is the JAR file signed ? Output property set by the {@link #execute()} call.
375 *
376 * @return <code>true</code> if the jar was signed, <code>false</code> otherwise.
377 */
378 public boolean isSigned()
379 {
380 return signed;
381 }
382
383 /**
384 * Sets a boolean that is to determine if an exception should be thrown when
385 * the JAR file being verified is unsigned. If you just what to check if a
386 * JAR is unsigned and then act on the result, then you probably want to
387 * set this to <code>true</code>.
388 */
389 public void setErrorWhenNotSigned( boolean errorWhenNotSigned )
390 {
391 this.errorWhenNotSigned = errorWhenNotSigned;
392 }
393 }