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'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-SNAPSHOT/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 < 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 }