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