1/*
2 * Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23import sun.management.jmxremote.ConnectorBootstrap;
24
25import java.io.File;
26import java.io.FileInputStream;
27import java.io.InputStream;
28import java.io.FilenameFilter;
29import java.io.IOException;
30
31import java.security.GeneralSecurityException;
32import java.security.KeyStore;
33
34import java.util.Properties;
35import java.util.Iterator;
36import java.util.Set;
37import java.util.Arrays;
38import java.util.ArrayList;
39import java.util.HashMap;
40import java.util.Map;
41import java.util.Enumeration;
42
43import javax.management.remote.*;
44import javax.management.*;
45
46import jdk.internal.agent.AgentConfigurationError;
47
48/**
49 * <p>This class implements unit test for RMI Bootstrap.
50 * When called with no arguments main() looks in the directory indicated
51 * by the "test.src" system property for files called management*ok.properties
52 * or management*ko.properties. The *ok.properties files are assumed to be
53 * valid Java M&M config files for which the bootstrap should succeed.
54 * The *ko.properties files are assumed to be configurations for which the
55 * bootstrap & connection test will fail.</p>
56 *
57 * <p>The rmi port number can be specified with the "rmi.port" system property.
58 * If not, this test will use 12424</p>
59 *
60 * <p>When called with some argument, the main() will interprete its args to
61 * be Java M&M configuration file names. The filenames are expected to end
62 * with ok.properties or ko.properties - and are interpreted as above.</p>
63 *
64 * <p>Note that a limitation of the RMI registry (bug 4267864) prevent
65 * this test from succeeding if more than 1 configuration is used.
66 * As long as 4267864 isn't fix, this test must be called as many times
67 * as needed but with a single argument (no arguments, or several arguments
68 * will fail).</p>
69 *
70 * <p>Debug traces are logged in "sun.management.test"</p>
71 **/
72public class RmiSslNoKeyStoreTest {
73
74    static TestLogger log =
75        new TestLogger("RmiSslNoKeyStoreTest");
76
77    /**
78     * When launching several registries, we increment the port number
79     * to avoid falling into "port number already in use" problems.
80     **/
81    static int testPort = 0;
82
83    /**
84     * Default values for RMI configuration properties.
85     **/
86    public static interface DefaultValues {
87        public static final String PORT="0";
88        public static final String CONFIG_FILE_NAME="management.properties";
89        public static final String USE_SSL="true";
90        public static final String USE_AUTHENTICATION="true";
91        public static final String PASSWORD_FILE_NAME="jmxremote.password";
92        public static final String ACCESS_FILE_NAME="jmxremote.access";
93        public static final String KEYSTORE="keystore";
94        public static final String KEYSTORE_PASSWD="password";
95        public static final String TRUSTSTORE="truststore";
96        public static final String TRUSTSTORE_PASSWD="trustword";
97    }
98
99    /**
100     * Names of RMI configuration properties.
101     **/
102    public static interface PropertyNames {
103        public static final String PORT="com.sun.management.jmxremote.port";
104        public static final String CONFIG_FILE_NAME=
105            "com.sun.management.config.file";
106        public static final String USE_SSL="com.sun.management.jmxremote.ssl";
107        public static final String USE_AUTHENTICATION=
108            "com.sun.management.jmxremote.authenticate";
109        public static final String PASSWORD_FILE_NAME=
110            "com.sun.management.jmxremote.password.file";
111        public static final String ACCESS_FILE_NAME=
112            "com.sun.management.jmxremote.access.file";
113        public static final String INSTRUMENT_ALL=
114            "com.sun.management.instrumentall";
115        public static final String CREDENTIALS =
116            "jmx.remote.credentials";
117        public static final String KEYSTORE="javax.net.ssl.keyStore";
118        public static final String KEYSTORE_PASSWD=
119            "javax.net.ssl.keyStorePassword";
120        public static final String KEYSTORE_TYPE="javax.net.ssl.keyStoreType";
121        public static final String TRUSTSTORE="javax.net.ssl.trustStore";
122        public static final String TRUSTSTORE_PASSWD=
123            "javax.net.ssl.trustStorePassword";
124    }
125
126    /**
127     * Compute the full path name for a default file.
128     * @param basename basename (with extension) of the default file.
129     * @return ${JRE}/conf/management/${basename}
130     **/
131    private static String getDefaultFileName(String basename) {
132        final String fileSeparator = File.separator;
133        final StringBuffer defaultFileName =
134            new StringBuffer(System.getProperty("java.home")).
135            append(fileSeparator).append("conf").append(fileSeparator).
136            append("management").append(fileSeparator).
137            append(basename);
138        return defaultFileName.toString();
139    }
140
141    /**
142     * Compute the full path name for a default file.
143     * @param basename basename (with extension) of the default file.
144     * @return ${JRE}/conf/management/${basename}
145     **/
146    private static String getDefaultStoreName(String basename) {
147        final String fileSeparator = File.separator;
148        final StringBuffer defaultFileName =
149            new StringBuffer(System.getProperty("test.src")).
150            append(fileSeparator).append("ssl").append(fileSeparator).
151            append(basename);
152        return defaultFileName.toString();
153    }
154
155    private static void checkKeystore(Properties props)
156        throws IOException, GeneralSecurityException {
157        if (log.isDebugOn())
158            log.debug("checkKeystore","Checking Keystore configuration");
159
160        final String keyStore =
161            System.getProperty(PropertyNames.KEYSTORE);
162        if (keyStore == null)
163            throw new IllegalArgumentException("System property " +
164                                               PropertyNames.KEYSTORE +
165                                               " not specified");
166
167        final String keyStorePass =
168            System.getProperty(PropertyNames.KEYSTORE_PASSWD);
169        if (keyStorePass == null) {
170            // We don't have the password, we can only check whether the
171            // file exists...
172            //
173            final File ksf = new File(keyStore);
174            if (! ksf.canRead())
175                throw new IOException(keyStore + ": not readable");
176
177            if (log.isDebugOn())
178                log.debug("checkSSL", "No password.");
179            throw new IllegalArgumentException("System property " +
180                                               PropertyNames.KEYSTORE_PASSWD +
181                                               " not specified");
182        }
183
184        // Now we're going to load the keyStore - just to check it's
185        // correct.
186        //
187        final String keyStoreType =
188            System.getProperty(PropertyNames.KEYSTORE_TYPE,
189                               KeyStore.getDefaultType());
190        final KeyStore ks         = KeyStore.getInstance(keyStoreType);
191        final FileInputStream fin = new FileInputStream(keyStore);
192        final char keypassword[]  = keyStorePass.toCharArray();
193
194        try {
195            ks.load(fin,keypassword);
196        } finally {
197            Arrays.fill(keypassword,' ');
198            fin.close();
199        }
200
201        if (log.isDebugOn())
202            log.debug("checkSSL","SSL configuration successfully checked");
203    }
204
205    private void checkSslConfiguration() throws Exception {
206        final String defaultConf =
207            getDefaultFileName(DefaultValues.CONFIG_FILE_NAME);
208        final String confname =
209            System.getProperty(PropertyNames.CONFIG_FILE_NAME,defaultConf);
210
211        final Properties props = new Properties();
212        final File conf = new File(confname);
213        if (conf.exists()) {
214            FileInputStream fin = new FileInputStream(conf);
215            try {props.load(fin);} finally {fin.close();}
216        }
217
218        // Do we use SSL?
219        final String  useSslStr =
220            props.getProperty(PropertyNames.USE_SSL,
221                              DefaultValues.USE_SSL);
222        final boolean useSsl =
223            Boolean.valueOf(useSslStr).booleanValue();
224
225        log.debug("checkSslConfiguration",PropertyNames.USE_SSL+"="+useSsl);
226        if (useSsl == false) {
227            final String msg =
228                PropertyNames.USE_SSL+"="+useSsl+", can't run test";
229            throw new IllegalArgumentException(msg);
230        }
231
232        try {
233            checkKeystore(props);
234        } catch (Exception x) {
235            // Ok!
236            log.debug("checkSslConfiguration","Test configuration OK: " + x);
237            return;
238        }
239
240        final String msg = "KeyStore properly configured, can't run test";
241        throw new IllegalArgumentException(msg);
242    }
243
244    /**
245     * Test the configuration indicated by `file'.
246     * Sets the appropriate System properties for config file and
247     * port and then calls ConnectorBootstrap.initialize().
248     * eventually cleans up by calling ConnectorBootstrap.terminate().
249     * @return null if the test succeeds, an error message otherwise.
250     **/
251    private String testConfiguration(File file,int port) {
252
253        final String path = (file==null)?null:file.getAbsolutePath();
254        final String config = (path==null)?"Default config file":path;
255
256        try {
257            System.out.println("***");
258            System.out.println("*** Testing configuration (port="+
259                               port + "): "+ path);
260            System.out.println("***");
261
262            System.setProperty("com.sun.management.jmxremote.port",
263                               Integer.toString(port));
264            if (path != null)
265                System.setProperty("com.sun.management.config.file", path);
266            else
267                System.getProperties().
268                    remove("com.sun.management.config.file");
269
270            log.trace("testConfiguration","com.sun.management.jmxremote.port="+port);
271            if (path != null && log.isDebugOn())
272                log.trace("testConfiguration",
273                          "com.sun.management.config.file="+path);
274
275            checkSslConfiguration();
276
277            final JMXConnectorServer cs;
278            try {
279                cs = ConnectorBootstrap.initialize();
280            } catch (AgentConfigurationError x) {
281                final String err = "Failed to initialize connector:" +
282                    "\n\tcom.sun.management.jmxremote.port=" + port +
283                    ((path!=null)?"\n\tcom.sun.management.config.file="+path:
284                     "\n\t"+config) +
285                    "\n\tError is: " + x;
286
287                log.trace("testConfiguration","Expected failure: " + err);
288                log.debug("testConfiguration",x);
289                System.out.println("Got expected failure: " + x);
290                return null;
291            } catch (Exception x) {
292                log.debug("testConfiguration",x);
293                return x.toString();
294            }
295            try {
296                JMXConnector cc =
297                    JMXConnectorFactory.connect(cs.getAddress(), null);
298                cc.close();
299            } catch (IOException x) {
300                final String err = "Failed to initialize connector:" +
301                    "\n\tcom.sun.management.jmxremote.port=" + port +
302                    ((path!=null)?"\n\tcom.sun.management.config.file="+path:
303                     "\n\t"+config) +
304                    "\n\tError is: " + x;
305
306                log.trace("testConfiguration","Expected failure: " + err);
307                log.debug("testConfiguration",x);
308                System.out.println("Got expected failure: " + x);
309                return null;
310            } catch (Exception x) {
311                log.debug("testConfiguration",x);
312                return x.toString();
313            }
314            try {
315                cs.stop();
316            } catch (Exception x) {
317                final String err = "Failed to terminate: "+x;
318                log.trace("testConfiguration",err);
319                log.debug("testConfiguration",x);
320            }
321            final String err = "Bootstrap should have failed:" +
322                "\n\tcom.sun.management.jmxremote.port=" + port +
323                ((path!=null)?"\n\tcom.sun.management.config.file="+path:
324                 "\n\t"+config);
325            log.trace("testConfiguration",err);
326            return err;
327        } catch (Exception x) {
328            final String err = "Failed to test bootstrap for:" +
329                "\n\tcom.sun.management.jmxremote.port=" + port +
330                ((path!=null)?"\n\tcom.sun.management.config.file="+path:
331                 "\n\t"+config)+
332                "\n\tError is: " + x;
333
334            log.trace("testConfiguration",err);
335            log.debug("testConfiguration",x);
336            return err;
337        }
338    }
339
340    /**
341     * Test a configuration file. Determines whether the bootstrap
342     * should succeed or fail depending on the file name:
343     *     *ok.properties: bootstrap should succeed.
344     *     *ko.properties: bootstrap or connection should fail.
345     * @return null if the test succeeds, an error message otherwise.
346     **/
347    private String testConfigurationFile(String fileName) {
348        File file = new File(fileName);
349        final String portStr = System.getProperty("rmi.port","12424");
350        final int port       = Integer.parseInt(portStr);
351
352        return testConfiguration(file,port+testPort++);
353    }
354
355
356    /**
357     * Tests the specified configuration files.
358     * If args[] is not empty, each element in args[] is expected to be
359     * a filename ending either by ok.properties or ko.properties.
360     * Otherwise, the configuration files will be automatically determined
361     * by looking at all *.properties files located in the directory
362     * indicated by the System property "test.src".
363     * @throws RuntimeException if the test fails.
364     **/
365    public void run(String args[]) {
366        final String defaultKeyStore =
367            getDefaultStoreName(DefaultValues.KEYSTORE);
368        final String keyStore =
369            System.getProperty(PropertyNames.KEYSTORE, defaultKeyStore);
370
371        for (int i=0; i<args.length; i++) {
372
373            String errStr =testConfigurationFile(args[i]);
374            if (errStr != null) {
375                throw new RuntimeException(errStr);
376            }
377
378            if ((System.getProperty(PropertyNames.KEYSTORE) == null) &&
379                (System.getProperty(PropertyNames.KEYSTORE_PASSWD) == null)) {
380                try {
381
382                    // Specify the keystore, but don't specify the
383                    // password.
384                    //
385                    System.setProperty(PropertyNames.KEYSTORE,keyStore);
386                    log.trace("run",PropertyNames.KEYSTORE+"="+keyStore);
387
388                    errStr =testConfigurationFile(args[i]);
389                    if (errStr != null) {
390                        throw new RuntimeException(errStr);
391                    }
392                } finally {
393                    System.getProperties().remove(PropertyNames.KEYSTORE);
394                }
395            }
396        }
397    }
398
399    /**
400     * Calls run(args[]).
401     * exit(1) if the test fails.
402     **/
403    public static void main(String args[]) {
404        RmiSslNoKeyStoreTest manager = new RmiSslNoKeyStoreTest();
405        try {
406            manager.run(args);
407        } catch (RuntimeException r) {
408            System.err.println("Test Failed: "+ r.getMessage());
409            System.exit(1);
410        } catch (Throwable t) {
411            System.err.println("Test Failed: "+ t);
412            t.printStackTrace();
413            System.exit(2);
414        }
415        System.out.println("**** Test RmiSslNoKeyStoreTest Passed ****");
416    }
417
418}
419