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