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