001/* Security.java --- Java base security class implementation
002   Copyright (C) 1999, 2001, 2002, 2003, 2004, 2005, 2006
003   Free Software Foundation, Inc.
004
005This file is part of GNU Classpath.
006
007GNU Classpath is free software; you can redistribute it and/or modify
008it under the terms of the GNU General Public License as published by
009the Free Software Foundation; either version 2, or (at your option)
010any later version.
011
012GNU Classpath is distributed in the hope that it will be useful, but
013WITHOUT ANY WARRANTY; without even the implied warranty of
014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015General Public License for more details.
016
017You should have received a copy of the GNU General Public License
018along with GNU Classpath; see the file COPYING.  If not, write to the
019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02002110-1301 USA.
021
022Linking this library statically or dynamically with other modules is
023making a combined work based on this library.  Thus, the terms and
024conditions of the GNU General Public License cover the whole
025combination.
026
027As a special exception, the copyright holders of this library give you
028permission to link this library with independent modules to produce an
029executable, regardless of the license terms of these independent
030modules, and to copy and distribute the resulting executable under
031terms of your choice, provided that you also meet, for each linked
032independent module, the terms and conditions of the license of that
033module.  An independent module is a module which is not derived from
034or based on this library.  If you modify this library, you may extend
035this exception to your version of the library, but you are not
036obligated to do so.  If you do not wish to do so, delete this
037exception statement from your version. */
038
039
040package java.security;
041
042import gnu.classpath.SystemProperties;
043
044import gnu.classpath.Configuration;
045import gnu.classpath.VMStackWalker;
046
047import java.io.IOException;
048import java.io.InputStream;
049import java.net.URL;
050import java.util.Collections;
051import java.util.Enumeration;
052import java.util.HashMap;
053import java.util.HashSet;
054import java.util.Iterator;
055import java.util.LinkedHashSet;
056import java.util.Map;
057import java.util.Properties;
058import java.util.Set;
059import 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 */
067public 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>&lt;crypto_service&gt;.&lt;algorithm_or_type&gt;: 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>&lt;crypto_service&gt;.&lt;algorithm_or_type&gt; &lt;attribute_name&gt;:&lt;value&gt;:
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>&lt;crypto_service&gt;.&lt;algorithm_or_type&gt;: 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>&lt;crypto_service&gt;.&lt;algorithm_or_type&gt; &lt;attribute_name&gt;:
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}