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