1 /*
2  * Copyright (c) 1997, 2016, 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.CryptoPrimitive;
32 import java.security.MessageDigest;
33 import java.security.NoSuchAlgorithmException;
34 import java.security.SignatureException;
35 import java.security.cert.CertPath;
36 import java.security.cert.X509Certificate;
37 import java.security.cert.CertificateException;
38 import java.security.cert.CertificateFactory;
39 import java.util.ArrayList;
40 import java.util.Base64;
41 import java.util.Collections;
42 import java.util.EnumSet;
43 import java.util.HashMap;
44 import java.util.Hashtable;
45 import java.util.Iterator;
46 import java.util.List;
47 import java.util.Locale;
48 import java.util.Map;
49 import java.util.Set;
50 import java.util.jar.Attributes;
51 import java.util.jar.JarException;
52 import java.util.jar.JarFile;
53 import java.util.jar.Manifest;
54 
55 import sun.security.jca.Providers;
56 import sun.security.pkcs.PKCS7;
57 import sun.security.pkcs.SignerInfo;
58 
59 public class SignatureFileVerifier {
60 
61     /* Are we debugging ? */
62     private static final Debug debug = Debug.getInstance("jar");
63 
64     private static final Set<CryptoPrimitive> DIGEST_PRIMITIVE_SET =
65             Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST));
66 
67     private static final DisabledAlgorithmConstraints JAR_DISABLED_CHECK =
68             new DisabledAlgorithmConstraints(
69                     DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS);
70 
71     private ArrayList<CodeSigner[]> signerCache;
72 
73     private static final String ATTR_DIGEST =
74         ("-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS).toUpperCase
75         (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     /**
101      * Create the named SignatureFileVerifier.
102      *
103      * @param name the name of the signature block file (.DSA/.RSA/.EC)
104      *
105      * @param rawBytes the raw bytes of the signature block file
106      */
SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache, ManifestDigester md, String name, byte rawBytes[])107     public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache,
108                                  ManifestDigester md,
109                                  String name,
110                                  byte rawBytes[])
111         throws IOException, CertificateException
112     {
113         // new PKCS7() calls CertificateFactory.getInstance()
114         // need to use local providers here, see Providers class
115         Object obj = null;
116         try {
117             obj = Providers.startJarVerification();
118             block = new PKCS7(rawBytes);
119             sfBytes = block.getContentInfo().getData();
120             certificateFactory = CertificateFactory.getInstance("X509");
121         } finally {
122             Providers.stopJarVerification(obj);
123         }
124         this.name = name.substring(0, name.lastIndexOf("."))
125                                                    .toUpperCase(Locale.ENGLISH);
126         this.md = md;
127         this.signerCache = signerCache;
128     }
129 
130     /**
131      * returns true if we need the .SF file
132      */
needSignatureFileBytes()133     public boolean needSignatureFileBytes()
134     {
135 
136         return sfBytes == null;
137     }
138 
139 
140     /**
141      * returns true if we need this .SF file.
142      *
143      * @param name the name of the .SF file without the extension
144      *
145      */
needSignatureFile(String name)146     public boolean needSignatureFile(String name)
147     {
148         return this.name.equalsIgnoreCase(name);
149     }
150 
151     /**
152      * used to set the raw bytes of the .SF file when it
153      * is external to the signature block file.
154      */
setSignatureFile(byte sfBytes[])155     public void setSignatureFile(byte sfBytes[])
156     {
157         this.sfBytes = sfBytes;
158     }
159 
160     /**
161      * Utility method used by JarVerifier and JarSigner
162      * to determine the signature file names and PKCS7 block
163      * files names that are supported
164      *
165      * @param s file name
166      * @return true if the input file name is a supported
167      *          Signature File or PKCS7 block file name
168      */
isBlockOrSF(String s)169     public static boolean isBlockOrSF(String s) {
170         // we currently only support DSA and RSA PKCS7 blocks
171         if (s.endsWith(".SF") || s.endsWith(".DSA") ||
172                 s.endsWith(".RSA") || s.endsWith(".EC")) {
173             return true;
174         }
175         return false;
176     }
177 
178     /**
179      * Yet another utility method used by JarVerifier and JarSigner
180      * to determine what files are signature related, which includes
181      * the MANIFEST, SF files, known signature block files, and other
182      * unknown signature related files (those starting with SIG- with
183      * an optional [A-Z0-9]{1,3} extension right inside META-INF).
184      *
185      * @param s file name
186      * @return true if the input file name is signature related
187      */
isSigningRelated(String name)188     public static boolean isSigningRelated(String name) {
189         name = name.toUpperCase(Locale.ENGLISH);
190         if (!name.startsWith("META-INF/")) {
191             return false;
192         }
193         name = name.substring(9);
194         if (name.indexOf('/') != -1) {
195             return false;
196         }
197         if (isBlockOrSF(name) || name.equals("MANIFEST.MF")) {
198             return true;
199         } else if (name.startsWith("SIG-")) {
200             // check filename extension
201             // see https://docs.oracle.com/javase/7/docs/technotes/guides/jar/jar.html#Digital_Signatures
202             // for what filename extensions are legal
203             int extIndex = name.lastIndexOf('.');
204             if (extIndex != -1) {
205                 String ext = name.substring(extIndex + 1);
206                 // validate length first
207                 if (ext.length() > 3 || ext.length() < 1) {
208                     return false;
209                 }
210                 // then check chars, must be in [a-zA-Z0-9] per the jar spec
211                 for (int index = 0; index < ext.length(); index++) {
212                     char cc = ext.charAt(index);
213                     // chars are promoted to uppercase so skip lowercase checks
214                     if ((cc < 'A' || cc > 'Z') && (cc < '0' || cc > '9')) {
215                         return false;
216                     }
217                 }
218             }
219             return true; // no extension is OK
220         }
221         return false;
222     }
223 
224     /** get digest from cache */
225 
getDigest(String algorithm)226     private MessageDigest getDigest(String algorithm) throws SignatureException {
227         // check that algorithm is not restricted
228         if (!JAR_DISABLED_CHECK.permits(DIGEST_PRIMITIVE_SET, algorithm, null)) {
229             SignatureException e =
230                     new SignatureException("SignatureFile check failed. " +
231                             "Disabled algorithm used: " + algorithm);
232             throw e;
233         }
234 
235         if (createdDigests == null)
236             createdDigests = new HashMap<String, MessageDigest>();
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         Iterator<Map.Entry<String,Attributes>> entries =
307                                 sf.getEntries().entrySet().iterator();
308 
309         // see if we can verify the whole manifest first
310         boolean manifestSigned = verifyManifestHash(sf, md, manifestDigests);
311 
312         // verify manifest main attributes
313         if (!manifestSigned && !verifyManifestMainAttrs(sf, md)) {
314             throw new SecurityException
315                 ("Invalid signature file digest for Manifest main attributes");
316         }
317 
318         // go through each section in the signature file
319         while(entries.hasNext()) {
320 
321             Map.Entry<String,Attributes> e = entries.next();
322             String name = e.getKey();
323 
324             if (manifestSigned ||
325                 (verifySection(e.getValue(), name, md))) {
326 
327                 if (name.startsWith("./"))
328                     name = name.substring(2);
329 
330                 if (name.startsWith("/"))
331                     name = name.substring(1);
332 
333                 updateSigners(newSigners, signers, name);
334 
335                 if (debug != null) {
336                     debug.println("processSignature signed name = "+name);
337                 }
338 
339             } else if (debug != null) {
340                 debug.println("processSignature unsigned name = "+name);
341             }
342         }
343 
344         // MANIFEST.MF is always regarded as signed
345         updateSigners(newSigners, signers, JarFile.MANIFEST_NAME);
346     }
347 
348     /**
349      * See if the whole manifest was signed.
350      */
verifyManifestHash(Manifest sf, ManifestDigester md, List<Object> manifestDigests)351     private boolean verifyManifestHash(Manifest sf,
352                                        ManifestDigester md,
353                                        List<Object> manifestDigests)
354          throws IOException, SignatureException
355     {
356         Attributes mattr = sf.getMainAttributes();
357         boolean manifestSigned = false;
358 
359         // go through all the attributes and process *-Digest-Manifest entries
360         for (Map.Entry<Object,Object> se : mattr.entrySet()) {
361 
362             String key = se.getKey().toString();
363 
364             if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) {
365                 // 16 is length of "-Digest-Manifest"
366                 String algorithm = key.substring(0, key.length()-16);
367 
368                 manifestDigests.add(key);
369                 manifestDigests.add(se.getValue());
370                 MessageDigest digest = getDigest(algorithm);
371                 if (digest != null) {
372                     byte[] computedHash = md.manifestDigest(digest);
373                     byte[] expectedHash =
374                         Base64.getMimeDecoder().decode((String)se.getValue());
375 
376                     if (debug != null) {
377                      debug.println("Signature File: Manifest digest " +
378                                           digest.getAlgorithm());
379                      debug.println( "  sigfile  " + toHex(expectedHash));
380                      debug.println( "  computed " + toHex(computedHash));
381                      debug.println();
382                     }
383 
384                     if (MessageDigest.isEqual(computedHash,
385                                               expectedHash)) {
386                         manifestSigned = true;
387                     } else {
388                         //XXX: we will continue and verify each section
389                     }
390                 }
391             }
392         }
393         return manifestSigned;
394     }
395 
verifyManifestMainAttrs(Manifest sf, ManifestDigester md)396     private boolean verifyManifestMainAttrs(Manifest sf,
397                                         ManifestDigester md)
398          throws IOException, SignatureException
399     {
400         Attributes mattr = sf.getMainAttributes();
401         boolean attrsVerified = true;
402 
403         // go through all the attributes and process
404         // digest entries for the manifest main attributes
405         for (Map.Entry<Object,Object> se : mattr.entrySet()) {
406             String key = se.getKey().toString();
407 
408             if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) {
409                 String algorithm =
410                         key.substring(0, key.length() - ATTR_DIGEST.length());
411 
412                 MessageDigest digest = getDigest(algorithm);
413                 if (digest != null) {
414                     ManifestDigester.Entry mde =
415                         md.get(ManifestDigester.MF_MAIN_ATTRS, false);
416                     byte[] computedHash = mde.digest(digest);
417                     byte[] expectedHash =
418                         Base64.getMimeDecoder().decode((String)se.getValue());
419 
420                     if (debug != null) {
421                      debug.println("Signature File: " +
422                                         "Manifest Main Attributes digest " +
423                                         digest.getAlgorithm());
424                      debug.println( "  sigfile  " + toHex(expectedHash));
425                      debug.println( "  computed " + toHex(computedHash));
426                      debug.println();
427                     }
428 
429                     if (MessageDigest.isEqual(computedHash,
430                                               expectedHash)) {
431                         // good
432                     } else {
433                         // we will *not* continue and verify each section
434                         attrsVerified = false;
435                         if (debug != null) {
436                             debug.println("Verification of " +
437                                         "Manifest main attributes failed");
438                             debug.println();
439                         }
440                         break;
441                     }
442                 }
443             }
444         }
445 
446         // this method returns 'true' if either:
447         //      . manifest main attributes were not signed, or
448         //      . manifest main attributes were signed and verified
449         return attrsVerified;
450     }
451 
452     /**
453      * given the .SF digest header, and the data from the
454      * section in the manifest, see if the hashes match.
455      * if not, throw a SecurityException.
456      *
457      * @return true if all the -Digest headers verified
458      * @exception SecurityException if the hash was not equal
459      */
460 
verifySection(Attributes sfAttr, String name, ManifestDigester md)461     private boolean verifySection(Attributes sfAttr,
462                                   String name,
463                                   ManifestDigester md)
464          throws IOException, SignatureException
465     {
466         boolean oneDigestVerified = false;
467         ManifestDigester.Entry mde = md.get(name,block.isOldStyle());
468 
469         if (mde == null) {
470             throw new SecurityException(
471                   "no manifest section for signature file entry "+name);
472         }
473 
474         if (sfAttr != null) {
475 
476             //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder();
477             //hex.encodeBuffer(data, System.out);
478 
479             // go through all the attributes and process *-Digest entries
480             for (Map.Entry<Object,Object> se : sfAttr.entrySet()) {
481                 String key = se.getKey().toString();
482 
483                 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
484                     // 7 is length of "-Digest"
485                     String algorithm = key.substring(0, key.length()-7);
486 
487                     MessageDigest digest = getDigest(algorithm);
488 
489                     if (digest != null) {
490                         boolean ok = false;
491 
492                         byte[] expected =
493                             Base64.getMimeDecoder().decode((String)se.getValue());
494                         byte[] computed;
495                         if (workaround) {
496                             computed = mde.digestWorkaround(digest);
497                         } else {
498                             computed = mde.digest(digest);
499                         }
500 
501                         if (debug != null) {
502                           debug.println("Signature Block File: " +
503                                    name + " digest=" + digest.getAlgorithm());
504                           debug.println("  expected " + toHex(expected));
505                           debug.println("  computed " + toHex(computed));
506                           debug.println();
507                         }
508 
509                         if (MessageDigest.isEqual(computed, expected)) {
510                             oneDigestVerified = true;
511                             ok = true;
512                         } else {
513                             // attempt to fallback to the workaround
514                             if (!workaround) {
515                                computed = mde.digestWorkaround(digest);
516                                if (MessageDigest.isEqual(computed, expected)) {
517                                    if (debug != null) {
518                                        debug.println("  re-computed " + toHex(computed));
519                                        debug.println();
520                                    }
521                                    workaround = true;
522                                    oneDigestVerified = true;
523                                    ok = true;
524                                }
525                             }
526                         }
527                         if (!ok){
528                             throw new SecurityException("invalid " +
529                                        digest.getAlgorithm() +
530                                        " signature file digest for " + name);
531                         }
532                     }
533                 }
534             }
535         }
536         return oneDigestVerified;
537     }
538 
539     /**
540      * Given the PKCS7 block and SignerInfo[], create an array of
541      * CodeSigner objects. We do this only *once* for a given
542      * signature block file.
543      */
getSigners(SignerInfo infos[], PKCS7 block)544     private CodeSigner[] getSigners(SignerInfo infos[], PKCS7 block)
545         throws IOException, NoSuchAlgorithmException, SignatureException,
546             CertificateException {
547 
548         ArrayList<CodeSigner> signers = null;
549 
550         for (int i = 0; i < infos.length; i++) {
551 
552             SignerInfo info = infos[i];
553             ArrayList<X509Certificate> chain = info.getCertificateChain(block);
554             CertPath certChain = certificateFactory.generateCertPath(chain);
555             if (signers == null) {
556                 signers = new ArrayList<CodeSigner>();
557             }
558             // Append the new code signer
559             signers.add(new CodeSigner(certChain, info.getTimestamp()));
560 
561             if (debug != null) {
562                 debug.println("Signature Block Certificate: " +
563                     chain.get(0));
564             }
565         }
566 
567         if (signers != null) {
568             return signers.toArray(new CodeSigner[signers.size()]);
569         } else {
570             return null;
571         }
572     }
573 
574     // for the toHex function
575     private static final char[] hexc =
576             {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
577     /**
578      * convert a byte array to a hex string for debugging purposes
579      * @param data the binary data to be converted to a hex string
580      * @return an ASCII hex string
581      */
582 
toHex(byte[] data)583     static String toHex(byte[] data) {
584 
585         StringBuffer sb = new StringBuffer(data.length*2);
586 
587         for (int i=0; i<data.length; i++) {
588             sb.append(hexc[(data[i] >>4) & 0x0f]);
589             sb.append(hexc[data[i] & 0x0f]);
590         }
591         return sb.toString();
592     }
593 
594     // returns true if set contains signer
contains(CodeSigner[] set, CodeSigner signer)595     static boolean contains(CodeSigner[] set, CodeSigner signer)
596     {
597         for (int i = 0; i < set.length; i++) {
598             if (set[i].equals(signer))
599                 return true;
600         }
601         return false;
602     }
603 
604     // returns true if subset is a subset of set
isSubSet(CodeSigner[] subset, CodeSigner[] set)605     static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set)
606     {
607         // check for the same object
608         if (set == subset)
609             return true;
610 
611         boolean match;
612         for (int i = 0; i < subset.length; i++) {
613             if (!contains(set, subset[i]))
614                 return false;
615         }
616         return true;
617     }
618 
619     /**
620      * returns true if signer contains exactly the same code signers as
621      * oldSigner and newSigner, false otherwise. oldSigner
622      * is allowed to be null.
623      */
matches(CodeSigner[] signers, CodeSigner[] oldSigners, CodeSigner[] newSigners)624     static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners,
625         CodeSigner[] newSigners) {
626 
627         // special case
628         if ((oldSigners == null) && (signers == newSigners))
629             return true;
630 
631         boolean match;
632 
633         // make sure all oldSigners are in signers
634         if ((oldSigners != null) && !isSubSet(oldSigners, signers))
635             return false;
636 
637         // make sure all newSigners are in signers
638         if (!isSubSet(newSigners, signers)) {
639             return false;
640         }
641 
642         // now make sure all the code signers in signers are
643         // also in oldSigners or newSigners
644 
645         for (int i = 0; i < signers.length; i++) {
646             boolean found =
647                 ((oldSigners != null) && contains(oldSigners, signers[i])) ||
648                 contains(newSigners, signers[i]);
649             if (!found)
650                 return false;
651         }
652         return true;
653     }
654 
updateSigners(CodeSigner[] newSigners, Hashtable<String, CodeSigner[]> signers, String name)655     void updateSigners(CodeSigner[] newSigners,
656         Hashtable<String, CodeSigner[]> signers, String name) {
657 
658         CodeSigner[] oldSigners = signers.get(name);
659 
660         // search through the cache for a match, go in reverse order
661         // as we are more likely to find a match with the last one
662         // added to the cache
663 
664         CodeSigner[] cachedSigners;
665         for (int i = signerCache.size() - 1; i != -1; i--) {
666             cachedSigners = signerCache.get(i);
667             if (matches(cachedSigners, oldSigners, newSigners)) {
668                 signers.put(name, cachedSigners);
669                 return;
670             }
671         }
672 
673         if (oldSigners == null) {
674             cachedSigners = newSigners;
675         } else {
676             cachedSigners =
677                 new CodeSigner[oldSigners.length + newSigners.length];
678             System.arraycopy(oldSigners, 0, cachedSigners, 0,
679                 oldSigners.length);
680             System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length,
681                 newSigners.length);
682         }
683         signerCache.add(cachedSigners);
684         signers.put(name, cachedSigners);
685     }
686 }
687