You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

SignJar.java 18 kB

11 years ago
11 years ago
11 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645
  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. package org.apache.tools.ant.taskdefs;
  19. import java.io.File;
  20. import java.io.IOException;
  21. import org.apache.tools.ant.BuildException;
  22. import org.apache.tools.ant.Project;
  23. import org.apache.tools.ant.taskdefs.condition.IsSigned;
  24. import org.apache.tools.ant.types.Path;
  25. import org.apache.tools.ant.types.Resource;
  26. import org.apache.tools.ant.types.resources.FileProvider;
  27. import org.apache.tools.ant.types.resources.FileResource;
  28. import org.apache.tools.ant.util.FileNameMapper;
  29. import org.apache.tools.ant.util.FileUtils;
  30. import org.apache.tools.ant.util.IdentityMapper;
  31. import org.apache.tools.ant.util.ResourceUtils;
  32. /**
  33. * Signs JAR or ZIP files with the javasign command line tool. The tool detailed
  34. * dependency checking: files are only signed if they are not signed. The
  35. * <tt>signjar</tt> attribute can point to the file to generate; if this file
  36. * exists then its modification date is used as a cue as to whether to resign
  37. * any JAR file.
  38. *
  39. * Timestamp driven signing is based on the unstable and inadequately documented
  40. * information in the Java1.5 docs
  41. * @see <a href="http://java.sun.com/j2se/1.5.0/docs/guide/security/time-of-signing-beta1.html">
  42. * beta documentation</a>
  43. * @ant.task category="java"
  44. * @since Ant 1.1
  45. */
  46. public class SignJar extends AbstractJarSignerTask {
  47. // CheckStyle:VisibilityModifier OFF - bc
  48. private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
  49. /**
  50. * name to a signature file
  51. */
  52. protected String sigfile;
  53. /**
  54. * name of a single jar
  55. */
  56. protected File signedjar;
  57. /**
  58. * flag for internal sf signing
  59. */
  60. protected boolean internalsf;
  61. /**
  62. * sign sections only?
  63. */
  64. protected boolean sectionsonly;
  65. /**
  66. * flag to preserve timestamp on modified files
  67. */
  68. private boolean preserveLastModified;
  69. /**
  70. * Whether to assume a jar which has an appropriate .SF file in is already
  71. * signed.
  72. */
  73. protected boolean lazy;
  74. /**
  75. * the output directory when using paths.
  76. */
  77. protected File destDir;
  78. /**
  79. * mapper for todir work
  80. */
  81. private FileNameMapper mapper;
  82. /**
  83. * URL for a tsa; null implies no tsa support
  84. */
  85. protected String tsaurl;
  86. /**
  87. * Proxy host to be used when connecting to TSA server
  88. */
  89. protected String tsaproxyhost;
  90. /**
  91. * Proxy port to be used when connecting to TSA server
  92. */
  93. protected String tsaproxyport;
  94. /**
  95. * alias for the TSA in the keystore
  96. */
  97. protected String tsacert;
  98. /**
  99. * force signing even if the jar is already signed.
  100. */
  101. private boolean force = false;
  102. /**
  103. * signature algorithm
  104. */
  105. private String sigAlg;
  106. /**
  107. * digest algorithm
  108. */
  109. private String digestAlg;
  110. /**
  111. * error string for unit test verification: {@value}
  112. */
  113. public static final String ERROR_TODIR_AND_SIGNEDJAR
  114. = "'destdir' and 'signedjar' cannot both be set";
  115. /**
  116. * error string for unit test verification: {@value}
  117. */
  118. public static final String ERROR_TOO_MANY_MAPPERS = "Too many mappers";
  119. /**
  120. * error string for unit test verification {@value}
  121. */
  122. public static final String ERROR_SIGNEDJAR_AND_PATHS
  123. = "You cannot specify the signed JAR when using paths or filesets";
  124. /**
  125. * error string for unit test verification: {@value}
  126. */
  127. public static final String ERROR_BAD_MAP = "Cannot map source file to anything sensible: ";
  128. /**
  129. * error string for unit test verification: {@value}
  130. */
  131. public static final String ERROR_MAPPER_WITHOUT_DEST
  132. = "The destDir attribute is required if a mapper is set";
  133. /**
  134. * error string for unit test verification: {@value}
  135. */
  136. public static final String ERROR_NO_ALIAS = "alias attribute must be set";
  137. /**
  138. * error string for unit test verification: {@value}
  139. */
  140. public static final String ERROR_NO_STOREPASS = "storepass attribute must be set";
  141. // CheckStyle:VisibilityModifier ON
  142. /**
  143. * name of .SF/.DSA file; optional
  144. *
  145. * @param sigfile the name of the .SF/.DSA file
  146. */
  147. public void setSigfile(final String sigfile) {
  148. this.sigfile = sigfile;
  149. }
  150. /**
  151. * name of signed JAR file; optional
  152. *
  153. * @param signedjar the name of the signed jar file
  154. */
  155. public void setSignedjar(final File signedjar) {
  156. this.signedjar = signedjar;
  157. }
  158. /**
  159. * Flag to include the .SF file inside the signature; optional; default
  160. * false
  161. *
  162. * @param internalsf if true include the .SF file inside the signature
  163. */
  164. public void setInternalsf(final boolean internalsf) {
  165. this.internalsf = internalsf;
  166. }
  167. /**
  168. * flag to compute hash of entire manifest; optional, default false
  169. *
  170. * @param sectionsonly flag to compute hash of entire manifest
  171. */
  172. public void setSectionsonly(final boolean sectionsonly) {
  173. this.sectionsonly = sectionsonly;
  174. }
  175. /**
  176. * flag to control whether the presence of a signature file means a JAR is
  177. * signed; optional, default false
  178. *
  179. * @param lazy flag to control whether the presence of a signature
  180. */
  181. public void setLazy(final boolean lazy) {
  182. this.lazy = lazy;
  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 destDir) {
  191. this.destDir = destDir;
  192. }
  193. /**
  194. * add a mapper to determine file naming policy. Only used with toDir
  195. * processing.
  196. *
  197. * @param newMapper the mapper to add.
  198. * @since Ant 1.7
  199. */
  200. public void add(FileNameMapper newMapper) {
  201. if (mapper != null) {
  202. throw new BuildException(ERROR_TOO_MANY_MAPPERS);
  203. }
  204. mapper = newMapper;
  205. }
  206. /**
  207. * get the active mapper; may be null
  208. * @return mapper or null
  209. * @since Ant 1.7
  210. */
  211. public FileNameMapper getMapper() {
  212. return mapper;
  213. }
  214. /**
  215. * get the -tsaurl url
  216. * @return url or null
  217. * @since Ant 1.7
  218. */
  219. public String getTsaurl() {
  220. return tsaurl;
  221. }
  222. /**
  223. *
  224. * @param tsaurl the tsa url.
  225. * @since Ant 1.7
  226. */
  227. public void setTsaurl(String tsaurl) {
  228. this.tsaurl = tsaurl;
  229. }
  230. /**
  231. * Get the proxy host to be used when connecting to the TSA url
  232. * @return url or null
  233. * @since Ant 1.9.5
  234. */
  235. public String getTsaproxyhost() {
  236. return tsaproxyhost;
  237. }
  238. /**
  239. *
  240. * @param tsaproxyhost the proxy host to be used when connecting to the TSA.
  241. * @since Ant 1.9.5
  242. */
  243. public void setTsaproxyhost(String tsaproxyhost) {
  244. this.tsaproxyhost = tsaproxyhost;
  245. }
  246. /**
  247. * Get the proxy host to be used when connecting to the TSA url
  248. * @return url or null
  249. * @since Ant 1.9.5
  250. */
  251. public String getTsaproxyport() {
  252. return tsaproxyport;
  253. }
  254. /**
  255. *
  256. * @param tsaproxyport the proxy port to be used when connecting to the TSA.
  257. * @since Ant 1.9.5
  258. */
  259. public void setTsaproxyport(String tsaproxyport) {
  260. this.tsaproxyport = tsaproxyport;
  261. }
  262. /**
  263. * get the -tsacert option
  264. * @since Ant 1.7
  265. * @return a certificate alias or null
  266. */
  267. public String getTsacert() {
  268. return tsacert;
  269. }
  270. /**
  271. * set the alias in the keystore of the TSA to use;
  272. * @param tsacert the cert alias.
  273. */
  274. public void setTsacert(String tsacert) {
  275. this.tsacert = tsacert;
  276. }
  277. /**
  278. * Whether to force signing of a jar even it is already signed.
  279. * @since Ant 1.8.0
  280. */
  281. public void setForce(boolean b) {
  282. force = b;
  283. }
  284. /**
  285. * Should the task force signing of a jar even it is already
  286. * signed?
  287. * @since Ant 1.8.0
  288. */
  289. public boolean isForce() {
  290. return force;
  291. }
  292. /**
  293. * Signature Algorithm; optional
  294. *
  295. * @param sigAlg the signature algorithm
  296. */
  297. public void setSigAlg(String sigAlg) {
  298. this.sigAlg = sigAlg;
  299. }
  300. /**
  301. * Signature Algorithm; optional
  302. */
  303. public String getSigAlg() {
  304. return sigAlg;
  305. }
  306. /**
  307. * Digest Algorithm; optional
  308. *
  309. * @param digestAlg the digest algorithm
  310. */
  311. public void setDigestAlg(String digestAlg) {
  312. this.digestAlg = digestAlg;
  313. }
  314. /**
  315. * Digest Algorithm; optional
  316. */
  317. public String getDigestAlg() {
  318. return digestAlg;
  319. }
  320. /**
  321. * sign the jar(s)
  322. *
  323. * @throws BuildException on errors
  324. */
  325. @Override
  326. public void execute() throws BuildException {
  327. //validation logic
  328. final boolean hasJar = jar != null;
  329. final boolean hasSignedJar = signedjar != null;
  330. final boolean hasDestDir = destDir != null;
  331. final boolean hasMapper = mapper != null;
  332. if (!hasJar && !hasResources()) {
  333. throw new BuildException(ERROR_NO_SOURCE);
  334. }
  335. if (null == alias) {
  336. throw new BuildException(ERROR_NO_ALIAS);
  337. }
  338. if (null == storepass) {
  339. throw new BuildException(ERROR_NO_STOREPASS);
  340. }
  341. if (hasDestDir && hasSignedJar) {
  342. throw new BuildException(ERROR_TODIR_AND_SIGNEDJAR);
  343. }
  344. if (hasResources() && hasSignedJar) {
  345. throw new BuildException(ERROR_SIGNEDJAR_AND_PATHS);
  346. }
  347. //this isn't strictly needed, but by being fussy now,
  348. //we can change implementation details later
  349. if (!hasDestDir && hasMapper) {
  350. throw new BuildException(ERROR_MAPPER_WITHOUT_DEST);
  351. }
  352. beginExecution();
  353. try {
  354. //special case single jar handling with signedjar attribute set
  355. if (hasJar && hasSignedJar) {
  356. // single jar processing
  357. signOneJar(jar, signedjar);
  358. //return here.
  359. return;
  360. }
  361. //the rest of the method treats single jar like
  362. //a nested path with one file
  363. Path sources = createUnifiedSourcePath();
  364. //set up our mapping policy
  365. FileNameMapper destMapper;
  366. if (hasMapper) {
  367. destMapper = mapper;
  368. } else {
  369. //no mapper? use the identity policy
  370. destMapper = new IdentityMapper();
  371. }
  372. //at this point the paths are set up with lists of files,
  373. //and the mapper is ready to map from source dirs to dest files
  374. //now we iterate through every JAR giving source and dest names
  375. // deal with the paths
  376. for (Resource r : sources) {
  377. FileResource fr = ResourceUtils
  378. .asFileResource(r.as(FileProvider.class));
  379. //calculate our destination directory; it is either the destDir
  380. //attribute, or the base dir of the fileset (for in situ updates)
  381. File toDir = hasDestDir ? destDir : fr.getBaseDir();
  382. //determine the destination filename via the mapper
  383. String[] destFilenames = destMapper.mapFileName(fr.getName());
  384. if (destFilenames == null || destFilenames.length != 1) {
  385. //we only like simple mappers.
  386. throw new BuildException(ERROR_BAD_MAP + fr.getFile());
  387. }
  388. File destFile = new File(toDir, destFilenames[0]);
  389. signOneJar(fr.getFile(), destFile);
  390. }
  391. } finally {
  392. endExecution();
  393. }
  394. }
  395. /**
  396. * Sign one jar.
  397. * <p/>
  398. * The signing only takes place if {@link #isUpToDate(File, File)} indicates
  399. * that it is needed.
  400. *
  401. * @param jarSource source to sign
  402. * @param jarTarget target; may be null
  403. * @throws BuildException
  404. */
  405. private void signOneJar(File jarSource, File jarTarget)
  406. throws BuildException {
  407. File targetFile = jarTarget;
  408. if (targetFile == null) {
  409. targetFile = jarSource;
  410. }
  411. if (isUpToDate(jarSource, targetFile)) {
  412. return;
  413. }
  414. long lastModified = jarSource.lastModified();
  415. final ExecTask cmd = createJarSigner();
  416. setCommonOptions(cmd);
  417. bindToKeystore(cmd);
  418. if (null != sigfile) {
  419. addValue(cmd, "-sigfile");
  420. String value = this.sigfile;
  421. addValue(cmd, value);
  422. }
  423. try {
  424. //DO NOT SET THE -signedjar OPTION if source==dest
  425. //unless you like fielding hotspot crash reports
  426. if (!FILE_UTILS.areSame(jarSource, targetFile)) {
  427. addValue(cmd, "-signedjar");
  428. addValue(cmd, targetFile.getPath());
  429. }
  430. } catch (IOException ioex) {
  431. throw new BuildException(ioex);
  432. }
  433. if (internalsf) {
  434. addValue(cmd, "-internalsf");
  435. }
  436. if (sectionsonly) {
  437. addValue(cmd, "-sectionsonly");
  438. }
  439. if (sigAlg != null) {
  440. addValue(cmd, "-sigalg");
  441. addValue(cmd, sigAlg);
  442. }
  443. if (digestAlg != null) {
  444. addValue(cmd, "-digestalg");
  445. addValue(cmd, digestAlg);
  446. }
  447. //add -tsa operations if declared
  448. addTimestampAuthorityCommands(cmd);
  449. //JAR source is required
  450. addValue(cmd, jarSource.getPath());
  451. //alias is required for signing
  452. addValue(cmd, alias);
  453. log("Signing JAR: "
  454. + jarSource.getAbsolutePath()
  455. + " to "
  456. + targetFile.getAbsolutePath()
  457. + " as " + alias);
  458. cmd.execute();
  459. // restore the lastModified attribute
  460. if (preserveLastModified) {
  461. FILE_UTILS.setFileLastModified(targetFile, lastModified);
  462. }
  463. }
  464. /**
  465. * If the tsa parameters are set, this passes them to the command.
  466. * There is no validation of java version, as third party JDKs
  467. * may implement this on earlier/later jarsigner implementations.
  468. * @param cmd the exec task.
  469. */
  470. private void addTimestampAuthorityCommands(final ExecTask cmd) {
  471. if (tsaurl != null) {
  472. addValue(cmd, "-tsa");
  473. addValue(cmd, tsaurl);
  474. }
  475. if (tsacert != null) {
  476. addValue(cmd, "-tsacert");
  477. addValue(cmd, tsacert);
  478. }
  479. if (tsaproxyhost != null) {
  480. if (tsaurl == null || tsaurl.startsWith("https")) {
  481. addProxyFor(cmd, "https");
  482. }
  483. if (tsaurl == null || !tsaurl.startsWith("https")) {
  484. addProxyFor(cmd, "http");
  485. }
  486. }
  487. }
  488. /**
  489. * <p>Compare a jar file with its corresponding signed jar. The logic for this
  490. * is complex, and best explained in the source itself. Essentially if
  491. * either file doesn't exist, or the destfile has an out of date timestamp,
  492. * then the return value is false.</p>
  493. *
  494. * <p>If we are signing ourself, the check {@link #isSigned(File)} is used to
  495. * trigger the process.</p>
  496. *
  497. * @param jarFile the unsigned jar file
  498. * @param signedjarFile the result signed jar file
  499. * @return true if the signedjarFile is considered up to date
  500. */
  501. protected boolean isUpToDate(File jarFile, File signedjarFile) {
  502. if (isForce() || null == jarFile || !jarFile.exists()) {
  503. //these are pathological cases, but retained in case somebody
  504. //subclassed us.
  505. return false;
  506. }
  507. //we normally compare destination with source
  508. File destFile = signedjarFile;
  509. if (destFile == null) {
  510. //but if no dest is specified, compare source to source
  511. destFile = jarFile;
  512. }
  513. //if, by any means, the destfile and source match,
  514. if (jarFile.equals(destFile)) {
  515. if (lazy) {
  516. //we check the presence of signatures on lazy signing
  517. return isSigned(jarFile);
  518. }
  519. //unsigned or non-lazy self signings are always false
  520. return false;
  521. }
  522. //if they are different, the timestamps are used
  523. return FILE_UTILS.isUpToDate(jarFile, destFile);
  524. }
  525. /**
  526. * test for a file being signed, by looking for a signature in the META-INF
  527. * directory with our alias/sigfile.
  528. *
  529. * @param file the file to be checked
  530. * @return true if the file is signed
  531. * @see IsSigned#isSigned(File, String)
  532. */
  533. protected boolean isSigned(File file) {
  534. try {
  535. return IsSigned.isSigned(file, sigfile == null ? alias : sigfile);
  536. } catch (IOException e) {
  537. //just log this
  538. log(e.toString(), Project.MSG_VERBOSE);
  539. return false;
  540. }
  541. }
  542. /**
  543. * true to indicate that the signed jar modification date remains the same
  544. * as the original. Defaults to false
  545. *
  546. * @param preserveLastModified if true preserve the last modified time
  547. */
  548. public void setPreserveLastModified(boolean preserveLastModified) {
  549. this.preserveLastModified = preserveLastModified;
  550. }
  551. private void addProxyFor(final ExecTask cmd, final String scheme) {
  552. addValue(cmd, "-J-D" + scheme + ".proxyHost=" + tsaproxyhost);
  553. if (tsaproxyport != null) {
  554. addValue(cmd, "-J-D" + scheme + ".proxyPort=" + tsaproxyport);
  555. }
  556. }
  557. }