All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.schlichtherle.key.KeyManager Maven / Gradle / Ivy

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

package de.schlichtherle.key;

import de.schlichtherle.util.ClassLoaders;
import java.awt.GraphicsEnvironment;
import java.lang.reflect.UndeclaredThrowableException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * An abstract class which maintains a static map of {@link KeyProvider}
 * instances for any protected resource which clients need to create or open.
 * This key manager class is designed to be of general purpose:
 * Resources are simply represented by a string as their identifier, called
 * the resource identifier or resource ID for short.
 * For each resource ID, a key provider may be associated to it which handles
 * the actual retrieval of the key.
 * 

* Clients need to call {@link #getInstance} to get the default instance. * Because the map of key providers and some associated methods are static * members of this class, the default instance of this class may be changed * dynamically (using {@link #setInstance}) without affecting already mapped * key providers. * This allows to change other aspects of the implementation dynamically * (the user interface for example) without affecting the key providers and hence the * keys. *

* Implementations need to subclass this class and provide a public * no-arguments constructor. * Finally, an instance of the implementation must be installed either by * calling {@link #setInstance(KeyManager)} or by setting the system property * {@code de.schlichtherle.key.KeyManager} to the fully qualified class * name of the implementation before this class is ever used. * In the latter case, the class will be loaded using the context class loader * of the current thread. *

* Note that class loading and instantiation may happen in a JVM shutdown hook, * so class initializers and constructors must behave accordingly. * In particular, it's not permitted to construct or use a Swing GUI there. *

* This class is thread safe. * * @author Christian Schlichtherle * @version $Id: KeyManager.java 5e709f50671d 2010/11/05 11:52:07 christian $ * @since TrueZIP 6.0 */ public class KeyManager { private static volatile KeyManager keyManager; /** * Maps resource IDs [String] -> providers [KeyProvider] */ private static final Map providers = new HashMap(); private final Map providerTypes = new HashMap(); // // Static Methods. // /** * Returns the default instance of the key manager. *

* If the default instance has been explicitly set using * {@link #setInstance}, then this instance is returned. *

* Otherwise, the value of the system property * {@code de.schlichtherle.key.KeyManager} is considered: *

* If this system property is set, it must denote the fully qualified * class name of a subclass of this class. The class is loaded by name * using the current thread's context class loader and instantiated using * its public, no-arguments constructor. *

* Otherwise, if the JVM is running in headless mode and the API conforms * to JSE 6 (where the class {@code java.io.Console} is available), * then the console I/O based implementation in the class * {@link de.schlichtherle.key.passwd.console.PromptingKeyManager} * is loaded by name using the current thread's context class loader and * instantiated using its public, no-arguments constructor. *

* Otherwise, the Swing based implementation in the class * {@link de.schlichtherle.key.passwd.swing.PromptingKeyManager} * is loaded by name using the current thread's context class loader and * instantiated using its public, no-arguments constructor. *

* In order to support this plug-in architecture, you should not * cache the instance returned by this method! * * @throws ClassCastException If the class name in the system property * does not denote a subclass of this class. * @throws UndeclaredThrowableException If any other precondition on the * value of the system property does not hold. */ public static synchronized KeyManager getInstance() { if (keyManager != null) return keyManager; final String n = System.getProperty( "de.schlichtherle.key.KeyManager", getDefaultKeyManagerClassName()); try { Class c = ClassLoaders.loadClass(n, KeyManager.class); keyManager = (KeyManager) c.newInstance(); } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new UndeclaredThrowableException(ex); } return keyManager; } private static String getDefaultKeyManagerClassName() { if (GraphicsEnvironment.isHeadless()) { try { Class.forName("java.io.Console"); return "de.schlichtherle.key.passwd.console.PromptingKeyManager"; } catch (ClassNotFoundException noJSE6ConsoleAvailable) { // Ignore and fall through - prompting will be disabled. } } return "de.schlichtherle.key.passwd.swing.PromptingKeyManager"; } /** * Sets the default instance of the key manager explicitly. * * @param keyManager The key manager to use as the default instance. * If this is set to {@code null}, on the next call to * {@link #getInstance} the default instance will be recreated. */ public static void setInstance(final KeyManager keyManager) { KeyManager.keyManager = keyManager; } /** * Maps the given key provider for the given resource identifier. * * @return The key provider previously mapped for the given resource * identifier or {@code null} if no key provider was mapped. * @throws NullPointerException If {@code resourceID} or * {@code provider} is {@code null}. */ static final synchronized KeyProvider mapKeyProvider( String resourceID, KeyProvider provider) throws NullPointerException { if (resourceID == null || provider == null) throw new NullPointerException(); return (KeyProvider) providers.put(resourceID, provider); } /** * Removes the key provider for the given resource identifier from the map. * * @return The key provider previously mapped for the given resource * identifier or {@code null} if no key provider was mapped. * @throws NullPointerException If {@code resourceID} is * {@code null}. */ static synchronized final KeyProvider unmapKeyProvider(String resourceID) throws NullPointerException { if (resourceID == null) throw new NullPointerException(); return (KeyProvider) providers.remove(resourceID); } /** * Resets the key provider for the given resource identifier, causing it * to forget its common key. * This works only if the key provider associated with the given resource * identifier is an instance of {@link AbstractKeyProvider}. * Otherwise, nothing happens. * * @param resourceID The resource identifier. * @return Whether or not an instance of {@link AbstractKeyProvider} * is mapped for the resource identifier and has been reset. */ public static synchronized boolean resetKeyProvider(final String resourceID) { final KeyProvider provider = (KeyProvider) providers.get(resourceID); if (provider instanceof AbstractKeyProvider) { final AbstractKeyProvider skp = (AbstractKeyProvider) provider; skp.reset(); return true; } return false; } /** * Resets the key provider for the given resource identifier, causing it * to forget its common key, and throws the key provider away. * If the key provider associated with the given resource identifier is * not an instance of {@link AbstractKeyProvider}, it is just removed from * the map. * * @param resourceID The resource identifier. * @return Whether or not a key provider was mapped for the resource * identifier and has been removed. */ public static synchronized boolean resetAndRemoveKeyProvider(final String resourceID) { final KeyProvider provider = (KeyProvider) providers.get(resourceID); if (provider instanceof AbstractKeyProvider) { final AbstractKeyProvider skp = (AbstractKeyProvider) provider; skp.reset(); final KeyProvider result = skp.removeFromKeyManager(resourceID); assert provider == result; return true; } else if (provider != null) { final KeyProvider previous = unmapKeyProvider(resourceID); assert provider == previous; return true; } return false; } /** * Resets all key providers, causing them to forget their respective * common key. * If a mapped key provider is not an instance of {@link AbstractKeyProvider}, * nothing happens. */ public static void resetKeyProviders() { forEachKeyProvider(new KeyProviderCommand() { public void run(String resourceID, KeyProvider provider) { if (provider instanceof AbstractKeyProvider) { ((AbstractKeyProvider) provider).reset(); } } }); } /** * @deprecated Use {@link #resetAndRemoveKeyProviders} instead. */ public static final void resetAndClearKeyProviders() { resetAndRemoveKeyProviders(); } /** * Resets all key providers, causing them to forget their key, and removes * them from the map. * If the key provider associated with the given resource identifier is * not an instance of {@link AbstractKeyProvider}, it is just removed from * the map. * * @since TrueZIP 6.1 * @throws IllegalStateException If resetting or unmapping one or more * key providers is prohibited by a constraint in a subclass of * {@link AbstractKeyProvider}, in which case the respective key * provider(s) are reset but remain mapped. * The operation is continued normally for all other key providers. * Please refer to the respective subclass documentation for * more information about its constraint(s). */ public static synchronized void resetAndRemoveKeyProviders() { class ResetAndRemoveKeyProvider implements KeyProviderCommand { IllegalStateException ise = null; public void run(String resourceID, KeyProvider provider) { if (provider instanceof AbstractKeyProvider) { final AbstractKeyProvider skp = (AbstractKeyProvider) provider; skp.reset(); try { skp.removeFromKeyManager(resourceID); // support proper clean up! } catch (IllegalStateException exc) { ise = exc; // mark and forget any previous exception } } else { final KeyProvider previous = unmapKeyProvider(resourceID); assert provider == previous; } } } final ResetAndRemoveKeyProvider cmd = new ResetAndRemoveKeyProvider(); forEachKeyProvider(cmd); if (cmd.ise != null) throw cmd.ise; } /** * Executes a {@link KeyProviderCommand} for each mapped key provider. * It is safe to call any method of this class within the command, * even if it modifies the map of key providers. */ protected static synchronized void forEachKeyProvider( final KeyProviderCommand command) { // We can't use an iterator because the command may modify the map. // Otherwise, resetAndClearKeyProviders() would fail with a // ConcurrentModificationException. final Set entrySet = providers.entrySet(); final int n = entrySet.size(); final Map.Entry[] entries = (Map.Entry[]) entrySet.toArray(new Map.Entry[n]); for (int i = 0; i < n; i++) { final Map.Entry entry = entries[i]; final String resourceID = (String) entry.getKey(); final KeyProvider provider = (KeyProvider) entry.getValue(); command.run(resourceID, provider); } } /** * Implemented by sub classes to define commands which shall be executed * on key providers with the {@link #forEachKeyProvider} method. */ protected interface KeyProviderCommand { void run(String resourceID, KeyProvider provider); } /** * Moves a key provider from one resource identifier to another. * This may be useful if a protected resource changes its identifier. * For example, if the protected resource is a file, the most obvious * identifier would be its canonical path name. * Calling this method then allows you to rename a file without the need * to retrieve its keys again, thereby possibly prompting (and confusing) * the user. * * @return {@code true} if and only if the operation succeeded, * which means that there was an instance of * {@link KeyProvider} associated with * {@code oldResourceID}. * @throws NullPointerException If {@code oldResourceID} or * {@code newResourceID} is {@code null}. * @throws IllegalStateException If unmapping or mapping the key provider * is prohibited by a constraint in a subclass of * {@link AbstractKeyProvider}, in which case the transaction is * rolled back before this exception is (re)thrown. * Please refer to the respective subclass documentation for * more information about its constraint(s). */ public static synchronized boolean moveKeyProvider( final String oldResourceID, final String newResourceID) throws NullPointerException, IllegalStateException { if (oldResourceID == null || newResourceID == null) throw new NullPointerException(); final KeyProvider provider = (KeyProvider) providers.get(oldResourceID); if (provider == null) return false; if (provider instanceof AbstractKeyProvider) { final AbstractKeyProvider skp = (AbstractKeyProvider) provider; // Implement transactional behaviour. skp.removeFromKeyManager(oldResourceID); try { skp.addToKeyManager(newResourceID); } catch (RuntimeException failure) { skp.addToKeyManager(oldResourceID); throw failure; } } else { unmapKeyProvider(oldResourceID); mapKeyProvider(newResourceID, provider); } return true; } // // Instance methods: // /** * Creates a new {@code KeyManager}. * This class does not map any key provider types. * This must be done in the subclass using {@link #mapKeyProviderType}. */ public KeyManager() { } /** * Subclasses must use this method to register default implementation * classes for the interfaces {@link KeyProvider} and {@link AesKeyProvider} * and optionally other subinterfaces or subclasses of * {@code KeyProvider}. * This is best done in the constructor of the subclass. * * @param forKeyProviderType The type which shall be substituted with * {@code useKeyProviderType} when determining a suitable * run time type in {@code getKeyProvider(String, Class)}. * @param useKeyProviderType The type which shall be substituted for * {@code forKeyProviderType} when determining a suitable * run time type in {@code getKeyProvider(String, Class)}. * @throws NullPointerException If any of the parameters is * {@code null}. * @throws IllegalArgumentException If {@code forKeyProviderType} * is not assignment compatible to the {@code KeyProvider} * interface, * or if {@code useKeyProviderType} is the same as * {@code forKeyProviderType}, * or if {@code useKeyProviderType} is not assignment * compatible to {@code forKeyProviderType}, * or if {@code useKeyProviderType} does not provide a * public constructor with no parameters. * @see #getKeyProvider(String, Class) * @since TrueZIP 6.1 */ protected final synchronized void mapKeyProviderType( final Class forKeyProviderType, final Class useKeyProviderType) { if (!KeyProvider.class.isAssignableFrom(forKeyProviderType) || !forKeyProviderType.isAssignableFrom(useKeyProviderType) || forKeyProviderType == useKeyProviderType) throw new IllegalArgumentException( useKeyProviderType.getName() + " must be a subclass or implementation of " + forKeyProviderType.getName() + "!"); try { useKeyProviderType.getConstructor(null); } catch (NoSuchMethodException noPublicNullaryConstructor) { final IllegalArgumentException iae = new IllegalArgumentException( useKeyProviderType.getName() + " (no public nullary constructor)"); iae.initCause(noPublicNullaryConstructor); throw iae; } providerTypes.put(forKeyProviderType, useKeyProviderType); } /** * Equivalent to {@code return {@link #getKeyProvider(String, Class) * getKeyProvider(resourceID, KeyProvider.class)};} - provided for * convenience. * * @deprecated Use #getKeyProvider(String, Class) instead. */ public KeyProvider getKeyProvider(String resourceID) { return getKeyProvider(resourceID, KeyProvider.class); } /** * Returns the {@link KeyProvider} for the given resource identifier. * If no key provider is mapped, this key manager will determine an * appropriate class which is assignment compatible to * {@code keyProviderType} (but is not necessarily the same), * instantiate it, map the instance for the protected resource and return * it. *

* Client applications should specify an interface rather than an * implementation as the {@code keyProviderType} in order to allow * the key manager to instantiate a useful default implementation of this * interface unless another key provider was already mapped for the * protected resource. *

* Example: * The following example asks the default key manager to provide a * suitable implementation of the {@link AesKeyProvider} interface * for a protected resource. *

     * String pathname = file.getCanonicalPath();
     * KeyManager km = KeyManager.getInstance();
     * KeyProvider kp = km.getKeyProvider(pathname, AesKeyProvider.class);
     * Object key = kp.getCreateKey(); // may prompt the user
     * int ks;
     * if (kp instanceof AesKeyProvider) {
     *      // The run time type of the implementing class is determined
     *      // by the key manager.
     *      // Anyway, the AES key provider can be safely asked for a cipher
     *      // key strength.
     *      ks = ((AesKeyProvider) kp).getKeyStrength();
     * } else {
     *      // Unfortunately, another key provider was already mapped for the
     *      // pathname before - use default key strength.
     *      ks = AesKeyProvider.KEY_STRENGTH_256;
     * }
     * 
. * * @param resourceID The identifier of the protected resource. * @param keyProviderType Unless another key provider is already mapped * for the protected resource, this denotes the root of the class * hierarchy to which the run time type of the returned instance * may belong. * In case the key manager does not know a more suitable class in * this hierarchy, this parameter must denote an implementation of * the {@link KeyProvider} interface with a public no-argument * constructor. * @return The {@link KeyProvider} mapped for the protected resource. * If no key provider has been previously mapped for the protected * resource, the run time type of this instance is guaranteed to be * assignment compatible to the given {@code keyProviderType}. * @throws NullPointerException If {@code resourceID} or * {@code keyProviderType} is {@code null}. * @throws ClassCastException If no other key provider is mapped for the * protected resource and the given class is not an implementation * of the {@code KeyProvider} interface. * @throws IllegalArgumentException If any other precondition on the * parameter {@code keyProviderType} does not hold. * @see #getInstance */ public synchronized KeyProvider getKeyProvider( final String resourceID, Class keyProviderType) throws NullPointerException, ClassCastException, IllegalArgumentException { if (resourceID == null) throw new NullPointerException(); synchronized (KeyManager.class) { KeyProvider provider = (KeyProvider) providers.get(resourceID); if (provider == null) { final Class subst = (Class) providerTypes.get(keyProviderType); if (subst != null) keyProviderType = subst; try { provider = (KeyProvider) keyProviderType.newInstance(); } catch (InstantiationException failure) { IllegalArgumentException iae = new IllegalArgumentException( keyProviderType.getName()); iae.initCause(failure); throw iae; } catch (IllegalAccessException failure) { IllegalArgumentException iae = new IllegalArgumentException( keyProviderType.getName()); iae.initCause(failure); throw iae; } setKeyProvider(resourceID, provider); } return provider; } } /** * Sets the key provider programmatically. *

* Warning: This method replaces any key provider previously * associated with the given resource ID and installs it as the return * value for {@link #getKeyProvider}. * While this allows a reasonable level of flexibility, it may easily * confuse users if they have already been prompted for a key by the * previous provider before and may negatively affect the security if the * provider is not properly guarded by the application. * Use with caution only! *

* If you are using this method in order to set the key provider for a * RAES encrypted ZIP file, then the resource ID must be the canonical * or at least absolute path of this file - * see {@link de.schlichtherle.io.File#getCanOrAbsPath}. * * @param resourceID The resource identifier to associate the key * provider with. * For an RAES encrypted ZIP file, this must be the canonical * path name of the archive file. * @param provider The key provider for {@code resourceID}. * For an RAES encrypted ZIP file, this must be an instance of * the {@link AesKeyProvider} interface. * @throws NullPointerException If {@code resourceID} or * {@code provider} is {@code null}. * @throws IllegalStateException If mapping this instance is prohibited * by a constraint in a subclass of {@link AbstractKeyProvider}. * Please refer to the respective subclass documentation for * more information about its constraint(s). */ public void setKeyProvider( final String resourceID, final KeyProvider provider) throws NullPointerException, IllegalStateException { /*if (resourceID == null || provider == null) throw new NullPointerException();*/ if (provider instanceof AbstractKeyProvider) { ((AbstractKeyProvider) provider).addToKeyManager(resourceID); } else { mapKeyProvider(resourceID, provider); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy