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