1. /*
  2. * @(#)JarVerifier.java 1.32 03/02/27
  3. *
  4. * Copyright 2003 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.32 03/02/27
  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(Locale.ENGLISH);
  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()
  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(sigFileCerts);
  198. }
  199. }
  200. return;
  201. }
  202. // now we are parsing a signature block file
  203. String key = uname.substring(0, uname.lastIndexOf("."));
  204. if (certCache == null)
  205. certCache = 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(certCache,
  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(sigFileCerts);
  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. }
  247. }
  248. }
  249. /**
  250. * return an array of java.security.cert.Certificate objects for
  251. * the given file in the jar. this array is not cloned.
  252. *
  253. */
  254. public java.security.cert.Certificate[] getCerts(String name)
  255. {
  256. return (java.security.cert.Certificate[])verifiedCerts.get(name);
  257. }
  258. /**
  259. * returns true if there no files to verify.
  260. * should only be called after all the META-INF entries
  261. * have been processed.
  262. */
  263. boolean nothingToVerify()
  264. {
  265. return (anyToVerify == false);
  266. }
  267. /**
  268. * called to let us know we have processed all the
  269. * META-INF entries, and if we re-read one of them, don't
  270. * re-process it. Also gets rid of any data structures
  271. * we needed when parsing META-INF entries.
  272. */
  273. void doneWithMeta()
  274. {
  275. parsingMeta = false;
  276. anyToVerify = !sigFileCerts.isEmpty();
  277. baos = null;
  278. sigFileData = null;
  279. pendingBlocks = null;
  280. certCache = null;
  281. manDig = null;
  282. }
  283. static class VerifierStream extends java.io.InputStream {
  284. private InputStream is;
  285. private JarVerifier jv;
  286. private ManifestEntryVerifier mev;
  287. private long numLeft;
  288. VerifierStream(Manifest man,
  289. JarEntry je,
  290. InputStream is,
  291. JarVerifier jv) throws IOException
  292. {
  293. this.is = is;
  294. this.jv = jv;
  295. this.mev = new ManifestEntryVerifier(man);
  296. this.jv.beginEntry(je, mev);
  297. this.numLeft = je.getSize();
  298. if (this.numLeft == 0)
  299. this.jv.update(-1, this.mev);
  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. }