1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.taskdefs;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Enumeration;
  21. import java.util.Vector;
  22. import java.util.zip.ZipEntry;
  23. import java.util.zip.ZipFile;
  24. import org.apache.tools.ant.BuildException;
  25. import org.apache.tools.ant.DirectoryScanner;
  26. import org.apache.tools.ant.Project;
  27. import org.apache.tools.ant.Task;
  28. import org.apache.tools.ant.types.FileSet;
  29. import org.apache.tools.ant.util.JavaEnvUtils;
  30. /**
  31. * Signs JAR or ZIP files with the javasign command line tool. The
  32. * tool detailed dependency checking: files are only signed if they
  33. * are not signed. The <tt>signjar</tt> attribute can point to the file to
  34. * generate; if this file exists then
  35. * its modification date is used as a cue as to whether to resign any JAR file.
  36. *
  37. * @since Ant 1.1
  38. * @ant.task category="java"
  39. */
  40. public class SignJar extends Task {
  41. /**
  42. * The name of the jar file.
  43. */
  44. protected File jar;
  45. /**
  46. * The alias of signer.
  47. */
  48. protected String alias;
  49. /**
  50. * The name of keystore file.
  51. */
  52. private String keystore;
  53. protected String storepass;
  54. protected String storetype;
  55. protected String keypass;
  56. protected String sigfile;
  57. protected File signedjar;
  58. protected boolean verbose;
  59. protected boolean internalsf;
  60. protected boolean sectionsonly;
  61. /** The maximum amount of memory to use for Jar signer */
  62. private String maxMemory;
  63. /**
  64. * the filesets of the jars to sign
  65. */
  66. protected Vector filesets = new Vector();
  67. /**
  68. * Whether to assume a jar which has an appropriate .SF file in is already
  69. * signed.
  70. */
  71. protected boolean lazy;
  72. /**
  73. * Set the maximum memory to be used by the jarsigner process
  74. *
  75. * @param max a string indicating the maximum memory according to the
  76. * JVM conventions (e.g. 128m is 128 Megabytes)
  77. */
  78. public void setMaxmemory(String max) {
  79. maxMemory = max;
  80. }
  81. /**
  82. * the jar file to sign; required
  83. */
  84. public void setJar(final File jar) {
  85. this.jar = jar;
  86. }
  87. /**
  88. * the alias to sign under; required
  89. */
  90. public void setAlias(final String alias) {
  91. this.alias = alias;
  92. }
  93. /**
  94. * keystore location; required
  95. */
  96. public void setKeystore(final String keystore) {
  97. this.keystore = keystore;
  98. }
  99. /**
  100. * password for keystore integrity; required
  101. */
  102. public void setStorepass(final String storepass) {
  103. this.storepass = storepass;
  104. }
  105. /**
  106. * keystore type; optional
  107. */
  108. public void setStoretype(final String storetype) {
  109. this.storetype = storetype;
  110. }
  111. /**
  112. * password for private key (if different); optional
  113. */
  114. public void setKeypass(final String keypass) {
  115. this.keypass = keypass;
  116. }
  117. /**
  118. * name of .SF/.DSA file; optional
  119. */
  120. public void setSigfile(final String sigfile) {
  121. this.sigfile = sigfile;
  122. }
  123. /**
  124. * name of signed JAR file; optional
  125. */
  126. public void setSignedjar(final File signedjar) {
  127. this.signedjar = signedjar;
  128. }
  129. /**
  130. * Enable verbose output when signing
  131. * ; optional: default false
  132. */
  133. public void setVerbose(final boolean verbose) {
  134. this.verbose = verbose;
  135. }
  136. /**
  137. * Flag to include the .SF file inside the signature;
  138. * optional; default false
  139. */
  140. public void setInternalsf(final boolean internalsf) {
  141. this.internalsf = internalsf;
  142. }
  143. /**
  144. * flag to compute hash of entire manifest;
  145. * optional, default false
  146. */
  147. public void setSectionsonly(final boolean sectionsonly) {
  148. this.sectionsonly = sectionsonly;
  149. }
  150. /**
  151. * flag to control whether the presence of a signature
  152. * file means a JAR is signed;
  153. * optional, default false
  154. */
  155. public void setLazy(final boolean lazy) {
  156. this.lazy = lazy;
  157. }
  158. /**
  159. * Adds a set of files to sign
  160. * @since Ant 1.4
  161. */
  162. public void addFileset(final FileSet set) {
  163. filesets.addElement(set);
  164. }
  165. /**
  166. * sign the jar(s)
  167. */
  168. public void execute() throws BuildException {
  169. if (null == jar && filesets.size() == 0) {
  170. throw new BuildException("jar must be set through jar attribute "
  171. + "or nested filesets");
  172. }
  173. if (null != jar) {
  174. if (filesets.size() != 0) {
  175. log("nested filesets will be ignored if the jar attribute has"
  176. + " been specified.", Project.MSG_WARN);
  177. }
  178. doOneJar(jar, signedjar);
  179. return;
  180. } else {
  181. // deal with the filesets
  182. for (int i = 0; i < filesets.size(); i++) {
  183. FileSet fs = (FileSet) filesets.elementAt(i);
  184. DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  185. String[] jarFiles = ds.getIncludedFiles();
  186. for (int j = 0; j < jarFiles.length; j++) {
  187. doOneJar(new File(fs.getDir(getProject()), jarFiles[j]), null);
  188. }
  189. }
  190. }
  191. }
  192. /**
  193. * sign one jar
  194. */
  195. private void doOneJar(File jarSource, File jarTarget)
  196. throws BuildException {
  197. if (null == alias) {
  198. throw new BuildException("alias attribute must be set");
  199. }
  200. if (null == storepass) {
  201. throw new BuildException("storepass attribute must be set");
  202. }
  203. if (isUpToDate(jarSource, jarTarget)) {
  204. return;
  205. }
  206. final ExecTask cmd = (ExecTask) getProject().createTask("exec");
  207. cmd.setExecutable(JavaEnvUtils.getJdkExecutable("jarsigner"));
  208. if (maxMemory != null) {
  209. cmd.createArg().setValue("-J-Xmx" + maxMemory);
  210. }
  211. if (null != keystore) {
  212. // is the keystore a file
  213. File keystoreFile = getProject().resolveFile(keystore);
  214. if (keystoreFile.exists()) {
  215. cmd.createArg().setValue("-keystore");
  216. cmd.createArg().setValue(keystoreFile.getPath());
  217. } else {
  218. // must be a URL - just pass as is
  219. cmd.createArg().setValue("-keystore");
  220. cmd.createArg().setValue(keystore);
  221. }
  222. }
  223. if (null != storepass) {
  224. cmd.createArg().setValue("-storepass");
  225. cmd.createArg().setValue(storepass);
  226. }
  227. if (null != storetype) {
  228. cmd.createArg().setValue("-storetype");
  229. cmd.createArg().setValue(storetype);
  230. }
  231. if (null != keypass) {
  232. cmd.createArg().setValue("-keypass");
  233. cmd.createArg().setValue(keypass);
  234. }
  235. if (null != sigfile) {
  236. cmd.createArg().setValue("-sigfile");
  237. cmd.createArg().setValue(sigfile);
  238. }
  239. if (null != jarTarget) {
  240. cmd.createArg().setValue("-signedjar");
  241. cmd.createArg().setValue(jarTarget.toString());
  242. }
  243. if (verbose) {
  244. cmd.createArg().setValue("-verbose");
  245. }
  246. if (internalsf) {
  247. cmd.createArg().setValue("-internalsf");
  248. }
  249. if (sectionsonly) {
  250. cmd.createArg().setValue("-sectionsonly");
  251. }
  252. cmd.createArg().setValue(jarSource.toString());
  253. cmd.createArg().setValue(alias);
  254. log("Signing JAR: " + jarSource.getAbsolutePath());
  255. cmd.setFailonerror(true);
  256. cmd.setTaskName(getTaskName());
  257. cmd.execute();
  258. }
  259. protected boolean isUpToDate(File jarFile, File signedjarFile) {
  260. if (null == jarFile) {
  261. return false;
  262. }
  263. if (null != signedjarFile) {
  264. if (!jarFile.exists()) {
  265. return false;
  266. }
  267. if (!signedjarFile.exists()) {
  268. return false;
  269. }
  270. if (jarFile.equals(signedjarFile)) {
  271. return false;
  272. }
  273. if (signedjarFile.lastModified() > jarFile.lastModified()) {
  274. return true;
  275. }
  276. } else {
  277. if (lazy) {
  278. return isSigned(jarFile);
  279. }
  280. }
  281. return false;
  282. }
  283. /**
  284. * test for a file being signed, by looking for a signature in the META-INF
  285. * directory
  286. * @param file
  287. * @return true if the file is signed
  288. */
  289. protected boolean isSigned(File file) {
  290. final String SIG_START = "META-INF/";
  291. final String SIG_END = ".SF";
  292. if (!file.exists()) {
  293. return false;
  294. }
  295. ZipFile jarFile = null;
  296. try {
  297. jarFile = new ZipFile(file);
  298. if (null == alias) {
  299. Enumeration entries = jarFile.entries();
  300. while (entries.hasMoreElements()) {
  301. String name = ((ZipEntry) entries.nextElement()).getName();
  302. if (name.startsWith(SIG_START) && name.endsWith(SIG_END)) {
  303. return true;
  304. }
  305. }
  306. return false;
  307. } else {
  308. return jarFile.getEntry(SIG_START + alias.toUpperCase()
  309. + SIG_END) != null;
  310. }
  311. } catch (IOException e) {
  312. return false;
  313. } finally {
  314. if (jarFile != null) {
  315. try {
  316. jarFile.close();
  317. } catch (IOException e) {
  318. }
  319. }
  320. }
  321. }
  322. }