Path: blob/master/test/jdk/javax/management/security/HashedPasswordFileTest.java
41149 views
/*1* Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223/* @test24* @bug 5016517 820466125* @summary Test Hashed passwords26* @library /test/lib27* @modules java.management28* jdk.management.agent/jdk.internal.agent29* @build HashedPasswordFileTest30* @run testng/othervm HashedPasswordFileTest31*32*/3334import jdk.internal.agent.ConnectorAddressLink;35import jdk.test.lib.Utils;36import jdk.test.lib.process.ProcessTools;37import org.testng.Assert;38import org.testng.annotations.AfterClass;39import org.testng.annotations.Test;4041import javax.management.MBeanServer;42import javax.management.remote.*;43import java.io.*;44import java.lang.management.ManagementFactory;45import java.nio.charset.StandardCharsets;46import java.nio.file.FileSystems;47import java.nio.file.Files;48import java.nio.file.attribute.PosixFilePermission;49import java.security.MessageDigest;50import java.security.NoSuchAlgorithmException;51import java.util.*;52import java.util.List;53import java.util.Set;54import java.util.concurrent.*;5556@Test57public class HashedPasswordFileTest {5859private final String[] randomWords = {"accost", "savoie", "bogart", "merest",60"azuela", "hoodie", "bursal", "lingua", "wincey", "trilby", "egesta",61"wester", "gilgai", "weinek", "ochone", "sanest", "gainst", "defang",62"ranket", "mayhem", "tagger", "timber", "eggcup", "mhren", "colloq",63"dreamy", "hattie", "rootle", "bloody", "helyne", "beater", "cosine",64"enmity", "outbox", "issuer", "lumina", "dekker", "vetoed", "dennis",65"strove", "gurnet", "talkie", "bennie", "behove", "coates", "shiloh",66"yemeni", "boleyn", "coaxal", "irne"};6768private final String[] hashAlgs = {69"MD2",70"MD5",71"SHA-1",72"SHA-224",73"SHA-256",74"SHA-384",75"SHA-512/224",76"SHA-512/256",77"SHA3-224",78"SHA3-256",79"SHA3-384",80"SHA3-512"81};8283private final Random random = Utils.getRandomInstance();8485private JMXConnectorServer cs;8687private String randomWord() {88int idx = random.nextInt(randomWords.length);89return randomWords[idx];90}9192private String[] getHash(String algorithm, String password) {93try {94byte[] salt = new byte[64];95random.nextBytes(salt);9697MessageDigest digest = MessageDigest.getInstance(algorithm);98digest.reset();99digest.update(salt);100byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));101102String saltStr = Base64.getEncoder().encodeToString(salt);103String hashStr = Base64.getEncoder().encodeToString(hash);104105return new String[]{saltStr, hashStr};106} catch (NoSuchAlgorithmException ex) {107throw new RuntimeException(ex);108}109}110111private String getPasswordFilePath() {112String testDir = System.getProperty("test.src");113String testFileName = "jmxremote.password";114return testDir + File.separator + testFileName;115}116117private File createNewPasswordFile() throws IOException {118File file = new File(getPasswordFilePath());119if (file.exists()) {120file.delete();121}122file.createNewFile();123return file;124}125126private Map<String, String> generateClearTextPasswordFile() throws IOException {127File file = createNewPasswordFile();128Map<String, String> props = new HashMap<>();129BufferedWriter br;130try (FileWriter fw = new FileWriter(file)) {131br = new BufferedWriter(fw);132int numentries = random.nextInt(5) + 3;133for (int i = 0; i < numentries; i++) {134String username;135do {136username = randomWord();137} while (props.get(username) != null);138String password = randomWord();139props.put(username, password);140br.write(username + " " + password + "\n");141}142br.flush();143}144br.close();145return props;146}147148private boolean isPasswordFileHashed() throws IOException {149BufferedReader br;150boolean result;151try (FileReader fr = new FileReader(getPasswordFilePath())) {152br = new BufferedReader(fr);153result = br.lines().anyMatch(line -> {154if (line.startsWith("#")) {155return false;156}157String[] tokens = line.split("\\s+");158return tokens.length == 3 || tokens.length == 4;159});160}161br.close();162return result;163}164165private Map<String, String> generateHashedPasswordFile() throws IOException {166File file = createNewPasswordFile();167Map<String, String> props = new HashMap<>();168BufferedWriter br;169try (FileWriter fw = new FileWriter(file)) {170br = new BufferedWriter(fw);171int numentries = random.nextInt(5) + 3;172for (int i = 0; i < numentries; i++) {173String username;174do {175username = randomWord();176} while (props.get(username) != null);177String password = randomWord();178String alg = hashAlgs[random.nextInt(hashAlgs.length)];179String[] b64str = getHash(alg, password);180br.write(username + " " + b64str[0] + " " + b64str[1] + " " + alg + "\n");181props.put(username, password);182}183br.flush();184}185br.close();186return props;187}188189private JMXServiceURL createServerSide(boolean useHash)190throws IOException {191MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();192JMXServiceURL url = new JMXServiceURL("rmi", null, 0);193194HashMap<String, Object> env = new HashMap<>();195env.put("jmx.remote.x.password.file", getPasswordFilePath());196env.put("jmx.remote.x.password.toHashes", useHash ? "true" : "false");197cs = JMXConnectorServerFactory.newJMXConnectorServer(url, env, mbs);198cs.start();199return cs.getAddress();200}201202@Test203public void testClearTextPasswordFile() throws IOException {204Boolean[] bvals = new Boolean[]{true, false};205for (boolean bval : bvals) {206try {207Map<String, String> credentials = generateClearTextPasswordFile();208JMXServiceURL serverUrl = createServerSide(bval);209for (Map.Entry<String, String> entry : credentials.entrySet()) {210HashMap<String, Object> env = new HashMap<>();211env.put("jmx.remote.credentials",212new String[]{entry.getKey(), entry.getValue()});213try (JMXConnector cc = JMXConnectorFactory.connect(serverUrl, env)) {214cc.getMBeanServerConnection();215}216}217Assert.assertEquals(isPasswordFileHashed(), bval);218} finally {219cs.stop();220}221}222}223224@Test225public void testReadOnlyPasswordFile() throws IOException {226Boolean[] bvals = new Boolean[]{true, false};227for (boolean bval : bvals) {228try {229Map<String, String> credentials = generateClearTextPasswordFile();230File file = new File(getPasswordFilePath());231file.setReadOnly();232JMXServiceURL serverUrl = createServerSide(bval);233for (Map.Entry<String, String> entry : credentials.entrySet()) {234HashMap<String, Object> env = new HashMap<>();235env.put("jmx.remote.credentials",236new String[]{entry.getKey(), entry.getValue()});237try (JMXConnector cc = JMXConnectorFactory.connect(serverUrl, env)) {238cc.getMBeanServerConnection();239}240}241Assert.assertEquals(isPasswordFileHashed(), false);242} finally {243cs.stop();244}245}246}247248@Test249public void testHashedPasswordFile() throws IOException {250Boolean[] bvals = new Boolean[]{true, false};251for (boolean bval : bvals) {252try {253Map<String, String> credentials = generateHashedPasswordFile();254JMXServiceURL serverUrl = createServerSide(bval);255Assert.assertEquals(isPasswordFileHashed(), true);256for (Map.Entry<String, String> entry : credentials.entrySet()) {257HashMap<String, Object> env = new HashMap<>();258env.put("jmx.remote.credentials",259new String[]{entry.getKey(), entry.getValue()});260try (JMXConnector cc = JMXConnectorFactory.connect(serverUrl, env)) {261cc.getMBeanServerConnection();262}263}264} finally {265cs.stop();266}267}268}269270private static class SimpleJMXClient implements Callable {271private final JMXServiceURL url;272private final Map<String, String> credentials;273274public SimpleJMXClient(JMXServiceURL url, Map<String, String> credentials) {275this.url = url;276this.credentials = credentials;277}278279@Override280public Object call() throws Exception {281for (Map.Entry<String, String> entry : credentials.entrySet()) {282HashMap<String, Object> env = new HashMap<>();283env.put("jmx.remote.credentials",284new String[]{entry.getKey(), entry.getValue()});285try (JMXConnector cc = JMXConnectorFactory.connect(url, env)) {286cc.getMBeanServerConnection();287}288}289return null;290}291}292293@Test294public void testMultipleClients() throws Throwable {295Map<String, String> credentials = generateClearTextPasswordFile();296JMXServiceURL serverUrl = createServerSide(true);297Assert.assertEquals(isPasswordFileHashed(), false);298// create random number of clients299int numClients = random.nextInt(20) + 10;300List<Future> futures = new ArrayList<>();301ExecutorService executor = Executors.newFixedThreadPool(numClients);302for (int i = 0; i < numClients; i++) {303Future future = executor.submit(new SimpleJMXClient(serverUrl, credentials));304futures.add(future);305}306try {307for (Future future : futures) {308future.get();309}310} catch (InterruptedException ex) {311Thread.currentThread().interrupt();312} catch (ExecutionException ex) {313throw ex.getCause();314} finally {315executor.shutdown();316}317318Assert.assertEquals(isPasswordFileHashed(), true);319}320321@Test322public void testPasswordChange() throws IOException {323try {324Map<String, String> credentials = generateClearTextPasswordFile();325JMXServiceURL serverUrl = createServerSide(true);326Assert.assertEquals(isPasswordFileHashed(), false);327328for (Map.Entry<String, String> entry : credentials.entrySet()) {329HashMap<String, Object> env = new HashMap<>();330env.put("jmx.remote.credentials",331new String[]{entry.getKey(), entry.getValue()});332try (JMXConnector cc = JMXConnectorFactory.connect(serverUrl, env)) {333cc.getMBeanServerConnection();334}335}336Assert.assertEquals(isPasswordFileHashed(), true);337338// Read the file back. Add new entries. Change passwords for few339BufferedReader br = new BufferedReader(new FileReader(getPasswordFilePath()));340String line;341StringBuilder sbuild = new StringBuilder();342while ((line = br.readLine()) != null) {343if (line.trim().startsWith("#")) {344sbuild.append(line).append("\n");345continue;346}347348// Change password for random entries349if (random.nextBoolean()) {350String[] tokens = line.split("\\s+");351if ((tokens.length == 4 || tokens.length == 3)) {352String password = randomWord();353credentials.put(tokens[0], password);354sbuild.append(tokens[0]).append(" ").append(password).append("\n");355}356} else {357sbuild.append(line).append("\n");358}359}360361// Add new entries in clear362int newentries = random.nextInt(2) + 3;363for (int i = 0; i < newentries; i++) {364String username;365do {366username = randomWord();367} while (credentials.get(username) != null);368String password = randomWord();369credentials.put(username, password);370sbuild.append(username).append(" ").append(password).append("\n");371}372373// Add new entries as a hash374int numentries = random.nextInt(2) + 3;375for (int i = 0; i < numentries; i++) {376String username;377do {378username = randomWord();379} while (credentials.get(username) != null);380String password = randomWord();381String alg = hashAlgs[random.nextInt(hashAlgs.length)];382String[] b64str = getHash(alg, password);383credentials.put(username, password);384sbuild.append(username).append(" ").append(b64str[0])385.append(" ").append(b64str[1]).append(" ")386.append(alg).append("\n");387}388389try (BufferedWriter bw = new BufferedWriter(new FileWriter(getPasswordFilePath()))) {390bw.write(sbuild.toString());391}392393for (Map.Entry<String, String> entry : credentials.entrySet()) {394HashMap<String, Object> env = new HashMap<>();395env.put("jmx.remote.credentials",396new String[]{entry.getKey(), entry.getValue()});397try (JMXConnector cc = JMXConnectorFactory.connect(serverUrl, env)) {398cc.getMBeanServerConnection();399}400}401} finally {402cs.stop();403}404}405406@Test407public void testDefaultAgent() throws Exception {408List<String> pbArgs = new ArrayList<>();409generateClearTextPasswordFile();410411// This will run only on a POSIX compliant system412if (!FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {413return;414}415416// Make sure only owner is able to read/write the file or else417// default agent will fail to start418File file = new File(getPasswordFilePath());419Set<PosixFilePermission> perms = new HashSet<>();420perms.add(PosixFilePermission.OWNER_READ);421perms.add(PosixFilePermission.OWNER_WRITE);422Files.setPosixFilePermissions(file.toPath(), perms);423424pbArgs.add("-cp");425pbArgs.add(System.getProperty("test.class.path"));426427pbArgs.add("-Dcom.sun.management.jmxremote.port=0");428pbArgs.add("-Dcom.sun.management.jmxremote.authenticate=true");429pbArgs.add("-Dcom.sun.management.jmxremote.password.file=" + file.getAbsolutePath());430pbArgs.add("-Dcom.sun.management.jmxremote.ssl=false");431pbArgs.add("--add-exports");432pbArgs.add("jdk.management.agent/jdk.internal.agent=ALL-UNNAMED");433pbArgs.add(TestApp.class.getSimpleName());434435ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(436pbArgs.toArray(new String[0]));437Process process = ProcessTools.startProcess(438TestApp.class.getSimpleName(),439pb);440441if (process.waitFor() != 0) {442throw new RuntimeException("Test Failed : Error starting default agent");443}444Assert.assertEquals(isPasswordFileHashed(), true);445}446447@Test448public void testDefaultAgentNoHash() throws Exception {449List<String> pbArgs = new ArrayList<>();450generateClearTextPasswordFile();451452// This will run only on a POSIX compliant system453if (!FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) {454return;455}456457// Make sure only owner is able to read/write the file or else458// default agent will fail to start459File file = new File(getPasswordFilePath());460Set<PosixFilePermission> perms = new HashSet<>();461perms.add(PosixFilePermission.OWNER_READ);462perms.add(PosixFilePermission.OWNER_WRITE);463Files.setPosixFilePermissions(file.toPath(), perms);464465pbArgs.add("-cp");466pbArgs.add(System.getProperty("test.class.path"));467468pbArgs.add("-Dcom.sun.management.jmxremote.port=0");469pbArgs.add("-Dcom.sun.management.jmxremote.authenticate=true");470pbArgs.add("-Dcom.sun.management.jmxremote.password.file=" + file.getAbsolutePath());471pbArgs.add("-Dcom.sun.management.jmxremote.password.toHashes=false");472pbArgs.add("-Dcom.sun.management.jmxremote.ssl=false");473pbArgs.add("--add-exports");474pbArgs.add("jdk.management.agent/jdk.internal.agent=ALL-UNNAMED");475pbArgs.add(TestApp.class.getSimpleName());476477ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(478pbArgs.toArray(new String[0]));479Process process = ProcessTools.startProcess(480TestApp.class.getSimpleName(),481pb);482483if (process.waitFor() != 0) {484throw new RuntimeException("Test Failed : Error starting default agent");485}486Assert.assertEquals(isPasswordFileHashed(), false);487}488489@AfterClass490public void cleanUp() {491File file = new File(getPasswordFilePath());492if (file.exists()) {493file.delete();494}495}496}497498class TestApp {499500public static void main(String[] args) throws IOException {501try {502Map<String, String> propsMap = ConnectorAddressLink.importRemoteFrom(0);503String jmxServiceUrl = propsMap.get("sun.management.JMXConnectorServer.0.remoteAddress");504Map<String, Object> env = new HashMap<>(1);505// any dummy credentials will do. We just have to trigger password hashing506env.put("jmx.remote.credentials", new String[]{"a", "a"});507try (JMXConnector cc = JMXConnectorFactory.connect(new JMXServiceURL(jmxServiceUrl), env)) {508cc.getMBeanServerConnection();509}510} catch (SecurityException ex) {511// Catch authentication failure here512}513}514}515516517