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