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     */
021    package org.jdesktop.swingx.auth;
022    
023    import java.io.EOFException;
024    import java.io.File;
025    import java.io.FileInputStream;
026    import java.io.FileOutputStream;
027    import java.io.IOException;
028    import java.io.InputStream;
029    import java.io.OutputStream;
030    import java.security.KeyStore;
031    import java.security.KeyStoreException;
032    import java.security.NoSuchAlgorithmException;
033    import java.security.UnrecoverableEntryException;
034    import java.security.cert.CertificateException;
035    import java.util.logging.Level;
036    import java.util.logging.Logger;
037    
038    import 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     */
060    public 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    }