Cumulus4j API
(1.0.0)

org.cumulus4j.keystore
Class KeyStore

java.lang.Object
  extended by org.cumulus4j.keystore.KeyStore

public class KeyStore
extends Object

KeyStore is a storage facility for cryptographic keys.

An instance of KeyStore manages a file in the local file system, in which it stores the keys used by the Cumulus4j-DataNucleus-plug-in in an encrypted form. All data written to the file is encrypted, hence plain data never touches the local file system (except for swapping!).

For every read/write operation, the KeyStore requires a user to authenticate via a user-name and a password. The password is used to encrypt/decrypt an internally used master-key which is then used to encrypt/decrypt the actual keys used by the Cumulus4j-DataNucleus-plug-in. Due to this internal master key, a user can be added or deleted and a user's password can be changed without the need of decrypting and encrypting all the contents of the KeyStore.

By default, a KeyStore generates keys with a size of 256 bit. This can be controlled, however, by specifying the system property "cumulus4j.KeyStore.keySize" (e.g. passing the argument "-Dcumulus4j.KeyStore.keySize=128" to the java command line will switch to 128-bit-keys).

Important: As the master key is generated when the first user is created and is then not changed anymore, you must therefore specify the desired key-size already at the moment when you initialise the key store (i.e. create the first user). If you change the key-size later, it will affect only those keys that are created later.

Note, that the "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" does not need to be installed for very strong cryptography, because we don't use the JCE (see Cipher).

File format of the key store file (version 1)

BytesDescrition
17Header "Cumulus4jKeyStore" (ASCII encoded)
4int: File version
4int: Number of entries in 'Block A' to follow.
Block A: String constants
In order to reduce the file size (and thus increase the write speed), various strings like encryption algorithm, checksum algorithm and the like are not written again and again for every key, but instead only once here. In every key, these Strings are then referenced instead by their position-index (zero-based).
BytesDescrition
2short len: Number of bytes to follow (written by DataOutputStream.writeUTF(String)).
lenString: Constant's value (written by DataOutputStream.writeUTF(String)).
4int: Number of entries in 'Block B' to follow.
Block B: User-key-map
For every user, the master-key is stored encrypted with the user's password in this block.
BytesDescrition
2short len1: User name: Number of bytes to follow (written by DataOutputStream.writeUTF(String)).
len1String: User name (written by DataOutputStream.writeUTF(String)).
4int: Key size for the password-based key (in bits! i.e. usually 128 or 256).
4int: Iteration count for the password-based key.
4int: Reference to the name of the key-generator-algorithm for creating the password-based key (index in the list of 'Block A').
2UNSIGNED short len2: Salt: Number of bytes to follow (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
len2byte[]: Salt to be used when generating the password-based key (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
4int: Reference to the name of the encryption algorithm used to encrypt this record's data (index in the list of 'Block A').
2UNSIGNED short lenIV: IV: Number of bytes to follow (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
lenIVbyte[]: The actual IV (initialisation vector) used to encrypt the key's data (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
4int: Reference to the name of the MAC algorithm used to authenticate this record's data (index in the list of 'Block A').
2short lenMACKey: MAC key: Number of bytes in the MAC's key.
2short lenMACIV: MAC IV: Number of bytes in the MAC's IV.
2short lenMAC: MAC: Number of bytes in the MAC.
ENCRYPTED
lenMACKeyMAC key: The actual MAC's key (random).
lenMACIVMAC IV: The actual MAC's IV (random).
all until MACThe actual data (payload).
lenMACMAC: The actual MAC.
4int: Number of entries in 'Block C' to follow.
Block C: Key-ID-key-map
This block contains the actual keys. Every key is encrypted with the master-key.
BytesDescrition
8long: Key identifier.
4int: Reference to the name of the encryption algorithm used to encrypt this record's data (index in the list of 'Block A').
2UNSIGNED short lenIV: IV: Number of bytes to follow (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
lenIVbyte[]: The actual IV (initialisation vector) used to encrypt the key's data (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
4int: Reference to the name of the MAC algorithm used to authenticate this record's data (index in the list of 'Block A').
2short lenMACKey: MAC key: Number of bytes in the MAC's key.
2short lenMACIV: MAC IV: Number of bytes in the MAC's IV.
2short lenMAC: MAC: Number of bytes in the MAC.
ENCRYPTED
lenMACKeyMAC key: The actual MAC's key (random).
lenMACIVMAC IV: The actual MAC's IV (random).
all until MACThe actual data (payload).
lenMACMAC: The actual MAC.
4int: Number of entries in 'Block D' to follow.
Block D: Properties
See Property for details about what this block is used for.
BytesDescrition
2short len1: Property name: Number of bytes to follow (written by DataOutputStream.writeUTF(String)).
len1String: Property name (written by DataOutputStream.writeUTF(String)).
4int: Reference to the fully qualified class name of the Property (index in the list of 'Block A').
4int: Reference to the name of the encryption algorithm used to encrypt this record's data (index in the list of 'Block A').
2UNSIGNED short lenIV: IV: Number of bytes to follow (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
lenIVbyte[]: The actual IV (initialisation vector) used to encrypt the key's data (written by KeyStoreUtil.writeByteArrayWithShortLengthHeader(DataOutputStream, byte[])).
4int: Reference to the name of the MAC algorithm used to authenticate this record's data (index in the list of 'Block A').
2short lenMACKey: MAC key: Number of bytes in the MAC's key.
2short lenMACIV: MAC IV: Number of bytes in the MAC's IV.
2short lenMAC: MAC: Number of bytes in the MAC.
ENCRYPTED
lenMACKeyMAC key: The actual MAC's key (random).
lenMACIVMAC IV: The actual MAC's IV (random).
all until MACThe actual data (payload).
lenMACMAC: The actual MAC.
20SHA1 checksum over the complete file except for the header "Cumulus4jKeyStore", i.e. from the file version at byte offset 17 (including) till here (excluding).

Author:
Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de

Field Summary
static String MAC_ALGORITHM_NONE
           Constant for deactivating the MAC.
static String SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM
           System property to control the encryption algorithm that is used to encrypt data within the key-store.
static String SYSTEM_PROPERTY_KEY_SIZE
           System property to control the size of the keys generated.
static String SYSTEM_PROPERTY_MAC_ALGORITHM
           System property to control the MAC algorithm that is used to protect the data within the key-store against manipulation.
 
Constructor Summary
KeyStore(File keyStoreFile)
           Create a new instance of KeyStore.
 
Method Summary
 void changeUserPassword(String authUserName, char[] authPassword, String userName, char[] newPassword)
           Change a user's password.
 void clearCache(String userName)
           Clear all cached data for the specified user name.
 void createUser(String authUserName, char[] authPassword, String userName, char[] password)
           Create a new user.
 void deleteUser(String authUserName, char[] authPassword, String userName)
           Delete the user specified by userName.
protected  void finalize()
           
 GeneratedKey generateKey(String authUserName, char[] authPassword)
           Generate a new key and store it to the file.
 List<GeneratedKey> generateKeys(String authUserName, char[] authPassword, int qty)
           Generate qty new keys and store them to the file.
 byte[] getKey(String authUserName, char[] authPassword, long keyID)
          Get the key identified by the given keyID.
 SortedSet<Long> getKeyIDs(String authUserName, char[] authPassword)
           
 int getMasterKeySize(String authUserName, char[] authPassword)
           
 SortedSet<Property<?>> getProperties(String authUserName, char[] authPassword)
           
<P extends Property<?>>
P
getProperty(String authUserName, char[] authPassword, Class<P> propertyType, String name)
           Get a named property.
 SortedSet<String> getUsers(String authUserName, char[] authPassword)
           Get all users who can authenticate at this KeyStore.
 boolean isEmpty()
          Determine if this KeyStore is completely empty.
 boolean removeProperty(String authUserName, char[] authPassword, String name)
           Remove a property.
 void setProperty(String authUserName, char[] authPassword, Property<?> property)
           Set a property.
 
Methods inherited from class java.lang.Object
clone, equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
 

Field Detail

SYSTEM_PROPERTY_KEY_SIZE

public static final String SYSTEM_PROPERTY_KEY_SIZE

System property to control the size of the keys generated. This includes not only the actual keys for the main encryption/decryption (in the database), but also the master key used to protect the file managed by the KeyStore.

By default (if the system property "cumulus4j.KeyStore.keySize" is not specified), keys will have a size of 256 bit.

Note, that specifying the system property does not change any old keys - only new keys are generated with the currently active key size. Therefore, if you want to ensure that the internal master key is only 128 bit long, you have to make sure that the proper key size is specified when the first user is created!

See Also:
Constant Field Values

SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM

public static final String SYSTEM_PROPERTY_ENCRYPTION_ALGORITHM

System property to control the encryption algorithm that is used to encrypt data within the key-store. Whenever a new user is created or a new key is generated, data has to be encrypted (note that the encryption does not happen directly before data is written to the file, but already most data in memory is encrypted!).

By default (if the system property "cumulus4j.KeyStore.encryptionAlgorithm" is not specified), "Twofish/GCM/NoPadding" is used. For example, to switch to "AES/CFB/NoPadding", you'd have to specify the command line argument "-Dcumulus4j.KeyStore.encryptionAlgorithm=AES/CFB/NoPadding".

See this document for further information about what values are supported.

Important: The default MAC algorithm is "NONE", which is a very bad choice for most encryption algorithms! Therefore, you must change the MAC algorithm via the system property "cumulus4j.KeyStore.macAlgorithm" if you change the encryption algorithm!

See Also:
Constant Field Values

SYSTEM_PROPERTY_MAC_ALGORITHM

public static final String SYSTEM_PROPERTY_MAC_ALGORITHM

System property to control the MAC algorithm that is used to protect the data within the key-store against manipulation.

Whenever data is encrypted, this MAC algorithm is used to calculate a MAC over the original plain-text-data. The MAC is then stored together with the plain-text-data within the encrypted area. When data is decrypted, the MAC is calculated again over the decrypted plain-text-data and compared to the original MAC in order to make sure (1) that data was correctly decrypted [i.e. the password provided by the user is correct] and (2) that the data in the key-store was not manipulated by an attacker.

The MAC algorithm used during encryption is stored in the encryption-record's meta-data in order to use the correct algorithm during decryption, no matter what current MAC algorithm is configured. Therefore, you can safely change this setting at any time - it will affect future encryption operations, only.

Some block cipher modes (e.g. GCM) already include authentication and therefore no MAC is necessary. In this case, you can specify the MAC algorithm "NONE".

Important: If you specify the MAC algorithm "NONE" and use an encryption algorithm without authentication, the key store will not be able to detect a wrong password and instead return corrupt data!!! Be VERY careful with the MAC algorithm "NONE"!!!

The default value (used when this system property is not specified) is "NONE", because the default encryption algorithm is "Twofish/GCM/NoPadding", which (due to "GCM") does not require an additional MAC.

See Also:
Constant Field Values

MAC_ALGORITHM_NONE

public static final String MAC_ALGORITHM_NONE

Constant for deactivating the MAC.

Important: Deactivating the MAC is dangerous! Choose this value only, if you are absolutely sure that your encryption algorithm already provides authentication - like GCM does for example.

See Also:
SYSTEM_PROPERTY_MAC_ALGORITHM, Constant Field Values
Constructor Detail

KeyStore

public KeyStore(File keyStoreFile)
         throws IOException

Create a new instance of KeyStore.

If the file specified by keyStoreFile exists, it is read into memory. If it does not exist, an empty KeyStore is created and written to this file.

Parameters:
keyStoreFile - the file to be read (if existing) or created. Note that temporary files (and later maybe backup files, too) are created in the same directory (i.e. in keyStoreFile.getParentFile()).
Throws:
IOException - if reading from or writing to the local file-system failed.
Method Detail

isEmpty

public boolean isEmpty()
Determine if this KeyStore is completely empty. As soon as the first user has been created, this method will return false.

Returns:
true if this KeyStore contains neither any user nor any key, i.e. is totally empty; false otherwise.

getMasterKeySize

public int getMasterKeySize(String authUserName,
                            char[] authPassword)
                     throws AuthenticationException
Throws:
AuthenticationException

generateKey

public GeneratedKey generateKey(String authUserName,
                                char[] authPassword)
                         throws AuthenticationException,
                                IOException

Generate a new key and store it to the file.

The new key will be generated with the size specified by the system property "cumulus4j.KeyStore.keySize" and encrypted with the master-key and the encryption-algorithm specified by the system property "cumulus4j.KeyStore.encryptionAlgorithm".

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
Returns:
the newly created key.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
IOException - if writing to the local file-system failed.

generateKeys

public List<GeneratedKey> generateKeys(String authUserName,
                                       char[] authPassword,
                                       int qty)
                                throws AuthenticationException,
                                       IOException

Generate qty new keys and store them to the file.

This method behaves like generateKey(String, char[]) but is much faster when multiple keys have to be generated (bulk operation).

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
qty - the number of keys to be generated. If 0, the method will do nothing and return an empty list, if < 0, an IllegalArgumentException will be thrown.
Returns:
a list of generated keys; never null.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
IOException - if writing to the local file-system failed.

createUser

public void createUser(String authUserName,
                       char[] authPassword,
                       String userName,
                       char[] password)
                throws AuthenticationException,
                       UserAlreadyExistsException,
                       IOException

Create a new user.

Before the KeyStore can be used (i.e. before most methods work), this method has to be called to create the first user. When the first user is created, the internal master-key is generated, which will then not be changed anymore (double-check that the key-size is set correctly at this time).

Parameters:
authUserName - the authenticated user authorizing this action. If the very first user is created, this value is ignored and can be null.
authPassword - the password for authenticating the user specified by authUserName. If the very first user is created, this value is ignored and can be null.
userName - the name of the user to be created.
password - the password of the new user.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
UserAlreadyExistsException - if a user with the name specified by userName already exists.
IOException - if writing to the local file-system failed.

getUsers

public SortedSet<String> getUsers(String authUserName,
                                  char[] authPassword)
                           throws AuthenticationException

Get all users who can authenticate at this KeyStore.

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
Returns:
a read-only Set of all user-names known to this KeyStore. This Set is an unmodifiable copy of the internally used data and therefore is both thread-safe and iteration-safe (i.e. it can be iterated while simultaneously users are deleted).
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.

deleteUser

public void deleteUser(String authUserName,
                       char[] authPassword,
                       String userName)
                throws AuthenticationException,
                       UserNotFoundException,
                       CannotDeleteLastUserException,
                       IOException

Delete the user specified by userName.

Deleting the authenticated user himself (i.e. authUserName == userName) is possible, as long as it is not the last user.

Parameters:
authUserName - the name of the principal, i.e. the user authorizing this operation.
authPassword - the password of the principal.
userName - the name of the user to be deleted.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
UserNotFoundException - if there is no user with the name specified by userName.
CannotDeleteLastUserException - if the last user would be deleted by this method invocation (thus rendering the KeyStore unusable and unrecoverable - i.e. totally lost).
IOException - if writing to the local file-system failed.

changeUserPassword

public void changeUserPassword(String authUserName,
                               char[] authPassword,
                               String userName,
                               char[] newPassword)
                        throws AuthenticationException,
                               UserNotFoundException,
                               IOException

Change a user's password.

The user identified by userName will have the new password specified by newPassword immediately after this method. Authenticating this user with his old password will fail afterwards.

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
userName - the user whose password is to be changed. This can be the same as authUserName.
newPassword - the new password.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
UserNotFoundException - if there is no user with the name specified by userName.
IOException - if writing to the local file-system failed.

getKey

public byte[] getKey(String authUserName,
                     char[] authPassword,
                     long keyID)
              throws AuthenticationException,
                     KeyNotFoundException
Get the key identified by the given keyID.

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
keyID - the identifier of the key to get.
Returns:
the key associated with the given identifier; never null (if there is no key for the given keyID, a KeyNotFoundException is thrown).
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
KeyNotFoundException - if the specified keyID does not reference any existing key. Note, that the authentication process occurs before any lookup and therefore a KeyNotFoundException indicates a correct authentication (otherwise the AuthenticationException would have been thrown before).

getKeyIDs

public SortedSet<Long> getKeyIDs(String authUserName,
                                 char[] authPassword)
                          throws AuthenticationException
Throws:
AuthenticationException

getProperty

public <P extends Property<?>> P getProperty(String authUserName,
                                             char[] authPassword,
                                             Class<P> propertyType,
                                             String name)
                                  throws AuthenticationException

Get a named property.

The KeyStore supports managing arbitrary properties in the form of name-value-pairs. The names are plain-text, but the values are encrypted. A property-value can be of any type for which a subclass of Property exists.

This method will always return an instance of the given propertyType, no matter, if the property exists in this KeyStore or not. If the property does not exist, its value will be null.

Important: Never directly instantiate a Property-subclass. Always use this method as a factory for property instances.

Type Parameters:
P - the type of the property.
Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
propertyType - the type of the property; must not be null. If the property does not yet exist, every type can be specified. If the property already exists, this type must match the type of the property. If they do not match, an IllegalArgumentException is thrown.
name - the unique name of the property; must not be null.
Returns:
the property; never null. If the property does not yet exist, a new, empty property is returned.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
See Also:
setProperty(String, char[], Property), removeProperty(String, char[], String)

getProperties

public SortedSet<Property<?>> getProperties(String authUserName,
                                            char[] authPassword)
                                     throws AuthenticationException
Throws:
AuthenticationException

removeProperty

public boolean removeProperty(String authUserName,
                              char[] authPassword,
                              String name)
                       throws AuthenticationException,
                              IOException

Remove a property.

If the property with the given name does not exist, this method won't do anything.

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
name - the unique name of the property; must not be null.
Returns:
whether the property was removed, i.e. whether this KeyStore was changed by the operation.
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
IOException - if writing to the local file-system failed.
See Also:
getProperty(String, char[], Class, String), setProperty(String, char[], Property)

setProperty

public void setProperty(String authUserName,
                        char[] authPassword,
                        Property<?> property)
                 throws AuthenticationException,
                        IOException

Set a property.

If the property's value is null, the property is removed instead.

If a property with the same name already exists, it is overwritten.

The property's value is encrypted with the internal master-key. The property's name is stored in plain (unencrypted) form.

Parameters:
authUserName - the authenticated user authorizing this action.
authPassword - the password for authenticating the user specified by authUserName.
property - the property to set. Do not instantiate any property directly! Use getProperty(String, char[], Class, String) instead!
Throws:
AuthenticationException - if the specified authUserName does not exist or the specified authPassword is not correct for the given authUserName.
IOException - if writing to the local file-system failed.
See Also:
getProperty(String, char[], Class, String), removeProperty(String, char[], String)

clearCache

public void clearCache(String userName)

Clear all cached data for the specified user name.

Every time, a user calls a method requiring authUserName and authPassword, either an authentication process happens, or a previously cached authentication result (i.e. a decrypted master-key) is used. In order to speed things up, authentication results are cached for a limited time. After this time elapses, the data is cleared by a timer. If a user wants (for security reasons) remove the cached data from the memory earlier, he can call this method.

Parameters:
userName - the user for which to clear all the cached data. null to clear the complete cache for all users.

finalize

protected void finalize()
                 throws Throwable
Overrides:
finalize in class Object
Throws:
Throwable

Cumulus4j API
(1.0.0)

Copyright © 2012 NightLabs Consulting GmbH. All Rights Reserved.