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>&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    }