8215937: Check usages of security-related Resources files

Reviewed-by: mullan
This commit is contained in:
Weijun Wang 2019-01-19 09:20:47 +08:00
parent db89805fe0
commit 54815061a1
8 changed files with 271 additions and 590 deletions

View File

@ -3784,9 +3784,9 @@ public final class Main {
replyCerts.length);
tmpCerts[tmpCerts.length-1] = root.snd;
replyCerts = tmpCerts;
checkWeak(String.format(rb.getString(fromKeyStore ?
"alias.in.keystore" :
"alias.in.cacerts"),
checkWeak(String.format(fromKeyStore
? rb.getString("alias.in.keystore")
: rb.getString("alias.in.cacerts"),
root.fst),
root.snd);
}

View File

@ -253,7 +253,6 @@ public class Resources extends java.util.ListResourceBundle {
{"Keystore.password.is.too.short.must.be.at.least.6.characters",
"Keystore password is too short - must be at least 6 characters"},
{"Unknown.Entry.Type", "Unknown Entry Type"},
{"Too.many.failures.Alias.not.changed", "Too many failures. Alias not changed"},
{"Entry.for.alias.alias.successfully.imported.",
"Entry for alias {0} successfully imported."},
{"Entry.for.alias.alias.not.imported.", "Entry for alias {0} not imported."},
@ -314,10 +313,6 @@ public class Resources extends java.util.ListResourceBundle {
{"Too.many.failures.Key.entry.not.cloned",
"Too many failures. Key entry not cloned"},
{"key.password.for.alias.", "key password for <{0}>"},
{"Keystore.entry.for.id.getName.already.exists",
"Keystore entry for <{0}> already exists"},
{"Creating.keystore.entry.for.id.getName.",
"Creating keystore entry for <{0}> ..."},
{"No.entries.from.identity.database.added",
"No entries from identity database added"},
{"Alias.name.alias", "Alias name: {0}"},
@ -355,7 +350,6 @@ public class Resources extends java.util.ListResourceBundle {
{"Do.you.still.want.to.add.it.to.your.own.keystore.no.",
"Do you still want to add it to your own keystore? [no]: "},
{"Trust.this.certificate.no.", "Trust this certificate? [no]: "},
{"YES", "YES"},
{"New.prompt.", "New {0}: "},
{"Passwords.must.differ", "Passwords must differ"},
{"Re.enter.new.prompt.", "Re-enter new {0}: "},
@ -395,7 +389,6 @@ public class Resources extends java.util.ListResourceBundle {
{"Signer.d.", "Signer #%d:"},
{"Timestamp.", "Timestamp:"},
{"Signature.", "Signature:"},
{"CRLs.", "CRLs:"},
{"Certificate.owner.", "Certificate owner: "},
{"Not.a.signed.jar.file", "Not a signed jar file"},
{"No.certificate.from.the.SSL.server",
@ -414,13 +407,10 @@ public class Resources extends java.util.ListResourceBundle {
"Certificate reply does not contain public key for <{0}>"},
{"Incomplete.certificate.chain.in.reply",
"Incomplete certificate chain in reply"},
{"Certificate.chain.in.reply.does.not.verify.",
"Certificate chain in reply does not verify: "},
{"Top.level.certificate.in.reply.",
"Top-level certificate in reply:\n"},
{".is.not.trusted.", "... is not trusted. "},
{"Install.reply.anyway.no.", "Install reply anyway? [no]: "},
{"NO", "NO"},
{"Public.keys.in.reply.and.keystore.don.t.match",
"Public keys in reply and keystore don't match"},
{"Certificate.reply.and.certificate.in.keystore.are.identical",

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2000, 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2000, 2018, 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
@ -66,8 +66,6 @@ public class Resources extends java.util.ListResourceBundle {
{"Subject.", "Subject:\n"},
{".Principal.", "\tPrincipal: "},
{".Public.Credential.", "\tPublic Credential: "},
{".Private.Credentials.inaccessible.",
"\tPrivate Credentials inaccessible\n"},
{".Private.Credential.", "\tPrivate Credential: "},
{".Private.Credential.inaccessible.",
"\tPrivate Credential inaccessible\n"},
@ -89,16 +87,6 @@ public class Resources extends java.util.ListResourceBundle {
"invalid null CallbackHandler provided"},
{"null.subject.logout.called.before.login",
"null subject - logout called before login"},
{"unable.to.instantiate.LoginModule.module.because.it.does.not.provide.a.no.argument.constructor",
"unable to instantiate LoginModule, {0}, because it does not provide a no-argument constructor"},
{"unable.to.instantiate.LoginModule",
"unable to instantiate LoginModule"},
{"unable.to.instantiate.LoginModule.",
"unable to instantiate LoginModule: "},
{"unable.to.find.LoginModule.class.",
"unable to find LoginModule class: "},
{"unable.to.access.LoginModule.",
"unable to access LoginModule: "},
{"Login.Failure.all.modules.ignored",
"Login Failure: all modules ignored"},

View File

@ -1030,31 +1030,31 @@ public class Main {
(hasExpiredTsaCert && !signerNotExpired)) {
if (strict) {
result = rb.getString(isSigning
? "jar.signed.with.signer.errors."
: "jar.verified.with.signer.errors.");
result = isSigning
? rb.getString("jar.signed.with.signer.errors.")
: rb.getString("jar.verified.with.signer.errors.");
} else {
result = rb.getString(isSigning
? "jar.signed."
: "jar.verified.");
result = isSigning
? rb.getString("jar.signed.")
: rb.getString("jar.verified.");
}
if (badKeyUsage) {
errors.add(rb.getString(isSigning
? "The.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."
: "This.jar.contains.entries.whose.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
errors.add(isSigning
? rb.getString("The.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.s.KeyUsage.extension.doesn.t.allow.code.signing."));
}
if (badExtendedKeyUsage) {
errors.add(rb.getString(isSigning
? "The.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."
: "This.jar.contains.entries.whose.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."));
errors.add(isSigning
? rb.getString("The.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.s.ExtendedKeyUsage.extension.doesn.t.allow.code.signing."));
}
if (badNetscapeCertType) {
errors.add(rb.getString(isSigning
? "The.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."
: "This.jar.contains.entries.whose.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."));
errors.add(isSigning
? rb.getString("The.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.s.NetscapeCertType.extension.doesn.t.allow.code.signing."));
}
// only in verifying
@ -1063,20 +1063,20 @@ public class Main {
"This.jar.contains.unsigned.entries.which.have.not.been.integrity.checked."));
}
if (hasExpiredCert) {
errors.add(rb.getString(isSigning
? "The.signer.certificate.has.expired."
: "This.jar.contains.entries.whose.signer.certificate.has.expired."));
errors.add(isSigning
? rb.getString("The.signer.certificate.has.expired.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.has.expired."));
}
if (notYetValidCert) {
errors.add(rb.getString(isSigning
? "The.signer.certificate.is.not.yet.valid."
: "This.jar.contains.entries.whose.signer.certificate.is.not.yet.valid."));
errors.add(isSigning
? rb.getString("The.signer.certificate.is.not.yet.valid.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.is.not.yet.valid."));
}
if (chainNotValidated) {
errors.add(String.format(rb.getString(isSigning
? "The.signer.s.certificate.chain.is.invalid.reason.1"
: "This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1"),
errors.add(String.format(isSigning
? rb.getString("The.signer.s.certificate.chain.is.invalid.reason.1")
: rb.getString("This.jar.contains.entries.whose.certificate.chain.is.invalid.reason.1"),
chainNotValidatedReason.getLocalizedMessage()));
}
@ -1084,9 +1084,9 @@ public class Main {
errors.add(rb.getString("The.timestamp.has.expired."));
}
if (tsaChainNotValidated) {
errors.add(String.format(rb.getString(isSigning
? "The.tsa.certificate.chain.is.invalid.reason.1"
: "This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1"),
errors.add(String.format(isSigning
? rb.getString("The.tsa.certificate.chain.is.invalid.reason.1")
: rb.getString("This.jar.contains.entries.whose.tsa.certificate.chain.is.invalid.reason.1"),
tsaChainNotValidatedReason.getLocalizedMessage()));
}
@ -1102,9 +1102,9 @@ public class Main {
}
if (signerSelfSigned) {
errors.add(rb.getString(isSigning
? "The.signer.s.certificate.is.self.signed."
: "This.jar.contains.entries.whose.signer.certificate.is.self.signed."));
errors.add(isSigning
? rb.getString("The.signer.s.certificate.is.self.signed.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.is.self.signed."));
}
// weakAlg only detected in signing. The jar file is
@ -1131,7 +1131,7 @@ public class Main {
privateKey.getAlgorithm(), KeyUtil.getKeySize(privateKey)));
}
} else {
result = rb.getString(isSigning ? "jar.signed." : "jar.verified.");
result = isSigning ? rb.getString("jar.signed.") : rb.getString("jar.verified.");
}
if (hasExpiredTsaCert) {
@ -1155,9 +1155,9 @@ public class Main {
hasExpiredTsaCert = false;
}
if (hasExpiringCert) {
warnings.add(rb.getString(isSigning
? "The.signer.certificate.will.expire.within.six.months."
: "This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
warnings.add(isSigning
? rb.getString("The.signer.certificate.will.expire.within.six.months.")
: rb.getString("This.jar.contains.entries.whose.signer.certificate.will.expire.within.six.months."));
}
if (hasExpiringTsaCert && expireDate != null) {
if (expireDate.after(tsaExpireDate)) {
@ -1170,13 +1170,13 @@ public class Main {
}
if (noTimestamp && expireDate != null) {
if (hasTimestampBlock) {
warnings.add(String.format(rb.getString(isSigning
? "invalid.timestamp.signing"
: "bad.timestamp.verifying"), expireDate));
warnings.add(String.format(isSigning
? rb.getString("invalid.timestamp.signing")
: rb.getString("bad.timestamp.verifying"), expireDate));
} else {
warnings.add(String.format(rb.getString(isSigning
? "no.timestamp.signing"
: "no.timestamp.verifying"), expireDate));
warnings.add(String.format(isSigning
? rb.getString("no.timestamp.signing")
: rb.getString("no.timestamp.verifying"), expireDate));
}
}
}
@ -1551,7 +1551,20 @@ public class Main {
if (verbose != null) {
builder.eventHandler((action, file) -> {
System.out.println(rb.getString("." + action + ".") + file);
switch (action) {
case "signing":
System.out.println(rb.getString(".signing.") + file);
break;
case "adding":
System.out.println(rb.getString(".adding.") + file);
break;
case "updating":
System.out.println(rb.getString(".updating.") + file);
break;
default:
throw new IllegalArgumentException("unknown action: "
+ action);
}
});
}

View File

@ -36,7 +36,6 @@ public class Resources extends java.util.ListResourceBundle {
// shared (from jarsigner)
{"SPACE", " "},
{"2SPACE", " "},
{"6SPACE", " "},
{"COMMA", ", "},
@ -196,7 +195,6 @@ public class Resources extends java.util.ListResourceBundle {
"Certificate chain not found in the file specified."},
{"found.non.X.509.certificate.in.signer.s.chain",
"found non-X.509 certificate in signer's chain"},
{"incomplete.certificate.chain", "incomplete certificate chain"},
{"Enter.key.password.for.alias.", "Enter key password for {0}: "},
{"unable.to.recover.key.from.keystore",
"unable to recover key from keystore"},
@ -240,8 +238,6 @@ public class Resources extends java.util.ListResourceBundle {
"This jar contains entries whose signer certificate is not yet valid. "},
{"This.jar.contains.entries.whose.signer.certificate.is.self.signed.",
"This jar contains entries whose signer certificate is self-signed."},
{"Re.run.with.the.verbose.option.for.more.details.",
"Re-run with the -verbose option for more details."},
{"Re.run.with.the.verbose.and.certs.options.for.more.details.",
"Re-run with the -verbose and -certs options for more details."},
{"The.signer.certificate.has.expired.",

View File

@ -1,78 +0,0 @@
/*
* Copyright (c) 2010, 2012, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 6987827
* @modules java.base/sun.security.util
* java.base/sun.security.tools.keytool
* jdk.jartool/sun.security.tools.jarsigner
* @summary security/util/Resources.java needs improvement
*/
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;
/**
* This test makes sure that the keys in resources files are using the new
* format and there is no duplication.
*/
public class NewNamesFormat {
public static void main(String[] args) throws Exception {
checkRes("sun.security.util.Resources");
checkRes("sun.security.util.AuthResources");
checkRes("sun.security.tools.jarsigner.Resources");
checkRes("sun.security.tools.keytool.Resources");
}
private static void checkRes(String resName) throws Exception {
System.out.println("Checking " + resName + "...");
Class clazz = Class.forName(resName);
Method m = clazz.getMethod("getContents");
Object[][] contents = (Object[][])m.invoke(clazz.newInstance());
Set<String> keys = new HashSet<String>();
for (Object[] pair: contents) {
String key = (String)pair[0];
if (keys.contains(key)) {
System.out.println("Found dup: " + key);
throw new Exception();
}
checkKey(key);
keys.add(key);
}
}
private static void checkKey(String key) throws Exception {
for (char c: key.toCharArray()) {
if (Character.isLetter(c) || Character.isDigit(c) ||
c == '{' || c == '}' || c == '.') {
// OK
} else {
System.out.println("Illegal char [" + c + "] in key: " + key);
throw new Exception();
}
}
}
}

View File

@ -1,441 +0,0 @@
/*
* Copyright (c) 2010, 2012, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.ListResourceBundle;
import java.util.Map;
import java.util.Set;
/**
* Prepares new key names for Resources.java.
* 6987827: security/util/Resources.java needs improvement
*
* Run inside jdk/src/share/classes:
*
* java NewResourcesNames $(
* for a in $(find com/sun/security sun/security javax/security -type f); do
* egrep -q '(ResourcesMgr.getString|rb.getString)' $a && echo $a
* done)
*
* Before running this tool, run the following two commands to make sure there
* are only these 2 types of calls into the resources:
* for a in `find com/sun/security sun/security javax/security -type f`; do
* cat $a | perl -ne 'print if /\bResourcesMgr\b/'; done |
* grep -v ResourcesMgr.getString
* for a in `find com/sun/security sun/security -type f`; do
* cat $a | perl -ne 'print if /\brb\b/'; done |
* grep -v rb.getString
*/
class NewResourcesNames {
// Max length of normalized names
static int MAXLEN = 127;
static String[] resources = {
"sun/security/tools/jarsigner/Resources.java",
"sun/security/tools/keytool/Resources.java",
"sun/security/tools/policytool/Resources.java",
"sun/security/util/Resources.java",
"sun/security/util/AuthResources.java",
};
public static void main(String[] args) throws Exception {
// Load all names inside resources files
Map<String,String> allnames = loadResources();
// Modify the callers. There are two patterns:
// 1. ResourcesMgr.getString("
// used by most JAAS codes
// 2. rb.getString("
// used by tools
Set<String> allfound = new HashSet<String>();
for (String arg: args) {
allfound.addAll(rewriteFile(arg, "ResourcesMgr.getString(\""));
allfound.addAll(rewriteFile(arg, "rb.getString(\""));
}
// Special case 1: KeyTool's enum definition of commands and options
allfound.addAll(keyToolEnums());
// Special case 2: PolicyFile called this 4 times
allfound.addAll(rewriteFile("sun/security/provider/PolicyFile.java",
"ResourcesMgr.getString(POLICY+\""));
// During the calls above, you can read sth like:
//
// Working on com/sun/security/auth/PolicyParser.java
// GOOD match is 17
//
// This means a " exists right after getString(. Sometimes you see
//
// Working on sun/security/tools/keytool/Main.java
// BAD!! pmatch != match: 212 != 209
// Working on sun/security/provider/PolicyFile.java
// BAD!! pmatch != match: 14 != 10
//
// which is mismatch. There are only two such special cases list above.
// For KeyTool, there are 3 calls for showing help. For PolicyTool, 3
// for name prefixed with POLICY. They are covered in the two special
// cases above.
// Names used but not defined. This is mostly error, except for
// special case 2 above. So it's OK to see 3 entries red here
if (!allnames.keySet().containsAll(allfound)) {
err("FATAL: Undefined names");
for (String name: allfound) {
if (!allnames.keySet().contains(name)) {
err(" " + name);
}
}
}
// Names defined but not used. Mostly this is old entries not removed.
// When soemone remove a line of code, he dares not remove the entry
// in case it's also used somewhere else.
if (!allfound.containsAll(allnames.keySet())) {
System.err.println("WARNING: Unused names");
for (String name: allnames.keySet()) {
if (!allfound.contains(name)) {
System.err.println(allnames.get(name));
System.err.println(" " + normalize(name));
System.err.println(" [" + name + "]");
}
}
}
}
/**
* Loads the three resources files. Saves names into a Map.
*/
private static Map<String,String> loadResources() throws Exception {
// Name vs Resource
Map<String,String> allnames = new HashMap<String,String>();
for (String f: resources) {
String clazz =
f.replace('/', '.').substring(0, f.length()-5);
Set<String> expected = loadClass(clazz);
Set<String> found = rewriteFile(f, "{\"");
// This is to check that word parsing is identical to Java thinks
if (!expected.equals(found)) {
throw new Exception("Expected and found do not match");
}
for (String name: found) {
allnames.put(name, f);
}
}
return allnames;
}
/**
* Special case treat for enums description in KeyTool
*/
private static Set<String> keyToolEnums() throws Exception {
Set<String> names = new HashSet<String>();
String file = "sun/security/tools/keytool/Main.java";
System.err.println("Working on " + file);
File origFile = new File(file);
File tmpFile = new File(file + ".tmp");
origFile.renameTo(tmpFile);
tmpFile.deleteOnExit();
BufferedReader br = new BufferedReader(
new InputStreamReader(new FileInputStream(tmpFile)));
PrintWriter out = new PrintWriter(new FileOutputStream(origFile));
int stage = 0; // 1. commands, 2. options, 3. finished
int match = 0;
while (true) {
String s = br.readLine();
if (s == null) {
break;
}
if (s.indexOf("enum Command") >= 0) stage = 1;
else if (s.indexOf("enum Option") >= 0) stage = 2;
else if (s.indexOf("private static final String JKS") >= 0) stage = 3;
if (stage == 1 || stage == 2) {
if (s.indexOf("(\"") >= 0) {
match++;
int p1, p2;
if (stage == 1) {
p1 = s.indexOf("\"");
p2 = s.indexOf("\"", p1+1);
} else {
p2 = s.lastIndexOf("\"");
p1 = s.lastIndexOf("\"", p2-1);
}
String name = s.substring(p1+1, p2);
names.add(name);
out.println(s.substring(0, p1+1) +
normalize(name) +
s.substring(p2));
} else {
out.println(s);
}
} else {
out.println(s);
}
}
br.close();
out.close();
System.err.println(" GOOD match is " + match);
return names;
}
/**
* Loads a resources using JRE and returns the names
*/
private static Set<String> loadClass(String clazz) throws Exception {
ListResourceBundle lrb =
(ListResourceBundle)Class.forName(clazz).newInstance();
Set<String> keys = lrb.keySet();
Map<String,String> newold = new HashMap<String,String>();
boolean dup = false;
// Check if normalize() creates dup entries. This is crucial.
for (String k: keys) {
String key = normalize(k);
if (newold.containsKey(key)) {
err("Dup found for " + key + ":");
err("["+newold.get(key)+"]");
err("["+k+"]");
dup = true;
}
newold.put(key, k);
}
if (dup) throw new Exception();
return keys;
}
/**
* Rewrites a file using a pattern. The name string should be right after
* the pattern. Note: pattern ignores whitespaces. Returns names found.
*/
private static Set<String> rewriteFile(String file, String pattern)
throws Exception {
System.err.println("Working on " + file);
Set<String> names = new HashSet<String>();
int plen = pattern.length();
int match = 0;
// The bare XXX.getString is also matched. Sometimes getString is
// called but does not use literal strings. This is harder to solve.
int pmatch = 0;
int pheadlen = plen - 2;
String phead = pattern.substring(0, plen-2);
// The non-whitespace chars read since, used to check for pattern
StringBuilder history = new StringBuilder();
int hlen = 0;
File origFile = new File(file);
File tmpFile = new File(file + ".tmp");
origFile.renameTo(tmpFile);
tmpFile.deleteOnExit();
FileInputStream fis = new FileInputStream(tmpFile);
FileOutputStream fos = new FileOutputStream(origFile);
while (true) {
int ch = fis.read();
if (ch < 0) break;
if (!Character.isWhitespace(ch)) {
history.append((char)ch);
hlen++;
if (pheadlen > 0 && hlen >= pheadlen &&
history.substring(hlen-pheadlen, hlen).equals(phead)) {
pmatch++;
}
}
if (hlen >= plen &&
history.substring(hlen-plen, hlen).equals(pattern)) {
match++;
history = new StringBuilder();
hlen = 0;
fos.write(ch);
// Save a name
StringBuilder sb = new StringBuilder();
// Save things after the second ". Maybe it's an end, maybe
// it's just literal string concatenation.
StringBuilder tail = new StringBuilder();
boolean in = true; // inside name string
while (true) {
int n = fis.read();
if (in) {
if (n == '\\') {
int second = fis.read();
switch (second) {
case 'n': sb.append('\n'); break;
case 'r': sb.append('\r'); break;
case 't': sb.append('\t'); break;
case '"': sb.append('"'); break;
default: throw new Exception(String.format(
"I don't know this escape: %s%c",
sb.toString(), second));
}
} else if (n == '"') {
in = false;
// Maybe string concat? say bytes until clear
tail = new StringBuilder();
tail.append('"');
} else {
sb.append((char)n);
}
} else {
tail.append((char)n);
if (n == '"') { // string concat, in again
in = true;
} else if (n == ',' || n == ')') { // real end
break;
} else if (Character.isWhitespace(n) || n == '+') {
// string concat
} else {
throw new Exception("Not a correct concat");
}
}
}
String s = sb.toString();
names.add(s);
fos.write(normalize(s).getBytes());
fos.write(tail.toString().getBytes());
} else {
fos.write(ch);
}
}
// Check pheadlen > 0. Don't want to mess with rewrite for resources
if (pheadlen > 0 && pmatch != match) {
err(" BAD!! pmatch != match: " + pmatch + " != " + match);
} else {
System.err.println(" GOOD match is " + match);
}
fis.close();
fos.close();
return names;
}
/**
* Normalize a string. Rules:
*
* 1. If all spacebar return "nSPACE", n is count
* 2. If consisting at least one alphanumeric:
* a. All alphanumeric remain
* b. All others in a row goes to a single ".", even if at head or tail
* 3. Otherwise:
* a. "****\n\n" to "STARNN", special case
* b. the English name if first char in *,.\n():'"
*
* Current observations show there's no dup, Hurray! Otherwise, add more
* special cases.
*/
private static String normalize(String s) throws Exception {
boolean needDot = false;
// All spacebar case
int n = 0;
for (char c: s.toCharArray()) {
if (c == ' ') n++;
else n = -10000;
}
if (n == 1) return "SPACE";
else if (n > 1) return "" + n + "SPACE";
StringBuilder sb = new StringBuilder();
int dotpos = -1;
for (int i=0; i<s.length(); i++) {
char c = s.charAt(i);
if (Character.isLetter(c) || Character.isDigit(c) ||
c == '{' || c == '}') {
if (needDot) {
// Rememeber the last dot, we want shorter form nice
if (sb.length() <= MAXLEN) dotpos = sb.length();
// "." only added when an alphanumeric is seen. This makes
// sure sb is empty when there's no alphanumerics at all
sb.append(".");
}
sb.append(c);
needDot = false;
} else {
needDot = true;
}
}
// No alphanemeric?
if (sb.length() == 0) {
if (s.contains("*") && s.contains("\n")) {
return "STARNN";
}
for (char c: s.toCharArray()) {
switch (c) {
case '*': return "STAR";
case ',': return "COMMA";
case '.': return "PERIOD";
case '\n': return "NEWLINE";
case '(': return "LPARAM";
case ')': return "RPARAM";
case ':': return "COLON";
case '\'': case '"': return "QUOTE";
}
}
throw new Exception("Unnamed char: [" + s + "]");
}
// tail "." only added when there are alphanumerics
if (needDot) sb.append('.');
String res = sb.toString();
if (res.length() > MAXLEN) {
if (dotpos < 0) throw new Exception("No dot all over? " + s);
return res.substring(0, dotpos);
} else {
return res;
}
}
private static void err(String string) {
System.out.println("\u001b[1;37;41m" + string + "\u001b[m");
}
}

View File

@ -0,0 +1,213 @@
/*
* Copyright (c) 2018, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8215937
* @modules java.base/sun.security.util
* java.base/sun.security.tools.keytool
* jdk.jartool/sun.security.tools.jarsigner
* @summary Check usages of security-related Resources files
*/
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListResourceBundle;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* This test checks if the strings in various Resources files are used
* properly. Each string must be used somewhere, and each getString() call
* must use an existing string.
* <p>
* For each Resources file, the test maintains a list of where the strings are
* used (a file or a directory) and how they are used (one or more patterns).
* <p>
* If this test fails, there can be several reasons:
* <p>
* 1. If a string is not found, it has not been added to a Resources file.
* <p>
* 2. If a string is not used, maybe the call was removed earlier but the
* Resources file was not updated. Or, the file is not listed or the
* pattern is not correct and the usage is not found.
* <p>
* Because of #2 above, this test might not be complete. If a getString()
* is called but either the file and calling pattern is not listed here,
* we cannot guarantee it exists in a Resources file.
*/
public class Usages {
// src folder
static Path SRC = Path.of(
System.getProperty("test.src"), "../../../../../../src/")
.normalize();
// rb.getString(). Used by keytool, jarsigner, and KeyStoreUtil.
static Pattern RB_GETSTRING = Pattern.compile(
"(?m)rb[ \\n]*\\.getString[ \\n]*\\([ \\n]*\"(.*?)\"\\)");
// Command and Option enums in keytool
static Pattern KT_ENUM = Pattern.compile("\\n +[A-Z]+\\(.*\"(.*)\"");
// ResourceMgr.getAuthResourceString
static Pattern GETAUTHSTRING = Pattern.compile(
"getAuthResourceString[ \\n]*\\([ \\n]*\"(.*?)\"\\)");
// ResourceMgr.getString
static Pattern MGR_GETSTRING = Pattern.compile(
"ResourcesMgr\\.getString[ \\n]*\\([ \\n]*\"(.*?)\"\\)");
// LocalizedMessage.getNonlocalized("...")
static Pattern LOC_GETNONLOC = Pattern.compile(
"LocalizedMessage\\.getNonlocalized[ \\n]*\\([ \\n]*\"(.*?)\"");
// LocalizedMessage.getNonlocalized(POLICY + "...")
static Pattern LOC_GETNONLOC_POLICY = Pattern.compile(
"LocalizedMessage\\.getNonlocalized[ \\n]*\\([ \\n]*(POLICY \\+ \".*?)\"");
// new LocalizedMessage("...")
static Pattern NEW_LOC = Pattern.compile(
"new LocalizedMessage[ \\n]*\\([ \\n]*\"(.*?)\"");
// ioException in ConfigFile.java
static Pattern IOEXCEPTION = Pattern.compile(
"ioException[ \\n]*\\([ \\n]*\"(.*?)\",");
// For each Resources file, where and how the strings are used.
static Map<ListResourceBundle, List<Pair>> MAP = Map.of(
new sun.security.tools.keytool.Resources(), List.of(
new Pair("java.base/share/classes/sun/security/tools/keytool/Main.java",
List.of(RB_GETSTRING, KT_ENUM)),
new Pair("java.base/share/classes/sun/security/tools/KeyStoreUtil.java",
List.of(RB_GETSTRING))),
new sun.security.util.AuthResources(), List.of(
new Pair("java.base/share/classes/sun/security/provider/ConfigFile.java",
List.of(GETAUTHSTRING, IOEXCEPTION)),
new Pair("jdk.security.auth/share/classes/com/sun/security/auth/",
List.of(GETAUTHSTRING))),
new sun.security.tools.jarsigner.Resources(), List.of(
new Pair("jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java",
List.of(RB_GETSTRING)),
new Pair("java.base/share/classes/sun/security/tools/KeyStoreUtil.java",
List.of(RB_GETSTRING))),
new sun.security.util.Resources(), List.of(
new Pair("jdk.crypto.cryptoki/share/classes/sun/security/pkcs11/SunPKCS11.java",
List.of(MGR_GETSTRING)),
new Pair("java.base/share/classes/sun/security/provider/PolicyParser.java",
List.of(LOC_GETNONLOC, NEW_LOC)),
new Pair("java.base/share/classes/sun/security/provider/PolicyFile.java",
List.of(MGR_GETSTRING, LOC_GETNONLOC, LOC_GETNONLOC_POLICY)),
new Pair("java.base/share/classes/javax/security/auth/",
List.of(MGR_GETSTRING)))
);
public static void main(String[] args) {
if (Files.exists(SRC)) {
MAP.forEach(Usages::check);
} else {
System.out.println("No src directory. Test skipped.");
}
}
private static void check(ListResourceBundle res, List<Pair> fnps) {
try {
System.out.println(">>>> Checking " + res.getClass().getName());
List<String> keys = Collections.list(res.getKeys());
// Initialize unused to be all keys. Each time a key is used it
// is removed. We cannot reuse keys because a key might be used
// multiple times. Make it a Set so we can check duplicates.
Set<String> unused = new HashSet<>(keys);
keys.forEach(Usages::checkKeyFormat);
if (keys.size() != unused.size()) {
throw new RuntimeException("Duplicates found");
}
for (Pair fnp : fnps) {
Files.find(SRC.resolve(fnp.path), Integer.MAX_VALUE,
(p, attr) -> p.toString().endsWith(".java"))
.forEach(pa -> {
try {
String content = Files.readString(pa);
for (Pattern p : fnp.patterns) {
Matcher m = p.matcher(content);
while (m.find()) {
String arg = m.group(1);
// Special case in PolicyFile.java:
if (arg.startsWith("POLICY + \"")) {
arg = "java.security.policy"
+ arg.substring(10);
}
if (!keys.contains(arg)) {
throw new RuntimeException(
"Not found: " + arg);
}
unused.remove(arg);
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
}
if (!unused.isEmpty()) {
throw new RuntimeException("Unused keys: " + unused);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void checkKeyFormat(String key) {
for (char c : key.toCharArray()) {
if (Character.isLetter(c) || Character.isDigit(c) ||
c == '{' || c == '}' || c == '.') {
// OK
} else {
throw new RuntimeException(
"Illegal char [" + c + "] in key: " + key);
}
}
}
static class Pair {
public final String path;
public final List<Pattern> patterns;
public Pair(String path, List<Pattern> patterns) {
this.path = path;
this.patterns = patterns;
}
}
}