提交 a6421b54 编写于 作者: W weijun

8180289: jarsigner treats timestamped signed jar invalid after the signer cert expires

Reviewed-by: mullan
上级 2f04bb1e
......@@ -724,7 +724,8 @@ public class SignatureFileVerifier {
if (signers == null) {
signers = new ArrayList<>();
}
// Append the new code signer
// Append the new code signer. If timestamp is invalid, this
// jar will be treated as unsigned.
signers.add(new CodeSigner(certChain, info.getTimestamp()));
if (debug != null) {
......
/*
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
......@@ -162,11 +162,20 @@ public class Main {
private boolean noTimestamp = false;
private Date expireDate = new Date(0L); // used in noTimestamp warning
// Severe warnings
// Severe warnings.
// jarsigner used to check signer cert chain validity and key usages
// itself and set various warnings. Later CertPath validation is
// added but chainNotValidated is only flagged when no other existing
// warnings are set. TSA cert chain check is added separately and
// only tsaChainNotValidated is set, i.e. has no affect on hasExpiredCert,
// notYetValidCert, or any badXyzUsage.
private int weakAlg = 0; // 1. digestalg, 2. sigalg, 4. tsadigestalg
private boolean hasExpiredCert = false;
private boolean notYetValidCert = false;
private boolean chainNotValidated = false;
private boolean tsaChainNotValidated = false;
private boolean notSignedByAlias = false;
private boolean aliasNotInStore = false;
private boolean hasUnsignedEntry = false;
......@@ -176,6 +185,7 @@ public class Main {
private boolean signerSelfSigned = false;
private Throwable chainNotValidatedReason = null;
private Throwable tsaChainNotValidatedReason = null;
private boolean seeWeak = false;
......@@ -266,7 +276,8 @@ public class Main {
if (strict) {
int exitCode = 0;
if (weakAlg != 0 || chainNotValidated || hasExpiredCert || notYetValidCert || signerSelfSigned) {
if (weakAlg != 0 || chainNotValidated
|| hasExpiredCert || notYetValidCert || signerSelfSigned) {
exitCode |= 4;
}
if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType) {
......@@ -278,6 +289,9 @@ public class Main {
if (notSignedByAlias || aliasNotInStore) {
exitCode |= 32;
}
if (tsaChainNotValidated) {
exitCode |= 64;
}
if (exitCode != 0) {
System.exit(exitCode);
}
......@@ -864,6 +878,9 @@ public class Main {
signerSelfSigned = false;
}
// If there is a time stamp block inside the PKCS7 block file
boolean hasTimestampBlock = false;
// Even if the verbose option is not specified, all out strings
// must be generated so seeWeak can be updated.
if (!digestMap.isEmpty()
......@@ -892,6 +909,7 @@ public class Main {
PublicKey key = signer.getPublicKey();
PKCS7 tsToken = si.getTsToken();
if (tsToken != null) {
hasTimestampBlock = true;
SignerInfo tsSi = tsToken.getSignerInfos()[0];
X509Certificate tsSigner = tsSi.getCertificate(tsToken);
byte[] encTsTokenInfo = tsToken.getContentInfo().getData();
......@@ -967,7 +985,7 @@ public class Main {
if (badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
notYetValidCert || chainNotValidated || hasExpiredCert ||
hasUnsignedEntry || signerSelfSigned || (weakAlg != 0) ||
aliasNotInStore || notSignedByAlias) {
aliasNotInStore || notSignedByAlias || tsaChainNotValidated) {
if (strict) {
System.out.println(rb.getString("jar.verified.with.signer.errors."));
......@@ -1019,10 +1037,16 @@ public class Main {
if (chainNotValidated) {
System.out.println(String.format(
rb.getString("This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1"),
rb.getString("This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1"),
chainNotValidatedReason.getLocalizedMessage()));
}
if (tsaChainNotValidated) {
System.out.println(String.format(
rb.getString("This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1"),
tsaChainNotValidatedReason.getLocalizedMessage()));
}
if (notSignedByAlias) {
System.out.println(
rb.getString("This.jar.contains.signed.entries.which.is.not.signed.by.the.specified.alias.es."));
......@@ -1050,8 +1074,15 @@ public class Main {
"This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
}
if (noTimestamp) {
System.out.println(
String.format(rb.getString("no.timestamp.verifying"), expireDate));
if (hasTimestampBlock) {
// JarSigner API has not seen the timestamp,
// might have ignored it due to weak alg, etc.
System.out.println(
String.format(rb.getString("bad.timestamp.verifying"), expireDate));
} else {
System.out.println(
String.format(rb.getString("no.timestamp.verifying"), expireDate));
}
}
}
if (warningAppeared || errorAppeared) {
......@@ -1106,16 +1137,23 @@ public class Main {
private static MessageFormat expiredTimeForm = null;
private static MessageFormat expiringTimeForm = null;
/*
* Display some details about a certificate:
/**
* Returns a string about a certificate:
*
* [<tab>] <cert-type> [", " <subject-DN>] [" (" <keystore-entry-alias> ")"]
* [<validity-period> | <expiry-warning>]
* [<key-usage-warning>]
*
* Note: no newline character at the end.
*
* Note: no newline character at the end
* When isTsCert is true, this method sets global flags like hasExpiredCert,
* notYetValidCert, badKeyUsage, badExtendedKeyUsage, badNetscapeCertType.
*
* @param isTsCert true if c is in the TSA cert chain, false otherwise.
* @param checkUsage true to check code signer keyUsage
*/
String printCert(String tab, Certificate c, boolean checkValidityPeriod,
Date timestamp, boolean checkUsage) {
String printCert(boolean isTsCert, String tab, Certificate c,
Date timestamp, boolean checkUsage) throws Exception {
StringBuilder certStr = new StringBuilder();
String space = rb.getString("SPACE");
......@@ -1135,7 +1173,7 @@ public class Main {
certStr.append(space).append(alias);
}
if (checkValidityPeriod && x509Cert != null) {
if (x509Cert != null) {
certStr.append("\n").append(tab).append("[");
Date notAfter = x509Cert.getNotAfter();
......@@ -1148,7 +1186,7 @@ public class Main {
x509Cert.checkValidity();
// test if cert will expire within six months
if (notAfter.getTime() < System.currentTimeMillis() + SIX_MONTHS) {
hasExpiringCert = true;
if (!isTsCert) hasExpiringCert = true;
if (expiringTimeForm == null) {
expiringTimeForm = new MessageFormat(
rb.getString("certificate.will.expire.on"));
......@@ -1169,7 +1207,7 @@ public class Main {
certStr.append(validityTimeForm.format(source));
}
} catch (CertificateExpiredException cee) {
hasExpiredCert = true;
if (!isTsCert) hasExpiredCert = true;
if (expiredTimeForm == null) {
expiredTimeForm = new MessageFormat(
......@@ -1179,7 +1217,7 @@ public class Main {
certStr.append(expiredTimeForm.format(source));
} catch (CertificateNotYetValidException cnyve) {
notYetValidCert = true;
if (!isTsCert) notYetValidCert = true;
if (notYetTimeForm == null) {
notYetTimeForm = new MessageFormat(
......@@ -1398,7 +1436,7 @@ public class Main {
System.out.println(rb.getString("TSA.location.") + tsaUrl);
} else if (tsaCert != null) {
System.out.println(rb.getString("TSA.certificate.") +
printCert("", tsaCert, false, null, false));
printCert(true, "", tsaCert, null, false));
}
}
builder.tsa(tsaURI);
......@@ -1458,6 +1496,30 @@ public class Main {
}
}
// The JarSigner API always accepts the timestamp received.
// We need to extract the certs from the signed jar to
// validate it.
if (!noTimestamp) {
try (JarFile check = new JarFile(signedJarFile)) {
PKCS7 p7 = new PKCS7(check.getInputStream(check.getEntry(
"META-INF/" + sigfile + "." + privateKey.getAlgorithm())));
SignerInfo si = p7.getSignerInfos()[0];
PKCS7 tsToken = si.getTsToken();
SignerInfo tsSi = tsToken.getSignerInfos()[0];
try {
validateCertChain(Validator.VAR_TSA_SERVER,
tsSi.getCertificateChain(tsToken), null);
} catch (Exception e) {
tsaChainNotValidated = true;
tsaChainNotValidatedReason = e;
}
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
}
}
// no IOException thrown in the follow try clause, so disable
// the try clause.
// try {
......@@ -1487,8 +1549,10 @@ public class Main {
}
boolean warningAppeared = false;
if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage || badNetscapeCertType ||
notYetValidCert || chainNotValidated || hasExpiredCert || signerSelfSigned) {
if (weakAlg != 0 || badKeyUsage || badExtendedKeyUsage
|| badNetscapeCertType || notYetValidCert
|| chainNotValidated || tsaChainNotValidated
|| hasExpiredCert || signerSelfSigned) {
if (strict) {
System.out.println(rb.getString("jar.signed.with.signer.errors."));
System.out.println();
......@@ -1525,10 +1589,16 @@ public class Main {
if (chainNotValidated) {
System.out.println(String.format(
rb.getString("The.signer.s.certificate.chain.is.not.validated.reason.1"),
rb.getString("The.signer.s.certificate.chain.is.invalid.reason.1"),
chainNotValidatedReason.getLocalizedMessage()));
}
if (tsaChainNotValidated) {
System.out.println(String.format(
rb.getString("The.tsa.certificate.chain.is.invalid.reason.1"),
tsaChainNotValidatedReason.getLocalizedMessage()));
}
if (signerSelfSigned) {
System.out.println(
rb.getString("The.signer.s.certificate.is.self.signed."));
......@@ -1600,7 +1670,7 @@ public class Main {
/**
* Returns a string of singer info, with a newline at the end
*/
private String signerInfo(CodeSigner signer, String tab) {
private String signerInfo(CodeSigner signer, String tab) throws Exception {
if (cacheForSignerInfo.containsKey(signer)) {
return cacheForSignerInfo.get(signer);
}
......@@ -1620,18 +1690,35 @@ public class Main {
// display the certificate(sb). The first one is end-entity cert and
// its KeyUsage should be checked.
boolean first = true;
sb.append(tab).append(rb.getString("...Signer")).append('\n');
for (Certificate c : certs) {
sb.append(printCert(tab, c, true, timestamp, first));
sb.append(printCert(false, tab, c, timestamp, first));
sb.append('\n');
first = false;
}
try {
validateCertChain(certs);
validateCertChain(Validator.VAR_CODE_SIGNING, certs, ts);
} catch (Exception e) {
chainNotValidated = true;
chainNotValidatedReason = e;
sb.append(tab).append(rb.getString(".CertPath.not.validated."))
.append(e.getLocalizedMessage()).append("]\n"); // TODO
sb.append(tab).append(rb.getString(".Invalid.certificate.chain."))
.append(e.getLocalizedMessage()).append("]\n");
}
if (ts != null) {
sb.append(tab).append(rb.getString("...TSA")).append('\n');
for (Certificate c : ts.getSignerCertPath().getCertificates()) {
sb.append(printCert(true, tab, c, timestamp, false));
sb.append('\n');
}
try {
validateCertChain(Validator.VAR_TSA_SERVER,
ts.getSignerCertPath().getCertificates(), null);
} catch (Exception e) {
tsaChainNotValidated = true;
tsaChainNotValidatedReason = e;
sb.append(tab).append(rb.getString(".Invalid.TSA.certificate.chain."))
.append(e.getLocalizedMessage()).append("]\n");
}
}
if (certs.size() == 1
&& KeyStoreUtil.isSelfSigned((X509Certificate)certs.get(0))) {
......@@ -1841,7 +1928,7 @@ public class Main {
}
}
void getAliasInfo(String alias) {
void getAliasInfo(String alias) throws Exception {
Key key = null;
......@@ -1887,10 +1974,11 @@ public class Main {
// We don't meant to print anything, the next call
// checks validity and keyUsage etc
printCert("", certChain[0], true, null, true);
printCert(false, "", certChain[0], null, true);
try {
validateCertChain(Arrays.asList(certChain));
validateCertChain(Validator.VAR_CODE_SIGNING,
Arrays.asList(certChain), null);
} catch (Exception e) {
chainNotValidated = true;
chainNotValidatedReason = e;
......@@ -1949,17 +2037,31 @@ public class Main {
System.exit(1);
}
void validateCertChain(List<? extends Certificate> certs) throws Exception {
/**
* Validates a cert chain.
*
* @param parameter this might be a timestamp
*/
void validateCertChain(String variant, List<? extends Certificate> certs,
Object parameter)
throws Exception {
try {
Validator.getInstance(Validator.TYPE_PKIX,
Validator.VAR_CODE_SIGNING,
variant,
pkixParameters)
.validate(certs.toArray(new X509Certificate[certs.size()]));
.validate(certs.toArray(new X509Certificate[certs.size()]),
null, parameter);
} catch (Exception e) {
if (debug) {
e.printStackTrace();
}
if (e instanceof ValidatorException) {
// Exception might be dismissed if another warning flag
// is already set by printCert. This is only done for
// code signing certs.
if (variant.equals(Validator.VAR_CODE_SIGNING) &&
e instanceof ValidatorException) {
// Throw cause if it's CertPathValidatorException,
if (e.getCause() != null &&
e.getCause() instanceof CertPathValidatorException) {
......
/*
* Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
......@@ -207,7 +207,8 @@ public class Resources extends java.util.ListResourceBundle {
{"certificate.is.not.valid.until",
"certificate is not valid until {0}"},
{"certificate.will.expire.on", "certificate will expire on {0}"},
{".CertPath.not.validated.", "[CertPath not validated: "},
{".Invalid.certificate.chain.", "[Invalid certificate chain: "},
{".Invalid.TSA.certificate.chain.", "[Invalid TSA certificate chain: "},
{"requesting.a.signature.timestamp",
"requesting a signature timestamp"},
{"TSA.location.", "TSA location: "},
......@@ -224,6 +225,8 @@ public class Resources extends java.util.ListResourceBundle {
{"entry.was.signed.on", "entry was signed on {0}"},
{"Warning.", "Warning: "},
{"Error.", "Error: "},
{"...Signer", ">>> Signer"},
{"...TSA", ">>> TSA"},
{"This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked.",
"This jar contains unsigned entries which have not been integrity-checked. "},
{"This.jar.contains.entries.whose.signer.certificate.has.expired.",
......@@ -258,20 +261,26 @@ public class Resources extends java.util.ListResourceBundle {
"This jar contains entries whose signer certificate's NetscapeCertType extension doesn't allow code signing."},
{".{0}.extension.does.not.support.code.signing.",
"[{0} extension does not support code signing]"},
{"The.signer.s.certificate.chain.is.not.validated.reason.1",
"The signer's certificate chain is not validated. Reason: %s"},
{"The.signer.s.certificate.chain.is.invalid.reason.1",
"The signer's certificate chain is invalid. Reason: %s"},
{"The.tsa.certificate.chain.is.invalid.reason.1",
"The TSA certificate chain is invalid. Reason: %s"},
{"The.signer.s.certificate.is.self.signed.",
"The signer's certificate is self-signed."},
{"The.1.algorithm.specified.for.the.2.option.is.considered.a.security.risk.",
"The %1$s algorithm specified for the %2$s option is considered a security risk."},
{"The.1.signing.key.has.a.keysize.of.2.which.is.considered.a.security.risk.",
"The %s signing key has a keysize of %d which is considered a security risk."},
{"This.jar.contains.entries.whose.certificate.chain.is.not.validated.reason.1",
"This jar contains entries whose certificate chain is not validated. Reason: %s"},
{"This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1",
"This jar contains entries whose certificate chain is invalid. Reason: %s"},
{"This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1",
"This jar contains entries whose TSA certificate chain is invalid. Reason: %s"},
{"no.timestamp.signing",
"No -tsa or -tsacert is provided and this jar is not timestamped. Without a timestamp, users may not be able to validate this jar after the signer certificate's expiration date (%1$tY-%1$tm-%1$td)."},
{"no.timestamp.verifying",
"This jar contains signatures that do not include a timestamp. Without a timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as %1$tY-%1$tm-%1$td)."},
{"bad.timestamp.verifying",
"This jar contains signatures that include an invalid timestamp. Without a valid timestamp, users may not be able to validate this jar after any of the signer certificates expire (as early as %1$tY-%1$tm-%1$td).\nRerun jarsigner with -J-Djava.security.debug=jar for more information."},
{"Unknown.password.type.", "Unknown password type: "},
{"Cannot.find.environment.variable.",
"Cannot find environment variable: "},
......
......@@ -83,14 +83,14 @@ public class Warning {
issueCert("b", "-sigalg MD5withRSA");
run("jarsigner", "a.jar b")
.shouldMatch("chain is not validated. Reason:.*MD5withRSA");
.shouldMatch("chain is invalid. Reason:.*MD5withRSA");
recreateJar();
newCert("c", "-keysize 512");
issueCert("c");
run("jarsigner", "a.jar c")
.shouldContain("chain is not validated. " +
.shouldContain("chain is invalid. " +
"Reason: Algorithm constraints check failed");
recreateJar();
......
#
# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
......@@ -91,7 +91,7 @@ echo $RESULT
#[ $RESULT = 0 ] || exit 2
# Test 3: When no keystore is specified, the error is only
# "chain not validated"
# "chain invalid"
$JARSIGNER -strict -verify a.jar
RESULT=$?
......@@ -99,7 +99,7 @@ echo $RESULT
#[ $RESULT = 4 ] || exit 3
# Test 4: When unrelated keystore is specified, the error is
# "chain not validated" and "not alias in keystore"
# "chain invalid" and "not alias in keystore"
$JARSIGNER -keystore unrelated.jks -strict -verify a.jar
RESULT=$?
......
/*
* Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
......@@ -63,7 +63,7 @@ public abstract class Test {
static final String CHAIN_NOT_VALIDATED_VERIFYING_WARNING
= "This jar contains entries "
+ "whose certificate chain is not validated.";
+ "whose certificate chain is invalid.";
static final String ALIAS_NOT_IN_STORE_VERIFYING_WARNING
= "This jar contains signed entries "
......@@ -95,7 +95,7 @@ public abstract class Test {
+ "doesn't allow code signing.";
static final String CHAIN_NOT_VALIDATED_SIGNING_WARNING
= "The signer's certificate chain is not validated.";
= "The signer's certificate chain is invalid.";
static final String HAS_EXPIRING_CERT_SIGNING_WARNING
= "The signer certificate will expire within six months.";
......
#
# Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
# Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
......@@ -54,9 +54,9 @@ $KT -certreq -alias signer | \
$JAR cvf a.jar ks
# We always trust a TrustedCertificateEntry
$JS a.jar ca | grep "chain is not validated" && exit 1
$JS a.jar ca | grep "chain is invalid" && exit 1
# An end-entity cert must follow algorithm constraints
$JS a.jar signer | grep "chain is not validated" || exit 2
$JS a.jar signer | grep "chain is invalid" || exit 2
exit 0
......@@ -52,10 +52,7 @@ public class SecurityTools {
launcher.addToolArg(arg);
}
}
String[] cmds = launcher.getCommand();
String cmdLine = Arrays.stream(cmds).collect(Collectors.joining(" "));
System.out.println("Command line: [" + cmdLine + "]");
return new ProcessBuilder(cmds);
return new ProcessBuilder(launcher.getCommand());
}
// keytool
......@@ -72,7 +69,7 @@ public class SecurityTools {
pb.redirectInput(ProcessBuilder.Redirect.from(new File(RESPONSE_FILE)));
try {
return ProcessTools.executeProcess(pb);
return execute(pb);
} finally {
Files.delete(p);
}
......@@ -102,8 +99,21 @@ public class SecurityTools {
public static OutputAnalyzer jarsigner(List<String> args)
throws Exception {
return ProcessTools.executeProcess(
getProcessBuilder("jarsigner", args));
return execute(getProcessBuilder("jarsigner", args));
}
private static OutputAnalyzer execute(ProcessBuilder pb) throws Exception {
try {
OutputAnalyzer oa = ProcessTools.executeCommand(pb);
System.out.println("Exit value: " + oa.getExitValue());
return oa;
} catch (Throwable t) {
if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new Exception(t);
}
}
}
// Only call this if there is no white space in every argument
......
......@@ -27,9 +27,13 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.JarOutputStream;
......@@ -79,70 +83,93 @@ public final class JarUtils {
*/
public static void updateJar(String src, String dest, String... files)
throws IOException {
Map<String,Object> changes = new HashMap<>();
boolean update = true;
for (String file : files) {
if (file.equals("-")) {
update = false;
} else if (update) {
try {
Path p = Paths.get(file);
if (Files.exists(p)) {
changes.put(file, p);
} else {
changes.put(file, file);
}
} catch (InvalidPathException e) {
// Fallback if file not a valid Path.
changes.put(file, file);
}
} else {
changes.put(file, Boolean.FALSE);
}
}
updateJar(src, dest, changes);
}
/**
* Update content of a jar file.
*
* @param src the original jar file name
* @param dest the new jar file name
* @param changes a map of changes, key is jar entry name, value is content.
* Value can be Path, byte[] or String. If key exists in
* src but value is Boolean FALSE. The entry is removed.
* Existing entries in src not a key is unmodified.
* @throws IOException
*/
public static void updateJar(String src, String dest,
Map<String,Object> changes)
throws IOException {
// What if input changes is immutable?
changes = new HashMap<>(changes);
System.out.printf("Creating %s from %s...\n", dest, src);
try (JarOutputStream jos = new JarOutputStream(
new FileOutputStream(dest))) {
// copy each old entry into destination unless the entry name
// is in the updated list
List<String> updatedFiles = new ArrayList<>();
try (JarFile srcJarFile = new JarFile(src)) {
Enumeration<JarEntry> entries = srcJarFile.entries();
while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String name = entry.getName();
boolean found = false;
boolean update = true;
for (String file : files) {
if (file.equals("-")) {
update = false;
} else if (name.equals(file)) {
updatedFiles.add(file);
found = true;
break;
}
}
if (found) {
if (update) {
System.out.println(String.format("Updating %s with %s",
dest, name));
jos.putNextEntry(new JarEntry(name));
try (FileInputStream fis = new FileInputStream(name)) {
fis.transferTo(jos);
} catch (FileNotFoundException e) {
jos.write(name.getBytes());
}
} else {
System.out.println(String.format("Removing %s from %s",
name, dest));
}
if (changes.containsKey(name)) {
System.out.println(String.format("- Update %s", name));
updateEntry(jos, name, changes.get(name));
changes.remove(name);
} else {
System.out.println(String.format("Copying %s to %s",
name, dest));
System.out.println(String.format("- Copy %s", name));
jos.putNextEntry(entry);
srcJarFile.getInputStream(entry).transferTo(jos);
}
}
}
// append new files
for (String file : files) {
if (file.equals("-")) {
break;
}
if (!updatedFiles.contains(file)) {
System.out.println(String.format("Adding %s with %s",
dest, file));
jos.putNextEntry(new JarEntry(file));
try (FileInputStream fis = new FileInputStream(file)) {
fis.transferTo(jos);
} catch (FileNotFoundException e) {
jos.write(file.getBytes());
}
}
for (Map.Entry<String, Object> e : changes.entrySet()) {
System.out.println(String.format("- Add %s", e.getKey()));
updateEntry(jos, e.getKey(), e.getValue());
}
}
System.out.println();
}
private static void updateEntry(JarOutputStream jos, String name, Object content)
throws IOException {
if (content instanceof Boolean) {
if (((Boolean) content).booleanValue()) {
throw new RuntimeException("Boolean value must be FALSE");
}
} else {
jos.putNextEntry(new JarEntry(name));
if (content instanceof Path) {
Files.newInputStream((Path) content).transferTo(jos);
} else if (content instanceof byte[]) {
jos.write((byte[]) content);
} else if (content instanceof String) {
jos.write(((String) content).getBytes());
} else {
throw new RuntimeException("Unknown type " + content.getClass());
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册