View Javadoc
1   /*
2    * Copyright (c) 2000, 2009, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  package sun.security.jgss;
27  
28  import java.lang.reflect.InvocationTargetException;
29  import org.ietf.jgss.*;
30  import java.security.AccessController;
31  import java.security.Provider;
32  import java.security.Security;
33  import java.util.ArrayList;
34  import java.util.HashSet;
35  import java.util.HashMap;
36  import java.util.Enumeration;
37  import java.util.Iterator;
38  import sun.security.jgss.spi.*;
39  import sun.security.jgss.wrapper.NativeGSSFactory;
40  import sun.security.jgss.wrapper.SunNativeProvider;
41  import sun.security.action.GetPropertyAction;
42  
43  /**
44   * This class stores the list of providers that this
45   * GSS-Implementation is configured to use. The GSSManagerImpl class
46   * queries this class whenever it needs a mechanism's factory.<p>
47   *
48   * This class stores an ordered list of pairs of the form
49   * <provider, oid>. When it attempts to instantiate a mechanism
50   * defined by oid o, it steps through the list looking for an entry
51   * with oid=o, or with oid=null. (An entry with oid=null matches all
52   * mechanisms.) When it finds such an entry, the corresponding
53   * provider is approached for the mechanism's factory class.
54   * At instantiation time this list in initialized to contain those
55   * system wide providers that contain a property of the form
56   * "GssApiMechanism.x.y.z..." where "x.y.z..." is a numeric object
57   * identifier with numbers x, y, z, etc. Such a property is defined
58   * to map to that provider's implementation of the MechanismFactory
59   * interface for the mechanism x.y.z...
60   * As and when a MechanismFactory is instantiated, it is
61   * cached for future use. <p>
62   *
63   * An application can cause more providers to be added by means of
64   * the addProviderAtFront and addProviderAtEnd methods on
65   * GSSManager which get delegated to this class. The
66   * addProviderAtFront method can also cause a change in the ordering
67   * of the providers without adding any new providers, by causing a
68   * provider to move up in a list. The method addProviderAtEnd can
69   * only add providers at the end of the list if they are not already
70   * in the list. The rationale is that an application will call
71   * addProviderAtFront when it wants a provider to be used in
72   * preference over the default ones. And it will call
73   * addProviderAtEnd when it wants a provider to be used in case
74   * the system ones don't suffice.<p>
75   *
76   * If a mechanism's factory is being obtained from a provider as a
77   * result of encountering a entryof the form <provider, oid> where
78   * oid is non-null, then the assumption is that the application added
79   * this entry and it wants this mechanism to be obtained from this
80   * provider. Thus is the provider does not actually contain the
81   * requested mechanism, an exception will be thrown. However, if the
82   * entry were of the form <provider, null>, then it is viewed more
83   * liberally and is simply skipped over if the provider does not claim to
84   * support the requested mechanism.
85   */
86  
87  public final class ProviderList {
88  
89      private static final String PROV_PROP_PREFIX = "GssApiMechanism.";
90      private static final int PROV_PROP_PREFIX_LEN =
91          PROV_PROP_PREFIX.length();
92  
93      private static final String SPI_MECH_FACTORY_TYPE
94          = "sun.security.jgss.spi.MechanismFactory";
95  
96      // Undocumented property?
97      private static final String DEFAULT_MECH_PROP =
98          "sun.security.jgss.mechanism";
99  
100     public static final Oid DEFAULT_MECH_OID;
101 
102     static {
103         /*
104          * Set the default mechanism. Kerberos v5 is the default
105          * mechanism unless it is overridden by a system property.
106          * with a valid OID value
107          */
108         Oid defOid = null;
109         String defaultOidStr = AccessController.doPrivileged
110             (new GetPropertyAction(DEFAULT_MECH_PROP));
111         if (defaultOidStr != null) {
112             defOid = GSSUtil.createOid(defaultOidStr);
113         }
114         DEFAULT_MECH_OID =
115             (defOid == null ? GSSUtil.GSS_KRB5_MECH_OID : defOid);
116    }
117 
118     private ArrayList<PreferencesEntry> preferences =
119                         new ArrayList<PreferencesEntry>(5);
120     private HashMap<PreferencesEntry, MechanismFactory> factories =
121                         new HashMap<PreferencesEntry, MechanismFactory>(5);
122     private HashSet<Oid> mechs = new HashSet<Oid>(5);
123 
124     final private GSSCaller caller;
125 
126     public ProviderList(GSSCaller caller, boolean useNative) {
127         this.caller = caller;
128         Provider[] provList;
129         if (useNative) {
130             provList = new Provider[1];
131             provList[0] = new SunNativeProvider();
132         } else {
133             provList = Security.getProviders();
134         }
135 
136         for (int i = 0; i < provList.length; i++) {
137             Provider prov = provList[i];
138             try {
139                 addProviderAtEnd(prov, null);
140             } catch (GSSException ge) {
141                 // Move on to the next provider
142                 GSSUtil.debug("Error in adding provider " +
143                               prov.getName() + ": " + ge);
144             }
145         } // End of for loop
146     }
147 
148     /**
149      * Determines if the given provider property represents a GSS-API
150      * Oid to MechanismFactory mapping.
151      * @return true if this is a GSS-API property, false otherwise.
152      */
153     private boolean isMechFactoryProperty(String prop) {
154         return (prop.startsWith(PROV_PROP_PREFIX) ||
155                 prop.regionMatches(true, 0, // Try ignoring case
156                                    PROV_PROP_PREFIX, 0,
157                                    PROV_PROP_PREFIX_LEN));
158     }
159 
160     private Oid getOidFromMechFactoryProperty(String prop)
161         throws GSSException {
162 
163         String oidPart = prop.substring(PROV_PROP_PREFIX_LEN);
164         return new Oid(oidPart);
165     }
166 
167     // So the existing code do not have to be changed
168     synchronized public MechanismFactory getMechFactory(Oid mechOid)
169         throws GSSException {
170         if (mechOid == null) mechOid = ProviderList.DEFAULT_MECH_OID;
171         return getMechFactory(mechOid, null);
172     }
173 
174     /**
175      * Obtains a MechanismFactory for a given mechanism. If the
176      * specified provider is not null, then the impl from the
177      * provider is used. Otherwise, the most preferred impl based
178      * on the configured preferences is used.
179      * @param mechOid the oid of the desired mechanism
180      * @return a MechanismFactory for the desired mechanism.
181      * @throws GSSException when the specified provider does not
182      * support the desired mechanism, or when no provider supports
183      * the desired mechanism.
184      */
185     synchronized public MechanismFactory getMechFactory(Oid mechOid,
186                                                         Provider p)
187         throws GSSException {
188 
189         if (mechOid == null) mechOid = ProviderList.DEFAULT_MECH_OID;
190 
191         if (p == null) {
192             // Iterate thru all preferences to find right provider
193             String className;
194             PreferencesEntry entry;
195 
196             Iterator<PreferencesEntry> list = preferences.iterator();
197             while (list.hasNext()) {
198                 entry = list.next();
199                 if (entry.impliesMechanism(mechOid)) {
200                     MechanismFactory retVal = getMechFactory(entry, mechOid);
201                     if (retVal != null) return retVal;
202                 }
203             } // end of while loop
204             throw new GSSExceptionImpl(GSSException.BAD_MECH, mechOid);
205         } else {
206             // Use the impl from the specified provider; return null if the
207             // the mech is unsupported by the specified provider.
208             PreferencesEntry entry = new PreferencesEntry(p, mechOid);
209             return getMechFactory(entry, mechOid);
210         }
211     }
212 
213     /**
214      * Helper routine that uses a preferences entry to obtain an
215      * implementation of a MechanismFactory from it.
216      * @param e the preferences entry that contains the provider and
217      * either a null of an explicit oid that matched the oid of the
218      * desired mechanism.
219      * @param mechOid the oid of the desired mechanism
220      * @throws GSSException If the application explicitly requested
221      * this entry's provider to be used for the desired mechanism but
222      * some problem is encountered
223      */
224     private MechanismFactory getMechFactory(PreferencesEntry e, Oid mechOid)
225         throws GSSException {
226         Provider p = e.getProvider();
227 
228         /*
229          * See if a MechanismFactory was previously instantiated for
230          * this provider and mechanism combination.
231          */
232         PreferencesEntry searchEntry = new PreferencesEntry(p, mechOid);
233         MechanismFactory retVal = factories.get(searchEntry);
234         if (retVal == null) {
235             /*
236              * Apparently not. Now try to instantiate this class from
237              * the provider.
238              */
239             String prop = PROV_PROP_PREFIX + mechOid.toString();
240             String className = p.getProperty(prop);
241             if (className != null) {
242                 retVal = getMechFactoryImpl(p, className, mechOid, caller);
243                 factories.put(searchEntry, retVal);
244             } else {
245                 /*
246                  * This provider does not support this mechanism.
247                  * If the application explicitly requested that
248                  * this provider be used for this mechanism, then
249                  * throw an exception
250                  */
251                 if (e.getOid() != null) {
252                     throw new GSSExceptionImpl(GSSException.BAD_MECH,
253                          "Provider " + p.getName() +
254                          " does not support mechanism " + mechOid);
255                 }
256             }
257         }
258         return retVal;
259     }
260 
261     /**
262      * Helper routine to obtain a MechanismFactory implementation
263      * from the same class loader as the provider of this
264      * implementation.
265      * @param p the provider whose classloader must be used for
266      * instantiating the desired MechanismFactory
267      * @ param className the name of the MechanismFactory class
268      * @throws GSSException If some error occurs when trying to
269      * instantiate this MechanismFactory.
270      */
271     private static MechanismFactory getMechFactoryImpl(Provider p,
272                                                        String className,
273                                                        Oid mechOid,
274                                                        GSSCaller caller)
275         throws GSSException {
276 
277         try {
278             Class<?> baseClass = Class.forName(SPI_MECH_FACTORY_TYPE);
279 
280             /*
281              * Load the implementation class with the same class loader
282              * that was used to load the provider.
283              * In order to get the class loader of a class, the
284              * caller's class loader must be the same as or an ancestor of
285              * the class loader being returned. Otherwise, the caller must
286              * have "getClassLoader" permission, or a SecurityException
287              * will be thrown.
288              */
289 
290             ClassLoader cl = p.getClass().getClassLoader();
291             Class<?> implClass;
292             if (cl != null) {
293                 implClass = cl.loadClass(className);
294             } else {
295                 implClass = Class.forName(className);
296             }
297 
298             if (baseClass.isAssignableFrom(implClass)) {
299 
300                 java.lang.reflect.Constructor<?> c =
301                                 implClass.getConstructor(GSSCaller.class);
302                 MechanismFactory mf = (MechanismFactory) (c.newInstance(caller));
303 
304                 if (mf instanceof NativeGSSFactory) {
305                     ((NativeGSSFactory) mf).setMech(mechOid);
306                 }
307                 return mf;
308             } else {
309                 throw createGSSException(p, className, "is not a " +
310                                          SPI_MECH_FACTORY_TYPE, null);
311             }
312         } catch (ClassNotFoundException e) {
313             throw createGSSException(p, className, "cannot be created", e);
314         } catch (NoSuchMethodException e) {
315             throw createGSSException(p, className, "cannot be created", e);
316         } catch (InvocationTargetException e) {
317             throw createGSSException(p, className, "cannot be created", e);
318         } catch (InstantiationException e) {
319             throw createGSSException(p, className, "cannot be created", e);
320         } catch (IllegalAccessException e) {
321             throw createGSSException(p, className, "cannot be created", e);
322         } catch (SecurityException e) {
323             throw createGSSException(p, className, "cannot be created", e);
324         }
325     }
326 
327     // Only used by getMechFactoryImpl
328     private static GSSException createGSSException(Provider p,
329                                                    String className,
330                                                    String trailingMsg,
331                                                    Exception cause) {
332         String errClassInfo = className + " configured by " +
333             p.getName() + " for GSS-API Mechanism Factory ";
334         return new GSSExceptionImpl(GSSException.BAD_MECH,
335                                     errClassInfo + trailingMsg,
336                                     cause);
337     }
338 
339     public Oid[] getMechs() {
340         return mechs.toArray(new Oid[] {});
341     }
342 
343     synchronized public void addProviderAtFront(Provider p, Oid mechOid)
344         throws GSSException {
345 
346         PreferencesEntry newEntry = new PreferencesEntry(p, mechOid);
347         PreferencesEntry oldEntry;
348         boolean foundSomeMech;
349 
350         Iterator<PreferencesEntry> list = preferences.iterator();
351         while (list.hasNext()) {
352             oldEntry = list.next();
353             if (newEntry.implies(oldEntry))
354                 list.remove();
355         }
356 
357         if (mechOid == null) {
358             foundSomeMech = addAllMechsFromProvider(p);
359         } else {
360             String oidStr = mechOid.toString();
361             if (p.getProperty(PROV_PROP_PREFIX + oidStr) == null)
362                 throw new GSSExceptionImpl(GSSException.BAD_MECH,
363                                            "Provider " + p.getName()
364                                            + " does not support "
365                                            + oidStr);
366             mechs.add(mechOid);
367             foundSomeMech = true;
368         }
369 
370         if (foundSomeMech) {
371             preferences.add(0, newEntry);
372         }
373     }
374 
375     synchronized public void addProviderAtEnd(Provider p, Oid mechOid)
376         throws GSSException {
377 
378         PreferencesEntry newEntry = new PreferencesEntry(p, mechOid);
379         PreferencesEntry oldEntry;
380         boolean foundSomeMech;
381 
382         Iterator<PreferencesEntry> list = preferences.iterator();
383         while (list.hasNext()) {
384             oldEntry = list.next();
385             if (oldEntry.implies(newEntry))
386                 return;
387         }
388 
389         // System.out.println("addProviderAtEnd: No it is not redundant");
390 
391         if (mechOid == null)
392             foundSomeMech = addAllMechsFromProvider(p);
393         else {
394             String oidStr = mechOid.toString();
395             if (p.getProperty(PROV_PROP_PREFIX + oidStr) == null)
396                 throw new GSSExceptionImpl(GSSException.BAD_MECH,
397                                        "Provider " + p.getName()
398                                        + " does not support "
399                                        + oidStr);
400             mechs.add(mechOid);
401             foundSomeMech = true;
402         }
403 
404         if (foundSomeMech) {
405             preferences.add(newEntry);
406         }
407     }
408 
409     /**
410      * Helper routine to go through all properties contined in a
411      * provider and add its mechanisms to the list of supported
412      * mechanisms. If no default mechanism has been assinged so far,
413      * it sets the default MechanismFactory and Oid as well.
414      * @param p the provider to query
415      * @return true if there is at least one mechanism that this
416      * provider contributed, false otherwise
417      */
418     private boolean addAllMechsFromProvider(Provider p) {
419 
420         String prop;
421         boolean retVal = false;
422 
423         // Get all props for this provider
424         Enumeration<Object> props = p.keys();
425 
426         // See if there are any GSS prop's
427         while (props.hasMoreElements()) {
428             prop = (String) props.nextElement();
429             if (isMechFactoryProperty(prop)) {
430                 // Ok! This is a GSS provider!
431                 try {
432                     Oid mechOid = getOidFromMechFactoryProperty(prop);
433                     mechs.add(mechOid);
434                     retVal = true;
435                 } catch (GSSException e) {
436                     // Skip to next property
437                     GSSUtil.debug("Ignore the invalid property " +
438                                   prop + " from provider " + p.getName());
439                 }
440             } // Processed GSS property
441         } // while loop
442 
443         return retVal;
444 
445     }
446 
447     /**
448      * Stores a provider and a mechanism oid indicating that the
449      * provider should be used for the mechanism. If the mechanism
450      * Oid is null, then it indicates that this preference holds for
451      * any mechanism.<p>
452      *
453      * The ProviderList maintains an ordered list of
454      * PreferencesEntry's and iterates thru them as it tries to
455      * instantiate MechanismFactory's.
456      */
457     private static final class PreferencesEntry {
458         private Provider p;
459         private Oid oid;
460         PreferencesEntry(Provider p, Oid oid) {
461             this.p = p;
462             this.oid = oid;
463         }
464 
465         public boolean equals(Object other) {
466             if (this == other) {
467                 return true;
468             }
469 
470             if (!(other instanceof PreferencesEntry)) {
471                 return false;
472             }
473 
474             PreferencesEntry that = (PreferencesEntry)other;
475             if (this.p.getName().equals(that.p.getName())) {
476                 if (this.oid != null && that.oid != null) {
477                     return this.oid.equals(that.oid);
478                 } else {
479                     return (this.oid == null && that.oid == null);
480                 }
481             }
482 
483             return false;
484         }
485 
486         public int hashCode() {
487             int result = 17;
488 
489             result = 37 * result + p.getName().hashCode();
490             if (oid != null) {
491                 result = 37 * result + oid.hashCode();
492             }
493 
494             return result;
495         }
496 
497         /**
498          * Determines if a preference implies another. A preference
499          * implies another if the latter is subsumed by the
500          * former. e.g., <Provider1, null> implies <Provider1, OidX>
501          * because the null in the former indicates that it should
502          * be used for all mechanisms.
503          */
504         boolean implies(Object other) {
505 
506             if (other instanceof PreferencesEntry) {
507                 PreferencesEntry temp = (PreferencesEntry) other;
508                 return (equals(temp) ||
509                         p.getName().equals(temp.p.getName()) &&
510                         oid == null);
511             } else {
512                 return false;
513             }
514         }
515 
516         Provider getProvider() {
517             return p;
518         }
519 
520         Oid getOid() {
521             return oid;
522         }
523 
524         /**
525          * Determines if this entry is applicable to the desired
526          * mechanism. The entry is applicable to the desired mech if
527          * it contains the same oid or if it contains a null oid
528          * indicating that it is applicable to all mechs.
529          * @param mechOid the desired mechanism
530          * @return true if the provider in this entry should be
531          * queried for this mechanism.
532          */
533         boolean impliesMechanism(Oid oid) {
534             return (this.oid == null || this.oid.equals(oid));
535         }
536 
537         // For debugging
538         public String toString() {
539             StringBuffer buf = new StringBuffer("<");
540             buf.append(p.getName());
541             buf.append(", ");
542             buf.append(oid);
543             buf.append(">");
544             return buf.toString();
545         }
546     }
547 }