Frames | No Frames |
1: /* Security.java --- Java base security class implementation 2: Copyright (C) 1999, 2001, 2002, 2003, 2004, 2005, 2006 3: Free Software Foundation, Inc. 4: 5: This file is part of GNU Classpath. 6: 7: GNU Classpath is free software; you can redistribute it and/or modify 8: it under the terms of the GNU General Public License as published by 9: the Free Software Foundation; either version 2, or (at your option) 10: any later version. 11: 12: GNU Classpath is distributed in the hope that it will be useful, but 13: WITHOUT ANY WARRANTY; without even the implied warranty of 14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15: General Public License for more details. 16: 17: You should have received a copy of the GNU General Public License 18: along with GNU Classpath; see the file COPYING. If not, write to the 19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 20: 02110-1301 USA. 21: 22: Linking this library statically or dynamically with other modules is 23: making a combined work based on this library. Thus, the terms and 24: conditions of the GNU General Public License cover the whole 25: combination. 26: 27: As a special exception, the copyright holders of this library give you 28: permission to link this library with independent modules to produce an 29: executable, regardless of the license terms of these independent 30: modules, and to copy and distribute the resulting executable under 31: terms of your choice, provided that you also meet, for each linked 32: independent module, the terms and conditions of the license of that 33: module. An independent module is a module which is not derived from 34: or based on this library. If you modify this library, you may extend 35: this exception to your version of the library, but you are not 36: obligated to do so. If you do not wish to do so, delete this 37: exception statement from your version. */ 38: 39: 40: package java.security; 41: 42: import gnu.classpath.SystemProperties; 43: 44: import gnu.classpath.Configuration; 45: import gnu.classpath.VMStackWalker; 46: 47: import java.io.IOException; 48: import java.io.InputStream; 49: import java.net.URL; 50: import java.util.Collections; 51: import java.util.Enumeration; 52: import java.util.HashMap; 53: import java.util.HashSet; 54: import java.util.Iterator; 55: import java.util.LinkedHashSet; 56: import java.util.Map; 57: import java.util.Properties; 58: import java.util.Set; 59: import java.util.Vector; 60: 61: /** 62: * This class centralizes all security properties and common security methods. 63: * One of its primary uses is to manage security providers. 64: * 65: * @author Mark Benvenuto (ivymccough@worldnet.att.net) 66: */ 67: public final class Security 68: { 69: private static final String ALG_ALIAS = "Alg.Alias."; 70: 71: private static Vector providers = new Vector(); 72: private static Properties secprops = new Properties(); 73: 74: static 75: { 76: String base = SystemProperties.getProperty("gnu.classpath.home.url"); 77: String vendor = SystemProperties.getProperty("gnu.classpath.vm.shortname"); 78: 79: // Try VM specific security file 80: boolean loaded = loadProviders (base, vendor); 81: 82: // Append classpath standard provider if possible 83: if (!loadProviders (base, "classpath") 84: && !loaded 85: && providers.size() == 0) 86: { 87: if (Configuration.DEBUG) 88: { 89: /* No providers found and both security files failed to 90: * load properly. Give a warning in case of DEBUG is 91: * enabled. Could be done with java.util.logging later. 92: */ 93: System.err.println 94: ("WARNING: could not properly read security provider files:"); 95: System.err.println 96: (" " + base + "/security/" + vendor 97: + ".security"); 98: System.err.println 99: (" " + base + "/security/" + "classpath" 100: + ".security"); 101: System.err.println 102: (" Falling back to standard GNU security provider"); 103: } 104: // Note that this matches our classpath.security file. 105: providers.addElement (new gnu.java.security.provider.Gnu()); 106: providers.addElement(new gnu.javax.crypto.jce.GnuCrypto()); 107: providers.addElement(new gnu.javax.crypto.jce.GnuSasl()); 108: providers.addElement(new gnu.javax.net.ssl.provider.Jessie()); 109: providers.addElement(new gnu.javax.security.auth.callback.GnuCallbacks()); 110: } 111: } 112: // This class can't be instantiated. 113: private Security() 114: { 115: } 116: 117: /** 118: * Tries to load the vender specific security providers from the given base 119: * URL. Returns true if the resource could be read and completely parsed 120: * successfully, false otherwise. 121: */ 122: private static boolean loadProviders(String baseUrl, String vendor) 123: { 124: if (baseUrl == null || vendor == null) 125: return false; 126: 127: boolean result = true; 128: String secfilestr = baseUrl + "/security/" + vendor + ".security"; 129: try 130: { 131: InputStream fin = new URL(secfilestr).openStream(); 132: secprops.load(fin); 133: 134: int i = 1; 135: String name; 136: while ((name = secprops.getProperty("security.provider." + i)) != null) 137: { 138: Exception exception = null; 139: try 140: { 141: ClassLoader sys = ClassLoader.getSystemClassLoader(); 142: providers.addElement(Class.forName(name, true, sys).newInstance()); 143: } 144: catch (ClassNotFoundException x) 145: { 146: exception = x; 147: } 148: catch (InstantiationException x) 149: { 150: exception = x; 151: } 152: catch (IllegalAccessException x) 153: { 154: exception = x; 155: } 156: 157: if (exception != null) 158: { 159: System.err.println ("WARNING: Error loading security provider " 160: + name + ": " + exception); 161: result = false; 162: } 163: i++; 164: } 165: } 166: catch (IOException ignored) 167: { 168: result = false; 169: } 170: 171: return result; 172: } 173: 174: /** 175: * Returns the value associated to a designated property name for a given 176: * algorithm. 177: * 178: * @param algName 179: * the algorithm name. 180: * @param propName 181: * the name of the property to return. 182: * @return the value of the specified property or <code>null</code> if none 183: * found. 184: * @deprecated Use the provider-based and algorithm-independent 185: * {@link AlgorithmParameters} and {@link KeyFactory} engine 186: * classes instead. 187: */ 188: public static String getAlgorithmProperty(String algName, String propName) 189: { 190: if (algName == null || propName == null) 191: return null; 192: 193: String property = String.valueOf(propName) + "." + String.valueOf(algName); 194: Provider p; 195: for (Iterator i = providers.iterator(); i.hasNext(); ) 196: { 197: p = (Provider) i.next(); 198: for (Iterator j = p.keySet().iterator(); j.hasNext(); ) 199: { 200: String key = (String) j.next(); 201: if (key.equalsIgnoreCase(property)) 202: return p.getProperty(key); 203: } 204: } 205: return null; 206: } 207: 208: /** 209: * Inserts a new designated {@link Provider} at a designated (1-based) 210: * position in the current list of installed {@link Provider}s, 211: * 212: * @param provider 213: * the new {@link Provider} to add. 214: * @param position 215: * the position (starting from 1) of where to install 216: * <code>provider</code>. 217: * @return the actual position, in the list of installed Providers. Returns 218: * <code>-1</code> if <code>provider</code> was laready in the 219: * list. The actual position may be different than the desired 220: * <code>position</code>. 221: * @throws SecurityException 222: * if a {@link SecurityManager} is installed and it disallows this 223: * operation. 224: * @see #getProvider(String) 225: * @see #removeProvider(String) 226: * @see SecurityPermission 227: */ 228: public static int insertProviderAt(Provider provider, int position) 229: { 230: SecurityManager sm = System.getSecurityManager(); 231: if (sm != null) 232: sm.checkSecurityAccess("insertProvider." + provider.getName()); 233: 234: position--; 235: int max = providers.size (); 236: for (int i = 0; i < max; i++) 237: { 238: if (((Provider) providers.elementAt(i)).getName().equals(provider.getName())) 239: return -1; 240: } 241: 242: if (position < 0) 243: position = 0; 244: if (position > max) 245: position = max; 246: 247: providers.insertElementAt(provider, position); 248: 249: return position + 1; 250: } 251: 252: /** 253: * Appends the designated new {@link Provider} to the current list of 254: * installed {@link Provider}s. 255: * 256: * @param provider 257: * the new {@link Provider} to append. 258: * @return the position (starting from 1) of <code>provider</code> in the 259: * current list of {@link Provider}s, or <code>-1</code> if 260: * <code>provider</code> was already there. 261: * @throws SecurityException 262: * if a {@link SecurityManager} is installed and it disallows this 263: * operation. 264: * @see #getProvider(String) 265: * @see #removeProvider(String) 266: * @see SecurityPermission 267: */ 268: public static int addProvider(Provider provider) 269: { 270: return insertProviderAt (provider, providers.size () + 1); 271: } 272: 273: /** 274: * Removes an already installed {@link Provider}, given its name, from the 275: * current list of installed {@link Provider}s. 276: * 277: * @param name 278: * the name of an already installed {@link Provider} to remove. 279: * @throws SecurityException 280: * if a {@link SecurityManager} is installed and it disallows this 281: * operation. 282: * @see #getProvider(String) 283: * @see #addProvider(Provider) 284: */ 285: public static void removeProvider(String name) 286: { 287: SecurityManager sm = System.getSecurityManager(); 288: if (sm != null) 289: sm.checkSecurityAccess("removeProvider." + name); 290: 291: int max = providers.size (); 292: for (int i = 0; i < max; i++) 293: { 294: if (((Provider) providers.elementAt(i)).getName().equals(name)) 295: { 296: providers.remove(i); 297: break; 298: } 299: } 300: } 301: 302: /** 303: * Returns the current list of installed {@link Provider}s as an array 304: * ordered according to their installation preference order. 305: * 306: * @return an array of all the installed providers. 307: */ 308: public static Provider[] getProviders() 309: { 310: Provider[] array = new Provider[providers.size ()]; 311: providers.copyInto (array); 312: return array; 313: } 314: 315: /** 316: * Returns an already installed {@link Provider} given its name. 317: * 318: * @param name 319: * the name of an already installed {@link Provider}. 320: * @return the {@link Provider} known by <code>name</code>. Returns 321: * <code>null</code> if the current list of {@link Provider}s does 322: * not include one named <code>name</code>. 323: * @see #removeProvider(String) 324: * @see #addProvider(Provider) 325: */ 326: public static Provider getProvider(String name) 327: { 328: if (name == null) 329: return null; 330: else 331: { 332: name = name.trim(); 333: if (name.length() == 0) 334: return null; 335: } 336: Provider p; 337: int max = providers.size (); 338: for (int i = 0; i < max; i++) 339: { 340: p = (Provider) providers.elementAt(i); 341: if (p.getName().equals(name)) 342: return p; 343: } 344: return null; 345: } 346: 347: /** 348: * Returns the value associated with a Security propery. 349: * 350: * @param key 351: * the key of the property to fetch. 352: * @return the value of the Security property associated with 353: * <code>key</code>. Returns <code>null</code> if no such property 354: * was found. 355: * @throws SecurityException 356: * if a {@link SecurityManager} is installed and it disallows this 357: * operation. 358: * @see #setProperty(String, String) 359: * @see SecurityPermission 360: */ 361: public static String getProperty(String key) 362: { 363: // XXX To prevent infinite recursion when the SecurityManager calls us, 364: // don't do a security check if the caller is trusted (by virtue of having 365: // been loaded by the bootstrap class loader). 366: SecurityManager sm = System.getSecurityManager(); 367: if (sm != null && VMStackWalker.getCallingClassLoader() != null) 368: sm.checkSecurityAccess("getProperty." + key); 369: 370: return secprops.getProperty(key); 371: } 372: 373: /** 374: * Sets or changes a designated Security property to a designated value. 375: * 376: * @param key 377: * the name of the property to set. 378: * @param datum 379: * the new value of the property. 380: * @throws SecurityException 381: * if a {@link SecurityManager} is installed and it disallows this 382: * operation. 383: * @see #getProperty(String) 384: * @see SecurityPermission 385: */ 386: public static void setProperty(String key, String datum) 387: { 388: SecurityManager sm = System.getSecurityManager(); 389: if (sm != null) 390: sm.checkSecurityAccess("setProperty." + key); 391: 392: if (datum == null) 393: secprops.remove(key); 394: else 395: secprops.put(key, datum); 396: } 397: 398: /** 399: * For a given <i>service</i> (e.g. Signature, MessageDigest, etc...) this 400: * method returns the {@link Set} of all available algorithm names (instances 401: * of {@link String}, from all currently installed {@link Provider}s. 402: * 403: * @param serviceName 404: * the case-insensitive name of a service (e.g. Signature, 405: * MessageDigest, etc). 406: * @return a {@link Set} of {@link String}s containing the names of all 407: * algorithm names provided by all of the currently installed 408: * {@link Provider}s. 409: * @since 1.4 410: */ 411: public static Set<String> getAlgorithms(String serviceName) 412: { 413: HashSet<String> result = new HashSet<String>(); 414: if (serviceName == null || serviceName.length() == 0) 415: return result; 416: 417: serviceName = serviceName.trim(); 418: if (serviceName.length() == 0) 419: return result; 420: 421: serviceName = serviceName.toUpperCase()+"."; 422: Provider[] providers = getProviders(); 423: int ndx; 424: for (int i = 0; i < providers.length; i++) 425: for (Enumeration e = providers[i].propertyNames(); e.hasMoreElements(); ) 426: { 427: String service = ((String) e.nextElement()).trim(); 428: if (service.toUpperCase().startsWith(serviceName)) 429: { 430: service = service.substring(serviceName.length()).trim(); 431: ndx = service.indexOf(' '); // get rid of attributes 432: if (ndx != -1) 433: service = service.substring(0, ndx); 434: result.add(service); 435: } 436: } 437: return Collections.unmodifiableSet(result); 438: } 439: 440: /** 441: * Returns an array of currently installed {@link Provider}s, ordered 442: * according to their installation preference order, which satisfy a given 443: * <i>selection</i> criterion. 444: * 445: * <p>This implementation recognizes a <i>selection</i> criterion written in 446: * one of two following forms:</p> 447: * 448: * <ul> 449: * <li><crypto_service>.<algorithm_or_type>: Where 450: * <i>crypto_service</i> is a case-insensitive string, similar to what has 451: * been described in the {@link #getAlgorithms(String)} method, and 452: * <i>algorithm_or_type</i> is a known case-insensitive name of an 453: * Algorithm, or one of its aliases. 454: * 455: * <p>For example, "CertificateFactory.X.509" would return all the installed 456: * {@link Provider}s which provide a <i>CertificateFactory</i> 457: * implementation of <i>X.509</i>.</p></li> 458: * 459: * <li><crypto_service>.<algorithm_or_type> <attribute_name>:<value>: 460: * Where <i>crypto_service</i> is a case-insensitive string, similar to what 461: * has been described in the {@link #getAlgorithms(String)} method, 462: * <i>algorithm_or_type</i> is a case-insensitive known name of an Algorithm 463: * or one of its aliases, <i>attribute_name</i> is a case-insensitive 464: * property name with no whitespace characters, and no dots, in-between, and 465: * <i>value</i> is a {@link String} with no whitespace characters in-between. 466: * 467: * <p>For example, "Signature.Sha1WithDSS KeySize:1024" would return all the 468: * installed {@link Provider}s which declared their ability to provide 469: * <i>Signature</i> services, using the <i>Sha1WithDSS</i> algorithm with 470: * key sizes of <i>1024</i>.</p></li> 471: * </ul> 472: * 473: * @param filter 474: * the <i>selection</i> criterion for selecting among the installed 475: * {@link Provider}s. 476: * @return all the installed {@link Provider}s which satisfy the <i>selection</i> 477: * criterion. Returns <code>null</code> if no installed 478: * {@link Provider}s were found which satisfy the <i>selection</i> 479: * criterion. Returns ALL installed {@link Provider}s if 480: * <code>filter</code> is <code>null</code> or is an empty string. 481: * @throws InvalidParameterException 482: * if an exception occurs while parsing the <code>filter</code>. 483: * @see #getProviders(Map) 484: */ 485: public static Provider[] getProviders(String filter) 486: { 487: if (providers == null || providers.isEmpty()) 488: return null; 489: 490: if (filter == null || filter.length() == 0) 491: return getProviders(); 492: 493: HashMap map = new HashMap(1); 494: int i = filter.indexOf(':'); 495: if (i == -1) // <service>.<algorithm> 496: map.put(filter, ""); 497: else // <service>.<algorithm> <attribute>:<value> 498: map.put(filter.substring(0, i), filter.substring(i+1)); 499: 500: return getProviders(map); 501: } 502: 503: /** 504: * Returns an array of currently installed {@link Provider}s which satisfy a 505: * set of <i>selection</i> criteria. 506: * 507: * <p>The <i>selection</i> criteria are defined in a {@link Map} where each 508: * element specifies a <i>selection</i> querry. The <i>Keys</i> in this 509: * {@link Map} must be in one of the two following forms:</p> 510: * 511: * <ul> 512: * <li><crypto_service>.<algorithm_or_type>: Where 513: * <i>crypto_service</i> is a case-insensitive string, similar to what has 514: * been described in the {@link #getAlgorithms(String)} method, and 515: * <i>algorithm_or_type</i> is a case-insensitive known name of an 516: * Algorithm, or one of its aliases. The <i>value</i> of the entry in the 517: * {@link Map} for such a <i>Key</i> MUST be the empty string. 518: * {@link Provider}s which provide an implementation for the designated 519: * <i>service algorithm</i> are included in the result.</li> 520: * 521: * <li><crypto_service>.<algorithm_or_type> <attribute_name>: 522: * Where <i>crypto_service</i> is a case-insensitive string, similar to what 523: * has been described in the {@link #getAlgorithms(String)} method, 524: * <i>algorithm_or_type</i> is a case-insensitive known name of an Algorithm 525: * or one of its aliases, and <i>attribute_name</i> is a case-insensitive 526: * property name with no whitespace characters, and no dots, in-between. The 527: * <i>value</i> of the entry in this {@link Map} for such a <i>Key</i> MUST 528: * NOT be <code>null</code> or an empty string. {@link Provider}s which 529: * declare the designated <i>attribute_name</i> and <i>value</i> for the 530: * designated <i>service algorithm</i> are included in the result.</li> 531: * </ul> 532: * 533: * @param filter 534: * a {@link Map} of <i>selection querries</i>. 535: * @return all currently installed {@link Provider}s which satisfy ALL the 536: * <i>selection</i> criteria defined in <code>filter</code>. 537: * Returns ALL installed {@link Provider}s if <code>filter</code> 538: * is <code>null</code> or empty. 539: * @throws InvalidParameterException 540: * if an exception is encountered while parsing the syntax of the 541: * {@link Map}'s <i>keys</i>. 542: * @see #getProviders(String) 543: */ 544: public static Provider[] getProviders(Map<String,String> filter) 545: { 546: if (providers == null || providers.isEmpty()) 547: return null; 548: 549: if (filter == null) 550: return getProviders(); 551: 552: Set<String> querries = filter.keySet(); 553: if (querries == null || querries.isEmpty()) 554: return getProviders(); 555: 556: LinkedHashSet result = new LinkedHashSet(providers); // assume all 557: int dot, ws; 558: String querry, service, algorithm, attribute, value; 559: LinkedHashSet serviceProviders = new LinkedHashSet(); // preserve insertion order 560: for (Iterator i = querries.iterator(); i.hasNext(); ) 561: { 562: querry = (String) i.next(); 563: if (querry == null) // all providers 564: continue; 565: 566: querry = querry.trim(); 567: if (querry.length() == 0) // all providers 568: continue; 569: 570: dot = querry.indexOf('.'); 571: if (dot == -1) // syntax error 572: throw new InvalidParameterException( 573: "missing dot in '" + String.valueOf(querry)+"'"); 574: 575: value = filter.get(querry); 576: // deconstruct querry into [service, algorithm, attribute] 577: if (value == null || value.trim().length() == 0) // <service>.<algorithm> 578: { 579: value = null; 580: attribute = null; 581: service = querry.substring(0, dot).trim(); 582: algorithm = querry.substring(dot+1).trim(); 583: } 584: else // <service>.<algorithm> <attribute> 585: { 586: ws = querry.indexOf(' '); 587: if (ws == -1) 588: throw new InvalidParameterException( 589: "value (" + String.valueOf(value) + 590: ") is not empty, but querry (" + String.valueOf(querry) + 591: ") is missing at least one space character"); 592: value = value.trim(); 593: attribute = querry.substring(ws+1).trim(); 594: // was the dot in the attribute? 595: if (attribute.indexOf('.') != -1) 596: throw new InvalidParameterException( 597: "attribute_name (" + String.valueOf(attribute) + 598: ") in querry (" + String.valueOf(querry) + ") contains a dot"); 599: 600: querry = querry.substring(0, ws).trim(); 601: service = querry.substring(0, dot).trim(); 602: algorithm = querry.substring(dot+1).trim(); 603: } 604: 605: // service and algorithm must not be empty 606: if (service.length() == 0) 607: throw new InvalidParameterException( 608: "<crypto_service> in querry (" + String.valueOf(querry) + 609: ") is empty"); 610: 611: if (algorithm.length() == 0) 612: throw new InvalidParameterException( 613: "<algorithm_or_type> in querry (" + String.valueOf(querry) + 614: ") is empty"); 615: 616: selectProviders(service, algorithm, attribute, value, result, serviceProviders); 617: result.retainAll(serviceProviders); // eval next retaining found providers 618: if (result.isEmpty()) // no point continuing 619: break; 620: } 621: 622: if (result.isEmpty()) 623: return null; 624: 625: return (Provider[]) result.toArray(new Provider[result.size()]); 626: } 627: 628: private static void selectProviders(String svc, String algo, String attr, 629: String val, LinkedHashSet providerSet, 630: LinkedHashSet result) 631: { 632: result.clear(); // ensure we start with an empty result set 633: for (Iterator i = providerSet.iterator(); i.hasNext(); ) 634: { 635: Provider p = (Provider) i.next(); 636: if (provides(p, svc, algo, attr, val)) 637: result.add(p); 638: } 639: } 640: 641: private static boolean provides(Provider p, String svc, String algo, 642: String attr, String val) 643: { 644: Iterator it; 645: String serviceDotAlgorithm = null; 646: String key = null; 647: String realVal; 648: boolean found = false; 649: // if <svc>.<algo> <attr> is in the set then so is <svc>.<algo> 650: // but it may be stored under an alias <algo>. resolve 651: outer: for (int r = 0; r < 3; r++) // guard against circularity 652: { 653: serviceDotAlgorithm = (svc+"."+String.valueOf(algo)).trim(); 654: for (it = p.keySet().iterator(); it.hasNext(); ) 655: { 656: key = (String) it.next(); 657: if (key.equalsIgnoreCase(serviceDotAlgorithm)) // eureka 658: { 659: found = true; 660: break outer; 661: } 662: // it may be there but as an alias 663: if (key.equalsIgnoreCase(ALG_ALIAS + serviceDotAlgorithm)) 664: { 665: algo = p.getProperty(key); 666: continue outer; 667: } 668: // else continue inner 669: } 670: } 671: 672: if (!found) 673: return false; 674: 675: // found a candidate for the querry. do we have an attr to match? 676: if (val == null) // <service>.<algorithm> querry 677: return true; 678: 679: // <service>.<algorithm> <attribute>; find the key entry that match 680: String realAttr; 681: int limit = serviceDotAlgorithm.length() + 1; 682: for (it = p.keySet().iterator(); it.hasNext(); ) 683: { 684: key = (String) it.next(); 685: if (key.length() <= limit) 686: continue; 687: 688: if (key.substring(0, limit).equalsIgnoreCase(serviceDotAlgorithm+" ")) 689: { 690: realAttr = key.substring(limit).trim(); 691: if (! realAttr.equalsIgnoreCase(attr)) 692: continue; 693: 694: // eveything matches so far. do the value 695: realVal = p.getProperty(key); 696: if (realVal == null) 697: return false; 698: 699: realVal = realVal.trim(); 700: // is it a string value? 701: if (val.equalsIgnoreCase(realVal)) 702: return true; 703: 704: // assume value is a number. cehck for greater-than-or-equal 705: return (Integer.parseInt(val) >= Integer.parseInt(realVal)); 706: } 707: } 708: 709: return false; 710: } 711: }