KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > key > KeyManager


1 /*
2  * Copyright 2006 Schlichtherle IT Services
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16
17 package de.schlichtherle.key;
18
19 import java.awt.GraphicsEnvironment JavaDoc;
20 import java.lang.reflect.UndeclaredThrowableException JavaDoc;
21 import java.util.*;
22
23 /**
24  * An abstract class which maintains a static map of {@link KeyProvider}
25  * instances for any protected resource which clients need to create or open.
26  * This key manager class is designed to be of general purpose:
27  * Resources are simply represented by a string as their identifier, called
28  * the <i>resource identifier</i> or <i>resource ID</i> for short.
29  * For each resource ID, a key provider may be associated to it which handles
30  * the actual retrieval of the key.
31  * <p>
32  * Clients need to call {@link #getInstance} to get the default instance.
33  * Because the map of key providers and some associated methods are static
34  * members of this class, the default instance of this class may be changed
35  * dynamically (using {@link #setInstance}) without affecting already mapped
36  * key providers.
37  * This allows to change other aspects of the implementation dynamically
38  * (the user interface for example) without affecting the key providers and hence the
39  * keys.
40  * <p>
41  * Implementations need to subclass this class and provide a public
42  * no-arguments constructor.
43  * Finally, an instance of the implementation must be installed either by
44  * calling {@link #setInstance(KeyManager)} or by setting the system property
45  * <code>de.schlichtherle.key.KeyManager</code> to the fully qualified class
46  * name of the implementation before this class is ever used.
47  * In the latter case, the class will be loaded using the context class loader
48  * of the current thread.
49  * <p>
50  * Note that class loading and instantiation may happen in a JVM shutdown hook,
51  * so class initializers and constructors must behave accordingly.
52  * In particular, it's not permitted to construct or use a Swing GUI there.
53  * <p>
54  * This class is thread safe.
55  *
56  * @author Christian Schlichtherle
57  * @version @version@
58  * @since TrueZIP 6.0
59  */

60 public abstract class KeyManager {
61
62     private static volatile KeyManager keyManager;
63
64     /**
65      * Maps resource IDs [String] -> providers [KeyProvider]
66      */

67     private static final Map providers = new HashMap();
68
69     private final Map providerTypes = new HashMap();
70
71     //
72
// Static Methods.
73
//
74

75     /**
76      * Returns the default instance of the key manager.
77      * <p>
78      * If the default instance has been explicitly set using
79      * {@link #setInstance}, then this instance is returned.
80      * <p>
81      * Otherwise, the value of the system property
82      * <code>de.schlichtherle.key.KeyManager</code> is considered:
83      * <p>
84      * If this system property is set, it must denote the fully qualified
85      * class name of a subclass of this class. The class is loaded by name
86      * using the current thread's context class loader and instantiated using
87      * its public, no-arguments constructor.
88      * <p>
89      * Otherwise, if the JVM is running in headless mode and the API conforms
90      * to JSE 6 (where the class <code>java.io.Console</code> is available),
91      * then the console I/O based implementation in the class
92      * {@link de.schlichtherle.key.passwd.console.PromptingKeyManager}
93      * is loaded by name using the current thread's context class loader and
94      * instantiated using its public, no-arguments constructor.
95      * <p>
96      * Otherwise, the Swing based implementation in the class
97      * {@link de.schlichtherle.key.passwd.swing.PromptingKeyManager}
98      * is loaded by name using the current thread's context class loader and
99      * instantiated using its public, no-arguments constructor.
100      * <p>
101      * In order to support this plug-in architecture, you should <em>not</em>
102      * cache the instance returned by this method!
103      *
104      * @throws ClassCastException If the class name in the system property
105      * does not denote a subclass of this class.
106      * @throws UndeclaredThrowableException If any other precondition on the
107      * value of the system property does not hold.
108      */

109     public static synchronized KeyManager getInstance() {
110         if (keyManager != null)
111             return keyManager;
112
113         final String JavaDoc cn = System.getProperty(
114                 "de.schlichtherle.key.KeyManager",
115                 getDefaultKeyManagerClassName());
116         try {
117             final Class JavaDoc c = Thread.currentThread()
118                     .getContextClassLoader().loadClass(cn);
119             keyManager = (KeyManager) c.newInstance();
120         } catch (ClassNotFoundException JavaDoc failure) {
121             throw new UndeclaredThrowableException JavaDoc(failure);
122         } catch (InstantiationException JavaDoc failure) {
123             throw new UndeclaredThrowableException JavaDoc(failure);
124         } catch (IllegalAccessException JavaDoc failure) {
125             throw new UndeclaredThrowableException JavaDoc(failure);
126         }
127
128         return keyManager;
129     }
130
131     private static String JavaDoc getDefaultKeyManagerClassName() {
132         if (GraphicsEnvironment.isHeadless()) {
133             try {
134                 Class.forName("java.io.Console");
135                 return "de.schlichtherle.key.passwd.console.PromptingKeyManager";
136             } catch (ClassNotFoundException JavaDoc noJSE6ConsoleAvailable) {
137                 // Ignore and fall through - prompting will be disabled.
138
}
139         }
140
141         return "de.schlichtherle.key.passwd.swing.PromptingKeyManager";
142     }
143
144     /**
145      * Sets the default instance of the key manager explicitly.
146      *
147      * @param keyManager The key manager to use as the default instance.
148      * If this is set to <code>null</code>, on the next call to
149      * {@link #getInstance} the default instance will be recreated.
150      */

151     public static void setInstance(final KeyManager keyManager) {
152         KeyManager.keyManager = keyManager;
153     }
154
155     /**
156      * Maps the given key provider for the given resource identifier.
157      *
158      * @return The key provider previously mapped for the given resource
159      * identifier or <code>null</code> if no key provider was mapped.
160      * @throws NullPointerException If <code>resourceID</code> or
161      * <code>provider</code> is <code>null</code>.
162      */

163     static final synchronized KeyProvider mapKeyProvider(
164             String JavaDoc resourceID,
165             KeyProvider provider)
166     throws NullPointerException JavaDoc {
167         if (resourceID == null || provider == null)
168             throw new NullPointerException JavaDoc();
169         return (KeyProvider) providers.put(resourceID, provider);
170     }
171
172     /**
173      * Removes the key provider for the given resource identifier from the map.
174      *
175      * @return The key provider previously mapped for the given resource
176      * identifier or <code>null</code> if no key provider was mapped.
177      * @throws NullPointerException If <code>resourceID</code> is
178      * <code>null</code>.
179      */

180     static synchronized final KeyProvider unmapKeyProvider(String JavaDoc resourceID)
181     throws NullPointerException JavaDoc {
182         if (resourceID == null)
183             throw new NullPointerException JavaDoc();
184         return (KeyProvider) providers.remove(resourceID);
185     }
186
187     /**
188      * Resets the key provider for the given resource identifier, causing it
189      * to forget its common key.
190      * This works only if the key provider associated with the given resource
191      * identifier is an instance of {@link AbstractKeyProvider}.
192      * Otherwise, nothing happens.
193      *
194      * @param resourceID The resource identifier.
195      * @return Whether or not an instance of {@link AbstractKeyProvider}
196      * is mapped for the resource identifier and has been reset.
197      */

198     public static synchronized boolean resetKeyProvider(final String JavaDoc resourceID) {
199         final KeyProvider provider = (KeyProvider) providers.get(resourceID);
200         if (provider instanceof AbstractKeyProvider) {
201             final AbstractKeyProvider skp = (AbstractKeyProvider) provider;
202             skp.reset();
203             return true;
204         }
205         return false;
206     }
207
208     /**
209      * Resets the key provider for the given resource identifier, causing it
210      * to forget its common key, and throws the key provider away.
211      * If the key provider associated with the given resource identifier is
212      * not an instance of {@link AbstractKeyProvider}, it is just removed from
213      * the map.
214      *
215      * @param resourceID The resource identifier.
216      * @return Whether or not a key provider was mapped for the resource
217      * identifier and has been removed.
218      */

219     public static synchronized boolean resetAndRemoveKeyProvider(final String JavaDoc resourceID) {
220         final KeyProvider provider = (KeyProvider) providers.get(resourceID);
221         if (provider instanceof AbstractKeyProvider) {
222             final AbstractKeyProvider skp = (AbstractKeyProvider) provider;
223             skp.reset();
224             final KeyProvider result = skp.removeFromKeyManager(resourceID);
225             assert provider == result;
226             return true;
227         } else if (provider != null) {
228             final KeyProvider previous = unmapKeyProvider(resourceID);
229             assert provider == previous;
230             return true;
231         }
232         return false;
233     }
234     
235     /**
236      * Resets all key providers, causing them to forget their respective
237      * common key.
238      * If a mapped key provider is not an instance of {@link AbstractKeyProvider},
239      * nothing happens.
240      */

241     public static void resetKeyProviders() {
242         forEachKeyProvider(new KeyProviderCommand() {
243             public void run(String JavaDoc resourceID, KeyProvider provider) {
244                 if (provider instanceof AbstractKeyProvider) {
245                     ((AbstractKeyProvider) provider).reset();
246                 }
247             }
248         });
249     }
250
251     /**
252      * @deprecated Use {@link #resetAndRemoveKeyProviders} instead.
253      */

254     public static final void resetAndClearKeyProviders() {
255         resetAndRemoveKeyProviders();
256     }
257
258     /**
259      * Resets all key providers, causing them to forget their key, and removes
260      * them from the map.
261      * If the key provider associated with the given resource identifier is
262      * not an instance of {@link AbstractKeyProvider}, it is just removed from
263      * the map.
264      *
265      * @since TrueZIP 6.1
266      * @throws IllegalStateException If resetting or unmapping one or more
267      * key providers is prohibited by a constraint in a subclass of
268      * {@link AbstractKeyProvider}, in which case the respective key
269      * provider(s) are reset but remain mapped.
270      * The operation is continued normally for all other key providers.
271      * Please refer to the respective subclass documentation for
272      * more information about its constraint(s).
273      */

274     public static synchronized void resetAndRemoveKeyProviders() {
275         class ResetAndRemoveKeyProvider implements KeyProviderCommand {
276             IllegalStateException JavaDoc ise = null;
277
278             public void run(String JavaDoc resourceID, KeyProvider provider) {
279                 if (provider instanceof AbstractKeyProvider) {
280                     final AbstractKeyProvider skp = (AbstractKeyProvider) provider;
281                     skp.reset();
282                     try {
283                         skp.removeFromKeyManager(resourceID); // support proper clean up!
284
} catch (IllegalStateException JavaDoc exc) {
285                         ise = exc; // mark and forget any previous exception
286
}
287                 } else {
288                     final KeyProvider previous = unmapKeyProvider(resourceID);
289                     assert provider == previous;
290                 }
291             }
292         }
293         
294         final ResetAndRemoveKeyProvider cmd = new ResetAndRemoveKeyProvider();
295         forEachKeyProvider(cmd);
296         if (cmd.ise != null)
297             throw cmd.ise;
298     }
299
300     /**
301      * Executes a {@link KeyProviderCommand} for each mapped key provider.
302      * It is safe to call any method of this class within the command,
303      * even if it modifies the map of key providers.
304      */

305     protected static synchronized void forEachKeyProvider(
306             final KeyProviderCommand command) {
307         // We can't use an iterator because the command may modify the map.
308
// Otherwise, resetAndClearKeyProviders() would fail with a
309
// ConcurrentModificationException.
310
final Set entrySet = providers.entrySet();
311         final int n = entrySet.size();
312         final Map.Entry[] entries
313                 = (Map.Entry[]) entrySet.toArray(new Map.Entry[n]);
314         for (int i = 0; i < n; i++) {
315             final Map.Entry entry = entries[i];
316             final String JavaDoc resourceID = (String JavaDoc) entry.getKey();
317             final KeyProvider provider = (KeyProvider) entry.getValue();
318             command.run(resourceID, provider);
319         }
320     }
321
322     /**
323      * Implemented by sub classes to define commands which shall be executed
324      * on key providers with the {@link #forEachKeyProvider} method.
325      */

326     protected interface KeyProviderCommand {
327         void run(String JavaDoc resourceID, KeyProvider provider);
328     }
329
330     /**
331      * Moves a key provider from one resource identifier to another.
332      * This may be useful if a protected resource changes its identifier.
333      * For example, if the protected resource is a file, the most obvious
334      * identifier would be its canonical path name.
335      * Calling this method then allows you to rename a file without the need
336      * to retrieve its keys again, thereby possibly prompting (and confusing)
337      * the user.
338      *
339      * @return <code>true</code> if and only if the operation succeeded,
340      * which means that there was an instance of
341      * {@link KeyProvider} associated with
342      * <code>oldResourceID</code>.
343      * @throws NullPointerException If <code>oldResourceID</code> or
344      * <code>newResourceID</code> is <code>null</code>.
345      * @throws IllegalStateException If unmapping or mapping the key provider
346      * is prohibited by a constraint in a subclass of
347      * {@link AbstractKeyProvider}, in which case the transaction is
348      * rolled back before this exception is (re)thrown.
349      * Please refer to the respective subclass documentation for
350      * more information about its constraint(s).
351      */

352     public static synchronized boolean moveKeyProvider(
353             final String JavaDoc oldResourceID,
354             final String JavaDoc newResourceID)
355     throws NullPointerException JavaDoc, IllegalStateException JavaDoc {
356         if (oldResourceID == null || newResourceID == null)
357             throw new NullPointerException JavaDoc();
358
359         final KeyProvider provider = (KeyProvider) providers.get(oldResourceID);
360         if (provider == null)
361             return false;
362
363         if (provider instanceof AbstractKeyProvider) {
364             final AbstractKeyProvider skp = (AbstractKeyProvider) provider;
365             // Implement transactional behaviour.
366
skp.removeFromKeyManager(oldResourceID);
367             try {
368                 skp.addToKeyManager(newResourceID);
369             } catch (RuntimeException JavaDoc failure) {
370                 skp.addToKeyManager(oldResourceID);
371                 throw failure;
372             }
373         } else {
374             unmapKeyProvider(oldResourceID);
375             mapKeyProvider(newResourceID, provider);
376         }
377
378         return true;
379     }
380
381     //
382
// Instance methods:
383
//
384

385     /**
386      * Creates a new <code>KeyManager</code>.
387      * This class does <em>not</em> map any key provider types.
388      * This must be done in the subclass using {@link #mapKeyProviderType}.
389      */

390     public KeyManager() {
391     }
392     
393     /**
394      * Subclasses must use this method to register default implementation
395      * classes for the interfaces {@link KeyProvider} and {@link AesKeyProvider}
396      * and optionally other subinterfaces or subclasses of
397      * <code>KeyProvider</code>.
398      * This is best done in the constructor of the subclass.
399      *
400      * @param forKeyProviderType The type which shall be substituted with
401      * <code>useKeyProviderType</code> when determining a suitable
402      * run time type in <code>getKeyProvider(String, Class)</code>.
403      * @param useKeyProviderType The type which shall be substituted for
404      * <code>forKeyProviderType</code> when determining a suitable
405      * run time type in <code>getKeyProvider(String, Class)</code>.
406      * @throws NullPointerException If any of the parameters is
407      * <code>null</code>.
408      * @throws IllegalArgumentException If <code>forKeyProviderType</code>
409      * is not assignment compatible to the <code>KeyProvider</code>
410      * interface,
411      * or if <code>useKeyProviderType</code> is the same as
412      * <code>forKeyProviderType</code>,
413      * or if <code>useKeyProviderType</code> is not assignment
414      * compatible to <code>forKeyProviderType</code>,
415      * or if <code>useKeyProviderType</code> does not provide a
416      * public constructor with no parameters.
417      * @see #getKeyProvider(String, Class)
418      * @since TrueZIP 6.1
419      */

420     protected final synchronized void mapKeyProviderType(
421             final Class JavaDoc forKeyProviderType,
422             final Class JavaDoc useKeyProviderType) {
423         if (!KeyProvider.class.isAssignableFrom(forKeyProviderType)
424                 || !forKeyProviderType.isAssignableFrom(useKeyProviderType)
425                 || forKeyProviderType == useKeyProviderType)
426             throw new IllegalArgumentException JavaDoc(
427                     useKeyProviderType.getName()
428                     + " must be a subclass or implementation of "
429                     + forKeyProviderType.getName() + "!");
430         try {
431             useKeyProviderType.getConstructor(null);
432         } catch (NoSuchMethodException JavaDoc noPublicNullaryConstructor) {
433             final IllegalArgumentException JavaDoc iae = new IllegalArgumentException JavaDoc(
434                     useKeyProviderType.getName() + " (no public nullary constructor)");
435             iae.initCause(noPublicNullaryConstructor);
436             throw iae;
437         }
438         providerTypes.put(forKeyProviderType, useKeyProviderType);
439     }
440
441     /**
442      * Returns the {@link KeyProvider} for the given resource identifier.
443      * If no key provider is mapped, a new instance of the default
444      * <code>KeyProvider</code> implementation is mapped and returned.
445      * The default implementation is determined by the default instance
446      * of this class.
447      *
448      * @param resourceID The identifier of the protected resource.
449      * @return The {@link KeyProvider} mapped for the protected resource.
450      * @throws NullPointerException If <code>resourceID</code>
451      * is <code>null</code>.
452      * @see #getInstance
453      */

454     abstract public KeyProvider getKeyProvider(String JavaDoc resourceID)
455     throws NullPointerException JavaDoc;
456
457     /**
458      * Returns the {@link KeyProvider} for the given resource identifier.
459      * If no key provider is mapped, this key manager will determine an
460      * appropriate class which is assignment compatible to
461      * <code>keyProviderType</code> (but is not necessarily the same),
462      * instantiate it, map the instance for the protected resource and return
463      * it.
464      * <p>
465      * Client applications should specify an interface rather than an
466      * implementation as the <code>keyProviderType</code> in order to allow
467      * the key manager to instantiate a useful default implementation of this
468      * interface unless another key provider was already mapped for the
469      * protected resource.
470      * <p>
471      * <b>Example:</b>
472      * The following example asks the default key manager to provide a
473      * suitable implementation of the {@link AesKeyProvider} interface
474      * for a protected resource.
475      * <pre>
476      * String pathname = file.getCanonicalPath();
477      * KeyManager km = KeyManager.getInstance();
478      * KeyProvider kp = km.getKeyProvider(pathname, AesKeyProvider.class);
479      * Object key = kp.getCreateKey(); // may prompt the user
480      * int ks;
481      * if (kp instanceof AesKeyProvider) {
482      * // The run time type of the implementing class is determined
483      * // by the key manager.
484      * // Anyway, the AES key provider can be safely asked for a cipher
485      * // key strength.
486      * ks = ((AesKeyProvider) kp).getKeyStrength();
487      * } else {
488      * // Unfortunately, another key provider was already mapped for the
489      * // pathname before - use default key strength.
490      * ks = AesKeyProvider.KEY_STRENGTH_256;
491      * }
492      * </pre>.
493      *
494      * @param resourceID The identifier of the protected resource.
495      * @param keyProviderType Unless another key provider is already mapped
496      * for the protected resource, this denotes the root of the class
497      * hierarchy to which the run time type of the returned instance
498      * may belong.
499      * In case the key manager does not know a more suitable class in
500      * this hierarchy, this parameter must denote an implementation of
501      * the {@link KeyProvider} interface with a public no-argument
502      * constructor.
503      * @return The {@link KeyProvider} mapped for the protected resource.
504      * If no key provider has been previously mapped for the protected
505      * resource, the run time type of this instance is guaranteed to be
506      * assignment compatible to the given <code>keyProviderType</code>.
507      * @throws NullPointerException If <code>resourceID</code> or
508      * <code>keyProviderType</code> is <code>null</code>.
509      * @throws ClassCastException If no other key provider is mapped for the
510      * protected resource and the given class is not an implementation
511      * of the <code>KeyProvider</code> interface.
512      * @throws IllegalArgumentException If any other precondition on the
513      * parameter <code>keyProviderType</code> does not hold.
514      * @see #getInstance
515      */

516     public synchronized KeyProvider getKeyProvider(
517             final String JavaDoc resourceID,
518             Class JavaDoc keyProviderType)
519     throws NullPointerException JavaDoc, ClassCastException JavaDoc, IllegalArgumentException JavaDoc {
520         if (resourceID == null)
521             throw new NullPointerException JavaDoc();
522
523         synchronized (KeyManager.class) {
524             KeyProvider provider = (KeyProvider) providers.get(resourceID);
525             if (provider == null) {
526                 final Object JavaDoc value = providerTypes.get(keyProviderType);
527                 if (value != null)
528                     keyProviderType = (Class JavaDoc) value;
529                 try {
530                     provider = (KeyProvider) keyProviderType.newInstance();
531                 } catch (InstantiationException JavaDoc failure) {
532                     IllegalArgumentException JavaDoc iae = new IllegalArgumentException JavaDoc(
533                             keyProviderType.getName());
534                     iae.initCause(failure);
535                     throw iae;
536                 } catch (IllegalAccessException JavaDoc failure) {
537                     IllegalArgumentException JavaDoc iae = new IllegalArgumentException JavaDoc(
538                             keyProviderType.getName());
539                     iae.initCause(failure);
540                     throw iae;
541                 }
542                 setKeyProvider(resourceID, provider);
543             }
544
545             return provider;
546         }
547     }
548
549     /**
550      * Sets the key provider programmatically.
551      * <p>
552      * <b>Warning</b>: This method replaces any key provider previously
553      * associated with the given resource ID and installs it as the return
554      * value for {@link #getKeyProvider}.
555      * While this allows a reasonable level of flexibility, it may easily
556      * confuse users if they have already been prompted for a key by the
557      * previous provider before and may negatively affect the security if the
558      * provider is not properly guarded by the application.
559      * Use with caution only!
560      *
561      * @param resourceID The resource identifier to associate the key
562      * provider with.
563      * For an RAES encrypted ZIP file, this must be the canonical
564      * path name of the archive file.
565      * @param provider The key provider for <code>resourceID</code>.
566      * For an RAES encrypted ZIP file, this must be an instance of
567      * the {@link AesKeyProvider} interface.
568      * @throws NullPointerException If <code>resourceID</code> or
569      * <code>provider</code> is <code>null</code>.
570      * @throws IllegalStateException If mapping this instance is prohibited
571      * by a constraint in a subclass of {@link AbstractKeyProvider}.
572      * Please refer to the respective subclass documentation for
573      * more information about its constraint(s).
574      */

575     public void setKeyProvider(
576             final String JavaDoc resourceID,
577             final KeyProvider provider)
578     throws NullPointerException JavaDoc, IllegalStateException JavaDoc {
579         /*if (resourceID == null || provider == null)
580             throw new NullPointerException();*/

581
582         if (provider instanceof AbstractKeyProvider) {
583             ((AbstractKeyProvider) provider).addToKeyManager(resourceID);
584         } else {
585             mapKeyProvider(resourceID, provider);
586         }
587     }
588 }
589
Popular Tags