History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/sun/security/util/SignatureFileVerifier.java (1 of 18352 files). Show all

Unified · Split

java/pkg/sun/security/util/SignatureFileVerifier.java+671−0
@@ -0,0 +1,671 @@
1/*
2 * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.util;
27
28import java.security.cert.CertPath;
29import java.security.cert.X509Certificate;
30import java.security.cert.CertificateException;
31import java.security.cert.CertificateFactory;
32import java.security.*;
33import java.io.*;
34import java.util.*;
35import java.util.jar.*;
36
37import sun.security.pkcs.*;
38import sun.security.timestamp.TimestampToken;
39import sun.misc.BASE64Decoder;
40
41import sun.security.jca.Providers;
42
43public class SignatureFileVerifier {
44
45 /* Are we debugging ? */
46 private static final Debug debug = Debug.getInstance("jar");
47
48 /* cache of CodeSigner objects */
49 private ArrayList<CodeSigner[]> signerCache;
50
51 private static final String ATTR_DIGEST =
52 ("-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS).toUpperCase
53 (Locale.ENGLISH);
54
55 /** the PKCS7 block for this .DSA/.RSA/.EC file */
56 private PKCS7 block;
57
58 /** the raw bytes of the .SF file */
59 private byte sfBytes[];
60
61 /** the name of the signature block file, uppercased and without
62 * the extension (.DSA/.RSA/.EC)
63 */
64 private String name;
65
66 /** the ManifestDigester */
67 private ManifestDigester md;
68
69 /** cache of created MessageDigest objects */
70 private HashMap<String, MessageDigest> createdDigests;
71
72 /* workaround for parsing Netscape jars */
73 private boolean workaround = false;
74
75 /* for generating certpath objects */
76 private CertificateFactory certificateFactory = null;
77
78 /**
79 * Create the named SignatureFileVerifier.
80 *
81 * @param name the name of the signature block file (.DSA/.RSA/.EC)
82 *
83 * @param rawBytes the raw bytes of the signature block file
84 */
85 public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache,
86 ManifestDigester md,
87 String name,
88 byte rawBytes[])
89 throws IOException, CertificateException
90 {
91 // new PKCS7() calls CertificateFactory.getInstance()
92 // need to use local providers here, see Providers class
93 Object obj = null;
94 try {
95 obj = Providers.startJarVerification();
96 block = new PKCS7(rawBytes);
97 sfBytes = block.getContentInfo().getData();
98 certificateFactory = CertificateFactory.getInstance("X509");
99 } finally {
100 Providers.stopJarVerification(obj);
101 }
102 this.name = name.substring(0, name.lastIndexOf("."))
103 .toUpperCase(Locale.ENGLISH);
104 this.md = md;
105 this.signerCache = signerCache;
106 }
107
108 /**
109 * returns true if we need the .SF file
110 */
111 public boolean needSignatureFileBytes()
112 {
113
114 return sfBytes == null;
115 }
116
117
118 /**
119 * returns true if we need this .SF file.
120 *
121 * @param name the name of the .SF file without the extension
122 *
123 */
124 public boolean needSignatureFile(String name)
125 {
126 return this.name.equalsIgnoreCase(name);
127 }
128
129 /**
130 * used to set the raw bytes of the .SF file when it
131 * is external to the signature block file.
132 */
133 public void setSignatureFile(byte sfBytes[])
134 {
135 this.sfBytes = sfBytes;
136 }
137
138 /**
139 * Utility method used by JarVerifier and JarSigner
140 * to determine the signature file names and PKCS7 block
141 * files names that are supported
142 *
143 * @param s file name
144 * @return true if the input file name is a supported
145 * Signature File or PKCS7 block file name
146 */
147 public static boolean isBlockOrSF(String s) {
148 // we currently only support DSA and RSA PKCS7 blocks
149 if (s.endsWith(".SF") || s.endsWith(".DSA") ||
150 s.endsWith(".RSA") || s.endsWith(".EC")) {
151 return true;
152 }
153 return false;
154 }
155
156 /** get digest from cache */
157
158 private MessageDigest getDigest(String algorithm)
159 {
160 if (createdDigests == null)
161 createdDigests = new HashMap<String, MessageDigest>();
162
163 MessageDigest digest = createdDigests.get(algorithm);
164
165 if (digest == null) {
166 try {
167 digest = MessageDigest.getInstance(algorithm);
168 createdDigests.put(algorithm, digest);
169 } catch (NoSuchAlgorithmException nsae) {
170 // ignore
171 }
172 }
173 return digest;
174 }
175
176 /**
177 * process the signature block file. Goes through the .SF file
178 * and adds code signers for each section where the .SF section
179 * hash was verified against the Manifest section.
180 *
181 *
182 */
183 public void process(Hashtable<String, CodeSigner[]> signers,
184 List manifestDigests)
185 throws IOException, SignatureException, NoSuchAlgorithmException,
186 JarException, CertificateException
187 {
188 // calls Signature.getInstance() and MessageDigest.getInstance()
189 // need to use local providers here, see Providers class
190 Object obj = null;
191 try {
192 obj = Providers.startJarVerification();
193 processImpl(signers, manifestDigests);
194 } finally {
195 Providers.stopJarVerification(obj);
196 }
197
198 }
199
200 private void processImpl(Hashtable<String, CodeSigner[]> signers,
201 List manifestDigests)
202 throws IOException, SignatureException, NoSuchAlgorithmException,
203 JarException, CertificateException
204 {
205 Manifest sf = new Manifest();
206 sf.read(new ByteArrayInputStream(sfBytes));
207
208 String version =
209 sf.getMainAttributes().getValue(Attributes.Name.SIGNATURE_VERSION);
210
211 if ((version == null) || !(version.equalsIgnoreCase("1.0"))) {
212 // XXX: should this be an exception?
213 // for now we just ignore this signature file
214 return;
215 }
216
217 SignerInfo[] infos = block.verify(sfBytes);
218
219 if (infos == null) {
220 throw new SecurityException("cannot verify signature block file " +
221 name);
222 }
223
224 BASE64Decoder decoder = new BASE64Decoder();
225
226 CodeSigner[] newSigners = getSigners(infos, block);
227
228 // make sure we have something to do all this work for...
229 if (newSigners == null)
230 return;
231
232 Iterator<Map.Entry<String,Attributes>> entries =
233 sf.getEntries().entrySet().iterator();
234
235 // see if we can verify the whole manifest first
236 boolean manifestSigned = verifyManifestHash(sf, md, decoder, manifestDigests);
237
238 // verify manifest main attributes
239 if (!manifestSigned && !verifyManifestMainAttrs(sf, md, decoder)) {
240 throw new SecurityException
241 ("Invalid signature file digest for Manifest main attributes");
242 }
243
244 // go through each section in the signature file
245 while(entries.hasNext()) {
246
247 Map.Entry<String,Attributes> e = entries.next();
248 String name = e.getKey();
249
250 if (manifestSigned ||
251 (verifySection(e.getValue(), name, md, decoder))) {
252
253 if (name.startsWith("./"))
254 name = name.substring(2);
255
256 if (name.startsWith("/"))
257 name = name.substring(1);
258
259 updateSigners(newSigners, signers, name);
260
261 if (debug != null) {
262 debug.println("processSignature signed name = "+name);
263 }
264
265 } else if (debug != null) {
266 debug.println("processSignature unsigned name = "+name);
267 }
268 }
269
270 // MANIFEST.MF is always regarded as signed
271 updateSigners(newSigners, signers, JarFile.MANIFEST_NAME);
272 }
273
274 /**
275 * See if the whole manifest was signed.
276 */
277 private boolean verifyManifestHash(Manifest sf,
278 ManifestDigester md,
279 BASE64Decoder decoder,
280 List manifestDigests)
281 throws IOException
282 {
283 Attributes mattr = sf.getMainAttributes();
284 boolean manifestSigned = false;
285
286 // go through all the attributes and process *-Digest-Manifest entries
287 for (Map.Entry<Object,Object> se : mattr.entrySet()) {
288
289 String key = se.getKey().toString();
290
291 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) {
292 // 16 is length of "-Digest-Manifest"
293 String algorithm = key.substring(0, key.length()-16);
294
295 manifestDigests.add(key);
296 manifestDigests.add(se.getValue());
297 MessageDigest digest = getDigest(algorithm);
298 if (digest != null) {
299 byte[] computedHash = md.manifestDigest(digest);
300 byte[] expectedHash =
301 decoder.decodeBuffer((String)se.getValue());
302
303 if (debug != null) {
304 debug.println("Signature File: Manifest digest " +
305 digest.getAlgorithm());
306 debug.println( " sigfile " + toHex(expectedHash));
307 debug.println( " computed " + toHex(computedHash));
308 debug.println();
309 }
310
311 if (MessageDigest.isEqual(computedHash,
312 expectedHash)) {
313 manifestSigned = true;
314 } else {
315 //XXX: we will continue and verify each section
316 }
317 }
318 }
319 }
320 return manifestSigned;
321 }
322
323 private boolean verifyManifestMainAttrs(Manifest sf,
324 ManifestDigester md,
325 BASE64Decoder decoder)
326 throws IOException
327 {
328 Attributes mattr = sf.getMainAttributes();
329 boolean attrsVerified = true;
330
331 // go through all the attributes and process
332 // digest entries for the manifest main attributes
333 for (Map.Entry<Object,Object> se : mattr.entrySet()) {
334 String key = se.getKey().toString();
335
336 if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) {
337 String algorithm =
338 key.substring(0, key.length() - ATTR_DIGEST.length());
339
340 MessageDigest digest = getDigest(algorithm);
341 if (digest != null) {
342 ManifestDigester.Entry mde =
343 md.get(ManifestDigester.MF_MAIN_ATTRS, false);
344 byte[] computedHash = mde.digest(digest);
345 byte[] expectedHash =
346 decoder.decodeBuffer((String)se.getValue());
347
348 if (debug != null) {
349 debug.println("Signature File: " +
350 "Manifest Main Attributes digest " +
351 digest.getAlgorithm());
352 debug.println( " sigfile " + toHex(expectedHash));
353 debug.println( " computed " + toHex(computedHash));
354 debug.println();
355 }
356
357 if (MessageDigest.isEqual(computedHash,
358 expectedHash)) {
359 // good
360 } else {
361 // we will *not* continue and verify each section
362 attrsVerified = false;
363 if (debug != null) {
364 debug.println("Verification of " +
365 "Manifest main attributes failed");
366 debug.println();
367 }
368 break;
369 }
370 }
371 }
372 }
373
374 // this method returns 'true' if either:
375 // . manifest main attributes were not signed, or
376 // . manifest main attributes were signed and verified
377 return attrsVerified;
378 }
379
380 /**
381 * given the .SF digest header, and the data from the
382 * section in the manifest, see if the hashes match.
383 * if not, throw a SecurityException.
384 *
385 * @return true if all the -Digest headers verified
386 * @exception SecurityException if the hash was not equal
387 */
388
389 private boolean verifySection(Attributes sfAttr,
390 String name,
391 ManifestDigester md,
392 BASE64Decoder decoder)
393 throws IOException
394 {
395 boolean oneDigestVerified = false;
396 ManifestDigester.Entry mde = md.get(name,block.isOldStyle());
397
398 if (mde == null) {
399 throw new SecurityException(
400 "no manifiest section for signature file entry "+name);
401 }
402
403 if (sfAttr != null) {
404
405 //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder();
406 //hex.encodeBuffer(data, System.out);
407
408 // go through all the attributes and process *-Digest entries
409 for (Map.Entry<Object,Object> se : sfAttr.entrySet()) {
410 String key = se.getKey().toString();
411
412 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
413 // 7 is length of "-Digest"
414 String algorithm = key.substring(0, key.length()-7);
415
416 MessageDigest digest = getDigest(algorithm);
417
418 if (digest != null) {
419 boolean ok = false;
420
421 byte[] expected =
422 decoder.decodeBuffer((String)se.getValue());
423 byte[] computed;
424 if (workaround) {
425 computed = mde.digestWorkaround(digest);
426 } else {
427 computed = mde.digest(digest);
428 }
429
430 if (debug != null) {
431 debug.println("Signature Block File: " +
432 name + " digest=" + digest.getAlgorithm());
433 debug.println(" expected " + toHex(expected));
434 debug.println(" computed " + toHex(computed));
435 debug.println();
436 }
437
438 if (MessageDigest.isEqual(computed, expected)) {
439 oneDigestVerified = true;
440 ok = true;
441 } else {
442 // attempt to fallback to the workaround
443 if (!workaround) {
444 computed = mde.digestWorkaround(digest);
445 if (MessageDigest.isEqual(computed, expected)) {
446 if (debug != null) {
447 debug.println(" re-computed " + toHex(computed));
448 debug.println();
449 }
450 workaround = true;
451 oneDigestVerified = true;
452 ok = true;
453 }
454 }
455 }
456 if (!ok){
457 throw new SecurityException("invalid " +
458 digest.getAlgorithm() +
459 " signature file digest for " + name);
460 }
461 }
462 }
463 }
464 }
465 return oneDigestVerified;
466 }
467
468 /**
469 * Given the PKCS7 block and SignerInfo[], create an array of
470 * CodeSigner objects. We do this only *once* for a given
471 * signature block file.
472 */
473 private CodeSigner[] getSigners(SignerInfo infos[], PKCS7 block)
474 throws IOException, NoSuchAlgorithmException, SignatureException,
475 CertificateException {
476
477 ArrayList<CodeSigner> signers = null;
478
479 for (int i = 0; i < infos.length; i++) {
480
481 SignerInfo info = infos[i];
482 ArrayList<X509Certificate> chain = info.getCertificateChain(block);
483 CertPath certChain = certificateFactory.generateCertPath(chain);
484 if (signers == null) {
485 signers = new ArrayList<CodeSigner>();
486 }
487 // Append the new code signer
488 signers.add(new CodeSigner(certChain, getTimestamp(info)));
489
490 if (debug != null) {
491 debug.println("Signature Block Certificate: " +
492 chain.get(0));
493 }
494 }
495
496 if (signers != null) {
497 return signers.toArray(new CodeSigner[signers.size()]);
498 } else {
499 return null;
500 }
501 }
502
503 /*
504 * Examines a signature timestamp token to generate a timestamp object.
505 *
506 * Examines the signer's unsigned attributes for a
507 * <tt>signatureTimestampToken</tt> attribute. If present,
508 * then it is parsed to extract the date and time at which the
509 * timestamp was generated.
510 *
511 * @param info A signer information element of a PKCS 7 block.
512 *
513 * @return A timestamp token or null if none is present.
514 * @throws IOException if an error is encountered while parsing the
515 * PKCS7 data.
516 * @throws NoSuchAlgorithmException if an error is encountered while
517 * verifying the PKCS7 object.
518 * @throws SignatureException if an error is encountered while
519 * verifying the PKCS7 object.
520 * @throws CertificateException if an error is encountered while generating
521 * the TSA's certpath.
522 */
523 private Timestamp getTimestamp(SignerInfo info)
524 throws IOException, NoSuchAlgorithmException, SignatureException,
525 CertificateException {
526
527 Timestamp timestamp = null;
528
529 // Extract the signer's unsigned attributes
530 PKCS9Attributes unsignedAttrs = info.getUnauthenticatedAttributes();
531 if (unsignedAttrs != null) {
532 PKCS9Attribute timestampTokenAttr =
533 unsignedAttrs.getAttribute("signatureTimestampToken");
534 if (timestampTokenAttr != null) {
535 PKCS7 timestampToken =
536 new PKCS7((byte[])timestampTokenAttr.getValue());
537 // Extract the content (an encoded timestamp token info)
538 byte[] encodedTimestampTokenInfo =
539 timestampToken.getContentInfo().getData();
540 // Extract the signer (the Timestamping Authority)
541 // while verifying the content
542 SignerInfo[] tsa =
543 timestampToken.verify(encodedTimestampTokenInfo);
544 // Expect only one signer
545 ArrayList<X509Certificate> chain =
546 tsa[0].getCertificateChain(timestampToken);
547 CertPath tsaChain = certificateFactory.generateCertPath(chain);
548 // Create a timestamp token info object
549 TimestampToken timestampTokenInfo =
550 new TimestampToken(encodedTimestampTokenInfo);
551 // Create a timestamp object
552 timestamp =
553 new Timestamp(timestampTokenInfo.getDate(), tsaChain);
554 }
555 }
556 return timestamp;
557 }
558
559 // for the toHex function
560 private static final char[] hexc =
561 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
562 /**
563 * convert a byte array to a hex string for debugging purposes
564 * @param data the binary data to be converted to a hex string
565 * @return an ASCII hex string
566 */
567
568 static String toHex(byte[] data) {
569
570 StringBuffer sb = new StringBuffer(data.length*2);
571
572 for (int i=0; i<data.length; i++) {
573 sb.append(hexc[(data[i] >>4) & 0x0f]);
574 sb.append(hexc[data[i] & 0x0f]);
575 }
576 return sb.toString();
577 }
578
579 // returns true if set contains signer
580 static boolean contains(CodeSigner[] set, CodeSigner signer)
581 {
582 for (int i = 0; i < set.length; i++) {
583 if (set[i].equals(signer))
584 return true;
585 }
586 return false;
587 }
588
589 // returns true if subset is a subset of set
590 static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set)
591 {
592 // check for the same object
593 if (set == subset)
594 return true;
595
596 boolean match;
597 for (int i = 0; i < subset.length; i++) {
598 if (!contains(set, subset[i]))
599 return false;
600 }
601 return true;
602 }
603
604 /**
605 * returns true if signer contains exactly the same code signers as
606 * oldSigner and newSigner, false otherwise. oldSigner
607 * is allowed to be null.
608 */
609 static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners,
610 CodeSigner[] newSigners) {
611
612 // special case
613 if ((oldSigners == null) && (signers == newSigners))
614 return true;
615
616 boolean match;
617
618 // make sure all oldSigners are in signers
619 if ((oldSigners != null) && !isSubSet(oldSigners, signers))
620 return false;
621
622 // make sure all newSigners are in signers
623 if (!isSubSet(newSigners, signers)) {
624 return false;
625 }
626
627 // now make sure all the code signers in signers are
628 // also in oldSigners or newSigners
629
630 for (int i = 0; i < signers.length; i++) {
631 boolean found =
632 ((oldSigners != null) && contains(oldSigners, signers[i])) ||
633 contains(newSigners, signers[i]);
634 if (!found)
635 return false;
636 }
637 return true;
638 }
639
640 void updateSigners(CodeSigner[] newSigners,
641 Hashtable<String, CodeSigner[]> signers, String name) {
642
643 CodeSigner[] oldSigners = signers.get(name);
644
645 // search through the cache for a match, go in reverse order
646 // as we are more likely to find a match with the last one
647 // added to the cache
648
649 CodeSigner[] cachedSigners;
650 for (int i = signerCache.size() - 1; i != -1; i--) {
651 cachedSigners = signerCache.get(i);
652 if (matches(cachedSigners, oldSigners, newSigners)) {
653 signers.put(name, cachedSigners);
654 return;
655 }
656 }
657
658 if (oldSigners == null) {
659 cachedSigners = newSigners;
660 } else {
661 cachedSigners =
662 new CodeSigner[oldSigners.length + newSigners.length];
663 System.arraycopy(oldSigners, 0, cachedSigners, 0,
664 oldSigners.length);
665 System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length,
666 newSigners.length);
667 }
668 signerCache.add(cachedSigners);
669 signers.put(name, cachedSigners);
670 }
671}