KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > SignJar


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.File JavaDoc;
22 import java.io.IOException JavaDoc;
23 import java.util.Iterator JavaDoc;
24
25 import org.apache.tools.ant.BuildException;
26 import org.apache.tools.ant.Project;
27 import org.apache.tools.ant.taskdefs.condition.IsSigned;
28 import org.apache.tools.ant.types.Path;
29 import org.apache.tools.ant.types.resources.FileResource;
30 import org.apache.tools.ant.util.FileUtils;
31 import org.apache.tools.ant.util.IdentityMapper;
32 import org.apache.tools.ant.util.FileNameMapper;
33
34 /**
35  * Signs JAR or ZIP files with the javasign command line tool. The tool detailed
36  * dependency checking: files are only signed if they are not signed. The
37  * <tt>signjar</tt> attribute can point to the file to generate; if this file
38  * exists then its modification date is used as a cue as to whether to resign
39  * any JAR file.
40  *
41  * Timestamp driven signing is based on the unstable and inadequately documented
42  * information in the Java1.5 docs
43  * @see <a HREF="http://java.sun.com/j2se/1.5.0/docs/guide/security/time-of-signing-beta1.html">
44  * beta documentation</a>
45  * @ant.task category="java"
46  * @since Ant 1.1
47  */

48 public class SignJar extends AbstractJarSignerTask {
49     // CheckStyle:VisibilityModifier OFF - bc
50

51     private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
52
53     /**
54      * name to a signature file
55      */

56     protected String JavaDoc sigfile;
57
58     /**
59      * name of a single jar
60      */

61     protected File JavaDoc signedjar;
62
63     /**
64      * flag for internal sf signing
65      */

66     protected boolean internalsf;
67
68     /**
69      * sign sections only?
70      */

71     protected boolean sectionsonly;
72
73     /**
74      * flag to preserve timestamp on modified files
75      */

76     private boolean preserveLastModified;
77
78     /**
79      * Whether to assume a jar which has an appropriate .SF file in is already
80      * signed.
81      */

82     protected boolean lazy;
83
84     /**
85      * the output directory when using paths.
86      */

87     protected File JavaDoc destDir;
88
89     /**
90      * mapper for todir work
91      */

92     private FileNameMapper mapper;
93
94     /**
95      * URL for a tsa; null implies no tsa support
96      */

97     protected String JavaDoc tsaurl;
98
99     /**
100      * alias for the TSA in the keystore
101      */

102     protected String JavaDoc tsacert;
103
104     /**
105      * error string for unit test verification: {@value}
106      */

107     public static final String JavaDoc ERROR_TODIR_AND_SIGNEDJAR
108             = "'destdir' and 'signedjar' cannot both be set";
109     /**
110      * error string for unit test verification: {@value}
111      */

112     public static final String JavaDoc ERROR_TOO_MANY_MAPPERS = "Too many mappers";
113     /**
114      * error string for unit test verification {@value}
115      */

116     public static final String JavaDoc ERROR_SIGNEDJAR_AND_PATHS
117         = "You cannot specify the signed JAR when using paths or filesets";
118     /**
119      * error string for unit test verification: {@value}
120      */

121     public static final String JavaDoc ERROR_BAD_MAP = "Cannot map source file to anything sensible: ";
122     /**
123      * error string for unit test verification: {@value}
124      */

125     public static final String JavaDoc ERROR_MAPPER_WITHOUT_DEST
126         = "The destDir attribute is required if a mapper is set";
127     /**
128      * error string for unit test verification: {@value}
129      */

130     public static final String JavaDoc ERROR_NO_ALIAS = "alias attribute must be set";
131     /**
132      * error string for unit test verification: {@value}
133      */

134     public static final String JavaDoc ERROR_NO_STOREPASS = "storepass attribute must be set";
135     // CheckStyle:VisibilityModifier ON
136

137     /**
138      * name of .SF/.DSA file; optional
139      *
140      * @param sigfile the name of the .SF/.DSA file
141      */

142     public void setSigfile(final String JavaDoc sigfile) {
143         this.sigfile = sigfile;
144     }
145
146     /**
147      * name of signed JAR file; optional
148      *
149      * @param signedjar the name of the signed jar file
150      */

151     public void setSignedjar(final File JavaDoc signedjar) {
152         this.signedjar = signedjar;
153     }
154
155     /**
156      * Flag to include the .SF file inside the signature; optional; default
157      * false
158      *
159      * @param internalsf if true include the .SF file inside the signature
160      */

161     public void setInternalsf(final boolean internalsf) {
162         this.internalsf = internalsf;
163     }
164
165     /**
166      * flag to compute hash of entire manifest; optional, default false
167      *
168      * @param sectionsonly flag to compute hash of entire manifest
169      */

170     public void setSectionsonly(final boolean sectionsonly) {
171         this.sectionsonly = sectionsonly;
172     }
173
174     /**
175      * flag to control whether the presence of a signature file means a JAR is
176      * signed; optional, default false
177      *
178      * @param lazy flag to control whether the presence of a signature
179      */

180     public void setLazy(final boolean lazy) {
181         this.lazy = lazy;
182     }
183
184     /**
185      * Optionally sets the output directory to be used.
186      *
187      * @param destDir the directory in which to place signed jars
188      * @since Ant 1.7
189      */

190     public void setDestDir(File JavaDoc destDir) {
191         this.destDir = destDir;
192     }
193
194
195     /**
196      * add a mapper to determine file naming policy. Only used with toDir
197      * processing.
198      *
199      * @param newMapper the mapper to add.
200      * @since Ant 1.7
201      */

202     public void add(FileNameMapper newMapper) {
203         if (mapper != null) {
204             throw new BuildException(ERROR_TOO_MANY_MAPPERS);
205         }
206         mapper = newMapper;
207     }
208
209     /**
210      * get the active mapper; may be null
211      * @return mapper or null
212      * @since Ant 1.7
213      */

214     public FileNameMapper getMapper() {
215         return mapper;
216     }
217
218     /**
219      * get the -tsaurl url
220      * @return url or null
221      * @since Ant 1.7
222      */

223     public String JavaDoc getTsaurl() {
224         return tsaurl;
225     }
226
227     /**
228      *
229      * @param tsaurl the tsa url.
230      * @since Ant 1.7
231      */

232     public void setTsaurl(String JavaDoc tsaurl) {
233         this.tsaurl = tsaurl;
234     }
235
236     /**
237      * get the -tsacert option
238      * @since Ant 1.7
239      * @return a certificate alias or null
240      */

241     public String JavaDoc getTsacert() {
242         return tsacert;
243     }
244
245     /**
246      * set the alias in the keystore of the TSA to use;
247      * @param tsacert the cert alias.
248      */

249     public void setTsacert(String JavaDoc tsacert) {
250         this.tsacert = tsacert;
251     }
252
253     /**
254      * sign the jar(s)
255      *
256      * @throws BuildException on errors
257      */

258     public void execute() throws BuildException {
259         //validation logic
260
final boolean hasJar = jar != null;
261         final boolean hasSignedJar = signedjar != null;
262         final boolean hasDestDir = destDir != null;
263         final boolean hasMapper = mapper != null;
264
265         if (!hasJar && !hasResources()) {
266             throw new BuildException(ERROR_NO_SOURCE);
267         }
268         if (null == alias) {
269             throw new BuildException(ERROR_NO_ALIAS);
270         }
271
272         if (null == storepass) {
273             throw new BuildException(ERROR_NO_STOREPASS);
274         }
275
276         if (hasDestDir && hasSignedJar) {
277             throw new BuildException(ERROR_TODIR_AND_SIGNEDJAR);
278         }
279
280
281         if (hasResources() && hasSignedJar) {
282             throw new BuildException(ERROR_SIGNEDJAR_AND_PATHS);
283         }
284
285         //this isnt strictly needed, but by being fussy now,
286
//we can change implementation details later
287
if (!hasDestDir && hasMapper) {
288             throw new BuildException(ERROR_MAPPER_WITHOUT_DEST);
289         }
290
291         beginExecution();
292
293
294         try {
295             //special case single jar handling with signedjar attribute set
296
if (hasJar && hasSignedJar) {
297                 // single jar processing
298
signOneJar(jar, signedjar);
299                 //return here.
300
return;
301             }
302
303             //the rest of the method treats single jar like
304
//a nested path with one file
305

306             Path sources = createUnifiedSourcePath();
307             //set up our mapping policy
308
FileNameMapper destMapper;
309             if (hasMapper) {
310                 destMapper = mapper;
311             } else {
312                 //no mapper? use the identity policy
313
destMapper = new IdentityMapper();
314             }
315
316
317             //at this point the paths are set up with lists of files,
318
//and the mapper is ready to map from source dirs to dest files
319
//now we iterate through every JAR giving source and dest names
320
// deal with the paths
321
Iterator JavaDoc iter = sources.iterator();
322             while (iter.hasNext()) {
323                 FileResource fr = (FileResource) iter.next();
324
325                 //calculate our destination directory; it is either the destDir
326
//attribute, or the base dir of the fileset (for in situ updates)
327
File JavaDoc toDir = hasDestDir ? destDir : fr.getBaseDir();
328
329                 //determine the destination filename via the mapper
330
String JavaDoc[] destFilenames = destMapper.mapFileName(fr.getName());
331                 if (destFilenames == null || destFilenames.length != 1) {
332                     //we only like simple mappers.
333
throw new BuildException(ERROR_BAD_MAP + fr.getFile());
334                 }
335                 File JavaDoc destFile = new File JavaDoc(toDir, destFilenames[0]);
336                 signOneJar(fr.getFile(), destFile);
337             }
338         } finally {
339             endExecution();
340         }
341     }
342
343     /**
344      * Sign one jar.
345      * <p/>
346      * The signing only takes place if {@link #isUpToDate(File, File)} indicates
347      * that it is needed.
348      *
349      * @param jarSource source to sign
350      * @param jarTarget target; may be null
351      * @throws BuildException
352      */

353     private void signOneJar(File JavaDoc jarSource, File JavaDoc jarTarget)
354             throws BuildException {
355
356
357         File JavaDoc targetFile = jarTarget;
358         if (targetFile == null) {
359             targetFile = jarSource;
360         }
361         if (isUpToDate(jarSource, targetFile)) {
362             return;
363         }
364
365         long lastModified = jarSource.lastModified();
366         final ExecTask cmd = createJarSigner();
367
368         setCommonOptions(cmd);
369
370         bindToKeystore(cmd);
371         if (null != sigfile) {
372             addValue(cmd, "-sigfile");
373             String JavaDoc value = this.sigfile;
374             addValue(cmd, value);
375         }
376
377         //DO NOT SET THE -signedjar OPTION if source==dest
378
//unless you like fielding hotspot crash reports
379
if (null != targetFile && !jarSource.equals(targetFile)) {
380             addValue(cmd, "-signedjar");
381             addValue(cmd, targetFile.getPath());
382         }
383
384         if (internalsf) {
385             addValue(cmd, "-internalsf");
386         }
387
388         if (sectionsonly) {
389             addValue(cmd, "-sectionsonly");
390         }
391
392         //add -tsa operations if declared
393
addTimestampAuthorityCommands(cmd);
394
395         //JAR source is required
396
addValue(cmd, jarSource.getPath());
397
398         //alias is required for signing
399
addValue(cmd, alias);
400
401         log("Signing JAR: "
402             + jarSource.getAbsolutePath()
403             + " to "
404             + targetFile.getAbsolutePath()
405             + " as " + alias);
406
407         cmd.execute();
408
409         // restore the lastModified attribute
410
if (preserveLastModified) {
411             targetFile.setLastModified(lastModified);
412         }
413     }
414
415     /**
416      * If the tsa parameters are set, this passes them to the command.
417      * There is no validation of java version, as third party JDKs
418      * may implement this on earlier/later jarsigner implementations.
419      * @param cmd the exec task.
420      */

421     private void addTimestampAuthorityCommands(final ExecTask cmd) {
422         if (tsaurl != null) {
423             addValue(cmd, "-tsa");
424             addValue(cmd, tsaurl);
425         }
426         if (tsacert != null) {
427             addValue(cmd, "-tsacert");
428             addValue(cmd, tsacert);
429         }
430     }
431
432     /**
433      * Compare a jar file with its corresponding signed jar. The logic for this
434      * is complex, and best explained in the source itself. Essentially if
435      * either file doesnt exist, or the destfile has an out of date timestamp,
436      * then the return value is false.
437      * <p/>
438      * If we are signing ourself, the check {@link #isSigned(File)} is used to
439      * trigger the process.
440      *
441      * @param jarFile the unsigned jar file
442      * @param signedjarFile the result signed jar file
443      * @return true if the signedjarFile is considered up to date
444      */

445     protected boolean isUpToDate(File JavaDoc jarFile, File JavaDoc signedjarFile) {
446         if (null == jarFile || !jarFile.exists()) {
447             //these are pathological cases, but retained in case somebody
448
//subclassed us.
449
return false;
450         }
451
452         //we normally compare destination with source
453
File JavaDoc destFile = signedjarFile;
454         if (destFile == null) {
455             //but if no dest is specified, compare source to source
456
destFile = jarFile;
457         }
458
459         //if, by any means, the destfile and source match,
460
if (jarFile.equals(destFile)) {
461             if (lazy) {
462                 //we check the presence of signatures on lazy signing
463
return isSigned(jarFile);
464             }
465             //unsigned or non-lazy self signings are always false
466
return false;
467         }
468
469         //if they are different, the timestamps are used
470
return FILE_UTILS.isUpToDate(jarFile, destFile);
471     }
472
473     /**
474      * test for a file being signed, by looking for a signature in the META-INF
475      * directory with our alias.
476      *
477      * @param file the file to be checked
478      * @return true if the file is signed
479      * @see IsSigned#isSigned(File, String)
480      */

481     protected boolean isSigned(File JavaDoc file) {
482         try {
483             return IsSigned.isSigned(file, alias);
484         } catch (IOException JavaDoc e) {
485             //just log this
486
log(e.toString(), Project.MSG_VERBOSE);
487             return false;
488         }
489     }
490
491     /**
492      * true to indicate that the signed jar modification date remains the same
493      * as the original. Defaults to false
494      *
495      * @param preserveLastModified if true preserve the last modified time
496      */

497     public void setPreserveLastModified(boolean preserveLastModified) {
498         this.preserveLastModified = preserveLastModified;
499     }
500 }
501
Popular Tags