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  
19  package org.apache.river.tool;
20  
21  import java.io.BufferedWriter;
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FilePermission;
25  import java.io.FileWriter;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.PrintWriter;
29  import java.net.MalformedURLException;
30  import java.net.URI;
31  import java.net.URISyntaxException;
32  import java.net.URL;
33  import java.security.AccessController;
34  import java.security.CodeSource;
35  import java.security.GeneralSecurityException;
36  import java.security.KeyStore;
37  import java.security.KeyStoreException;
38  import java.security.Permission;
39  import java.security.Permissions;
40  import java.security.Policy;
41  import java.security.Principal;
42  import java.security.ProtectionDomain;
43  import java.security.cert.Certificate;
44  import java.util.ArrayList;
45  import java.util.Collection;
46  import java.util.Iterator;
47  import java.util.List;
48  import java.util.Map.Entry;
49  import java.util.concurrent.ConcurrentHashMap;
50  import java.util.concurrent.ConcurrentMap;
51  import java.util.concurrent.ConcurrentSkipListSet;
52  import java.util.function.Function;
53  import java.util.logging.Level;
54  import java.util.logging.Logger;
55  import javax.security.auth.PrivateCredentialPermission;
56  import org.apache.river.action.GetBooleanAction;
57  import org.apache.river.api.net.Uri;
58  import org.apache.river.api.security.CombinerSecurityManager;
59  import org.apache.river.api.security.PermissionComparator;
60  
61  /**
62   * This SecurityManager can be used in a simulation environment to generate
63   * policy files that conform to the principle of least privilege.
64   * <p>
65   * It should be installed from the command line using the following system property:<br>
66   * <code>-Djava.security.manager=org.apache.river.tool.SecurityPolicyWriter</code>
67   * <p>
68   * The policy file generated will be specific to the runtime where it is generated.
69   * Users should edit the files after generation if they wish to deploy the policy
70   * else where, replacing file path characters etc.
71   * <p>
72   * Note that the file generated will not contain the <code>ProtectionDomain</code>
73   * of the jar file containing this SecurityManager, neither will it contain
74   * Java platform jars.
75   * <p>
76   * 
77   * The following properties should be set to obtain CodeSource signer Certificate Aliases.<br>
78   * <table><caption>System Properties</caption>
79   * <tr>
80   * <td>System Property</td>
81   * <td>Explanation</td>
82   * </tr><tr>
83   * <td>org.apache.river.tool.SecurityPolicyWriter.Directory</td>
84   * <td>Directory to create policy files in.</td>
85   * </tr><tr>
86   * <td>javax.net.ssl.trustStore</td>
87   * <td>The location of the KeyStore,  if no location is specified, then the 
88   * cacerts file in the lib/security subdirectory of the JDK installation 
89   * directory is used. If specified, the location is treated as a URL.
90   * If no protocol is specified in the URL or it is an unknown protocol, 
91   * then, the location is treated as a file name.</td>
92   * </tr><tr>
93   * <td>javax.net.ssl.trustStoreType</td>
94   * <td>The KeyStore type, If no keystore type is specified, then the type 
95   * returned by KeyStore.getDefaultType() is used.</td>
96   * </tr><tr>
97   * <td>javax.net.ssl.trustStorePassword</td>
98   * <td>The KeyStore password, if no password is specified, then no password
99   * is used when loading the keystore.</td>
100  * </tr>
101  * </table>
102  * 
103  * @see KeyStore
104  * @author Peter Firmstone
105  * @since 3.0.0
106  */
107 public class SecurityPolicyWriter extends CombinerSecurityManager{
108     
109     private static Logger logger;
110     private static final Object loggerLock = new Object();
111 
112     /**
113      * @return the logger
114      */
115     private static Logger getLogger() {
116         synchronized (loggerLock){
117             if (logger != null) return logger;
118             logger = 
119             Logger.getLogger("org.apache.river.tool.SecurityPolicyWriter");
120             return logger;
121         }
122     }
123     // Use concurrent collections to avoid deadlock from recursive calls.
124     private final ConcurrentMap<ProtectionDomain, Collection<Permission>> domainPermissions;
125     private static KeyStore keyStore = null;
126     private static final Object keyStoreLock = new Object();
127     private final ConcurrentMap<Certificate,String> aliases;
128     private final Cert certFunc;
129 //    private final Map properties;
130     
131     private SecurityPolicyWriter(
132 	    ConcurrentMap<ProtectionDomain,Collection<Permission>> map) 
133     {
134         super();
135         domainPermissions = map;
136         aliases = new ConcurrentHashMap<Certificate,String>();
137         certFunc = new Cert();
138 //	Properties properties = System.getProperties();
139 //	Map<String,String> props = new HashMap<String,String>();
140 //	synchronized (properties){
141 //	    Iterator<Entry<Object,Object>> it = properties.entrySet().iterator();
142 //	    StringBuilder jini = new StringBuilder("net.jini");
143 //	    StringBuilder river = new StringBuilder("org.apache.river");
144 //	    while (it.hasNext()){
145 //		Entry<Object,Object> ent = it.next();
146 //		Object k = ent.getKey();
147 //		Object v = ent.getValue();
148 //		if (k instanceof String && v instanceof String 
149 //			&& (((String)k).contains(jini) || ((String)k).contains(river)))
150 //		{
151 //		    props.put((String) k, (String) v);
152 //		}
153 //	    }
154 //	}
155 //	this.properties = props;
156     } 
157     
158     public SecurityPolicyWriter() {
159         this(new ConcurrentHashMap<ProtectionDomain,Collection<Permission>>());
160         Runtime.getRuntime().addShutdownHook(shutdownHook());
161     }
162     
163     private static File policyFile() throws URISyntaxException{
164         String policy = System.getProperty("java.security.policy");
165 //        Uuid timestamp = UuidFactory.generate();
166 //        File policyFile = new File(policyDirectory + File.separator + timestamp + ".policy");
167 	Uri polLocation = new Uri(policy +".new");
168 	URI poliLoc = Uri.uriToURI(polLocation);
169 	File policyFile = new File(poliLoc);
170 	if (!policyFile.exists()){
171 	    try {
172 		policyFile.createNewFile();
173 	    } catch (IOException ex) {
174 		throw new RuntimeException("Unable to create a policy file: "+ policy +".new", ex);
175 	    }
176 	}
177         return policyFile;
178     }
179     
180     private static KeyStore keyStore(){
181 	synchronized (keyStoreLock){
182 	    if (keyStore == null){
183 		try {
184 		    keyStore = initStore();
185 		} catch (IOException ex) {
186 		    getLogger().log(Level.FINE, "Unable to create KeyStore instance", ex);
187 		} catch (GeneralSecurityException ex) {
188 		    getLogger().log(Level.FINE, "Unable to create KeyStore instance", ex);
189 		}
190 	    }
191 	    return keyStore;
192 	}
193     }
194     
195     /**
196      * Initializes trust store and cert stores based on system property values.
197      */
198     private static KeyStore initStore() throws IOException, GeneralSecurityException {
199 	String path, type, passwd;
200 	if ((path = System.getProperty("javax.net.ssl.trustStore")) != null) {
201 	    type = System.getProperty(
202 		"javax.net.ssl.trustStoreType", KeyStore.getDefaultType());
203 	    passwd = System.getProperty("javax.net.ssl.trustStorePassword");
204 	} else {
205 	    path = System.getProperty("java.home") + "/lib/security/cacerts";
206 	    type = KeyStore.getDefaultType();
207 	    passwd = null;
208 	}
209 	KeyStore kstore = KeyStore.getInstance(type);
210 	InputStream in;
211 	URL url = null;
212 	try {
213 	    url = new Uri(path).toURL();
214 	} catch (MalformedURLException e) {
215 	    getLogger().log(Level.SEVERE, null, e);
216 	} catch (URISyntaxException ex) {
217 	    getLogger().log(Level.SEVERE, null, ex);
218 	}
219 	if (url != null) {
220 	    in = url.openStream();
221 	} else {
222 	    in = new FileInputStream(path);
223 	}
224 	try {
225 	    kstore.load(in, (passwd != null) ? passwd.toCharArray() : null);
226 	} finally {
227 	    in.close();
228 	}
229 //	if (getLogger().isLoggable(Level.FINEST)) {
230 //	    getLogger().log(Level.FINEST, "loaded trust store from {0} ({1})",
231 //		       new Object[]{ path, type });
232 //	}
233         return kstore;
234     }
235     
236     @Override
237     protected boolean checkPermission(ProtectionDomain pd, Permission p){
238 	Collection<Permission> perms = domainPermissions.get(pd);
239 	    if (perms == null) {
240 		perms = new ConcurrentSkipListSet<Permission>(new PermissionComparator());
241 		Collection<Permission> existed = domainPermissions.putIfAbsent(pd, perms);
242 		if (existed != null) perms = existed;
243 	    }
244 	perms.add(p);
245         return true;
246     }
247     
248     private Thread shutdownHook(){
249         Thread t = new Thread ( new Runnable(){
250             @Override
251             public void run (){
252                 PrintWriter pw = null;
253 		boolean onlyAdditional = AccessController.doPrivileged(new GetBooleanAction("org.apache.river.tool.addPerms"));
254 		Policy policy = Policy.getPolicy();
255                 try {
256 		    File policyFile = policyFile();
257 //		    policy = new ConcurrentPolicyFile(new URL[]{policyFile.toURI().toURL()});
258 		    pw = new PrintWriter(new BufferedWriter(new FileWriter(policyFile, true)));
259                 } catch (IOException ex) {
260                     getLogger().log(Level.SEVERE, "unable to write to policy file ", ex);
261                     return;
262                 } catch (URISyntaxException ex) {
263 		    getLogger().log(Level.SEVERE, "unable to write to policy file ", ex);
264 		    return;
265 		} 
266 //		catch (PolicyInitializationException ex) {
267 //		    getLogger().log(Level.SEVERE, "unable to parse to policy file ", ex);
268 //		    return;
269 //		}
270                 //REMIND: keystore "some_keystore_url", "keystore_type", "keystore_provider";
271                 //        keystorePasswordURL "some_password_url";
272                 
273 //                grant signedBy "signer_names", codeBase "URL",
274 //                    principal principal_class_name "principal_name",
275 //                    principal principal_class_name "principal_name",
276 //                    ... {
277 //
278 //                      permission permission_class_name "target_name", "action", 
279 //                          signedBy "signer_names";
280 //                      permission permission_class_name "target_name", "action", 
281 //                          signedBy "signer_names";
282 //                      ...
283 //                  };
284 
285                 
286                 Iterator<Entry<ProtectionDomain,Collection<Permission>>> it 
287                         = domainPermissions.entrySet().iterator();
288                 while (it.hasNext()){
289                     Entry<ProtectionDomain,Collection<Permission>> entry = it.next();
290                     ProtectionDomain pd = entry.getKey();
291 		    Collection<Permission> perms = entry.getValue();
292 		    if (onlyAdditional){
293 			Iterator<Permission> pIt = perms.iterator();
294 			while (pIt.hasNext()){
295 			    Permission p = pIt.next();
296 			    if (policy.implies(pd, p)) pIt.remove();
297 			}
298 		    }
299 		    if (perms.isEmpty()) continue;
300                     CodeSource cs = null; 
301                     Principal [] principals = null;
302 		    try {
303 			cs = pd.getCodeSource();
304 			principals = pd.getPrincipals();
305 		    } catch (NullPointerException e){
306 			// On some occassions ProtectionDomain hasn't been
307 			// safely published.
308 			System.err.println(
309 			    "ProtectionDomain wasn't safely published: " 
310 			    + pd.toString()
311 			);
312 		    }
313 		    // Delegate tasks to an executor so we don't cause recursive calls / stack overflow.		    
314 		    if (cs != null){
315 			Certificate [] signers = cs.getCertificates();
316 			if (signers != null && signers.length > 0){
317 			     if (keyStore() != null){
318 				for (int i=0, l=signers.length; i<l; i++){
319 				    aliases.computeIfAbsent(signers[i], certFunc);
320 				}
321 			    }
322 			}
323 		    }
324                     if (cs != null || (principals != null && principals.length > 0)){
325                         URL codebase = cs.getLocation();
326                         pw.print("grant ");
327                         if (keyStore != null){
328                             Certificate [] signers = cs.getCertificates();
329                             if (signers != null && signers.length > 0) {
330                                 List<String> alia = new ArrayList<String>(signers.length);
331                                 for (int i=0, l=signers.length; i<l; i++){
332                                     alia.add(aliases.get(signers[i]));
333                                 }
334                                 if (!alia.isEmpty()){
335                                     pw.print("signedBy \"");
336                                     for (int i=0, l=alia.size(); i<l; i++){
337                                        if (i != 0) pw.print(",");
338                                        pw.print(alia.get(i));
339                                     }
340                                     pw.print("\", ");
341                                 }
342                             }
343                         }
344 			if (codebase != null){
345 			    pw.print("codebase \"");
346 			    String codebaseStr = replaceValuesWithProperties(codebase.toString());
347 			    pw.print(codebaseStr);
348 			    pw.print("\"");
349 			    if (principals != null && principals.length >0) pw.print(",\n");
350 			}
351                         if (principals != null && principals.length > 0){
352                             for (int i=0, l=principals.length; i<l; i++){
353                                 pw.print("    principal ");
354                                 pw.print(principals[i].getClass().getCanonicalName());
355                                 pw.print(" \"");
356                                 pw.print(principals[i].getName());
357                                 if (i<l-1) pw.print("\",\n");
358                                 else pw.print("\"\n");
359                             }
360                         } else {
361                             pw.print("\n");
362                         }
363                         pw.print("{\n");
364 
365                         Iterator<Permission> permIt = entry.getValue().iterator();
366                         Permissions permissions = new Permissions();
367                         while (permIt.hasNext()){
368                             Permission p = permIt.next();
369                             if (permissions.implies(p)) continue;
370                             permissions.add(p);
371                             pw.print("    permission ");
372                             pw.print(p.getClass().getCanonicalName());
373                             pw.print(" \"");
374 			    if (p instanceof PrivateCredentialPermission){
375 				PrivateCredentialPermission pcp = (PrivateCredentialPermission) p;
376 				String credential = pcp.getCredentialClass();
377 				String [][] princpals = pcp.getPrincipals();
378 				StringBuilder sb = new StringBuilder();
379 				sb.append(credential);
380 				sb.append(" ");
381 				for (int i=0,l=princpals.length; i<l; i++){
382 				    String [] pals = princpals [i];
383 				    for (int j=0,m=pals.length; j<m; j++){
384 					sb.append(pals[j]);
385 					if (j < m-1) sb.append(" \\\"");
386 					else sb.append("\\\"");
387 				    }
388 				    if (i < l-1) sb.append(" ");
389 				}
390 				pw.print(sb.toString());
391 			    } else {
392 				/* Some complex permissions have quoted strings embedded or
393 				literal carriage returns that must be escaped.  */
394 				String name = p.getName();
395 				if (p instanceof FilePermission && File.separatorChar == '\\'){
396 				    name = name.replace("\\", "\\\\");
397 				} else {
398 				    name = name.replace("\\\"", "\\\\\"").replace("\"","\\\"").replace("\r","\\\r");
399 				}
400 				pw.print(name);
401 			    }
402                             String actions = p.getActions();
403                             if (actions != null && !"".equals(actions)){
404                                 pw.print("\", \"");
405                                 pw.print(actions);
406                                 pw.print("\"");
407                             } else {
408                                 pw.print("\"");
409                             }
410                             // REMIND signedBy?
411                             pw.print(";\n");
412                         }
413                         pw.print("};\n\n");
414                     }
415                 }
416                 pw.flush();
417                 pw.close();
418             }
419         },"SecurityPolicyWriter policy creation thread");
420         if (t.isDaemon()) t.setDaemon(false);
421         /**
422          * See jtreg sun bug ID:4404702
423          * This ensures that this thread doesn't unnecessarily hold 
424          * a strong reference to a ClassLoader, thus preventing
425          * it from being garbage collected.
426          */ 
427         t.setContextClassLoader(ClassLoader.getSystemClassLoader());
428         return t;
429     }
430     
431     private String replaceValuesWithProperties(String s){
432 	return System.getProperty(s,s);
433     }
434     
435     private class Cert implements Function<Certificate,String> {
436 
437         @Override
438         public String apply(Certificate t) {
439             try {
440                 return keyStore().getCertificateAlias(t);
441             } catch (KeyStoreException ex) {
442                 getLogger().log(Level.WARNING, "Alias not found in keystore", ex);
443             }
444             return null;
445         }
446         
447     }
448 }