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