Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/jdk.jartool/share/classes/jdk/security/jarsigner/JarSigner.java
66646 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
ManifestDigester.Entry oldEntry = oldMd.getMainAttsEntry();
797
798
// main attributes
799
if (oldEntry != null
800
&& manifest.getMainAttributes().equals(
801
oldManifest.getMainAttributes())
802
&& (manifest.getEntries().isEmpty() ||
803
oldEntry.isProperlyDelimited())) {
804
oldEntry.reproduceRaw(baos);
805
} else {
806
if (newMd.getMainAttsEntry() == null) {
807
throw new SignatureException("Error getting new main attribute entry");
808
}
809
newMd.getMainAttsEntry().reproduceRaw(baos);
810
}
811
812
// individual sections
813
for (Map.Entry<String,Attributes> entry :
814
manifest.getEntries().entrySet()) {
815
String sectionName = entry.getKey();
816
Attributes entryAtts = entry.getValue();
817
if (entryAtts.equals(oldManifest.getAttributes(sectionName))
818
&& oldMd.get(sectionName).isProperlyDelimited()) {
819
oldMd.get(sectionName).reproduceRaw(baos);
820
} else {
821
newMd.get(sectionName).reproduceRaw(baos);
822
}
823
}
824
825
mfRawBytes = baos.toByteArray();
826
}
827
}
828
829
// Write out the manifest
830
if (mfModified) {
831
// manifest file has new length
832
mfFile = new ZipEntry(JarFile.MANIFEST_NAME);
833
}
834
if (handler != null) {
835
if (mfCreated || !mfModified) {
836
handler.accept("adding", mfFile.getName());
837
} else {
838
handler.accept("updating", mfFile.getName());
839
}
840
}
841
zos.putNextEntry(mfFile);
842
zos.write(mfRawBytes);
843
844
// Calculate SignatureFile (".SF") and SignatureBlockFile
845
ManifestDigester manDig = new ManifestDigester(mfRawBytes);
846
SignatureFile sf = new SignatureFile(digests, manifest, manDig,
847
signerName, sectionsonly);
848
849
byte[] block;
850
851
baos.reset();
852
sf.write(baos);
853
byte[] content = baos.toByteArray();
854
855
if (altSigner == null) {
856
Function<byte[], PKCS9Attributes> timestamper = null;
857
if (tsaUrl != null) {
858
timestamper = s -> {
859
try {
860
// Timestamp the signature
861
HttpTimestamper tsa = new HttpTimestamper(tsaUrl);
862
byte[] tsToken = PKCS7.generateTimestampToken(
863
tsa, tSAPolicyID, tSADigestAlg, s);
864
865
return new PKCS9Attributes(new PKCS9Attribute[]{
866
new PKCS9Attribute(
867
PKCS9Attribute.SIGNATURE_TIMESTAMP_TOKEN_OID,
868
tsToken)});
869
} catch (IOException | CertificateException e) {
870
throw new RuntimeException(e);
871
}
872
};
873
}
874
// We now create authAttrs in block data, so "direct == false".
875
block = PKCS7.generateNewSignedData(sigalg, sigProvider, privateKey, certChain,
876
content, internalsf, false, timestamper);
877
} else {
878
Signature signer = SignatureUtil.fromKey(sigalg, privateKey, sigProvider);
879
signer.update(content);
880
byte[] signature = signer.sign();
881
882
@SuppressWarnings("removal")
883
ContentSignerParameters params =
884
new JarSignerParameters(null, tsaUrl, tSAPolicyID,
885
tSADigestAlg, signature,
886
signer.getAlgorithm(), certChain, content, zipFile);
887
@SuppressWarnings("removal")
888
ContentSigner signingMechanism = loadSigningMechanism(altSigner, altSignerPath);
889
block = signingMechanism.generateSignedData(
890
params,
891
!internalsf,
892
params.getTimestampingAuthority() != null
893
|| params.getTimestampingAuthorityCertificate() != null);
894
}
895
896
String sfFilename = sf.getMetaName();
897
String bkFilename = sf.getBlockName(privateKey);
898
899
ZipEntry sfFile = new ZipEntry(sfFilename);
900
ZipEntry bkFile = new ZipEntry(bkFilename);
901
902
long time = System.currentTimeMillis();
903
sfFile.setTime(time);
904
bkFile.setTime(time);
905
906
// signature file
907
zos.putNextEntry(sfFile);
908
sf.write(zos);
909
910
if (handler != null) {
911
if (zipFile.getEntry(sfFilename) != null) {
912
handler.accept("updating", sfFilename);
913
} else {
914
handler.accept("adding", sfFilename);
915
}
916
}
917
918
// signature block file
919
zos.putNextEntry(bkFile);
920
zos.write(block);
921
922
if (handler != null) {
923
if (zipFile.getEntry(bkFilename) != null) {
924
handler.accept("updating", bkFilename);
925
} else {
926
handler.accept("adding", bkFilename);
927
}
928
}
929
930
// Write out all other META-INF files that we stored in the
931
// vector
932
for (int i = 0; i < mfFiles.size(); i++) {
933
ZipEntry ze = mfFiles.elementAt(i);
934
if (!ze.getName().equalsIgnoreCase(JarFile.MANIFEST_NAME)
935
&& !ze.getName().equalsIgnoreCase(sfFilename)
936
&& !ze.getName().equalsIgnoreCase(bkFilename)) {
937
if (ze.getName().startsWith(SignatureFile
938
.getBaseSignatureFilesName(signerName))
939
&& SignatureFileVerifier.isBlockOrSF(ze.getName())) {
940
if (handler != null) {
941
handler.accept("updating", ze.getName());
942
}
943
continue;
944
}
945
if (handler != null) {
946
if (manifest.getAttributes(ze.getName()) != null) {
947
handler.accept("signing", ze.getName());
948
} else if (!ze.isDirectory()) {
949
handler.accept("adding", ze.getName());
950
}
951
}
952
writeEntry(zipFile, zos, ze);
953
}
954
}
955
956
// Write out all other files
957
for (Enumeration<? extends ZipEntry> enum_ = zipFile.entries();
958
enum_.hasMoreElements(); ) {
959
ZipEntry ze = enum_.nextElement();
960
961
if (!ze.getName().startsWith(META_INF)) {
962
if (handler != null) {
963
if (manifest.getAttributes(ze.getName()) != null) {
964
handler.accept("signing", ze.getName());
965
} else {
966
handler.accept("adding", ze.getName());
967
}
968
}
969
writeEntry(zipFile, zos, ze);
970
}
971
}
972
zipFile.close();
973
zos.close();
974
}
975
976
private void writeEntry(ZipFile zf, ZipOutputStream os, ZipEntry ze)
977
throws IOException {
978
ZipEntry ze2 = new ZipEntry(ze.getName());
979
ze2.setMethod(ze.getMethod());
980
ze2.setTime(ze.getTime());
981
ze2.setComment(ze.getComment());
982
ze2.setExtra(ze.getExtra());
983
int extraAttrs = JUZFA.getExtraAttributes(ze);
984
if (!extraAttrsDetected && extraAttrs != -1) {
985
extraAttrsDetected = true;
986
Event.report(Event.ReporterCategory.ZIPFILEATTRS, "detected");
987
}
988
JUZFA.setExtraAttributes(ze2, extraAttrs);
989
if (ze.getMethod() == ZipEntry.STORED) {
990
ze2.setSize(ze.getSize());
991
ze2.setCrc(ze.getCrc());
992
}
993
os.putNextEntry(ze2);
994
writeBytes(zf, ze, os);
995
}
996
997
private void writeBytes
998
(ZipFile zf, ZipEntry ze, ZipOutputStream os) throws IOException {
999
try (InputStream is = zf.getInputStream(ze)) {
1000
is.transferTo(os);
1001
}
1002
}
1003
1004
private void updateDigests(ZipEntry ze, ZipFile zf,
1005
MessageDigest[] digests,
1006
Manifest mf) throws IOException {
1007
Attributes attrs = mf.getAttributes(ze.getName());
1008
String[] base64Digests = getDigests(ze, zf, digests);
1009
1010
for (int i = 0; i < digests.length; i++) {
1011
// The entry name to be written into attrs
1012
String name = null;
1013
try {
1014
// Find if the digest already exists. An algorithm could have
1015
// different names. For example, last time it was SHA, and this
1016
// time it's SHA-1.
1017
AlgorithmId aid = AlgorithmId.get(digests[i].getAlgorithm());
1018
for (Object key : attrs.keySet()) {
1019
if (key instanceof Attributes.Name) {
1020
String n = key.toString();
1021
if (n.toUpperCase(Locale.ENGLISH).endsWith("-DIGEST")) {
1022
String tmp = n.substring(0, n.length() - 7);
1023
if (AlgorithmId.get(tmp).equals(aid)) {
1024
name = n;
1025
break;
1026
}
1027
}
1028
}
1029
}
1030
} catch (NoSuchAlgorithmException nsae) {
1031
// Ignored. Writing new digest entry.
1032
}
1033
1034
if (name == null) {
1035
name = digests[i].getAlgorithm() + "-Digest";
1036
}
1037
attrs.putValue(name, base64Digests[i]);
1038
}
1039
}
1040
1041
private Attributes getDigestAttributes(
1042
ZipEntry ze, ZipFile zf, MessageDigest[] digests)
1043
throws IOException {
1044
1045
String[] base64Digests = getDigests(ze, zf, digests);
1046
Attributes attrs = new Attributes();
1047
1048
for (int i = 0; i < digests.length; i++) {
1049
attrs.putValue(digests[i].getAlgorithm() + "-Digest",
1050
base64Digests[i]);
1051
}
1052
return attrs;
1053
}
1054
1055
/*
1056
* Returns manifest entry from given jar file, or null if given jar file
1057
* does not have a manifest entry.
1058
*/
1059
private ZipEntry getManifestFile(ZipFile zf) {
1060
ZipEntry ze = zf.getEntry(JarFile.MANIFEST_NAME);
1061
if (ze == null) {
1062
// Check all entries for matching name
1063
Enumeration<? extends ZipEntry> enum_ = zf.entries();
1064
while (enum_.hasMoreElements() && ze == null) {
1065
ze = enum_.nextElement();
1066
if (!JarFile.MANIFEST_NAME.equalsIgnoreCase
1067
(ze.getName())) {
1068
ze = null;
1069
}
1070
}
1071
}
1072
return ze;
1073
}
1074
1075
private String[] getDigests(
1076
ZipEntry ze, ZipFile zf, MessageDigest[] digests)
1077
throws IOException {
1078
1079
int n, i;
1080
try (InputStream is = zf.getInputStream(ze)) {
1081
long left = ze.getSize();
1082
byte[] buffer = new byte[8192];
1083
while ((left > 0)
1084
&& (n = is.read(buffer, 0, buffer.length)) != -1) {
1085
for (i = 0; i < digests.length; i++) {
1086
digests[i].update(buffer, 0, n);
1087
}
1088
left -= n;
1089
}
1090
}
1091
1092
// complete the digests
1093
String[] base64Digests = new String[digests.length];
1094
for (i = 0; i < digests.length; i++) {
1095
base64Digests[i] = Base64.getEncoder()
1096
.encodeToString(digests[i].digest());
1097
}
1098
return base64Digests;
1099
}
1100
1101
/*
1102
* Try to load the specified signing mechanism.
1103
* The URL class loader is used.
1104
*/
1105
@SuppressWarnings("removal")
1106
private ContentSigner loadSigningMechanism(String signerClassName,
1107
String signerClassPath) {
1108
1109
// If there is no signerClassPath provided, search from here
1110
if (signerClassPath == null) {
1111
signerClassPath = ".";
1112
}
1113
1114
// construct class loader
1115
String cpString; // make sure env.class.path defaults to dot
1116
1117
// do prepends to get correct ordering
1118
cpString = PathList.appendPath(
1119
System.getProperty("env.class.path"), null);
1120
cpString = PathList.appendPath(
1121
System.getProperty("java.class.path"), cpString);
1122
cpString = PathList.appendPath(signerClassPath, cpString);
1123
URL[] urls = PathList.pathToURLs(cpString);
1124
ClassLoader appClassLoader = new URLClassLoader(urls);
1125
1126
try {
1127
// attempt to find signer
1128
Class<?> signerClass = appClassLoader.loadClass(signerClassName);
1129
Object signer = signerClass.getDeclaredConstructor().newInstance();
1130
return (ContentSigner) signer;
1131
} catch (ClassNotFoundException|InstantiationException|
1132
IllegalAccessException|ClassCastException|
1133
NoSuchMethodException| InvocationTargetException e) {
1134
throw new IllegalArgumentException(
1135
"Invalid altSigner or altSignerPath", e);
1136
}
1137
}
1138
1139
static class SignatureFile {
1140
1141
/**
1142
* SignatureFile
1143
*/
1144
Manifest sf;
1145
1146
/**
1147
* .SF base name
1148
*/
1149
String baseName;
1150
1151
public SignatureFile(MessageDigest digests[],
1152
Manifest mf,
1153
ManifestDigester md,
1154
String baseName,
1155
boolean sectionsonly) {
1156
1157
this.baseName = baseName;
1158
1159
String version = System.getProperty("java.version");
1160
String javaVendor = System.getProperty("java.vendor");
1161
1162
sf = new Manifest();
1163
Attributes mattr = sf.getMainAttributes();
1164
1165
mattr.putValue(Attributes.Name.SIGNATURE_VERSION.toString(), "1.0");
1166
mattr.putValue("Created-By", version + " (" + javaVendor + ")");
1167
1168
if (!sectionsonly) {
1169
for (MessageDigest digest: digests) {
1170
mattr.putValue(digest.getAlgorithm() + "-Digest-Manifest",
1171
Base64.getEncoder().encodeToString(
1172
md.manifestDigest(digest)));
1173
}
1174
}
1175
1176
// create digest of the manifest main attributes
1177
ManifestDigester.Entry mde = md.getMainAttsEntry(false);
1178
if (mde != null) {
1179
for (MessageDigest digest : digests) {
1180
mattr.putValue(digest.getAlgorithm() + "-Digest-" +
1181
ManifestDigester.MF_MAIN_ATTRS,
1182
Base64.getEncoder().encodeToString(mde.digest(digest)));
1183
}
1184
} else {
1185
throw new IllegalStateException
1186
("ManifestDigester failed to create " +
1187
"Manifest-Main-Attribute entry");
1188
}
1189
1190
// go through the manifest entries and create the digests
1191
Map<String, Attributes> entries = sf.getEntries();
1192
for (String name: mf.getEntries().keySet()) {
1193
mde = md.get(name, false);
1194
if (mde != null) {
1195
Attributes attr = new Attributes();
1196
for (MessageDigest digest: digests) {
1197
attr.putValue(digest.getAlgorithm() + "-Digest",
1198
Base64.getEncoder().encodeToString(
1199
mde.digest(digest)));
1200
}
1201
entries.put(name, attr);
1202
}
1203
}
1204
}
1205
1206
// Write .SF file
1207
public void write(OutputStream out) throws IOException {
1208
sf.write(out);
1209
}
1210
1211
private static String getBaseSignatureFilesName(String baseName) {
1212
return "META-INF/" + baseName + ".";
1213
}
1214
1215
// get .SF file name
1216
public String getMetaName() {
1217
return getBaseSignatureFilesName(baseName) + "SF";
1218
}
1219
1220
// get .DSA (or .DSA, .EC) file name
1221
public String getBlockName(PrivateKey privateKey) {
1222
String type = SignatureFileVerifier.getBlockExtension(privateKey);
1223
return getBaseSignatureFilesName(baseName) + type;
1224
}
1225
}
1226
1227
@SuppressWarnings("removal")
1228
@Deprecated(since="16", forRemoval=true)
1229
class JarSignerParameters implements ContentSignerParameters {
1230
1231
private String[] args;
1232
private URI tsa;
1233
private byte[] signature;
1234
private String signatureAlgorithm;
1235
private X509Certificate[] signerCertificateChain;
1236
private byte[] content;
1237
private ZipFile source;
1238
private String tSAPolicyID;
1239
private String tSADigestAlg;
1240
1241
JarSignerParameters(String[] args, URI tsa,
1242
String tSAPolicyID, String tSADigestAlg,
1243
byte[] signature, String signatureAlgorithm,
1244
X509Certificate[] signerCertificateChain,
1245
byte[] content, ZipFile source) {
1246
1247
Objects.requireNonNull(signature);
1248
Objects.requireNonNull(signatureAlgorithm);
1249
Objects.requireNonNull(signerCertificateChain);
1250
1251
this.args = args;
1252
this.tsa = tsa;
1253
this.tSAPolicyID = tSAPolicyID;
1254
this.tSADigestAlg = tSADigestAlg;
1255
this.signature = signature;
1256
this.signatureAlgorithm = signatureAlgorithm;
1257
this.signerCertificateChain = signerCertificateChain;
1258
this.content = content;
1259
this.source = source;
1260
}
1261
1262
public String[] getCommandLine() {
1263
return args;
1264
}
1265
1266
public URI getTimestampingAuthority() {
1267
return tsa;
1268
}
1269
1270
public X509Certificate getTimestampingAuthorityCertificate() {
1271
// We don't use this param. Always provide tsaURI.
1272
return null;
1273
}
1274
1275
public String getTSAPolicyID() {
1276
return tSAPolicyID;
1277
}
1278
1279
public String getTSADigestAlg() {
1280
return tSADigestAlg;
1281
}
1282
1283
public byte[] getSignature() {
1284
return signature;
1285
}
1286
1287
public String getSignatureAlgorithm() {
1288
return signatureAlgorithm;
1289
}
1290
1291
public X509Certificate[] getSignerCertificateChain() {
1292
return signerCertificateChain;
1293
}
1294
1295
public byte[] getContent() {
1296
return content;
1297
}
1298
1299
public ZipFile getSource() {
1300
return source;
1301
}
1302
}
1303
}
1304
1305