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.1/documentation/supported-algorithms.html">this document</a> 460 * for further information about what values are supported. 461 * </p> 462 * <p> 463 * <b>Important:</b> The default MAC algorithm is "NONE", which is a very bad choice for most encryption algorithms! 464 * Therefore, you must change the MAC algorithm via the system property {@value #SYSTEM_PROPERTY_MAC_ALGORITHM} 465 * if you change the encryption algorithm! 466 * </p> 467 */ 468 public static final String SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM = "cumulus4j.KeyStore" + ".encryptionAlgorithm"; 469 470 /** 471 * <p> 472 * System property to control the <a target="_blank" href="http://en.wikipedia.org/wiki/Message_authentication_code">MAC</a> 473 * algorithm that is used to protect the data within the key-store against manipulation. 474 * </p> 475 * <p> 476 * Whenever data is encrypted, this MAC algorithm is used to calculate a MAC over the original plain-text-data. 477 * The MAC is then stored together with the plain-text-data within the encrypted area. 478 * When data is decrypted, the MAC is calculated again over the decrypted plain-text-data and compared to the 479 * original MAC in order to make sure (1) that data was correctly decrypted [i.e. the password provided by the user 480 * is correct] and (2) that the data in the key-store was not manipulated by an attacker. 481 * </p> 482 * <p> 483 * The MAC algorithm used during encryption is stored in the encryption-record's meta-data in order 484 * to use the correct algorithm during decryption, no matter what current MAC algorithm is configured. 485 * Therefore, you can safely change this setting at any time - it will affect future encryption 486 * operations, only. 487 * </p> 488 * <p> 489 * Some block cipher modes (e.g. <a target="_blank" href="http://en.wikipedia.org/wiki/Galois/Counter_Mode">GCM</a>) already include authentication 490 * and therefore no MAC is necessary. In this case, you can specify the MAC algorithm {@value #MAC_ALGORITHM_NONE}. 491 * </p> 492 * <p> 493 * <b>Important:</b> If you specify the MAC algorithm "NONE" and use an encryption algorithm without 494 * authentication, the key store will not be able to detect a wrong password and instead return 495 * corrupt data!!! Be VERY careful with the MAC algorithm "NONE"!!! 496 * </p> 497 * <p> 498 * The default value (used when this system property is not specified) is "NONE", because the default 499 * encryption algorithm is "Twofish/GCM/NoPadding", which (due to "GCM") does not require an additional 500 * MAC. 501 * </p> 502 */ 503 public static final String SYSTEM_PROPERTY_MAC_ALGORITHM = "cumulus4j.KeyStore" + ".macAlgorithm"; 504 505 /** 506 * <p> 507 * Constant for deactivating the <a target="_blank" href="http://en.wikipedia.org/wiki/Message_authentication_code">MAC</a>. 508 * </p> 509 * <p> 510 * <b>Important: Deactivating the MAC is dangerous!</b> Choose this value only, if you are absolutely 511 * sure that your {@link #SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM encryption algorithm} already 512 * provides authentication - like <a target="_blank" href="http://en.wikipedia.org/wiki/Galois/Counter_Mode">GCM</a> 513 * does for example. 514 * </p> 515 * @see #SYSTEM_PROPERTY_MAC_ALGORITHM 516 */ 517 public static final String MAC_ALGORITHM_NONE = "NONE"; 518 519 private static final String KEY_STORE_PROPERTY_NAME_NEXT_KEY_ID = "nextKeyID"; 520 521 private SecureRandom secureRandom = new SecureRandom(); 522 523 private static Timer expireCacheEntryTimer = new Timer(true); 524 525 private TimerTask expireCacheEntryTimerTask = new ExipreCacheEntryTimerTask(this); 526 527 private KeyStoreData keyStoreData = new KeyStoreData(); 528 529 private static class ExipreCacheEntryTimerTask extends TimerTask 530 { 531 private static final Logger logger = LoggerFactory.getLogger(ExipreCacheEntryTimerTask.class); 532 533 private WeakReference<KeyStore> keyStoreRef; 534 535 public ExipreCacheEntryTimerTask(KeyStore keyStore) 536 { 537 if (keyStore == null) 538 throw new IllegalArgumentException("keyStore == null"); 539 540 this.keyStoreRef = new WeakReference<KeyStore>(keyStore); 541 } 542 543 @Override 544 public void run() 545 { 546 try { 547 KeyStore keyStore = keyStoreRef.get(); 548 if (keyStore == null) { 549 logger.info("run: KeyStore has been garbage-collected. Removing this ExipreCacheEntryTimerTask."); 550 this.cancel(); 551 return; 552 } 553 554 Date removeCachedEntriesOlderThanThisDate = new Date(System.currentTimeMillis() - 3L * 60L * 1000L); // TODO make this configurable! 555 556 LinkedList<String> userNamesToExpire = new LinkedList<String>(); 557 synchronized (keyStore) { 558 for (CachedMasterKey cmk : keyStore.cache_userName2cachedMasterKey.values()) { 559 if (cmk.getLastUse().before(removeCachedEntriesOlderThanThisDate)) 560 userNamesToExpire.add(cmk.getUserName()); 561 } 562 } 563 564 for (String userName : userNamesToExpire) { 565 logger.info("run: Expiring cache for user '{}'.", userName); 566 keyStore.clearCache(userName); 567 } 568 569 if (logger.isDebugEnabled()) { 570 synchronized (keyStore) { 571 logger.debug("run: {} users left in cache.", keyStore.cache_userName2cachedMasterKey.size()); 572 } 573 } 574 } catch (Throwable x) { 575 // The TimerThread is cancelled, if a task throws an exception. Furthermore, they are not logged at all. 576 // Since we do not want the TimerThread to die, we catch everything (Throwable - not only Exception) and log 577 // it here. IMHO there's nothing better we can do. Marco :-) 578 logger.error("run: " + x, x); 579 } 580 } 581 } 582 583 /** 584 * Gets the key-size that is currently configured. Therefore, this method checks, if the 585 * system property {@value #SYSTEM_PROPERTY_KEY_SIZE} has been specified, and if so returns its value. 586 * If not, it falls back to 256. 587 * 588 * @return the current key-size. 589 */ 590 int getKeySize() 591 { 592 int ks = keySize; 593 594 if (ks == 0) { 595 String keySizePropName = SYSTEM_PROPERTY_KEY_SIZE; 596 String keySizePropValue = System.getProperty(keySizePropName); 597 if (keySizePropValue == null || keySizePropValue.trim().isEmpty()) { 598 ks = 256; // default value, if the property was not defined. 599 logger.info("getKeySize: System property '{}' is not set. Using default key size ({} bit).", keySizePropName, ks); 600 } 601 else { 602 try { 603 ks = Integer.parseInt(keySizePropValue.trim()); 604 } catch (NumberFormatException x) { 605 NumberFormatException n = new NumberFormatException("Value of system property '" + keySizePropName + "' is not a valid integer!"); 606 n.initCause(x); 607 throw n; 608 } 609 if (ks < 1) 610 throw new IllegalStateException("Value of system property '" + keySizePropName + "' is " + keySize + " but must be >= 1!!!"); 611 612 logger.info("getKeySize: System property '{}' is set to {} bit. Using this key size.", keySizePropName, ks); 613 } 614 keySize = ks; 615 } 616 617 return ks; 618 } 619 private int keySize = 0; 620 621 622 String getEncryptionAlgorithm() 623 { 624 String ea = encryptionAlgorithm; 625 626 if (ea == null) { 627 String encryptionAlgorithmPropName = SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM; 628 String encryptionAlgorithmPropValue = System.getProperty(encryptionAlgorithmPropName); 629 if (encryptionAlgorithmPropValue == null || encryptionAlgorithmPropValue.trim().isEmpty()) { 630 ea = "Twofish/GCM/NoPadding"; // default value, if the property was not defined. 631 // ea = "Twofish/CBC/PKCS5Padding"; // default value, if the property was not defined. 632 // ea = "AES/CBC/PKCS5Padding"; // default value, if the property was not defined. 633 // ea = "AES/CFB/NoPadding"; // default value, if the property was not defined. 634 logger.info("getEncryptionAlgorithm: System property '{}' is not set. Using default algorithm '{}'.", encryptionAlgorithmPropName, ea); 635 } 636 else { 637 ea = encryptionAlgorithmPropValue.trim(); 638 logger.info("getEncryptionAlgorithm: System property '{}' is set to '{}'. Using this encryption algorithm.", encryptionAlgorithmPropName, ea); 639 } 640 encryptionAlgorithm = ea; 641 } 642 643 return ea; 644 } 645 private String encryptionAlgorithm = null; 646 647 648 String getMACAlgorithm() 649 { 650 String ma = macAlgorithm; 651 652 if (ma == null) { 653 String macAlgorithmPropName = SYSTEM_PROPERTY_MAC_ALGORITHM; 654 String macAlgorithmPropValue = System.getProperty(macAlgorithmPropName); 655 if (macAlgorithmPropValue == null || macAlgorithmPropValue.trim().isEmpty()) { 656 ma = MAC_ALGORITHM_NONE; // default value, if the property was not defined. 657 logger.info("getMACAlgorithm: System property '{}' is not set. Using default MAC algorithm '{}'.", macAlgorithmPropName, ma); 658 } 659 else { 660 ma = macAlgorithmPropValue.trim(); 661 logger.info("getMACAlgorithm: System property '{}' is set to '{}'. Using this MAC algorithm.", macAlgorithmPropName, ma); 662 } 663 macAlgorithm = ma; 664 } 665 666 return ma; 667 } 668 private String macAlgorithm = null; 669 670 671 byte[] generateKey(int keySize) 672 { 673 byte[] result = new byte[(keySize + 7) / 8]; 674 secureRandom.nextBytes(result); 675 return result; 676 } 677 678 byte[] generateKey() 679 { 680 return generateKey(getKeySize()); 681 // return new SecretKeySpec( 682 // generateKey(getKeySize()), 683 // getBaseAlgorithm(getEncryptionAlgorithm()) 684 // ); 685 } 686 687 private File keyStoreFile; 688 689 /** 690 * <p> 691 * Create a new instance of <code>KeyStore</code>. 692 * </p> 693 * <p> 694 * If the file specified by <code>keyStoreFile</code> exists, it is read into memory. If it does not exist, 695 * an empty <code>KeyStore</code> is created and written to this file. 696 * </p> 697 * 698 * @param keyStoreFile the file to be read (if existing) or created. Note that temporary files (and later maybe backup files, too) 699 * are created in the same directory (i.e. in {@link File#getParentFile() keyStoreFile.getParentFile()}). 700 * @throws IOException if reading from or writing to the local file-system failed. 701 */ 702 public KeyStore(File keyStoreFile) throws IOException 703 { 704 if (keyStoreFile == null) 705 throw new IllegalArgumentException("keyStoreFile == null"); 706 707 this.keyStoreFile = keyStoreFile; 708 709 if (!keyStoreFile.getParentFile().isDirectory()) 710 throw new FileNotFoundException("Path does not exist or is not a directory: " + keyStoreFile.getParentFile().getAbsolutePath()); 711 712 // In case the old file was already deleted, but the new not yet renamed, we check, if a new file 713 // exists and the old file is missing - in this case, we load the new file. 714 File newKeyStoreFile = getNewKeyStoreFile(); 715 if (!keyStoreFile.exists() && newKeyStoreFile.exists()) 716 keyStoreFile = newKeyStoreFile; 717 718 FileInputStream in = keyStoreFile.length() == 0 ? null : new FileInputStream(keyStoreFile); 719 if (in != null) { 720 try { 721 keyStoreData.readFromStream(in); 722 } finally { 723 in.close(); 724 } 725 } 726 else 727 storeToFile(); // create the file (empty) already now, if it does not exist. 728 729 expireCacheEntryTimer.schedule(expireCacheEntryTimerTask, 60000, 60000); // TODO make this configurable 730 } 731 732 File getNewKeyStoreFile() 733 { 734 return new File(keyStoreFile.getParentFile(), keyStoreFile.getName() + ".new"); 735 } 736 737 /** 738 * Determine if this <code>KeyStore</code> is completely empty. As soon as the first user has been 739 * created, this method will return <code>false</code>. 740 * 741 * @return <code>true</code> if this <code>KeyStore</code> contains neither any user nor any key, i.e. is totally empty; 742 * <code>false</code> otherwise. 743 */ 744 public synchronized boolean isEmpty() 745 { 746 return keyStoreData.user2keyMap.isEmpty(); 747 } 748 749 synchronized long nextKeyID(String authUserName, char[] authPassword) throws AuthenticationException 750 { 751 LongProperty property = getProperty(authUserName, authPassword, LongProperty.class, KEY_STORE_PROPERTY_NAME_NEXT_KEY_ID); 752 if (property.getValue() == null) 753 property.setValue(1L); 754 755 long result = property.getValue(); 756 property.setValue(result + 1); 757 _setProperty(authUserName, authPassword, property); 758 return result; 759 } 760 761 private Map<String, CachedMasterKey> cache_userName2cachedMasterKey = new HashMap<String, CachedMasterKey>(); 762 763 public synchronized int getMasterKeySize(String authUserName, char[] authPassword) 764 throws AuthenticationException 765 { 766 MasterKey masterKey = getMasterKey(authUserName, authPassword); 767 return masterKey.getEncoded().length * 8; 768 } 769 770 /** 771 * Authenticate and get the master-key. If there is a cache-entry existing, directly return this 772 * (after comparing the password); otherwise decrypt the master-key using the given password. 773 * 774 * @param authUserName the user from whose slot to take and decrypt the master-key. 775 * @param authPassword the password with which to try to decrypt the master-key. 776 * @return the decrypted, plain master-key. 777 * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code> 778 * is not correct for the given <code>authUserName</code>. 779 */ 780 synchronized MasterKey getMasterKey(String authUserName, char[] authPassword) 781 throws AuthenticationException 782 { 783 // logger.trace("getMasterKey: authUserName={} authPassword={}", authUserName, new String(authPassword)); 784 785 CachedMasterKey cachedMasterKey = cache_userName2cachedMasterKey.get(authUserName); 786 MasterKey result = cachedMasterKey == null ? null : cachedMasterKey.getMasterKey(); 787 if (result != null && Arrays.equals(authPassword, cachedMasterKey.getPassword())) { 788 cachedMasterKey.updateLastUse(); 789 return result; 790 } 791 result = null; 792 793 EncryptedMasterKey encryptedKey = keyStoreData.user2keyMap.get(authUserName); 794 if (encryptedKey == null) 795 logger.warn("getMasterKey: Unknown userName: {}", authUserName); // NOT throw exception here to not disclose the true reason of the AuthenticationException - see below 796 else { 797 PlaintextDataAndMAC plaintextDataAndMAC; 798 try { 799 Cipher cipher = getCipherForUserPassword( 800 authPassword, 801 encryptedKey.getPasswordBasedKeySize(), 802 encryptedKey.getPasswordBasedIterationCount(), 803 encryptedKey.getPasswordBasedKeyGeneratorAlgorithm(), 804 encryptedKey.getSalt(), 805 encryptedKey.getEncryptionIV(), encryptedKey.getEncryptionAlgorithm(), 806 CipherOperationMode.DECRYPT 807 ); 808 byte[] decrypted = cipher.doFinal(encryptedKey.getEncryptedData()); 809 810 plaintextDataAndMAC = new PlaintextDataAndMAC(decrypted, encryptedKey); 811 } catch (CryptoException x) { 812 logger.warn("getMasterKey: Caught CryptoException indicating a wrong password for user \"{}\"!", authUserName); 813 plaintextDataAndMAC = null; 814 } catch (GeneralSecurityException x) { 815 throw new RuntimeException(x); 816 } 817 818 try { 819 if (plaintextDataAndMAC != null && plaintextDataAndMAC.verifyMAC()) 820 result = new MasterKey(plaintextDataAndMAC.getData()); 821 else 822 logger.warn("getMasterKey: Wrong password for user \"{}\"! MAC verification failed.", authUserName); 823 } catch (GeneralSecurityException x) { 824 throw new RuntimeException(x); 825 } 826 } 827 828 // We check only once at the end of this method if we could successfully authenticate and otherwise 829 // throw a AuthenticationException. If we threw the AuthenticationException at different locations (even with the same 830 // message), and attacker might know from the stack trace (=> line number) whether the user-name 831 // or the password was wrong. This information will be logged, but not disclosed in the exception. 832 // Marco :-) 833 if (result == null) 834 throw new AuthenticationException("Unknown user \"" + authUserName + "\" or wrong password!"); 835 836 cache_userName2cachedMasterKey.put(authUserName, new CachedMasterKey(authUserName, authPassword, result)); 837 return result; 838 } 839 840 private Cipher getCipherForUserPassword( 841 char[] password, 842 int passwordBasedKeySize, int passwordBasedIterationCount, String passwordBasedKeyGeneratorAlgorithm, 843 byte[] salt, byte[] iv, String algorithm, CipherOperationMode opmode) throws GeneralSecurityException 844 { 845 if (iv == null) { 846 if (CipherOperationMode.ENCRYPT != opmode) 847 throw new IllegalArgumentException("iv must not be null when decrypting!"); 848 } 849 else { 850 if (CipherOperationMode.ENCRYPT == opmode) 851 throw new IllegalArgumentException("iv must be null when encrypting!"); 852 } 853 854 if (algorithm == null) { 855 if (CipherOperationMode.ENCRYPT != opmode) 856 throw new IllegalArgumentException("algorithm must not be null when decrypting!"); 857 858 algorithm = getEncryptionAlgorithm(); 859 } 860 861 SecretKeyFactory factory = SecretKeyFactory.getInstance(passwordBasedKeyGeneratorAlgorithm); 862 863 KeySpec spec = new PBEKeySpec(password, salt, passwordBasedIterationCount, passwordBasedKeySize); 864 SecretKey secretKey = factory.generateSecret(spec); 865 866 Cipher cipher = CryptoRegistry.sharedInstance().createCipher(algorithm); 867 868 if (iv == null) { 869 iv = new byte[cipher.getIVSize()]; 870 secureRandom.nextBytes(iv); 871 } 872 873 cipher.init(opmode, new ParametersWithIV(new KeyParameter(secretKey.getEncoded()), iv)); 874 875 return cipher; 876 } 877 878 // private String getBaseAlgorithm(String algorithm) 879 // { 880 // int slashIdx = algorithm.indexOf('/'); 881 // if (slashIdx < 0) 882 // return algorithm; 883 // 884 // return algorithm.substring(0, slashIdx); 885 // } 886 887 private Cipher getCipherForMasterKey(MasterKey masterKey, byte[] iv, String algorithm, CipherOperationMode opmode) throws GeneralSecurityException 888 { 889 if (iv == null) { 890 if (CipherOperationMode.ENCRYPT != opmode) 891 throw new IllegalArgumentException("iv must not be null when decrypting!"); 892 } 893 else { 894 if (CipherOperationMode.ENCRYPT == opmode) 895 throw new IllegalArgumentException("iv must be null when encrypting!"); 896 } 897 898 if (algorithm == null) { 899 if (CipherOperationMode.ENCRYPT != opmode) 900 throw new IllegalArgumentException("algorithm must not be null when decrypting!"); 901 902 algorithm = getEncryptionAlgorithm(); 903 } 904 905 Cipher cipher = CryptoRegistry.sharedInstance().createCipher(algorithm); 906 907 if (iv == null) { 908 iv = new byte[cipher.getIVSize()]; 909 secureRandom.nextBytes(iv); 910 } 911 cipher.init(opmode, new ParametersWithIV(new KeyParameter(masterKey.getEncoded()), iv)); 912 return cipher; 913 } 914 915 /** 916 * <p> 917 * Generate a new key and store it to the file. 918 * </p> 919 * <p> 920 * The new key will be generated with the size specified by the 921 * system property {@value #SYSTEM_PROPERTY_KEY_SIZE} and encrypted with the 922 * master-key and the encryption-algorithm specified by the 923 * system property {@value #SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM}. 924 * </p> 925 * 926 * @param authUserName the authenticated user authorizing this action. 927 * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. 928 * @return the newly created key. 929 * @throws AuthenticationException if the specified <code>authUserName</code> does not exist or the specified <code>authPassword</code> 930 * is not correct for the given <code>authUserName</code>. 931 * @throws IOException if writing to the local file-system failed. 932 */ 933 public synchronized GeneratedKey generateKey(String authUserName, char[] authPassword) 934 throws AuthenticationException, IOException 935 { 936 long keyID = nextKeyID(authUserName, authPassword); 937 byte[] key = generateKey(); 938 GeneratedKey generatedKey = new GeneratedKey(keyID, key); 939 _setKey(authUserName, authPassword, keyID, key); 940 storeToFile(); 941 return generatedKey; 942 } 943 944 /** 945 * <p> 946 * Generate <code>qty</code> new keys and store them to the file. 947 * </p> 948 * <p> 949 * This method behaves like {@link #generateKey(String, char[])} but is much 950 * faster when multiple keys have to be generated (bulk operation). 951 * </p> 952 * 953 * @param authUserName the authenticated user authorizing this action. 954 * @param authPassword the password for authenticating the user specified by <code>authUserName</code>. 955 * @param qty the number of keys to be generated. If 0, the method will do nothing and return 956 * an empty list, if < 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 }