View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.river.tool.envcheck.plugins;
19  
20  import org.apache.river.start.SharedActivationGroupDescriptor;
21  import org.apache.river.start.SharedActivatableServiceDescriptor;
22  
23  import org.apache.river.tool.envcheck.AbstractPlugin;
24  import org.apache.river.tool.envcheck.EnvCheck;
25  import org.apache.river.tool.envcheck.Plugin;
26  import org.apache.river.tool.envcheck.Reporter;
27  import org.apache.river.tool.envcheck.Reporter.Message;
28  import org.apache.river.tool.envcheck.SubVMTask;
29  import org.apache.river.tool.envcheck.Util;
30  
31  import java.io.File;
32  import java.io.IOException;
33  import java.net.InetAddress;
34  import java.net.MalformedURLException;
35  import java.net.URL;
36  import java.rmi.activation.ActivationGroup;
37  import java.rmi.activation.ActivationException;
38  import java.security.Policy;
39  import java.util.ArrayList;
40  import java.util.Properties;
41  import java.util.ResourceBundle;
42  
43  import net.jini.config.ConfigurationException;
44  import net.jini.security.policy.DynamicPolicyProvider;
45  
46  //explanations need tuning (warnings vs errors)
47  
48  /**
49   * Checks whether various security oriented configuration files exist and are
50   * accessible.  content verification is done only for the login configuration,
51   * and is limited to verifying that
52   * <code>javax.security.auth.login.Configuration.getConfiguration()</code> can
53   * be called successfully. The <code>-security</code> must be included on the
54   * command line for these checks to be done. The checks performed include:
55   * <ul>
56   * <li>verify that the security provider used is an instance of 
57   *     <code>DynamicPolicyProvider</code>
58   * <li>verify that the property <code>javax.net.ssl.trustStore</code> is defined
59   *     and its value is the name of a readable non-directory file
60   * <li>check whether <code>org.apache.river.discovery.x500.trustStore</code> is
61   *     defined, and if so that its value is the name of a readable non-directory
62   *     file
63   * <li>check whether <code>javax.net.ssl.keyStore</code> is defined, and if
64   *     so that its value is the name of a readable non-directory file
65   * <li>obtain the list of login configuration files which the system will will
66   *     attempt to load (the value of the
67   *     <code>java.security.auth.login.config</code> system property and the
68   *     <code>login.config.url.[n]</code> entries in the security properties, or
69   *     the users <code>.java.login.config</code> file if no other source is
70   *     defined).  Verify that at least one such entry exists. Verify that all
71   *     defined configuration files exists, are readable, and are not
72   *     directories.  Verify that
73   *     <code>javax.security.auth.login.Configuration.getConfiguration()</code>
74   *     can be called successfully.
75   * </ul>
76   * These checks are performed for the command line being analyzed and
77   * for the activation group if one exists.
78   */
79  public class CheckJsseProps extends AbstractPlugin {
80  
81      /** reference to the plugin container */
82      EnvCheck envCheck;
83  
84      /** flag indicating whether to run this plugin */
85      private static boolean doChecks = false;
86  
87      String fileAccessTask = FileAccessCheckTask.class.getName();
88  
89      // inherit javadoc
90      public boolean isPluginOption(String opt) {
91  	if (opt.equals("-security")) {
92  	    doChecks = true;
93  	    return true;
94  	}
95  	return false;
96      }
97  
98      /**
99       * Check the security files for the current VM and for the group
100      * VM if there is a <code>SharedActivationGroupDescriptor</code>.
101      *
102      * @param envCheck a reference to the plugin container
103      */
104     public void run(EnvCheck envCheck) {
105 	if (!doChecks) {
106 	    return;
107 	}
108 	this.envCheck = envCheck;
109 	checkProvider(null);
110 	checkTrustStore(null);
111 	checkDiscoveryStore(null);
112 	checkKeyStore(null);
113 	checkLoginConfigs(null);
114 	SharedActivationGroupDescriptor gd = envCheck.getGroupDescriptor();
115 	if (gd != null) {
116 	    checkProvider(gd);
117 	    checkTrustStore(gd);
118 	    checkDiscoveryStore(gd);
119 	    checkKeyStore(gd);
120 	    checkLoginConfigs(gd);
121 	}
122     }
123 
124     /**
125      * Get the source string identifying the activation group (if 
126      * <code>gd</code> is not <code>null</code>) or the command line
127      * (if <code>gd</code> is <code>null</code>).
128      *
129      * @param gd the group descriptor
130      * @return the source text
131      */
132     private String getSource(SharedActivationGroupDescriptor gd) {
133 	return gd == null ? getString("cmdlineVM") : getString("groupVM");
134     }
135 
136     /**
137      * Return a string array representing the given arguments.
138      *
139      * @param s1 first array object
140      * @param s2 second array object
141      * @return the array
142      */
143     private String[] args(String s1, String s2) {
144 	return new String[]{s1, s2};
145     }
146 
147     /**
148      * Check the validity of the trust store definition for the command line
149      * or group.
150      * 
151      * @param gd the group descriptor, or <code>null</code> to test the 
152      *        command line
153      */
154     private void checkTrustStore(SharedActivationGroupDescriptor gd) {
155 	String source = getSource(gd);
156 	String name = "javax.net.ssl.trustStore"; // the property name
157 	String phrase =  getString("truststore"); // brief description
158 	if (checkExistance(gd, name, phrase, source)) {
159 	    Message message;
160 	    Object lobj = 
161 		envCheck.launch(fileAccessTask, args(name, phrase));
162 	    if (lobj == null) {
163 		message = new Message(Reporter.INFO,
164 				      getString("truststoreOK"),
165 				      getString("configExp", phrase, name));
166 	    } else if (lobj instanceof String) {
167 		message = new Message(Reporter.ERROR,
168 				      (String) lobj,
169 				      getString("configExp", phrase, name));
170 	    } else {
171 		message = new Message(Reporter.ERROR,
172 				      getString("accessexception", 
173 						phrase,
174 						name),
175 				      (Throwable) lobj,
176 				      getString("configExp", phrase, name));
177 	    }
178 	    Reporter.print(message, source);
179 	}
180     }
181 
182     /**
183      * Check the validity of the discovery trust store definition for the
184      * command line or group.
185      * 
186      * @param gd the group descriptor, or <code>null</code> to test the 
187      *        command line
188      */
189     private void checkDiscoveryStore(SharedActivationGroupDescriptor gd) {
190 	String source = getSource(gd);
191 	String name = "org.apache.river.discovery.x500.trustStore";
192 	String phrase = getString("discoverystore");
193 	if (checkExistance(gd, name, phrase, source)) {
194 	    Message message;
195 	    Object lobj = 
196 		envCheck.launch(null, gd, fileAccessTask, args(name, phrase));
197 	    if (lobj == null) {
198 		message = new Message(Reporter.INFO,
199 				      getString("discoverystoreOK"),
200 				      getString("dsExp"));
201 	    } else if (lobj instanceof String) {
202 		message = new Message(Reporter.ERROR,
203 				      (String) lobj,
204 				      getString("dsExp"));
205 	    } else {
206 		message = new Message(Reporter.ERROR,
207 				      getString("accessexception", 
208 						phrase,
209 						name),
210 				      (Throwable) lobj,
211 				      getString("dsExp"));
212 	    }
213 	    Reporter.print(message, source);
214 	}
215     }
216 
217     /**
218      * Check the validity of the key store definition for the command line
219      * or group.
220      * 
221      * @param gd the group descriptor, or <code>null</code> to test the 
222      *        command line
223      */
224     private void checkKeyStore(SharedActivationGroupDescriptor gd) {
225 	String source = getSource(gd);
226 	String name = "javax.net.ssl.keyStore";
227 	String phrase = getString("keystore");
228 	if (checkExistance(gd, name, phrase, source)) {
229 	    Message message;
230 	    Object lobj = 
231 		envCheck.launch(null, gd, fileAccessTask, args(name, phrase));
232 	    if (lobj == null) {
233 		message = new Message(Reporter.INFO,
234 				      getString("keystoreOK"),
235 				      getString("configExp", phrase, name));
236 	    } else if (lobj instanceof String) {
237 		message = new Message(Reporter.ERROR,
238 				      (String) lobj,
239 				      getString("configExp", phrase, name));
240 	    } else {
241 		message = new Message(Reporter.ERROR,
242 				      getString("accessexception", 
243 						phrase,
244 						name),
245 				      (Throwable) lobj,
246 				      getString("configExp", phrase, name));
247 	    }
248 	    Reporter.print(message, source);
249 	}
250     }
251 
252     /**
253      * Check the validity of the login configuration for the command line
254      * or group.
255      * 
256      * @param gd the group descriptor, or <code>null</code> to test the 
257      *        command line
258      */
259     private void checkLoginConfigs(SharedActivationGroupDescriptor gd) {
260 	String source = getSource(gd);
261 	Object lobj = 
262 	    envCheck.launch(null, gd, taskName("GetGroupLoginConfigs"));
263 	if (lobj instanceof Throwable) {
264 	    handleUnexpectedSubtaskReturn(lobj, source);
265 	    return;
266 	}
267 	Message message;
268 	ArrayList configs = (ArrayList) lobj;
269 	if (configs.size() == 0) {
270 	    message = new Message(Reporter.WARNING,
271 				  getString("noconfigfiles"),
272 				  getString("loginConfigExp"));
273 	    Reporter.print(message, source);
274 	}
275 	for (int i = 0; i < configs.size(); i += 2) {
276 	    String errorMsg;
277 	    String desc = (String) configs.get(i + 1);
278 	    if (configs.get(i) instanceof URL) {
279 		URL url = (URL) configs.get(i);
280 		errorMsg = Util.checkURL(url, desc);
281 	    } else {
282 		errorMsg = (String) configs.get(i);
283 	    }
284 	    if (errorMsg == null) {
285 		message = new Message(Reporter.INFO,
286 				      getString("loginconfigOK"),
287 				      getString("loginConfigExp"));
288 	    } else {
289 		message = new Message(Reporter.ERROR,
290 				      errorMsg,
291 				      getString("loginConfigExp"));
292 	    }
293 	    Reporter.print(message, source + " " + desc);
294 	}
295 	lobj = envCheck.launch(null, gd, taskName("CheckLoginConfigInit"));
296 	if (lobj == null) {
297 	    message = new Message(Reporter.INFO,
298 				  getString("loginInitOK"),
299 				  getString("loginConfigExp"));
300 	    Reporter.print(message, source);
301 	} else {
302 	    Throwable cause = ((Throwable) lobj).getCause();
303 	    if (cause instanceof IOException) {
304 		message = new Message(Reporter.INFO,
305 				      getString("loginInitBad"),
306 				      cause,
307 				      getString("loginConfigExp"));
308 		Reporter.print(message, source);
309 	    } else {  // unexpected exception
310 		handleUnexpectedSubtaskReturn(lobj, source);
311 	    }
312 	}
313     }
314 
315     /** 
316      * Get the names of the login configuration files which will be accessed
317      * when the login configuration is constructed. If
318      * <code>java.security.auth.login.config</code> is defined with a '==', then
319      * it's value is the sole configuration file.  Otherwise, search the
320      * security properties for property names of the form
321      * <code>login.config.url.[n]</code>, starting with <code>n</code> of one
322      * until there is a break in the sequence. Merge the resulting list with the
323      * value of <code>java.security.auth.login.config</code> if it was defined
324      * (with a single '='). If the resulting list is not empty, return it;
325      * otherwise, check for the existence of a file named
326      * <code>.java.login.config</code> in the users home directory. If found,
327      * place this value in the list.
328      * 
329      * @return the list of login configuration files which will apply to
330      *         the calling VM, or an empty list if there are not such files.
331      */
332     private static ArrayList getLoginConfigs() {
333 	ResourceBundle bundle = Util.getResourceBundle(CheckJsseProps.class);
334 	ArrayList list = new ArrayList();
335 	String source;
336 	boolean override = false;
337 	String propDefined = 
338 	    System.getProperty("java.security.auth.login.config");
339 	if (propDefined != null) {
340 	    source = Util.getString("fromProp", bundle);
341 	    if (propDefined.indexOf('=') == 0) {
342 		override = true;
343 		propDefined = propDefined.substring(1);
344 		source += " " + Util.getString("withOverride", bundle);
345 	    }
346 	    try {
347 		list.add(new URL("file:" + propDefined));
348 	    } catch (MalformedURLException e) {
349 		list.add(Util.getString("badname", bundle, propDefined));
350 	    }
351 	    list.add(source);
352 	    if (override) {
353 		return list;
354 	    }
355 	}
356 	int n = 1;
357         String config_url;
358         while ((config_url = java.security.Security.getProperty
359                                         ("login.config.url." + n)) != null) {
360 	    try {
361 		list.add(new URL(config_url));
362 	    } catch (MalformedURLException e) {
363 		list.add(Util.getString("malformedurl", bundle, config_url));
364 	    }
365 	    list.add(Util.getString("secprop", bundle, "login.config.url." + n));
366 	    n++;
367 	}
368 	if (list.size() == 0) {
369 	    String userFile = System.getProperty("user.home");
370 	    userFile += File.separator + ".java.login.config";
371 	    if (new File(userFile).exists()) {
372 		try {
373 		    list.add(new URL("file:" + userFile));
374 		    list.add(Util.getString("userfile", bundle));
375 		} catch (MalformedURLException e) { // should never happen
376 		    e.printStackTrace();
377 		}
378 	    }
379 	}
380 	return list;
381     }
382 
383     /**
384      * Check the existence of a property definition in the group or
385      * command line.
386      *
387      * @param gd the group descriptor, or <code>null</code> to check
388      *           the command line
389      * @param propName the property name to check for
390      * @param desc phrase describing the property
391      * @param source the source descriptive text
392      * @return <code>true</code> if the property is defined
393      */
394     private boolean checkExistance(SharedActivationGroupDescriptor gd,
395 				   String propName, 
396 				   String desc, 
397 				   String source) {
398 	
399 	Properties p = (gd == null ? System.getProperties()
400 			           : gd.getServerProperties());
401 	if (p == null || p.getProperty(propName) == null) {
402 	    Message message = 
403 		new Message(Reporter.WARNING,
404 			    getString("noprop", propName, desc),
405 			    getString("configExp", desc, propName));
406 	    Reporter.print(message, source);
407 	    return false;
408 	}
409 	return true;
410     }
411 
412     /**
413      * Check that the security provider is an instance of
414      * <code>DynamicPolicyProvider</code>. Done for the tool VM and for the
415      * group VM if a <code>SharedActivationGroupDescriptor</code> exists.
416      */
417     private void checkProvider(SharedActivationGroupDescriptor gd) {
418 	String source = getSource(gd);
419 	Object lobj = envCheck.launch(null, gd, taskName("CheckProviderTask"));
420 	if (lobj instanceof Boolean) {
421 	    Message message;
422 	    if (((Boolean) lobj).booleanValue()) {
423 		message = new Message(Reporter.INFO,
424 				      getString("providerOK"),
425 				      getString("providerExp"));
426 	    } else {
427 		message = new Message(Reporter.WARNING,
428 				      getString("providerBad"),
429 				      getString("providerExp"));
430 	    }
431 	    Reporter.print(message, source);
432 	} else {
433 	    handleUnexpectedSubtaskReturn(lobj, source);
434 	}
435     }
436 
437    /**
438      * Checks the existence and accessibility of the login configuration.
439      */
440     public static class CheckLoginConfigInit implements SubVMTask {
441 
442 	public Object run(String[] args) {
443 	    try {
444 		javax.security.auth.login.Configuration.getConfiguration();
445 		return null;
446 	    } catch (SecurityException e) {
447 		return e;
448 	    }
449 	}
450 
451     }
452 
453     /**
454      * Checks the policy provider of the group.
455      */
456     public static class CheckProviderTask implements SubVMTask {
457 
458 	public Object run(String[] args) {
459 	    try {
460 		if (Policy.getPolicy() instanceof DynamicPolicyProvider) {
461 		    return new Boolean(true);
462 		} else {
463 		    return new Boolean(false);
464 		}
465 	    } catch (SecurityException e) {
466 		return e;
467 	    }
468 	}
469 
470     }
471 
472     /**
473      * Gets login configuration urls of the group.
474      */
475     public static class GetGroupLoginConfigs implements SubVMTask {
476 
477 	public Object run(String[] args) {
478 	    return getLoginConfigs();
479 	}
480 
481     }
482 }