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 }