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