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