Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (c) 1997, 2013, 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.security.cert.CertPath;
     29 import java.security.cert.X509Certificate;
     30 import java.security.cert.CertificateException;
     31 import java.security.cert.CertificateFactory;
     32 import java.security.*;
     33 import java.io.*;
     34 import java.util.*;
     35 import java.util.jar.*;
     36 
     37 import sun.security.pkcs.*;
     38 import sun.security.timestamp.TimestampToken;
     39 import sun.misc.BASE64Decoder;
     40 
     41 import sun.security.jca.Providers;
     42 
     43 public class SignatureFileVerifier {
     44 
     45     /* Are we debugging ? */
     46     private static final Debug debug = Debug.getInstance("jar");
     47 
     48     /* cache of CodeSigner objects */
     49     private ArrayList<CodeSigner[]> signerCache;
     50 
     51     private static final String ATTR_DIGEST =
     52         ("-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS).toUpperCase
     53         (Locale.ENGLISH);
     54 
     55     /** the PKCS7 block for this .DSA/.RSA/.EC file */
     56     private PKCS7 block;
     57 
     58     /** the raw bytes of the .SF file */
     59     private byte sfBytes[];
     60 
     61     /** the name of the signature block file, uppercased and without
     62      *  the extension (.DSA/.RSA/.EC)
     63      */
     64     private String name;
     65 
     66     /** the ManifestDigester */
     67     private ManifestDigester md;
     68 
     69     /** cache of created MessageDigest objects */
     70     private HashMap<String, MessageDigest> createdDigests;
     71 
     72     /* workaround for parsing Netscape jars  */
     73     private boolean workaround = false;
     74 
     75     /* for generating certpath objects */
     76     private CertificateFactory certificateFactory = null;
     77 
     78     /**
     79      * Create the named SignatureFileVerifier.
     80      *
     81      * @param name the name of the signature block file (.DSA/.RSA/.EC)
     82      *
     83      * @param rawBytes the raw bytes of the signature block file
     84      */
     85     public SignatureFileVerifier(ArrayList<CodeSigner[]> signerCache,
     86                                  ManifestDigester md,
     87                                  String name,
     88                                  byte rawBytes[])
     89         throws IOException, CertificateException
     90     {
     91         // new PKCS7() calls CertificateFactory.getInstance()
     92         // need to use local providers here, see Providers class
     93         Object obj = null;
     94         try {
     95             obj = Providers.startJarVerification();
     96             block = new PKCS7(rawBytes);
     97             sfBytes = block.getContentInfo().getData();
     98             certificateFactory = CertificateFactory.getInstance("X509");
     99         } finally {
    100             Providers.stopJarVerification(obj);
    101         }
    102         this.name = name.substring(0, name.lastIndexOf("."))
    103                                                    .toUpperCase(Locale.ENGLISH);
    104         this.md = md;
    105         this.signerCache = signerCache;
    106     }
    107 
    108     /**
    109      * returns true if we need the .SF file
    110      */
    111     public boolean needSignatureFileBytes()
    112     {
    113 
    114         return sfBytes == null;
    115     }
    116 
    117 
    118     /**
    119      * returns true if we need this .SF file.
    120      *
    121      * @param name the name of the .SF file without the extension
    122      *
    123      */
    124     public boolean needSignatureFile(String name)
    125     {
    126         return this.name.equalsIgnoreCase(name);
    127     }
    128 
    129     /**
    130      * used to set the raw bytes of the .SF file when it
    131      * is external to the signature block file.
    132      */
    133     public void setSignatureFile(byte sfBytes[])
    134     {
    135         this.sfBytes = sfBytes;
    136     }
    137 
    138     /**
    139      * Utility method used by JarVerifier and JarSigner
    140      * to determine the signature file names and PKCS7 block
    141      * files names that are supported
    142      *
    143      * @param s file name
    144      * @return true if the input file name is a supported
    145      *          Signature File or PKCS7 block file name
    146      */
    147     public static boolean isBlockOrSF(String s) {
    148         // we currently only support DSA and RSA PKCS7 blocks
    149         if (s.endsWith(".SF") || s.endsWith(".DSA") ||
    150                 s.endsWith(".RSA") || s.endsWith(".EC")) {
    151             return true;
    152         }
    153         return false;
    154     }
    155 
    156     /** get digest from cache */
    157 
    158     private MessageDigest getDigest(String algorithm)
    159     {
    160         if (createdDigests == null)
    161             createdDigests = new HashMap<String, MessageDigest>();
    162 
    163         MessageDigest digest = createdDigests.get(algorithm);
    164 
    165         if (digest == null) {
    166             try {
    167                 digest = MessageDigest.getInstance(algorithm);
    168                 createdDigests.put(algorithm, digest);
    169             } catch (NoSuchAlgorithmException nsae) {
    170                 // ignore
    171             }
    172         }
    173         return digest;
    174     }
    175 
    176     /**
    177      * process the signature block file. Goes through the .SF file
    178      * and adds code signers for each section where the .SF section
    179      * hash was verified against the Manifest section.
    180      *
    181      *
    182      */
    183     public void process(Hashtable<String, CodeSigner[]> signers,
    184             List manifestDigests)
    185         throws IOException, SignatureException, NoSuchAlgorithmException,
    186             JarException, CertificateException
    187     {
    188         // calls Signature.getInstance() and MessageDigest.getInstance()
    189         // need to use local providers here, see Providers class
    190         Object obj = null;
    191         try {
    192             obj = Providers.startJarVerification();
    193             processImpl(signers, manifestDigests);
    194         } finally {
    195             Providers.stopJarVerification(obj);
    196         }
    197 
    198     }
    199 
    200     private void processImpl(Hashtable<String, CodeSigner[]> signers,
    201             List manifestDigests)
    202         throws IOException, SignatureException, NoSuchAlgorithmException,
    203             JarException, CertificateException
    204     {
    205         Manifest sf = new Manifest();
    206         sf.read(new ByteArrayInputStream(sfBytes));
    207 
    208         String version =
    209             sf.getMainAttributes().getValue(Attributes.Name.SIGNATURE_VERSION);
    210 
    211         if ((version == null) || !(version.equalsIgnoreCase("1.0"))) {
    212             // XXX: should this be an exception?
    213             // for now we just ignore this signature file
    214             return;
    215         }
    216 
    217         SignerInfo[] infos = block.verify(sfBytes);
    218 
    219         if (infos == null) {
    220             throw new SecurityException("cannot verify signature block file " +
    221                                         name);
    222         }
    223 
    224         BASE64Decoder decoder = new BASE64Decoder();
    225 
    226         CodeSigner[] newSigners = getSigners(infos, block);
    227 
    228         // make sure we have something to do all this work for...
    229         if (newSigners == null)
    230             return;
    231 
    232         Iterator<Map.Entry<String,Attributes>> entries =
    233                                 sf.getEntries().entrySet().iterator();
    234 
    235         // see if we can verify the whole manifest first
    236         boolean manifestSigned = verifyManifestHash(sf, md, decoder, manifestDigests);
    237 
    238         // verify manifest main attributes
    239         if (!manifestSigned && !verifyManifestMainAttrs(sf, md, decoder)) {
    240             throw new SecurityException
    241                 ("Invalid signature file digest for Manifest main attributes");
    242         }
    243 
    244         // go through each section in the signature file
    245         while(entries.hasNext()) {
    246 
    247             Map.Entry<String,Attributes> e = entries.next();
    248             String name = e.getKey();
    249 
    250             if (manifestSigned ||
    251                 (verifySection(e.getValue(), name, md, decoder))) {
    252 
    253                 if (name.startsWith("./"))
    254                     name = name.substring(2);
    255 
    256                 if (name.startsWith("/"))
    257                     name = name.substring(1);
    258 
    259                 updateSigners(newSigners, signers, name);
    260 
    261                 if (debug != null) {
    262                     debug.println("processSignature signed name = "+name);
    263                 }
    264 
    265             } else if (debug != null) {
    266                 debug.println("processSignature unsigned name = "+name);
    267             }
    268         }
    269 
    270         // MANIFEST.MF is always regarded as signed
    271         updateSigners(newSigners, signers, JarFile.MANIFEST_NAME);
    272     }
    273 
    274     /**
    275      * See if the whole manifest was signed.
    276      */
    277     private boolean verifyManifestHash(Manifest sf,
    278                                        ManifestDigester md,
    279                                        BASE64Decoder decoder,
    280                                        List manifestDigests)
    281          throws IOException
    282     {
    283         Attributes mattr = sf.getMainAttributes();
    284         boolean manifestSigned = false;
    285 
    286         // go through all the attributes and process *-Digest-Manifest entries
    287         for (Map.Entry<Object,Object> se : mattr.entrySet()) {
    288 
    289             String key = se.getKey().toString();
    290 
    291             if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST-MANIFEST")) {
    292                 // 16 is length of "-Digest-Manifest"
    293                 String algorithm = key.substring(0, key.length()-16);
    294 
    295                 manifestDigests.add(key);
    296                 manifestDigests.add(se.getValue());
    297                 MessageDigest digest = getDigest(algorithm);
    298                 if (digest != null) {
    299                     byte[] computedHash = md.manifestDigest(digest);
    300                     byte[] expectedHash =
    301                         decoder.decodeBuffer((String)se.getValue());
    302 
    303                     if (debug != null) {
    304                      debug.println("Signature File: Manifest digest " +
    305                                           digest.getAlgorithm());
    306                      debug.println( "  sigfile  " + toHex(expectedHash));
    307                      debug.println( "  computed " + toHex(computedHash));
    308                      debug.println();
    309                     }
    310 
    311                     if (MessageDigest.isEqual(computedHash,
    312                                               expectedHash)) {
    313                         manifestSigned = true;
    314                     } else {
    315                         //XXX: we will continue and verify each section
    316                     }
    317                 }
    318             }
    319         }
    320         return manifestSigned;
    321     }
    322 
    323     private boolean verifyManifestMainAttrs(Manifest sf,
    324                                         ManifestDigester md,
    325                                         BASE64Decoder decoder)
    326          throws IOException
    327     {
    328         Attributes mattr = sf.getMainAttributes();
    329         boolean attrsVerified = true;
    330 
    331         // go through all the attributes and process
    332         // digest entries for the manifest main attributes
    333         for (Map.Entry<Object,Object> se : mattr.entrySet()) {
    334             String key = se.getKey().toString();
    335 
    336             if (key.toUpperCase(Locale.ENGLISH).endsWith(ATTR_DIGEST)) {
    337                 String algorithm =
    338                         key.substring(0, key.length() - ATTR_DIGEST.length());
    339 
    340                 MessageDigest digest = getDigest(algorithm);
    341                 if (digest != null) {
    342                     ManifestDigester.Entry mde =
    343                         md.get(ManifestDigester.MF_MAIN_ATTRS, false);
    344                     byte[] computedHash = mde.digest(digest);
    345                     byte[] expectedHash =
    346                         decoder.decodeBuffer((String)se.getValue());
    347 
    348                     if (debug != null) {
    349                      debug.println("Signature File: " +
    350                                         "Manifest Main Attributes digest " +
    351                                         digest.getAlgorithm());
    352                      debug.println( "  sigfile  " + toHex(expectedHash));
    353                      debug.println( "  computed " + toHex(computedHash));
    354                      debug.println();
    355                     }
    356 
    357                     if (MessageDigest.isEqual(computedHash,
    358                                               expectedHash)) {
    359                         // good
    360                     } else {
    361                         // we will *not* continue and verify each section
    362                         attrsVerified = false;
    363                         if (debug != null) {
    364                             debug.println("Verification of " +
    365                                         "Manifest main attributes failed");
    366                             debug.println();
    367                         }
    368                         break;
    369                     }
    370                 }
    371             }
    372         }
    373 
    374         // this method returns 'true' if either:
    375         //      . manifest main attributes were not signed, or
    376         //      . manifest main attributes were signed and verified
    377         return attrsVerified;
    378     }
    379 
    380     /**
    381      * given the .SF digest header, and the data from the
    382      * section in the manifest, see if the hashes match.
    383      * if not, throw a SecurityException.
    384      *
    385      * @return true if all the -Digest headers verified
    386      * @exception SecurityException if the hash was not equal
    387      */
    388 
    389     private boolean verifySection(Attributes sfAttr,
    390                                   String name,
    391                                   ManifestDigester md,
    392                                   BASE64Decoder decoder)
    393          throws IOException
    394     {
    395         boolean oneDigestVerified = false;
    396         ManifestDigester.Entry mde = md.get(name,block.isOldStyle());
    397 
    398         if (mde == null) {
    399             throw new SecurityException(
    400                   "no manifiest section for signature file entry "+name);
    401         }
    402 
    403         if (sfAttr != null) {
    404 
    405             //sun.misc.HexDumpEncoder hex = new sun.misc.HexDumpEncoder();
    406             //hex.encodeBuffer(data, System.out);
    407 
    408             // go through all the attributes and process *-Digest entries
    409             for (Map.Entry<Object,Object> se : sfAttr.entrySet()) {
    410                 String key = se.getKey().toString();
    411 
    412                 if (key.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
    413                     // 7 is length of "-Digest"
    414                     String algorithm = key.substring(0, key.length()-7);
    415 
    416                     MessageDigest digest = getDigest(algorithm);
    417 
    418                     if (digest != null) {
    419                         boolean ok = false;
    420 
    421                         byte[] expected =
    422                             decoder.decodeBuffer((String)se.getValue());
    423                         byte[] computed;
    424                         if (workaround) {
    425                             computed = mde.digestWorkaround(digest);
    426                         } else {
    427                             computed = mde.digest(digest);
    428                         }
    429 
    430                         if (debug != null) {
    431                           debug.println("Signature Block File: " +
    432                                    name + " digest=" + digest.getAlgorithm());
    433                           debug.println("  expected " + toHex(expected));
    434                           debug.println("  computed " + toHex(computed));
    435                           debug.println();
    436                         }
    437 
    438                         if (MessageDigest.isEqual(computed, expected)) {
    439                             oneDigestVerified = true;
    440                             ok = true;
    441                         } else {
    442                             // attempt to fallback to the workaround
    443                             if (!workaround) {
    444                                computed = mde.digestWorkaround(digest);
    445                                if (MessageDigest.isEqual(computed, expected)) {
    446                                    if (debug != null) {
    447                                        debug.println("  re-computed " + toHex(computed));
    448                                        debug.println();
    449                                    }
    450                                    workaround = true;
    451                                    oneDigestVerified = true;
    452                                    ok = true;
    453                                }
    454                             }
    455                         }
    456                         if (!ok){
    457                             throw new SecurityException("invalid " +
    458                                        digest.getAlgorithm() +
    459                                        " signature file digest for " + name);
    460                         }
    461                     }
    462                 }
    463             }
    464         }
    465         return oneDigestVerified;
    466     }
    467 
    468     /**
    469      * Given the PKCS7 block and SignerInfo[], create an array of
    470      * CodeSigner objects. We do this only *once* for a given
    471      * signature block file.
    472      */
    473     private CodeSigner[] getSigners(SignerInfo infos[], PKCS7 block)
    474         throws IOException, NoSuchAlgorithmException, SignatureException,
    475             CertificateException {
    476 
    477         ArrayList<CodeSigner> signers = null;
    478 
    479         for (int i = 0; i < infos.length; i++) {
    480 
    481             SignerInfo info = infos[i];
    482             ArrayList<X509Certificate> chain = info.getCertificateChain(block);
    483             CertPath certChain = certificateFactory.generateCertPath(chain);
    484             if (signers == null) {
    485                 signers = new ArrayList<CodeSigner>();
    486             }
    487             // Append the new code signer
    488             signers.add(new CodeSigner(certChain, getTimestamp(info)));
    489 
    490             if (debug != null) {
    491                 debug.println("Signature Block Certificate: " +
    492                     chain.get(0));
    493             }
    494         }
    495 
    496         if (signers != null) {
    497             return signers.toArray(new CodeSigner[signers.size()]);
    498         } else {
    499             return null;
    500         }
    501     }
    502 
    503     /*
    504      * Examines a signature timestamp token to generate a timestamp object.
    505      *
    506      * Examines the signer's unsigned attributes for a
    507      * <tt>signatureTimestampToken</tt> attribute. If present,
    508      * then it is parsed to extract the date and time at which the
    509      * timestamp was generated.
    510      *
    511      * @param info A signer information element of a PKCS 7 block.
    512      *
    513      * @return A timestamp token or null if none is present.
    514      * @throws IOException if an error is encountered while parsing the
    515      *         PKCS7 data.
    516      * @throws NoSuchAlgorithmException if an error is encountered while
    517      *         verifying the PKCS7 object.
    518      * @throws SignatureException if an error is encountered while
    519      *         verifying the PKCS7 object.
    520      * @throws CertificateException if an error is encountered while generating
    521      *         the TSA's certpath.
    522      */
    523     private Timestamp getTimestamp(SignerInfo info)
    524         throws IOException, NoSuchAlgorithmException, SignatureException,
    525             CertificateException {
    526 
    527         Timestamp timestamp = null;
    528 
    529         // Extract the signer's unsigned attributes
    530         PKCS9Attributes unsignedAttrs = info.getUnauthenticatedAttributes();
    531         if (unsignedAttrs != null) {
    532             PKCS9Attribute timestampTokenAttr =
    533                 unsignedAttrs.getAttribute("signatureTimestampToken");
    534             if (timestampTokenAttr != null) {
    535                 PKCS7 timestampToken =
    536                     new PKCS7((byte[])timestampTokenAttr.getValue());
    537                 // Extract the content (an encoded timestamp token info)
    538                 byte[] encodedTimestampTokenInfo =
    539                     timestampToken.getContentInfo().getData();
    540                 // Extract the signer (the Timestamping Authority)
    541                 // while verifying the content
    542                 SignerInfo[] tsa =
    543                     timestampToken.verify(encodedTimestampTokenInfo);
    544                 // Expect only one signer
    545                 ArrayList<X509Certificate> chain =
    546                                 tsa[0].getCertificateChain(timestampToken);
    547                 CertPath tsaChain = certificateFactory.generateCertPath(chain);
    548                 // Create a timestamp token info object
    549                 TimestampToken timestampTokenInfo =
    550                     new TimestampToken(encodedTimestampTokenInfo);
    551                 // Check that the signature timestamp applies to this signature
    552                 verifyTimestamp(timestampTokenInfo, info.getEncryptedDigest());
    553                 // Create a timestamp object
    554                 timestamp =
    555                     new Timestamp(timestampTokenInfo.getDate(), tsaChain);
    556             }
    557         }
    558         return timestamp;
    559     }
    560 
    561     /*
    562      * Check that the signature timestamp applies to this signature.
    563      * Match the hash present in the signature timestamp token against the hash
    564      * of this signature.
    565      */
    566     private void verifyTimestamp(TimestampToken token, byte[] signature)
    567         throws NoSuchAlgorithmException, SignatureException {
    568 
    569         MessageDigest md =
    570             MessageDigest.getInstance(token.getHashAlgorithm().getName());
    571 
    572         if (!Arrays.equals(token.getHashedMessage(), md.digest(signature))) {
    573             throw new SignatureException("Signature timestamp (#" +
    574                 token.getSerialNumber() + ") generated on " + token.getDate() +
    575                 " is inapplicable");
    576         }
    577 
    578         if (debug != null) {
    579             debug.println();
    580             debug.println("Detected signature timestamp (#" +
    581                 token.getSerialNumber() + ") generated on " + token.getDate());
    582             debug.println();
    583         }
    584     }
    585 
    586     // for the toHex function
    587     private static final char[] hexc =
    588             {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
    589     /**
    590      * convert a byte array to a hex string for debugging purposes
    591      * @param data the binary data to be converted to a hex string
    592      * @return an ASCII hex string
    593      */
    594 
    595     static String toHex(byte[] data) {
    596 
    597         StringBuffer sb = new StringBuffer(data.length*2);
    598 
    599         for (int i=0; i<data.length; i++) {
    600             sb.append(hexc[(data[i] >>4) & 0x0f]);
    601             sb.append(hexc[data[i] & 0x0f]);
    602         }
    603         return sb.toString();
    604     }
    605 
    606     // returns true if set contains signer
    607     static boolean contains(CodeSigner[] set, CodeSigner signer)
    608     {
    609         for (int i = 0; i < set.length; i++) {
    610             if (set[i].equals(signer))
    611                 return true;
    612         }
    613         return false;
    614     }
    615 
    616     // returns true if subset is a subset of set
    617     static boolean isSubSet(CodeSigner[] subset, CodeSigner[] set)
    618     {
    619         // check for the same object
    620         if (set == subset)
    621             return true;
    622 
    623         boolean match;
    624         for (int i = 0; i < subset.length; i++) {
    625             if (!contains(set, subset[i]))
    626                 return false;
    627         }
    628         return true;
    629     }
    630 
    631     /**
    632      * returns true if signer contains exactly the same code signers as
    633      * oldSigner and newSigner, false otherwise. oldSigner
    634      * is allowed to be null.
    635      */
    636     static boolean matches(CodeSigner[] signers, CodeSigner[] oldSigners,
    637         CodeSigner[] newSigners) {
    638 
    639         // special case
    640         if ((oldSigners == null) && (signers == newSigners))
    641             return true;
    642 
    643         boolean match;
    644 
    645         // make sure all oldSigners are in signers
    646         if ((oldSigners != null) && !isSubSet(oldSigners, signers))
    647             return false;
    648 
    649         // make sure all newSigners are in signers
    650         if (!isSubSet(newSigners, signers)) {
    651             return false;
    652         }
    653 
    654         // now make sure all the code signers in signers are
    655         // also in oldSigners or newSigners
    656 
    657         for (int i = 0; i < signers.length; i++) {
    658             boolean found =
    659                 ((oldSigners != null) && contains(oldSigners, signers[i])) ||
    660                 contains(newSigners, signers[i]);
    661             if (!found)
    662                 return false;
    663         }
    664         return true;
    665     }
    666 
    667     void updateSigners(CodeSigner[] newSigners,
    668         Hashtable<String, CodeSigner[]> signers, String name) {
    669 
    670         CodeSigner[] oldSigners = signers.get(name);
    671 
    672         // search through the cache for a match, go in reverse order
    673         // as we are more likely to find a match with the last one
    674         // added to the cache
    675 
    676         CodeSigner[] cachedSigners;
    677         for (int i = signerCache.size() - 1; i != -1; i--) {
    678             cachedSigners = signerCache.get(i);
    679             if (matches(cachedSigners, oldSigners, newSigners)) {
    680                 signers.put(name, cachedSigners);
    681                 return;
    682             }
    683         }
    684 
    685         if (oldSigners == null) {
    686             cachedSigners = newSigners;
    687         } else {
    688             cachedSigners =
    689                 new CodeSigner[oldSigners.length + newSigners.length];
    690             System.arraycopy(oldSigners, 0, cachedSigners, 0,
    691                 oldSigners.length);
    692             System.arraycopy(newSigners, 0, cachedSigners, oldSigners.length,
    693                 newSigners.length);
    694         }
    695         signerCache.add(cachedSigners);
    696         signers.put(name, cachedSigners);
    697     }
    698 }
    699