001 /* Security.java --- Java base security class implementation 002 Copyright (C) 1999, 2001, 2002, 2003, 2004, 2005, 2006 003 Free Software Foundation, Inc. 004 005 This file is part of GNU Classpath. 006 007 GNU Classpath is free software; you can redistribute it and/or modify 008 it under the terms of the GNU General Public License as published by 009 the Free Software Foundation; either version 2, or (at your option) 010 any later version. 011 012 GNU Classpath is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of 014 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 General Public License for more details. 016 017 You should have received a copy of the GNU General Public License 018 along with GNU Classpath; see the file COPYING. If not, write to the 019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 020 02110-1301 USA. 021 022 Linking this library statically or dynamically with other modules is 023 making a combined work based on this library. Thus, the terms and 024 conditions of the GNU General Public License cover the whole 025 combination. 026 027 As a special exception, the copyright holders of this library give you 028 permission to link this library with independent modules to produce an 029 executable, regardless of the license terms of these independent 030 modules, and to copy and distribute the resulting executable under 031 terms of your choice, provided that you also meet, for each linked 032 independent module, the terms and conditions of the license of that 033 module. An independent module is a module which is not derived from 034 or based on this library. If you modify this library, you may extend 035 this exception to your version of the library, but you are not 036 obligated to do so. If you do not wish to do so, delete this 037 exception statement from your version. */ 038 039 040 package java.security; 041 042 import gnu.classpath.SystemProperties; 043 044 import gnu.classpath.Configuration; 045 import gnu.classpath.VMStackWalker; 046 047 import java.io.IOException; 048 import java.io.InputStream; 049 import java.net.URL; 050 import java.util.Collections; 051 import java.util.Enumeration; 052 import java.util.HashMap; 053 import java.util.HashSet; 054 import java.util.Iterator; 055 import java.util.LinkedHashSet; 056 import java.util.Map; 057 import java.util.Properties; 058 import java.util.Set; 059 import java.util.Vector; 060 061 /** 062 * This class centralizes all security properties and common security methods. 063 * One of its primary uses is to manage security providers. 064 * 065 * @author Mark Benvenuto (ivymccough@worldnet.att.net) 066 */ 067 public final class Security 068 { 069 private static final String ALG_ALIAS = "Alg.Alias."; 070 071 private static Vector providers = new Vector(); 072 private static Properties secprops = new Properties(); 073 074 static 075 { 076 String base = SystemProperties.getProperty("gnu.classpath.home.url"); 077 String vendor = SystemProperties.getProperty("gnu.classpath.vm.shortname"); 078 079 // Try VM specific security file 080 boolean loaded = loadProviders (base, vendor); 081 082 // Append classpath standard provider if possible 083 if (!loadProviders (base, "classpath") 084 && !loaded 085 && providers.size() == 0) 086 { 087 if (Configuration.DEBUG) 088 { 089 /* No providers found and both security files failed to 090 * load properly. Give a warning in case of DEBUG is 091 * enabled. Could be done with java.util.logging later. 092 */ 093 System.err.println 094 ("WARNING: could not properly read security provider files:"); 095 System.err.println 096 (" " + base + "/security/" + vendor 097 + ".security"); 098 System.err.println 099 (" " + 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 (new Integer(val).intValue() >= new Integer(realVal).intValue()); 706 } 707 } 708 709 return false; 710 } 711 }