Path: blob/master/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
41161 views
/*1* Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package jdk.security.jarsigner;2627import com.sun.jarsigner.ContentSigner;28import com.sun.jarsigner.ContentSignerParameters;29import jdk.internal.access.JavaUtilZipFileAccess;30import jdk.internal.access.SharedSecrets;31import sun.security.pkcs.PKCS7;32import sun.security.pkcs.PKCS9Attribute;33import sun.security.pkcs.PKCS9Attributes;34import sun.security.timestamp.HttpTimestamper;35import sun.security.tools.PathList;36import sun.security.util.Event;37import sun.security.util.ManifestDigester;38import sun.security.util.SignatureFileVerifier;39import sun.security.util.SignatureUtil;40import sun.security.x509.AlgorithmId;4142import java.io.*;43import java.lang.reflect.InvocationTargetException;44import java.net.SocketTimeoutException;45import java.net.URI;46import java.net.URL;47import java.net.URLClassLoader;48import java.security.*;49import java.security.cert.CertPath;50import java.security.cert.Certificate;51import java.security.cert.CertificateException;52import java.security.cert.X509Certificate;53import java.security.spec.InvalidParameterSpecException;54import java.util.*;55import java.util.function.BiConsumer;56import java.util.function.Function;57import java.util.jar.Attributes;58import java.util.jar.JarEntry;59import java.util.jar.JarFile;60import java.util.jar.Manifest;61import java.util.zip.ZipEntry;62import java.util.zip.ZipFile;63import java.util.zip.ZipOutputStream;6465/**66* An immutable utility class to sign a jar file.67* <p>68* A caller creates a {@code JarSigner.Builder} object, (optionally) sets69* some parameters, and calls {@link JarSigner.Builder#build build} to create70* a {@code JarSigner} object. This {@code JarSigner} object can then71* be used to sign a jar file.72* <p>73* Unless otherwise stated, calling a method of {@code JarSigner} or74* {@code JarSigner.Builder} with a null argument will throw75* a {@link NullPointerException}.76* <p>77* Example:78* <pre>79* JarSigner signer = new JarSigner.Builder(key, certPath)80* .digestAlgorithm("SHA-1")81* .signatureAlgorithm("SHA1withDSA")82* .build();83* try (ZipFile in = new ZipFile(inputFile);84* FileOutputStream out = new FileOutputStream(outputFile)) {85* signer.sign(in, out);86* }87* </pre>88*89* @since 990*/91public final class JarSigner {9293static final JavaUtilZipFileAccess JUZFA = SharedSecrets.getJavaUtilZipFileAccess();9495/**96* A mutable builder class that can create an immutable {@code JarSigner}97* from various signing-related parameters.98*99* @since 9100*/101public static class Builder {102103// Signer materials:104final PrivateKey privateKey;105final X509Certificate[] certChain;106107// JarSigner options:108// Support multiple digestalg internally. Can be null, but not empty109String[] digestalg;110String sigalg;111// Precisely should be one provider for each digestalg, maybe later112Provider digestProvider;113Provider sigProvider;114URI tsaUrl;115String signerName;116BiConsumer<String,String> handler;117118// Implementation-specific properties:119String tSAPolicyID;120String tSADigestAlg;121boolean sectionsonly = false;122boolean internalsf = false;123String altSignerPath;124String altSigner;125126/**127* Creates a {@code JarSigner.Builder} object with128* a {@link KeyStore.PrivateKeyEntry} object.129*130* @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.131*/132public Builder(KeyStore.PrivateKeyEntry entry) {133this.privateKey = entry.getPrivateKey();134try {135// called internally, no need to clone136Certificate[] certs = entry.getCertificateChain();137this.certChain = Arrays.copyOf(certs, certs.length,138X509Certificate[].class);139} catch (ArrayStoreException ase) {140// Wrong type, not X509Certificate. Won't document.141throw new IllegalArgumentException(142"Entry does not contain X509Certificate");143}144}145146/**147* Creates a {@code JarSigner.Builder} object with a private key and148* a certification path.149*150* @param privateKey the private key of the signer.151* @param certPath the certification path of the signer.152* @throws IllegalArgumentException if {@code certPath} is empty, or153* the {@code privateKey} algorithm does not match the algorithm154* of the {@code PublicKey} in the end entity certificate155* (the first certificate in {@code certPath}).156*/157public Builder(PrivateKey privateKey, CertPath certPath) {158List<? extends Certificate> certs = certPath.getCertificates();159if (certs.isEmpty()) {160throw new IllegalArgumentException("certPath cannot be empty");161}162if (!privateKey.getAlgorithm().equals163(certs.get(0).getPublicKey().getAlgorithm())) {164throw new IllegalArgumentException165("private key algorithm does not match " +166"algorithm of public key in end entity " +167"certificate (the 1st in certPath)");168}169this.privateKey = privateKey;170try {171this.certChain = certs.toArray(new X509Certificate[certs.size()]);172} catch (ArrayStoreException ase) {173// Wrong type, not X509Certificate.174throw new IllegalArgumentException(175"Entry does not contain X509Certificate");176}177}178179/**180* Sets the digest algorithm. If no digest algorithm is specified,181* the default algorithm returned by {@link #getDefaultDigestAlgorithm}182* will be used.183*184* @param algorithm the standard name of the algorithm. See185* the {@code MessageDigest} section in the <a href=186* "{@docRoot}/../specs/security/standard-names.html#messagedigest-algorithms">187* Java Cryptography Architecture Standard Algorithm Name188* Documentation</a> for information about standard algorithm names.189* @return the {@code JarSigner.Builder} itself.190* @throws NoSuchAlgorithmException if {@code algorithm} is not available.191*/192public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException {193MessageDigest.getInstance(Objects.requireNonNull(algorithm));194this.digestalg = new String[]{algorithm};195this.digestProvider = null;196return this;197}198199/**200* Sets the digest algorithm from the specified provider.201* If no digest algorithm is specified, the default algorithm202* returned by {@link #getDefaultDigestAlgorithm} will be used.203*204* @param algorithm the standard name of the algorithm. See205* the {@code MessageDigest} section in the <a href=206* "{@docRoot}/../specs/security/standard-names.html#messagedigest-algorithms">207* Java Cryptography Architecture Standard Algorithm Name208* Documentation</a> for information about standard algorithm names.209* @param provider the provider.210* @return the {@code JarSigner.Builder} itself.211* @throws NoSuchAlgorithmException if {@code algorithm} is not212* available in the specified provider.213*/214public Builder digestAlgorithm(String algorithm, Provider provider)215throws NoSuchAlgorithmException {216MessageDigest.getInstance(217Objects.requireNonNull(algorithm),218Objects.requireNonNull(provider));219this.digestalg = new String[]{algorithm};220this.digestProvider = provider;221return this;222}223224/**225* Sets the signature algorithm. If no signature algorithm226* is specified, the default signature algorithm returned by227* {@link #getDefaultSignatureAlgorithm} for the private key228* will be used.229*230* @param algorithm the standard name of the algorithm. See231* the {@code Signature} section in the <a href=232* "{@docRoot}/../specs/security/standard-names.html#signature-algorithms">233* Java Cryptography Architecture Standard Algorithm Name234* Documentation</a> for information about standard algorithm names.235* @return the {@code JarSigner.Builder} itself.236* @throws NoSuchAlgorithmException if {@code algorithm} is not available.237* @throws IllegalArgumentException if {@code algorithm} is not238* compatible with the algorithm of the signer's private key.239*/240public Builder signatureAlgorithm(String algorithm)241throws NoSuchAlgorithmException {242// Check availability243Signature.getInstance(Objects.requireNonNull(algorithm));244SignatureUtil.checkKeyAndSigAlgMatch(privateKey, algorithm);245this.sigalg = algorithm;246this.sigProvider = null;247return this;248}249250/**251* Sets the signature algorithm from the specified provider. If no252* signature algorithm is specified, the default signature algorithm253* returned by {@link #getDefaultSignatureAlgorithm} for the private254* key will be used.255*256* @param algorithm the standard name of the algorithm. See257* the {@code Signature} section in the <a href=258* "{@docRoot}/../specs/security/standard-names.html#signature-algorithms">259* Java Cryptography Architecture Standard Algorithm Name260* Documentation</a> for information about standard algorithm names.261* @param provider the provider.262* @return the {@code JarSigner.Builder} itself.263* @throws NoSuchAlgorithmException if {@code algorithm} is not264* available in the specified provider.265* @throws IllegalArgumentException if {@code algorithm} is not266* compatible with the algorithm of the signer's private key.267*/268public Builder signatureAlgorithm(String algorithm, Provider provider)269throws NoSuchAlgorithmException {270// Check availability271Signature.getInstance(272Objects.requireNonNull(algorithm),273Objects.requireNonNull(provider));274SignatureUtil.checkKeyAndSigAlgMatch(privateKey, algorithm);275this.sigalg = algorithm;276this.sigProvider = provider;277return this;278}279280/**281* Sets the URI of the Time Stamping Authority (TSA).282*283* @param uri the URI.284* @return the {@code JarSigner.Builder} itself.285*/286public Builder tsa(URI uri) {287this.tsaUrl = Objects.requireNonNull(uri);288return this;289}290291/**292* Sets the signer name. The name will be used as the base name for293* the signature files. All lowercase characters will be converted to294* uppercase for signature file names. If a signer name is not295* specified, the string "SIGNER" will be used.296*297* @param name the signer name.298* @return the {@code JarSigner.Builder} itself.299* @throws IllegalArgumentException if {@code name} is empty or has300* a size bigger than 8, or it contains characters not from the301* set "a-zA-Z0-9_-".302*/303public Builder signerName(String name) {304if (name.isEmpty() || name.length() > 8) {305throw new IllegalArgumentException("Name too long");306}307308name = name.toUpperCase(Locale.ENGLISH);309310for (int j = 0; j < name.length(); j++) {311char c = name.charAt(j);312if (!313((c >= 'A' && c <= 'Z') ||314(c >= '0' && c <= '9') ||315(c == '-') ||316(c == '_'))) {317throw new IllegalArgumentException(318"Invalid characters in name");319}320}321this.signerName = name;322return this;323}324325/**326* Sets en event handler that will be triggered when a {@link JarEntry}327* is to be added, signed, or updated during the signing process.328* <p>329* The handler can be used to display signing progress. The first330* argument of the handler can be "adding", "signing", or "updating",331* and the second argument is the name of the {@link JarEntry}332* being processed.333*334* @param handler the event handler.335* @return the {@code JarSigner.Builder} itself.336*/337public Builder eventHandler(BiConsumer<String,String> handler) {338this.handler = Objects.requireNonNull(handler);339return this;340}341342/**343* Sets an additional implementation-specific property indicated by344* the specified key.345*346* @implNote This implementation supports the following properties:347* <ul>348* <li>"tsaDigestAlg": algorithm of digest data in the timestamping349* request. The default value is the same as the result of350* {@link #getDefaultDigestAlgorithm}.351* <li>"tsaPolicyId": TSAPolicyID for Timestamping Authority.352* No default value.353* <li>"internalsf": "true" if the .SF file is included inside the354* signature block, "false" otherwise. Default "false".355* <li>"sectionsonly": "true" if the .SF file only contains the hash356* value for each section of the manifest and not for the whole357* manifest, "false" otherwise. Default "false".358* </ul>359* All property names are case-insensitive.360*361* @param key the name of the property.362* @param value the value of the property.363* @return the {@code JarSigner.Builder} itself.364* @throws UnsupportedOperationException if the key is not supported365* by this implementation.366* @throws IllegalArgumentException if the value is not accepted as367* a legal value for this key.368*/369public Builder setProperty(String key, String value) {370Objects.requireNonNull(key);371Objects.requireNonNull(value);372switch (key.toLowerCase(Locale.US)) {373case "tsadigestalg":374try {375MessageDigest.getInstance(value);376} catch (NoSuchAlgorithmException nsae) {377throw new IllegalArgumentException(378"Invalid tsadigestalg", nsae);379}380this.tSADigestAlg = value;381break;382case "tsapolicyid":383this.tSAPolicyID = value;384break;385case "internalsf":386this.internalsf = parseBoolean("interalsf", value);387break;388case "sectionsonly":389this.sectionsonly = parseBoolean("sectionsonly", value);390break;391case "altsignerpath":392altSignerPath = value;393break;394case "altsigner":395altSigner = value;396break;397default:398throw new UnsupportedOperationException(399"Unsupported key " + key);400}401return this;402}403404private static boolean parseBoolean(String name, String value) {405switch (value) {406case "true":407return true;408case "false":409return false;410default:411throw new IllegalArgumentException(412"Invalid " + name + " value");413}414}415416/**417* Gets the default digest algorithm.418*419* @implNote This implementation returns "SHA-256". The value may420* change in the future.421*422* @return the default digest algorithm.423*/424public static String getDefaultDigestAlgorithm() {425return "SHA-256";426}427428/**429* Gets the default signature algorithm for a private key.430* For example, SHA256withRSA for a 2048-bit RSA key, and431* SHA384withECDSA for a 384-bit EC key.432*433* @implNote This implementation makes use of comparable strengths434* as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.4.435* Specifically, if a DSA or RSA key with a key size greater than 7680436* bits, or an EC key with a key size greater than or equal to 512 bits,437* SHA-512 will be used as the hash function for the signature.438* If a DSA or RSA key has a key size greater than 3072 bits, or an439* EC key has a key size greater than or equal to 384 bits, SHA-384 will440* be used. Otherwise, SHA-256 will be used. The value may441* change in the future.442*443* @param key the private key.444* @return the default signature algorithm. Returns null if a default445* signature algorithm cannot be found. In this case,446* {@link #signatureAlgorithm} must be called to specify a447* signature algorithm. Otherwise, the {@link #build} method448* will throw an {@link IllegalArgumentException}.449*/450public static String getDefaultSignatureAlgorithm(PrivateKey key) {451// Attention: sync the spec with SignatureUtil::ecStrength and452// SignatureUtil::ifcFfcStrength.453return SignatureUtil.getDefaultSigAlgForKey(Objects.requireNonNull(key));454}455456/**457* Builds a {@code JarSigner} object from the parameters set by the458* setter methods.459* <p>460* This method does not modify internal state of this {@code Builder}461* object and can be called multiple times to generate multiple462* {@code JarSigner} objects. After this method is called, calling463* any method on this {@code Builder} will have no effect on464* the newly built {@code JarSigner} object.465*466* @return the {@code JarSigner} object.467* @throws IllegalArgumentException if a signature algorithm is not468* set and cannot be derived from the private key using the469* {@link #getDefaultSignatureAlgorithm} method.470*/471public JarSigner build() {472return new JarSigner(this);473}474}475476private static final String META_INF = "META-INF/";477478// All fields in Builder are duplicated here as final. Those not479// provided but has a default value will be filled with default value.480481// Precisely, a final array field can still be modified if only482// reference is copied, no clone is done because we are concerned about483// casual change instead of malicious attack.484485// Signer materials:486private final PrivateKey privateKey;487private final X509Certificate[] certChain;488489// JarSigner options:490private final String[] digestalg;491private final String sigalg;492private final Provider digestProvider;493private final Provider sigProvider;494private final URI tsaUrl;495private final String signerName;496private final BiConsumer<String,String> handler;497498// Implementation-specific properties:499private final String tSAPolicyID;500private final String tSADigestAlg;501private final boolean sectionsonly; // do not "sign" the whole manifest502private final boolean internalsf; // include the .SF inside the PKCS7 block503504@Deprecated(since="16", forRemoval=true)505private final String altSignerPath;506@Deprecated(since="16", forRemoval=true)507private final String altSigner;508private boolean extraAttrsDetected;509510private JarSigner(JarSigner.Builder builder) {511512this.privateKey = builder.privateKey;513this.certChain = builder.certChain;514if (builder.digestalg != null) {515// No need to clone because builder only accepts one alg now516this.digestalg = builder.digestalg;517} else {518this.digestalg = new String[] {519Builder.getDefaultDigestAlgorithm() };520}521this.digestProvider = builder.digestProvider;522if (builder.sigalg != null) {523this.sigalg = builder.sigalg;524} else {525this.sigalg = JarSigner.Builder526.getDefaultSignatureAlgorithm(privateKey);527if (this.sigalg == null) {528throw new IllegalArgumentException(529"No signature alg for " + privateKey.getAlgorithm());530}531}532this.sigProvider = builder.sigProvider;533this.tsaUrl = builder.tsaUrl;534535if (builder.signerName == null) {536this.signerName = "SIGNER";537} else {538this.signerName = builder.signerName;539}540this.handler = builder.handler;541542if (builder.tSADigestAlg != null) {543this.tSADigestAlg = builder.tSADigestAlg;544} else {545this.tSADigestAlg = Builder.getDefaultDigestAlgorithm();546}547this.tSAPolicyID = builder.tSAPolicyID;548this.sectionsonly = builder.sectionsonly;549this.internalsf = builder.internalsf;550this.altSigner = builder.altSigner;551this.altSignerPath = builder.altSignerPath;552553// altSigner cannot support modern algorithms like RSASSA-PSS and EdDSA554if (altSigner != null555&& !sigalg.toUpperCase(Locale.ENGLISH).contains("WITH")) {556throw new IllegalArgumentException(557"Customized ContentSigner is not supported for " + sigalg);558}559}560561/**562* Signs a file into an {@link OutputStream}. This method will not close563* {@code file} or {@code os}.564* <p>565* If an I/O error or signing error occurs during the signing, then it may566* do so after some bytes have been written. Consequently, the output567* stream may be in an inconsistent state. It is strongly recommended that568* it be promptly closed in this case.569*570* @param file the file to sign.571* @param os the output stream.572* @throws JarSignerException if the signing fails.573*/574public void sign(ZipFile file, OutputStream os) {575try {576sign0(Objects.requireNonNull(file),577Objects.requireNonNull(os));578} catch (SocketTimeoutException | CertificateException e) {579// CertificateException is thrown when the received cert from TSA580// has no id-kp-timeStamping in its Extended Key Usages extension.581throw new JarSignerException("Error applying timestamp", e);582} catch (IOException ioe) {583throw new JarSignerException("I/O error", ioe);584} catch (NoSuchAlgorithmException | InvalidKeyException585| InvalidParameterSpecException e) {586throw new JarSignerException("Error in signer materials", e);587} catch (SignatureException se) {588throw new JarSignerException("Error creating signature", se);589}590}591592/**593* Returns the digest algorithm for this {@code JarSigner}.594* <p>595* The return value is never null.596*597* @return the digest algorithm.598*/599public String getDigestAlgorithm() {600return digestalg[0];601}602603/**604* Returns the signature algorithm for this {@code JarSigner}.605* <p>606* The return value is never null.607*608* @return the signature algorithm.609*/610public String getSignatureAlgorithm() {611return sigalg;612}613614/**615* Returns the URI of the Time Stamping Authority (TSA).616*617* @return the URI of the TSA.618*/619public URI getTsa() {620return tsaUrl;621}622623/**624* Returns the signer name of this {@code JarSigner}.625* <p>626* The return value is never null.627*628* @return the signer name.629*/630public String getSignerName() {631return signerName;632}633634/**635* Returns the value of an additional implementation-specific property636* indicated by the specified key. If a property is not set but has a637* default value, the default value will be returned.638*639* @implNote See {@link JarSigner.Builder#setProperty} for a list of640* properties this implementation supports. All property names are641* case-insensitive.642*643* @param key the name of the property.644* @return the value for the property.645* @throws UnsupportedOperationException if the key is not supported646* by this implementation.647*/648public String getProperty(String key) {649Objects.requireNonNull(key);650switch (key.toLowerCase(Locale.US)) {651case "tsadigestalg":652return tSADigestAlg;653case "tsapolicyid":654return tSAPolicyID;655case "internalsf":656return Boolean.toString(internalsf);657case "sectionsonly":658return Boolean.toString(sectionsonly);659case "altsignerpath":660return altSignerPath;661case "altsigner":662return altSigner;663default:664throw new UnsupportedOperationException(665"Unsupported key " + key);666}667}668669private void sign0(ZipFile zipFile, OutputStream os)670throws IOException, CertificateException, NoSuchAlgorithmException,671SignatureException, InvalidKeyException, InvalidParameterSpecException {672MessageDigest[] digests;673try {674digests = new MessageDigest[digestalg.length];675for (int i = 0; i < digestalg.length; i++) {676if (digestProvider == null) {677digests[i] = MessageDigest.getInstance(digestalg[i]);678} else {679digests[i] = MessageDigest.getInstance(680digestalg[i], digestProvider);681}682}683} catch (NoSuchAlgorithmException asae) {684// Should not happen. User provided alg were checked, and default685// alg should always be available.686throw new AssertionError(asae);687}688689ZipOutputStream zos = new ZipOutputStream(os);690691Manifest manifest = new Manifest();692byte[] mfRawBytes = null;693694// Check if manifest exists695ZipEntry mfFile = getManifestFile(zipFile);696boolean mfCreated = mfFile == null;697if (!mfCreated) {698// Manifest exists. Read its raw bytes.699mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes();700manifest.read(new ByteArrayInputStream(mfRawBytes));701} else {702// Create new manifest703Attributes mattr = manifest.getMainAttributes();704mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),705"1.0");706String javaVendor = System.getProperty("java.vendor");707String jdkVersion = System.getProperty("java.version");708mattr.putValue("Created-By", jdkVersion + " (" + javaVendor709+ ")");710mfFile = new ZipEntry(JarFile.MANIFEST_NAME);711}712713/*714* For each entry in jar715* (except for signature-related META-INF entries),716* do the following:717*718* - if entry is not contained in manifest, add it to manifest;719* - if entry is contained in manifest, calculate its hash and720* compare it with the one in the manifest; if they are721* different, replace the hash in the manifest with the newly722* generated one. (This may invalidate existing signatures!)723*/724Vector<ZipEntry> mfFiles = new Vector<>();725726boolean wasSigned = false;727728for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();729enum_.hasMoreElements(); ) {730ZipEntry ze = enum_.nextElement();731732if (ze.getName().startsWith(META_INF)) {733// Store META-INF files in vector, so they can be written734// out first735mfFiles.addElement(ze);736737String zeNameUp = ze.getName().toUpperCase(Locale.ENGLISH);738if (SignatureFileVerifier.isBlockOrSF(zeNameUp)739// no need to preserve binary manifest portions740// if the only existing signature will be replaced741&& !zeNameUp.startsWith(SignatureFile742.getBaseSignatureFilesName(signerName))) {743wasSigned = true;744}745746if (SignatureFileVerifier.isSigningRelated(ze.getName())) {747// ignore signature-related and manifest files748continue;749}750}751752if (manifest.getAttributes(ze.getName()) != null) {753// jar entry is contained in manifest, check and754// possibly update its digest attributes755updateDigests(ze, zipFile, digests, manifest);756} else if (!ze.isDirectory()) {757// Add entry to manifest758Attributes attrs = getDigestAttributes(ze, zipFile, digests);759manifest.getEntries().put(ze.getName(), attrs);760}761}762763/*764* Note:765*766* The Attributes object is based on HashMap and can handle767* continuation lines. Therefore, even if the contents are not changed768* (in a Map view), the bytes that it write() may be different from769* the original bytes that it read() from. Since the signature is770* based on raw bytes, we must retain the exact bytes.771*/772boolean mfModified;773ByteArrayOutputStream baos = new ByteArrayOutputStream();774if (mfCreated || !wasSigned) {775mfModified = true;776manifest.write(baos);777mfRawBytes = baos.toByteArray();778} else {779780// the manifest before updating781Manifest oldManifest = new Manifest(782new ByteArrayInputStream(mfRawBytes));783mfModified = !oldManifest.equals(manifest);784if (!mfModified) {785// leave whole manifest (mfRawBytes) unmodified786} else {787// reproduce the manifest raw bytes for unmodified sections788manifest.write(baos);789byte[] mfNewRawBytes = baos.toByteArray();790baos.reset();791792ManifestDigester oldMd = new ManifestDigester(mfRawBytes);793ManifestDigester newMd = new ManifestDigester(mfNewRawBytes);794795// main attributes796if (manifest.getMainAttributes().equals(797oldManifest.getMainAttributes())798&& (manifest.getEntries().isEmpty() ||799oldMd.getMainAttsEntry().isProperlyDelimited())) {800oldMd.getMainAttsEntry().reproduceRaw(baos);801} else {802newMd.getMainAttsEntry().reproduceRaw(baos);803}804805// individual sections806for (Map.Entry<String,Attributes> entry :807manifest.getEntries().entrySet()) {808String sectionName = entry.getKey();809Attributes entryAtts = entry.getValue();810if (entryAtts.equals(oldManifest.getAttributes(sectionName))811&& oldMd.get(sectionName).isProperlyDelimited()) {812oldMd.get(sectionName).reproduceRaw(baos);813} else {814newMd.get(sectionName).reproduceRaw(baos);815}816}817818mfRawBytes = baos.toByteArray();819}820}821822// Write out the manifest823if (mfModified) {824// manifest file has new length825mfFile = new ZipEntry(JarFile.MANIFEST_NAME);826}827if (handler != null) {828if (mfCreated || !mfModified) {829handler.accept("adding", mfFile.getName());830} else {831handler.accept("updating", mfFile.getName());832}833}834zos.putNextEntry(mfFile);835zos.write(mfRawBytes);836837// Calculate SignatureFile (".SF") and SignatureBlockFile838ManifestDigester manDig = new ManifestDigester(mfRawBytes);839SignatureFile sf = new SignatureFile(digests, manifest, manDig,840signerName, sectionsonly);841842byte[] block;843844baos.reset();845sf.write(baos);846byte[] content = baos.toByteArray();847848if (altSigner == null) {849Function<byte[], PKCS9Attributes> timestamper = null;850if (tsaUrl != null) {851timestamper = s -> {852try {853// Timestamp the signature854HttpTimestamper tsa = new HttpTimestamper(tsaUrl);855byte[] tsToken = PKCS7.generateTimestampToken(856tsa, tSAPolicyID, tSADigestAlg, s);857858return new PKCS9Attributes(new PKCS9Attribute[]{859new PKCS9Attribute(860PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID,861tsToken)});862} catch (IOException | CertificateException e) {863throw new RuntimeException(e);864}865};866}867// We now create authAttrs in block data, so "direct == false".868block = PKCS7.generateNewSignedData(sigalg, sigProvider, privateKey, certChain,869content, internalsf, false, timestamper);870} else {871Signature signer = SignatureUtil.fromKey(sigalg, privateKey, sigProvider);872signer.update(content);873byte[] signature = signer.sign();874875@SuppressWarnings("removal")876ContentSignerParameters params =877new JarSignerParameters(null, tsaUrl, tSAPolicyID,878tSADigestAlg, signature,879signer.getAlgorithm(), certChain, content, zipFile);880@SuppressWarnings("removal")881ContentSigner signingMechanism = loadSigningMechanism(altSigner, altSignerPath);882block = signingMechanism.generateSignedData(883params,884!internalsf,885params.getTimestampingAuthority() != null886|| params.getTimestampingAuthorityCertificate() != null);887}888889String sfFilename = sf.getMetaName();890String bkFilename = sf.getBlockName(privateKey);891892ZipEntry sfFile = new ZipEntry(sfFilename);893ZipEntry bkFile = new ZipEntry(bkFilename);894895long time = System.currentTimeMillis();896sfFile.setTime(time);897bkFile.setTime(time);898899// signature file900zos.putNextEntry(sfFile);901sf.write(zos);902903if (handler != null) {904if (zipFile.getEntry(sfFilename) != null) {905handler.accept("updating", sfFilename);906} else {907handler.accept("adding", sfFilename);908}909}910911// signature block file912zos.putNextEntry(bkFile);913zos.write(block);914915if (handler != null) {916if (zipFile.getEntry(bkFilename) != null) {917handler.accept("updating", bkFilename);918} else {919handler.accept("adding", bkFilename);920}921}922923// Write out all other META-INF files that we stored in the924// vector925for (int i = 0; i < mfFiles.size(); i++) {926ZipEntry ze = mfFiles.elementAt(i);927if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)928&& !ze.getName().equalsIgnoreCase(sfFilename)929&& !ze.getName().equalsIgnoreCase(bkFilename)) {930if (ze.getName().startsWith(SignatureFile931.getBaseSignatureFilesName(signerName))932&& SignatureFileVerifier.isBlockOrSF(ze.getName())) {933if (handler != null) {934handler.accept("updating", ze.getName());935}936continue;937}938if (handler != null) {939if (manifest.getAttributes(ze.getName()) != null) {940handler.accept("signing", ze.getName());941} else if (!ze.isDirectory()) {942handler.accept("adding", ze.getName());943}944}945writeEntry(zipFile, zos, ze);946}947}948949// Write out all other files950for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();951enum_.hasMoreElements(); ) {952ZipEntry ze = enum_.nextElement();953954if (!ze.getName().startsWith(META_INF)) {955if (handler != null) {956if (manifest.getAttributes(ze.getName()) != null) {957handler.accept("signing", ze.getName());958} else {959handler.accept("adding", ze.getName());960}961}962writeEntry(zipFile, zos, ze);963}964}965zipFile.close();966zos.close();967}968969private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)970throws IOException {971ZipEntry ze2 = new ZipEntry(ze.getName());972ze2.setMethod(ze.getMethod());973ze2.setTime(ze.getTime());974ze2.setComment(ze.getComment());975ze2.setExtra(ze.getExtra());976int extraAttrs = JUZFA.getExtraAttributes(ze);977if (!extraAttrsDetected && extraAttrs != -1) {978extraAttrsDetected = true;979Event.report(Event.ReporterCategory.ZIPFILEATTRS, "detected");980}981JUZFA.setExtraAttributes(ze2, extraAttrs);982if (ze.getMethod() == ZipEntry.STORED) {983ze2.setSize(ze.getSize());984ze2.setCrc(ze.getCrc());985}986os.putNextEntry(ze2);987writeBytes(zf, ze, os);988}989990private void writeBytes991(ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {992try (InputStream is = zf.getInputStream(ze)) {993is.transferTo(os);994}995}996997private void updateDigests(ZipEntry ze, ZipFile zf,998MessageDigest[] digests,999Manifest mf) throws IOException {1000Attributes attrs = mf.getAttributes(ze.getName());1001String[] base64Digests = getDigests(ze, zf, digests);10021003for (int i = 0; i < digests.length; i++) {1004// The entry name to be written into attrs1005String name = null;1006try {1007// Find if the digest already exists. An algorithm could have1008// different names. For example, last time it was SHA, and this1009// time it's SHA-1.1010AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());1011for (Object key : attrs.keySet()) {1012if (key instanceof Attributes.Name) {1013String n = key.toString();1014if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {1015String tmp = n.substring(0, n.length() - 7);1016if (AlgorithmId.get(tmp).equals(aid)) {1017name = n;1018break;1019}1020}1021}1022}1023} catch (NoSuchAlgorithmException nsae) {1024// Ignored. Writing new digest entry.1025}10261027if (name == null) {1028name = digests[i].getAlgorithm() + "-Digest";1029}1030attrs.putValue(name, base64Digests[i]);1031}1032}10331034private Attributes getDigestAttributes(1035ZipEntry ze, ZipFile zf, MessageDigest[] digests)1036throws IOException {10371038String[] base64Digests = getDigests(ze, zf, digests);1039Attributes attrs = new Attributes();10401041for (int i = 0; i < digests.length; i++) {1042attrs.putValue(digests[i].getAlgorithm() + "-Digest",1043base64Digests[i]);1044}1045return attrs;1046}10471048/*1049* Returns manifest entry from given jar file, or null if given jar file1050* does not have a manifest entry.1051*/1052private ZipEntry getManifestFile(ZipFile zf) {1053ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);1054if (ze == null) {1055// Check all entries for matching name1056Enumeration<? extends ZipEntry> enum_ = zf.entries();1057while (enum_.hasMoreElements() && ze == null) {1058ze = enum_.nextElement();1059if (!JarFile.MANIFEST_NAME.equalsIgnoreCase1060(ze.getName())) {1061ze = null;1062}1063}1064}1065return ze;1066}10671068private String[] getDigests(1069ZipEntry ze, ZipFile zf, MessageDigest[] digests)1070throws IOException {10711072int n, i;1073try (InputStream is = zf.getInputStream(ze)) {1074long left = ze.getSize();1075byte[] buffer = new byte[8192];1076while ((left > 0)1077&& (n = is.read(buffer, 0, buffer.length)) != -1) {1078for (i = 0; i < digests.length; i++) {1079digests[i].update(buffer, 0, n);1080}1081left -= n;1082}1083}10841085// complete the digests1086String[] base64Digests = new String[digests.length];1087for (i = 0; i < digests.length; i++) {1088base64Digests[i] = Base64.getEncoder()1089.encodeToString(digests[i].digest());1090}1091return base64Digests;1092}10931094/*1095* Try to load the specified signing mechanism.1096* The URL class loader is used.1097*/1098@SuppressWarnings("removal")1099private ContentSigner loadSigningMechanism(String signerClassName,1100String signerClassPath) {11011102// If there is no signerClassPath provided, search from here1103if (signerClassPath == null) {1104signerClassPath = ".";1105}11061107// construct class loader1108String cpString; // make sure env.class.path defaults to dot11091110// do prepends to get correct ordering1111cpString = PathList.appendPath(1112System.getProperty("env.class.path"), null);1113cpString = PathList.appendPath(1114System.getProperty("java.class.path"), cpString);1115cpString = PathList.appendPath(signerClassPath, cpString);1116URL[] urls = PathList.pathToURLs(cpString);1117ClassLoader appClassLoader = new URLClassLoader(urls);11181119try {1120// attempt to find signer1121Class<?> signerClass = appClassLoader.loadClass(signerClassName);1122Object signer = signerClass.getDeclaredConstructor().newInstance();1123return (ContentSigner) signer;1124} catch (ClassNotFoundException|InstantiationException|1125IllegalAccessException|ClassCastException|1126NoSuchMethodException| InvocationTargetException e) {1127throw new IllegalArgumentException(1128"Invalid altSigner or altSignerPath", e);1129}1130}11311132static class SignatureFile {11331134/**1135* SignatureFile1136*/1137Manifest sf;11381139/**1140* .SF base name1141*/1142String baseName;11431144public SignatureFile(MessageDigest digests[],1145Manifest mf,1146ManifestDigester md,1147String baseName,1148boolean sectionsonly) {11491150this.baseName = baseName;11511152String version = System.getProperty("java.version");1153String javaVendor = System.getProperty("java.vendor");11541155sf = new Manifest();1156Attributes mattr = sf.getMainAttributes();11571158mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");1159mattr.putValue("Created-By", version + " (" + javaVendor + ")");11601161if (!sectionsonly) {1162for (MessageDigest digest: digests) {1163mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest",1164Base64.getEncoder().encodeToString(1165md.manifestDigest(digest)));1166}1167}11681169// create digest of the manifest main attributes1170ManifestDigester.Entry mde = md.getMainAttsEntry(false);1171if (mde != null) {1172for (MessageDigest digest : digests) {1173mattr.putValue(digest.getAlgorithm() + "-Digest-" +1174ManifestDigester.MF_MAIN_ATTRS,1175Base64.getEncoder().encodeToString(mde.digest(digest)));1176}1177} else {1178throw new IllegalStateException1179("ManifestDigester failed to create " +1180"Manifest-Main-Attribute entry");1181}11821183// go through the manifest entries and create the digests1184Map<String, Attributes> entries = sf.getEntries();1185for (String name: mf.getEntries().keySet()) {1186mde = md.get(name, false);1187if (mde != null) {1188Attributes attr = new Attributes();1189for (MessageDigest digest: digests) {1190attr.putValue(digest.getAlgorithm() + "-Digest",1191Base64.getEncoder().encodeToString(1192mde.digest(digest)));1193}1194entries.put(name, attr);1195}1196}1197}11981199// Write .SF file1200public void write(OutputStream out) throws IOException {1201sf.write(out);1202}12031204private static String getBaseSignatureFilesName(String baseName) {1205return "META-INF/" + baseName + ".";1206}12071208// get .SF file name1209public String getMetaName() {1210return getBaseSignatureFilesName(baseName) + "SF";1211}12121213// get .DSA (or .DSA, .EC) file name1214public String getBlockName(PrivateKey privateKey) {1215String type = SignatureFileVerifier.getBlockExtension(privateKey);1216return getBaseSignatureFilesName(baseName) + type;1217}1218}12191220@SuppressWarnings("removal")1221@Deprecated(since="16", forRemoval=true)1222class JarSignerParameters implements ContentSignerParameters {12231224private String[] args;1225private URI tsa;1226private byte[] signature;1227private String signatureAlgorithm;1228private X509Certificate[] signerCertificateChain;1229private byte[] content;1230private ZipFile source;1231private String tSAPolicyID;1232private String tSADigestAlg;12331234JarSignerParameters(String[] args, URI tsa,1235String tSAPolicyID, String tSADigestAlg,1236byte[] signature, String signatureAlgorithm,1237X509Certificate[] signerCertificateChain,1238byte[] content, ZipFile source) {12391240Objects.requireNonNull(signature);1241Objects.requireNonNull(signatureAlgorithm);1242Objects.requireNonNull(signerCertificateChain);12431244this.args = args;1245this.tsa = tsa;1246this.tSAPolicyID = tSAPolicyID;1247this.tSADigestAlg = tSADigestAlg;1248this.signature = signature;1249this.signatureAlgorithm = signatureAlgorithm;1250this.signerCertificateChain = signerCertificateChain;1251this.content = content;1252this.source = source;1253}12541255public String[] getCommandLine() {1256return args;1257}12581259public URI getTimestampingAuthority() {1260return tsa;1261}12621263public X509Certificate getTimestampingAuthorityCertificate() {1264// We don't use this param. Always provide tsaURI.1265return null;1266}12671268public String getTSAPolicyID() {1269return tSAPolicyID;1270}12711272public String getTSADigestAlg() {1273return tSADigestAlg;1274}12751276public byte[] getSignature() {1277return signature;1278}12791280public String getSignatureAlgorithm() {1281return signatureAlgorithm;1282}12831284public X509Certificate[] getSignerCertificateChain() {1285return signerCertificateChain;1286}12871288public byte[] getContent() {1289return content;1290}12911292public ZipFile getSource() {1293return source;1294}1295}1296}129712981299