001    /*
002     * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003     * Copyright (C) 2011 NightLabs Consulting GmbH
004     *
005     * This program is free software: you can redistribute it and/or modify
006     * it under the terms of the GNU Affero General Public License as
007     * published by the Free Software Foundation, either version 3 of the
008     * License, or (at your option) any later version.
009     *
010     * This program is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU Affero General Public License for more details.
014     *
015     * You should have received a copy of the GNU Affero General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    package org.cumulus4j.keystore;
019    
020    import java.io.DataOutputStream;
021    import java.io.File;
022    import java.io.FileInputStream;
023    import java.io.FileNotFoundException;
024    import java.io.FileOutputStream;
025    import java.io.IOException;
026    import java.io.OutputStream;
027    import java.lang.ref.WeakReference;
028    import java.security.GeneralSecurityException;
029    import java.security.SecureRandom;
030    import java.security.spec.KeySpec;
031    import java.util.ArrayList;
032    import java.util.Arrays;
033    import java.util.Collections;
034    import java.util.Date;
035    import java.util.HashMap;
036    import java.util.LinkedList;
037    import java.util.List;
038    import java.util.Map;
039    import java.util.Set;
040    import java.util.SortedSet;
041    import java.util.Timer;
042    import java.util.TimerTask;
043    import java.util.TreeSet;
044    import java.util.UUID;
045    
046    import javax.crypto.SecretKey;
047    import javax.crypto.SecretKeyFactory;
048    import javax.crypto.spec.PBEKeySpec;
049    
050    import org.bouncycastle.crypto.CryptoException;
051    import org.bouncycastle.crypto.params.KeyParameter;
052    import org.bouncycastle.crypto.params.ParametersWithIV;
053    import org.cumulus4j.crypto.Cipher;
054    import org.cumulus4j.crypto.CipherOperationMode;
055    import org.cumulus4j.crypto.CryptoRegistry;
056    import org.cumulus4j.keystore.prop.LongProperty;
057    import org.cumulus4j.keystore.prop.Property;
058    import org.slf4j.Logger;
059    import org.slf4j.LoggerFactory;
060    
061    /**
062     * <p>
063     * <code>KeyStore</code> is a storage facility for cryptographic keys.
064     * </p>
065     * <p>
066     * An instance of <code>KeyStore</code> manages a file in the local file system, in which it stores
067     * the keys used by the Cumulus4j-DataNucleus-plug-in in an encrypted form. All data written to the
068     * file is encrypted, hence plain data never touches the local file system (except for
069     * <a target="_blank" href="http://en.wikipedia.org/wiki/Swap_space">swapping</a>!).
070     * </p>
071     * <p>
072     * For every read/write operation, the <code>KeyStore</code> requires a user to authenticate via a
073     * user-name and a password. The password is used to encrypt/decrypt an internally used master-key
074     * which is then used to encrypt/decrypt the actual keys used by the Cumulus4j-DataNucleus-plug-in.
075     * Due to this internal master key, a user can be added or deleted and a user's password can be
076     * changed without the need of decrypting and encrypting all the contents of the KeyStore.
077     * </p>
078     * <p>
079     * By default, a <code>KeyStore</code> {@link #generateKey(String, char[]) generates keys} with a size
080     * of 256 bit. This can be controlled, however, by specifying the system property
081     * {@value #SYSTEM_PROPERTY_KEY_SIZE} (e.g. passing the argument "-Dcumulus4j.KeyStore.keySize=128"
082     * to the <code>java</code> command line will switch to 128-bit-keys).
083     * </p>
084     * <p>
085     * <b>Important:</b> As the master key is generated when the first
086     * {@link #createUser(String, char[], String, char[]) user is created} and is then not changed anymore, you must therefore
087     * specify the desired key-size already at the moment when you initialise the key store (i.e. create the first user). If
088     * you change the key-size later, it will affect only those keys that are created later.
089     * </p>
090     * <p>
091     * Note, that the "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" does not
092     * need to be installed for very strong cryptography, because we don't use the JCE (see {@link Cipher}).
093     * </p>
094     * <h3>File format of the key store file (version 1)</h3>
095     * <p>
096     * <table border="1" width="100%">
097     *      <tbody>
098     *      <tr>
099     *              <td align="right" valign="top"><b>Bytes</b></td><td valign="top"><b>Descrition</b></td>
100     *      </tr>
101     *      <tr>
102     *              <td align="right" valign="top">17</td><td valign="top">Header "Cumulus4jKeyStore" (ASCII encoded)</td>
103     *      </tr>
104     *      <tr>
105     *              <td align="right" valign="top">4</td><td valign="top">int: File version</td>
106     *      </tr>
107     *      <tr>
108     *              <td align="right" valign="top">4</td><td valign="top">int: Number of entries in 'Block A' to follow.</td>
109     *      </tr>
110     *      <tr>
111     *              <td colspan="2">
112     *              <table bgcolor="#F0F0F0" border="1" width="100%">
113     *                      <tbody>
114     *                      <tr><td bgcolor="#D0D0D0" colspan="2"><b>Block A: String constants</b></td></tr>
115     *                      <tr>
116     *                              <td colspan="2">
117     * In order to reduce the file size (and thus increase the write speed), various
118     * strings like encryption algorithm, checksum algorithm and the like are not written
119     * again and again for every key, but instead only once here. In every key, these
120     * Strings are then referenced instead by their position-index (zero-based).
121     *                              </td>
122     *                      </tr>
123     *
124     *                      <tr>
125     *                              <td align="right" valign="top"><b>Bytes</b></td><td valign="top"><b>Descrition</b></td>
126     *                      </tr>
127     *                      <tr>
128     *                              <td align="right" valign="top">2</td><td valign="top">short <i>len</i>: Number of bytes to follow (written by {@link DataOutputStream#writeUTF(String)}).</td>
129     *                      </tr>
130     *                      <tr>
131     *                              <td align="right" valign="top"><i>len</i></td><td valign="top">String: Constant&apos;s value (written by {@link DataOutputStream#writeUTF(String)}).</td>
132     *                      </tr>
133     *                      </tbody>
134     *              </table>
135     *              </td>
136     *      </tr>
137     *
138     *
139     *      <tr>
140     *              <td align="right" valign="top">4</td><td valign="top">int: Number of entries in 'Block B' to follow.</td>
141     *      </tr>
142     *  <tr>
143     *              <td colspan="2">
144     *              <table bgcolor="#F0F0F0" border="1" width="100%">
145     *                      <tbody>
146     *                      <tr><td bgcolor="#D0D0D0" colspan="2"><b>Block B: User-key-map</b></td></tr>
147     *
148     *                      <tr>
149     *                              <td colspan="2">
150     *                                      For every user, the master-key is stored encrypted with the user's password in this block.
151     *                              </td>
152     *                      </tr>
153     *
154     *                      <tr>
155     *                              <td align="right" valign="top"><b>Bytes</b></td><td valign="top"><b>Descrition</b></td>
156     *                      </tr>
157     *
158     *                      <tr>
159     *                              <td align="right" valign="top">2</td><td valign="top">short <i>len1</i>: User name: Number of bytes to follow (written by {@link DataOutputStream#writeUTF(String)}).</td>
160     *                      </tr>
161     *                      <tr>
162     *                              <td align="right" valign="top"><i>len1</i></td><td valign="top">String: User name (written by {@link DataOutputStream#writeUTF(String)}).</td>
163     *                      </tr>
164     *
165     *                      <tr>
166     *                              <td align="right" valign="top">4</td><td valign="top">int: Key size for the password-based key (in bits! i.e. usually 128 or 256).</td>
167     *                      </tr>
168     *                      <tr>
169     *                              <td align="right" valign="top">4</td><td valign="top">int: Iteration count for the password-based key.</td>
170     *                      </tr>
171     *                      <tr>
172     *                              <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the key-generator-algorithm for creating the password-based key (index in the list of 'Block A').</td>
173     *                      </tr>
174     *
175     *                      <tr>
176     *                              <td align="right" valign="top">2</td><td valign="top">UNSIGNED short <i>len2</i>: Salt: Number of bytes to follow (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
177     *                      </tr>
178     *                      <tr>
179     *                              <td align="right" valign="top"><i>len2</i></td><td valign="top">byte[]: Salt to be used when generating the password-based key (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
180     *                      </tr>
181     *
182     *                      <!-- BEGIN written by {@link AbstractEncryptedData#write(DataOutputStream, Map)} -->
183     *                              <tr>
184     *                                      <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the encryption algorithm used to encrypt this record's data (index in the list of 'Block A').</td>
185     *                              </tr>
186     *
187     *                              <tr>
188     *                                      <td align="right" valign="top">2</td><td valign="top">UNSIGNED short <i>lenIV</i>: IV: Number of bytes to follow (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
189     *                              </tr>
190     *                              <tr>
191     *                                      <td align="right" valign="top"><i>lenIV</i></td><td valign="top">byte[]: The actual IV (initialisation vector) used to encrypt the key's data (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
192     *                              </tr>
193     *
194     *                              <tr>
195     *                                      <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the <a target="_blank" href="http://en.wikipedia.org/wiki/Message_authentication_code">MAC</a> algorithm used to authenticate this record's data (index in the list of 'Block A').</td>
196     *                              </tr>
197     *
198     *                              <tr>
199     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMACKey</i>: MAC key: Number of bytes in the MAC's key.</td>
200     *                              </tr>
201     *                              <tr>
202     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMACIV</i>: MAC IV: Number of bytes in the MAC's IV.</td>
203     *                              </tr>
204     *                              <tr>
205     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMAC</i>: MAC: Number of bytes in the MAC.</td>
206     *                              </tr>
207     *
208     *                              <tr>
209     *                                      <td colspan="2">
210     *                                              <table bgcolor="#E0E0E0" border="1" width="100%">
211     *                                                      <tbody>
212     *                                                              <tr><td bgcolor="#C0C0C0" colspan="2"><b>ENCRYPTED</b></td></tr>
213     *                                                              <tr>
214     *                                                                      <td align="right" valign="top"><i>lenMACKey</i></td><td valign="top">MAC key: The actual MAC's key (random).</td>
215     *                                                              </tr>
216     *                                                              <tr>
217     *                                                                      <td align="right" valign="top"><i>lenMACIV</i></td><td valign="top">MAC IV: The actual MAC's IV (random).</td>
218     *                                                              </tr>
219     *                                                              <tr>
220     *                                                                      <td align="right" valign="top"><i>all until MAC</i></td><td valign="top">The actual data (payload).</td>
221     *                                                              </tr>
222     *                                                              <tr>
223     *                                                                      <td align="right" valign="top"><i>lenMAC</i></td><td valign="top">MAC: The actual MAC.</td>
224     *                                                              </tr>
225     *                                                      </tbody>
226     *                                              </table>
227     *                                      </td>
228     *                              </tr>
229     *
230     *                      <!-- END written by {@link AbstractEncryptedData#write(DataOutputStream, Map)} -->
231     *
232     *                      </tbody>
233     *              </table>
234     *              </td>
235     *      </tr>
236     *
237     *
238     *      <tr>
239     *              <td align="right" valign="top">4</td><td valign="top">int: Number of entries in 'Block C' to follow.</td>
240     *      </tr>
241     *      <tr>
242     *              <td colspan="2">
243     *              <table bgcolor="#F0F0F0" border="1" width="100%">
244     *                      <tbody>
245     *                      <tr><td bgcolor="#D0D0D0" colspan="2"><b>Block C: Key-ID-key-map</b></td></tr>
246     *
247     *                      <tr>
248     *                              <td colspan="2">
249     *                                      This block contains the actual keys. Every key is encrypted with the master-key.
250     *                              </td>
251     *                      </tr>
252     *
253     *                      <tr>
254     *                              <td align="right" valign="top"><b>Bytes</b></td><td valign="top"><b>Descrition</b></td>
255     *                      </tr>
256     *
257     *                      <tr>
258     *                              <td align="right" valign="top">8</td><td valign="top">long: Key identifier.</td>
259     *                      </tr>
260     *
261     *                      <!-- BEGIN written by {@link AbstractEncryptedData#write(DataOutputStream, Map)} -->
262     *                              <tr>
263     *                                      <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the encryption algorithm used to encrypt this record's data (index in the list of 'Block A').</td>
264     *                              </tr>
265     *
266     *                              <tr>
267     *                                      <td align="right" valign="top">2</td><td valign="top">UNSIGNED short <i>lenIV</i>: IV: Number of bytes to follow (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
268     *                              </tr>
269     *                              <tr>
270     *                                      <td align="right" valign="top"><i>lenIV</i></td><td valign="top">byte[]: The actual IV (initialisation vector) used to encrypt the key's data (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
271     *                              </tr>
272     *
273     *                              <tr>
274     *                                      <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the MAC algorithm used to authenticate this record's data (index in the list of 'Block A').</td>
275     *                              </tr>
276     *
277     *                              <tr>
278     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMACKey</i>: MAC key: Number of bytes in the MAC's key.</td>
279     *                              </tr>
280     *                              <tr>
281     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMACIV</i>: MAC IV: Number of bytes in the MAC's IV.</td>
282     *                              </tr>
283     *                              <tr>
284     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMAC</i>: MAC: Number of bytes in the MAC.</td>
285     *                              </tr>
286     *
287     *                              <tr>
288     *                                      <td colspan="2">
289     *                                              <table bgcolor="#E0E0E0" border="1" width="100%">
290     *                                                      <tbody>
291     *                                                              <tr><td bgcolor="#C0C0C0" colspan="2"><b>ENCRYPTED</b></td></tr>
292     *                                                              <tr>
293     *                                                                      <td align="right" valign="top"><i>lenMACKey</i></td><td valign="top">MAC key: The actual MAC's key (random).</td>
294     *                                                              </tr>
295     *                                                              <tr>
296     *                                                                      <td align="right" valign="top"><i>lenMACIV</i></td><td valign="top">MAC IV: The actual MAC's IV (random).</td>
297     *                                                              </tr>
298     *                                                              <tr>
299     *                                                                      <td align="right" valign="top"><i>all until MAC</i></td><td valign="top">The actual data (payload).</td>
300     *                                                              </tr>
301     *                                                              <tr>
302     *                                                                      <td align="right" valign="top"><i>lenMAC</i></td><td valign="top">MAC: The actual MAC.</td>
303     *                                                              </tr>
304     *                                                      </tbody>
305     *                                              </table>
306     *                                      </td>
307     *                              </tr>
308     *
309     *                      <!-- END written by {@link AbstractEncryptedData#write(DataOutputStream, Map)} -->
310     *
311     *                      </tbody>
312     *              </table>
313     *              </td>
314     *      </tr>
315     *
316     *
317     *      <tr>
318     *              <td align="right" valign="top">4</td><td valign="top">int: Number of entries in 'Block D' to follow.</td>
319     *      </tr>
320     *      <tr>
321     *              <td colspan="2">
322     *              <table bgcolor="#F0F0F0" border="1" width="100%">
323     *                      <tbody>
324     *                      <tr><td bgcolor="#D0D0D0" colspan="2"><b>Block D: Properties</b></td></tr>
325     *                      <tr>
326     *                              <td colspan="2">
327     * See {@link Property} for details about what this block is used for.
328     *                              </td>
329     *                      </tr>
330     *                      <tr>
331     *                              <td align="right" valign="top"><b>Bytes</b></td><td valign="top"><b>Descrition</b></td>
332     *                      </tr>
333     *
334     *                      <tr>
335     *                              <td align="right" valign="top">2</td><td valign="top">short <i>len1</i>: Property name: Number of bytes to follow (written by {@link DataOutputStream#writeUTF(String)}).</td>
336     *                      </tr>
337     *                      <tr>
338     *                              <td align="right" valign="top"><i>len1</i></td><td valign="top">String: Property name (written by {@link DataOutputStream#writeUTF(String)}).</td>
339     *                      </tr>
340     *
341     *                      <tr>
342     *                              <td align="right" valign="top">4</td><td valign="top">int: Reference to the fully qualified class name of the {@link Property} (index in the list of 'Block A').</td>
343     *                      </tr>
344     *
345     *                      <!-- BEGIN written by {@link AbstractEncryptedData#write(DataOutputStream, Map)} -->
346     *                              <tr>
347     *                                      <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the encryption algorithm used to encrypt this record's data (index in the list of 'Block A').</td>
348     *                              </tr>
349     *
350     *                              <tr>
351     *                                      <td align="right" valign="top">2</td><td valign="top">UNSIGNED short <i>lenIV</i>: IV: Number of bytes to follow (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
352     *                              </tr>
353     *                              <tr>
354     *                                      <td align="right" valign="top"><i>lenIV</i></td><td valign="top">byte[]: The actual IV (initialisation vector) used to encrypt the key's data (written by {@link KeyStoreUtil#writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])}).</td>
355     *                              </tr>
356     *
357     *                              <tr>
358     *                                      <td align="right" valign="top">4</td><td valign="top">int: Reference to the name of the MAC algorithm used to authenticate this record's data (index in the list of 'Block A').</td>
359     *                              </tr>
360     *
361     *                              <tr>
362     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMACKey</i>: MAC key: Number of bytes in the MAC's key.</td>
363     *                              </tr>
364     *                              <tr>
365     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMACIV</i>: MAC IV: Number of bytes in the MAC's IV.</td>
366     *                              </tr>
367     *                              <tr>
368     *                                      <td align="right" valign="top">2</td><td valign="top">short <i>lenMAC</i>: MAC: Number of bytes in the MAC.</td>
369     *                              </tr>
370     *
371     *                              <tr>
372     *                                      <td colspan="2">
373     *                                              <table bgcolor="#E0E0E0" border="1" width="100%">
374     *                                                      <tbody>
375     *                                                              <tr><td bgcolor="#C0C0C0" colspan="2"><b>ENCRYPTED</b></td></tr>
376     *                                                              <tr>
377     *                                                                      <td align="right" valign="top"><i>lenMACKey</i></td><td valign="top">MAC key: The actual MAC's key (random).</td>
378     *                                                              </tr>
379     *                                                              <tr>
380     *                                                                      <td align="right" valign="top"><i>lenMACIV</i></td><td valign="top">MAC IV: The actual MAC's IV (random).</td>
381     *                                                              </tr>
382     *                                                              <tr>
383     *                                                                      <td align="right" valign="top"><i>all until MAC</i></td><td valign="top">The actual data (payload).</td>
384     *                                                              </tr>
385     *                                                              <tr>
386     *                                                                      <td align="right" valign="top"><i>lenMAC</i></td><td valign="top">MAC: The actual MAC.</td>
387     *                                                              </tr>
388     *                                                      </tbody>
389     *                                              </table>
390     *                                      </td>
391     *                              </tr>
392     *
393     *                      <!-- END written by {@link AbstractEncryptedData#write(DataOutputStream, Map)} -->
394     *
395     *                      <tr>
396     *                              <td align="right" valign="top">20</td><td valign="top">SHA1 checksum over the complete file except for the header "Cumulus4jKeyStore", i.e. from the file version at byte offset 17 (including) till here (excluding).</td>
397     *                      </tr>
398     *                      </tbody>
399     *              </table>
400     *              </td>
401     *      </tr>
402     *
403     *      </tbody>
404     * </table>
405     * </p>
406     *
407     * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
408     */
409    public class KeyStore
410    {
411            static final Logger logger = LoggerFactory.getLogger(KeyStore.class);
412    
413    //      private static final BouncyCastleProvider bouncyCastleProvider = new BouncyCastleProvider();
414    //      static {
415    //              Security.insertProviderAt(bouncyCastleProvider, 2);
416    //
417    //              KeyGenerator kg;
418    //              try {
419    //                      kg = KeyGenerator.getInstance("AES");
420    //              } catch (NoSuchAlgorithmException e) {
421    //                      logger.warn("KeyGenerator.getInstance(\"AES\") failed: " + e, e);
422    //                      kg = null;
423    //              }
424    //
425    //              if (kg == null || kg.getProvider() != bouncyCastleProvider)
426    //                      logger.warn("BouncyCastleProvider was NOT registered!!!");
427    //      }
428    
429            /**
430             * <p>
431             * System property to control the size of the keys {@link #generateKey(String, char[]) generated}. This
432             * includes not only the actual keys for the main encryption/decryption (in the database), but also the
433             * master key used to protect the file managed by the <code>KeyStore</code>.
434             * </p>
435             * <p>
436             * By default (if the system property {@value #SYSTEM_PROPERTY_KEY_SIZE} is not specified), keys will have a size of 256 bit.
437             * </p>
438             * <p>
439             * Note, that specifying the system property does not change any old keys - only new keys are generated
440             * with the currently active key size. Therefore, if you want to ensure that the internal master key is
441             * only 128 bit long, you have to make sure that the proper key size is specified when the first
442             * {@link #createUser(String, char[], String, char[]) user is created}!
443             * </p>
444             */
445            public static final String SYSTEM_PROPERTY_KEY_SIZE = "cumulus4j.KeyStore" + ".keySize";
446    
447            /**
448             * <p>
449             * System property to control the encryption algorithm that is used to encrypt data within the key-store. Whenever a new user is
450             * created or a new key is generated, data has to be encrypted (note that the encryption does not happen directly
451             * before data is written to the file, but already most data in memory is encrypted!).
452             * </p>
453             * <p>
454             * By default (if the system property {@value #SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM} is not specified),
455             * "Twofish/GCM/NoPadding" is used. For example, to switch to "AES/CFB/NoPadding", you'd have
456             * to specify the command line argument "-Dcumulus4j.KeyStore.encryptionAlgorithm=AES/CFB/NoPadding".
457             * </p>
458             * <p>
459             * See <a target="_blank" href="http://cumulus4j.org/1.0.1/documentation/supported-algorithms.html">this document</a>
460             * for further information about what values are supported.
461             * </p>
462             * <p>
463             * <b>Important:</b> The default MAC algorithm is "NONE", which is a very bad choice for most encryption algorithms!
464             * Therefore, you must change the MAC algorithm via the system property {@value #SYSTEM_PROPERTY_MAC_ALGORITHM}
465             * if you change the encryption algorithm!
466             * </p>
467             */
468            public static final String SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM = "cumulus4j.KeyStore" + ".encryptionAlgorithm";
469    
470            /**
471             * <p>
472             * System property to control the <a target="_blank" href="http://en.wikipedia.org/wiki/Message_authentication_code">MAC</a>
473             * algorithm that is used to protect the data within the key-store against manipulation.
474             * </p>
475             * <p>
476             * Whenever data is encrypted, this MAC algorithm is used to calculate a MAC over the original plain-text-data.
477             * The MAC is then stored together with the plain-text-data within the encrypted area.
478             * When data is decrypted, the MAC is calculated again over the decrypted plain-text-data and compared to the
479             * original MAC in order to make sure (1) that data was correctly decrypted [i.e. the password provided by the user
480             * is correct] and (2) that the data in the key-store was not manipulated by an attacker.
481             * </p>
482             * <p>
483             * The MAC algorithm used during encryption is stored in the encryption-record's meta-data in order
484             * to use the correct algorithm during decryption, no matter what current MAC algorithm is configured.
485             * Therefore, you can safely change this setting at any time - it will affect future encryption
486             * operations, only.
487             * </p>
488             * <p>
489             * Some block cipher modes (e.g. <a target="_blank" href="http://en.wikipedia.org/wiki/Galois/Counter_Mode">GCM</a>) already include authentication
490             * and therefore no MAC is necessary. In this case, you can specify the MAC algorithm {@value #MAC_ALGORITHM_NONE}.
491             * </p>
492             * <p>
493             * <b>Important:</b> If you specify the MAC algorithm "NONE" and use an encryption algorithm without
494             * authentication, the key store will not be able to detect a wrong password and instead return
495             * corrupt data!!! Be VERY careful with the MAC algorithm "NONE"!!!
496             * </p>
497             * <p>
498             * The default value (used when this system property is not specified) is "NONE", because the default
499             * encryption algorithm is "Twofish/GCM/NoPadding", which (due to "GCM") does not require an additional
500             * MAC.
501             * </p>
502             */
503            public static final String SYSTEM_PROPERTY_MAC_ALGORITHM = "cumulus4j.KeyStore" + ".macAlgorithm";
504    
505            /**
506             * <p>
507             * Constant for deactivating the <a target="_blank" href="http://en.wikipedia.org/wiki/Message_authentication_code">MAC</a>.
508             * </p>
509             * <p>
510             * <b>Important: Deactivating the MAC is dangerous!</b> Choose this value only, if you are absolutely
511             * sure that your {@link #SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM encryption algorithm} already
512             * provides authentication - like <a target="_blank" href="http://en.wikipedia.org/wiki/Galois/Counter_Mode">GCM</a>
513             * does for example.
514             * </p>
515             * @see #SYSTEM_PROPERTY_MAC_ALGORITHM
516             */
517            public static final String MAC_ALGORITHM_NONE = "NONE";
518    
519            private static final String KEY_STORE_PROPERTY_NAME_NEXT_KEY_ID = "nextKeyID";
520    
521            private SecureRandom secureRandom = new SecureRandom();
522    
523            private static Timer expireCacheEntryTimer = new Timer(true);
524    
525            private TimerTask expireCacheEntryTimerTask = new ExipreCacheEntryTimerTask(this);
526    
527            private KeyStoreData keyStoreData = new KeyStoreData();
528    
529            private static class ExipreCacheEntryTimerTask extends TimerTask
530            {
531                    private static final Logger logger = LoggerFactory.getLogger(ExipreCacheEntryTimerTask.class);
532    
533                    private WeakReference<KeyStore> keyStoreRef;
534    
535                    public ExipreCacheEntryTimerTask(KeyStore keyStore)
536                    {
537                            if (keyStore == null)
538                                    throw new IllegalArgumentException("keyStore == null");
539    
540                            this.keyStoreRef = new WeakReference<KeyStore>(keyStore);
541                    }
542    
543                    @Override
544                    public void run()
545                    {
546                            try {
547                                    KeyStore keyStore = keyStoreRef.get();
548                                    if (keyStore == null) {
549                                            logger.info("run: KeyStore has been garbage-collected. Removing this ExipreCacheEntryTimerTask.");
550                                            this.cancel();
551                                            return;
552                                    }
553    
554                                    Date removeCachedEntriesOlderThanThisDate = new Date(System.currentTimeMillis() - 3L * 60L * 1000L); // TODO make this configurable!
555    
556                                    LinkedList<String> userNamesToExpire = new LinkedList<String>();
557                                    synchronized (keyStore) {
558                                            for (CachedMasterKey cmk : keyStore.cache_userName2cachedMasterKey.values()) {
559                                                    if (cmk.getLastUse().before(removeCachedEntriesOlderThanThisDate))
560                                                            userNamesToExpire.add(cmk.getUserName());
561                                            }
562                                    }
563    
564                                    for (String userName : userNamesToExpire) {
565                                            logger.info("run: Expiring cache for user '{}'.", userName);
566                                            keyStore.clearCache(userName);
567                                    }
568    
569                                    if (logger.isDebugEnabled()) {
570                                            synchronized (keyStore) {
571                                                    logger.debug("run: {} users left in cache.", keyStore.cache_userName2cachedMasterKey.size());
572                                            }
573                                    }
574                            } catch (Throwable x) {
575                                    // The TimerThread is cancelled, if a task throws an exception. Furthermore, they are not logged at all.
576                                    // Since we do not want the TimerThread to die, we catch everything (Throwable - not only Exception) and log
577                                    // it here. IMHO there's nothing better we can do. Marco :-)
578                                    logger.error("run: " + x, x);
579                            }
580                    }
581            }
582    
583            /**
584             * Gets the key-size that is currently configured. Therefore, this method checks, if the
585             * system property {@value #SYSTEM_PROPERTY_KEY_SIZE} has been specified, and if so returns its value.
586             * If not, it falls back to 256.
587             *
588             * @return the current key-size.
589             */
590            int getKeySize()
591            {
592                    int ks = keySize;
593    
594                    if (ks == 0) {
595                            String keySizePropName = SYSTEM_PROPERTY_KEY_SIZE;
596                            String keySizePropValue = System.getProperty(keySizePropName);
597                            if (keySizePropValue == null || keySizePropValue.trim().isEmpty()) {
598                                    ks = 256; // default value, if the property was not defined.
599                                    logger.info("getKeySize: System property '{}' is not set. Using default key size ({} bit).", keySizePropName, ks);
600                            }
601                            else {
602                                    try {
603                                            ks = Integer.parseInt(keySizePropValue.trim());
604                                    } catch (NumberFormatException x) {
605                                            NumberFormatException n = new NumberFormatException("Value of system property '" + keySizePropName + "' is not a valid integer!");
606                                            n.initCause(x);
607                                            throw n;
608                                    }
609                                    if (ks < 1)
610                                            throw new IllegalStateException("Value of system property '" + keySizePropName + "' is " + keySize + " but must be >= 1!!!");
611    
612                                    logger.info("getKeySize: System property '{}' is set to {} bit. Using this key size.", keySizePropName, ks);
613                            }
614                            keySize = ks;
615                    }
616    
617                    return ks;
618            }
619            private int keySize = 0;
620    
621    
622            String getEncryptionAlgorithm()
623            {
624                    String ea = encryptionAlgorithm;
625    
626                    if (ea == null) {
627                            String encryptionAlgorithmPropName = SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM;
628                            String encryptionAlgorithmPropValue = System.getProperty(encryptionAlgorithmPropName);
629                            if (encryptionAlgorithmPropValue == null || encryptionAlgorithmPropValue.trim().isEmpty()) {
630                                    ea = "Twofish/GCM/NoPadding"; // default value, if the property was not defined.
631    //                              ea = "Twofish/CBC/PKCS5Padding"; // default value, if the property was not defined.
632    //                              ea = "AES/CBC/PKCS5Padding"; // default value, if the property was not defined.
633    //                              ea = "AES/CFB/NoPadding"; // default value, if the property was not defined.
634                                    logger.info("getEncryptionAlgorithm: System property '{}' is not set. Using default algorithm '{}'.", encryptionAlgorithmPropName, ea);
635                            }
636                            else {
637                                    ea = encryptionAlgorithmPropValue.trim();
638                                    logger.info("getEncryptionAlgorithm: System property '{}' is set to '{}'. Using this encryption algorithm.", encryptionAlgorithmPropName, ea);
639                            }
640                            encryptionAlgorithm = ea;
641                    }
642    
643                    return ea;
644            }
645            private String encryptionAlgorithm = null;
646    
647    
648            String getMACAlgorithm()
649            {
650                    String ma = macAlgorithm;
651    
652                    if (ma == null) {
653                            String macAlgorithmPropName = SYSTEM_PROPERTY_MAC_ALGORITHM;
654                            String macAlgorithmPropValue = System.getProperty(macAlgorithmPropName);
655                            if (macAlgorithmPropValue == null || macAlgorithmPropValue.trim().isEmpty()) {
656                                    ma = MAC_ALGORITHM_NONE; // default value, if the property was not defined.
657                                    logger.info("getMACAlgorithm: System property '{}' is not set. Using default MAC algorithm '{}'.", macAlgorithmPropName, ma);
658                            }
659                            else {
660                                    ma = macAlgorithmPropValue.trim();
661                                    logger.info("getMACAlgorithm: System property '{}' is set to '{}'. Using this MAC algorithm.", macAlgorithmPropName, ma);
662                            }
663                            macAlgorithm = ma;
664                    }
665    
666                    return ma;
667            }
668            private String macAlgorithm = null;
669    
670    
671            byte[] generateKey(int keySize)
672            {
673                    byte[] result = new byte[(keySize + 7) / 8];
674                    secureRandom.nextBytes(result);
675                    return result;
676            }
677    
678            byte[] generateKey()
679            {
680                    return generateKey(getKeySize());
681    //              return new SecretKeySpec(
682    //                              generateKey(getKeySize()),
683    //                              getBaseAlgorithm(getEncryptionAlgorithm())
684    //              );
685            }
686    
687            private File keyStoreFile;
688    
689            /**
690             * <p>
691             * Create a new instance of <code>KeyStore</code>.
692             * </p>
693             * <p>
694             * If the file specified by <code>keyStoreFile</code> exists, it is read into memory. If it does not exist,
695             * an empty <code>KeyStore</code> is created and written to this file.
696             * </p>
697             *
698             * @param keyStoreFile the file to be read (if existing) or created. Note that temporary files (and later maybe backup files, too)
699             * are created in the same directory (i.e. in {@link File#getParentFile() keyStoreFile.getParentFile()}).
700             * @throws IOException if reading from or writing to the local file-system failed.
701             */
702            public KeyStore(File keyStoreFile) throws IOException
703            {
704                    if (keyStoreFile == null)
705                            throw new IllegalArgumentException("keyStoreFile == null");
706    
707                    this.keyStoreFile = keyStoreFile;
708    
709                    if (!keyStoreFile.getParentFile().isDirectory())
710                            throw new FileNotFoundException("Path does not exist or is not a directory: " + keyStoreFile.getParentFile().getAbsolutePath());
711    
712                    // In case the old file was already deleted, but the new not yet renamed, we check, if a new file
713                    // exists and the old file is missing - in this case, we load the new file.
714                    File newKeyStoreFile = getNewKeyStoreFile();
715                    if (!keyStoreFile.exists() && newKeyStoreFile.exists())
716                            keyStoreFile = newKeyStoreFile;
717    
718                    FileInputStream in = keyStoreFile.length() == 0 ? null : new FileInputStream(keyStoreFile);
719                    if (in != null) {
720                            try {
721                                    keyStoreData.readFromStream(in);
722                            } finally {
723                                    in.close();
724                            }
725                    }
726                    else
727                            storeToFile(); // create the file (empty) already now, if it does not exist.
728    
729                    expireCacheEntryTimer.schedule(expireCacheEntryTimerTask, 60000, 60000); // TODO make this configurable
730            }
731    
732            File getNewKeyStoreFile()
733            {
734                    return new File(keyStoreFile.getParentFile(), keyStoreFile.getName() + ".new");
735            }
736    
737            /**
738             * Determine if this <code>KeyStore</code> is completely empty. As soon as the first user has been
739             * created, this method will return <code>false</code>.
740             *
741             * @return <code>true</code> if this <code>KeyStore</code> contains neither any user nor any key, i.e. is totally empty;
742             * <code>false</code> otherwise.
743             */
744            public synchronized boolean isEmpty()
745            {
746                    return keyStoreData.user2keyMap.isEmpty();
747            }
748    
749            synchronized long nextKeyID(String authUserName, char[] authPassword) throws AuthenticationException
750            {
751                    LongProperty property = getProperty(authUserName, authPassword, LongProperty.class, KEY_STORE_PROPERTY_NAME_NEXT_KEY_ID);
752                    if (property.getValue() == null)
753                            property.setValue(1L);
754    
755                    long result = property.getValue();
756                    property.setValue(result + 1);
757                    _setProperty(authUserName, authPassword, property);
758                    return result;
759            }
760    
761            private Map<String, CachedMasterKey> cache_userName2cachedMasterKey = new HashMap<String, CachedMasterKey>();
762    
763            public synchronized int getMasterKeySize(String authUserName, char[] authPassword)
764            throws AuthenticationException
765            {
766                    MasterKey masterKey = getMasterKey(authUserName, authPassword);
767                    return masterKey.getEncoded().length * 8;
768            }
769    
770            /**
771             * Authenticate and get the master-key. If there is a cache-entry existing, directly return this
772             * (after comparing the password); otherwise decrypt the master-key using the given password.
773             *
774             * @param authUserName the user from whose slot to take and decrypt the master-key.
775             * @param authPassword the password with which to try to decrypt the master-key.
776             * @return the decrypted, plain master-key.
777             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
778             * is not correct for the given <code>authUserName</code>.
779             */
780            synchronized MasterKey getMasterKey(String authUserName, char[] authPassword)
781            throws AuthenticationException
782            {
783    //              logger.trace("getMasterKey: authUserName={} authPassword={}", authUserName, new String(authPassword));
784    
785                    CachedMasterKey cachedMasterKey = cache_userName2cachedMasterKey.get(authUserName);
786                    MasterKey result = cachedMasterKey == null ? null : cachedMasterKey.getMasterKey();
787                    if (result != null && Arrays.equals(authPassword, cachedMasterKey.getPassword())) {
788                            cachedMasterKey.updateLastUse();
789                            return result;
790                    }
791                    result = null;
792    
793                    EncryptedMasterKey encryptedKey = keyStoreData.user2keyMap.get(authUserName);
794                    if (encryptedKey == null)
795                            logger.warn("getMasterKey: Unknown userName: {}", authUserName); // NOT throw exception here to not disclose the true reason of the AuthenticationException - see below
796                    else {
797                            PlaintextDataAndMAC plaintextDataAndMAC;
798                            try {
799                                    Cipher cipher = getCipherForUserPassword(
800                                                    authPassword,
801                                                    encryptedKey.getPasswordBasedKeySize(),
802                                                    encryptedKey.getPasswordBasedIterationCount(),
803                                                    encryptedKey.getPasswordBasedKeyGeneratorAlgorithm(),
804                                                    encryptedKey.getSalt(),
805                                                    encryptedKey.getEncryptionIV(), encryptedKey.getEncryptionAlgorithm(),
806                                                    CipherOperationMode.DECRYPT
807                                    );
808                                    byte[] decrypted = cipher.doFinal(encryptedKey.getEncryptedData());
809    
810                                    plaintextDataAndMAC = new PlaintextDataAndMAC(decrypted, encryptedKey);
811                            } catch (CryptoException x) {
812                                    logger.warn("getMasterKey: Caught CryptoException indicating a wrong password for user \"{}\"!", authUserName);
813                                    plaintextDataAndMAC = null;
814                            } catch (GeneralSecurityException x) {
815                                    throw new RuntimeException(x);
816                            }
817    
818                            try {
819                                    if (plaintextDataAndMAC != null && plaintextDataAndMAC.verifyMAC())
820                                            result = new MasterKey(plaintextDataAndMAC.getData());
821                                    else
822                                            logger.warn("getMasterKey: Wrong password for user \"{}\"! MAC verification failed.", authUserName);
823                            } catch (GeneralSecurityException x) {
824                                    throw new RuntimeException(x);
825                            }
826                    }
827    
828                    // We check only once at the end of this method if we could successfully authenticate and otherwise
829                    // throw a AuthenticationException. If we threw the AuthenticationException at different locations (even with the same
830                    // message), and attacker might know from the stack trace (=> line number) whether the user-name
831                    // or the password was wrong. This information will be logged, but not disclosed in the exception.
832                    // Marco :-)
833                    if (result == null)
834                            throw new AuthenticationException("Unknown user \"" + authUserName + "\" or wrong password!");
835    
836                    cache_userName2cachedMasterKey.put(authUserName, new CachedMasterKey(authUserName, authPassword, result));
837                    return result;
838            }
839    
840            private Cipher getCipherForUserPassword(
841                            char[] password,
842                            int passwordBasedKeySize, int passwordBasedIterationCount, String passwordBasedKeyGeneratorAlgorithm,
843                            byte[] salt, byte[] iv, String algorithm, CipherOperationMode opmode) throws GeneralSecurityException
844            {
845                    if (iv == null) {
846                            if (CipherOperationMode.ENCRYPT != opmode)
847                                    throw new IllegalArgumentException("iv must not be null when decrypting!");
848                    }
849                    else {
850                            if (CipherOperationMode.ENCRYPT == opmode)
851                                    throw new IllegalArgumentException("iv must be null when encrypting!");
852                    }
853    
854                    if (algorithm == null) {
855                            if (CipherOperationMode.ENCRYPT != opmode)
856                                    throw new IllegalArgumentException("algorithm must not be null when decrypting!");
857    
858                            algorithm = getEncryptionAlgorithm();
859                    }
860    
861                    SecretKeyFactory factory = SecretKeyFactory.getInstance(passwordBasedKeyGeneratorAlgorithm);
862    
863                    KeySpec spec = new PBEKeySpec(password, salt, passwordBasedIterationCount, passwordBasedKeySize);
864                    SecretKey secretKey = factory.generateSecret(spec);
865    
866                    Cipher cipher = CryptoRegistry.sharedInstance().createCipher(algorithm);
867    
868                    if (iv == null) {
869                            iv = new byte[cipher.getIVSize()];
870                            secureRandom.nextBytes(iv);
871                    }
872    
873                    cipher.init(opmode, new ParametersWithIV(new KeyParameter(secretKey.getEncoded()), iv));
874    
875                    return cipher;
876            }
877    
878    //      private String getBaseAlgorithm(String algorithm)
879    //      {
880    //              int slashIdx = algorithm.indexOf('/');
881    //              if (slashIdx < 0)
882    //                      return algorithm;
883    //
884    //              return algorithm.substring(0, slashIdx);
885    //      }
886    
887            private Cipher getCipherForMasterKey(MasterKey masterKey, byte[] iv, String algorithm, CipherOperationMode opmode) throws GeneralSecurityException
888            {
889                    if (iv == null) {
890                            if (CipherOperationMode.ENCRYPT != opmode)
891                                    throw new IllegalArgumentException("iv must not be null when decrypting!");
892                    }
893                    else {
894                            if (CipherOperationMode.ENCRYPT == opmode)
895                                    throw new IllegalArgumentException("iv must be null when encrypting!");
896                    }
897    
898                    if (algorithm == null) {
899                            if (CipherOperationMode.ENCRYPT != opmode)
900                                    throw new IllegalArgumentException("algorithm must not be null when decrypting!");
901    
902                            algorithm = getEncryptionAlgorithm();
903                    }
904    
905                    Cipher cipher = CryptoRegistry.sharedInstance().createCipher(algorithm);
906    
907                    if (iv == null) {
908                            iv = new byte[cipher.getIVSize()];
909                            secureRandom.nextBytes(iv);
910                    }
911                    cipher.init(opmode, new ParametersWithIV(new KeyParameter(masterKey.getEncoded()), iv));
912                    return cipher;
913            }
914    
915            /**
916             * <p>
917             * Generate a new key and store it to the file.
918             * </p>
919             * <p>
920             * The new key will be generated with the size specified by the
921             * system property {@value #SYSTEM_PROPERTY_KEY_SIZE} and encrypted with the
922             * master-key and the encryption-algorithm specified by the
923             * system property {@value #SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM}.
924             * </p>
925             *
926             * @param authUserName the authenticated user authorizing this action.
927             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
928             * @return the newly created key.
929             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
930             * is not correct for the given <code>authUserName</code>.
931             * @throws IOException if writing to the local file-system failed.
932             */
933            public synchronized GeneratedKey generateKey(String authUserName, char[] authPassword)
934            throws AuthenticationException, IOException
935            {
936                    long keyID = nextKeyID(authUserName, authPassword);
937                    byte[] key = generateKey();
938                    GeneratedKey generatedKey = new GeneratedKey(keyID, key);
939                    _setKey(authUserName, authPassword, keyID, key);
940                    storeToFile();
941                    return generatedKey;
942            }
943    
944            /**
945             * <p>
946             * Generate <code>qty</code> new keys and store them to the file.
947             * </p>
948             * <p>
949             * This method behaves like {@link #generateKey(String, char[])} but is much
950             * faster when multiple keys have to be generated (bulk operation).
951             * </p>
952             *
953             * @param authUserName the authenticated user authorizing this action.
954             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
955             * @param qty the number of keys to be generated. If 0, the method will do nothing and return
956             * an empty list, if &lt; 0, an {@link IllegalArgumentException} will be thrown.
957             * @return a list of generated keys; never <code>null</code>.
958             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
959             * is not correct for the given <code>authUserName</code>.
960             * @throws IOException if writing to the local file-system failed.
961             */
962            public synchronized List<GeneratedKey> generateKeys(String authUserName, char[] authPassword, int qty)
963            throws AuthenticationException, IOException
964            {
965                    if (qty < 0)
966                            throw new IllegalArgumentException("qty < 0");
967    
968                    List<GeneratedKey> result = new ArrayList<GeneratedKey>(qty);
969                    for (int i = 0; i < qty; ++i) {
970                            long keyID = nextKeyID(authUserName, authPassword);
971                            byte[] key = generateKey();
972                            GeneratedKey generatedKey = new GeneratedKey(keyID, key);
973                            _setKey(authUserName, authPassword, keyID, key);
974                            result.add(generatedKey);
975                    }
976                    storeToFile();
977                    return result;
978            }
979    
980            /**
981             * <p>
982             * Create a new user.
983             * </p>
984             * <p>
985             * Before the <code>KeyStore</code> can be used (i.e. before most methods work), this method has to be called
986             * to create the first user. When the first user is created, the internal master-key is generated, which will
987             * then not be changed anymore (double-check that the {@link #SYSTEM_PROPERTY_KEY_SIZE key-size} is set correctly at
988             * this time).
989             * </p>
990             *
991             * @param authUserName the authenticated user authorizing this action. If the very first user is created, this value
992             * is ignored and can be <code>null</code>.
993             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. If the very first user is created, this value
994             * is ignored and can be <code>null</code>.
995             * @param userName the name of the user to be created.
996             * @param password the password of the new user.
997             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
998             * is not correct for the given <code>authUserName</code>.
999             * @throws UserAlreadyExistsException if a user with the name specified by <code>userName</code> already exists.
1000             * @throws IOException if writing to the local file-system failed.
1001             */
1002            public synchronized void createUser(String authUserName, char[] authPassword, String userName, char[] password)
1003            throws AuthenticationException, UserAlreadyExistsException, IOException
1004            {
1005                    if (userName == null)
1006                            throw new IllegalArgumentException("userName must not be null!");
1007    
1008                    if (password == null)
1009                            throw new IllegalArgumentException("password must not be null!");
1010    
1011                    MasterKey masterKey;
1012    
1013                    if (isEmpty()) {
1014                            byte[] key = generateKey();
1015                            masterKey = new MasterKey(key);
1016                            // Unfortunately, we cannot clear the sensitive data from the key instance, because
1017                            // there is no nice way to do this (we could only do very ugly reflection-based stuff).
1018                            // But fortunately, this happens only the very first time a new, empty KeyStore is created.
1019                            // With an existing KeyStore we won't come here and our MasterKey can [and will] be cleared.
1020                            // Marco :-)
1021                            logger.info("createUser: Created master-key with a size of {} bits. This key will not be modified for this key-store anymore.", key.length * 8);
1022                    }
1023                    else
1024                            masterKey = getMasterKey(authUserName, authPassword);
1025    
1026                    if (keyStoreData.user2keyMap.containsKey(userName))
1027                            throw new UserAlreadyExistsException("User '" + userName + "' already exists!");
1028    
1029                    setUser(masterKey, userName, password);
1030            }
1031    
1032            synchronized void setUser(MasterKey masterKey, String userName, char[] password)
1033            throws IOException
1034            {
1035                    byte[] plainMasterKeyData = masterKey.getEncoded();
1036    
1037                    byte[] salt = new byte[8]; // Are 8 bytes salt salty (i.e. secure) enough?
1038                    secureRandom.nextBytes(salt);
1039                    try {
1040                            int passwordBasedKeySize = getKeySize();
1041                            int passwordBasedIterationCount = 1024; // TODO make configurable!
1042                            String passwordBasedKeyGeneratorAlgorithm = "PBKDF2WithHmacSHA1"; // TODO make configurable
1043    
1044                            Cipher cipher = getCipherForUserPassword(
1045                                            password,
1046                                            passwordBasedKeySize,
1047                                            passwordBasedIterationCount,
1048                                            passwordBasedKeyGeneratorAlgorithm,
1049                                            salt, null, null, CipherOperationMode.ENCRYPT
1050                            );
1051    
1052                            PlaintextDataAndMAC plaintextDataAndMAC = new PlaintextDataAndMAC(plainMasterKeyData, getMACAlgorithm());
1053                            byte[] encrypted = cipher.doFinal(plaintextDataAndMAC.toByteArray());
1054    
1055                            byte[] iv = ((ParametersWithIV)cipher.getParameters()).getIV();
1056    
1057                            EncryptedMasterKey encryptedKey = new EncryptedMasterKey(
1058                                            userName,
1059                                            passwordBasedKeySize,
1060                                            passwordBasedIterationCount,
1061                                            keyStoreData.stringConstant(passwordBasedKeyGeneratorAlgorithm),
1062                                            salt,
1063                                            keyStoreData.stringConstant(cipher.getTransformation()),
1064                                            iv,
1065                                            keyStoreData.stringConstant(plaintextDataAndMAC.getMACAlgorithm()),
1066                                            (short)plaintextDataAndMAC.getMACKey().length,
1067                                            (short)plaintextDataAndMAC.getMACIV().length,
1068                                            (short)plaintextDataAndMAC.getMAC().length,
1069                                            encrypted
1070                            );
1071                            keyStoreData.user2keyMap.put(userName, encryptedKey);
1072                            usersCache = null;
1073                    } catch (CryptoException e) {
1074                            throw new RuntimeException(e);
1075                    } catch (GeneralSecurityException e) {
1076                            throw new RuntimeException(e);
1077                    }
1078    
1079                    storeToFile();
1080            }
1081    
1082            synchronized void storeToFile() throws IOException
1083            {
1084                    File newKeyStoreFile = getNewKeyStoreFile();
1085                    boolean deleteNewKeyStoreFile = true;
1086                    try {
1087                            OutputStream out = new FileOutputStream(newKeyStoreFile);
1088                            try {
1089                                    keyStoreData.writeToStream(out);
1090                            } finally {
1091                                    out.close();
1092                            }
1093    
1094                            deleteNewKeyStoreFile = false;
1095                            keyStoreFile.delete();
1096                            newKeyStoreFile.renameTo(keyStoreFile);
1097                    } finally {
1098                            if (deleteNewKeyStoreFile) {
1099                                    try {
1100                                            newKeyStoreFile.delete();
1101                                    } catch (Exception x) {
1102                                            logger.warn("Deleting the newKeyStoreFile failed!", x);
1103                                    }
1104                            }
1105                    }
1106            }
1107    
1108            /**
1109             * <p>
1110             * Get all users who can authenticate at this <code>KeyStore</code>.
1111             * </p>
1112             *
1113             * @param authUserName the authenticated user authorizing this action.
1114             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
1115             * @return a read-only {@link Set} of all user-names known to this <code>KeyStore</code>. This
1116             * <code>Set</code> is an unmodifiable copy of the internally used data and therefore is both thread-safe
1117             * and iteration-safe (i.e. it can be iterated while simultaneously users are {@link #deleteUser(String, char[], String) deleted}).
1118             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1119             * is not correct for the given <code>authUserName</code>.
1120             */
1121            public synchronized SortedSet<String> getUsers(String authUserName, char[] authPassword)
1122            throws AuthenticationException
1123            {
1124                    // The following getMasterKey(...) is no real protection, because the information returned by this method
1125                    // is currently not protected, but this way, we already have the right arguments to later encrypt this
1126                    // information, too - if we ever want to.
1127                    // Marco :-)
1128                    getMasterKey(authUserName, authPassword);
1129    
1130                    SortedSet<String> users = usersCache;
1131                    if (users == null) {
1132                            users = Collections.unmodifiableSortedSet(new TreeSet<String>(keyStoreData.user2keyMap.keySet()));
1133                            usersCache = users;
1134                    }
1135    
1136                    return users;
1137            }
1138    
1139            private SortedSet<String> usersCache = null;
1140    
1141            /**
1142             * <p>
1143             * Delete the user specified by <code>userName</code>.
1144             * </p>
1145             * <p>
1146             * Deleting the authenticated user himself (i.e. <code>authUserName == userName</code>) is possible,
1147             * as long as it is not the last user.
1148             * </p>
1149             *
1150             * @param authUserName the name of the principal, i.e. the user authorizing this operation.
1151             * @param authPassword the password of the principal.
1152             * @param userName the name of the user to be deleted.
1153             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1154             * is not correct for the given <code>authUserName</code>.
1155             * @throws UserNotFoundException if there is no user with the name specified by <code>userName</code>.
1156             * @throws CannotDeleteLastUserException if the last user would be deleted by this method invocation (thus rendering
1157             * the <code>KeyStore</code> unusable and unrecoverable - i.e. totally lost).
1158             * @throws IOException if writing to the local file-system failed.
1159             */
1160            public synchronized void deleteUser(String authUserName, char[] authPassword, String userName)
1161            throws AuthenticationException, UserNotFoundException, CannotDeleteLastUserException, IOException
1162            {
1163                    // The following getMasterKey(...) is no real protection, because a user can be deleted without
1164                    // authenticating on the file-base (as this doesn't require to decrypt data, currently), but
1165                    // this way, we already have the right arguments here and might later encrypt the required infos.
1166                    // Marco :-)
1167                    getMasterKey(authUserName, authPassword);
1168    
1169                    EncryptedMasterKey encryptedKey = keyStoreData.user2keyMap.get(userName);
1170                    if (encryptedKey == null)
1171                            throw new UserNotFoundException("The user \"" + userName + "\" does not exist!");
1172    
1173                    if (keyStoreData.user2keyMap.size() == 1)
1174                            throw new CannotDeleteLastUserException("You cannot delete the last user and \"" + userName + "\" is the last user!");
1175    
1176                    clearCache(userName);
1177                    keyStoreData.user2keyMap.remove(userName);
1178                    usersCache = null;
1179    
1180                    storeToFile();
1181            }
1182    
1183            /**
1184             * <p>
1185             * Change a user's password.
1186             * </p>
1187             * <p>
1188             * The user identified by <code>userName</code> will have the new password specified by
1189             * <code>newPassword</code> immediately after this method. Authenticating this user with
1190             * his old password will fail afterwards.
1191             * </p>
1192             *
1193             * @param authUserName the authenticated user authorizing this action.
1194             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
1195             * @param userName the user whose password is to be changed. This can be the same as <code>authUserName</code>.
1196             * @param newPassword the new password.
1197             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1198             * is not correct for the given <code>authUserName</code>.
1199             * @throws UserNotFoundException if there is no user with the name specified by <code>userName</code>.
1200             * @throws IOException if writing to the local file-system failed.
1201             */
1202            public synchronized void changeUserPassword(String authUserName, char[] authPassword, String userName, char[] newPassword)
1203            throws AuthenticationException, UserNotFoundException, IOException
1204            {
1205                    MasterKey masterKey = getMasterKey(authUserName, authPassword);
1206    
1207                    if (!keyStoreData.user2keyMap.containsKey(userName))
1208                            throw new UserNotFoundException("User '" + userName + "' does not exist!");
1209    
1210                    setUser(masterKey, userName, newPassword);
1211            }
1212    
1213            /**
1214             * Get the key identified by the given <code>keyID</code>.
1215             *
1216             * @param authUserName the authenticated user authorizing this action.
1217             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
1218             * @param keyID the identifier of the key to get.
1219             * @return the key associated with the given identifier; never <code>null</code> (if there is no key for the given <code>keyID</code>,
1220             * a {@link KeyNotFoundException} is thrown).
1221             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1222             * is not correct for the given <code>authUserName</code>.
1223             * @throws KeyNotFoundException if the specified <code>keyID</code> does not reference any existing key. Note, that the
1224             * authentication process occurs before any lookup and therefore a {@link KeyNotFoundException} indicates a correct authentication
1225             * (otherwise the {@link AuthenticationException} would have been thrown before).
1226             */
1227            public synchronized byte[] getKey(String authUserName, char[] authPassword, long keyID)
1228            throws AuthenticationException, KeyNotFoundException
1229            {
1230                    MasterKey masterKey = getMasterKey(authUserName, authPassword);
1231                    EncryptedKey encryptedKey = keyStoreData.keyID2keyMap.get(keyID);
1232                    if (encryptedKey == null)
1233                            throw new KeyNotFoundException("There is no key with keyID=" + keyID + "!");
1234    
1235                    try {
1236                            Cipher cipher = getCipherForMasterKey(
1237                                            masterKey,
1238                                            encryptedKey.getEncryptionIV(),
1239                                            encryptedKey.getEncryptionAlgorithm(),
1240                                            CipherOperationMode.DECRYPT
1241                            );
1242                            byte[] decrypted = cipher.doFinal(encryptedKey.getEncryptedData());
1243    
1244                            PlaintextDataAndMAC plaintextDataAndMAC = new PlaintextDataAndMAC(decrypted, encryptedKey);
1245                            if (!plaintextDataAndMAC.verifyMAC())
1246                                    throw new IllegalStateException("MAC mismatch!!! This means, the decryption key was wrong!");
1247    
1248                            return plaintextDataAndMAC.getData();
1249                    } catch (CryptoException e) {
1250                            throw new RuntimeException(e);
1251                    } catch (GeneralSecurityException e) {
1252                            throw new RuntimeException(e);
1253                    }
1254            }
1255    
1256            public synchronized SortedSet<Long> getKeyIDs(String authUserName, char[] authPassword)
1257            throws AuthenticationException
1258            {
1259                    getMasterKey(authUserName, authPassword);
1260                    SortedSet<Long> result = new TreeSet<Long>(keyStoreData.keyID2keyMap.keySet());
1261                    return result;
1262            }
1263    
1264            private void _setKey(String authUserName, char[] authPassword, long keyID, byte[] key)
1265            throws AuthenticationException
1266            {
1267                    MasterKey masterKey = getMasterKey(authUserName, authPassword);
1268    
1269                    try {
1270                            PlaintextDataAndMAC plaintextDataAndMAC = new PlaintextDataAndMAC(key, getMACAlgorithm());
1271    
1272                            Cipher cipher = getCipherForMasterKey(masterKey, null, null, CipherOperationMode.ENCRYPT);
1273                            byte[] iv = ((ParametersWithIV)cipher.getParameters()).getIV();
1274                            byte[] encrypted = cipher.doFinal(plaintextDataAndMAC.toByteArray());
1275    
1276                            EncryptedKey encryptedKey = new EncryptedKey(
1277                                            keyID,
1278                                            keyStoreData.stringConstant(cipher.getTransformation()),
1279                                            iv,
1280                                            plaintextDataAndMAC.getMACAlgorithm(),
1281                                            (short)plaintextDataAndMAC.getMACKey().length,
1282                                            (short)plaintextDataAndMAC.getMACIV().length,
1283                                            (short)plaintextDataAndMAC.getMAC().length,
1284                                            encrypted
1285                            );
1286                            keyStoreData.keyID2keyMap.put(keyID, encryptedKey);
1287                    } catch (CryptoException e) {
1288                            throw new RuntimeException(e);
1289                    } catch (GeneralSecurityException e) {
1290                            throw new RuntimeException(e);
1291                    }
1292            }
1293    
1294            /**
1295             * <p>
1296             * Get a named property.
1297             * </p>
1298             * <p>
1299             * The <code>KeyStore</code> supports managing arbitrary properties in the form of
1300             * name-value-pairs. The names are plain-text, but the values are encrypted.
1301             * A property-value can be of any type for which a subclass of
1302             * {@link org.cumulus4j.keystore.prop.Property} exists.
1303             * </p>
1304             * <p>
1305             * This method will always return an instance of the given <code>propertyType</code>, no matter,
1306             * if the property exists in this <code>KeyStore</code> or not. If the property does not exist,
1307             * its {@link Property#getValue() value} will be <code>null</code>.
1308             * </p>
1309             * <p>
1310             * <b>Important:</b> Never directly instantiate a {@link Property}-subclass. Always use this method
1311             * as a factory for property instances.
1312             * </p>
1313             *
1314             * @param <P> the type of the property.
1315             * @param authUserName the authenticated user authorizing this action.
1316             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
1317             * @param propertyType the type of the property; must not be <code>null</code>. If the property does not yet exist,
1318             * every type can be specified. If the property already exists, this type must match the type of the property.
1319             * If they do not match, an {@link IllegalArgumentException} is thrown.
1320             * @param name the unique name of the property; must not be <code>null</code>.
1321             * @return the property; never <code>null</code>. If the property does not yet exist, a new, empty property is returned.
1322             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1323             * is not correct for the given <code>authUserName</code>.
1324             * @see #setProperty(String, char[], Property)
1325             * @see #removeProperty(String, char[], String)
1326             */
1327            public synchronized <P extends Property<?>> P getProperty(String authUserName, char[] authPassword, Class<P> propertyType, String name)
1328            throws AuthenticationException
1329            {
1330                    MasterKey masterKey = getMasterKey(authUserName, authPassword);
1331    
1332                    if (name == null)
1333                            throw new IllegalArgumentException("name == null");
1334    
1335                    EncryptedProperty encryptedProperty = keyStoreData.name2propertyMap.get(name);
1336    
1337                    P result;
1338                    try {
1339                            result = propertyType.newInstance();
1340                    } catch (InstantiationException e) {
1341                            throw new RuntimeException(e);
1342                    } catch (IllegalAccessException e) {
1343                            throw new RuntimeException(e);
1344                    }
1345                    result.setName(name);
1346                    result.setXxx(propertyXxx);
1347    
1348                    if (encryptedProperty != null) {
1349                            if (!propertyType.equals(encryptedProperty.getType()))
1350                                    throw new IllegalArgumentException("propertyType != encryptedProperty.type :: " + propertyType.getClass().getName() + " != " + encryptedProperty.getType().getName());
1351    
1352                            try {
1353                                    Cipher cipher = getCipherForMasterKey(
1354                                                    masterKey,
1355                                                    encryptedProperty.getEncryptionIV(),
1356                                                    encryptedProperty.getEncryptionAlgorithm(),
1357                                                    CipherOperationMode.DECRYPT
1358                                    );
1359                                    byte[] decrypted = cipher.doFinal(encryptedProperty.getEncryptedData());
1360    
1361                                    PlaintextDataAndMAC plaintextDataAndMAC = new PlaintextDataAndMAC(decrypted, encryptedProperty);
1362                                    if (!plaintextDataAndMAC.verifyMAC())
1363                                            throw new IllegalStateException("MAC mismatch!!! This means, the decryption key was wrong!");
1364    
1365                                    result.setValueEncoded(plaintextDataAndMAC.getData());
1366                            } catch (CryptoException e) {
1367                                    throw new RuntimeException(e);
1368                            } catch (GeneralSecurityException e) {
1369                                    throw new RuntimeException(e);
1370                            }
1371                    }
1372    
1373                    return result;
1374            }
1375    
1376            public synchronized SortedSet<Property<?>> getProperties(String authUserName, char[] authPassword)
1377            throws AuthenticationException
1378            {
1379                    SortedSet<Property<?>> result = new TreeSet<Property<?>>();
1380                    for (Map.Entry<String, EncryptedProperty> me : keyStoreData.name2propertyMap.entrySet()) {
1381                            Property<?> property = getProperty(authUserName, authPassword, me.getValue().getType(), me.getKey());
1382                            result.add(property);
1383                    }
1384                    return result;
1385            }
1386    
1387            /**
1388             * <p>
1389             * Remove a property.
1390             * </p>
1391             * <p>
1392             * If the property with the given name does not exist, this method won't do anything.
1393             * </p>
1394             *
1395             * @param authUserName the authenticated user authorizing this action.
1396             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
1397             * @param name the unique name of the property; must not be <code>null</code>.
1398             * @return whether the property was removed, i.e. whether this <code>KeyStore</code> was changed by the operation.
1399             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1400             * is not correct for the given <code>authUserName</code>.
1401             * @throws IOException if writing to the local file-system failed.
1402             * @see #getProperty(String, char[], Class, String)
1403             * @see #setProperty(String, char[], Property)
1404             */
1405            public synchronized boolean removeProperty(String authUserName, char[] authPassword, String name)
1406            throws AuthenticationException, IOException
1407            {
1408                    boolean removed = _removeProperty(authUserName, authPassword, name);
1409    
1410                    if (removed)
1411                            storeToFile();
1412    
1413                    return removed;
1414            }
1415    
1416            boolean _removeProperty(String authUserName, char[] authPassword, String name)
1417            throws AuthenticationException
1418            {
1419                    getMasterKey(authUserName, authPassword);
1420                    return keyStoreData.name2propertyMap.remove(name) != null;
1421            }
1422    
1423            private UUID propertyXxx = UUID.randomUUID();
1424    
1425            /**
1426             * <p>
1427             * Set a property.
1428             * </p>
1429             * <p>
1430             * If the property's {@link Property#getValue() value} is <code>null</code>, the property is
1431             * {@link #removeProperty(String, char[], String) removed} instead.
1432             * </p>
1433             * <p>
1434             * If a property with the same {@link Property#getName() name} already exists, it is overwritten.
1435             * </p>
1436             * <p>
1437             * The property's value is encrypted with the internal master-key. The property's name is stored
1438             * in plain (unencrypted) form.
1439             * </p>
1440             *
1441             * @param authUserName the authenticated user authorizing this action.
1442             * @param authPassword the password for authenticating the user specified by <code>authUserName</code>.
1443             * @param property the property to set. Do not instantiate any property directly!
1444             * Use {@link #getProperty(String, char[], Class, String)} instead!
1445             * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code>
1446             * is not correct for the given <code>authUserName</code>.
1447             * @throws IOException if writing to the local file-system failed.
1448             * @see #getProperty(String, char[], Class, String)
1449             * @see #removeProperty(String, char[], String)
1450             */
1451            public synchronized void setProperty(String authUserName, char[] authPassword, Property<?> property)
1452            throws AuthenticationException, IOException
1453            {
1454                    _setProperty(authUserName, authPassword, property);
1455                    storeToFile();
1456            }
1457    
1458            private void _setProperty(String authUserName, char[] authPassword, Property<?> property)
1459            throws AuthenticationException
1460            {
1461                    MasterKey masterKey = getMasterKey(authUserName, authPassword);
1462    
1463                    if (property == null)
1464                            throw new IllegalArgumentException("property == null");
1465    
1466                    if (!propertyXxx.equals(property.getXxx()))
1467                            throw new IllegalArgumentException("property was not created by this KeyStore! You should use 'getProperty(...)' instead of 'new SomeProperty(...)'!!! And you should never store properties unencrypted somewhere outside!");
1468    
1469                    if (property.getName() == null)
1470                            throw new IllegalArgumentException("property.name == null");
1471    
1472                    keyStoreData.stringConstant(property.getClass().getName());
1473    
1474                    byte[] plainValueEncoded = property.getValueEncoded();
1475                    if (plainValueEncoded == null) {
1476                            _removeProperty(authUserName, authPassword, property.getName());
1477                    }
1478                    else {
1479                            try {
1480                                    PlaintextDataAndMAC plaintextDataAndMAC = new PlaintextDataAndMAC(plainValueEncoded, getMACAlgorithm());
1481    
1482                                    Cipher cipher = getCipherForMasterKey(masterKey, null, null, CipherOperationMode.ENCRYPT);
1483                                    byte[] encrypted = cipher.doFinal(plaintextDataAndMAC.toByteArray());
1484                                    byte[] iv = ((ParametersWithIV)cipher.getParameters()).getIV();
1485    
1486                                    @SuppressWarnings("unchecked")
1487                                    Class<? extends Property<?>> propertyType = (Class<? extends Property<?>>) property.getClass();
1488                                    EncryptedProperty encryptedProperty = new EncryptedProperty(
1489                                                    property.getName(), propertyType,
1490                                                    keyStoreData.stringConstant(cipher.getTransformation()),
1491                                                    iv,
1492                                                    plaintextDataAndMAC.getMACAlgorithm(),
1493                                                    (short)plaintextDataAndMAC.getMACKey().length,
1494                                                    (short)plaintextDataAndMAC.getMACIV().length,
1495                                                    (short)plaintextDataAndMAC.getMAC().length,
1496                                                    encrypted
1497                                    );
1498                                    keyStoreData.name2propertyMap.put(encryptedProperty.getName(), encryptedProperty);
1499                            } catch (CryptoException e) {
1500                                    throw new RuntimeException(e);
1501                            } catch (GeneralSecurityException e) {
1502                                    throw new RuntimeException(e);
1503                            }
1504                    }
1505            }
1506    
1507            /**
1508             * <p>
1509             * Clear all cached data for the specified user name.
1510             * </p>
1511             * <p>
1512             * Every time, a user
1513             * calls a method requiring <code>authUserName</code> and <code>authPassword</code>,
1514             * either an authentication process happens, or a previously cached authentication
1515             * result (i.e. a decrypted master-key) is used. In order to speed things up, authentication results are cached for a
1516             * limited time. After this time elapses, the data is cleared by a timer. If a user wants (for security reasons)
1517             * remove the cached data from the memory earlier, he can call this method.
1518             * </p>
1519             *
1520             * @param userName the user for which to clear all the cached data. <code>null</code> to clear the complete cache for all users.
1521             */
1522            public synchronized void clearCache(String userName)
1523            {
1524                    if (userName == null) {
1525                            for(CachedMasterKey cachedMasterKey : cache_userName2cachedMasterKey.values())
1526                                    cachedMasterKey.clear();
1527    
1528                            cache_userName2cachedMasterKey.clear();
1529                    }
1530                    else {
1531                            CachedMasterKey cachedMasterKey = cache_userName2cachedMasterKey.remove(userName);
1532                            if (cachedMasterKey != null)
1533                                    cachedMasterKey.clear();
1534                    }
1535            }
1536    
1537            @Override
1538            protected void finalize() throws Throwable {
1539                    clearCache(null);
1540                    super.finalize();
1541            }
1542    }