/* * Copyright (c) 2001-2007 Sun Microsystems, Inc. All rights reserved. * * The Sun Project JXTA(TM) Software License * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The end-user documentation included with the redistribution, if any, must * include the following acknowledgment: "This product includes software * developed by Sun Microsystems, Inc. for JXTA(TM) technology." * Alternately, this acknowledgment may appear in the software itself, if * and wherever such third-party acknowledgments normally appear. * * 4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must * not be used to endorse or promote products derived from this software * without prior written permission. For written permission, please contact * Project JXTA at http://www.jxta.org. * * 5. Products derived from this software may not be called "JXTA", nor may * "JXTA" appear in their name, without prior written permission of Sun. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN * MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * JXTA is a registered trademark of Sun Microsystems, Inc. in the United * States and other countries. * * Please see the license information page at : * <http://www.jxta.org/project/www/license.html> for instructions on use of * the license in source files. * * ==================================================================== * * This software consists of voluntary contributions made by many individuals * on behalf of Project JXTA. For more information on Project JXTA, please see * http://www.jxta.org. * * This license is based on the BSD license adopted by the Apache Foundation. */ package net.jxta.impl.membership.pse; import net.jxta.id.ID; import net.jxta.id.IDFactory; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import net.jxta.logging.Logger; import net.jxta.logging.Logging; /** * Manages the state of a Personal Security Enviroment. */ public final class PSEConfig { private final static transient Logger LOG = Logging.getLogger(PSEConfig.class.getName()); /** * Manager for the keystore we are using. */ private final KeyStoreManager keystoreManager; /** * The keystore passphrase. */ private char[] keystorePassword = null; /** * Standard constructor. * * @param storeManager The StoreManager to be used for this PSEConfig * instance. * @param storePassword The passphrase for the keystore or <tt>null</tt>. * The passphrase may be set independantly via * {@link #setKeyStorePassword(char[])}. */ PSEConfig(KeyStoreManager storeManager, char[] storePassword) { this.keystoreManager = storeManager; setKeyStorePassword(storePassword); } /** * Sets the passphrase to be used when unlocking the keystore. * * @param storePassword The passphrase used to unlock the keystore may be * {@code null} for keystores with no passphrase. */ public final void setKeyStorePassword(char[] storePassword) { if (null != this.keystorePassword) { Arrays.fill(this.keystorePassword, '\0'); } if (null == storePassword) { this.keystorePassword = null; } else { this.keystorePassword = storePassword.clone(); } } /** * {@inheritDoc} */ @Override protected void finalize() throws Throwable { if (null != keystorePassword) { Arrays.fill(keystorePassword, '\0'); } super.finalize(); } /** * Returns {@code true} if the PSE has been initialized (created). Some * keystore formats may not require initialization and may always return * {@code true}. {@code false} may also be returned if the keystore passphrase is * incorrect. * * @return {@code true} if the PSE has been previously initialized * otherwise {@code false}. */ public boolean isInitialized() { try { if (keystorePassword != null) { return keystoreManager.isInitialized(keystorePassword); } else { return keystoreManager.isInitialized(); } } catch (KeyStoreException ignored) { return false; } } /** * Initializes the PSE environment. * * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public void initialize() throws KeyStoreException, IOException { Logging.logCheckedInfo(LOG, "Initializing new PSE keystore..."); synchronized (keystoreManager) { try { if (keystoreManager.isInitialized(keystorePassword)) { return; } keystoreManager.createKeyStore(keystorePassword); } catch (KeyStoreException failed) { Logging.logCheckedError(LOG, "Failure accessing or creating keystore.\n", failed); keystoreManager.eraseKeyStore(); throw failed; } } } /** * Removes an existing PSE enviroment. * * @throws IOException If the PSE cannot be successfully deleted. */ public void erase() throws IOException { synchronized (keystoreManager) { keystoreManager.eraseKeyStore(); } } /** * Gets a copy of the KeyStore associated with this PSE instance. The * returned KeyStore is a copy and not tied to the instance maintained by * the PSE. Changing the returned keystore will not result in changes to * the PSE. * * @return The keystore or {@code null} if it cannot be retrieved. */ public KeyStore getKeyStore() { Throwable failure; try { return getKeyStore(keystorePassword); } catch (KeyStoreException failed) { failure = failed; } catch (IOException failed) { failure = failed; } Logging.logCheckedWarning(LOG, "Failure recovering keystore : \n", failure); return null; } /** * Gets a copy of the KeyStore associated with this PSE instance. The * returned KeyStore is a copy and not tied to the instance maintained by * the PSE. Changing the returned keystore will not result in changes to * the PSE. * * @param storePassword The passphrase used to unlock the keystore may be * {@code null} for keystores with no passphrase. * @return The keystore. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. * @since JXTA 2.4 */ public KeyStore getKeyStore(char[] storePassword) throws KeyStoreException, IOException { synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(storePassword); return store; } } /** * Check if the provided passwords are correct for the specified identity. * * @param id The identity to be validated. * @param storePassword The passphrase used to unlock the keystore may be * {@code null} for keystores with no passphrase. * @param key_password The passphrase associated with the private key or * {@code null} if the key has no passphrase. * @return {@code true} if the passwords were valid for the given id * otherwise {@code false}. */ boolean validPasswd(ID id, char[] storePassword, char[] key_password) { if (null == id) { Logging.logCheckedDebug(LOG, "null id"); return false; } Throwable failure; try { synchronized (keystoreManager) { KeyStore store; if (null != storePassword) { store = keystoreManager.loadKeyStore(storePassword); } else { if (null != keystorePassword) { store = keystoreManager.loadKeyStore(keystorePassword); } else { throw new UnrecoverableKeyException("KeyStore passphrase not initialized"); } } String alias = id.toString(); Key key = store.getKey(alias, key_password); if ( key == null ) Logging.logCheckedDebug(LOG, "Can't retrieve key for alias: ", alias); return (null != key); } } catch (UnrecoverableKeyException failed) { failure = failed; } catch (NoSuchAlgorithmException failed) { failure = failed; } catch (KeyStoreException failed) { failure = failed; } catch (IOException failed) { failure = failed; } Logging.logCheckedWarning(LOG, "Failure checking passphrase : \n", failure); return false; } /** * Returns the list of the trusted certificates available in this keystore. * * @return an array of the IDs of the available trusted certificates. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public ID[] getTrustedCertsList() throws KeyStoreException, IOException { List<ID> trustedCertsList = new ArrayList<ID>(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); Enumeration<String> eachAlias = store.aliases(); while (eachAlias.hasMoreElements()) { String anAlias = eachAlias.nextElement(); if (store.isCertificateEntry(anAlias) || store.isKeyEntry(anAlias)) { try { URI id = new URI(anAlias); trustedCertsList.add(IDFactory.fromURI(id)); } catch (URISyntaxException badID) {// ignored } } } return trustedCertsList.toArray(new ID[trustedCertsList.size()]); } } /** * Returns the list of root certificates for which there is an associated * local private key. * * @return an array of the available keys. May be an empty array. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public ID[] getKeysList() throws KeyStoreException, IOException { return getKeysList(keystorePassword); } /** * Returns the list of root certificates for which there is an associated * local private key. * * @param storePassword The passphrase used to unlock the keystore may be * {@code null} for keystores with no passphrase. * @return an array of the available keys. May be an empty array. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ ID[] getKeysList(char[] storePassword) throws KeyStoreException, IOException { List<ID> keyedRootsList = new ArrayList<ID>(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(storePassword); Enumeration<String> eachAlias = store.aliases(); while (eachAlias.hasMoreElements()) { String anAlias = eachAlias.nextElement(); if (store.isKeyEntry(anAlias)) { try { URI id = new URI(anAlias); keyedRootsList.add(IDFactory.fromURI(id)); } catch (URISyntaxException badID) {// ignored } } } return keyedRootsList.toArray(new ID[keyedRootsList.size()]); } } /** * Returns the ID of the provided certificate or null if the certificate is * not found in the keystore. * * @param cert The certificate who's ID is desired. * @return The ID of the certificate or <tt>null</tt> if no matching * Certificate was found. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public ID getTrustedCertificateID(X509Certificate cert) throws KeyStoreException, IOException { String anAlias = null; synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); anAlias = store.getCertificateAlias(cert); } // not found. if (null == anAlias) { return null; } try { URI id = new URI(anAlias); return IDFactory.fromURI(id); } catch (URISyntaxException badID) { return null; } } /** * Returns the trusted cert for the specified id. * * @param id The id of the Certificate to retrieve. * @return Certificate for the specified ID or null if the store does not * contain the specified certificate. * @throws KeyStoreException When the wrong keystore key has been provided. * @throws IOException For errors related to processing the keystore. */ public X509Certificate getTrustedCertificate(ID id) throws KeyStoreException, IOException { return getTrustedCertificate(id, keystorePassword); } /** * Returns the trusted cert for the specified id. * * @param id The id of the Certificate to retrieve. * @param storePassword The passphrase used to unlock the keystore may be * {@code null} for keystores with no passphrase. * @return Certificate for the specified ID or null if the store does not * contain the specified certificate. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ X509Certificate getTrustedCertificate(ID id, char[] storePassword) throws KeyStoreException, IOException { String alias = id.toString(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(storePassword); if (!store.containsAlias(alias)) { return null; } return (X509Certificate) store.getCertificate(alias); } } /** * Returns the trusted cert chain for the specified id. * * @param id The ID of the certificate who's certificate chain is desired. * @return Certificate chain for the specified ID or null if the PSE does * not contain the specified certificate. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public X509Certificate[] getTrustedCertificateChain(ID id) throws KeyStoreException, IOException { String alias = id.toString(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); if (!store.containsAlias(alias)) { return null; } Certificate certs[] = store.getCertificateChain(alias); if (null == certs) { return null; } X509Certificate x509certs[] = new X509Certificate[certs.length]; System.arraycopy(certs, 0, x509certs, 0, certs.length); return x509certs; } } /** * Returns the private key for the specified ID. * * @param id The ID of the requested private key. * @param key_password The passphrase associated with the private key or * {@code null} if the key has no passphrase. * @return PrivateKey for the specified ID. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public PrivateKey getKey(ID id, char[] key_password) throws KeyStoreException, IOException { String alias = id.toString(); try { synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); if (!store.containsAlias(alias) || !store.isKeyEntry(alias)) { return null; } return (PrivateKey) store.getKey(alias, key_password); } } catch (NoSuchAlgorithmException failed) { Logging.logCheckedError(LOG, "Something failed\n", failed); KeyStoreException failure = new KeyStoreException("Something Failed"); failure.initCause(failed); throw failure; } catch (UnrecoverableKeyException failed) { Logging.logCheckedError(LOG, "Key passphrase failure\n", failed); KeyStoreException failure = new KeyStoreException("Key passphrase failure"); failure.initCause(failed); throw failure; } } /** * Returns <tt>true</tt> if the specified id is associated with a private * key. * * @param id The ID of the requested private key. * @return <tt>true</tt> if a private key with the specified ID is present * otherwise <tt>false</tt> * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public boolean isKey(ID id) throws KeyStoreException, IOException { return isKey(id, keystorePassword); } /** * Returns <tt>true</tt> if the specified id is associated with a private * key. * * @param id The ID of the requested private key. * @param storePassword The passphrase used to unlock the keystore may be * {@code null} for keystores with no passphrase. * @return <tt>true</tt> if a private key with the specified ID is present * otherwise <tt>false</tt> * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public boolean isKey(ID id, char[] storePassword) throws KeyStoreException, IOException { String alias = id.toString(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(storePassword); return store.containsAlias(alias) & store.isKeyEntry(alias); } } /** * Adds a trusted certificate with the specified id to the key store. The * certificate replaces any existing certificate or private key stored at * this ID. * * @param id The ID under which the certificate will be stored. * @param cert Certificate for the specified ID. * @throws KeyStoreException When the wrong keystore has been provided. * @throws IOException For errors related to processing the keystore. */ public void setTrustedCertificate(ID id, X509Certificate cert) throws KeyStoreException, IOException { String alias = id.toString(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); store.deleteEntry(alias); store.setCertificateEntry(alias, cert); keystoreManager.saveKeyStore(store, keystorePassword); } } /** * Adds a private key to the PSE using the specified ID. The key replaces * any existing certificate or private key stored at this ID. The key is * stored using the provided key passphrase. * * @param id The ID under which the certificate chain and private key will be stored. * @param certchain The certificate chain matching the private key. * @param key The private key to be stored in the kestore. * @param key_password The passphrase associated with the private key or * {@code null} if the key has no passphrase. * @throws KeyStoreException When the wrong keystore key has been provided. * @throws IOException For errors related to processing the keystore. */ public void setKey(ID id, Certificate[] certchain, PrivateKey key, char[] key_password) throws KeyStoreException, IOException { String alias = id.toString(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); // Remove any existing entry. if (store.isKeyEntry(alias)) store.deleteEntry(alias); store.setKeyEntry(alias, key, key_password, certchain); keystoreManager.saveKeyStore(store, keystorePassword); } } /** * Erases the specified id from the keystore. * * @param id The ID of the key or certificate to be deleted. * @throws KeyStoreException When the wrong keystore password has been * provided. * @throws IOException For errors related to processing the keystore. */ public void erase(ID id) throws KeyStoreException, IOException { String alias = id.toString(); synchronized (keystoreManager) { KeyStore store = keystoreManager.loadKeyStore(keystorePassword); store.deleteEntry(alias); keystoreManager.saveKeyStore(store, keystorePassword); } } }