1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /** 19 * @author Alexey V. Varlamov 20 * @version $Revision$ 21 */ 22 23 package org.apache.harmony.security.fortress; 24 25 import java.io.BufferedReader; 26 import java.io.InputStream; 27 import java.io.InputStreamReader; 28 import java.io.Reader; 29 import java.net.URL; 30 import java.security.AccessController; 31 import java.security.CodeSource; 32 import java.security.KeyStore; 33 import java.security.KeyStoreException; 34 import java.security.Permission; 35 import java.security.Principal; 36 import java.security.UnresolvedPermission; 37 import java.security.cert.Certificate; 38 import java.security.cert.CertificateException; 39 import java.security.cert.X509Certificate; 40 import java.util.ArrayList; 41 import java.util.Collection; 42 import java.util.HashSet; 43 import java.util.Iterator; 44 import java.util.List; 45 import java.util.Properties; 46 import java.util.Set; 47 import java.util.StringTokenizer; 48 import org.apache.harmony.security.DefaultPolicyScanner; 49 import org.apache.harmony.security.DefaultPolicyScanner.GrantEntry; 50 import org.apache.harmony.security.DefaultPolicyScanner.KeystoreEntry; 51 import org.apache.harmony.security.DefaultPolicyScanner.PermissionEntry; 52 import org.apache.harmony.security.DefaultPolicyScanner.PrincipalEntry; 53 import org.apache.harmony.security.PolicyEntry; 54 import org.apache.harmony.security.UnresolvedPrincipal; 55 56 /** 57 * This is a basic loader of policy files. It delegates lexical analysis to 58 * a pluggable scanner and converts received tokens to a set of 59 * {@link org.apache.harmony.security.PolicyEntry PolicyEntries}. 60 * For details of policy format, see the 61 * {@link org.apache.harmony.security.DefaultPolicy default policy description}. 62 * <br> 63 * For ordinary uses, this class has just one public method <code>parse()</code>, 64 * which performs the main task. 65 * Extensions of this parser may redefine specific operations separately, 66 * by overriding corresponding protected methods. 67 * <br> 68 * This implementation is effectively thread-safe, as it has no field references 69 * to data being processed (that is, passes all the data as method parameters). 70 * 71 * @see org.apache.harmony.security.DefaultPolicy 72 * @see org.apache.harmony.security.DefaultPolicyScanner 73 * @see org.apache.harmony.security.PolicyEntry 74 */ 75 public class DefaultPolicyParser { 76 77 // Pluggable scanner for a specific file format 78 private final DefaultPolicyScanner scanner; 79 80 /** 81 * Default constructor, 82 * {@link org.apache.harmony.security.DefaultPolicyScanner DefaultPolicyScanner} 83 * is used. 84 */ 85 public DefaultPolicyParser() { 86 scanner = new DefaultPolicyScanner(); 87 } 88 89 /** 90 * Extension constructor for plugging-in custom scanner. 91 */ 92 public DefaultPolicyParser(DefaultPolicyScanner s) { 93 this.scanner = s; 94 } 95 96 /** 97 * This is the main business method. It manages loading process as follows: 98 * the associated scanner is used to parse the stream to a set of 99 * {@link org.apache.harmony.security.DefaultPolicyScanner.GrantEntry composite tokens}, 100 * then this set is iterated and each token is translated to a PolicyEntry. 101 * Semantically invalid tokens are ignored, the same as void PolicyEntries. 102 * <br> 103 * A policy file may refer to some KeyStore(s), and in this case the first 104 * valid reference is initialized and used in processing tokens. 105 * 106 * @param location an URL of a policy file to be loaded 107 * @param system system properties, used for property expansion 108 * @return a collection of PolicyEntry objects, may be empty 109 * @throws Exception IO error while reading location or file syntax error 110 */ 111 public Collection<PolicyEntry>parse(URL location, Properties system) 112 throws Exception { 113 114 boolean resolve = PolicyUtils.canExpandProperties(); 115 Reader r = 116 new BufferedReader( 117 new InputStreamReader( 118 AccessController.doPrivileged( 119 new PolicyUtils.URLLoader(location)))); 120 121 Collection<GrantEntry> grantEntries = new HashSet<GrantEntry>(); 122 List<KeystoreEntry> keystores = new ArrayList<KeystoreEntry>(); 123 124 try { 125 scanner.scanStream(r, grantEntries, keystores); 126 } 127 finally { 128 r.close(); 129 } 130 131 //XXX KeyStore could be loaded lazily... 132 KeyStore ks = initKeyStore(keystores, location, system, resolve); 133 134 Collection<PolicyEntry> result = new HashSet<PolicyEntry>(); 135 for (Iterator<GrantEntry> iter = grantEntries.iterator(); iter.hasNext();) { 136 DefaultPolicyScanner.GrantEntry ge = iter 137 .next(); 138 try { 139 PolicyEntry pe = resolveGrant(ge, ks, system, resolve); 140 if (!pe.isVoid()) { 141 result.add(pe); 142 } 143 } 144 catch (Exception e) { 145 // TODO: log warning 146 } 147 } 148 149 return result; 150 } 151 152 /** 153 * Translates GrantEntry token to PolicyEntry object. It goes step by step, 154 * trying to resolve each component of the GrantEntry: 155 * <ul> 156 * <li> If <code>codebase</code> is specified, expand it and construct an URL. 157 * <li> If <code>signers</code> is specified, expand it and obtain 158 * corresponding Certificates. 159 * <li> If <code>principals</code> collection is specified, iterate over it. 160 * For each PrincipalEntry, expand name and if no class specified, 161 * resolve actual X500Principal from a KeyStore certificate; otherwise keep it 162 * as UnresolvedPrincipal. 163 * <li> Iterate over <code>permissions</code> collection. For each PermissionEntry, 164 * try to resolve (see method 165 * {@link #resolvePermission(DefaultPolicyScanner.PermissionEntry, DefaultPolicyScanner.GrantEntry, KeyStore, Properties, boolean) resolvePermission()}) 166 * a corresponding permission. If resolution failed, ignore the PermissionEntry. 167 * </ul> 168 * In fact, property expansion in the steps above is conditional and is ruled by 169 * the parameter <i>resolve</i>. 170 * <br> 171 * Finally a new PolicyEntry is created, which associates the trinity 172 * of resolved URL, Certificates and Principals to a set of granted Permissions. 173 * 174 * @param ge GrantEntry token to be resolved 175 * @param ks KeyStore for resolving Certificates, may be <code>null</code> 176 * @param system system properties, used for property expansion 177 * @param resolve flag enabling/disabling property expansion 178 * @return resolved PolicyEntry 179 * @throws Exception if unable to resolve codebase, signers or principals 180 * of the GrantEntry 181 * @see DefaultPolicyScanner.PrincipalEntry 182 * @see DefaultPolicyScanner.PermissionEntry 183 * @see org.apache.harmony.security.PolicyUtils 184 */ 185 protected PolicyEntry resolveGrant(DefaultPolicyScanner.GrantEntry ge, 186 KeyStore ks, Properties system, boolean resolve) throws Exception { 187 188 URL codebase = null; 189 Certificate[] signers = null; 190 Set<Principal>principals = new HashSet<Principal>(); 191 Set<Permission>permissions = new HashSet<Permission>(); 192 if (ge.codebase != null) { 193 codebase = new URL(resolve ? PolicyUtils.expandURL(ge.codebase, 194 system) : ge.codebase); 195 } 196 if (ge.signers != null) { 197 if (resolve) { 198 ge.signers = PolicyUtils.expand(ge.signers, system); 199 } 200 signers = resolveSigners(ks, ge.signers); 201 } 202 if (ge.principals != null) { 203 for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter.hasNext();) { 204 DefaultPolicyScanner.PrincipalEntry pe = iter 205 .next(); 206 if (resolve) { 207 pe.name = PolicyUtils.expand(pe.name, system); 208 } 209 if (pe.klass == null) { 210 principals.add(getPrincipalByAlias(ks, pe.name)); 211 } else { 212 principals.add(new UnresolvedPrincipal(pe.klass, pe.name)); 213 } 214 } 215 } 216 if (ge.permissions != null) { 217 for (Iterator<PermissionEntry> iter = ge.permissions.iterator(); iter.hasNext();) { 218 DefaultPolicyScanner.PermissionEntry pe = iter 219 .next(); 220 try { 221 permissions.add(resolvePermission(pe, ge, ks, system, 222 resolve)); 223 } 224 catch (Exception e) { 225 // TODO: log warning 226 } 227 } 228 } 229 return new PolicyEntry(new CodeSource(codebase, signers), principals, 230 permissions); 231 } 232 233 /** 234 * Translates PermissionEntry token to Permission object. 235 * First, it performs general expansion for non-null <code>name</code> and 236 * properties expansion for non-null <code>name</code>, <code>action</code> 237 * and <code>signers</code>. 238 * Then, it obtains signing Certificates(if any), tries to find a class specified by 239 * <code>klass</code> name and instantiate a corresponding permission object. 240 * If class is not found or it is signed improperly, returns UnresolvedPermission. 241 * 242 * @param pe PermissionEntry token to be resolved 243 * @param ge parental GrantEntry of the PermissionEntry 244 * @param ks KeyStore for resolving Certificates, may be <code>null</code> 245 * @param system system properties, used for property expansion 246 * @param resolve flag enabling/disabling property expansion 247 * @return resolved Permission object, either of concrete class or UnresolvedPermission 248 * @throws Exception if failed to expand properties, 249 * or to get a Certificate, 250 * or to create an instance of a successfully found class 251 */ 252 protected Permission resolvePermission( 253 DefaultPolicyScanner.PermissionEntry pe, 254 DefaultPolicyScanner.GrantEntry ge, KeyStore ks, Properties system, 255 boolean resolve) throws Exception { 256 if (pe.name != null) { 257 pe.name = PolicyUtils.expandGeneral(pe.name, 258 new PermissionExpander().configure(ge, ks)); 259 } 260 if (resolve) { 261 if (pe.name != null) { 262 pe.name = PolicyUtils.expand(pe.name, system); 263 } 264 if (pe.actions != null) { 265 pe.actions = PolicyUtils.expand(pe.actions, system); 266 } 267 if (pe.signers != null) { 268 pe.signers = PolicyUtils.expand(pe.signers, system); 269 } 270 } 271 Certificate[] signers = (pe.signers == null) ? null : resolveSigners( 272 ks, pe.signers); 273 try { 274 Class<?> klass = Class.forName(pe.klass); 275 if (PolicyUtils.matchSubset(signers, klass.getSigners())) { 276 return PolicyUtils.instantiatePermission(klass, pe.name, 277 pe.actions); 278 } 279 } 280 catch (ClassNotFoundException cnfe) {} 281 //maybe properly signed class will be loaded later 282 return new UnresolvedPermission(pe.klass, pe.name, pe.actions, signers); 283 } 284 285 /** 286 * Specific handler for expanding <i>self</i> and <i>alias</i> protocols. 287 */ 288 class PermissionExpander implements PolicyUtils.GeneralExpansionHandler { 289 290 // Store KeyStore 291 private KeyStore ks; 292 293 // Store GrantEntry 294 private DefaultPolicyScanner.GrantEntry ge; 295 296 /** 297 * Combined setter of all required fields. 298 */ 299 public PermissionExpander configure(DefaultPolicyScanner.GrantEntry ge, 300 KeyStore ks) { 301 this.ge = ge; 302 this.ks = ks; 303 return this; 304 } 305 306 /** 307 * Resolves the following protocols: 308 * <dl> 309 * <dt>self 310 * <dd>Denotes substitution to a principal information of the parental 311 * GrantEntry. Returns a space-separated list of resolved Principals 312 * (including wildcarded), formatting each as <b>class "name"</b>. 313 * If parental GrantEntry has no Principals, throws ExpansionFailedException. 314 * <dt>alias:<i>name</i> 315 * <dd>Denotes substitution of a KeyStore alias. Namely, if a KeyStore has 316 * an X.509 certificate associated with the specified name, then returns 317 * <b>javax.security.auth.x500.X500Principal "<i>DN</i>"</b> string, 318 * where <i>DN</i> is a certificate's subject distinguished name. 319 * </dl> 320 * @throws ExpansionFailedException - if protocol is other than 321 * <i>self</i> or <i>alias</i>, or if data resolution failed 322 */ 323 public String resolve(String protocol, String data) 324 throws PolicyUtils.ExpansionFailedException { 325 326 if ("self".equals(protocol)) { 327 //need expanding to list of principals in grant clause 328 if (ge.principals != null && ge.principals.size() != 0) { 329 StringBuilder sb = new StringBuilder(); 330 for (Iterator<PrincipalEntry> iter = ge.principals.iterator(); iter 331 .hasNext();) { 332 DefaultPolicyScanner.PrincipalEntry pr = iter 333 .next(); 334 if (pr.klass == null) { 335 // aliased X500Principal 336 try { 337 sb.append(pc2str(getPrincipalByAlias(ks, 338 pr.name))); 339 } 340 catch (Exception e) { 341 throw new PolicyUtils.ExpansionFailedException("Error expanding alias: " + pr.name, e); 342 } 343 } else { 344 sb.append(pr.klass).append(" \"").append(pr.name) 345 .append("\" "); 346 } 347 } 348 return sb.toString(); 349 } else { 350 throw new PolicyUtils.ExpansionFailedException("Self protocol is valid only in context of Principal-based grant entries"); 351 } 352 } 353 if ("alias".equals(protocol)) { 354 try { 355 return pc2str(getPrincipalByAlias(ks, data)); 356 } catch (Exception e) { 357 throw new PolicyUtils.ExpansionFailedException("Error expanding alias: " + data, e); 358 } 359 } 360 throw new PolicyUtils.ExpansionFailedException("Unknown expansion protocol: " + protocol); 361 } 362 363 // Formats a string describing the passed Principal. 364 private String pc2str(Principal pc) { 365 String klass = pc.getClass().getName(); 366 String name = pc.getName(); 367 StringBuilder sb = new StringBuilder(klass.length() + name.length() 368 + 5); 369 return sb.append(klass).append(" \"").append(name).append("\"") 370 .toString(); 371 } 372 } 373 374 /** 375 * Takes a comma-separated list of aliases and obtains corresponding 376 * certificates. 377 * @param ks KeyStore for resolving Certificates, may be <code>null</code> 378 * @param signers comma-separated list of certificate aliases, 379 * must be not <code>null</code> 380 * @return an array of signing Certificates 381 * @throws Exception if KeyStore is <code>null</code> 382 * or if it failed to provide a certificate 383 */ 384 protected Certificate[] resolveSigners(KeyStore ks, String signers) 385 throws Exception { 386 if (ks == null) { 387 throw new KeyStoreException("No KeyStore to resolve signers: " + signers); 388 } 389 390 Collection<Certificate> certs = new HashSet<Certificate>(); 391 StringTokenizer snt = new StringTokenizer(signers, ","); 392 while (snt.hasMoreTokens()) { 393 //XXX cache found certs ?? 394 certs.add(ks.getCertificate(snt.nextToken().trim())); 395 } 396 return certs.toArray(new Certificate[certs.size()]); 397 } 398 399 /** 400 * Returns a subject's X500Principal of an X509Certificate, 401 * which is associated with the specified keystore alias. 402 * @param ks KeyStore for resolving Certificate, may be <code>null</code> 403 * @param alias alias to a certificate 404 * @return X500Principal with a subject distinguished name 405 * @throws KeyStoreException if KeyStore is <code>null</code> 406 * or if it failed to provide a certificate 407 * @throws CertificateException if found certificate is not 408 * an X509Certificate 409 */ 410 protected Principal getPrincipalByAlias(KeyStore ks, String alias) 411 throws KeyStoreException, CertificateException { 412 413 if (ks == null) { 414 throw new KeyStoreException("No KeyStore to resolve principal by alias: " + alias); 415 } 416 //XXX cache found certs ?? 417 Certificate x509 = ks.getCertificate(alias); 418 if (x509 instanceof X509Certificate) { 419 return ((X509Certificate) x509).getSubjectX500Principal(); 420 } else { 421 throw new CertificateException("Invalid certificate for alias '" + alias + "': " + 422 x509 + ". Only X509Certificate should be aliased to principals."); 423 } 424 } 425 426 /** 427 * Returns the first successfully loaded KeyStore, from the specified list of 428 * possible locations. This method iterates over the list of KeystoreEntries; 429 * for each entry expands <code>url</code> and <code>type</code>, 430 * tries to construct instances of specified URL and KeyStore and to load 431 * the keystore. If it is loaded, returns the keystore, otherwise proceeds to 432 * the next KeystoreEntry. 433 * <br> 434 * <b>Note:</b> an url may be relative to the policy file location or absolute. 435 * @param keystores list of available KeystoreEntries 436 * @param base the policy file location 437 * @param system system properties, used for property expansion 438 * @param resolve flag enabling/disabling property expansion 439 * @return the first successfully loaded KeyStore or <code>null</code> 440 */ 441 protected KeyStore initKeyStore(List<KeystoreEntry>keystores, 442 URL base, Properties system, boolean resolve) { 443 444 for (int i = 0; i < keystores.size(); i++) { 445 try { 446 DefaultPolicyScanner.KeystoreEntry ke = keystores 447 .get(i); 448 if (resolve) { 449 ke.url = PolicyUtils.expandURL(ke.url, system); 450 if (ke.type != null) { 451 ke.type = PolicyUtils.expand(ke.type, system); 452 } 453 } 454 if (ke.type == null || ke.type.length() == 0) { 455 ke.type = KeyStore.getDefaultType(); 456 } 457 KeyStore ks = KeyStore.getInstance(ke.type); 458 URL location = new URL(base, ke.url); 459 InputStream is = AccessController 460 .doPrivileged(new PolicyUtils.URLLoader(location)); 461 try { 462 ks.load(is, null); 463 } 464 finally { 465 is.close(); 466 } 467 return ks; 468 } 469 catch (Exception e) { 470 // TODO: log warning 471 } 472 } 473 return null; 474 } 475 } 476