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 }