1. /*
  2. * @(#)JarVerifier.java 1.35 03/12/19
  3. *
  4. * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package java.util.jar;
  8. import java.io.*;
  9. import java.util.*;
  10. import java.util.zip.*;
  11. import java.security.*;
  12. import java.security.cert.CertificateException;
  13. import sun.security.util.ManifestDigester;
  14. import sun.security.util.ManifestEntryVerifier;
  15. import sun.security.util.SignatureFileVerifier;
  16. import sun.security.util.Debug;
  17. /**
  18. *
  19. * @version 1.35 03/12/19
  20. * @author Roland Schemers
  21. */
  22. class JarVerifier {
  23. /* Are we debugging ? */
  24. static final Debug debug = Debug.getInstance("jar");
  25. /* a table mapping names to code signers, for jar entries that have
  26. had their actual hashes verified */
  27. private Hashtable verifiedSigners;
  28. /* a table mapping names to code signers, for jar entries that have
  29. passed the .SF/.DSA -> MANIFEST check */
  30. private Hashtable sigFileSigners;
  31. /* a hash table to hold .SF bytes */
  32. private Hashtable sigFileData;
  33. /** "queue" of pending PKCS7 blocks that we couldn't parse
  34. * until we parsed the .SF file */
  35. private ArrayList pendingBlocks;
  36. /* cache of CodeSigner objects */
  37. private ArrayList signerCache;
  38. /* Are we parsing a block? */
  39. private boolean parsingBlockOrSF = false;
  40. /* Are we done parsing META-INF entries? */
  41. private boolean parsingMeta = true;
  42. /* Are there are files to verify? */
  43. private boolean anyToVerify = true;
  44. /* The manifest file */
  45. private Manifest manifest;
  46. /* The output stream to use when keeping track of files we are interested
  47. in */
  48. private ByteArrayOutputStream baos;
  49. /** The ManifestDigester object */
  50. private ManifestDigester manDig;
  51. /** the bytes for the manDig object */
  52. byte manifestRawBytes[] = null;
  53. /**
  54. */
  55. public JarVerifier(Manifest manifest, byte rawBytes[]) {
  56. manifestRawBytes = rawBytes;
  57. sigFileSigners = new Hashtable();
  58. verifiedSigners = new Hashtable();
  59. sigFileData = new Hashtable(11);
  60. pendingBlocks = new ArrayList();
  61. baos = new ByteArrayOutputStream();
  62. this.manifest = manifest;
  63. }
  64. /**
  65. * This method scans to see which entry we're parsing and
  66. * keeps various state information depending on what type of
  67. * file is being parsed.
  68. */
  69. public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
  70. throws IOException
  71. {
  72. if (je == null)
  73. return;
  74. if (debug != null) {
  75. debug.println("beginEntry "+je.getName());
  76. }
  77. String name = je.getName();
  78. /*
  79. * Assumptions:
  80. * 1. The manifest should be the first entry in the META-INF directory.
  81. * 2. The .SF/.DSA files follow the manifest, before any normal entries
  82. * 3. Any of the following will throw a SecurityException:
  83. * a. digest mismatch between a manifest section and
  84. * the SF section.
  85. * b. digest mismatch between the actual jar entry and the manifest
  86. */
  87. if (parsingMeta) {
  88. String uname = name.toUpperCase(Locale.ENGLISH);
  89. if ((uname.startsWith("META-INF/") ||
  90. uname.startsWith("/META-INF/"))) {
  91. if (je.isDirectory()) {
  92. mev.setEntry(null, je);
  93. return;
  94. }
  95. if (SignatureFileVerifier.isBlockOrSF(uname)) {
  96. /* We parse only DSA or RSA PKCS7 blocks. */
  97. parsingBlockOrSF = true;
  98. baos.reset();
  99. mev.setEntry(null, je);
  100. }
  101. return;
  102. }
  103. }
  104. if (parsingMeta) {
  105. doneWithMeta();
  106. }
  107. if (je.isDirectory()) {
  108. mev.setEntry(null, je);
  109. return;
  110. }
  111. // be liberal in what you accept. If the name starts with ./, remove
  112. // it as we internally canonicalize it with out the ./.
  113. if (name.startsWith("./"))
  114. name = name.substring(2);
  115. // be liberal in what you accept. If the name starts with /, remove
  116. // it as we internally canonicalize it with out the /.
  117. if (name.startsWith("/"))
  118. name = name.substring(1);
  119. // only set the jev object for entries that have a signature
  120. if (sigFileSigners.get(name) != null) {
  121. mev.setEntry(name, je);
  122. return;
  123. }
  124. // don't compute the digest for this entry
  125. mev.setEntry(null, je);
  126. return;
  127. }
  128. /**
  129. * update a single byte.
  130. */
  131. public void update(int b, ManifestEntryVerifier mev)
  132. throws IOException
  133. {
  134. if (b != -1) {
  135. if (parsingBlockOrSF) {
  136. baos.write(b);
  137. } else {
  138. mev.update((byte)b);
  139. }
  140. } else {
  141. processEntry(mev);
  142. }
  143. }
  144. /**
  145. * update an array of bytes.
  146. */
  147. public void update(int n, byte[] b, int off, int len,
  148. ManifestEntryVerifier mev)
  149. throws IOException
  150. {
  151. if (n != -1) {
  152. if (parsingBlockOrSF) {
  153. baos.write(b, off, n);
  154. } else {
  155. mev.update(b, off, n);
  156. }
  157. } else {
  158. processEntry(mev);
  159. }
  160. }
  161. /**
  162. * called when we reach the end of entry in one of the read() methods.
  163. */
  164. private void processEntry(ManifestEntryVerifier mev)
  165. throws IOException
  166. {
  167. if (!parsingBlockOrSF) {
  168. JarEntry je = mev.getEntry();
  169. if ((je != null) && (je.signers == null)) {
  170. je.signers = mev.verify(verifiedSigners, sigFileSigners);
  171. }
  172. } else {
  173. try {
  174. parsingBlockOrSF = false;
  175. if (debug != null) {
  176. debug.println("processEntry: processing block");
  177. }
  178. String uname = mev.getEntry().getName()
  179. .toUpperCase(Locale.ENGLISH);
  180. if (uname.endsWith(".SF")) {
  181. String key = uname.substring(0, uname.length()-3);
  182. byte bytes[] = baos.toByteArray();
  183. // add to sigFileData in case future blocks need it
  184. sigFileData.put(key, bytes);
  185. // check pending blocks, we can now process
  186. // anyone waiting for this .SF file
  187. Iterator it = pendingBlocks.iterator();
  188. while (it.hasNext()) {
  189. SignatureFileVerifier sfv =
  190. (SignatureFileVerifier) it.next();
  191. if (sfv.needSignatureFile(key)) {
  192. if (debug != null) {
  193. debug.println(
  194. "processEntry: processing pending block");
  195. }
  196. sfv.setSignatureFile(bytes);
  197. sfv.process(sigFileSigners);
  198. }
  199. }
  200. return;
  201. }
  202. // now we are parsing a signature block file
  203. String key = uname.substring(0, uname.lastIndexOf("."));
  204. if (signerCache == null)
  205. signerCache = new ArrayList();
  206. if (manDig == null) {
  207. synchronized(manifestRawBytes) {
  208. if (manDig == null) {
  209. manDig = new ManifestDigester(manifestRawBytes);
  210. manifestRawBytes = null;
  211. }
  212. }
  213. }
  214. SignatureFileVerifier sfv =
  215. new SignatureFileVerifier(signerCache,
  216. manDig, uname, baos.toByteArray());
  217. if (sfv.needSignatureFileBytes()) {
  218. // see if we have already parsed an external .SF file
  219. byte[] bytes = (byte[]) sigFileData.get(key);
  220. if (bytes == null) {
  221. // put this block on queue for later processing
  222. // since we don't have the .SF bytes yet
  223. // (uname, block);
  224. if (debug != null) {
  225. debug.println("adding pending block");
  226. }
  227. pendingBlocks.add(sfv);
  228. return;
  229. } else {
  230. sfv.setSignatureFile(bytes);
  231. }
  232. }
  233. sfv.process(sigFileSigners);
  234. } catch (sun.security.pkcs.ParsingException pe) {
  235. if (debug != null) debug.println("processEntry caught: "+pe);
  236. // ignore and treat as unsigned
  237. } catch (IOException ioe) {
  238. if (debug != null) debug.println("processEntry caught: "+ioe);
  239. // ignore and treat as unsigned
  240. } catch (SignatureException se) {
  241. if (debug != null) debug.println("processEntry caught: "+se);
  242. // ignore and treat as unsigned
  243. } catch (NoSuchAlgorithmException nsae) {
  244. if (debug != null) debug.println("processEntry caught: "+nsae);
  245. // ignore and treat as unsigned
  246. } catch (CertificateException ce) {
  247. if (debug != null) debug.println("processEntry caught: "+ce);
  248. // ignore and treat as unsigned
  249. }
  250. }
  251. }
  252. /**
  253. * Return an array of java.security.cert.Certificate objects for
  254. * the given file in the jar.
  255. */
  256. public java.security.cert.Certificate[] getCerts(String name)
  257. {
  258. CodeSigner[] signers = getCodeSigners(name);
  259. // Extract the certs in each code signer's cert chain
  260. if (signers != null) {
  261. ArrayList certChains = new ArrayList();
  262. for (int i = 0; i < signers.length; i++) {
  263. certChains.addAll(
  264. signers[i].getSignerCertPath().getCertificates());
  265. }
  266. // Convert into a Certificate[]
  267. return (java.security.cert.Certificate[])
  268. certChains.toArray(
  269. new java.security.cert.Certificate[certChains.size()]);
  270. }
  271. return null;
  272. }
  273. /**
  274. * return an array of CodeSigner objects for
  275. * the given file in the jar. this array is not cloned.
  276. *
  277. */
  278. public CodeSigner[] getCodeSigners(String name)
  279. {
  280. return (CodeSigner[])verifiedSigners.get(name);
  281. }
  282. /**
  283. * returns true if there no files to verify.
  284. * should only be called after all the META-INF entries
  285. * have been processed.
  286. */
  287. boolean nothingToVerify()
  288. {
  289. return (anyToVerify == false);
  290. }
  291. /**
  292. * called to let us know we have processed all the
  293. * META-INF entries, and if we re-read one of them, don't
  294. * re-process it. Also gets rid of any data structures
  295. * we needed when parsing META-INF entries.
  296. */
  297. void doneWithMeta()
  298. {
  299. parsingMeta = false;
  300. anyToVerify = !sigFileSigners.isEmpty();
  301. baos = null;
  302. sigFileData = null;
  303. pendingBlocks = null;
  304. signerCache = null;
  305. manDig = null;
  306. }
  307. static class VerifierStream extends java.io.InputStream {
  308. private InputStream is;
  309. private JarVerifier jv;
  310. private ManifestEntryVerifier mev;
  311. private long numLeft;
  312. VerifierStream(Manifest man,
  313. JarEntry je,
  314. InputStream is,
  315. JarVerifier jv) throws IOException
  316. {
  317. this.is = is;
  318. this.jv = jv;
  319. this.mev = new ManifestEntryVerifier(man);
  320. this.jv.beginEntry(je, mev);
  321. this.numLeft = je.getSize();
  322. if (this.numLeft == 0)
  323. this.jv.update(-1, this.mev);
  324. }
  325. public int read() throws IOException
  326. {
  327. if (numLeft > 0) {
  328. int b = is.read();
  329. jv.update(b, mev);
  330. numLeft--;
  331. if (numLeft == 0)
  332. jv.update(-1, mev);
  333. return b;
  334. } else {
  335. return -1;
  336. }
  337. }
  338. public int read(byte b[], int off, int len) throws IOException {
  339. if ((numLeft > 0) && (numLeft < len)) {
  340. len = (int)numLeft;
  341. }
  342. if (numLeft > 0) {
  343. int n = is.read(b, off, len);
  344. jv.update(n, b, off, len, mev);
  345. numLeft -= n;
  346. if (numLeft == 0)
  347. jv.update(-1, b, off, len, mev);
  348. return n;
  349. } else {
  350. return -1;
  351. }
  352. }
  353. public void close()
  354. throws IOException
  355. {
  356. if (is != null)
  357. is.close();
  358. is = null;
  359. mev = null;
  360. jv = null;
  361. }
  362. public int available() throws IOException {
  363. return is.available();
  364. }
  365. }
  366. }