8215937: Check usages of security-related Resources files
authorweijun
Sat, 19 Jan 2019 09:20:47 +0800
changeset 53398 dd1be616c95e
parent 53397 bc1de01b3e78
child 53399 bf806003a4de
8215937: Check usages of security-related Resources files Reviewed-by: mullan
src/java.base/share/classes/sun/security/tools/keytool/Main.java
src/java.base/share/classes/sun/security/tools/keytool/Resources.java
src/java.base/share/classes/sun/security/util/Resources.java
src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java
src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Resources.java
test/jdk/sun/security/util/Resources/NewNamesFormat.java
test/jdk/sun/security/util/Resources/NewResourcesNames.java
test/jdk/sun/security/util/Resources/Usages.java
--- a/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Fri Jan 18 15:44:17 2019 -0800
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Main.java	Sat Jan 19 09:20:47 2019 +0800
@@ -3784,9 +3784,9 @@
                                  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);
             }
--- a/src/java.base/share/classes/sun/security/tools/keytool/Resources.java	Fri Jan 18 15:44:17 2019 -0800
+++ b/src/java.base/share/classes/sun/security/tools/keytool/Resources.java	Sat Jan 19 09:20:47 2019 +0800
@@ -253,7 +253,6 @@
         {"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 @@
         {"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 @@
         {"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 @@
         {"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 @@
                 "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",
--- a/src/java.base/share/classes/sun/security/util/Resources.java	Fri Jan 18 15:44:17 2019 -0800
+++ b/src/java.base/share/classes/sun/security/util/Resources.java	Sat Jan 19 09:20:47 2019 +0800
@@ -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 @@
         {"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 @@
                 "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"},
 
--- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Fri Jan 18 15:44:17 2019 -0800
+++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Main.java	Sat Jan 19 09:20:47 2019 +0800
@@ -1030,31 +1030,31 @@
                 (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 @@
                         "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 @@
                 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 @@
             }
 
             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 @@
                         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 @@
                 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 @@
             }
             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 @@
 
         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);
+                }
             });
         }
 
--- a/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Resources.java	Fri Jan 18 15:44:17 2019 -0800
+++ b/src/jdk.jartool/share/classes/sun/security/tools/jarsigner/Resources.java	Sat Jan 19 09:20:47 2019 +0800
@@ -36,7 +36,6 @@
 
         // shared (from jarsigner)
         {"SPACE", " "},
-        {"2SPACE", "  "},
         {"6SPACE", "      "},
         {"COMMA", ", "},
 
@@ -196,7 +195,6 @@
                 "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 @@
                 "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.",
--- a/test/jdk/sun/security/util/Resources/NewNamesFormat.java	Fri Jan 18 15:44:17 2019 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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();
-            }
-        }
-    }
-}
--- a/test/jdk/sun/security/util/Resources/NewResourcesNames.java	Fri Jan 18 15:44:17 2019 -0800
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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");
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/sun/security/util/Resources/Usages.java	Sat Jan 19 09:20:47 2019 +0800
@@ -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;
+        }
+    }
+}