Home | History | Annotate | Download | only in util
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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      */
    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 
    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      */
    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 
    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 
    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      */
    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 
    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
    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
    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      */
    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 
    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