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