001: /*
002: * Gruntspud
003: *
004: * Copyright (C) 2002 Brett Smith.
005: *
006: * Written by: Brett Smith <t_magicthize@users.sourceforge.net>
007: *
008: * This program is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU Library General Public License
010: * as published by the Free Software Foundation; either version 2 of
011: * the License, or (at your option) any later version.
012: * This program 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
015: * GNU Library General Public License for more details.
016: *
017: * You should have received a copy of the GNU Library General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
020: */
021:
022: package gruntspud.authentication;
023:
024: import gruntspud.GruntspudContext;
025: import gruntspud.GruntspudUtil;
026: import gruntspud.StringUtil;
027:
028: import java.io.BufferedReader;
029: import java.io.File;
030: import java.io.FileInputStream;
031: import java.io.FileOutputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.io.InputStreamReader;
035: import java.io.OutputStream;
036: import java.net.URLDecoder;
037: import java.text.MessageFormat;
038: import java.util.Collections;
039: import java.util.Enumeration;
040: import java.util.Properties;
041: import java.util.ResourceBundle;
042: import java.util.Vector;
043:
044: /**
045: * A password together with a key (<code>PasswordKey</code>) that makes it unique
046: * makes up a <code>PasswordPair</code>. This object holds a list of these pairs
047: * and also is responsible for saving the list to disk, possibly encrypted if an
048: * <code>Encrypter</code> instance has been registered.
049: *
050: * @author magicthize
051: */
052: public class PasswordPairList extends Vector {
053: static ResourceBundle res = ResourceBundle
054: .getBundle("gruntspud.authentication.ResourceBundle");
055: // Statics
056: protected final static String ENC = "1x45cvs32zp29aapttk22u8";
057: protected final static String FORMAT_KEY = "Format: ";
058: protected final static String ENCRYPTED_FORMAT = "encrypted";
059: protected final static String PLAIN_FORMAT = "plain";
060: protected final static PasswordKey PWMGR = new PasswordKey("pwmgr",
061: "pwmgr", "pwmgr");
062:
063: // Private instance variables
064: private File passwordFile;
065: private boolean encrypted;
066: private char[] masterPassword;
067: private PasswordPair pwmgr;
068: private GruntspudContext context;
069:
070: /**
071: * Creates a new PasswordPairList object.
072: *
073: * @param passwordFile file to store passwords in
074: * @param context context
075: */
076: public PasswordPairList(File passwordFile, GruntspudContext context) {
077: this .passwordFile = passwordFile;
078: this .context = context;
079: }
080:
081: /**
082: * Encrypt all stored passwords using the registered <code>Encrypter</code>
083: * and save them to disk
084: *
085: * @throws IOException if passwords cannot be saved
086: */
087: public void encryptAllPairs() throws IOException {
088: if (isEncrypted()) {
089: throw new IOException(
090: res
091: .getString("passwordPairList.error.alreadyEncrypted.text"));
092: }
093:
094: if (context.getEncrypter() == null) {
095: throw new IOException(
096: res
097: .getString("passwordPairList.error.noEncrypterAvailable.text"));
098: }
099:
100: setEncrypted(true);
101:
102: for (Enumeration e = elements(); e.hasMoreElements();) {
103: PasswordPair p = (PasswordPair) e.nextElement();
104: p.setUserPassword(context.getEncrypter().encryptString(
105: p.getUserPassword(), getMasterPassword()));
106: }
107:
108: pwmgr = new PasswordPair(PWMGR, context.getEncrypter()
109: .encryptString(ENC, getMasterPassword()), true);
110: }
111:
112: /**
113: * Decrypt all stored passwords using the registered <code>Encrypter</code>
114: * and save them to disk
115: *
116: * @throws IOException if password file cannot be saved
117: */
118: public void decryptAllPairs() throws IOException {
119: if (!isEncrypted()) {
120: throw new IOException(
121: res
122: .getString("passwordPairList.error.notEncrypted.text"));
123: }
124:
125: if (context.getEncrypter() == null) {
126: throw new IOException(
127: res
128: .getString("passwordPairList.error.couldNotParsePasswordFile.text"));
129: }
130:
131: setEncrypted(false);
132:
133: for (Enumeration e = elements(); e.hasMoreElements();) {
134: PasswordPair p = (PasswordPair) e.nextElement();
135: p.setUserPassword(context.getEncrypter().decryptString(
136: p.getUserPassword(), getMasterPassword()));
137: }
138:
139: pwmgr = null;
140: }
141:
142: /**
143: * Test if the password file can be decrypted given the master password.
144: *
145: * @param p master password
146: * @return valid password
147: * @throws IOException if password file cannot be read
148: */
149: public boolean checkMasterPassword(char[] p) throws IOException {
150: if (context.getEncrypter() == null) {
151: throw new IOException("Encrypter not available.");
152: }
153: return isEncrypted() ? context.getEncrypter().decryptString(
154: pwmgr.getUserPassword(), p).equals(ENC) : false;
155: }
156:
157: /**
158: * Change the master password and rewrite the password list.
159: *
160: * @param newMasterPassword new master password
161: * @throws IOException if the password file cannot be written
162: */
163: public void changeMasterPassword(char[] newMasterPassword)
164: throws IOException {
165: decryptAllPairs();
166: setMasterPassword(newMasterPassword);
167: encryptAllPairs();
168: }
169:
170: /**
171: * Set the supplied <code>PasswordPair</code>. If the pairs key already
172: * exists it will be overwritten, if not it will be created. The list
173: * will <strong>not</strong> be saved.
174: *
175: * @param pair password pair
176: */
177: public void setPair(PasswordPair pair) {
178: removeKey(pair.getKey());
179: addPair(pair);
180: }
181:
182: /**
183: * Set the supplied <code>PasswordPair</code> and update the password.
184: * If the pairs key already exists it will be overwritten, if not it
185: * will be created. The list will <strong>not</strong> be saved.
186: *
187: * @param key key
188: * @param userPassword new password
189: */
190: public void setKey(PasswordKey key, String userPassword) {
191: removeKey(key);
192: addKey(key, userPassword);
193: }
194:
195: /**
196: * Add a new password key with a new password. A <code>PasswordPair</code> will
197: * be created and added to the list.
198: *
199: * @param key key
200: * @param userPassword key
201: */
202: public void addKey(PasswordKey key, String userPassword) {
203: addPair(new PasswordPair(key, userPassword));
204: }
205:
206: /**
207: * Add a new password pair to the list. The list will <strong>not</strong> be saved.
208: *
209: * @param pair pair
210: */
211: public void addPair(PasswordPair pair) {
212: addElement(pair);
213: }
214:
215: /**
216: * Get a <code>PasswordPair</code> at a given index in the list
217: *
218: * @param r index in list
219: * @return password pair
220: */
221: public PasswordPair getPasswordPairAt(int r) {
222: return (PasswordPair) elementAt(r);
223: }
224:
225: /**
226: * Remove a password pair from the list given its key. The list will <strong>not</strong> be saved.
227: *
228: * @param key key to remove
229: */
230: public void removeKey(PasswordKey key) {
231: for (int i = size() - 1; i >= 0; i--) {
232: if (getPasswordPairAt(i).getKey().equals(key)) {
233: removeElementAt(i);
234: }
235: }
236: }
237:
238: /**
239: * Get a password pair given its key. <code>null</code> will be returned if no
240: * password with the supplied key can be found
241: *
242: * @param key key
243: * @return password pair
244: */
245: public PasswordPair getPair(PasswordKey key) {
246: for (int i = 0; i < size(); i++) {
247: PasswordPair pair = (PasswordPair) elementAt(i);
248:
249: if (pair.getKey().equals(key)) {
250: return pair;
251: }
252: }
253:
254: return null;
255: }
256:
257: /**
258: * Get the master password
259: *
260: * @return master password
261: */
262: public char[] getMasterPassword() {
263: return masterPassword;
264: }
265:
266: /**
267: * Set the master password.
268: *
269: * @param masterPassword master password
270: */
271: public void setMasterPassword(char[] masterPassword) {
272: this .masterPassword = masterPassword;
273: }
274:
275: /**
276: * Save all of the passwords to disk.
277: *
278: * @throws IOException if the password file cannot be written
279: */
280: public void savePasswordFile() throws IOException {
281: OutputStream out = null;
282:
283: try {
284: out = new FileOutputStream(passwordFile);
285:
286: Properties p = new Properties();
287:
288: if (isEncrypted()) {
289: p.put(pwmgr.getKey().toString(), pwmgr
290: .getUserPassword());
291:
292: }
293: for (int i = 0; i < size(); i++) {
294: PasswordPair pair = (PasswordPair) elementAt(i);
295:
296: if (pair.isPersistant()) {
297: p.put(pair.getKey().toString(), pair
298: .getUserPassword());
299: }
300: }
301:
302: p.store(out, FORMAT_KEY
303: + (encrypted ? ENCRYPTED_FORMAT : PLAIN_FORMAT));
304: out.flush();
305: } finally {
306: GruntspudUtil.closeStream(out);
307: }
308: }
309:
310: /**
311: * Load the password list from disk.
312: *
313: * @throws IOException if password file cannot be read
314: */
315: public void loadPasswordFile() throws IOException {
316: InputStream in = null;
317: removeAllElements();
318:
319: Properties p = new Properties();
320:
321: try {
322: in = new FileInputStream(passwordFile);
323:
324: BufferedReader reader = new BufferedReader(
325: new InputStreamReader(in));
326: String format = reader.readLine();
327: in.close();
328: in = new FileInputStream(passwordFile);
329:
330: if (format == null) {
331: encrypted = false;
332: } else {
333: if (!format.startsWith("#" + FORMAT_KEY)) {
334: encrypted = false;
335: } else {
336: encrypted = format.substring(9).trim().equals(
337: ENCRYPTED_FORMAT);
338: }
339: }
340:
341: p.load(in);
342: } finally {
343: GruntspudUtil.closeStream(in);
344: }
345:
346: //
347: try {
348: for (Enumeration e = p.keys(); e.hasMoreElements();) {
349: String key = (String) e.nextElement();
350: String[] sa = StringUtil.splitString(key, '@');
351:
352: // Deprecated in 1.4, but not available in 1.3. Bit of a pain
353: // String protocol = URLDecoder.decode(sa[0], "UTF-8");
354: // String scheme = URLDecoder.decode(sa[1], "UTF-8");
355: // String host = URLDecoder.decode(sa[2], "UTF-8");
356: String protocol = URLDecoder.decode(sa[0]);
357: String scheme = URLDecoder.decode(sa[1]);
358: String host = URLDecoder.decode(sa[2]);
359:
360: PasswordKey k = new PasswordKey(protocol, scheme, host);
361: String val = p.getProperty(key);
362: PasswordPair pair = new PasswordPair(k, val);
363: pair.setPersistant(true);
364:
365: if (!k.equals(PWMGR)) {
366: addElement(pair);
367: } else {
368: pwmgr = pair;
369: }
370: }
371: } catch (Exception e) {
372: throw new IOException(
373: MessageFormat
374: .format(
375: res
376: .getString("passwordPairList.error.couldNotParsePasswordFile.text"),
377: new Object[] { getPasswordFile()
378: .getAbsolutePath() }));
379: }
380:
381: //
382: Collections.sort(this );
383: }
384:
385: /**
386: * Return the password list file
387: *
388: * @return password list file
389: */
390: public File getPasswordFile() {
391: return passwordFile;
392: }
393:
394: /**
395: * Get if the current password list is encrypted
396: *
397: * @return password list is encrypter
398: */
399: public boolean isEncrypted() {
400: return encrypted;
401: }
402:
403: /**
404: * Set whether the current password list encrypted
405: *
406: * @param encrypted encrypted
407: */
408: public void setEncrypted(boolean encrypted) {
409: this.encrypted = encrypted;
410: }
411: }
|