View Javadoc

1   /*
2    * Copyright (c) 2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.mercury.accounts.spring;
14  
15  import java.io.File;
16  import java.io.FileInputStream;
17  import java.io.FileOutputStream;
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.math.BigInteger;
22  import java.net.MalformedURLException;
23  import java.security.InvalidKeyException;
24  import java.security.Key;
25  import java.security.KeyPair;
26  import java.security.KeyPairGenerator;
27  import java.security.KeyStore;
28  import java.security.KeyStoreException;
29  import java.security.NoSuchAlgorithmException;
30  import java.security.NoSuchProviderException;
31  import java.security.Security;
32  import java.security.SignatureException;
33  import java.security.cert.Certificate;
34  import java.security.cert.CertificateEncodingException;
35  import java.security.cert.CertificateException;
36  import java.util.Date;
37  import java.util.HashMap;
38  import java.util.Map;
39  
40  import org.abstracthorizon.mercury.common.ConfigurableStorageManager;
41  import org.abstracthorizon.mercury.common.SimpleStorageManager;
42  import org.bouncycastle.jce.X509Principal;
43  import org.bouncycastle.jce.provider.BouncyCastleProvider;
44  import org.bouncycastle.x509.X509V3CertificateGenerator;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  /**
49   * Storage manager that uses maildir java mail provider for storing messages, properties file for defining
50   * storages, domains and aliases and keystore for stroring passwords.
51   *
52   * @author Daniel Sendula
53   */
54  public class MaildirKeystoreStorageManager extends SimpleStorageManager
55          implements ConfigurableStorageManager {
56  
57      /** Logger */
58      protected Logger logger = LoggerFactory.getLogger(getClass());
59  
60      /** Path where maildir domains are stored */
61      private File mailboxesPath;
62  
63      /** Keystore URL */
64      private File keystoreFile;
65  
66      /** Keystore password */
67      private String keystorePassword;
68  
69      /** Keystore type */
70      private String keystoreType = KeyStore.getDefaultType();
71  
72      /** Keystore provider */
73      private String keystoreProvider = "";
74  
75      /** Options */
76      private Map<String, Object> options = new HashMap<String, Object>();
77  
78      /**
79       * Constructor
80       */
81      public MaildirKeystoreStorageManager() {
82      }
83  
84      /**
85       * Sets keystore password
86       * @param password keystore password
87       */
88      public void setKeyStorePassword(String password) {
89          this.keystorePassword = password;
90          if (password != null) {
91              options.put("keyStorePassword", password);
92          } else {
93              options.remove("keyStorePassword");
94          }
95      }
96  
97      /**
98       * Keystore location
99       * @param keystoreLocation keystore location
100      * @throws IOException
101      */
102     public void setKeyStoreFile(File keystoreFile) throws IOException {
103         this.keystoreFile = keystoreFile;
104     }
105 
106     /**
107      * Returns keystore resource
108      * @return keystore resource
109      */
110     public File getKeyStoreFile() {
111         return keystoreFile;
112     }
113 
114     /**
115      * Sets mailboxes path
116      * @param mailboxesPath path mailboxes are defined on
117      * @throws IOException
118      */
119     public void setMailboxesPath(File mailboxesPath) throws IOException {
120         this.mailboxesPath = mailboxesPath;
121     }
122 
123     /**
124      * Returns mailboxes path
125      * @return mailboxes path
126      */
127     public File getMailboxesPath() {
128         return mailboxesPath;
129     }
130 
131     /**
132      * Sets keystore type
133      * @param type keystore type
134      */
135     public void setKeyStoreType(String type) {
136         keystoreType = type;
137         if (type != null) {
138             options.put("keyStoreType", type);
139         } else {
140             options.remove("keyStoreType");
141         }
142     }
143 
144     /**
145      * Returns keystore type
146      * @return keystore type
147      */
148     public String getKeyStoreType() {
149         return keystoreType;
150     }
151 
152     /**
153      * Sets keystore provider
154      * @param provider keystore provider
155      */
156     public void setKeyStoreProvider(String provider) {
157         this.keystoreProvider = provider;
158         if (provider != null) {
159             options.put("keyStoreProvider", provider);
160         } else {
161             options.remove("keyStoreProvider");
162         }
163     }
164 
165     /**
166      * Returns keystore provider
167      * @return keystore provider
168      */
169     public String getKeyStoreProvider() {
170         return keystoreProvider;
171     }
172 
173     /**
174      * Loads keystore
175      * @return keystore
176      * @throws KeyStoreException
177      * @throws NoSuchProviderException
178      * @throws MalformedURLException
179      * @throws IOException
180      * @throws NoSuchAlgorithmException
181      * @throws CertificateException
182      */
183     protected KeyStore loadKeyStore() throws KeyStoreException, NoSuchProviderException, MalformedURLException,
184         IOException, NoSuchAlgorithmException, CertificateException {
185 
186         logger.debug("Loading keystore from " + keystoreFile.getAbsolutePath());
187         InputStream keystoreInputStream = new FileInputStream(keystoreFile);
188         try {
189             KeyStore keyStore;
190             if ((keystoreProvider == null) || (keystoreProvider.length() == 0)) {
191                 keyStore = KeyStore.getInstance(keystoreType);
192             } else {
193                 keyStore = KeyStore.getInstance(keystoreType, keystoreProvider);
194             }
195 
196             /* Load KeyStore contents from file */
197             keyStore.load(keystoreInputStream, keystorePassword.toCharArray());
198             return keyStore;
199 
200         } finally {
201             keystoreInputStream.close();
202         }
203     }
204 
205     /**
206      * Stores keystore back to provided resource
207      * @param keystore keystore to be stored
208      * @throws KeyStoreException
209      * @throws NoSuchProviderException
210      * @throws MalformedURLException
211      * @throws IOException
212      * @throws NoSuchAlgorithmException
213      * @throws CertificateException
214      */
215     protected void storeKeyStore(KeyStore keystore) throws KeyStoreException, NoSuchProviderException, MalformedURLException,
216         IOException, NoSuchAlgorithmException, CertificateException {
217 
218         logger.debug("Storing keystore to " + keystoreFile.getAbsolutePath());
219         OutputStream keystoreOutputStream = new FileOutputStream(keystoreFile);
220 
221         try {
222             keystore.store(keystoreOutputStream, keystorePassword.toCharArray());
223         } finally {
224             keystoreOutputStream.close();
225         }
226     }
227 
228     /**
229      * Adds entry to a keystore
230      * @param keyStore keystore
231      * @param entry entry
232      * @param password password an entry
233      * @throws NoSuchAlgorithmException
234      * @throws KeyStoreException
235      * @throws InvalidKeyException
236      * @throws SecurityException
237      * @throws SignatureException
238      * @throws NoSuchProviderException 
239      * @throws IllegalStateException 
240      * @throws CertificateEncodingException 
241      */
242     protected void addEntryToStore(KeyStore keyStore, String entry, char[] password) throws NoSuchAlgorithmException, KeyStoreException, InvalidKeyException, SecurityException, SignatureException, CertificateEncodingException, IllegalStateException, NoSuchProviderException {
243         String name = "CN=" + entry + ", OU=, O=, L=, ST=, C=";
244             //CN=Me, OU=Java Card Development, O=MyFirm, C=UK, ST=MyCity";
245 
246         Security.addProvider(new BouncyCastleProvider());
247 
248         X509V3CertificateGenerator generator = new X509V3CertificateGenerator();
249         KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
250         kpGen.initialize(1024);
251         KeyPair pair = kpGen.generateKeyPair();
252 
253         generator.setSerialNumber(BigInteger.valueOf(1));
254         generator.setIssuerDN(new X509Principal(name));
255         generator.setNotBefore(new Date());
256         generator.setNotAfter(new Date(System.currentTimeMillis()+1000*60*60*24*365)); // a year
257         generator.setSubjectDN(new X509Principal(name));
258         generator.setPublicKey(pair.getPublic());
259         generator.setSignatureAlgorithm("MD5WithRSAEncryption");
260 
261         Certificate cert = generator.generate(pair.getPrivate(), "BC");
262 
263         keyStore.setKeyEntry(entry, pair.getPrivate(), password, new Certificate[]{cert});
264 
265     }
266 
267     protected String decorateStoreString(String mailbox, String domain, String storeString) {
268         if ((storeString == null) || (storeString.length() == 0)) {
269             File mailboxDir = ensureExists(mailbox, domain);
270             storeString = "maildir://" + mailbox + "@" + domain + "/" + mailboxDir.getAbsolutePath();
271         }
272         return storeString;
273     }
274 
275     /**
276      * Ensures that mailbox (in domain) exists
277      * @param mailbox mailbox
278      * @param domain domain
279      * @return a file of the mailbox (maildir)
280      */
281     protected File ensureExists(String mailbox, String domain) {
282         File domainPath = null;
283         if (domain != null) {
284             domainPath = new File(mailboxesPath, domain);
285         } else {
286             domainPath = mailboxesPath;
287         }
288         if (!domainPath.exists()) {
289             domainPath.mkdirs();
290         }
291         File mailboxPath = new File(domainPath, mailbox);
292         if (!mailboxPath.exists()) {
293             mailboxPath.mkdirs();
294         }
295         makeMaildirLayout(mailboxPath);
296         return mailboxPath;
297     }
298 
299     /**
300      * Makes maildir layout of an mailbox
301      * @param mailbox mailbox path
302      */
303     protected void makeMaildirLayout(File mailbox) {
304         File inbox = new File(mailbox, ".inbox");
305         File cur = new File(inbox, "cur");
306         File nw = new File(inbox, "new");
307         File tmp = new File(inbox, "tmp");
308         inbox.mkdirs();
309         cur.mkdirs();
310         nw.mkdirs();
311         tmp.mkdirs();
312         // TODO catch problems and report them somehow
313     }
314 
315     /**
316      * Adds new mailbox
317      * @param mailbox mailbox
318      * @param domain domain
319      * @param password password
320      */
321     public void addMailbox(String mailbox, String domain, char[] password) {
322         try {
323             /*File mailboxDir =*/ ensureExists(mailbox, domain);
324 
325             // String store = "maildir://" + mailbox + "@" + domain + "/" + mailboxDir.getAbsolutePath();
326             super.addMailbox(makeEntry(mailbox, domain), "");
327 
328             String entry = makeEntry(mailbox, domain);
329 
330             KeyStore keyStore = loadKeyStore();
331             addEntryToStore(keyStore, entry, password);
332             storeKeyStore(keyStore);
333         } catch (Exception e) {
334             throw new RuntimeException(e);
335         }
336     }
337 
338     /**
339      * Removes mailbox
340      * @param mailbox mailbox
341      * @param domain domain
342      * @return <code>true</code> if mailbox existed
343      */
344     public boolean removeMailbox(String mailbox, String domain) {
345         boolean res = false;
346         try {
347             res = super.removeMailbox(mailbox, domain);
348             String entry = makeEntry(mailbox, domain);
349             KeyStore keyStore = loadKeyStore();
350             keyStore.deleteEntry(entry);
351             storeKeyStore(keyStore);
352         } catch (Exception e) {
353             throw new RuntimeException(e);
354         }
355         return res;
356     }
357 
358     /**
359      * Changes password of a mailbox. This is administrator's function since it requires only new
360      * password
361      *
362      * @param mailbox mailbox
363      * @param domain domain
364      * @param newPassword new password
365      */
366     public void changeMailboxPassword(String mailbox, String domain, char[] newPassword) {
367         changeMailboxPassword(mailbox, domain, null, newPassword);
368     }
369 
370     /**
371      * Changes mailboxes password. This is user's function
372      * @param mailbox mailbox
373      * @param domain domain
374      * @param oldPassword old password
375      * @param newPassword new password
376      */
377     public void changeMailboxPassword(String mailbox, String domain, char[] oldPassword, char[] newPassword) {
378         try {
379             String entry = makeEntry(mailbox, domain);
380             KeyStore keyStore = loadKeyStore();
381             if (oldPassword != null) {
382                 Key key = keyStore.getKey(entry, oldPassword);
383 
384                 Certificate[] certs = keyStore.getCertificateChain(entry);
385 
386                 keyStore.setKeyEntry(entry, key, newPassword, certs);
387             } else {
388                 keyStore.deleteEntry(entry);
389                 addEntryToStore(keyStore, entry, newPassword);
390             }
391             storeKeyStore(keyStore);
392         } catch (Exception e) {
393             throw new RuntimeException(e);
394         }
395     }
396 
397     /**
398      * Returns an array of mailbox names in all domains
399      * @return an array of mailbox names
400      */
401     public String[] getMailboxNames() {
402         return super.getMailboxNames();
403     }
404 
405     /**
406      * Returns an array of mailbox names for given domain
407      * @param domain domain
408      * @return an array of mailbox names for given domain
409      */
410     public String[] getMailboxNames(String domain) {
411         return super.getMailboxNames(domain);
412     }
413 
414     /**
415      * Adds new domain
416      * @param domain
417      */
418     public void addDomain(String domain) {
419         super.addDomain(domain);
420     }
421 
422     /**
423      * Removes domain
424      * @param domain domain
425      * @return <code>true</code> if domain existed
426      */
427     public boolean removeDomain(String domain) {
428         return super.removeDomain(domain);
429     }
430 
431     /**
432      * Returns domains
433      * @return domains
434      */
435     public String[] getDomains() {
436         return super.getDomains();
437     }
438 
439     /**
440      * Sets main domain
441      * @param domain
442      */
443     public void setMainDomain(String domain) {
444         super.setMainDomain(domain);
445     }
446 
447 }