--- old/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java 2016-10-19 15:18:21.000000000 +0800 +++ new/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java 2016-10-19 15:18:20.000000000 +0800 @@ -50,6 +50,9 @@ import jdk.security.jarsigner.JarSigner; import jdk.security.jarsigner.JarSignerException; +import sun.security.pkcs.PKCS7; +import sun.security.pkcs.SignerInfo; +import sun.security.timestamp.TimestampToken; import sun.security.tools.KeyStoreUtil; import sun.security.x509.*; import sun.security.util.*; @@ -87,6 +90,20 @@ private static final long SIX_MONTHS = 180*24*60*60*1000L; //milliseconds + // PROPERTY_CERTPATH_DISABLED_ALGS is currently more restrictive than + // PROPERTY_JAR_DISABLED_ALGS and we used it at signing time. + private static final DisabledAlgorithmConstraints VERIFY_CHECK = + new DisabledAlgorithmConstraints( + DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS); + private static final DisabledAlgorithmConstraints SIGN_CHECK = + new DisabledAlgorithmConstraints( + DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS); + + private static final Set DIGEST_PRIMITIVE_SET = Collections + .unmodifiableSet(EnumSet.of(CryptoPrimitive.MESSAGE_DIGEST)); + private static final Set SIG_PRIMITIVE_SET = Collections + .unmodifiableSet(EnumSet.of(CryptoPrimitive.SIGNATURE)); + // Attention: // This is the entry that get launched by the security tool jarsigner. public static void main(String args[]) throws Exception { @@ -163,6 +180,8 @@ private Throwable chainNotValidatedReason = null; + private boolean seeWeak = false; + CertificateFactory certificateFactory; CertPathValidator validator; PKIXParameters pkixParameters; @@ -628,6 +647,10 @@ { boolean anySigned = false; // if there exists entry inside jar signed JarFile jf = null; + Map digestMap = new HashMap<>(); + Map sigMap = new HashMap<>(); + Map sigNameMap = new HashMap<>(); + Map unparsableSignatures = new HashMap<>(); try { jf = new JarFile(jarName, true); @@ -638,16 +661,44 @@ while (entries.hasMoreElements()) { JarEntry je = entries.nextElement(); entriesVec.addElement(je); - InputStream is = null; - try { - is = jf.getInputStream(je); - while (is.read(buffer, 0, buffer.length) != -1) { - // we just read. this will throw a SecurityException - // if a signature/digest check fails. - } - } finally { - if (is != null) { - is.close(); + try (InputStream is = jf.getInputStream(je)) { + String name = je.getName(); + if (signatureRelated(name) + && SignatureFileVerifier.isBlockOrSF(name)) { + String alias = name.substring(name.lastIndexOf('/') + 1, + name.lastIndexOf('.')); + try { + if (name.endsWith(".SF")) { + Manifest sf = new Manifest(is); + boolean found = false; + for (Object obj : sf.getMainAttributes().keySet()) { + String key = obj.toString(); + if (key.endsWith("-Digest-Manifest")) { + digestMap.put(alias, + key.substring(0, key.length() - 16)); + found = true; + break; + } + } + if (!found) { + unparsableSignatures.putIfAbsent(alias, + String.format( + rb.getString("history.unparsable"), + name)); + } + } else { + sigNameMap.put(alias, name); + sigMap.put(alias, new PKCS7(is)); + } + } catch (IOException ioe) { + unparsableSignatures.putIfAbsent(alias, String.format( + rb.getString("history.unparsable"), name)); + } + } else { + while (is.read(buffer, 0, buffer.length) != -1) { + // we just read. this will throw a SecurityException + // if a signature/digest check fails. + } } } } @@ -805,10 +856,11 @@ System.out.println(rb.getString( ".X.not.signed.by.specified.alias.es.")); } - System.out.println(); } - if (man == null) + if (man == null) { + System.out.println(); System.out.println(rb.getString("no.manifest.")); + } // If signer is a trusted cert or private entry in user's own // keystore, it can be self-signed. @@ -816,8 +868,99 @@ signerSelfSigned = false; } + // Even if the verbose option is not specified, all out strings + // must be generated so seeWeak can be updated. + if (!digestMap.isEmpty() + || !sigMap.isEmpty() + || !unparsableSignatures.isEmpty()) { + if (verbose != null) { + System.out.println(); + } + for (String s : sigMap.keySet()) { + if (!digestMap.containsKey(s)) { + unparsableSignatures.putIfAbsent(s, String.format( + rb.getString("history.nosf"), s)); + } + } + for (String s : digestMap.keySet()) { + PKCS7 p7 = sigMap.get(s); + if (p7 != null) { + String history; + try { + SignerInfo si = p7.getSignerInfos()[0]; + X509Certificate signer = si.getCertificate(p7); + String digestAlg = digestMap.get(s); + String sigAlg = AlgorithmId.makeSigAlg( + si.getDigestAlgorithmId().getName(), + si.getDigestEncryptionAlgorithmId().getName()); + PublicKey key = signer.getPublicKey(); + PKCS7 tsToken = si.getTsToken(); + if (tsToken != null) { + SignerInfo tsSi = tsToken.getSignerInfos()[0]; + X509Certificate tsSigner = tsSi.getCertificate(tsToken); + byte[] encTsTokenInfo = tsToken.getContentInfo().getData(); + TimestampToken tsTokenInfo = new TimestampToken(encTsTokenInfo); + PublicKey tsKey = tsSigner.getPublicKey(); + String tsDigestAlg = tsTokenInfo.getHashAlgorithm().getName(); + String tsSigAlg = AlgorithmId.makeSigAlg( + tsSi.getDigestAlgorithmId().getName(), + tsSi.getDigestEncryptionAlgorithmId().getName()); + Calendar c = Calendar.getInstance( + TimeZone.getTimeZone("UTC"), + Locale.getDefault(Locale.Category.FORMAT)); + c.setTime(tsTokenInfo.getDate()); + history = String.format( + rb.getString("history.with.ts"), + signer.getSubjectX500Principal(), + withWeak(digestAlg, DIGEST_PRIMITIVE_SET), + withWeak(sigAlg, SIG_PRIMITIVE_SET), + withWeak(key), + c, + tsSigner.getSubjectX500Principal(), + withWeak(tsDigestAlg, DIGEST_PRIMITIVE_SET), + withWeak(tsSigAlg, SIG_PRIMITIVE_SET), + withWeak(tsKey)); + } else { + history = String.format( + rb.getString("history.without.ts"), + signer.getSubjectX500Principal(), + withWeak(digestAlg, DIGEST_PRIMITIVE_SET), + withWeak(sigAlg, SIG_PRIMITIVE_SET), + withWeak(key)); + } + } catch (Exception e) { + // The only usage of sigNameMap, remember the name + // of the block file if it's invalid. + history = String.format( + rb.getString("history.unparsable"), + sigNameMap.get(s)); + } + if (verbose != null) { + System.out.println(history); + } + } else { + unparsableSignatures.putIfAbsent(s, String.format( + rb.getString("history.nobk"), s)); + } + } + if (verbose != null) { + for (String s : unparsableSignatures.keySet()) { + System.out.println(unparsableSignatures.get(s)); + } + } + } + System.out.println(); if (!anySigned) { - if (hasSignature) { + if (seeWeak) { + if (verbose != null) { + System.out.println(rb.getString("jar.treated.unsigned.see.weak.verbose")); + System.out.println("\n " + + DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS + + "=" + Security.getProperty(DisabledAlgorithmConstraints.PROPERTY_JAR_DISABLED_ALGS)); + } else { + System.out.println(rb.getString("jar.treated.unsigned.see.weak")); + } + } else if (hasSignature) { System.out.println(rb.getString("jar.treated.unsigned")); } else { System.out.println(rb.getString("jar.is.unsigned")); @@ -845,7 +988,9 @@ if (weakAlg != 0) { // In fact, jarsigner verification did not catch this // since it has not read the JarFile content itself. - // Everything is done with JarFile API. + // Everything is done with JarFile API. The signing + // history (digestMap etc) will show these info and + // print out proper warnings. } if (badKeyUsage) { @@ -936,6 +1081,26 @@ System.exit(1); } + private String withWeak(String alg, Set primitiveSet) { + if (VERIFY_CHECK.permits(primitiveSet, alg, null)) { + return alg; + } else { + seeWeak = true; + return String.format(rb.getString("with.weak"), alg); + } + } + + private String withWeak(PublicKey key) { + if (VERIFY_CHECK.permits(SIG_PRIMITIVE_SET, key)) { + return String.format( + rb.getString("key.bit"), KeyUtil.getKeySize(key)); + } else { + seeWeak = true; + return String.format( + rb.getString("key.bit.weak"), KeyUtil.getKeySize(key)); + } + } + private static MessageFormat validityTimeForm = null; private static MessageFormat notYetTimeForm = null; private static MessageFormat expiredTimeForm = null; @@ -1125,22 +1290,22 @@ void signJar(String jarName, String alias) throws Exception { - DisabledAlgorithmConstraints dac = - new DisabledAlgorithmConstraints( - DisabledAlgorithmConstraints.PROPERTY_CERTPATH_DISABLED_ALGS); - - if (digestalg != null && !dac.permits( - Collections.singleton(CryptoPrimitive.MESSAGE_DIGEST), digestalg, null)) { + if (digestalg != null && !SIGN_CHECK.permits( + DIGEST_PRIMITIVE_SET, digestalg, null)) { weakAlg |= 1; } - if (tSADigestAlg != null && !dac.permits( - Collections.singleton(CryptoPrimitive.MESSAGE_DIGEST), tSADigestAlg, null)) { + if (tSADigestAlg != null && !SIGN_CHECK.permits( + DIGEST_PRIMITIVE_SET, tSADigestAlg, null)) { weakAlg |= 4; } - if (sigalg != null && !dac.permits( - Collections.singleton(CryptoPrimitive.SIGNATURE), sigalg, null)) { + if (sigalg != null && !SIGN_CHECK.permits( + SIG_PRIMITIVE_SET , sigalg, null)) { weakAlg |= 2; } + if (!SIGN_CHECK.permits( + SIG_PRIMITIVE_SET, privateKey)) { + weakAlg |= 8; + } boolean aliasUsed = false; X509Certificate tsaCert = null; @@ -1385,6 +1550,11 @@ rb.getString("The.1.algorithm.specified.for.the.2.option.is.considered.a.security.risk."), tSADigestAlg, "-tsadigestalg")); } + if ((weakAlg & 8) == 8) { + System.out.println(String.format( + rb.getString("The.1.signing.key.has.a.keysize.of.2.which.is.considered.a.security.risk."), + privateKey.getAlgorithm(), KeyUtil.getKeySize(privateKey))); + } } else { System.out.println(rb.getString("jar.signed.")); }