1 /* 2 * Copyright (c) 1997, 2019, 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 26 package sun.security.util; 27 28 import java.io.ByteArrayInputStream; 29 import java.io.IOException; 30 import java.security.CodeSigner; 31 import java.security.GeneralSecurityException; 32 import java.security.MessageDigest; 33 import java.security.NoSuchAlgorithmException; 34 import java.security.SignatureException; 35 import java.security.Timestamp; 36 import java.security.cert.CertPath; 37 import java.security.cert.X509Certificate; 38 import java.security.cert.CertificateException; 39 import java.security.cert.CertificateFactory; 40 import java.util.ArrayList; 41 import java.util.Base64; 42 import java.util.HashMap; 43 import java.util.Hashtable; 44 import java.util.Iterator; 45 import java.util.List; 46 import java.util.Locale; 47 import java.util.Map; 48 import java.util.jar.Attributes; 49 import java.util.jar.JarException; 50 import java.util.jar.JarFile; 51 import java.util.jar.Manifest; 52 53 import sun.security.jca.Providers; 54 import sun.security.pkcs.PKCS7; 55 import sun.security.pkcs.SignerInfo; 56 57 public class SignatureFileVerifier { 58 59 /* Are we debugging ? */ 60 private static final Debug debug = Debug.getInstance("jar"); 61 62 /** 63 * Holder class to delay initialization of DisabledAlgorithmConstraints 64 * until needed. 65 */ 66 private static class ConfigurationHolder { 67 static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK = 68 new DisabledAlgorithmConstraints( 69 DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS); 70 } 71 72 private ArrayList<CodeSigner[]> signerCache; 73 74 private static final String ATTR_DIGEST = 75 "-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS.toUpperCase(Locale.ENGLISH); 76 77 /** the PKCS7 block for this .DSA/.RSA/.EC file */ 78 private PKCS7 block; 79 80 /** the raw bytes of the .SF file */ 81 private byte[] sfBytes; 82 83 /** the name of the signature block file, uppercased and without 84 * the extension (.DSA/.RSA/.EC) 85 */ 86 private String name; 87 88 /** the ManifestDigester */ 89 private ManifestDigester md; 90 91 /** cache of created MessageDigest objects */ 92 private HashMap<String, MessageDigest> createdDigests; 93 94 /* workaround for parsing Netscape jars */ 95 private boolean workaround = false; 96 97 /* for generating certpath objects */ 98 private CertificateFactory certificateFactory = null; 99 100 /** Algorithms that have been checked if they are weak. */ 101 private Map<String, Boolean> permittedAlgs= new HashMap<>(); 102 103 /** TSA timestamp of signed jar. The newest timestamp is used. If there 104 * was no TSA timestamp used when signed, current time is used ("null"). 105 */ 106 private Timestamp timestamp = null; 107 108 /** 109 * Create the named SignatureFileVerifier. 110 * 111 * @param name the name of the signature block file (.DSA/.RSA/.EC) 112 * 113 * @param rawBytes the raw bytes of the signature block file 114 */ 115 public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache, 116 ManifestDigester md, 117 String name, 118 byte[] rawBytes) 119 throws IOException, CertificateException 120 { 121 // new PKCS7() calls CertificateFactory.getInstance() 122 // need to use local providers here, see Providers class 123 Object obj = null; 124 try { 125 obj = Providers.startJarVerification(); 126 block = new PKCS7(rawBytes); 127 sfBytes = block.getContentInfo().getData(); 128 certificateFactory = CertificateFactory.getInstance("X509"); 129 } finally { 130 Providers.stopJarVerification(obj); 131 } 132 this.name = name.substring(0, name.lastIndexOf('.')) 133 .toUpperCase(Locale.ENGLISH); 134 this.md = md; 135 this.signerCache = signerCache; 136 } 137 138 /** 139 * returns true if we need the .SF file 140 */ 141 public boolean needSignatureFileBytes() 142 { 143 144 return sfBytes == null; 145 } 146 147 148 /** 149 * returns true if we need this .SF file. 150 * 151 * @param name the name of the .SF file without the extension 152 * 153 */ 154 public boolean needSignatureFile(String name) 155 { 156 return this.name.equalsIgnoreCase(name); 157 } 158 159 /** 160 * used to set the raw bytes of the .SF file when it 161 * is external to the signature block file. 162 */ 163 public void setSignatureFile(byte[] sfBytes) 164 { 165 this.sfBytes = sfBytes; 166 } 167 168 /** 169 * Utility method used by JarVerifier and JarSigner 170 * to determine the signature file names and PKCS7 block 171 * files names that are supported 172 * 173 * @param s file name 174 * @return true if the input file name is a supported 175 * Signature File or PKCS7 block file name 176 */ 177 public static boolean isBlockOrSF(String s) { 178 // Note: keep this in sync with j.u.z.ZipFile.Source#isSignatureRelated 179 // we currently only support DSA and RSA PKCS7 blocks 180 return s.endsWith(".SF") 181 || s.endsWith(".DSA") 182 || s.endsWith(".RSA") 183 || s.endsWith(".EC"); 184 } 185 186 /** 187 * Yet another utility method used by JarVerifier and JarSigner 188 * to determine what files are signature related, which includes 189 * the MANIFEST, SF files, known signature block files, and other 190 * unknown signature related files (those starting with SIG- with 191 * an optional [A-Z0-9]{1,3} extension right inside META-INF). 192 * 193 * @param name file name 194 * @return true if the input file name is signature related 195 */ 196 public static boolean isSigningRelated(String name) { 197 name = name.toUpperCase(Locale.ENGLISH); 198 if (!name.startsWith("META-INF/")) { 199 return false; 200 } 201 name = name.substring(9); 202 if (name.indexOf('/') != -1) { 203 return false; 204 } 205 if (isBlockOrSF(name) || name.equals("MANIFEST.MF")) { 206 return true; 207 } else if (name.startsWith("SIG-")) { 208 // check filename extension 209 // see http://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#Digital_Signatures 210 // for what filename extensions are legal 211 int extIndex = name.lastIndexOf('.'); 212 if (extIndex != -1) { 213 String ext = name.substring(extIndex + 1); 214 // validate length first 215 if (ext.length() > 3 || ext.length() < 1) { 216 return false; 217 } 218 // then check chars, must be in [a-zA-Z0-9] per the jar spec 219 for (int index = 0; index < ext.length(); index++) { 220 char cc = ext.charAt(index); 221 // chars are promoted to uppercase so skip lowercase checks 222 if ((cc < 'A' || cc > 'Z') && (cc < '0' || cc > '9')) { 223 return false; 224 } 225 } 226 } 227 return true; // no extension is OK 228 } 229 return false; 230 } 231 232 /** get digest from cache */ 233 234 private MessageDigest getDigest(String algorithm) 235 throws SignatureException { 236 if (createdDigests == null) 237 createdDigests = new HashMap<>(); 238 239 MessageDigest digest = createdDigests.get(algorithm); 240 241 if (digest == null) { 242 try { 243 digest = MessageDigest.getInstance(algorithm); 244 createdDigests.put(algorithm, digest); 245 } catch (NoSuchAlgorithmException nsae) { 246 // ignore 247 } 248 } 249 return digest; 250 } 251 252 /** 253 * process the signature block file. Goes through the .SF file 254 * and adds code signers for each section where the .SF section 255 * hash was verified against the Manifest section. 256 * 257 * 258 */ 259 public void process(Hashtable<String, CodeSigner[]> signers, 260 List<Object> manifestDigests) 261 throws IOException, SignatureException, NoSuchAlgorithmException, 262 JarException, CertificateException 263 { 264 // calls Signature.getInstance() and MessageDigest.getInstance() 265 // need to use local providers here, see Providers class 266 Object obj = null; 267 try { 268 obj = Providers.startJarVerification(); 269 processImpl(signers, manifestDigests); 270 } finally { 271 Providers.stopJarVerification(obj); 272 } 273 274 } 275 276 private void processImpl(Hashtable<String, CodeSigner[]> signers, 277 List<Object> manifestDigests) 278 throws IOException, SignatureException, NoSuchAlgorithmException, 279 JarException, CertificateException 280 { 281 Manifest sf = new Manifest(); 282 sf.read(new ByteArrayInputStream(sfBytes)); 283 284 String version = 285 sf.getMainAttributes().getValue(Attributes.Name.SIGNATURE_VERSION); 286 287 if ((version == null) || !(version.equalsIgnoreCase("1.0"))) { 288 // XXX: should this be an exception? 289 // for now we just ignore this signature file 290 return; 291 } 292 293 SignerInfo[] infos = block.verify(sfBytes); 294 295 if (infos == null) { 296 throw new SecurityException("cannot verify signature block file " + 297 name); 298 } 299 300 301 CodeSigner[] newSigners = getSigners(infos, block); 302 303 // make sure we have something to do all this work for... 304 if (newSigners == null) 305 return; 306 307 /* 308 * Look for the latest timestamp in the signature block. If an entry 309 * has no timestamp, use current time (aka null). 310 */ 311 for (CodeSigner s: newSigners) { 312 if (debug != null) { 313 debug.println("Gathering timestamp for: " + s.toString()); 314 } 315 if (s.getTimestamp() == null) { 316 timestamp = null; 317 break; 318 } else if (timestamp == null) { 319 timestamp = s.getTimestamp(); 320 } else { 321 if (timestamp.getTimestamp().before( 322 s.getTimestamp().getTimestamp())) { 323 timestamp = s.getTimestamp(); 324 } 325 } 326 } 327 328 Iterator<Map.Entry<String,Attributes>> entries = 329 sf.getEntries().entrySet().iterator(); 330 331 // see if we can verify the whole manifest first 332 boolean manifestSigned = verifyManifestHash(sf, md, manifestDigests); 333 334 // verify manifest main attributes 335 if (!manifestSigned && !verifyManifestMainAttrs(sf, md)) { 336 throw new SecurityException 337 ("Invalid signature file digest for Manifest main attributes"); 338 } 339 340 // go through each section in the signature file 341 while(entries.hasNext()) { 342 343 Map.Entry<String,Attributes> e = entries.next(); 344 String name = e.getKey(); 345 346 if (manifestSigned || 347 (verifySection(e.getValue(), name, md))) { 348 349 if (name.startsWith("./")) 350 name = name.substring(2); 351 352 if (name.startsWith("/")) 353 name = name.substring(1); 354 355 updateSigners(newSigners, signers, name); 356 357 if (debug != null) { 358 debug.println("processSignature signed name = "+name); 359 } 360 361 } else if (debug != null) { 362 debug.println("processSignature unsigned name = "+name); 363 } 364 } 365 366 // MANIFEST.MF is always regarded as signed 367 updateSigners(newSigners, signers, JarFile.MANIFEST_NAME); 368 } 369 370 /** 371 * Check if algorithm is permitted using the permittedAlgs Map. 372 * If the algorithm is not in the map, check against disabled algorithms and 373 * store the result. If the algorithm is in the map use that result. 374 * False is returned for weak algorithm, true for good algorithms. 375 */ 376 boolean permittedCheck(String key, String algorithm) { 377 Boolean permitted = permittedAlgs.get(algorithm); 378 if (permitted == null) { 379 try { 380 ConfigurationHolder.JAR_DISABLED_CHECK.permits(algorithm, 381 new ConstraintsParameters(timestamp)); 382 } catch(GeneralSecurityException e) { 383 permittedAlgs.put(algorithm, Boolean.FALSE); 384 permittedAlgs.put(key.toUpperCase(), Boolean.FALSE); 385 if (debug != null) { 386 if (e.getMessage() != null) { 387 debug.println(key + ": " + e.getMessage()); 388 } else { 389 debug.println("Debug info only. " + key + ": " + 390 algorithm + 391 " was disabled, no exception msg given."); 392 e.printStackTrace(); 393 } 394 } 395 return false; 396 } 397 398 permittedAlgs.put(algorithm, Boolean.TRUE); 399 return true; 400 } 401 402 // Algorithm has already been checked, return the value from map. 403 return permitted.booleanValue(); 404 } 405 406 /** 407 * With a given header (*-DIGEST*), return a string that lists all the 408 * algorithms associated with the header. 409 * If there are none, return "Unknown Algorithm". 410 */ 411 String getWeakAlgorithms(String header) { 412 String w = ""; 413 try { 414 for (String key : permittedAlgs.keySet()) { 415 if (key.endsWith(header)) { 416 w += key.substring(0, key.length() - header.length()) + " "; 417 } 418 } 419 } catch (RuntimeException e) { 420 w = "Unknown Algorithm(s). Error processing " + header + ". " + 421 e.getMessage(); 422 } 423 424 // This means we have an error in finding weak algorithms, run in 425 // debug mode to see permittedAlgs map's values. 426 if (w.isEmpty()) { 427 return "Unknown Algorithm(s)"; 428 } 429 430 return w; 431 } 432 433 /** 434 * See if the whole manifest was signed. 435 */ 436 private boolean verifyManifestHash(Manifest sf, 437 ManifestDigester md, 438 List<Object> manifestDigests) 439 throws IOException, SignatureException 440 { 441 Attributes mattr = sf.getMainAttributes(); 442 boolean manifestSigned = false; 443 // If only weak algorithms are used. 444 boolean weakAlgs = true; 445 // If a "*-DIGEST-MANIFEST" entry is found. 446 boolean validEntry = false; 447 448 // go through all the attributes and process *-Digest-Manifest entries 449 for (Map.Entry<Object,Object> se : mattr.entrySet()) { 450 451 String key = se.getKey().toString(); 452 453 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) { 454 // 16 is length of "-Digest-Manifest" 455 String algorithm = key.substring(0, key.length()-16); 456 validEntry = true; 457 458 // Check if this algorithm is permitted, skip if false. 459 if (!permittedCheck(key, algorithm)) { 460 continue; 461 } 462 463 // A non-weak algorithm was used, any weak algorithms found do 464 // not need to be reported. 465 weakAlgs = false; 466 467 manifestDigests.add(key); 468 manifestDigests.add(se.getValue()); 469 MessageDigest digest = getDigest(algorithm); 470 if (digest != null) { 471 byte[] computedHash = md.manifestDigest(digest); 472 byte[] expectedHash = 473 Base64.getMimeDecoder().decode((String)se.getValue()); 474 475 if (debug != null) { 476 debug.println("Signature File: Manifest digest " + 477 algorithm); 478 debug.println( " sigfile " + toHex(expectedHash)); 479 debug.println( " computed " + toHex(computedHash)); 480 debug.println(); 481 } 482 483 if (MessageDigest.isEqual(computedHash, expectedHash)) { 484 manifestSigned = true; 485 } else { 486 //XXX: we will continue and verify each section 487 } 488 } 489 } 490 } 491 492 if (debug != null) { 493 debug.println("PermittedAlgs mapping: "); 494 for (String key : permittedAlgs.keySet()) { 495 debug.println(key + " : " + 496 permittedAlgs.get(key).toString()); 497 } 498 } 499 500 // If there were only weak algorithms entries used, throw an exception. 501 if (validEntry && weakAlgs) { 502 throw new SignatureException("Manifest hash check failed " + 503 "(DIGEST-MANIFEST). Disabled algorithm(s) used: " + 504 getWeakAlgorithms("-DIGEST-MANIFEST")); 505 } 506 return manifestSigned; 507 } 508 509 private boolean verifyManifestMainAttrs(Manifest sf, ManifestDigester md) 510 throws IOException, SignatureException 511 { 512 Attributes mattr = sf.getMainAttributes(); 513 boolean attrsVerified = true; 514 // If only weak algorithms are used. 515 boolean weakAlgs = true; 516 // If a ATTR_DIGEST entry is found. 517 boolean validEntry = false; 518 519 // go through all the attributes and process 520 // digest entries for the manifest main attributes 521 for (Map.Entry<Object,Object> se : mattr.entrySet()) { 522 String key = se.getKey().toString(); 523 524 if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) { 525 String algorithm = 526 key.substring(0, key.length() - ATTR_DIGEST.length()); 527 validEntry = true; 528 529 // Check if this algorithm is permitted, skip if false. 530 if (!permittedCheck(key, algorithm)) { 531 continue; 532 } 533 534 // A non-weak algorithm was used, any weak algorithms found do 535 // not need to be reported. 536 weakAlgs = false; 537 538 MessageDigest digest = getDigest(algorithm); 539 if (digest != null) { 540 ManifestDigester.Entry mde = md.getMainAttsEntry(false); 541 byte[] computedHash = mde.digest(digest); 542 byte[] expectedHash = 543 Base64.getMimeDecoder().decode((String)se.getValue()); 544 545 if (debug != null) { 546 debug.println("Signature File: " + 547 "Manifest Main Attributes digest " + 548 digest.getAlgorithm()); 549 debug.println( " sigfile " + toHex(expectedHash)); 550 debug.println( " computed " + toHex(computedHash)); 551 debug.println(); 552 } 553 554 if (MessageDigest.isEqual(computedHash, expectedHash)) { 555 // good 556 } else { 557 // we will *not* continue and verify each section 558 attrsVerified = false; 559 if (debug != null) { 560 debug.println("Verification of " + 561 "Manifest main attributes failed"); 562 debug.println(); 563 } 564 break; 565 } 566 } 567 } 568 } 569 570 if (debug != null) { 571 debug.println("PermittedAlgs mapping: "); 572 for (String key : permittedAlgs.keySet()) { 573 debug.println(key + " : " + 574 permittedAlgs.get(key).toString()); 575 } 576 } 577 578 // If there were only weak algorithms entries used, throw an exception. 579 if (validEntry && weakAlgs) { 580 throw new SignatureException("Manifest Main Attribute check " + 581 "failed (" + ATTR_DIGEST + "). " + 582 "Disabled algorithm(s) used: " + 583 getWeakAlgorithms(ATTR_DIGEST)); 584 } 585 586 // this method returns 'true' if either: 587 // . manifest main attributes were not signed, or 588 // . manifest main attributes were signed and verified 589 return attrsVerified; 590 } 591 592 /** 593 * given the .SF digest header, and the data from the 594 * section in the manifest, see if the hashes match. 595 * if not, throw a SecurityException. 596 * 597 * @return true if all the -Digest headers verified 598 * @exception SecurityException if the hash was not equal 599 */ 600 601 private boolean verifySection(Attributes sfAttr, 602 String name, 603 ManifestDigester md) 604 throws IOException, SignatureException 605 { 606 boolean oneDigestVerified = false; 607 ManifestDigester.Entry mde = md.get(name,block.isOldStyle()); 608 // If only weak algorithms are used. 609 boolean weakAlgs = true; 610 // If a "*-DIGEST" entry is found. 611 boolean validEntry = false; 612 613 if (mde == null) { 614 throw new SecurityException( 615 "no manifest section for signature file entry "+name); 616 } 617 618 if (sfAttr != null) { 619 //sun.security.util.HexDumpEncoder hex = new sun.security.util.HexDumpEncoder(); 620 //hex.encodeBuffer(data, System.out); 621 622 // go through all the attributes and process *-Digest entries 623 for (Map.Entry<Object,Object> se : sfAttr.entrySet()) { 624 String key = se.getKey().toString(); 625 626 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { 627 // 7 is length of "-Digest" 628 String algorithm = key.substring(0, key.length()-7); 629 validEntry = true; 630 631 // Check if this algorithm is permitted, skip if false. 632 if (!permittedCheck(key, algorithm)) { 633 continue; 634 } 635 636 // A non-weak algorithm was used, any weak algorithms found do 637 // not need to be reported. 638 weakAlgs = false; 639 640 MessageDigest digest = getDigest(algorithm); 641 642 if (digest != null) { 643 boolean ok = false; 644 645 byte[] expected = 646 Base64.getMimeDecoder().decode((String)se.getValue()); 647 byte[] computed; 648 if (workaround) { 649 computed = mde.digestWorkaround(digest); 650 } else { 651 computed = mde.digest(digest); 652 } 653 654 if (debug != null) { 655 debug.println("Signature Block File: " + 656 name + " digest=" + digest.getAlgorithm()); 657 debug.println(" expected " + toHex(expected)); 658 debug.println(" computed " + toHex(computed)); 659 debug.println(); 660 } 661 662 if (MessageDigest.isEqual(computed, expected)) { 663 oneDigestVerified = true; 664 ok = true; 665 } else { 666 // attempt to fallback to the workaround 667 if (!workaround) { 668 computed = mde.digestWorkaround(digest); 669 if (MessageDigest.isEqual(computed, expected)) { 670 if (debug != null) { 671 debug.println(" re-computed " + toHex(computed)); 672 debug.println(); 673 } 674 workaround = true; 675 oneDigestVerified = true; 676 ok = true; 677 } 678 } 679 } 680 if (!ok){ 681 throw new SecurityException("invalid " + 682 digest.getAlgorithm() + 683 " signature file digest for " + name); 684 } 685 } 686 } 687 } 688 } 689 690 if (debug != null) { 691 debug.println("PermittedAlgs mapping: "); 692 for (String key : permittedAlgs.keySet()) { 693 debug.println(key + " : " + 694 permittedAlgs.get(key).toString()); 695 } 696 } 697 698 // If there were only weak algorithms entries used, throw an exception. 699 if (validEntry && weakAlgs) { 700 throw new SignatureException("Manifest Main Attribute check " + 701 "failed (DIGEST). Disabled algorithm(s) used: " + 702 getWeakAlgorithms("DIGEST")); 703 } 704 705 return oneDigestVerified; 706 } 707 708 /** 709 * Given the PKCS7 block and SignerInfo[], create an array of 710 * CodeSigner objects. We do this only *once* for a given 711 * signature block file. 712 */ 713 private CodeSigner[] getSigners(SignerInfo[] infos, PKCS7 block) 714 throws IOException, NoSuchAlgorithmException, SignatureException, 715 CertificateException { 716 717 ArrayList<CodeSigner> signers = null; 718 719 for (int i = 0; i < infos.length; i++) { 720 721 SignerInfo info = infos[i]; 722 ArrayList<X509Certificate> chain = info.getCertificateChain(block); 723 CertPath certChain = certificateFactory.generateCertPath(chain); 724 if (signers == null) { 725 signers = new ArrayList<>(); 726 } 727 // Append the new code signer. If timestamp is invalid, this 728 // jar will be treated as unsigned. 729 signers.add(new CodeSigner(certChain, info.getTimestamp())); 730 731 if (debug != null) { 732 debug.println("Signature Block Certificate: " + 733 chain.get(0)); 734 } 735 } 736 737 if (signers != null) { 738 return signers.toArray(new CodeSigner[signers.size()]); 739 } else { 740 return null; 741 } 742 } 743 744 // for the toHex function 745 private static final char[] hexc = 746 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; 747 /** 748 * convert a byte array to a hex string for debugging purposes 749 * @param data the binary data to be converted to a hex string 750 * @return an ASCII hex string 751 */ 752 753 static String toHex(byte[] data) { 754 755 StringBuilder sb = new StringBuilder(data.length*2); 756 757 for (int i=0; i<data.length; i++) { 758 sb.append(hexc[(data[i] >>4) & 0x0f]); 759 sb.append(hexc[data[i] & 0x0f]); 760 } 761 return sb.toString(); 762 } 763 764 // returns true if set contains signer 765 static boolean contains(CodeSigner[] set, CodeSigner signer) 766 { 767 for (int i = 0; i < set.length; i++) { 768 if (set[i].equals(signer)) 769 return true; 770 } 771 return false; 772 } 773 774 // returns true if subset is a subset of set 775 static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set) 776 { 777 // check for the same object 778 if (set == subset) 779 return true; 780 781 boolean match; 782 for (int i = 0; i < subset.length; i++) { 783 if (!contains(set, subset[i])) 784 return false; 785 } 786 return true; 787 } 788 789 /** 790 * returns true if signer contains exactly the same code signers as 791 * oldSigner and newSigner, false otherwise. oldSigner 792 * is allowed to be null. 793 */ 794 static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners, 795 CodeSigner[] newSigners) { 796 797 // special case 798 if ((oldSigners == null) && (signers == newSigners)) 799 return true; 800 801 boolean match; 802 803 // make sure all oldSigners are in signers 804 if ((oldSigners != null) && !isSubSet(oldSigners, signers)) 805 return false; 806 807 // make sure all newSigners are in signers 808 if (!isSubSet(newSigners, signers)) { 809 return false; 810 } 811 812 // now make sure all the code signers in signers are 813 // also in oldSigners or newSigners 814 815 for (int i = 0; i < signers.length; i++) { 816 boolean found = 817 ((oldSigners != null) && contains(oldSigners, signers[i])) || 818 contains(newSigners, signers[i]); 819 if (!found) 820 return false; 821 } 822 return true; 823 } 824 825 void updateSigners(CodeSigner[] newSigners, 826 Hashtable<String, CodeSigner[]> signers, String name) { 827 828 CodeSigner[] oldSigners = signers.get(name); 829 830 // search through the cache for a match, go in reverse order 831 // as we are more likely to find a match with the last one 832 // added to the cache 833 834 CodeSigner[] cachedSigners; 835 for (int i = signerCache.size() - 1; i != -1; i--) { 836 cachedSigners = signerCache.get(i); 837 if (matches(cachedSigners, oldSigners, newSigners)) { 838 signers.put(name, cachedSigners); 839 return; 840 } 841 } 842 843 if (oldSigners == null) { 844 cachedSigners = newSigners; 845 } else { 846 cachedSigners = 847 new CodeSigner[oldSigners.length + newSigners.length]; 848 System.arraycopy(oldSigners, 0, cachedSigners, 0, 849 oldSigners.length); 850 System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length, 851 newSigners.length); 852 } 853 signerCache.add(cachedSigners); 854 signers.put(name, cachedSigners); 855 } 856 }