1 /*
   2  * Copyright (c) 1997, 2012, 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.tools.jarsigner;
  27 
  28 import java.io.*;
  29 import java.util.*;
  30 import java.util.zip.*;
  31 import java.util.jar.*;
  32 import java.math.BigInteger;
  33 import java.net.URI;
  34 import java.net.URISyntaxException;
  35 import java.text.Collator;
  36 import java.text.MessageFormat;
  37 import java.security.cert.Certificate;
  38 import java.security.cert.X509Certificate;
  39 import java.security.cert.CertificateException;
  40 import java.security.*;
  41 import java.lang.reflect.Constructor;
  42 
  43 import com.sun.jarsigner.ContentSigner;
  44 import com.sun.jarsigner.ContentSignerParameters;
  45 import java.net.SocketTimeoutException;
  46 import java.net.URL;
  47 import java.net.URLClassLoader;
  48 import java.security.cert.CertPath;
  49 import java.security.cert.CertPathValidator;
  50 import java.security.cert.CertificateExpiredException;
  51 import java.security.cert.CertificateFactory;
  52 import java.security.cert.CertificateNotYetValidException;
  53 import java.security.cert.PKIXParameters;
  54 import java.security.cert.TrustAnchor;
  55 import java.util.Map.Entry;
  56 import sun.security.tools.KeyStoreUtil;
  57 import sun.security.tools.PathList;
  58 import sun.security.x509.*;
  59 import sun.security.util.*;
  60 import sun.misc.BASE64Encoder;
  61 
  62 
  63 /**
  64  * <p>The jarsigner utility.
  65  *
  66  * The exit codes for the main method are:
  67  *
  68  * 0: success
  69  * 1: any error that the jar cannot be signed or verified, including:
  70  *      keystore loading error
  71  *      TSP communication error
  72  *      jarsigner command line error...
  73  * otherwise: error codes from -strict
  74  *
  75  * @author Roland Schemers
  76  * @author Jan Luehe
  77  */
  78 
  79 public class Main {
  80 
  81     // for i18n
  82     private static final java.util.ResourceBundle rb =
  83         java.util.ResourceBundle.getBundle
  84         ("sun.security.tools.jarsigner.Resources");
  85     private static final Collator collator = Collator.getInstance();
  86     static {
  87         // this is for case insensitive string comparisions
  88         collator.setStrength(Collator.PRIMARY);
  89     }
  90 
  91     private static final String META_INF = "META-INF/";
  92 
  93     // prefix for new signature-related files in META-INF directory
  94     private static final String SIG_PREFIX = META_INF + "SIG-";
  95 
  96     private static final Class[] PARAM_STRING = { String.class };
  97 
  98     private static final String NONE = "NONE";
  99     private static final String P11KEYSTORE = "PKCS11";
 100 
 101     private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds
 102 
 103     // Attention:
 104     // This is the entry that get launched by the security tool jarsigner.
 105     public static void main(String args[]) throws Exception {
 106         Main js = new Main();
 107         js.run(args);
 108     }
 109 
 110     static final String VERSION = "1.0";
 111 
 112     static final int IN_KEYSTORE = 0x01;        // signer is in keystore
 113     static final int IN_SCOPE = 0x02;
 114     static final int NOT_ALIAS = 0x04;          // alias list is NOT empty and
 115                                                 // signer is not in alias list
 116     static final int SIGNED_BY_ALIAS = 0x08;    // signer is in alias list
 117 
 118     X509Certificate[] certChain;    // signer's cert chain (when composing)
 119     PrivateKey privateKey;          // private key
 120     KeyStore store;                 // the keystore specified by -keystore
 121                                     // or the default keystore, never null
 122 
 123     String keystore; // key store file
 124     boolean nullStream = false; // null keystore input stream (NONE)
 125     boolean token = false; // token-based keystore
 126     String jarfile;  // jar files to sign or verify
 127     String alias;    // alias to sign jar with
 128     List<String> ckaliases = new ArrayList<>(); // aliases in -verify
 129     char[] storepass; // keystore password
 130     boolean protectedPath; // protected authentication path
 131     String storetype; // keystore type
 132     String providerName; // provider name
 133     Vector<String> providers = null; // list of providers
 134     // arguments for provider constructors
 135     HashMap<String,String> providerArgs = new HashMap<>();
 136     char[] keypass; // private key password
 137     String sigfile; // name of .SF file
 138     String sigalg; // name of signature algorithm
 139     String digestalg = "SHA-256"; // name of digest algorithm
 140     String signedjar; // output filename
 141     String tsaUrl; // location of the Timestamping Authority
 142     String tsaAlias; // alias for the Timestamping Authority's certificate
 143     String altCertChain; // file to read alternative cert chain from
 144     boolean verify = false; // verify the jar
 145     String verbose = null; // verbose output when signing/verifying
 146     boolean showcerts = false; // show certs when verifying
 147     boolean debug = false; // debug
 148     boolean signManifest = true; // "sign" the whole manifest
 149     boolean externalSF = true; // leave the .SF out of the PKCS7 block
 150     boolean strict = false;  // treat warnings as error
 151 
 152     // read zip entry raw bytes
 153     private ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
 154     private byte[] buffer = new byte[8192];
 155     private ContentSigner signingMechanism = null;
 156     private String altSignerClass = null;
 157     private String altSignerClasspath = null;
 158     private ZipFile zipFile = null;
 159 
 160     private boolean hasExpiredCert = false;
 161     private boolean hasExpiringCert = false;
 162     private boolean notYetValidCert = false;
 163     private boolean chainNotValidated = false;
 164     private boolean notSignedByAlias = false;
 165     private boolean aliasNotInStore = false;
 166     private boolean hasUnsignedEntry = false;
 167     private boolean badKeyUsage = false;
 168     private boolean badExtendedKeyUsage = false;
 169     private boolean badNetscapeCertType = false;
 170 
 171     CertificateFactory certificateFactory;
 172     CertPathValidator validator;
 173     PKIXParameters pkixParameters;
 174 
 175     public void run(String args[]) {
 176         try {
 177             parseArgs(args);
 178 
 179             // Try to load and install the specified providers
 180             if (providers != null) {
 181                 ClassLoader cl = ClassLoader.getSystemClassLoader();
 182                 Enumeration<String> e = providers.elements();
 183                 while (e.hasMoreElements()) {
 184                     String provName = e.nextElement();
 185                     Class<?> provClass;
 186                     if (cl != null) {
 187                         provClass = cl.loadClass(provName);
 188                     } else {
 189                         provClass = Class.forName(provName);
 190                     }
 191 
 192                     String provArg = providerArgs.get(provName);
 193                     Object obj;
 194                     if (provArg == null) {
 195                         obj = provClass.newInstance();
 196                     } else {
 197                         Constructor<?> c =
 198                                 provClass.getConstructor(PARAM_STRING);
 199                         obj = c.newInstance(provArg);
 200                     }
 201 
 202                     if (!(obj instanceof Provider)) {
 203                         MessageFormat form = new MessageFormat(rb.getString
 204                             ("provName.not.a.provider"));
 205                         Object[] source = {provName};
 206                         throw new Exception(form.format(source));
 207                     }
 208                     Security.addProvider((Provider)obj);
 209                 }
 210             }
 211 
 212             if (verify) {
 213                 try {
 214                     loadKeyStore(keystore, false);
 215                 } catch (Exception e) {
 216                     if ((keystore != null) || (storepass != null)) {
 217                         System.out.println(rb.getString("jarsigner.error.") +
 218                                         e.getMessage());
 219                         System.exit(1);
 220                     }
 221                 }
 222                 /*              if (debug) {
 223                     SignatureFileVerifier.setDebug(true);
 224                     ManifestEntryVerifier.setDebug(true);
 225                 }
 226                 */
 227                 verifyJar(jarfile);
 228             } else {
 229                 loadKeyStore(keystore, true);
 230                 getAliasInfo(alias);
 231 
 232                 // load the alternative signing mechanism
 233                 if (altSignerClass != null) {
 234                     signingMechanism = loadSigningMechanism(altSignerClass,
 235                         altSignerClasspath);
 236                 }
 237                 signJar(jarfile, alias, args);
 238             }
 239         } catch (Exception e) {
 240             System.out.println(rb.getString("jarsigner.error.") + e);
 241             if (debug) {
 242                 e.printStackTrace();
 243             }
 244             System.exit(1);
 245         } finally {
 246             // zero-out private key password
 247             if (keypass != null) {
 248                 Arrays.fill(keypass, ' ');
 249                 keypass = null;
 250             }
 251             // zero-out keystore password
 252             if (storepass != null) {
 253                 Arrays.fill(storepass, ' ');
 254                 storepass = null;
 255             }
 256         }
 257 
 258         if (strict) {
 259             int exitCode = 0;
 260             if (hasExpiringCert) {
 261                 exitCode |= 2;
 262             }
 263             if (chainNotValidated || hasExpiredCert || notYetValidCert) {
 264                 exitCode |= 4;
 265             }
 266             if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) {
 267                 exitCode |= 8;
 268             }
 269             if (hasUnsignedEntry) {
 270                 exitCode |= 16;
 271             }
 272             if (notSignedByAlias || aliasNotInStore) {
 273                 exitCode |= 32;
 274             }
 275             if (exitCode != 0) {
 276                 System.exit(exitCode);
 277             }
 278         }
 279     }
 280 
 281     /*
 282      * Parse command line arguments.
 283      */
 284     void parseArgs(String args[]) {
 285         /* parse flags */
 286         int n = 0;
 287 
 288         if (args.length == 0) fullusage();
 289         for (n=0; n < args.length; n++) {
 290 
 291             String flags = args[n];
 292             String modifier = null;
 293             if (flags.charAt(0) == '-') {
 294                 int pos = flags.indexOf(':');
 295                 if (pos > 0) {
 296                     modifier = flags.substring(pos+1);
 297                     flags = flags.substring(0, pos);
 298                 }
 299             }
 300 
 301             if (collator.compare(flags, "-keystore") == 0) {
 302                 if (++n == args.length) usageNoArg();
 303                 keystore = args[n];
 304             } else if (collator.compare(flags, "-storepass") ==0) {
 305                 if (++n == args.length) usageNoArg();
 306                 storepass = getPass(modifier, args[n]);
 307             } else if (collator.compare(flags, "-storetype") ==0) {
 308                 if (++n == args.length) usageNoArg();
 309                 storetype = args[n];
 310             } else if (collator.compare(flags, "-providerName") ==0) {
 311                 if (++n == args.length) usageNoArg();
 312                 providerName = args[n];
 313             } else if ((collator.compare(flags, "-provider") == 0) ||
 314                         (collator.compare(flags, "-providerClass") == 0)) {
 315                 if (++n == args.length) usageNoArg();
 316                 if (providers == null) {
 317                     providers = new Vector<String>(3);
 318                 }
 319                 providers.add(args[n]);
 320 
 321                 if (args.length > (n+1)) {
 322                     flags = args[n+1];
 323                     if (collator.compare(flags, "-providerArg") == 0) {
 324                         if (args.length == (n+2)) usageNoArg();
 325                         providerArgs.put(args[n], args[n+2]);
 326                         n += 2;
 327                     }
 328                 }
 329             } else if (collator.compare(flags, "-protected") ==0) {
 330                 protectedPath = true;
 331             } else if (collator.compare(flags, "-certchain") ==0) {
 332                 if (++n == args.length) usageNoArg();
 333                 altCertChain = args[n];
 334             } else if (collator.compare(flags, "-debug") ==0) {
 335                 debug = true;
 336             } else if (collator.compare(flags, "-keypass") ==0) {
 337                 if (++n == args.length) usageNoArg();
 338                 keypass = getPass(modifier, args[n]);
 339             } else if (collator.compare(flags, "-sigfile") ==0) {
 340                 if (++n == args.length) usageNoArg();
 341                 sigfile = args[n];
 342             } else if (collator.compare(flags, "-signedjar") ==0) {
 343                 if (++n == args.length) usageNoArg();
 344                 signedjar = args[n];
 345             } else if (collator.compare(flags, "-tsa") ==0) {
 346                 if (++n == args.length) usageNoArg();
 347                 tsaUrl = args[n];
 348             } else if (collator.compare(flags, "-tsacert") ==0) {
 349                 if (++n == args.length) usageNoArg();
 350                 tsaAlias = args[n];
 351             } else if (collator.compare(flags, "-altsigner") ==0) {
 352                 if (++n == args.length) usageNoArg();
 353                 altSignerClass = args[n];
 354             } else if (collator.compare(flags, "-altsignerpath") ==0) {
 355                 if (++n == args.length) usageNoArg();
 356                 altSignerClasspath = args[n];
 357             } else if (collator.compare(flags, "-sectionsonly") ==0) {
 358                 signManifest = false;
 359             } else if (collator.compare(flags, "-internalsf") ==0) {
 360                 externalSF = false;
 361             } else if (collator.compare(flags, "-verify") ==0) {
 362                 verify = true;
 363             } else if (collator.compare(flags, "-verbose") ==0) {
 364                 verbose = (modifier != null) ? modifier : "all";
 365             } else if (collator.compare(flags, "-sigalg") ==0) {
 366                 if (++n == args.length) usageNoArg();
 367                 sigalg = args[n];
 368             } else if (collator.compare(flags, "-digestalg") ==0) {
 369                 if (++n == args.length) usageNoArg();
 370                 digestalg = args[n];
 371             } else if (collator.compare(flags, "-certs") ==0) {
 372                 showcerts = true;
 373             } else if (collator.compare(flags, "-strict") ==0) {
 374                 strict = true;
 375             } else if (collator.compare(flags, "-h") == 0 ||
 376                         collator.compare(flags, "-help") == 0) {
 377                 fullusage();
 378             } else {
 379                 if (!flags.startsWith("-")) {
 380                     if (jarfile == null) {
 381                         jarfile = flags;
 382                     } else {
 383                         alias = flags;
 384                         ckaliases.add(alias);
 385                     }
 386                 } else {
 387                     System.err.println(
 388                             rb.getString("Illegal.option.") + flags);
 389                     usage();
 390                 }
 391             }
 392         }
 393 
 394         // -certs must always be specified with -verbose
 395         if (verbose == null) showcerts = false;
 396 
 397         if (jarfile == null) {
 398             System.err.println(rb.getString("Please.specify.jarfile.name"));
 399             usage();
 400         }
 401         if (!verify && alias == null) {
 402             System.err.println(rb.getString("Please.specify.alias.name"));
 403             usage();
 404         }
 405         if (!verify && ckaliases.size() > 1) {
 406             System.err.println(rb.getString("Only.one.alias.can.be.specified"));
 407             usage();
 408         }
 409 
 410         if (storetype == null) {
 411             storetype = KeyStore.getDefaultType();
 412         }
 413         storetype = KeyStoreUtil.niceStoreTypeName(storetype);
 414 
 415         try {
 416             if (signedjar != null && new File(signedjar).getCanonicalPath().equals(
 417                     new File(jarfile).getCanonicalPath())) {
 418                 signedjar = null;
 419             }
 420         } catch (IOException ioe) {
 421             // File system error?
 422             // Just ignore it.
 423         }
 424 
 425         if (P11KEYSTORE.equalsIgnoreCase(storetype) ||
 426                 KeyStoreUtil.isWindowsKeyStore(storetype)) {
 427             token = true;
 428             if (keystore == null) {
 429                 keystore = NONE;
 430             }
 431         }
 432 
 433         if (NONE.equals(keystore)) {
 434             nullStream = true;
 435         }
 436 
 437         if (token && !nullStream) {
 438             System.err.println(MessageFormat.format(rb.getString
 439                 (".keystore.must.be.NONE.if.storetype.is.{0}"), storetype));
 440             usage();
 441         }
 442 
 443         if (token && keypass != null) {
 444             System.err.println(MessageFormat.format(rb.getString
 445                 (".keypass.can.not.be.specified.if.storetype.is.{0}"), storetype));
 446             usage();
 447         }
 448 
 449         if (protectedPath) {
 450             if (storepass != null || keypass != null) {
 451                 System.err.println(rb.getString
 452                         ("If.protected.is.specified.then.storepass.and.keypass.must.not.be.specified"));
 453                 usage();
 454             }
 455         }
 456         if (KeyStoreUtil.isWindowsKeyStore(storetype)) {
 457             if (storepass != null || keypass != null) {
 458                 System.err.println(rb.getString
 459                         ("If.keystore.is.not.password.protected.then.storepass.and.keypass.must.not.be.specified"));
 460                 usage();
 461             }
 462         }
 463     }
 464 
 465     static char[] getPass(String modifier, String arg) {
 466         char[] output = KeyStoreUtil.getPassWithModifier(modifier, arg, rb);
 467         if (output != null) return output;
 468         usage();
 469         return null;    // Useless, usage() already exit
 470     }
 471 
 472     static void usageNoArg() {
 473         System.out.println(rb.getString("Option.lacks.argument"));
 474         usage();
 475     }
 476 
 477     static void usage() {
 478         System.out.println();
 479         System.out.println(rb.getString("Please.type.jarsigner.help.for.usage"));
 480         System.exit(1);
 481     }
 482 
 483     static void fullusage() {
 484         System.out.println(rb.getString
 485                 ("Usage.jarsigner.options.jar.file.alias"));
 486         System.out.println(rb.getString
 487                 (".jarsigner.verify.options.jar.file.alias."));
 488         System.out.println();
 489         System.out.println(rb.getString
 490                 (".keystore.url.keystore.location"));
 491         System.out.println();
 492         System.out.println(rb.getString
 493                 (".storepass.password.password.for.keystore.integrity"));
 494         System.out.println();
 495         System.out.println(rb.getString
 496                 (".storetype.type.keystore.type"));
 497         System.out.println();
 498         System.out.println(rb.getString
 499                 (".keypass.password.password.for.private.key.if.different."));
 500         System.out.println();
 501         System.out.println(rb.getString
 502                 (".certchain.file.name.of.alternative.certchain.file"));
 503         System.out.println();
 504         System.out.println(rb.getString
 505                 (".sigfile.file.name.of.SF.DSA.file"));
 506         System.out.println();
 507         System.out.println(rb.getString
 508                 (".signedjar.file.name.of.signed.JAR.file"));
 509         System.out.println();
 510         System.out.println(rb.getString
 511                 (".digestalg.algorithm.name.of.digest.algorithm"));
 512         System.out.println();
 513         System.out.println(rb.getString
 514                 (".sigalg.algorithm.name.of.signature.algorithm"));
 515         System.out.println();
 516         System.out.println(rb.getString
 517                 (".verify.verify.a.signed.JAR.file"));
 518         System.out.println();
 519         System.out.println(rb.getString
 520                 (".verbose.suboptions.verbose.output.when.signing.verifying."));
 521         System.out.println(rb.getString
 522                 (".suboptions.can.be.all.grouped.or.summary"));
 523         System.out.println();
 524         System.out.println(rb.getString
 525                 (".certs.display.certificates.when.verbose.and.verifying"));
 526         System.out.println();
 527         System.out.println(rb.getString
 528                 (".tsa.url.location.of.the.Timestamping.Authority"));
 529         System.out.println();
 530         System.out.println(rb.getString
 531                 (".tsacert.alias.public.key.certificate.for.Timestamping.Authority"));
 532         System.out.println();
 533         System.out.println(rb.getString
 534                 (".altsigner.class.class.name.of.an.alternative.signing.mechanism"));
 535         System.out.println();
 536         System.out.println(rb.getString
 537                 (".altsignerpath.pathlist.location.of.an.alternative.signing.mechanism"));
 538         System.out.println();
 539         System.out.println(rb.getString
 540                 (".internalsf.include.the.SF.file.inside.the.signature.block"));
 541         System.out.println();
 542         System.out.println(rb.getString
 543                 (".sectionsonly.don.t.compute.hash.of.entire.manifest"));
 544         System.out.println();
 545         System.out.println(rb.getString
 546                 (".protected.keystore.has.protected.authentication.path"));
 547         System.out.println();
 548         System.out.println(rb.getString
 549                 (".providerName.name.provider.name"));
 550         System.out.println();
 551         System.out.println(rb.getString
 552                 (".providerClass.class.name.of.cryptographic.service.provider.s"));
 553         System.out.println(rb.getString
 554                 (".providerArg.arg.master.class.file.and.constructor.argument"));
 555         System.out.println();
 556         System.out.println(rb.getString
 557                 (".strict.treat.warnings.as.errors"));
 558         System.out.println();
 559 
 560         System.exit(0);
 561     }
 562 
 563     void verifyJar(String jarName)
 564         throws Exception
 565     {
 566         boolean anySigned = false;  // if there exists entry inside jar signed
 567         JarFile jf = null;
 568 
 569         try {
 570             jf = new JarFile(jarName, true);
 571             Vector<JarEntry> entriesVec = new Vector<>();
 572             byte[] buffer = new byte[8192];
 573 
 574             Enumeration<JarEntry> entries = jf.entries();
 575             while (entries.hasMoreElements()) {
 576                 JarEntry je = entries.nextElement();
 577                 entriesVec.addElement(je);
 578                 InputStream is = null;
 579                 try {
 580                     is = jf.getInputStream(je);
 581                     int n;
 582                     while ((n = is.read(buffer, 0, buffer.length)) != -1) {
 583                         // we just read. this will throw a SecurityException
 584                         // if  a signature/digest check fails.
 585                     }
 586                 } finally {
 587                     if (is != null) {
 588                         is.close();
 589                     }
 590                 }
 591             }
 592 
 593             Manifest man = jf.getManifest();
 594 
 595             // The map to record display info, only used when -verbose provided
 596             //      key: signer info string
 597             //      value: the list of files with common key
 598             Map<String,List<String>> output = new LinkedHashMap<>();
 599 
 600             if (man != null) {
 601                 if (verbose != null) System.out.println();
 602                 Enumeration<JarEntry> e = entriesVec.elements();
 603 
 604                 String tab = rb.getString("6SPACE");
 605 
 606                 while (e.hasMoreElements()) {
 607                     JarEntry je = e.nextElement();
 608                     String name = je.getName();
 609                     CodeSigner[] signers = je.getCodeSigners();
 610                     boolean isSigned = (signers != null);
 611                     anySigned |= isSigned;
 612                     hasUnsignedEntry |= !je.isDirectory() && !isSigned
 613                                         && !signatureRelated(name);
 614 
 615                     int inStoreOrScope = inKeyStore(signers);
 616 
 617                     boolean inStore = (inStoreOrScope & IN_KEYSTORE) != 0;
 618                     boolean inScope = (inStoreOrScope & IN_SCOPE) != 0;
 619 
 620                     notSignedByAlias |= (inStoreOrScope & NOT_ALIAS) != 0;
 621                     if (keystore != null) {
 622                         aliasNotInStore |= isSigned && (!inStore && !inScope);
 623                     }
 624 
 625                     // Only used when -verbose provided
 626                     StringBuffer sb = null;
 627                     if (verbose != null) {
 628                         sb = new StringBuffer();
 629                         boolean inManifest =
 630                             ((man.getAttributes(name) != null) ||
 631                              (man.getAttributes("./"+name) != null) ||
 632                              (man.getAttributes("/"+name) != null));
 633                         sb.append(
 634                           (isSigned ? rb.getString("s") : rb.getString("SPACE")) +
 635                           (inManifest ? rb.getString("m") : rb.getString("SPACE")) +
 636                           (inStore ? rb.getString("k") : rb.getString("SPACE")) +
 637                           (inScope ? rb.getString("i") : rb.getString("SPACE")) +
 638                           ((inStoreOrScope & NOT_ALIAS) != 0 ?"X":" ") +
 639                           rb.getString("SPACE"));
 640                         sb.append("|");
 641                     }
 642 
 643                     // When -certs provided, display info has extra empty
 644                     // lines at the beginning and end.
 645                     if (isSigned) {
 646                         if (showcerts) sb.append('\n');
 647                         for (CodeSigner signer: signers) {
 648                             // signerInfo() must be called even if -verbose
 649                             // not provided. The method updates various
 650                             // warning flags.
 651                             String si = signerInfo(signer, tab);
 652                             if (showcerts) {
 653                                 sb.append(si);
 654                                 sb.append('\n');
 655                             }
 656                         }
 657                     } else if (showcerts && !verbose.equals("all")) {
 658                         // Print no info for unsigned entries when -verbose:all,
 659                         // to be consistent with old behavior.
 660                         if (signatureRelated(name)) {
 661                             sb.append("\n" + tab + rb.getString(
 662                                     ".Signature.related.entries.") + "\n\n");
 663                         } else {
 664                             sb.append("\n" + tab + rb.getString(
 665                                     ".Unsigned.entries.") + "\n\n");
 666                         }
 667                     }
 668 
 669                     if (verbose != null) {
 670                         String label = sb.toString();
 671                         if (signatureRelated(name)) {
 672                             // Entries inside META-INF and other unsigned
 673                             // entries are grouped separately.
 674                             label = "-" + label;
 675                         }
 676 
 677                         // The label finally contains 2 parts separated by '|':
 678                         // The legend displayed before the entry names, and
 679                         // the cert info (if -certs specfied).
 680 
 681                         if (!output.containsKey(label)) {
 682                             output.put(label, new ArrayList<String>());
 683                         }
 684 
 685                         StringBuffer fb = new StringBuffer();
 686                         String s = Long.toString(je.getSize());
 687                         for (int i = 6 - s.length(); i > 0; --i) {
 688                             fb.append(' ');
 689                         }
 690                         fb.append(s).append(' ').
 691                                 append(new Date(je.getTime()).toString());
 692                         fb.append(' ').append(name);
 693 
 694                         output.get(label).add(fb.toString());
 695                     }
 696                 }
 697             }
 698             if (verbose != null) {
 699                 for (Entry<String,List<String>> s: output.entrySet()) {
 700                     List<String> files = s.getValue();
 701                     String key = s.getKey();
 702                     if (key.charAt(0) == '-') { // the signature-related group
 703                         key = key.substring(1);
 704                     }
 705                     int pipe = key.indexOf('|');
 706                     if (verbose.equals("all")) {
 707                         for (String f: files) {
 708                             System.out.println(key.substring(0, pipe) + f);
 709                             System.out.printf(key.substring(pipe+1));
 710                         }
 711                     } else {
 712                         if (verbose.equals("grouped")) {
 713                             for (String f: files) {
 714                                 System.out.println(key.substring(0, pipe) + f);
 715                             }
 716                         } else if (verbose.equals("summary")) {
 717                             System.out.print(key.substring(0, pipe));
 718                             if (files.size() > 1) {
 719                                 System.out.println(files.get(0) + " " +
 720                                         String.format(rb.getString(
 721                                         ".and.d.more."), files.size()-1));
 722                             } else {
 723                                 System.out.println(files.get(0));
 724                             }
 725                         }
 726                         System.out.printf(key.substring(pipe+1));
 727                     }
 728                 }
 729                 System.out.println();
 730                 System.out.println(rb.getString(
 731                     ".s.signature.was.verified."));
 732                 System.out.println(rb.getString(
 733                     ".m.entry.is.listed.in.manifest"));
 734                 System.out.println(rb.getString(
 735                     ".k.at.least.one.certificate.was.found.in.keystore"));
 736                 System.out.println(rb.getString(
 737                     ".i.at.least.one.certificate.was.found.in.identity.scope"));
 738                 if (ckaliases.size() > 0) {
 739                     System.out.println(rb.getString(
 740                         ".X.not.signed.by.specified.alias.es."));
 741                 }
 742                 System.out.println();
 743             }
 744             if (man == null)
 745                 System.out.println(rb.getString("no.manifest."));
 746 
 747             if (!anySigned) {
 748                 System.out.println(rb.getString(
 749                       "jar.is.unsigned.signatures.missing.or.not.parsable."));
 750             } else {
 751                 System.out.println(rb.getString("jar.verified."));
 752                 if (hasUnsignedEntry || hasExpiredCert || hasExpiringCert ||
 753                     badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
 754                     notYetValidCert || chainNotValidated ||
 755                     aliasNotInStore || notSignedByAlias) {
 756 
 757                     System.out.println();
 758                     System.out.println(rb.getString("Warning."));
 759                     if (badKeyUsage) {
 760                         System.out.println(
 761                             rb.getString("This.jar.contains.entries.whose.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
 762                     }
 763 
 764                     if (badExtendedKeyUsage) {
 765                         System.out.println(
 766                             rb.getString("This.jar.contains.entries.whose.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."));
 767                     }
 768 
 769                     if (badNetscapeCertType) {
 770                         System.out.println(
 771                             rb.getString("This.jar.contains.entries.whose.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."));
 772                     }
 773 
 774                     if (hasUnsignedEntry) {
 775                         System.out.println(rb.getString(
 776                             "This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked."));
 777                     }
 778                     if (hasExpiredCert) {
 779                         System.out.println(rb.getString(
 780                             "This.jar.contains.entries.whose.signer.certificate.has.expired."));
 781                     }
 782                     if (hasExpiringCert) {
 783                         System.out.println(rb.getString(
 784                             "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
 785                     }
 786                     if (notYetValidCert) {
 787                         System.out.println(rb.getString(
 788                             "This.jar.contains.entries.whose.signer.certificate.is.not.yet.valid."));
 789                     }
 790 
 791                     if (chainNotValidated) {
 792                         System.out.println(
 793                                 rb.getString("This.jar.contains.entries.whose.certificate.chain.is.not.validated."));
 794                     }
 795 
 796                     if (notSignedByAlias) {
 797                         System.out.println(
 798                                 rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es."));
 799                     }
 800 
 801                     if (aliasNotInStore) {
 802                         System.out.println(rb.getString("This.jar.contains.signed.entries.that.s.not.signed.by.alias.in.this.keystore."));
 803                     }
 804                     if (! (verbose != null && showcerts)) {
 805                         System.out.println();
 806                         System.out.println(rb.getString(
 807                             "Re.run.with.the.verbose.and.certs.options.for.more.details."));
 808                     }
 809                 }
 810             }
 811             return;
 812         } catch (Exception e) {
 813             System.out.println(rb.getString("jarsigner.") + e);
 814             if (debug) {
 815                 e.printStackTrace();
 816             }
 817         } finally { // close the resource
 818             if (jf != null) {
 819                 jf.close();
 820             }
 821         }
 822 
 823         System.exit(1);
 824     }
 825 
 826     private static MessageFormat validityTimeForm = null;
 827     private static MessageFormat notYetTimeForm = null;
 828     private static MessageFormat expiredTimeForm = null;
 829     private static MessageFormat expiringTimeForm = null;
 830 
 831     /*
 832      * Display some details about a certificate:
 833      *
 834      * [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"]
 835      * [<validity-period> | <expiry-warning>]
 836      *
 837      * Note: no newline character at the end
 838      */
 839     String printCert(String tab, Certificate c, boolean checkValidityPeriod,
 840         Date timestamp, boolean checkUsage) {
 841 
 842         StringBuilder certStr = new StringBuilder();
 843         String space = rb.getString("SPACE");
 844         X509Certificate x509Cert = null;
 845 
 846         if (c instanceof X509Certificate) {
 847             x509Cert = (X509Certificate) c;
 848             certStr.append(tab).append(x509Cert.getType())
 849                 .append(rb.getString("COMMA"))
 850                 .append(x509Cert.getSubjectDN().getName());
 851         } else {
 852             certStr.append(tab).append(c.getType());
 853         }
 854 
 855         String alias = storeHash.get(c);
 856         if (alias != null) {
 857             certStr.append(space).append(alias);
 858         }
 859 
 860         if (checkValidityPeriod && x509Cert != null) {
 861 
 862             certStr.append("\n").append(tab).append("[");
 863             Date notAfter = x509Cert.getNotAfter();
 864             try {
 865                 boolean printValidity = true;
 866                 if (timestamp == null) {
 867                     x509Cert.checkValidity();
 868                     // test if cert will expire within six months
 869                     if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) {
 870                         hasExpiringCert = true;
 871                         if (expiringTimeForm == null) {
 872                             expiringTimeForm = new MessageFormat(
 873                                 rb.getString("certificate.will.expire.on"));
 874                         }
 875                         Object[] source = { notAfter };
 876                         certStr.append(expiringTimeForm.format(source));
 877                         printValidity = false;
 878                     }
 879                 } else {
 880                     x509Cert.checkValidity(timestamp);
 881                 }
 882                 if (printValidity) {
 883                     if (validityTimeForm == null) {
 884                         validityTimeForm = new MessageFormat(
 885                             rb.getString("certificate.is.valid.from"));
 886                     }
 887                     Object[] source = { x509Cert.getNotBefore(), notAfter };
 888                     certStr.append(validityTimeForm.format(source));
 889                 }
 890             } catch (CertificateExpiredException cee) {
 891                 hasExpiredCert = true;
 892 
 893                 if (expiredTimeForm == null) {
 894                     expiredTimeForm = new MessageFormat(
 895                         rb.getString("certificate.expired.on"));
 896                 }
 897                 Object[] source = { notAfter };
 898                 certStr.append(expiredTimeForm.format(source));
 899 
 900             } catch (CertificateNotYetValidException cnyve) {
 901                 notYetValidCert = true;
 902 
 903                 if (notYetTimeForm == null) {
 904                     notYetTimeForm = new MessageFormat(
 905                         rb.getString("certificate.is.not.valid.until"));
 906                 }
 907                 Object[] source = { x509Cert.getNotBefore() };
 908                 certStr.append(notYetTimeForm.format(source));
 909             }
 910             certStr.append("]");
 911 
 912             if (checkUsage) {
 913                 boolean[] bad = new boolean[3];
 914                 checkCertUsage(x509Cert, bad);
 915                 if (bad[0] || bad[1] || bad[2]) {
 916                     String x = "";
 917                     if (bad[0]) {
 918                         x ="KeyUsage";
 919                     }
 920                     if (bad[1]) {
 921                         if (x.length() > 0) x = x + ", ";
 922                         x = x + "ExtendedKeyUsage";
 923                     }
 924                     if (bad[2]) {
 925                         if (x.length() > 0) x = x + ", ";
 926                         x = x + "NetscapeCertType";
 927                     }
 928                     certStr.append("\n").append(tab)
 929                         .append(MessageFormat.format(rb.getString(
 930                         ".{0}.extension.does.not.support.code.signing."), x));
 931                 }
 932             }
 933         }
 934         return certStr.toString();
 935     }
 936 
 937     private static MessageFormat signTimeForm = null;
 938 
 939     private String printTimestamp(String tab, Timestamp timestamp) {
 940 
 941         if (signTimeForm == null) {
 942             signTimeForm =
 943                 new MessageFormat(rb.getString("entry.was.signed.on"));
 944         }
 945         Object[] source = { timestamp.getTimestamp() };
 946 
 947         return new StringBuilder().append(tab).append("[")
 948             .append(signTimeForm.format(source)).append("]").toString();
 949     }
 950 
 951     private Map<CodeSigner,Integer> cacheForInKS = new IdentityHashMap<>();
 952 
 953     private int inKeyStoreForOneSigner(CodeSigner signer) {
 954         if (cacheForInKS.containsKey(signer)) {
 955             return cacheForInKS.get(signer);
 956         }
 957 
 958         boolean found = false;
 959         int result = 0;
 960         List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
 961         for (Certificate c : certs) {
 962             String alias = storeHash.get(c);
 963             if (alias != null) {
 964                 if (alias.startsWith("(")) {
 965                     result |= IN_KEYSTORE;
 966                 } else if (alias.startsWith("[")) {
 967                     result |= IN_SCOPE;
 968                 }
 969                 if (ckaliases.contains(alias.substring(1, alias.length() - 1))) {
 970                     result |= SIGNED_BY_ALIAS;
 971                 }
 972             } else {
 973                 if (store != null) {
 974                     try {
 975                         alias = store.getCertificateAlias(c);
 976                     } catch (KeyStoreException kse) {
 977                         // never happens, because keystore has been loaded
 978                     }
 979                     if (alias != null) {
 980                         storeHash.put(c, "(" + alias + ")");
 981                         found = true;
 982                         result |= IN_KEYSTORE;
 983                     }
 984                 }
 985                 if (ckaliases.contains(alias)) {
 986                     result |= SIGNED_BY_ALIAS;
 987                 }
 988             }
 989         }
 990         cacheForInKS.put(signer, result);
 991         return result;
 992     }
 993 
 994     Hashtable<Certificate, String> storeHash = new Hashtable<>();
 995 
 996     int inKeyStore(CodeSigner[] signers) {
 997 
 998         if (signers == null)
 999             return 0;
1000 
1001         int output = 0;
1002 
1003         for (CodeSigner signer: signers) {
1004             int result = inKeyStoreForOneSigner(signer);
1005             output |= result;
1006         }
1007         if (ckaliases.size() > 0 && (output & SIGNED_BY_ALIAS) == 0) {
1008             output |= NOT_ALIAS;
1009         }
1010         return output;
1011     }
1012 
1013     void signJar(String jarName, String alias, String[] args)
1014         throws Exception {
1015         boolean aliasUsed = false;
1016         X509Certificate tsaCert = null;
1017 
1018         if (sigfile == null) {
1019             sigfile = alias;
1020             aliasUsed = true;
1021         }
1022 
1023         if (sigfile.length() > 8) {
1024             sigfile = sigfile.substring(0, 8).toUpperCase(Locale.ENGLISH);
1025         } else {
1026             sigfile = sigfile.toUpperCase(Locale.ENGLISH);
1027         }
1028 
1029         StringBuilder tmpSigFile = new StringBuilder(sigfile.length());
1030         for (int j = 0; j < sigfile.length(); j++) {
1031             char c = sigfile.charAt(j);
1032             if (!
1033                 ((c>= 'A' && c<= 'Z') ||
1034                 (c>= '0' && c<= '9') ||
1035                 (c == '-') ||
1036                 (c == '_'))) {
1037                 if (aliasUsed) {
1038                     // convert illegal characters from the alias to be _'s
1039                     c = '_';
1040                 } else {
1041                  throw new
1042                    RuntimeException(rb.getString
1043                         ("signature.filename.must.consist.of.the.following.characters.A.Z.0.9.or."));
1044                 }
1045             }
1046             tmpSigFile.append(c);
1047         }
1048 
1049         sigfile = tmpSigFile.toString();
1050 
1051         String tmpJarName;
1052         if (signedjar == null) tmpJarName = jarName+".sig";
1053         else tmpJarName = signedjar;
1054 
1055         File jarFile = new File(jarName);
1056         File signedJarFile = new File(tmpJarName);
1057 
1058         // Open the jar (zip) file
1059         try {
1060             zipFile = new ZipFile(jarName);
1061         } catch (IOException ioe) {
1062             error(rb.getString("unable.to.open.jar.file.")+jarName, ioe);
1063         }
1064 
1065         FileOutputStream fos = null;
1066         try {
1067             fos = new FileOutputStream(signedJarFile);
1068         } catch (IOException ioe) {
1069             error(rb.getString("unable.to.create.")+tmpJarName, ioe);
1070         }
1071 
1072         PrintStream ps = new PrintStream(fos);
1073         ZipOutputStream zos = new ZipOutputStream(ps);
1074 
1075         /* First guess at what they might be - we don't xclude RSA ones. */
1076         String sfFilename = (META_INF + sigfile + ".SF").toUpperCase(Locale.ENGLISH);
1077         String bkFilename = (META_INF + sigfile + ".DSA").toUpperCase(Locale.ENGLISH);
1078 
1079         Manifest manifest = new Manifest();
1080         Map<String,Attributes> mfEntries = manifest.getEntries();
1081 
1082         // The Attributes of manifest before updating
1083         Attributes oldAttr = null;
1084 
1085         boolean mfModified = false;
1086         boolean mfCreated = false;
1087         byte[] mfRawBytes = null;
1088 
1089         try {
1090             MessageDigest digests[] = { MessageDigest.getInstance(digestalg) };
1091 
1092             // Check if manifest exists
1093             ZipEntry mfFile;
1094             if ((mfFile = getManifestFile(zipFile)) != null) {
1095                 // Manifest exists. Read its raw bytes.
1096                 mfRawBytes = getBytes(zipFile, mfFile);
1097                 manifest.read(new ByteArrayInputStream(mfRawBytes));
1098                 oldAttr = (Attributes)(manifest.getMainAttributes().clone());
1099             } else {
1100                 // Create new manifest
1101                 Attributes mattr = manifest.getMainAttributes();
1102                 mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
1103                                "1.0");
1104                 String javaVendor = System.getProperty("java.vendor");
1105                 String jdkVersion = System.getProperty("java.version");
1106                 mattr.putValue("Created-By", jdkVersion + " (" +javaVendor
1107                                + ")");
1108                 mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
1109                 mfCreated = true;
1110             }
1111 
1112             /*
1113              * For each entry in jar
1114              * (except for signature-related META-INF entries),
1115              * do the following:
1116              *
1117              * - if entry is not contained in manifest, add it to manifest;
1118              * - if entry is contained in manifest, calculate its hash and
1119              *   compare it with the one in the manifest; if they are
1120              *   different, replace the hash in the manifest with the newly
1121              *   generated one. (This may invalidate existing signatures!)
1122              */
1123             BASE64Encoder encoder = new JarBASE64Encoder();
1124             Vector<ZipEntry> mfFiles = new Vector<>();
1125 
1126             boolean wasSigned = false;
1127 
1128             for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
1129                         enum_.hasMoreElements();) {
1130                 ZipEntry ze = enum_.nextElement();
1131 
1132                 if (ze.getName().startsWith(META_INF)) {
1133                     // Store META-INF files in vector, so they can be written
1134                     // out first
1135                     mfFiles.addElement(ze);
1136 
1137                     if (SignatureFileVerifier.isBlockOrSF(
1138                             ze.getName().toUpperCase(Locale.ENGLISH))) {
1139                         wasSigned = true;
1140                     }
1141 
1142                     if (signatureRelated(ze.getName())) {
1143                         // ignore signature-related and manifest files
1144                         continue;
1145                     }
1146                 }
1147 
1148                 if (manifest.getAttributes(ze.getName()) != null) {
1149                     // jar entry is contained in manifest, check and
1150                     // possibly update its digest attributes
1151                     if (updateDigests(ze, zipFile, digests, encoder,
1152                                       manifest) == true) {
1153                         mfModified = true;
1154                     }
1155                 } else if (!ze.isDirectory()) {
1156                     // Add entry to manifest
1157                     Attributes attrs = getDigestAttributes(ze, zipFile,
1158                                                            digests,
1159                                                            encoder);
1160                     mfEntries.put(ze.getName(), attrs);
1161                     mfModified = true;
1162                 }
1163             }
1164 
1165             // Recalculate the manifest raw bytes if necessary
1166             if (mfModified) {
1167                 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1168                 manifest.write(baos);
1169                 if (wasSigned) {
1170                     byte[] newBytes = baos.toByteArray();
1171                     if (mfRawBytes != null
1172                             && oldAttr.equals(manifest.getMainAttributes())) {
1173 
1174                         /*
1175                          * Note:
1176                          *
1177                          * The Attributes object is based on HashMap and can handle
1178                          * continuation columns. Therefore, even if the contents are
1179                          * not changed (in a Map view), the bytes that it write()
1180                          * may be different from the original bytes that it read()
1181                          * from. Since the signature on the main attributes is based
1182                          * on raw bytes, we must retain the exact bytes.
1183                          */
1184 
1185                         int newPos = findHeaderEnd(newBytes);
1186                         int oldPos = findHeaderEnd(mfRawBytes);
1187 
1188                         if (newPos == oldPos) {
1189                             System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
1190                         } else {
1191                             // cat oldHead newTail > newBytes
1192                             byte[] lastBytes = new byte[oldPos +
1193                                     newBytes.length - newPos];
1194                             System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
1195                             System.arraycopy(newBytes, newPos, lastBytes, oldPos,
1196                                     newBytes.length - newPos);
1197                             newBytes = lastBytes;
1198                         }
1199                     }
1200                     mfRawBytes = newBytes;
1201                 } else {
1202                     mfRawBytes = baos.toByteArray();
1203                 }
1204             }
1205 
1206             // Write out the manifest
1207             if (mfModified) {
1208                 // manifest file has new length
1209                 mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
1210             }
1211             if (verbose != null) {
1212                 if (mfCreated) {
1213                     System.out.println(rb.getString(".adding.") +
1214                                         mfFile.getName());
1215                 } else if (mfModified) {
1216                     System.out.println(rb.getString(".updating.") +
1217                                         mfFile.getName());
1218                 }
1219             }
1220             zos.putNextEntry(mfFile);
1221             zos.write(mfRawBytes);
1222 
1223             // Calculate SignatureFile (".SF") and SignatureBlockFile
1224             ManifestDigester manDig = new ManifestDigester(mfRawBytes);
1225             SignatureFile sf = new SignatureFile(digests, manifest, manDig,
1226                                                  sigfile, signManifest);
1227 
1228             if (tsaAlias != null) {
1229                 tsaCert = getTsaCert(tsaAlias);
1230             }
1231 
1232             SignatureFile.Block block = null;
1233 
1234             try {
1235                 block =
1236                     sf.generateBlock(privateKey, sigalg, certChain,
1237                         externalSF, tsaUrl, tsaCert, signingMechanism, args,
1238                         zipFile);
1239             } catch (SocketTimeoutException e) {
1240                 // Provide a helpful message when TSA is beyond a firewall
1241                 error(rb.getString("unable.to.sign.jar.") +
1242                 rb.getString("no.response.from.the.Timestamping.Authority.") +
1243                 "\n  -J-Dhttp.proxyHost=<hostname>" +
1244                 "\n  -J-Dhttp.proxyPort=<portnumber>\n" +
1245                 rb.getString("or") +
1246                 "\n  -J-Dhttps.proxyHost=<hostname> " +
1247                 "\n  -J-Dhttps.proxyPort=<portnumber> ", e);
1248             }
1249 
1250             sfFilename = sf.getMetaName();
1251             bkFilename = block.getMetaName();
1252 
1253             ZipEntry sfFile = new ZipEntry(sfFilename);
1254             ZipEntry bkFile = new ZipEntry(bkFilename);
1255 
1256             long time = System.currentTimeMillis();
1257             sfFile.setTime(time);
1258             bkFile.setTime(time);
1259 
1260             // signature file
1261             zos.putNextEntry(sfFile);
1262             sf.write(zos);
1263             if (verbose != null) {
1264                 if (zipFile.getEntry(sfFilename) != null) {
1265                     System.out.println(rb.getString(".updating.") +
1266                                 sfFilename);
1267                 } else {
1268                     System.out.println(rb.getString(".adding.") +
1269                                 sfFilename);
1270                 }
1271             }
1272 
1273             if (verbose != null) {
1274                 if (tsaUrl != null || tsaCert != null) {
1275                     System.out.println(
1276                         rb.getString("requesting.a.signature.timestamp"));
1277                 }
1278                 if (tsaUrl != null) {
1279                     System.out.println(rb.getString("TSA.location.") + tsaUrl);
1280                 }
1281                 if (tsaCert != null) {
1282                     URI tsaURI = TimestampedSigner.getTimestampingURI(tsaCert);
1283                     if (tsaURI != null) {
1284                         System.out.println(rb.getString("TSA.location.") +
1285                             tsaURI);
1286                     }
1287                     System.out.println(rb.getString("TSA.certificate.") +
1288                         printCert("", tsaCert, false, null, false));
1289                 }
1290                 if (signingMechanism != null) {
1291                     System.out.println(
1292                         rb.getString("using.an.alternative.signing.mechanism"));
1293                 }
1294             }
1295 
1296             // signature block file
1297             zos.putNextEntry(bkFile);
1298             block.write(zos);
1299             if (verbose != null) {
1300                 if (zipFile.getEntry(bkFilename) != null) {
1301                     System.out.println(rb.getString(".updating.") +
1302                         bkFilename);
1303                 } else {
1304                     System.out.println(rb.getString(".adding.") +
1305                         bkFilename);
1306                 }
1307             }
1308 
1309             // Write out all other META-INF files that we stored in the
1310             // vector
1311             for (int i=0; i<mfFiles.size(); i++) {
1312                 ZipEntry ze = mfFiles.elementAt(i);
1313                 if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
1314                     && !ze.getName().equalsIgnoreCase(sfFilename)
1315                     && !ze.getName().equalsIgnoreCase(bkFilename)) {
1316                     writeEntry(zipFile, zos, ze);
1317                 }
1318             }
1319 
1320             // Write out all other files
1321             for (Enumeration<? extends ZipEntry> enum_=zipFile.entries();
1322                         enum_.hasMoreElements();) {
1323                 ZipEntry ze = enum_.nextElement();
1324 
1325                 if (!ze.getName().startsWith(META_INF)) {
1326                     if (verbose != null) {
1327                         if (manifest.getAttributes(ze.getName()) != null)
1328                           System.out.println(rb.getString(".signing.") +
1329                                 ze.getName());
1330                         else
1331                           System.out.println(rb.getString(".adding.") +
1332                                 ze.getName());
1333                     }
1334                     writeEntry(zipFile, zos, ze);
1335                 }
1336             }
1337         } catch(IOException ioe) {
1338             error(rb.getString("unable.to.sign.jar.")+ioe, ioe);
1339         } finally {
1340             // close the resouces
1341             if (zipFile != null) {
1342                 zipFile.close();
1343                 zipFile = null;
1344             }
1345 
1346             if (zos != null) {
1347                 zos.close();
1348             }
1349         }
1350 
1351         // no IOException thrown in the follow try clause, so disable
1352         // the try clause.
1353         // try {
1354             if (signedjar == null) {
1355                 // attempt an atomic rename. If that fails,
1356                 // rename the original jar file, then the signed
1357                 // one, then delete the original.
1358                 if (!signedJarFile.renameTo(jarFile)) {
1359                     File origJar = new File(jarName+".orig");
1360 
1361                     if (jarFile.renameTo(origJar)) {
1362                         if (signedJarFile.renameTo(jarFile)) {
1363                             origJar.delete();
1364                         } else {
1365                             MessageFormat form = new MessageFormat(rb.getString
1366                         ("attempt.to.rename.signedJarFile.to.jarFile.failed"));
1367                             Object[] source = {signedJarFile, jarFile};
1368                             error(form.format(source));
1369                         }
1370                     } else {
1371                         MessageFormat form = new MessageFormat(rb.getString
1372                             ("attempt.to.rename.jarFile.to.origJar.failed"));
1373                         Object[] source = {jarFile, origJar};
1374                         error(form.format(source));
1375                     }
1376                 }
1377             }
1378 
1379             if (hasExpiredCert || hasExpiringCert || notYetValidCert
1380                     || badKeyUsage || badExtendedKeyUsage
1381                     || badNetscapeCertType || chainNotValidated) {
1382                 System.out.println();
1383 
1384                 System.out.println(rb.getString("Warning."));
1385                 if (badKeyUsage) {
1386                     System.out.println(
1387                         rb.getString("The.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
1388                 }
1389 
1390                 if (badExtendedKeyUsage) {
1391                     System.out.println(
1392                         rb.getString("The.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."));
1393                 }
1394 
1395                 if (badNetscapeCertType) {
1396                     System.out.println(
1397                         rb.getString("The.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."));
1398                 }
1399 
1400                 if (hasExpiredCert) {
1401                     System.out.println(
1402                         rb.getString("The.signer.certificate.has.expired."));
1403                 } else if (hasExpiringCert) {
1404                     System.out.println(
1405                         rb.getString("The.signer.certificate.will.expire.within.six.months."));
1406                 } else if (notYetValidCert) {
1407                     System.out.println(
1408                         rb.getString("The.signer.certificate.is.not.yet.valid."));
1409                 }
1410 
1411                 if (chainNotValidated) {
1412                     System.out.println(
1413                             rb.getString("The.signer.s.certificate.chain.is.not.validated."));
1414                 }
1415             }
1416 
1417         // no IOException thrown in the above try clause, so disable
1418         // the catch clause.
1419         // } catch(IOException ioe) {
1420         //     error(rb.getString("unable.to.sign.jar.")+ioe, ioe);
1421         // }
1422     }
1423 
1424     /**
1425      * Find the length of header inside bs. The header is a multiple (>=0)
1426      * lines of attributes plus an empty line. The empty line is included
1427      * in the header.
1428      */
1429     @SuppressWarnings("fallthrough")
1430     private int findHeaderEnd(byte[] bs) {
1431         // Initial state true to deal with empty header
1432         boolean newline = true;     // just met a newline
1433         int len = bs.length;
1434         for (int i=0; i<len; i++) {
1435             switch (bs[i]) {
1436                 case '\r':
1437                     if (i < len - 1 && bs[i+1] == '\n') i++;
1438                     // fallthrough
1439                 case '\n':
1440                     if (newline) return i+1;    //+1 to get length
1441                     newline = true;
1442                     break;
1443                 default:
1444                     newline = false;
1445             }
1446         }
1447         // If header end is not found, it means the MANIFEST.MF has only
1448         // the main attributes section and it does not end with 2 newlines.
1449         // Returns the whole length so that it can be completely replaced.
1450         return len;
1451     }
1452 
1453     /**
1454      * signature-related files include:
1455      * . META-INF/MANIFEST.MF
1456      * . META-INF/SIG-*
1457      * . META-INF/*.SF
1458      * . META-INF/*.DSA
1459      * . META-INF/*.RSA
1460      * . META-INF/*.EC
1461      */
1462     private boolean signatureRelated(String name) {
1463         String ucName = name.toUpperCase(Locale.ENGLISH);
1464         if (ucName.equals(JarFile.MANIFEST_NAME) ||
1465             ucName.equals(META_INF) ||
1466             (ucName.startsWith(SIG_PREFIX) &&
1467                 ucName.indexOf("/") == ucName.lastIndexOf("/"))) {
1468             return true;
1469         }
1470 
1471         if (ucName.startsWith(META_INF) &&
1472             SignatureFileVerifier.isBlockOrSF(ucName)) {
1473             // .SF/.DSA/.RSA/.EC files in META-INF subdirs
1474             // are not considered signature-related
1475             return (ucName.indexOf("/") == ucName.lastIndexOf("/"));
1476         }
1477 
1478         return false;
1479     }
1480 
1481     Map<CodeSigner,String> cacheForSignerInfo = new IdentityHashMap<>();
1482 
1483     /**
1484      * Returns a string of singer info, with a newline at the end
1485      */
1486     private String signerInfo(CodeSigner signer, String tab) {
1487         if (cacheForSignerInfo.containsKey(signer)) {
1488             return cacheForSignerInfo.get(signer);
1489         }
1490         StringBuffer s = new StringBuffer();
1491         List<? extends Certificate> certs = signer.getSignerCertPath().getCertificates();
1492         // display the signature timestamp, if present
1493         Date timestamp;
1494         Timestamp ts = signer.getTimestamp();
1495         if (ts != null) {
1496             s.append(printTimestamp(tab, ts));
1497             s.append('\n');
1498             timestamp = ts.getTimestamp();
1499         } else {
1500             timestamp = null;
1501         }
1502         // display the certificate(s). The first one is end-entity cert and
1503         // its KeyUsage should be checked.
1504         boolean first = true;
1505         for (Certificate c : certs) {
1506             s.append(printCert(tab, c, true, timestamp, first));
1507             s.append('\n');
1508             first = false;
1509         }
1510         try {
1511             CertPath cp = certificateFactory.generateCertPath(certs);
1512             validator.validate(cp, pkixParameters);
1513         } catch (Exception e) {
1514             if (debug) {
1515                 e.printStackTrace();
1516             }
1517             if (e.getCause() != null &&
1518                     (e.getCause() instanceof CertificateExpiredException ||
1519                      e.getCause() instanceof CertificateNotYetValidException)) {
1520                 // No more warning, we alreay have hasExpiredCert or notYetValidCert
1521             } else {
1522                 chainNotValidated = true;
1523                 s.append(tab + rb.getString(".CertPath.not.validated.") +
1524                         e.getLocalizedMessage() + "]\n");   // TODO
1525             }
1526         }
1527         String result = s.toString();
1528         cacheForSignerInfo.put(signer, result);
1529         return result;
1530     }
1531 
1532     private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
1533     throws IOException
1534     {
1535         ZipEntry ze2 = new ZipEntry(ze.getName());
1536         ze2.setMethod(ze.getMethod());
1537         ze2.setTime(ze.getTime());
1538         ze2.setComment(ze.getComment());
1539         ze2.setExtra(ze.getExtra());
1540         if (ze.getMethod() == ZipEntry.STORED) {
1541             ze2.setSize(ze.getSize());
1542             ze2.setCrc(ze.getCrc());
1543         }
1544         os.putNextEntry(ze2);
1545         writeBytes(zf, ze, os);
1546     }
1547 
1548     /**
1549      * Writes all the bytes for a given entry to the specified output stream.
1550      */
1551     private synchronized void writeBytes
1552         (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
1553         int n;
1554 
1555         InputStream is = null;
1556         try {
1557             is = zf.getInputStream(ze);
1558             long left = ze.getSize();
1559 
1560             while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
1561                 os.write(buffer, 0, n);
1562                 left -= n;
1563             }
1564         } finally {
1565             if (is != null) {
1566                 is.close();
1567             }
1568         }
1569     }
1570 
1571     void loadKeyStore(String keyStoreName, boolean prompt) {
1572 
1573         if (!nullStream && keyStoreName == null) {
1574             keyStoreName = System.getProperty("user.home") + File.separator
1575                 + ".keystore";
1576         }
1577 
1578         try {
1579 
1580             certificateFactory = CertificateFactory.getInstance("X.509");
1581             validator = CertPathValidator.getInstance("PKIX");
1582             Set<TrustAnchor> tas = new HashSet<>();
1583             try {
1584                 KeyStore caks = KeyStoreUtil.getCacertsKeyStore();
1585                 if (caks != null) {
1586                     Enumeration<String> aliases = caks.aliases();
1587                     while (aliases.hasMoreElements()) {
1588                         String a = aliases.nextElement();
1589                         try {
1590                             tas.add(new TrustAnchor((X509Certificate)caks.getCertificate(a), null));
1591                         } catch (Exception e2) {
1592                             // ignore, when a SecretkeyEntry does not include a cert
1593                         }
1594                     }
1595                 }
1596             } catch (Exception e) {
1597                 // Ignore, if cacerts cannot be loaded
1598             }
1599 
1600             if (providerName == null) {
1601                 store = KeyStore.getInstance(storetype);
1602             } else {
1603                 store = KeyStore.getInstance(storetype, providerName);
1604             }
1605 
1606             // Get pass phrase
1607             // XXX need to disable echo; on UNIX, call getpass(char *prompt)Z
1608             // and on NT call ??
1609             if (token && storepass == null && !protectedPath
1610                     && !KeyStoreUtil.isWindowsKeyStore(storetype)) {
1611                 storepass = getPass
1612                         (rb.getString("Enter.Passphrase.for.keystore."));
1613             } else if (!token && storepass == null && prompt) {
1614                 storepass = getPass
1615                         (rb.getString("Enter.Passphrase.for.keystore."));
1616             }
1617 
1618             try {
1619                 if (nullStream) {
1620                     store.load(null, storepass);
1621                 } else {
1622                     keyStoreName = keyStoreName.replace(File.separatorChar, '/');
1623                     URL url = null;
1624                     try {
1625                         url = new URL(keyStoreName);
1626                     } catch (java.net.MalformedURLException e) {
1627                         // try as file
1628                         url = new File(keyStoreName).toURI().toURL();
1629                     }
1630                     InputStream is = null;
1631                     try {
1632                         is = url.openStream();
1633                         store.load(is, storepass);
1634                     } finally {
1635                         if (is != null) {
1636                             is.close();
1637                         }
1638                     }
1639                 }
1640                 Enumeration<String> aliases = store.aliases();
1641                 while (aliases.hasMoreElements()) {
1642                     String a = aliases.nextElement();
1643                     try {
1644                         X509Certificate c = (X509Certificate)store.getCertificate(a);
1645                         // Only add TrustedCertificateEntry and self-signed
1646                         // PrivateKeyEntry
1647                         if (store.isCertificateEntry(a) ||
1648                                 c.getSubjectDN().equals(c.getIssuerDN())) {
1649                             tas.add(new TrustAnchor(c, null));
1650                         }
1651                     } catch (Exception e2) {
1652                         // ignore, when a SecretkeyEntry does not include a cert
1653                     }
1654                 }
1655             } finally {
1656                 try {
1657                     pkixParameters = new PKIXParameters(tas);
1658                     pkixParameters.setRevocationEnabled(false);
1659                 } catch (InvalidAlgorithmParameterException ex) {
1660                     // Only if tas is empty
1661                 }
1662             }
1663         } catch (IOException ioe) {
1664             throw new RuntimeException(rb.getString("keystore.load.") +
1665                                         ioe.getMessage());
1666         } catch (java.security.cert.CertificateException ce) {
1667             throw new RuntimeException(rb.getString("certificate.exception.") +
1668                                         ce.getMessage());
1669         } catch (NoSuchProviderException pe) {
1670             throw new RuntimeException(rb.getString("keystore.load.") +
1671                                         pe.getMessage());
1672         } catch (NoSuchAlgorithmException nsae) {
1673             throw new RuntimeException(rb.getString("keystore.load.") +
1674                                         nsae.getMessage());
1675         } catch (KeyStoreException kse) {
1676             throw new RuntimeException
1677                 (rb.getString("unable.to.instantiate.keystore.class.") +
1678                 kse.getMessage());
1679         }
1680     }
1681 
1682     X509Certificate getTsaCert(String alias) {
1683 
1684         java.security.cert.Certificate cs = null;
1685 
1686         try {
1687             cs = store.getCertificate(alias);
1688         } catch (KeyStoreException kse) {
1689             // this never happens, because keystore has been loaded
1690         }
1691         if (cs == null || (!(cs instanceof X509Certificate))) {
1692             MessageFormat form = new MessageFormat(rb.getString
1693                 ("Certificate.not.found.for.alias.alias.must.reference.a.valid.KeyStore.entry.containing.an.X.509.public.key.certificate.for.the"));
1694             Object[] source = {alias, alias};
1695             error(form.format(source));
1696         }
1697         return (X509Certificate) cs;
1698     }
1699 
1700     /**
1701      * Check if userCert is designed to be a code signer
1702      * @param userCert the certificate to be examined
1703      * @param bad 3 booleans to show if the KeyUsage, ExtendedKeyUsage,
1704      *            NetscapeCertType has codeSigning flag turned on.
1705      *            If null, the class field badKeyUsage, badExtendedKeyUsage,
1706      *            badNetscapeCertType will be set.
1707      */
1708     void checkCertUsage(X509Certificate userCert, boolean[] bad) {
1709 
1710         // Can act as a signer?
1711         // 1. if KeyUsage, then [0:digitalSignature] or
1712         //    [1:nonRepudiation] should be true
1713         // 2. if ExtendedKeyUsage, then should contains ANY or CODE_SIGNING
1714         // 3. if NetscapeCertType, then should contains OBJECT_SIGNING
1715         // 1,2,3 must be true
1716 
1717         if (bad != null) {
1718             bad[0] = bad[1] = bad[2] = false;
1719         }
1720 
1721         boolean[] keyUsage = userCert.getKeyUsage();
1722         if (keyUsage != null) {
1723             keyUsage = Arrays.copyOf(keyUsage, 9);
1724             if (!keyUsage[0] && !keyUsage[1]) {
1725                 if (bad != null) {
1726                     bad[0] = true;
1727                     badKeyUsage = true;
1728                 }
1729             }
1730         }
1731 
1732         try {
1733             List<String> xKeyUsage = userCert.getExtendedKeyUsage();
1734             if (xKeyUsage != null) {
1735                 if (!xKeyUsage.contains("2.5.29.37.0") // anyExtendedKeyUsage
1736                         && !xKeyUsage.contains("1.3.6.1.5.5.7.3.3")) {  // codeSigning
1737                     if (bad != null) {
1738                         bad[1] = true;
1739                         badExtendedKeyUsage = true;
1740                     }
1741                 }
1742             }
1743         } catch (java.security.cert.CertificateParsingException e) {
1744             // shouldn't happen
1745         }
1746 
1747         try {
1748             // OID_NETSCAPE_CERT_TYPE
1749             byte[] netscapeEx = userCert.getExtensionValue
1750                     ("2.16.840.1.113730.1.1");
1751             if (netscapeEx != null) {
1752                 DerInputStream in = new DerInputStream(netscapeEx);
1753                 byte[] encoded = in.getOctetString();
1754                 encoded = new DerValue(encoded).getUnalignedBitString()
1755                         .toByteArray();
1756 
1757                 NetscapeCertTypeExtension extn =
1758                         new NetscapeCertTypeExtension(encoded);
1759 
1760                 Boolean val = extn.get(NetscapeCertTypeExtension.OBJECT_SIGNING);
1761                 if (!val) {
1762                     if (bad != null) {
1763                         bad[2] = true;
1764                         badNetscapeCertType = true;
1765                     }
1766                 }
1767             }
1768         } catch (IOException e) {
1769             //
1770         }
1771     }
1772 
1773     void getAliasInfo(String alias) {
1774 
1775         Key key = null;
1776 
1777         try {
1778             java.security.cert.Certificate[] cs = null;
1779             if (altCertChain != null) {
1780                 try {
1781                     cs = CertificateFactory.getInstance("X.509").
1782                             generateCertificates(new FileInputStream(altCertChain)).
1783                             toArray(new Certificate[0]);
1784                 } catch (CertificateException ex) {
1785                     error(rb.getString("Cannot.restore.certchain.from.file.specified"));
1786                 } catch (FileNotFoundException ex) {
1787                     error(rb.getString("File.specified.by.certchain.does.not.exist"));
1788                 }
1789             } else {
1790                 try {
1791                     cs = store.getCertificateChain(alias);
1792                 } catch (KeyStoreException kse) {
1793                     // this never happens, because keystore has been loaded
1794                 }
1795             }
1796             if (cs == null || cs.length == 0) {
1797                 if (altCertChain != null) {
1798                     error(rb.getString
1799                             ("Certificate.chain.not.found.in.the.file.specified."));
1800                 } else {
1801                     MessageFormat form = new MessageFormat(rb.getString
1802                         ("Certificate.chain.not.found.for.alias.alias.must.reference.a.valid.KeyStore.key.entry.containing.a.private.key.and"));
1803                     Object[] source = {alias, alias};
1804                     error(form.format(source));
1805                 }
1806             }
1807 
1808             certChain = new X509Certificate[cs.length];
1809             for (int i=0; i<cs.length; i++) {
1810                 if (!(cs[i] instanceof X509Certificate)) {
1811                     error(rb.getString
1812                         ("found.non.X.509.certificate.in.signer.s.chain"));
1813                 }
1814                 certChain[i] = (X509Certificate)cs[i];
1815             }
1816 
1817             // We don't meant to print anything, the next call
1818             // checks validity and keyUsage etc
1819             printCert("", certChain[0], true, null, true);
1820 
1821             try {
1822                 CertPath cp = certificateFactory.generateCertPath(Arrays.asList(certChain));
1823                 validator.validate(cp, pkixParameters);
1824             } catch (Exception e) {
1825                 if (debug) {
1826                     e.printStackTrace();
1827                 }
1828                 if (e.getCause() != null &&
1829                         (e.getCause() instanceof CertificateExpiredException ||
1830                         e.getCause() instanceof CertificateNotYetValidException)) {
1831                     // No more warning, we alreay have hasExpiredCert or notYetValidCert
1832                 } else {
1833                     chainNotValidated = true;
1834                 }
1835             }
1836 
1837             try {
1838                 if (!token && keypass == null)
1839                     key = store.getKey(alias, storepass);
1840                 else
1841                     key = store.getKey(alias, keypass);
1842             } catch (UnrecoverableKeyException e) {
1843                 if (token) {
1844                     throw e;
1845                 } else if (keypass == null) {
1846                     // Did not work out, so prompt user for key password
1847                     MessageFormat form = new MessageFormat(rb.getString
1848                         ("Enter.key.password.for.alias."));
1849                     Object[] source = {alias};
1850                     keypass = getPass(form.format(source));
1851                     key = store.getKey(alias, keypass);
1852                 }
1853             }
1854         } catch (NoSuchAlgorithmException e) {
1855             error(e.getMessage());
1856         } catch (UnrecoverableKeyException e) {
1857             error(rb.getString("unable.to.recover.key.from.keystore"));
1858         } catch (KeyStoreException kse) {
1859             // this never happens, because keystore has been loaded
1860         }
1861 
1862         if (!(key instanceof PrivateKey)) {
1863             MessageFormat form = new MessageFormat(rb.getString
1864                 ("key.associated.with.alias.not.a.private.key"));
1865             Object[] source = {alias};
1866             error(form.format(source));
1867         } else {
1868             privateKey = (PrivateKey)key;
1869         }
1870     }
1871 
1872     void error(String message)
1873     {
1874         System.out.println(rb.getString("jarsigner.")+message);
1875         System.exit(1);
1876     }
1877 
1878 
1879     void error(String message, Exception e)
1880     {
1881         System.out.println(rb.getString("jarsigner.")+message);
1882         if (debug) {
1883             e.printStackTrace();
1884         }
1885         System.exit(1);
1886     }
1887 
1888     char[] getPass(String prompt)
1889     {
1890         System.err.print(prompt);
1891         System.err.flush();
1892         try {
1893             char[] pass = Password.readPassword(System.in);
1894 
1895             if (pass == null) {
1896                 error(rb.getString("you.must.enter.key.password"));
1897             } else {
1898                 return pass;
1899             }
1900         } catch (IOException ioe) {
1901             error(rb.getString("unable.to.read.password.")+ioe.getMessage());
1902         }
1903         // this shouldn't happen
1904         return null;
1905     }
1906 
1907     /*
1908      * Reads all the bytes for a given zip entry.
1909      */
1910     private synchronized byte[] getBytes(ZipFile zf,
1911                                          ZipEntry ze) throws IOException {
1912         int n;
1913 
1914         InputStream is = null;
1915         try {
1916             is = zf.getInputStream(ze);
1917             baos.reset();
1918             long left = ze.getSize();
1919 
1920             while((left > 0) && (n = is.read(buffer, 0, buffer.length)) != -1) {
1921                 baos.write(buffer, 0, n);
1922                 left -= n;
1923             }
1924         } finally {
1925             if (is != null) {
1926                 is.close();
1927             }
1928         }
1929 
1930         return baos.toByteArray();
1931     }
1932 
1933     /*
1934      * Returns manifest entry from given jar file, or null if given jar file
1935      * does not have a manifest entry.
1936      */
1937     private ZipEntry getManifestFile(ZipFile zf) {
1938         ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
1939         if (ze == null) {
1940             // Check all entries for matching name
1941             Enumeration<? extends ZipEntry> enum_ = zf.entries();
1942             while (enum_.hasMoreElements() && ze == null) {
1943                 ze = enum_.nextElement();
1944                 if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
1945                     (ze.getName())) {
1946                     ze = null;
1947                 }
1948             }
1949         }
1950         return ze;
1951     }
1952 
1953     /*
1954      * Computes the digests of a zip entry, and returns them as an array
1955      * of base64-encoded strings.
1956      */
1957     private synchronized String[] getDigests(ZipEntry ze, ZipFile zf,
1958                                              MessageDigest[] digests,
1959                                              BASE64Encoder encoder)
1960         throws IOException {
1961 
1962         int n, i;
1963         InputStream is = null;
1964         try {
1965             is = zf.getInputStream(ze);
1966             long left = ze.getSize();
1967             while((left > 0)
1968                 && (n = is.read(buffer, 0, buffer.length)) != -1) {
1969                 for (i=0; i<digests.length; i++) {
1970                     digests[i].update(buffer, 0, n);
1971                 }
1972                 left -= n;
1973             }
1974         } finally {
1975             if (is != null) {
1976                 is.close();
1977             }
1978         }
1979 
1980         // complete the digests
1981         String[] base64Digests = new String[digests.length];
1982         for (i=0; i<digests.length; i++) {
1983             base64Digests[i] = encoder.encode(digests[i].digest());
1984         }
1985         return base64Digests;
1986     }
1987 
1988     /*
1989      * Computes the digests of a zip entry, and returns them as a list of
1990      * attributes
1991      */
1992     private Attributes getDigestAttributes(ZipEntry ze, ZipFile zf,
1993                                            MessageDigest[] digests,
1994                                            BASE64Encoder encoder)
1995         throws IOException {
1996 
1997         String[] base64Digests = getDigests(ze, zf, digests, encoder);
1998         Attributes attrs = new Attributes();
1999 
2000         for (int i=0; i<digests.length; i++) {
2001             attrs.putValue(digests[i].getAlgorithm()+"-Digest",
2002                            base64Digests[i]);
2003         }
2004         return attrs;
2005     }
2006 
2007     /*
2008      * Updates the digest attributes of a manifest entry, by adding or
2009      * replacing digest values.
2010      * A digest value is added if the manifest entry does not contain a digest
2011      * for that particular algorithm.
2012      * A digest value is replaced if it is obsolete.
2013      *
2014      * Returns true if the manifest entry has been changed, and false
2015      * otherwise.
2016      */
2017     private boolean updateDigests(ZipEntry ze, ZipFile zf,
2018                                   MessageDigest[] digests,
2019                                   BASE64Encoder encoder,
2020                                   Manifest mf) throws IOException {
2021         boolean update = false;
2022 
2023         Attributes attrs = mf.getAttributes(ze.getName());
2024         String[] base64Digests = getDigests(ze, zf, digests, encoder);
2025 
2026         for (int i=0; i<digests.length; i++) {
2027             // The entry name to be written into attrs
2028             String name = null;
2029             try {
2030                 // Find if the digest already exists
2031                 AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
2032                 for (Object key: attrs.keySet()) {
2033                     if (key instanceof Attributes.Name) {
2034                         String n = ((Attributes.Name)key).toString();
2035                         if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
2036                             String tmp = n.substring(0, n.length() - 7);
2037                             if (AlgorithmId.get(tmp).equals(aid)) {
2038                                 name = n;
2039                                 break;
2040                             }
2041                         }
2042                     }
2043                 }
2044             } catch (NoSuchAlgorithmException nsae) {
2045                 // Ignored. Writing new digest entry.
2046             }
2047 
2048             if (name == null) {
2049                 name = digests[i].getAlgorithm()+"-Digest";
2050                 attrs.putValue(name, base64Digests[i]);
2051                 update=true;
2052             } else {
2053                 // compare digests, and replace the one in the manifest
2054                 // if they are different
2055                 String mfDigest = attrs.getValue(name);
2056                 if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
2057                     attrs.putValue(name, base64Digests[i]);
2058                     update=true;
2059                 }
2060             }
2061         }
2062         return update;
2063     }
2064 
2065     /*
2066      * Try to load the specified signing mechanism.
2067      * The URL class loader is used.
2068      */
2069     private ContentSigner loadSigningMechanism(String signerClassName,
2070         String signerClassPath) throws Exception {
2071 
2072         // construct class loader
2073         String cpString = null;   // make sure env.class.path defaults to dot
2074 
2075         // do prepends to get correct ordering
2076         cpString = PathList.appendPath(System.getProperty("env.class.path"), cpString);
2077         cpString = PathList.appendPath(System.getProperty("java.class.path"), cpString);
2078         cpString = PathList.appendPath(signerClassPath, cpString);
2079         URL[] urls = PathList.pathToURLs(cpString);
2080         ClassLoader appClassLoader = new URLClassLoader(urls);
2081 
2082         // attempt to find signer
2083         Class<?> signerClass = appClassLoader.loadClass(signerClassName);
2084 
2085         // Check that it implements ContentSigner
2086         Object signer = signerClass.newInstance();
2087         if (!(signer instanceof ContentSigner)) {
2088             MessageFormat form = new MessageFormat(
2089                 rb.getString("signerClass.is.not.a.signing.mechanism"));
2090             Object[] source = {signerClass.getName()};
2091             throw new IllegalArgumentException(form.format(source));
2092         }
2093         return (ContentSigner)signer;
2094     }
2095 }
2096 
2097 /**
2098  * This is a BASE64Encoder that does not insert a default newline at the end of
2099  * every output line. This is necessary because java.util.jar does its own
2100  * line management (see Manifest.make72Safe()). Inserting additional new lines
2101  * can cause line-wrapping problems (see CR 6219522).
2102  */
2103 class JarBASE64Encoder extends BASE64Encoder {
2104     /**
2105      * Encode the suffix that ends every output line.
2106      */
2107     protected void encodeLineSuffix(OutputStream aStream) throws IOException { }
2108 }
2109 
2110 class SignatureFile {
2111 
2112     /** SignatureFile */
2113     Manifest sf;
2114 
2115     /** .SF base name */
2116     String baseName;
2117 
2118     public SignatureFile(MessageDigest digests[],
2119                          Manifest mf,
2120                          ManifestDigester md,
2121                          String baseName,
2122                          boolean signManifest)
2123 
2124     {
2125         this.baseName = baseName;
2126 
2127         String version = System.getProperty("java.version");
2128         String javaVendor = System.getProperty("java.vendor");
2129 
2130         sf = new Manifest();
2131         Attributes mattr = sf.getMainAttributes();
2132         BASE64Encoder encoder = new JarBASE64Encoder();
2133 
2134         mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
2135         mattr.putValue("Created-By", version + " (" + javaVendor + ")");
2136 
2137         if (signManifest) {
2138             // sign the whole manifest
2139             for (int i=0; i < digests.length; i++) {
2140                 mattr.putValue(digests[i].getAlgorithm()+"-Digest-Manifest",
2141                                encoder.encode(md.manifestDigest(digests[i])));
2142             }
2143         }
2144 
2145         // create digest of the manifest main attributes
2146         ManifestDigester.Entry mde =
2147                 md.get(ManifestDigester.MF_MAIN_ATTRS, false);
2148         if (mde != null) {
2149             for (int i=0; i < digests.length; i++) {
2150                 mattr.putValue(digests[i].getAlgorithm() +
2151                         "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
2152                         encoder.encode(mde.digest(digests[i])));
2153             }
2154         } else {
2155             throw new IllegalStateException
2156                 ("ManifestDigester failed to create " +
2157                 "Manifest-Main-Attribute entry");
2158         }
2159 
2160         /* go through the manifest entries and create the digests */
2161 
2162         Map<String,Attributes> entries = sf.getEntries();
2163         Iterator<Map.Entry<String,Attributes>> mit =
2164                                 mf.getEntries().entrySet().iterator();
2165         while(mit.hasNext()) {
2166             Map.Entry<String,Attributes> e = mit.next();
2167             String name = e.getKey();
2168             mde = md.get(name, false);
2169             if (mde != null) {
2170                 Attributes attr = new Attributes();
2171                 for (int i=0; i < digests.length; i++) {
2172                     attr.putValue(digests[i].getAlgorithm()+"-Digest",
2173                                   encoder.encode(mde.digest(digests[i])));
2174                 }
2175                 entries.put(name, attr);
2176             }
2177         }
2178     }
2179 
2180     /**
2181      * Writes the SignatureFile to the specified OutputStream.
2182      *
2183      * @param out the output stream
2184      * @exception IOException if an I/O error has occurred
2185      */
2186 
2187     public void write(OutputStream out) throws IOException
2188     {
2189         sf.write(out);
2190     }
2191 
2192     /**
2193      * get .SF file name
2194      */
2195     public String getMetaName()
2196     {
2197         return "META-INF/"+ baseName + ".SF";
2198     }
2199 
2200     /**
2201      * get base file name
2202      */
2203     public String getBaseName()
2204     {
2205         return baseName;
2206     }
2207 
2208     /*
2209      * Generate a signed data block.
2210      * If a URL or a certificate (containing a URL) for a Timestamping
2211      * Authority is supplied then a signature timestamp is generated and
2212      * inserted into the signed data block.
2213      *
2214      * @param sigalg signature algorithm to use, or null to use default
2215      * @param tsaUrl The location of the Timestamping Authority. If null
2216      *               then no timestamp is requested.
2217      * @param tsaCert The certificate for the Timestamping Authority. If null
2218      *               then no timestamp is requested.
2219      * @param signingMechanism The signing mechanism to use.
2220      * @param args The command-line arguments to jarsigner.
2221      * @param zipFile The original source Zip file.
2222      */
2223     public Block generateBlock(PrivateKey privateKey,
2224                                String sigalg,
2225                                X509Certificate[] certChain,
2226                                boolean externalSF, String tsaUrl,
2227                                X509Certificate tsaCert,
2228                                ContentSigner signingMechanism,
2229                                String[] args, ZipFile zipFile)
2230         throws NoSuchAlgorithmException, InvalidKeyException, IOException,
2231             SignatureException, CertificateException
2232     {
2233         return new Block(this, privateKey, sigalg, certChain, externalSF,
2234                 tsaUrl, tsaCert, signingMechanism, args, zipFile);
2235     }
2236 
2237 
2238     public static class Block {
2239 
2240         private byte[] block;
2241         private String blockFileName;
2242 
2243         /*
2244          * Construct a new signature block.
2245          */
2246         Block(SignatureFile sfg, PrivateKey privateKey, String sigalg,
2247             X509Certificate[] certChain, boolean externalSF, String tsaUrl,
2248             X509Certificate tsaCert, ContentSigner signingMechanism,
2249             String[] args, ZipFile zipFile)
2250             throws NoSuchAlgorithmException, InvalidKeyException, IOException,
2251             SignatureException, CertificateException {
2252 
2253             Principal issuerName = certChain[0].getIssuerDN();
2254             if (!(issuerName instanceof X500Name)) {
2255                 // must extract the original encoded form of DN for subsequent
2256                 // name comparison checks (converting to a String and back to
2257                 // an encoded DN could cause the types of String attribute
2258                 // values to be changed)
2259                 X509CertInfo tbsCert = new
2260                     X509CertInfo(certChain[0].getTBSCertificate());
2261                 issuerName = (Principal)
2262                     tbsCert.get(X509CertInfo.ISSUER + "." +
2263                                 X509CertInfo.DN_NAME);
2264                 }
2265             BigInteger serial = certChain[0].getSerialNumber();
2266 
2267             String signatureAlgorithm;
2268             String keyAlgorithm = privateKey.getAlgorithm();
2269             /*
2270              * If no signature algorithm was specified, we choose a
2271              * default that is compatible with the private key algorithm.
2272              */
2273             if (sigalg == null) {
2274 
2275                 if (keyAlgorithm.equalsIgnoreCase("DSA"))
2276                     signatureAlgorithm = "SHA1withDSA";
2277                 else if (keyAlgorithm.equalsIgnoreCase("RSA"))
2278                     signatureAlgorithm = "SHA256withRSA";
2279                 else if (keyAlgorithm.equalsIgnoreCase("EC"))
2280                     signatureAlgorithm = "SHA256withECDSA";
2281                 else
2282                     throw new RuntimeException("private key is not a DSA or "
2283                                                + "RSA key");
2284             } else {
2285                 signatureAlgorithm = sigalg;
2286             }
2287 
2288             // check common invalid key/signature algorithm combinations
2289             String sigAlgUpperCase = signatureAlgorithm.toUpperCase(Locale.ENGLISH);
2290             if ((sigAlgUpperCase.endsWith("WITHRSA") &&
2291                 !keyAlgorithm.equalsIgnoreCase("RSA")) ||
2292                 (sigAlgUpperCase.endsWith("WITHECDSA") &&
2293                 !keyAlgorithm.equalsIgnoreCase("EC")) ||
2294                 (sigAlgUpperCase.endsWith("WITHDSA") &&
2295                 !keyAlgorithm.equalsIgnoreCase("DSA"))) {
2296                 throw new SignatureException
2297                     ("private key algorithm is not compatible with signature algorithm");
2298             }
2299 
2300             blockFileName = "META-INF/"+sfg.getBaseName()+"."+keyAlgorithm;
2301 
2302             AlgorithmId sigAlg = AlgorithmId.get(signatureAlgorithm);
2303             AlgorithmId digEncrAlg = AlgorithmId.get(keyAlgorithm);
2304 
2305             Signature sig = Signature.getInstance(signatureAlgorithm);
2306             sig.initSign(privateKey);
2307 
2308             ByteArrayOutputStream baos = new ByteArrayOutputStream();
2309             sfg.write(baos);
2310 
2311             byte[] content = baos.toByteArray();
2312 
2313             sig.update(content);
2314             byte[] signature = sig.sign();
2315 
2316             // Timestamp the signature and generate the signature block file
2317             if (signingMechanism == null) {
2318                 signingMechanism = new TimestampedSigner();
2319             }
2320             URI tsaUri = null;
2321             try {
2322                 if (tsaUrl != null) {
2323                     tsaUri = new URI(tsaUrl);
2324                 }
2325             } catch (URISyntaxException e) {
2326                 throw new IOException(e);
2327             }
2328 
2329             // Assemble parameters for the signing mechanism
2330             ContentSignerParameters params =
2331                 new JarSignerParameters(args, tsaUri, tsaCert, signature,
2332                     signatureAlgorithm, certChain, content, zipFile);
2333 
2334             // Generate the signature block
2335             block = signingMechanism.generateSignedData(
2336                     params, externalSF, (tsaUrl != null || tsaCert != null));
2337         }
2338 
2339         /*
2340          * get block file name.
2341          */
2342         public String getMetaName()
2343         {
2344             return blockFileName;
2345         }
2346 
2347         /**
2348          * Writes the block file to the specified OutputStream.
2349          *
2350          * @param out the output stream
2351          * @exception IOException if an I/O error has occurred
2352          */
2353 
2354         public void write(OutputStream out) throws IOException
2355         {
2356             out.write(block);
2357         }
2358     }
2359 }
2360 
2361 
2362 /*
2363  * This object encapsulates the parameters used to perform content signing.
2364  */
2365 class JarSignerParameters implements ContentSignerParameters {
2366 
2367     private String[] args;
2368     private URI tsa;
2369     private X509Certificate tsaCertificate;
2370     private byte[] signature;
2371     private String signatureAlgorithm;
2372     private X509Certificate[] signerCertificateChain;
2373     private byte[] content;
2374     private ZipFile source;
2375 
2376     /**
2377      * Create a new object.
2378      */
2379     JarSignerParameters(String[] args, URI tsa, X509Certificate tsaCertificate,
2380         byte[] signature, String signatureAlgorithm,
2381         X509Certificate[] signerCertificateChain, byte[] content,
2382         ZipFile source) {
2383 
2384         if (signature == null || signatureAlgorithm == null ||
2385             signerCertificateChain == null) {
2386             throw new NullPointerException();
2387         }
2388         this.args = args;
2389         this.tsa = tsa;
2390         this.tsaCertificate = tsaCertificate;
2391         this.signature = signature;
2392         this.signatureAlgorithm = signatureAlgorithm;
2393         this.signerCertificateChain = signerCertificateChain;
2394         this.content = content;
2395         this.source = source;
2396     }
2397 
2398     /**
2399      * Retrieves the command-line arguments.
2400      *
2401      * @return The command-line arguments. May be null.
2402      */
2403     public String[] getCommandLine() {
2404         return args;
2405     }
2406 
2407     /**
2408      * Retrieves the identifier for a Timestamping Authority (TSA).
2409      *
2410      * @return The TSA identifier. May be null.
2411      */
2412     public URI getTimestampingAuthority() {
2413         return tsa;
2414     }
2415 
2416     /**
2417      * Retrieves the certificate for a Timestamping Authority (TSA).
2418      *
2419      * @return The TSA certificate. May be null.
2420      */
2421     public X509Certificate getTimestampingAuthorityCertificate() {
2422         return tsaCertificate;
2423     }
2424 
2425     /**
2426      * Retrieves the signature.
2427      *
2428      * @return The non-null signature bytes.
2429      */
2430     public byte[] getSignature() {
2431         return signature;
2432     }
2433 
2434     /**
2435      * Retrieves the name of the signature algorithm.
2436      *
2437      * @return The non-null string name of the signature algorithm.
2438      */
2439     public String getSignatureAlgorithm() {
2440         return signatureAlgorithm;
2441     }
2442 
2443     /**
2444      * Retrieves the signer's X.509 certificate chain.
2445      *
2446      * @return The non-null array of X.509 public-key certificates.
2447      */
2448     public X509Certificate[] getSignerCertificateChain() {
2449         return signerCertificateChain;
2450     }
2451 
2452     /**
2453      * Retrieves the content that was signed.
2454      *
2455      * @return The content bytes. May be null.
2456      */
2457     public byte[] getContent() {
2458         return content;
2459     }
2460 
2461     /**
2462      * Retrieves the original source ZIP file before it was signed.
2463      *
2464      * @return The original ZIP file. May be null.
2465      */
2466     public ZipFile getSource() {
2467         return source;
2468     }
2469 }