1 /*
2  * Copyright (c) 2015, 2018, 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 jdk.security.jarsigner;
27 
28 import com.sun.jarsigner.ContentSigner;
29 import com.sun.jarsigner.ContentSignerParameters;
30 import sun.security.tools.PathList;
31 import sun.security.tools.jarsigner.TimestampedSigner;
32 import sun.security.util.ManifestDigester;
33 import sun.security.util.SignatureFileVerifier;
34 import sun.security.x509.AlgorithmId;
35 
36 import java.io.*;
37 import java.net.SocketTimeoutException;
38 import java.net.URI;
39 import java.net.URL;
40 import java.net.URLClassLoader;
41 import java.security.*;
42 import java.security.cert.CertPath;
43 import java.security.cert.Certificate;
44 import java.security.cert.CertificateException;
45 import java.security.cert.X509Certificate;
46 import java.util.*;
47 import java.util.function.BiConsumer;
48 import java.util.jar.Attributes;
49 import java.util.jar.JarEntry;
50 import java.util.jar.JarFile;
51 import java.util.jar.Manifest;
52 import java.util.zip.ZipEntry;
53 import java.util.zip.ZipFile;
54 import java.util.zip.ZipOutputStream;
55 
56 /**
57  * An immutable utility class to sign a jar file.
58  * <p>
59  * A caller creates a {@code JarSigner.Builder} object, (optionally) sets
60  * some parameters, and calls {@link JarSigner.Builder#build build} to create
61  * a {@code JarSigner} object. This {@code JarSigner} object can then
62  * be used to sign a jar file.
63  * <p>
64  * Unless otherwise stated, calling a method of {@code JarSigner} or
65  * {@code JarSigner.Builder} with a null argument will throw
66  * a {@link NullPointerException}.
67  * <p>
68  * Example:
69  * <pre>
70  * JarSigner signer = new JarSigner.Builder(key, certPath)
71  *         .digestAlgorithm("SHA-1")
72  *         .signatureAlgorithm("SHA1withDSA")
73  *         .build();
74  * try (ZipFile in = new ZipFile(inputFile);
75  *         FileOutputStream out = new FileOutputStream(outputFile)) {
76  *     signer.sign(in, out);
77  * }
78  * </pre>
79  *
80  * @since 9
81  */
82 public final class JarSigner {
83 
84     /**
85      * A mutable builder class that can create an immutable {@code JarSigner}
86      * from various signing-related parameters.
87      *
88      * @since 9
89      */
90     public static class Builder {
91 
92         // Signer materials:
93         final PrivateKey privateKey;
94         final X509Certificate[] certChain;
95 
96         // JarSigner options:
97         // Support multiple digestalg internally. Can be null, but not empty
98         String[] digestalg;
99         String sigalg;
100         // Precisely should be one provider for each digestalg, maybe later
101         Provider digestProvider;
102         Provider sigProvider;
103         URI tsaUrl;
104         String signerName;
105         BiConsumer<String,String> handler;
106 
107         // Implementation-specific properties:
108         String tSAPolicyID;
109         String tSADigestAlg;
110         boolean signManifest = true;
111         boolean externalSF = true;
112         String altSignerPath;
113         String altSigner;
114 
115         /**
116          * Creates a {@code JarSigner.Builder} object with
117          * a {@link KeyStore.PrivateKeyEntry} object.
118          *
119          * @param entry the {@link KeyStore.PrivateKeyEntry} of the signer.
120          */
Builder(KeyStore.PrivateKeyEntry entry)121         public Builder(KeyStore.PrivateKeyEntry entry) {
122             this.privateKey = entry.getPrivateKey();
123             try {
124                 // called internally, no need to clone
125                 Certificate[] certs = entry.getCertificateChain();
126                 this.certChain = Arrays.copyOf(certs, certs.length,
127                         X509Certificate[].class);
128             } catch (ArrayStoreException ase) {
129                 // Wrong type, not X509Certificate. Won't document.
130                 throw new IllegalArgumentException(
131                         "Entry does not contain X509Certificate");
132             }
133         }
134 
135         /**
136          * Creates a {@code JarSigner.Builder} object with a private key and
137          * a certification path.
138          *
139          * @param privateKey the private key of the signer.
140          * @param certPath the certification path of the signer.
141          * @throws IllegalArgumentException if {@code certPath} is empty, or
142          *      the {@code privateKey} algorithm does not match the algorithm
143          *      of the {@code PublicKey} in the end entity certificate
144          *      (the first certificate in {@code certPath}).
145          */
Builder(PrivateKey privateKey, CertPath certPath)146         public Builder(PrivateKey privateKey, CertPath certPath) {
147             List<? extends Certificate> certs = certPath.getCertificates();
148             if (certs.isEmpty()) {
149                 throw new IllegalArgumentException("certPath cannot be empty");
150             }
151             if (!privateKey.getAlgorithm().equals
152                     (certs.get(0).getPublicKey().getAlgorithm())) {
153                 throw new IllegalArgumentException
154                         ("private key algorithm does not match " +
155                                 "algorithm of public key in end entity " +
156                                 "certificate (the 1st in certPath)");
157             }
158             this.privateKey = privateKey;
159             try {
160                 this.certChain = certs.toArray(new X509Certificate[certs.size()]);
161             } catch (ArrayStoreException ase) {
162                 // Wrong type, not X509Certificate.
163                 throw new IllegalArgumentException(
164                         "Entry does not contain X509Certificate");
165             }
166         }
167 
168         /**
169          * Sets the digest algorithm. If no digest algorithm is specified,
170          * the default algorithm returned by {@link #getDefaultDigestAlgorithm}
171          * will be used.
172          *
173          * @param algorithm the standard name of the algorithm. See
174          *      the {@code MessageDigest} section in the <a href=
175          *      "{@docRoot}/../specs/security/standard-names.html#messagedigest-algorithms">
176          *      Java Cryptography Architecture Standard Algorithm Name
177          *      Documentation</a> for information about standard algorithm names.
178          * @return the {@code JarSigner.Builder} itself.
179          * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
180          */
digestAlgorithm(String algorithm)181         public Builder digestAlgorithm(String algorithm) throws NoSuchAlgorithmException {
182             MessageDigest.getInstance(Objects.requireNonNull(algorithm));
183             this.digestalg = new String[]{algorithm};
184             this.digestProvider = null;
185             return this;
186         }
187 
188         /**
189          * Sets the digest algorithm from the specified provider.
190          * If no digest algorithm is specified, the default algorithm
191          * returned by {@link #getDefaultDigestAlgorithm} will be used.
192          *
193          * @param algorithm the standard name of the algorithm. See
194          *      the {@code MessageDigest} section in the <a href=
195          *      "{@docRoot}/../specs/security/standard-names.html#messagedigest-algorithms">
196          *      Java Cryptography Architecture Standard Algorithm Name
197          *      Documentation</a> for information about standard algorithm names.
198          * @param provider the provider.
199          * @return the {@code JarSigner.Builder} itself.
200          * @throws NoSuchAlgorithmException if {@code algorithm} is not
201          *      available in the specified provider.
202          */
digestAlgorithm(String algorithm, Provider provider)203         public Builder digestAlgorithm(String algorithm, Provider provider)
204                 throws NoSuchAlgorithmException {
205             MessageDigest.getInstance(
206                     Objects.requireNonNull(algorithm),
207                     Objects.requireNonNull(provider));
208             this.digestalg = new String[]{algorithm};
209             this.digestProvider = provider;
210             return this;
211         }
212 
213         /**
214          * Sets the signature algorithm. If no signature algorithm
215          * is specified, the default signature algorithm returned by
216          * {@link #getDefaultSignatureAlgorithm} for the private key
217          * will be used.
218          *
219          * @param algorithm the standard name of the algorithm. See
220          *      the {@code Signature} section in the <a href=
221          *      "{@docRoot}/../specs/security/standard-names.html#signature-algorithms">
222          *      Java Cryptography Architecture Standard Algorithm Name
223          *      Documentation</a> for information about standard algorithm names.
224          * @return the {@code JarSigner.Builder} itself.
225          * @throws NoSuchAlgorithmException if {@code algorithm} is not available.
226          * @throws IllegalArgumentException if {@code algorithm} is not
227          *      compatible with the algorithm of the signer's private key.
228          */
signatureAlgorithm(String algorithm)229         public Builder signatureAlgorithm(String algorithm)
230                 throws NoSuchAlgorithmException {
231             // Check availability
232             Signature.getInstance(Objects.requireNonNull(algorithm));
233             AlgorithmId.checkKeyAndSigAlgMatch(
234                     privateKey.getAlgorithm(), algorithm);
235             this.sigalg = algorithm;
236             this.sigProvider = null;
237             return this;
238         }
239 
240         /**
241          * Sets the signature algorithm from the specified provider. If no
242          * signature algorithm is specified, the default signature algorithm
243          * returned by {@link #getDefaultSignatureAlgorithm} for the private
244          * key will be used.
245          *
246          * @param algorithm the standard name of the algorithm. See
247          *      the {@code Signature} section in the <a href=
248          *      "{@docRoot}/../specs/security/standard-names.html#signature-algorithms">
249          *      Java Cryptography Architecture Standard Algorithm Name
250          *      Documentation</a> for information about standard algorithm names.
251          * @param provider  the provider.
252          * @return the {@code JarSigner.Builder} itself.
253          * @throws NoSuchAlgorithmException if {@code algorithm} is not
254          *      available in the specified provider.
255          * @throws IllegalArgumentException if {@code algorithm} is not
256          *      compatible with the algorithm of the signer's private key.
257          */
signatureAlgorithm(String algorithm, Provider provider)258         public Builder signatureAlgorithm(String algorithm, Provider provider)
259                 throws NoSuchAlgorithmException {
260             // Check availability
261             Signature.getInstance(
262                     Objects.requireNonNull(algorithm),
263                     Objects.requireNonNull(provider));
264             AlgorithmId.checkKeyAndSigAlgMatch(
265                     privateKey.getAlgorithm(), algorithm);
266             this.sigalg = algorithm;
267             this.sigProvider = provider;
268             return this;
269         }
270 
271         /**
272          * Sets the URI of the Time Stamping Authority (TSA).
273          *
274          * @param uri the URI.
275          * @return the {@code JarSigner.Builder} itself.
276          */
tsa(URI uri)277         public Builder tsa(URI uri) {
278             this.tsaUrl = Objects.requireNonNull(uri);
279             return this;
280         }
281 
282         /**
283          * Sets the signer name. The name will be used as the base name for
284          * the signature files. All lowercase characters will be converted to
285          * uppercase for signature file names. If a signer name is not
286          * specified, the string "SIGNER" will be used.
287          *
288          * @param name the signer name.
289          * @return the {@code JarSigner.Builder} itself.
290          * @throws IllegalArgumentException if {@code name} is empty or has
291          *      a size bigger than 8, or it contains characters not from the
292          *      set "a-zA-Z0-9_-".
293          */
signerName(String name)294         public Builder signerName(String name) {
295             if (name.isEmpty() || name.length() > 8) {
296                 throw new IllegalArgumentException("Name too long");
297             }
298 
299             name = name.toUpperCase(Locale.ENGLISH);
300 
301             for (int j = 0; j < name.length(); j++) {
302                 char c = name.charAt(j);
303                 if (!
304                         ((c >= 'A' && c <= 'Z') ||
305                                 (c >= '0' && c <= '9') ||
306                                 (c == '-') ||
307                                 (c == '_'))) {
308                     throw new IllegalArgumentException(
309                             "Invalid characters in name");
310                 }
311             }
312             this.signerName = name;
313             return this;
314         }
315 
316         /**
317          * Sets en event handler that will be triggered when a {@link JarEntry}
318          * is to be added, signed, or updated during the signing process.
319          * <p>
320          * The handler can be used to display signing progress. The first
321          * argument of the handler can be "adding", "signing", or "updating",
322          * and the second argument is the name of the {@link JarEntry}
323          * being processed.
324          *
325          * @param handler the event handler.
326          * @return the {@code JarSigner.Builder} itself.
327          */
eventHandler(BiConsumer<String,String> handler)328         public Builder eventHandler(BiConsumer<String,String> handler) {
329             this.handler = Objects.requireNonNull(handler);
330             return this;
331         }
332 
333         /**
334          * Sets an additional implementation-specific property indicated by
335          * the specified key.
336          *
337          * @implNote This implementation supports the following properties:
338          * <ul>
339          * <li>"tsaDigestAlg": algorithm of digest data in the timestamping
340          * request. The default value is the same as the result of
341          * {@link #getDefaultDigestAlgorithm}.
342          * <li>"tsaPolicyId": TSAPolicyID for Timestamping Authority.
343          * No default value.
344          * <li>"internalsf": "true" if the .SF file is included inside the
345          * signature block, "false" otherwise. Default "false".
346          * <li>"sectionsonly": "true" if the .SF file only contains the hash
347          * value for each section of the manifest and not for the whole
348          * manifest, "false" otherwise. Default "false".
349          * </ul>
350          * All property names are case-insensitive.
351          *
352          * @param key the name of the property.
353          * @param value the value of the property.
354          * @return the {@code JarSigner.Builder} itself.
355          * @throws UnsupportedOperationException if the key is not supported
356          *      by this implementation.
357          * @throws IllegalArgumentException if the value is not accepted as
358          *      a legal value for this key.
359          */
setProperty(String key, String value)360         public Builder setProperty(String key, String value) {
361             Objects.requireNonNull(key);
362             Objects.requireNonNull(value);
363             switch (key.toLowerCase(Locale.US)) {
364                 case "tsadigestalg":
365                     try {
366                         MessageDigest.getInstance(value);
367                     } catch (NoSuchAlgorithmException nsae) {
368                         throw new IllegalArgumentException(
369                                 "Invalid tsadigestalg", nsae);
370                     }
371                     this.tSADigestAlg = value;
372                     break;
373                 case "tsapolicyid":
374                     this.tSAPolicyID = value;
375                     break;
376                 case "internalsf":
377                     switch (value) {
378                         case "true":
379                             externalSF = false;
380                             break;
381                         case "false":
382                             externalSF = true;
383                             break;
384                         default:
385                             throw new IllegalArgumentException(
386                                 "Invalid internalsf value");
387                     }
388                     break;
389                 case "sectionsonly":
390                     switch (value) {
391                         case "true":
392                             signManifest = false;
393                             break;
394                         case "false":
395                             signManifest = true;
396                             break;
397                         default:
398                             throw new IllegalArgumentException(
399                                 "Invalid signManifest value");
400                     }
401                     break;
402                 case "altsignerpath":
403                     altSignerPath = value;
404                     break;
405                 case "altsigner":
406                     altSigner = value;
407                     break;
408                 default:
409                     throw new UnsupportedOperationException(
410                             "Unsupported key " + key);
411             }
412             return this;
413         }
414 
415         /**
416          * Gets the default digest algorithm.
417          *
418          * @implNote This implementation returns "SHA-256". The value may
419          * change in the future.
420          *
421          * @return the default digest algorithm.
422          */
getDefaultDigestAlgorithm()423         public static String getDefaultDigestAlgorithm() {
424             return "SHA-256";
425         }
426 
427         /**
428          * Gets the default signature algorithm for a private key.
429          * For example, SHA256withRSA for a 2048-bit RSA key, and
430          * SHA384withECDSA for a 384-bit EC key.
431          *
432          * @implNote This implementation makes use of comparable strengths
433          * as defined in Tables 2 and 3 of NIST SP 800-57 Part 1-Rev.4.
434          * Specifically, if a DSA or RSA key with a key size greater than 7680
435          * bits, or an EC key with a key size greater than or equal to 512 bits,
436          * SHA-512 will be used as the hash function for the signature.
437          * If a DSA or RSA key has a key size greater than 3072 bits, or an
438          * EC key has a key size greater than or equal to 384 bits, SHA-384 will
439          * be used. Otherwise, SHA-256 will be used. The value may
440          * change in the future.
441          *
442          * @param key the private key.
443          * @return the default signature algorithm. Returns null if a default
444          *      signature algorithm cannot be found. In this case,
445          *      {@link #signatureAlgorithm} must be called to specify a
446          *      signature algorithm. Otherwise, the {@link #build} method
447          *      will throw an {@link IllegalArgumentException}.
448          */
getDefaultSignatureAlgorithm(PrivateKey key)449         public static String getDefaultSignatureAlgorithm(PrivateKey key) {
450             return AlgorithmId.getDefaultSigAlgForKey(Objects.requireNonNull(key));
451         }
452 
453         /**
454          * Builds a {@code JarSigner} object from the parameters set by the
455          * setter methods.
456          * <p>
457          * This method does not modify internal state of this {@code Builder}
458          * object and can be called multiple times to generate multiple
459          * {@code JarSigner} objects. After this method is called, calling
460          * any method on this {@code Builder} will have no effect on
461          * the newly built {@code JarSigner} object.
462          *
463          * @return the {@code JarSigner} object.
464          * @throws IllegalArgumentException if a signature algorithm is not
465          *      set and cannot be derived from the private key using the
466          *      {@link #getDefaultSignatureAlgorithm} method.
467          */
build()468         public JarSigner build() {
469             return new JarSigner(this);
470         }
471     }
472 
473     private static final String META_INF = "META-INF/";
474 
475     // All fields in Builder are duplicated here as final. Those not
476     // provided but has a default value will be filled with default value.
477 
478     // Precisely, a final array field can still be modified if only
479     // reference is copied, no clone is done because we are concerned about
480     // casual change instead of malicious attack.
481 
482     // Signer materials:
483     private final PrivateKey privateKey;
484     private final X509Certificate[] certChain;
485 
486     // JarSigner options:
487     private final String[] digestalg;
488     private final String sigalg;
489     private final Provider digestProvider;
490     private final Provider sigProvider;
491     private final URI tsaUrl;
492     private final String signerName;
493     private final BiConsumer<String,String> handler;
494 
495     // Implementation-specific properties:
496     private final String tSAPolicyID;
497     private final String tSADigestAlg;
498     private final boolean signManifest; // "sign" the whole manifest
499     private final boolean externalSF; // leave the .SF out of the PKCS7 block
500     private final String altSignerPath;
501     private final String altSigner;
502 
JarSigner(JarSigner.Builder builder)503     private JarSigner(JarSigner.Builder builder) {
504 
505         this.privateKey = builder.privateKey;
506         this.certChain = builder.certChain;
507         if (builder.digestalg != null) {
508             // No need to clone because builder only accepts one alg now
509             this.digestalg = builder.digestalg;
510         } else {
511             this.digestalg = new String[] {
512                     Builder.getDefaultDigestAlgorithm() };
513         }
514         this.digestProvider = builder.digestProvider;
515         if (builder.sigalg != null) {
516             this.sigalg = builder.sigalg;
517         } else {
518             this.sigalg = JarSigner.Builder
519                     .getDefaultSignatureAlgorithm(privateKey);
520             if (this.sigalg == null) {
521                 throw new IllegalArgumentException(
522                         "No signature alg for " + privateKey.getAlgorithm());
523             }
524         }
525         this.sigProvider = builder.sigProvider;
526         this.tsaUrl = builder.tsaUrl;
527 
528         if (builder.signerName == null) {
529             this.signerName = "SIGNER";
530         } else {
531             this.signerName = builder.signerName;
532         }
533         this.handler = builder.handler;
534 
535         if (builder.tSADigestAlg != null) {
536             this.tSADigestAlg = builder.tSADigestAlg;
537         } else {
538             this.tSADigestAlg = Builder.getDefaultDigestAlgorithm();
539         }
540         this.tSAPolicyID = builder.tSAPolicyID;
541         this.signManifest = builder.signManifest;
542         this.externalSF = builder.externalSF;
543         this.altSigner = builder.altSigner;
544         this.altSignerPath = builder.altSignerPath;
545     }
546 
547     /**
548      * Signs a file into an {@link OutputStream}. This method will not close
549      * {@code file} or {@code os}.
550      * <p>
551      * If an I/O error or signing error occurs during the signing, then it may
552      * do so after some bytes have been written. Consequently, the output
553      * stream may be in an inconsistent state. It is strongly recommended that
554      * it be promptly closed in this case.
555      *
556      * @param file the file to sign.
557      * @param os the output stream.
558      * @throws JarSignerException if the signing fails.
559      */
sign(ZipFile file, OutputStream os)560     public void sign(ZipFile file, OutputStream os) {
561         try {
562             sign0(Objects.requireNonNull(file),
563                     Objects.requireNonNull(os));
564         } catch (SocketTimeoutException | CertificateException e) {
565             // CertificateException is thrown when the received cert from TSA
566             // has no id-kp-timeStamping in its Extended Key Usages extension.
567             throw new JarSignerException("Error applying timestamp", e);
568         } catch (IOException ioe) {
569             throw new JarSignerException("I/O error", ioe);
570         } catch (NoSuchAlgorithmException | InvalidKeyException e) {
571             throw new JarSignerException("Error in signer materials", e);
572         } catch (SignatureException se) {
573             throw new JarSignerException("Error creating signature", se);
574         }
575     }
576 
577     /**
578      * Returns the digest algorithm for this {@code JarSigner}.
579      * <p>
580      * The return value is never null.
581      *
582      * @return the digest algorithm.
583      */
getDigestAlgorithm()584     public String getDigestAlgorithm() {
585         return digestalg[0];
586     }
587 
588     /**
589      * Returns the signature algorithm for this {@code JarSigner}.
590      * <p>
591      * The return value is never null.
592      *
593      * @return the signature algorithm.
594      */
getSignatureAlgorithm()595     public String getSignatureAlgorithm() {
596         return sigalg;
597     }
598 
599     /**
600      * Returns the URI of the Time Stamping Authority (TSA).
601      *
602      * @return the URI of the TSA.
603      */
getTsa()604     public URI getTsa() {
605         return tsaUrl;
606     }
607 
608     /**
609      * Returns the signer name of this {@code JarSigner}.
610      * <p>
611      * The return value is never null.
612      *
613      * @return the signer name.
614      */
getSignerName()615     public String getSignerName() {
616         return signerName;
617     }
618 
619     /**
620      * Returns the value of an additional implementation-specific property
621      * indicated by the specified key. If a property is not set but has a
622      * default value, the default value will be returned.
623      *
624      * @implNote See {@link JarSigner.Builder#setProperty} for a list of
625      * properties this implementation supports. All property names are
626      * case-insensitive.
627      *
628      * @param key the name of the property.
629      * @return the value for the property.
630      * @throws UnsupportedOperationException if the key is not supported
631      *      by this implementation.
632      */
getProperty(String key)633     public String getProperty(String key) {
634         Objects.requireNonNull(key);
635         switch (key.toLowerCase(Locale.US)) {
636             case "tsadigestalg":
637                 return tSADigestAlg;
638             case "tsapolicyid":
639                 return tSAPolicyID;
640             case "internalsf":
641                 return Boolean.toString(!externalSF);
642             case "sectionsonly":
643                 return Boolean.toString(!signManifest);
644             case "altsignerpath":
645                 return altSignerPath;
646             case "altsigner":
647                 return altSigner;
648             default:
649                 throw new UnsupportedOperationException(
650                         "Unsupported key " + key);
651         }
652     }
653 
sign0(ZipFile zipFile, OutputStream os)654     private void sign0(ZipFile zipFile, OutputStream os)
655             throws IOException, CertificateException, NoSuchAlgorithmException,
656             SignatureException, InvalidKeyException {
657         MessageDigest[] digests;
658         try {
659             digests = new MessageDigest[digestalg.length];
660             for (int i = 0; i < digestalg.length; i++) {
661                 if (digestProvider == null) {
662                     digests[i] = MessageDigest.getInstance(digestalg[i]);
663                 } else {
664                     digests[i] = MessageDigest.getInstance(
665                             digestalg[i], digestProvider);
666                 }
667             }
668         } catch (NoSuchAlgorithmException asae) {
669             // Should not happen. User provided alg were checked, and default
670             // alg should always be available.
671             throw new AssertionError(asae);
672         }
673 
674         PrintStream ps = new PrintStream(os);
675         ZipOutputStream zos = new ZipOutputStream(ps);
676 
677         Manifest manifest = new Manifest();
678         Map<String, Attributes> mfEntries = manifest.getEntries();
679 
680         // The Attributes of manifest before updating
681         Attributes oldAttr = null;
682 
683         boolean mfModified = false;
684         boolean mfCreated = false;
685         byte[] mfRawBytes = null;
686 
687         // Check if manifest exists
688         ZipEntry mfFile;
689         if ((mfFile = getManifestFile(zipFile)) != null) {
690             // Manifest exists. Read its raw bytes.
691             mfRawBytes = zipFile.getInputStream(mfFile).readAllBytes();
692             manifest.read(new ByteArrayInputStream(mfRawBytes));
693             oldAttr = (Attributes) (manifest.getMainAttributes().clone());
694         } else {
695             // Create new manifest
696             Attributes mattr = manifest.getMainAttributes();
697             mattr.putValue(Attributes.Name.MANIFEST_VERSION.toString(),
698                     "1.0");
699             String javaVendor = System.getProperty("java.vendor");
700             String jdkVersion = System.getProperty("java.version");
701             mattr.putValue("Created-By", jdkVersion + " (" + javaVendor
702                     + ")");
703             mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
704             mfCreated = true;
705         }
706 
707         /*
708          * For each entry in jar
709          * (except for signature-related META-INF entries),
710          * do the following:
711          *
712          * - if entry is not contained in manifest, add it to manifest;
713          * - if entry is contained in manifest, calculate its hash and
714          *   compare it with the one in the manifest; if they are
715          *   different, replace the hash in the manifest with the newly
716          *   generated one. (This may invalidate existing signatures!)
717          */
718         Vector<ZipEntry> mfFiles = new Vector<>();
719 
720         boolean wasSigned = false;
721 
722         for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
723              enum_.hasMoreElements(); ) {
724             ZipEntry ze = enum_.nextElement();
725 
726             if (ze.getName().startsWith(META_INF)) {
727                 // Store META-INF files in vector, so they can be written
728                 // out first
729                 mfFiles.addElement(ze);
730 
731                 if (SignatureFileVerifier.isBlockOrSF(
732                         ze.getName().toUpperCase(Locale.ENGLISH))) {
733                     wasSigned = true;
734                 }
735 
736                 if (SignatureFileVerifier.isSigningRelated(ze.getName())) {
737                     // ignore signature-related and manifest files
738                     continue;
739                 }
740             }
741 
742             if (manifest.getAttributes(ze.getName()) != null) {
743                 // jar entry is contained in manifest, check and
744                 // possibly update its digest attributes
745                 if (updateDigests(ze, zipFile, digests,
746                         manifest)) {
747                     mfModified = true;
748                 }
749             } else if (!ze.isDirectory()) {
750                 // Add entry to manifest
751                 Attributes attrs = getDigestAttributes(ze, zipFile, digests);
752                 mfEntries.put(ze.getName(), attrs);
753                 mfModified = true;
754             }
755         }
756 
757         // Recalculate the manifest raw bytes if necessary
758         if (mfModified) {
759             ByteArrayOutputStream baos = new ByteArrayOutputStream();
760             manifest.write(baos);
761             if (wasSigned) {
762                 byte[] newBytes = baos.toByteArray();
763                 if (mfRawBytes != null
764                         && oldAttr.equals(manifest.getMainAttributes())) {
765 
766                     /*
767                      * Note:
768                      *
769                      * The Attributes object is based on HashMap and can handle
770                      * continuation columns. Therefore, even if the contents are
771                      * not changed (in a Map view), the bytes that it write()
772                      * may be different from the original bytes that it read()
773                      * from. Since the signature on the main attributes is based
774                      * on raw bytes, we must retain the exact bytes.
775                      */
776 
777                     int newPos = findHeaderEnd(newBytes);
778                     int oldPos = findHeaderEnd(mfRawBytes);
779 
780                     if (newPos == oldPos) {
781                         System.arraycopy(mfRawBytes, 0, newBytes, 0, oldPos);
782                     } else {
783                         // cat oldHead newTail > newBytes
784                         byte[] lastBytes = new byte[oldPos +
785                                 newBytes.length - newPos];
786                         System.arraycopy(mfRawBytes, 0, lastBytes, 0, oldPos);
787                         System.arraycopy(newBytes, newPos, lastBytes, oldPos,
788                                 newBytes.length - newPos);
789                         newBytes = lastBytes;
790                     }
791                 }
792                 mfRawBytes = newBytes;
793             } else {
794                 mfRawBytes = baos.toByteArray();
795             }
796         }
797 
798         // Write out the manifest
799         if (mfModified) {
800             // manifest file has new length
801             mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
802         }
803         if (handler != null) {
804             if (mfCreated) {
805                 handler.accept("adding", mfFile.getName());
806             } else if (mfModified) {
807                 handler.accept("updating", mfFile.getName());
808             }
809         }
810 
811         zos.putNextEntry(mfFile);
812         zos.write(mfRawBytes);
813 
814         // Calculate SignatureFile (".SF") and SignatureBlockFile
815         ManifestDigester manDig = new ManifestDigester(mfRawBytes);
816         SignatureFile sf = new SignatureFile(digests, manifest, manDig,
817                 signerName, signManifest);
818 
819         byte[] block;
820 
821         Signature signer;
822         if (sigProvider == null ) {
823             signer = Signature.getInstance(sigalg);
824         } else {
825             signer = Signature.getInstance(sigalg, sigProvider);
826         }
827         signer.initSign(privateKey);
828 
829         ByteArrayOutputStream baos = new ByteArrayOutputStream();
830         sf.write(baos);
831 
832         byte[] content = baos.toByteArray();
833 
834         signer.update(content);
835         byte[] signature = signer.sign();
836 
837         @SuppressWarnings("deprecation")
838         ContentSigner signingMechanism = null;
839         if (altSigner != null) {
840             signingMechanism = loadSigningMechanism(altSigner,
841                     altSignerPath);
842         }
843 
844         @SuppressWarnings("deprecation")
845         ContentSignerParameters params =
846                 new JarSignerParameters(null, tsaUrl, tSAPolicyID,
847                         tSADigestAlg, signature,
848                         signer.getAlgorithm(), certChain, content, zipFile);
849         block = sf.generateBlock(params, externalSF, signingMechanism);
850 
851         String sfFilename = sf.getMetaName();
852         String bkFilename = sf.getBlockName(privateKey);
853 
854         ZipEntry sfFile = new ZipEntry(sfFilename);
855         ZipEntry bkFile = new ZipEntry(bkFilename);
856 
857         long time = System.currentTimeMillis();
858         sfFile.setTime(time);
859         bkFile.setTime(time);
860 
861         // signature file
862         zos.putNextEntry(sfFile);
863         sf.write(zos);
864 
865         if (handler != null) {
866             if (zipFile.getEntry(sfFilename) != null) {
867                 handler.accept("updating", sfFilename);
868             } else {
869                 handler.accept("adding", sfFilename);
870             }
871         }
872 
873         // signature block file
874         zos.putNextEntry(bkFile);
875         zos.write(block);
876 
877         if (handler != null) {
878             if (zipFile.getEntry(bkFilename) != null) {
879                 handler.accept("updating", bkFilename);
880             } else {
881                 handler.accept("adding", bkFilename);
882             }
883         }
884 
885         // Write out all other META-INF files that we stored in the
886         // vector
887         for (int i = 0; i < mfFiles.size(); i++) {
888             ZipEntry ze = mfFiles.elementAt(i);
889             if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
890                     && !ze.getName().equalsIgnoreCase(sfFilename)
891                     && !ze.getName().equalsIgnoreCase(bkFilename)) {
892                 if (handler != null) {
893                     if (manifest.getAttributes(ze.getName()) != null) {
894                         handler.accept("signing", ze.getName());
895                     } else if (!ze.isDirectory()) {
896                         handler.accept("adding", ze.getName());
897                     }
898                 }
899                 writeEntry(zipFile, zos, ze);
900             }
901         }
902 
903         // Write out all other files
904         for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
905              enum_.hasMoreElements(); ) {
906             ZipEntry ze = enum_.nextElement();
907 
908             if (!ze.getName().startsWith(META_INF)) {
909                 if (handler != null) {
910                     if (manifest.getAttributes(ze.getName()) != null) {
911                         handler.accept("signing", ze.getName());
912                     } else {
913                         handler.accept("adding", ze.getName());
914                     }
915                 }
916                 writeEntry(zipFile, zos, ze);
917             }
918         }
919         zipFile.close();
920         zos.close();
921     }
922 
writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)923     private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
924             throws IOException {
925         ZipEntry ze2 = new ZipEntry(ze.getName());
926         ze2.setMethod(ze.getMethod());
927         ze2.setTime(ze.getTime());
928         ze2.setComment(ze.getComment());
929         ze2.setExtra(ze.getExtra());
930         if (ze.getMethod() == ZipEntry.STORED) {
931             ze2.setSize(ze.getSize());
932             ze2.setCrc(ze.getCrc());
933         }
934         os.putNextEntry(ze2);
935         writeBytes(zf, ze, os);
936     }
937 
writeBytes(ZipFile zf, ZipEntry ze, ZipOutputStream os)938     private void writeBytes
939             (ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
940         try (InputStream is = zf.getInputStream(ze)) {
941             is.transferTo(os);
942         }
943     }
944 
updateDigests(ZipEntry ze, ZipFile zf, MessageDigest[] digests, Manifest mf)945     private boolean updateDigests(ZipEntry ze, ZipFile zf,
946                                   MessageDigest[] digests,
947                                   Manifest mf) throws IOException {
948         boolean update = false;
949 
950         Attributes attrs = mf.getAttributes(ze.getName());
951         String[] base64Digests = getDigests(ze, zf, digests);
952 
953         for (int i = 0; i < digests.length; i++) {
954             // The entry name to be written into attrs
955             String name = null;
956             try {
957                 // Find if the digest already exists. An algorithm could have
958                 // different names. For example, last time it was SHA, and this
959                 // time it's SHA-1.
960                 AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
961                 for (Object key : attrs.keySet()) {
962                     if (key instanceof Attributes.Name) {
963                         String n = key.toString();
964                         if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
965                             String tmp = n.substring(0, n.length() - 7);
966                             if (AlgorithmId.get(tmp).equals(aid)) {
967                                 name = n;
968                                 break;
969                             }
970                         }
971                     }
972                 }
973             } catch (NoSuchAlgorithmException nsae) {
974                 // Ignored. Writing new digest entry.
975             }
976 
977             if (name == null) {
978                 name = digests[i].getAlgorithm() + "-Digest";
979                 attrs.putValue(name, base64Digests[i]);
980                 update = true;
981             } else {
982                 // compare digests, and replace the one in the manifest
983                 // if they are different
984                 String mfDigest = attrs.getValue(name);
985                 if (!mfDigest.equalsIgnoreCase(base64Digests[i])) {
986                     attrs.putValue(name, base64Digests[i]);
987                     update = true;
988                 }
989             }
990         }
991         return update;
992     }
993 
getDigestAttributes( ZipEntry ze, ZipFile zf, MessageDigest[] digests)994     private Attributes getDigestAttributes(
995             ZipEntry ze, ZipFile zf, MessageDigest[] digests)
996             throws IOException {
997 
998         String[] base64Digests = getDigests(ze, zf, digests);
999         Attributes attrs = new Attributes();
1000 
1001         for (int i = 0; i < digests.length; i++) {
1002             attrs.putValue(digests[i].getAlgorithm() + "-Digest",
1003                     base64Digests[i]);
1004         }
1005         return attrs;
1006     }
1007 
1008     /*
1009      * Returns manifest entry from given jar file, or null if given jar file
1010      * does not have a manifest entry.
1011      */
getManifestFile(ZipFile zf)1012     private ZipEntry getManifestFile(ZipFile zf) {
1013         ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
1014         if (ze == null) {
1015             // Check all entries for matching name
1016             Enumeration<? extends ZipEntry> enum_ = zf.entries();
1017             while (enum_.hasMoreElements() && ze == null) {
1018                 ze = enum_.nextElement();
1019                 if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
1020                         (ze.getName())) {
1021                     ze = null;
1022                 }
1023             }
1024         }
1025         return ze;
1026     }
1027 
getDigests( ZipEntry ze, ZipFile zf, MessageDigest[] digests)1028     private String[] getDigests(
1029             ZipEntry ze, ZipFile zf, MessageDigest[] digests)
1030             throws IOException {
1031 
1032         int n, i;
1033         try (InputStream is = zf.getInputStream(ze)) {
1034             long left = ze.getSize();
1035             byte[] buffer = new byte[8192];
1036             while ((left > 0)
1037                     && (n = is.read(buffer, 0, buffer.length)) != -1) {
1038                 for (i = 0; i < digests.length; i++) {
1039                     digests[i].update(buffer, 0, n);
1040                 }
1041                 left -= n;
1042             }
1043         }
1044 
1045         // complete the digests
1046         String[] base64Digests = new String[digests.length];
1047         for (i = 0; i < digests.length; i++) {
1048             base64Digests[i] = Base64.getEncoder()
1049                     .encodeToString(digests[i].digest());
1050         }
1051         return base64Digests;
1052     }
1053 
1054     @SuppressWarnings("fallthrough")
findHeaderEnd(byte[] bs)1055     private int findHeaderEnd(byte[] bs) {
1056         // Initial state true to deal with empty header
1057         boolean newline = true;     // just met a newline
1058         int len = bs.length;
1059         for (int i = 0; i < len; i++) {
1060             switch (bs[i]) {
1061                 case '\r':
1062                     if (i < len - 1 && bs[i + 1] == '\n') i++;
1063                     // fallthrough
1064                 case '\n':
1065                     if (newline) return i + 1;    //+1 to get length
1066                     newline = true;
1067                     break;
1068                 default:
1069                     newline = false;
1070             }
1071         }
1072         // If header end is not found, it means the MANIFEST.MF has only
1073         // the main attributes section and it does not end with 2 newlines.
1074         // Returns the whole length so that it can be completely replaced.
1075         return len;
1076     }
1077 
1078     /*
1079      * Try to load the specified signing mechanism.
1080      * The URL class loader is used.
1081      */
1082     @SuppressWarnings("deprecation")
loadSigningMechanism(String signerClassName, String signerClassPath)1083     private ContentSigner loadSigningMechanism(String signerClassName,
1084                                                String signerClassPath) {
1085 
1086         // construct class loader
1087         String cpString;   // make sure env.class.path defaults to dot
1088 
1089         // do prepends to get correct ordering
1090         cpString = PathList.appendPath(
1091                 System.getProperty("env.class.path"), null);
1092         cpString = PathList.appendPath(
1093                 System.getProperty("java.class.path"), cpString);
1094         cpString = PathList.appendPath(signerClassPath, cpString);
1095         URL[] urls = PathList.pathToURLs(cpString);
1096         ClassLoader appClassLoader = new URLClassLoader(urls);
1097 
1098         try {
1099             // attempt to find signer
1100             Class<?> signerClass = appClassLoader.loadClass(signerClassName);
1101             Object signer = signerClass.newInstance();
1102             return (ContentSigner) signer;
1103         } catch (ClassNotFoundException|InstantiationException|
1104                 IllegalAccessException|ClassCastException e) {
1105             throw new IllegalArgumentException(
1106                     "Invalid altSigner or altSignerPath", e);
1107         }
1108     }
1109 
1110     static class SignatureFile {
1111 
1112         /**
1113          * SignatureFile
1114          */
1115         Manifest sf;
1116 
1117         /**
1118          * .SF base name
1119          */
1120         String baseName;
1121 
SignatureFile(MessageDigest digests[], Manifest mf, ManifestDigester md, String baseName, boolean signManifest)1122         public SignatureFile(MessageDigest digests[],
1123                              Manifest mf,
1124                              ManifestDigester md,
1125                              String baseName,
1126                              boolean signManifest) {
1127 
1128             this.baseName = baseName;
1129 
1130             String version = System.getProperty("java.version");
1131             String javaVendor = System.getProperty("java.vendor");
1132 
1133             sf = new Manifest();
1134             Attributes mattr = sf.getMainAttributes();
1135 
1136             mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
1137             mattr.putValue("Created-By", version + " (" + javaVendor + ")");
1138 
1139             if (signManifest) {
1140                 for (MessageDigest digest: digests) {
1141                     mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest",
1142                             Base64.getEncoder().encodeToString(
1143                                     md.manifestDigest(digest)));
1144                 }
1145             }
1146 
1147             // create digest of the manifest main attributes
1148             ManifestDigester.Entry mde =
1149                     md.get(ManifestDigester.MF_MAIN_ATTRS, false);
1150             if (mde != null) {
1151                 for (MessageDigest digest: digests) {
1152                     mattr.putValue(digest.getAlgorithm() +
1153                                     "-Digest-" + ManifestDigester.MF_MAIN_ATTRS,
1154                             Base64.getEncoder().encodeToString(
1155                                     mde.digest(digest)));
1156                 }
1157             } else {
1158                 throw new IllegalStateException
1159                         ("ManifestDigester failed to create " +
1160                                 "Manifest-Main-Attribute entry");
1161             }
1162 
1163             // go through the manifest entries and create the digests
1164             Map<String, Attributes> entries = sf.getEntries();
1165             for (String name: mf.getEntries().keySet()) {
1166                 mde = md.get(name, false);
1167                 if (mde != null) {
1168                     Attributes attr = new Attributes();
1169                     for (MessageDigest digest: digests) {
1170                         attr.putValue(digest.getAlgorithm() + "-Digest",
1171                                 Base64.getEncoder().encodeToString(
1172                                         mde.digest(digest)));
1173                     }
1174                     entries.put(name, attr);
1175                 }
1176             }
1177         }
1178 
1179         // Write .SF file
write(OutputStream out)1180         public void write(OutputStream out) throws IOException {
1181             sf.write(out);
1182         }
1183 
1184         // get .SF file name
getMetaName()1185         public String getMetaName() {
1186             return "META-INF/" + baseName + ".SF";
1187         }
1188 
1189         // get .DSA (or .DSA, .EC) file name
getBlockName(PrivateKey privateKey)1190         public String getBlockName(PrivateKey privateKey) {
1191             String keyAlgorithm = privateKey.getAlgorithm();
1192             return "META-INF/" + baseName + "." + keyAlgorithm;
1193         }
1194 
1195         // Generates the PKCS#7 content of block file
1196         @SuppressWarnings("deprecation")
generateBlock(ContentSignerParameters params, boolean externalSF, ContentSigner signingMechanism)1197         public byte[] generateBlock(ContentSignerParameters params,
1198                                     boolean externalSF,
1199                                     ContentSigner signingMechanism)
1200                 throws NoSuchAlgorithmException,
1201                        IOException, CertificateException {
1202 
1203             if (signingMechanism == null) {
1204                 signingMechanism = new TimestampedSigner();
1205             }
1206             return signingMechanism.generateSignedData(
1207                     params,
1208                     externalSF,
1209                     params.getTimestampingAuthority() != null
1210                         || params.getTimestampingAuthorityCertificate() != null);
1211         }
1212     }
1213 
1214     @SuppressWarnings("deprecation")
1215     class JarSignerParameters implements ContentSignerParameters {
1216 
1217         private String[] args;
1218         private URI tsa;
1219         private byte[] signature;
1220         private String signatureAlgorithm;
1221         private X509Certificate[] signerCertificateChain;
1222         private byte[] content;
1223         private ZipFile source;
1224         private String tSAPolicyID;
1225         private String tSADigestAlg;
1226 
JarSignerParameters(String[] args, URI tsa, String tSAPolicyID, String tSADigestAlg, byte[] signature, String signatureAlgorithm, X509Certificate[] signerCertificateChain, byte[] content, ZipFile source)1227         JarSignerParameters(String[] args, URI tsa,
1228                             String tSAPolicyID, String tSADigestAlg,
1229                             byte[] signature, String signatureAlgorithm,
1230                             X509Certificate[] signerCertificateChain,
1231                             byte[] content, ZipFile source) {
1232 
1233             Objects.requireNonNull(signature);
1234             Objects.requireNonNull(signatureAlgorithm);
1235             Objects.requireNonNull(signerCertificateChain);
1236 
1237             this.args = args;
1238             this.tsa = tsa;
1239             this.tSAPolicyID = tSAPolicyID;
1240             this.tSADigestAlg = tSADigestAlg;
1241             this.signature = signature;
1242             this.signatureAlgorithm = signatureAlgorithm;
1243             this.signerCertificateChain = signerCertificateChain;
1244             this.content = content;
1245             this.source = source;
1246         }
1247 
getCommandLine()1248         public String[] getCommandLine() {
1249             return args;
1250         }
1251 
getTimestampingAuthority()1252         public URI getTimestampingAuthority() {
1253             return tsa;
1254         }
1255 
getTimestampingAuthorityCertificate()1256         public X509Certificate getTimestampingAuthorityCertificate() {
1257             // We don't use this param. Always provide tsaURI.
1258             return null;
1259         }
1260 
getTSAPolicyID()1261         public String getTSAPolicyID() {
1262             return tSAPolicyID;
1263         }
1264 
getTSADigestAlg()1265         public String getTSADigestAlg() {
1266             return tSADigestAlg;
1267         }
1268 
getSignature()1269         public byte[] getSignature() {
1270             return signature;
1271         }
1272 
getSignatureAlgorithm()1273         public String getSignatureAlgorithm() {
1274             return signatureAlgorithm;
1275         }
1276 
getSignerCertificateChain()1277         public X509Certificate[] getSignerCertificateChain() {
1278             return signerCertificateChain;
1279         }
1280 
getContent()1281         public byte[] getContent() {
1282             return content;
1283         }
1284 
getSource()1285         public ZipFile getSource() {
1286             return source;
1287         }
1288     }
1289 }
1290