001: package net.suberic.pooka;
002:
003: import java.util.Map;
004: import java.util.HashMap;
005: import java.util.Set;
006:
007: import java.io.File;
008: import java.io.FileInputStream;
009: import java.security.Key;
010: import java.security.KeyStoreException;
011: import java.security.GeneralSecurityException;
012: import java.util.HashSet;
013: import java.util.ArrayList;
014:
015: import javax.mail.internet.*;
016: import javax.mail.*;
017:
018: import net.suberic.crypto.*;
019: import net.suberic.util.VariableBundle;
020: import net.suberic.util.ValueChangeListener;
021:
022: /**
023: * The EncryptionManager manages Pooka's encryption facilities. It's
024: * basically one-stop shopping for all of your email encryption needs.
025: */
026: public class PookaEncryptionManager implements ValueChangeListener {
027:
028: String key;
029: VariableBundle sourceBundle;
030:
031: EncryptionKeyManager pgpKeyMgr = null;
032:
033: EncryptionKeyManager smimeKeyMgr = null;
034:
035: char[] keyMgrPasswd = null;
036:
037: Map cachedPrivateKeys = new HashMap();
038:
039: Map cachedPublicKeys = new HashMap();
040:
041: Map addressToPublicKeyMap = null;
042:
043: boolean savePasswordsForSession = false;
044:
045: boolean needsReload = false;
046:
047: /**
048: * Creates an EncryptionManager using the given VariableBundle and
049: * key property.
050: */
051: public PookaEncryptionManager(VariableBundle pSourceBundle,
052: String pKey) {
053: sourceBundle = pSourceBundle;
054: key = pKey;
055:
056: // register this for listening to changes to the store filenames and the
057: // store passwords.
058: sourceBundle.addValueChangeListener(this , key
059: + ".pgp.keyStore.private.filename");
060: sourceBundle.addValueChangeListener(this , key
061: + ".pgp.keyStore.private.password");
062: sourceBundle.addValueChangeListener(this , key
063: + ".pgp.keyStore.public.filename");
064:
065: sourceBundle.addValueChangeListener(this , key
066: + ".smime.keyStore.public.filename");
067: sourceBundle.addValueChangeListener(this , key
068: + ".smime.keyStore.private.filename");
069: sourceBundle.addValueChangeListener(this , key
070: + ".smime.keyStore.private.password");
071:
072: sourceBundle.addValueChangeListener(this , key
073: + ".savePasswordsForSession");
074:
075: final VariableBundle fBundle = sourceBundle;
076: final String fKey = key;
077: Thread storeLoadingThread = new Thread(new Runnable() {
078: public void run() {
079: // load the given pgp and smime stores.
080: loadStores(fBundle, fKey);
081: }
082: });
083:
084: storeLoadingThread.start();
085: }
086:
087: /**
088: * Loads the stores.
089: */
090: public void loadStores(VariableBundle sourceBundle, String key) {
091: String pgpPublicFilename = sourceBundle.getProperty(key
092: + ".pgp.keyStore.public.filename", "");
093:
094: String pgpPrivateFilename = sourceBundle.getProperty(key
095: + ".pgp.keyStore.private.filename", "");
096: String pgpPrivatePwString = sourceBundle.getProperty(key
097: + ".pgp.keyStore.private.password", "");
098: if (!pgpPrivatePwString.equals(""))
099: pgpPrivatePwString = net.suberic.util.gui.propedit.PasswordEditorPane
100: .descrambleString(pgpPrivatePwString);
101:
102: // if either store is configured, try loading.
103: if (!(pgpPrivateFilename.equals("") && pgpPublicFilename
104: .equals(""))) {
105: try {
106: EncryptionUtils pgpUtils = EncryptionManager
107: .getEncryptionUtils("PGP");
108: if (pgpUtils != null) {
109: pgpKeyMgr = pgpUtils.createKeyManager();
110: try {
111: pgpKeyMgr.loadPrivateKeystore(
112: new FileInputStream(new File(
113: pgpPrivateFilename)),
114: pgpPrivatePwString.toCharArray());
115: } catch (java.io.IOException fnfe) {
116: System.out
117: .println("Error loading PGP private keystore from file "
118: + pgpPrivateFilename
119: + ": "
120: + fnfe.getMessage());
121: } catch (java.security.GeneralSecurityException gse) {
122: System.out
123: .println("Error loading PGP private keystore from file "
124: + pgpPrivateFilename
125: + ": "
126: + gse.getMessage());
127: }
128: try {
129: pgpKeyMgr.loadPublicKeystore(
130: new FileInputStream(new File(
131: pgpPublicFilename)), null);
132: } catch (java.io.IOException fnfe) {
133: System.out
134: .println("Error loading PGP public keystore from file "
135: + pgpPublicFilename
136: + ": "
137: + fnfe.getMessage());
138: } catch (java.security.GeneralSecurityException gse) {
139: System.out
140: .println("Error loading PGP private keystore from file "
141: + pgpPublicFilename
142: + ": "
143: + gse.getMessage());
144: }
145: }
146: } catch (java.security.NoSuchProviderException nspe) {
147: System.out.println("Error loading PGP key store: "
148: + nspe.getMessage());
149: } catch (Exception e) {
150: System.out.println("Error loading PGP key store: "
151: + e.getMessage());
152: }
153: }
154:
155: String smimePublicFilename = sourceBundle.getProperty(key
156: + ".smime.keyStore.public.filename", "");
157:
158: String smimePrivateFilename = sourceBundle.getProperty(key
159: + ".smime.keyStore.private.filename", "");
160: String smimePrivatePwString = sourceBundle.getProperty(key
161: + ".smime.keyStore.private.password", "");
162: if (!smimePrivatePwString.equals(""))
163: smimePrivatePwString = net.suberic.util.gui.propedit.PasswordEditorPane
164: .descrambleString(smimePrivatePwString);
165:
166: // if either store is configured, try loading.
167: if (!(smimePrivateFilename.equals("") && smimePublicFilename
168: .equals(""))) {
169: try {
170: EncryptionUtils smimeUtils = EncryptionManager
171: .getEncryptionUtils("S/MIME");
172: if (smimeUtils != null) {
173: smimeKeyMgr = smimeUtils.createKeyManager();
174: try {
175: smimeKeyMgr.loadPrivateKeystore(
176: new FileInputStream(new File(
177: smimePrivateFilename)),
178: smimePrivatePwString.toCharArray());
179: } catch (java.security.GeneralSecurityException gse) {
180: System.out
181: .println("Error loading S/MIME private keystore from file "
182: + smimePrivateFilename
183: + ": "
184: + gse.getMessage());
185: } catch (java.io.IOException fnfe) {
186: System.out
187: .println("Error loading S/MIME private keystore from file "
188: + smimePrivateFilename
189: + ": "
190: + fnfe.getMessage());
191: }
192:
193: try {
194: smimeKeyMgr.loadPublicKeystore(
195: new FileInputStream(new File(
196: smimePublicFilename)),
197: smimePrivatePwString.toCharArray());
198: } catch (java.io.IOException fnfe) {
199: System.out
200: .println("Error loading S/MIME public keystore from file "
201: + smimePublicFilename
202: + ": "
203: + fnfe.getMessage());
204: } catch (java.security.GeneralSecurityException gse) {
205: System.out
206: .println("Error loading S/MIME private keystore from file "
207: + smimePublicFilename
208: + ": "
209: + gse.getMessage());
210: }
211: }
212: } catch (java.security.NoSuchProviderException nspe) {
213: System.out.println("Error loading S/MIME key store: "
214: + nspe.getMessage());
215: } catch (Exception e) {
216: System.out.println("Error loading S/MIME key store: "
217: + e.getMessage());
218: }
219: }
220:
221: savePasswordsForSession = Pooka.getProperty(
222: key + ".savePasswordsForSession", "false")
223: .equalsIgnoreCase("true");
224:
225: cachedPrivateKeys = new HashMap();
226:
227: cachedPublicKeys = new HashMap();
228:
229: addressToPublicKeyMap = null;
230:
231: }
232:
233: /**
234: * As defined in net.suberic.util.ValueChangeListener.
235: *
236: */
237: public void valueChanged(String changedValue) {
238: if (changedValue.equals(key + ".savePasswordsForSession")) {
239: savePasswordsForSession = Pooka.getProperty(
240: key + ".savePasswordsForSession", "false")
241: .equalsIgnoreCase("true");
242: } else {
243: // this is crazy.
244: needsReload = true;
245: javax.swing.SwingUtilities.invokeLater(new Runnable() {
246:
247: public void run() {
248: if (needsReload) {
249: needsReload = false;
250:
251: Thread updateThread = new Thread(
252: new Runnable() {
253: public void run() {
254: loadStores(sourceBundle, key);
255: }
256: });
257:
258: updateThread.start();
259: }
260: }
261: });
262: }
263: }
264:
265: /**
266: * Adds the private key to the store.
267: */
268: public void addPrivateKey(String alias, Key privateKey,
269: char[] passphrase, String type)
270: throws GeneralSecurityException {
271: EncryptionKeyManager currentMgr = getKeyMgr(type);
272: if (currentMgr != null) {
273: currentMgr
274: .setPrivateKeyEntry(alias, privateKey, passphrase);
275: } else {
276: throw new KeyStoreException(type
277: + " KeyStore not initialized.");
278: }
279: }
280:
281: /**
282: * Adds the public key to the store.
283: */
284: public void addPublicKey(String alias, Key publicKey, String type)
285: throws GeneralSecurityException {
286:
287: EncryptionKeyManager currentMgr = getKeyMgr(type);
288: if (currentMgr != null) {
289: currentMgr.setPublicKeyEntry(alias, publicKey);
290: } else {
291: throw new KeyStoreException(type
292: + " KeyStore not initialized.");
293: }
294: }
295:
296: /**
297: * Returns the private key(s) for the given email address.
298: */
299: public Key[] getPrivateKeys(String address) {
300: return getPrivateKeys(address, null);
301: }
302:
303: /**
304: * Returns the private key(s) for the given email address and
305: * the given encryption type, or all matching keys if type == null.
306: */
307: public Key[] getPrivateKeys(String address, String type) {
308: return null;
309: }
310:
311: /**
312: * Returns all private keys that have been cached.
313: */
314: public Key[] getCachedPrivateKeys() {
315: return (Key[]) cachedPrivateKeys.values().toArray(new Key[0]);
316: }
317:
318: /**
319: * Returns all available private key aliases.
320: */
321: public Set privateKeyAliases()
322: throws java.security.KeyStoreException {
323: return privateKeyAliases(null);
324: }
325:
326: /**
327: * Returns all available private key aliases for the give EncryptionType,
328: * or all available aliases if type is null.
329: */
330: public Set privateKeyAliases(String encryptionType)
331: throws java.security.KeyStoreException {
332: if (encryptionType != null
333: && encryptionType
334: .equalsIgnoreCase(EncryptionManager.PGP)) {
335: if (pgpKeyMgr != null)
336: return new HashSet(pgpKeyMgr.privateKeyAliases());
337: } else if (encryptionType != null
338: && encryptionType
339: .equalsIgnoreCase(EncryptionManager.SMIME)) {
340: if (smimeKeyMgr != null) {
341: return new HashSet(smimeKeyMgr.privateKeyAliases());
342: }
343: } else {
344: // return both.
345: Set returnValue = new java.util.HashSet();
346: if (pgpKeyMgr != null) {
347: try {
348: returnValue.addAll(pgpKeyMgr.privateKeyAliases());
349: } catch (KeyStoreException kse) {
350: // FIXME ignore for now?
351: }
352: }
353: if (smimeKeyMgr != null) {
354: try {
355: returnValue.addAll(smimeKeyMgr.privateKeyAliases());
356: } catch (KeyStoreException kse) {
357: // FIXME ignore for now?
358: }
359: }
360:
361: return returnValue;
362: }
363:
364: return new HashSet();
365: }
366:
367: /**
368: * Returns the Private key for the given alias.
369: */
370: public Key getPrivateKey(String alias)
371: throws java.security.KeyStoreException,
372: java.security.NoSuchAlgorithmException,
373: java.security.UnrecoverableKeyException {
374: KeyStoreException caughtException = null;
375:
376: // first check to see if this is in the cache.
377: Key cachedKey = (Key) cachedPrivateKeys.get(alias);
378: if (cachedKey != null)
379: return cachedKey;
380:
381: if (pgpKeyMgr != null || smimeKeyMgr != null) {
382:
383: // check to see if this exists anywhere.
384: if (pgpKeyMgr != null) {
385: try {
386: if (pgpKeyMgr.containsPrivateKeyAlias(alias)) {
387: Key returnValue = pgpKeyMgr.getPrivateKey(
388: alias, null);
389: cachedPrivateKeys.put(alias, returnValue);
390: return returnValue;
391: }
392: } catch (KeyStoreException kse) {
393: caughtException = kse;
394: }
395:
396: }
397:
398: if (smimeKeyMgr != null) {
399: try {
400: if (smimeKeyMgr.containsPrivateKeyAlias(alias)) {
401: Key returnValue = smimeKeyMgr.getPrivateKey(
402: alias, null);
403: cachedPrivateKeys.put(alias, returnValue);
404: return returnValue;
405: }
406: } catch (KeyStoreException kse) {
407: if (caughtException == null)
408: caughtException = kse;
409: }
410:
411: }
412: }
413:
414: if (caughtException != null)
415: throw caughtException;
416:
417: return null;
418: }
419:
420: /**
421: * Returns the Private key for the given alias.
422: */
423: public Key getPrivateKey(String alias, char[] password)
424: throws java.security.KeyStoreException,
425: java.security.NoSuchAlgorithmException,
426: java.security.UnrecoverableKeyException {
427:
428: KeyStoreException caughtException = null;
429:
430: // first check to see if this is in the cache.
431: Key cachedKey = (Key) cachedPrivateKeys.get(alias);
432: if (cachedKey != null)
433: return cachedKey;
434:
435: Key returnValue = null;
436: if (pgpKeyMgr != null) {
437: try {
438: returnValue = pgpKeyMgr.getPrivateKey(alias, password);
439: } catch (KeyStoreException kse) {
440: caughtException = kse;
441: }
442: }
443:
444: if (returnValue == null && smimeKeyMgr != null) {
445: try {
446: returnValue = smimeKeyMgr
447: .getPrivateKey(alias, password);
448: } catch (KeyStoreException kse) {
449: if (caughtException == null)
450: caughtException = kse;
451: }
452: }
453:
454: if (returnValue != null) {
455: cachedPrivateKeys.put(alias, returnValue);
456: }
457:
458: if (returnValue == null && caughtException != null)
459: throw caughtException;
460:
461: return returnValue;
462: }
463:
464: /**
465: * Returns the Public key for the given alias.
466: */
467: public Key getPublicKey(String alias)
468: throws java.security.KeyStoreException,
469: java.security.NoSuchAlgorithmException,
470: java.security.UnrecoverableKeyException {
471: Key returnValue = null;
472: if (pgpKeyMgr != null) {
473: try {
474: returnValue = pgpKeyMgr.getPublicKey(alias);
475: } catch (KeyStoreException kse) {
476: // FIXME ignore for now?
477: }
478: }
479:
480: if (returnValue == null && smimeKeyMgr != null) {
481: try {
482: returnValue = smimeKeyMgr.getPublicKey(alias);
483: } catch (KeyStoreException kse) {
484: // FIXME ignore for now?
485: }
486: }
487:
488: return returnValue;
489: }
490:
491: /**
492: * Returns the public key(s) for the given email address.
493: */
494: public Key[] getPublicKeys(String address)
495: throws java.security.KeyStoreException,
496: java.security.NoSuchAlgorithmException,
497: java.security.UnrecoverableKeyException {
498: return getPublicKeys(address, null);
499: }
500:
501: /**
502: * Returns the public key(s) for the given email address that match
503: * the given encryption type, or all matching keys if type == null.
504: */
505: public Key[] getPublicKeys(String address, String type)
506: throws java.security.KeyStoreException,
507: java.security.NoSuchAlgorithmException,
508: java.security.UnrecoverableKeyException {
509: if (addressToPublicKeyMap == null) {
510: sortPublicKeys();
511: }
512:
513: ArrayList list = (ArrayList) addressToPublicKeyMap.get(address);
514: if (list == null)
515: return new Key[0];
516: else if (type == null) {
517: return (Key[]) list.toArray(new Key[0]);
518: } else {
519: ArrayList sortedList = new ArrayList();
520: java.util.Iterator iter = list.iterator();
521: while (iter.hasNext()) {
522: EncryptionKey current = (EncryptionKey) iter.next();
523: try {
524: if (current.getEncryptionUtils().getType() == type) {
525: sortedList.add(current);
526: }
527: } catch (Exception e) {
528: }
529: }
530:
531: return (Key[]) sortedList.toArray(new Key[0]);
532: }
533: }
534:
535: /**
536: * Sorts all available public keys by associated address.
537: */
538: private synchronized void sortPublicKeys()
539: throws java.security.KeyStoreException,
540: java.security.NoSuchAlgorithmException,
541: java.security.UnrecoverableKeyException {
542: if (addressToPublicKeyMap == null) {
543: addressToPublicKeyMap = new HashMap();
544: Set aliases = publicKeyAliases();
545: java.util.Iterator iter = aliases.iterator();
546: while (iter.hasNext()) {
547: Key current = getPublicKey((String) iter.next());
548:
549: if (current instanceof EncryptionKey) {
550: String[] assocAddresses = ((EncryptionKey) current)
551: .getAssociatedAddresses();
552: for (int i = 0; assocAddresses != null
553: && i < assocAddresses.length; i++) {
554: String address = assocAddresses[i];
555: ArrayList matches = (ArrayList) addressToPublicKeyMap
556: .get(address);
557: if (matches != null) {
558: if (!matches.contains(current))
559: matches.add(current);
560: } else {
561: matches = new ArrayList();
562: matches.add(current);
563: addressToPublicKeyMap.put(address, matches);
564: }
565: }
566: }
567: }
568: }
569: }
570:
571: /**
572: * Returns all available public key aliases.
573: */
574: public Set publicKeyAliases()
575: throws java.security.KeyStoreException {
576: return publicKeyAliases(null);
577: }
578:
579: /**
580: * Returns available public key aliases for the given encryption type, or
581: * all available aliases if null.
582: */
583: public Set publicKeyAliases(String encryptionType)
584: throws java.security.KeyStoreException {
585:
586: if (encryptionType != null
587: && encryptionType
588: .equalsIgnoreCase(EncryptionManager.PGP)) {
589: if (pgpKeyMgr != null)
590: return new HashSet(pgpKeyMgr.publicKeyAliases());
591: } else if (encryptionType != null
592: && encryptionType
593: .equalsIgnoreCase(EncryptionManager.SMIME)) {
594: if (smimeKeyMgr != null) {
595: return new HashSet(smimeKeyMgr.publicKeyAliases());
596: }
597: } else {
598: // return both.
599: Set returnValue = new java.util.HashSet();
600: if (pgpKeyMgr != null) {
601: try {
602: returnValue.addAll(pgpKeyMgr.publicKeyAliases());
603: } catch (KeyStoreException kse) {
604: // FIXME ignore for now?
605: }
606: }
607: if (smimeKeyMgr != null) {
608: try {
609: returnValue.addAll(smimeKeyMgr.publicKeyAliases());
610: } catch (KeyStoreException kse) {
611: // FIXME ignore for now?
612: }
613: }
614:
615: return returnValue;
616: }
617:
618: return new HashSet();
619:
620: }
621:
622: /**
623: * Encrypts to given message. Actually checks all of the recipients
624: * configured to see if we have a key for each one.
625: */
626: public MimeMessage encryptMessage(MimeMessage mMsg)
627: throws MessagingException,
628: java.security.GeneralSecurityException, java.io.IOException {
629:
630: // if we don't have a key, see if we can get a default.
631: Key key = null;
632: Address[] recipients = mMsg
633: .getRecipients(Message.RecipientType.TO);
634: for (int i = 0; key == null && i < recipients.length; i++) {
635: if (recipients[i] instanceof InternetAddress) {
636: String inetAddr = ((InternetAddress) recipients[i])
637: .getAddress();
638: Key[] matchingKeys = getPublicKeys(inetAddr);
639: if (matchingKeys != null) {
640: for (int j = 0; key != null
641: && j < matchingKeys.length; j++) {
642: key = matchingKeys[j];
643: }
644: }
645: }
646: }
647:
648: return encryptMessage(mMsg, key);
649: }
650:
651: /**
652: * Encrypts the given message. If there's no key, return null.
653: */
654: public MimeMessage encryptMessage(MimeMessage mMsg, Key key)
655: throws MessagingException,
656: java.security.GeneralSecurityException, java.io.IOException {
657: if (key != null) {
658: if (key instanceof EncryptionKey) {
659: return ((EncryptionKey) key).getEncryptionUtils()
660: .encryptMessage(Pooka.getDefaultSession(),
661: mMsg, key);
662: } else {
663: return EncryptionManager.getEncryptionUtils("PGP")
664: .encryptMessage(Pooka.getDefaultSession(),
665: mMsg, key);
666: }
667:
668: }
669: return mMsg;
670: }
671:
672: /**
673: * Signs the given message.
674: */
675: public MimeMessage signMessage(MimeMessage mMsg,
676: UserProfile profile, Key key) throws MessagingException,
677: java.io.IOException, java.security.GeneralSecurityException {
678: if (key == null && profile != null) {
679: key = profile.getEncryptionKey();
680: }
681:
682: if (key == null) {
683: // get user input
684: }
685:
686: if (key != null) {
687: if (key instanceof net.suberic.crypto.EncryptionKey) {
688: return ((EncryptionKey) key).getEncryptionUtils()
689: .signMessage(Pooka.getDefaultSession(), mMsg,
690: key);
691: } else {
692: return EncryptionManager.getEncryptionUtils("PGP")
693: .signMessage(Pooka.getDefaultSession(), mMsg,
694: key);
695: }
696: } else {
697: return mMsg;
698: }
699: }
700:
701: /**
702: * Returns the EncryptionKeyManager for this type.
703: */
704: EncryptionKeyManager getKeyMgr(String type) {
705: if (type == EncryptionManager.PGP)
706: return pgpKeyMgr;
707: else if (type == EncryptionManager.SMIME)
708: return smimeKeyMgr;
709: else
710: return null;
711: }
712: }
|