1 /* 2 * Copyright (c) 1997, 2017, 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 76 (Locale.ENGLISH); 77 78 /** the PKCS7 block for this .DSA/.RSA/.EC file */ 79 private PKCS7 block; 80 81 /** the raw bytes of the .SF file */ 82 private byte[] sfBytes; 83 84 /** the name of the signature block file, uppercased and without 85 * the extension (.DSA/.RSA/.EC) 86 */ 87 private String name; 88 89 /** the ManifestDigester */ 90 private ManifestDigester md; 91 92 /** cache of created MessageDigest objects */ 93 private HashMap<String, MessageDigest> createdDigests; 94 95 /* workaround for parsing Netscape jars */ 96 private boolean workaround = false; 97 98 /* for generating certpath objects */ 99 private CertificateFactory certificateFactory = null; 100 101 /** Algorithms that have been checked if they are weak. */ 102 private Map<String, Boolean> permittedAlgs= new HashMap<>(); 103 104 /** TSA timestamp of signed jar. The newest timestamp is used. If there 105 * was no TSA timestamp used when signed, current time is used ("null"). 106 */ 107 private Timestamp timestamp = null; 108 109 /** 110 * Create the named SignatureFileVerifier. 111 * 112 * @param name the name of the signature block file (.DSA/.RSA/.EC) 113 * 114 * @param rawBytes the raw bytes of the signature block file 115 */ 116 public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache, 117 ManifestDigester md, 118 String name, 119 byte[] rawBytes) 120 throws IOException, CertificateException 121 { 122 // new PKCS7() calls CertificateFactory.getInstance() 123 // need to use local providers here, see Providers class 124 Object obj = null; 125 try { 126 obj = Providers.startJarVerification(); 127 block = new PKCS7(rawBytes); 128 sfBytes = block.getContentInfo().getData(); 129 certificateFactory = CertificateFactory.getInstance("X509"); 130 } finally { 131 Providers.stopJarVerification(obj); 132 } 133 this.name = name.substring(0, name.lastIndexOf('.')) 134 .toUpperCase(Locale.ENGLISH); 135 this.md = md; 136 this.signerCache = signerCache; 137 } 138 139 /** 140 * returns true if we need the .SF file 141 */ 142 public boolean needSignatureFileBytes() 143 { 144 145 return sfBytes == null; 146 } 147 148 149 /** 150 * returns true if we need this .SF file. 151 * 152 * @param name the name of the .SF file without the extension 153 * 154 */ 155 public boolean needSignatureFile(String name) 156 { 157 return this.name.equalsIgnoreCase(name); 158 } 159 160 /** 161 * used to set the raw bytes of the .SF file when it 162 * is external to the signature block file. 163 */ 164 public void setSignatureFile(byte[] sfBytes) 165 { 166 this.sfBytes = sfBytes; 167 } 168 169 /** 170 * Utility method used by JarVerifier and JarSigner 171 * to determine the signature file names and PKCS7 block 172 * files names that are supported 173 * 174 * @param s file name 175 * @return true if the input file name is a supported 176 * Signature File or PKCS7 block file name 177 */ 178 public static boolean isBlockOrSF(String s) { 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.length() == 0) { 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 = 541 md.get(ManifestDigester.MF_MAIN_ATTRS, false); 542 byte[] computedHash = mde.digest(digest); 543 byte[] expectedHash = 544 Base64.getMimeDecoder().decode((String)se.getValue()); 545 546 if (debug != null) { 547 debug.println("Signature File: " + 548 "Manifest Main Attributes digest " + 549 digest.getAlgorithm()); 550 debug.println( " sigfile " + toHex(expectedHash)); 551 debug.println( " computed " + toHex(computedHash)); 552 debug.println(); 553 } 554 555 if (MessageDigest.isEqual(computedHash, expectedHash)) { 556 // good 557 } else { 558 // we will *not* continue and verify each section 559 attrsVerified = false; 560 if (debug != null) { 561 debug.println("Verification of " + 562 "Manifest main attributes failed"); 563 debug.println(); 564 } 565 break; 566 } 567 } 568 } 569 } 570 571 if (debug != null) { 572 debug.println("PermittedAlgs mapping: "); 573 for (String key : permittedAlgs.keySet()) { 574 debug.println(key + " : " + 575 permittedAlgs.get(key).toString()); 576 } 577 } 578 579 // If there were only weak algorithms entries used, throw an exception. 580 if (validEntry && weakAlgs) { 581 throw new SignatureException("Manifest Main Attribute check " + 582 "failed (" + ATTR_DIGEST + "). " + 583 "Disabled algorithm(s) used: " + 584 getWeakAlgorithms(ATTR_DIGEST)); 585 } 586 587 // this method returns 'true' if either: 588 // . manifest main attributes were not signed, or 589 // . manifest main attributes were signed and verified 590 return attrsVerified; 591 } 592 593 /** 594 * given the .SF digest header, and the data from the 595 * section in the manifest, see if the hashes match. 596 * if not, throw a SecurityException. 597 * 598 * @return true if all the -Digest headers verified 599 * @exception SecurityException if the hash was not equal 600 */ 601 602 private boolean verifySection(Attributes sfAttr, 603 String name, 604 ManifestDigester md) 605 throws IOException, SignatureException 606 { 607 boolean oneDigestVerified = false; 608 ManifestDigester.Entry mde = md.get(name,block.isOldStyle()); 609 // If only weak algorithms are used. 610 boolean weakAlgs = true; 611 // If a "*-DIGEST" entry is found. 612 boolean validEntry = false; 613 614 if (mde == null) { 615 throw new SecurityException( 616 "no manifest section for signature file entry "+name); 617 } 618 619 if (sfAttr != null) { 620 //sun.security.util.HexDumpEncoder hex = new sun.security.util.HexDumpEncoder(); 621 //hex.encodeBuffer(data, System.out); 622 623 // go through all the attributes and process *-Digest entries 624 for (Map.Entry<Object,Object> se : sfAttr.entrySet()) { 625 String key = se.getKey().toString(); 626 627 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) { 628 // 7 is length of "-Digest" 629 String algorithm = key.substring(0, key.length()-7); 630 validEntry = true; 631 632 // Check if this algorithm is permitted, skip if false. 633 if (!permittedCheck(key, algorithm)) { 634 continue; 635 } 636 637 // A non-weak algorithm was used, any weak algorithms found do 638 // not need to be reported. 639 weakAlgs = false; 640 641 MessageDigest digest = getDigest(algorithm); 642 643 if (digest != null) { 644 boolean ok = false; 645 646 byte[] expected = 647 Base64.getMimeDecoder().decode((String)se.getValue()); 648 byte[] computed; 649 if (workaround) { 650 computed = mde.digestWorkaround(digest); 651 } else { 652 computed = mde.digest(digest); 653 } 654 655 if (debug != null) { 656 debug.println("Signature Block File: " + 657 name + " digest=" + digest.getAlgorithm()); 658 debug.println(" expected " + toHex(expected)); 659 debug.println(" computed " + toHex(computed)); 660 debug.println(); 661 } 662 663 if (MessageDigest.isEqual(computed, expected)) { 664 oneDigestVerified = true; 665 ok = true; 666 } else { 667 // attempt to fallback to the workaround 668 if (!workaround) { 669 computed = mde.digestWorkaround(digest); 670 if (MessageDigest.isEqual(computed, expected)) { 671 if (debug != null) { 672 debug.println(" re-computed " + toHex(computed)); 673 debug.println(); 674 } 675 workaround = true; 676 oneDigestVerified = true; 677 ok = true; 678 } 679 } 680 } 681 if (!ok){ 682 throw new SecurityException("invalid " + 683 digest.getAlgorithm() + 684 " signature file digest for " + name); 685 } 686 } 687 } 688 } 689 } 690 691 if (debug != null) { 692 debug.println("PermittedAlgs mapping: "); 693 for (String key : permittedAlgs.keySet()) { 694 debug.println(key + " : " + 695 permittedAlgs.get(key).toString()); 696 } 697 } 698 699 // If there were only weak algorithms entries used, throw an exception. 700 if (validEntry && weakAlgs) { 701 throw new SignatureException("Manifest Main Attribute check " + 702 "failed (DIGEST). Disabled algorithm(s) used: " + 703 getWeakAlgorithms("DIGEST")); 704 } 705 706 return oneDigestVerified; 707 } 708 709 /** 710 * Given the PKCS7 block and SignerInfo[], create an array of 711 * CodeSigner objects. We do this only *once* for a given 712 * signature block file. 713 */ 714 private CodeSigner[] getSigners(SignerInfo[] infos, PKCS7 block) 715 throws IOException, NoSuchAlgorithmException, SignatureException, 716 CertificateException { 717 718 ArrayList<CodeSigner> signers = null; 719 720 for (int i = 0; i < infos.length; i++) { 721 722 SignerInfo info = infos[i]; 723 ArrayList<X509Certificate> chain = info.getCertificateChain(block); 724 CertPath certChain = certificateFactory.generateCertPath(chain); 725 if (signers == null) { 726 signers = new ArrayList<>(); 727 } 728 // Append the new code signer. If timestamp is invalid, this 729 // jar will be treated as unsigned. 730 signers.add(new CodeSigner(certChain, info.getTimestamp())); 731 732 if (debug != null) { 733 debug.println("Signature Block Certificate: " + 734 chain.get(0)); 735 } 736 } 737 738 if (signers != null) { 739 return signers.toArray(new CodeSigner[signers.size()]); 740 } else { 741 return null; 742 } 743 } 744 745 // for the toHex function 746 private static final char[] hexc = 747 {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'}; 748 /** 749 * convert a byte array to a hex string for debugging purposes 750 * @param data the binary data to be converted to a hex string 751 * @return an ASCII hex string 752 */ 753 754 static String toHex(byte[] data) { 755 756 StringBuilder sb = new StringBuilder(data.length*2); 757 758 for (int i=0; i<data.length; i++) { 759 sb.append(hexc[(data[i] >>4) & 0x0f]); 760 sb.append(hexc[data[i] & 0x0f]); 761 } 762 return sb.toString(); 763 } 764 765 // returns true if set contains signer 766 static boolean contains(CodeSigner[] set, CodeSigner signer) 767 { 768 for (int i = 0; i < set.length; i++) { 769 if (set[i].equals(signer)) 770 return true; 771 } 772 return false; 773 } 774 775 // returns true if subset is a subset of set 776 static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set) 777 { 778 // check for the same object 779 if (set == subset) 780 return true; 781 782 boolean match; 783 for (int i = 0; i < subset.length; i++) { 784 if (!contains(set, subset[i])) 785 return false; 786 } 787 return true; 788 } 789 790 /** 791 * returns true if signer contains exactly the same code signers as 792 * oldSigner and newSigner, false otherwise. oldSigner 793 * is allowed to be null. 794 */ 795 static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners, 796 CodeSigner[] newSigners) { 797 798 // special case 799 if ((oldSigners == null) && (signers == newSigners)) 800 return true; 801 802 boolean match; 803 804 // make sure all oldSigners are in signers 805 if ((oldSigners != null) && !isSubSet(oldSigners, signers)) 806 return false; 807 808 // make sure all newSigners are in signers 809 if (!isSubSet(newSigners, signers)) { 810 return false; 811 } 812 813 // now make sure all the code signers in signers are 814 // also in oldSigners or newSigners 815 816 for (int i = 0; i < signers.length; i++) { 817 boolean found = 818 ((oldSigners != null) && contains(oldSigners, signers[i])) || 819 contains(newSigners, signers[i]); 820 if (!found) 821 return false; 822 } 823 return true; 824 } 825 826 void updateSigners(CodeSigner[] newSigners, 827 Hashtable<String, CodeSigner[]> signers, String name) { 828 829 CodeSigner[] oldSigners = signers.get(name); 830 831 // search through the cache for a match, go in reverse order 832 // as we are more likely to find a match with the last one 833 // added to the cache 834 835 CodeSigner[] cachedSigners; 836 for (int i = signerCache.size() - 1; i != -1; i--) { 837 cachedSigners = signerCache.get(i); 838 if (matches(cachedSigners, oldSigners, newSigners)) { 839 signers.put(name, cachedSigners); 840 return; 841 } 842 } 843 844 if (oldSigners == null) { 845 cachedSigners = newSigners; 846 } else { 847 cachedSigners = 848 new CodeSigner[oldSigners.length + newSigners.length]; 849 System.arraycopy(oldSigners, 0, cachedSigners, 0, 850 oldSigners.length); 851 System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length, 852 newSigners.length); 853 } 854 signerCache.add(cachedSigners); 855 signers.put(name, cachedSigners); 856 } 857 }