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