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