1. /*
  2. * @(#)JarVerifier.java 1.26 01/11/29
  3. *
  4. * Copyright 2002 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 sun.security.util.ManifestDigester;
  13. import sun.security.util.ManifestEntryVerifier;
  14. import sun.security.util.SignatureFileVerifier;
  15. import sun.security.util.Debug;
  16. /**
  17. *
  18. * @version 1.26 01/11/29
  19. * @author Roland Schemers
  20. */
  21. class JarVerifier {
  22. /* Are we debugging ? */
  23. static final Debug debug = Debug.getInstance("jar");
  24. /* a table mapping names to identities for entries that have
  25. had their actual hashes verified */
  26. private Hashtable verifiedCerts;
  27. /* a table mapping names to Certs for entries that have
  28. passed the .SF/.DSA -> MANIFEST check */
  29. private Hashtable sigFileCerts;
  30. /* a hash table to hold .SF bytes */
  31. private Hashtable sigFileData;
  32. /** "queue" of pending PKCS7 blocks that we couldn't parse
  33. * until we parsed the .SF file */
  34. private ArrayList pendingBlocks;
  35. /* cache of Certificate[] objects */
  36. private ArrayList certCache;
  37. /* Are we parsing a block? */
  38. private boolean parsingBlockOrSF = false;
  39. /* Are we done parsing META-INF entries? */
  40. private boolean parsingMeta = true;
  41. /* Are there are files to verify? */
  42. private boolean anyToVerify = true;
  43. /* The manifest file */
  44. private Manifest manifest;
  45. /* The output stream to use when keeping track of files we are interested
  46. in */
  47. private ByteArrayOutputStream baos;
  48. /** The ManifestDigester object */
  49. private ManifestDigester manDig;
  50. /** the bytes for the manDig object */
  51. byte manifestRawBytes[] = null;
  52. /**
  53. */
  54. public JarVerifier(Manifest manifest, byte rawBytes[]) {
  55. manifestRawBytes = rawBytes;
  56. sigFileCerts = new Hashtable();
  57. verifiedCerts = new Hashtable();
  58. sigFileData = new Hashtable(11);
  59. pendingBlocks = new ArrayList();
  60. baos = new ByteArrayOutputStream();
  61. this.manifest = manifest;
  62. }
  63. /**
  64. * This method scans to see which entry we're parsing and
  65. * keeps various state information depending on what type of
  66. * file is being parsed.
  67. */
  68. public void beginEntry(JarEntry je, ManifestEntryVerifier mev)
  69. throws IOException
  70. {
  71. if (je == null)
  72. return;
  73. if (debug != null) {
  74. debug.println("beginEntry "+je.getName());
  75. }
  76. String name = je.getName();
  77. /*
  78. * Assumptions:
  79. * 1. The manifest should be the first entry in the META-INF directory.
  80. * 2. The .SF/.DSA files follow the manifest, before any normal entries
  81. * 3. Any of the following will throw a SecurityException:
  82. * a. digest mismatch between a manifest section and
  83. * the SF section.
  84. * b. digest mismatch between the actual jar entry and the manifest
  85. */
  86. if (parsingMeta) {
  87. String uname = name.toUpperCase();
  88. if ((uname.startsWith("META-INF/") ||
  89. uname.startsWith("/META-INF/"))) {
  90. if (je.isDirectory()) {
  91. mev.setEntry(null, je);
  92. return;
  93. }
  94. if (uname.endsWith(".DSA") || uname.endsWith(".RSA") ||
  95. uname.endsWith(".SF")) {
  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 (sigFileCerts.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.certs == null)) {
  170. je.certs = mev.verify(verifiedCerts, sigFileCerts);
  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().toUpperCase();
  179. if (uname.endsWith(".SF")) {
  180. String key = uname.substring(0, uname.length()-3);
  181. byte bytes[] = baos.toByteArray();
  182. // add to sigFileData in case future blocks need it
  183. sigFileData.put(key, bytes);
  184. // check pending blocks, we can now process
  185. // anyone waiting for this .SF file
  186. Iterator it = pendingBlocks.iterator();
  187. while (it.hasNext()) {
  188. SignatureFileVerifier sfv =
  189. (SignatureFileVerifier) it.next();
  190. if (sfv.needSignatureFile(key)) {
  191. if (debug != null) {
  192. debug.println(
  193. "processEntry: processing pending block");
  194. }
  195. sfv.setSignatureFile(bytes);
  196. sfv.process(sigFileCerts);
  197. }
  198. }
  199. return;
  200. }
  201. // now we are parsing a signature block file
  202. String key = uname.substring(0, uname.lastIndexOf("."));
  203. if (certCache == null)
  204. certCache = new ArrayList();
  205. if (manDig == null) {
  206. synchronized(manifestRawBytes) {
  207. if (manDig == null) {
  208. manDig = new ManifestDigester(manifestRawBytes);
  209. manifestRawBytes = null;
  210. }
  211. }
  212. }
  213. SignatureFileVerifier sfv =
  214. new SignatureFileVerifier(certCache,
  215. manDig, uname, baos.toByteArray());
  216. if (sfv.needSignatureFileBytes()) {
  217. // see if we have already parsed an external .SF file
  218. byte[] bytes = (byte[]) sigFileData.get(key);
  219. if (bytes == null) {
  220. // put this block on queue for later processing
  221. // since we don't have the .SF bytes yet
  222. // (uname, block);
  223. if (debug != null) {
  224. debug.println("adding pending block");
  225. }
  226. pendingBlocks.add(sfv);
  227. return;
  228. } else {
  229. sfv.setSignatureFile(bytes);
  230. }
  231. }
  232. sfv.process(sigFileCerts);
  233. } catch (sun.security.pkcs.ParsingException pe) {
  234. if (debug != null) debug.println("processEntry caught: "+pe);
  235. // ignore and treat as unsigned
  236. } catch (IOException ioe) {
  237. if (debug != null) debug.println("processEntry caught: "+ioe);
  238. // ignore and treat as unsigned
  239. } catch (SignatureException se) {
  240. if (debug != null) debug.println("processEntry caught: "+se);
  241. // ignore and treat as unsigned
  242. } catch (NoSuchAlgorithmException nsae) {
  243. if (debug != null) debug.println("processEntry caught: "+nsae);
  244. // ignore and treat as unsigned
  245. }
  246. }
  247. }
  248. /**
  249. * return an array of java.security.cert.Certificate objects for
  250. * the given file in the jar. this array is not cloned.
  251. *
  252. */
  253. public java.security.cert.Certificate[] getCerts(String name)
  254. {
  255. return (java.security.cert.Certificate[])verifiedCerts.get(name);
  256. }
  257. /**
  258. * returns true if there no files to verify.
  259. * should only be called after all the META-INF entries
  260. * have been processed.
  261. */
  262. boolean nothingToVerify()
  263. {
  264. return (anyToVerify == false);
  265. }
  266. /**
  267. * called to let us know we have processed all the
  268. * META-INF entries, and if we re-read one of them, don't
  269. * re-process it. Also gets rid of any data structures
  270. * we needed when parsing META-INF entries.
  271. */
  272. void doneWithMeta()
  273. {
  274. parsingMeta = false;
  275. anyToVerify = !sigFileCerts.isEmpty();
  276. baos = null;
  277. sigFileData = null;
  278. pendingBlocks = null;
  279. certCache = null;
  280. manDig = null;
  281. }
  282. static class VerifierStream extends java.io.InputStream {
  283. private InputStream is;
  284. private JarVerifier jv;
  285. private ManifestEntryVerifier mev;
  286. private long numLeft;
  287. VerifierStream(Manifest man,
  288. JarEntry je,
  289. InputStream is,
  290. JarVerifier jv) throws IOException
  291. {
  292. this.is = is;
  293. this.jv = jv;
  294. this.mev = new ManifestEntryVerifier(man);
  295. this.jv.beginEntry(je, mev);
  296. this.numLeft = je.getSize();
  297. }
  298. public int read() throws IOException
  299. {
  300. if (numLeft > 0) {
  301. int b = is.read();
  302. jv.update(b, mev);
  303. numLeft--;
  304. if (numLeft == 0)
  305. jv.update(-1, mev);
  306. return b;
  307. } else {
  308. return -1;
  309. }
  310. }
  311. public int read(byte b[], int off, int len) throws IOException {
  312. if ((numLeft > 0) && (numLeft < len)) {
  313. len = (int)numLeft;
  314. }
  315. if (numLeft > 0) {
  316. int n = is.read(b, off, len);
  317. jv.update(n, b, off, len, mev);
  318. numLeft -= n;
  319. if (numLeft == 0)
  320. jv.update(-1, b, off, len, mev);
  321. return n;
  322. } else {
  323. return -1;
  324. }
  325. }
  326. public void close()
  327. throws IOException
  328. {
  329. is.close();
  330. is = null;
  331. mev = null;
  332. jv = null;
  333. }
  334. public int available() throws IOException {
  335. return is.available();
  336. }
  337. }
  338. }