001/*
002 * $Id: KeyChain.java 3100 2008-10-14 22:33:10Z rah003 $
003 *
004 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005 * Santa Clara, California 95054, U.S.A. All rights reserved.
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 * 
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015 * Lesser General Public License for more details.
016 * 
017 * You should have received a copy of the GNU Lesser General Public
018 * License along with this library; if not, write to the Free Software
019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020 */
021package org.jdesktop.swingx.auth;
022
023import java.io.EOFException;
024import java.io.File;
025import java.io.FileInputStream;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.io.OutputStream;
030import java.security.KeyStore;
031import java.security.KeyStoreException;
032import java.security.NoSuchAlgorithmException;
033import java.security.UnrecoverableEntryException;
034import java.security.cert.CertificateException;
035import java.util.logging.Level;
036import java.util.logging.Logger;
037
038import javax.crypto.spec.SecretKeySpec;
039
040/**
041 * <b>KeyChain</b> is a class that implements the "KeyChain" concept.
042 * Fundamentally, it allows you to store multiple keys/credentials 
043 * in a central password store. Access to this central store is
044 * controlled through a master password. This mechanism is used in
045 * many popular client applications where you need to store credentials
046 * for multiple servers/accounts. The actual store for the KeyStore
047 * can be any OutputStream and it can work in the webstart sandbox
048 * using Muffins.
049 * </p>
050 * <p>
051 * To contstruct a <b>KeyChain</b>, you need to pass in an InputStream to the
052 * store and it will initialize the KeyStore from the InputStream.
053 * You can add and remove entries any time once you have an instance of
054 * KeyChain. To persist the KeyChain and reflect any changes, you need to
055 * call <b>store</b> method with an OutputStream.
056 * </p>
057 * 
058 * @author Bino George
059 */
060public class KeyChain {
061    private static final Logger LOG = Logger
062            .getLogger(KeyChain.class.getName());
063    
064    private KeyStore store;
065
066    private char[] masterPassword;
067
068    /**
069     * Creates an instance of KeyChain and initializes the store
070     * from the InputStream.
071     * 
072     * @param masterPassword
073     * @param inputStream
074     * @throws IOException
075     */
076    public KeyChain(char[] masterPassword, InputStream inputStream)
077            throws IOException {
078        this.masterPassword = masterPassword;
079
080        try {
081            store = KeyStore.getInstance("JCEKS");
082            store.load(inputStream, masterPassword);
083
084        } catch (KeyStoreException ex) {
085            LOG.log(Level.WARNING, "", ex);
086        } catch (CertificateException ex) {
087                        LOG.log(Level.WARNING, "", ex);
088        } catch (NoSuchAlgorithmException ex) {
089                        LOG.log(Level.WARNING, "", ex);
090        } catch (EOFException ex) {
091                        LOG.log(Level.WARNING, "", ex);
092        }
093
094    }
095
096    /**
097     * Fetches the password for a given account/user and server.
098     * @param user
099     * @param server
100     * @return <code>null</code> if no password could be obtained, the password 
101     *         otherwise
102     */
103    public String getPassword(String user, String server) {
104
105        try {
106
107            KeyStore.SecretKeyEntry entry2 = (KeyStore.SecretKeyEntry) store
108                    .getEntry(user + "@" + server,
109                            new KeyStore.PasswordProtection(masterPassword));
110            return new String(entry2.getSecretKey().getEncoded());
111        } catch (KeyStoreException ex) {
112            LOG.log(Level.WARNING, "", ex);
113        } catch (UnrecoverableEntryException ex) {
114            LOG.log(Level.WARNING, "", ex);
115        } catch (NoSuchAlgorithmException ex) {
116            LOG.log(Level.WARNING, "", ex);
117        }
118
119        return null;
120    }
121
122    /**
123     * Adds a password to the KeyChain for a given account/user and server.
124     * 
125     * @param user
126     * @param server
127     * @param password
128     */
129    public void addPassword(String user, String server, char[] password)
130            {
131        String pass = new String(password);
132        SecretKeySpec passwordKey = new SecretKeySpec(pass.getBytes(), "JCEKS");
133        KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(passwordKey);
134        try {
135            store.setEntry(user + "@" + server, entry,
136                    new KeyStore.PasswordProtection(masterPassword));
137        } catch (KeyStoreException e) {
138            LOG.log(Level.WARNING, "", e);
139        }
140    }
141
142    /**
143     * Removes a password for a given account/user and server.
144     * 
145     * @param user
146     * @param server
147     */
148    public void removePassword(String user, String server) {
149        try {
150            store.deleteEntry(user + "@" + server);
151        } catch (KeyStoreException e) {
152            LOG.log(Level.WARNING, "", e);
153        }
154    }
155
156    /**
157     * Persists the KeyChain to an OutputStream
158     * 
159     * @param ostream
160     * @throws IOException
161     */
162
163    public void store(OutputStream ostream) throws IOException {
164        try {
165            store.store(ostream, masterPassword);
166        } catch (KeyStoreException ex) {
167                        LOG.log(Level.WARNING, "", ex);
168        } catch (CertificateException ex) {
169                        LOG.log(Level.WARNING, "", ex);
170        } catch (NoSuchAlgorithmException ex) {
171                        LOG.log(Level.WARNING, "", ex);
172        }
173    }
174
175
176    public static void main(String[] args) {
177        try {
178            File file = new File("c:\\test.txt");
179            FileInputStream fis;
180            if (!file.exists()) {
181                file.createNewFile();
182                fis = null;
183            } else {
184                fis = new FileInputStream(file);
185            }
186            KeyChain kc = new KeyChain("test".toCharArray(), fis);
187            kc.addPassword("bino", "sun-ds.sfbay", "test123".toCharArray());
188            LOG.fine("pass = "
189                    + kc.getPassword("bino", "sun-ds.sfbay"));
190
191            LOG.fine("More testing :");
192            for (int i = 0; i < 100; i++) {
193                kc.addPassword("" + i, "sun-ds.sfbay", ("" + i).toCharArray());
194            }
195            for (int i = 0; i < 100; i++) {
196                LOG.fine("key =" + i + " pass ="
197                        + kc.getPassword("" + i, "sun-ds.sfbay"));
198            }
199            kc.store(new FileOutputStream(file));
200        } catch (Exception e) {
201            LOG.log(Level.WARNING, "", e);
202        }
203    }
204    
205}