start page | rating of books | rating of authors | reviews | copyrights

Book Home Java Security Search this book

11.3. A Key Management Example

The Sun implementation of the keytool utility is useful in many circumstances where users have disjoint databases. In Figure 11-1 we showed just such an example, and we mentioned that this example was set up in such a way that the code signer and the end user could have different key databases.

This is not to say, however, that those two databases could not have been the same database--that is, one that is shared by the signer and the end user. Since access to the private key of the signer is protected by a password, the signer and the end user are able to share a single database without concern that the end user may obtain access to the signer's private key (assuming that she keeps her password secret, of course). In the case of a corporate network, this flexibility is important, since an enterprise may want to maintain a single database that contains the private keys of all of its employees as well as the certificates of all known external entities.

We could have these users share the keystore by using the appropriate filename in the application and the java.policy files. But sharing the keytool database by a file is somewhat inefficient. If the global file is on a machine in New York and is referenced by a user in Tokyo, you'll want to use a better network protocol to access it than a file-based protocol. In addition, the load() method reads in the entire file. If there are 10,000 users in your corporate keystore database, you shouldn't need to read each entry into memory to find the one entry you are interested in using.

Hence, for many applications, you'll want to provide your own implementation of the KeyStore class. We'll show a very simple example here as a starting point for your own implementations. For the payroll application being deployed by XYZ Corporation, a database containing each employee in the corporation is necessary. The HR department could set up its own keystore for this purpose, but a similar keystore will be needed by the finance department to implement its 401K application; a better solution is to have a single keystore that is shared between all departments of XYZ Corporation.

In this case, the question becomes how best to share this keystore. A single global file would be too large for programs to read into memory and too unwieldy for administrators to distribute to all locations of XYZ Corporation. A better architecture is shown in Figure 11-2. Here, the application uses the security provider architecture to instantiate a new keystore object (of a class that we'll sketch out below). Unknown to the users of this object, the keystore class uses RMI (or CORBA, or any other distributed computing protocol) to talk to a remote server, which accesses the 10,000 employee records from a database set up for that purpose.

figure

Figure 11-2. A distributed keystore example

Without getting bogged down in the details of the network and database programming required for this architecture, let's look at how the KeyStore class itself would be designed.

Implementing a keystore requires that we write a KeyStoreSpi class, just as any other engine class. For most methods in the KeyStore class, there is a corresponding abstract engine method in the KeyStoreSpi class that you must provide an implementation for. A complete list of these methods is given in Table 11-1.

Table 11-1. Engine methods in the KeyStoreSpi class

KeyStore Class

KeyStoreSpi class

aliases

engineAliases

containsAlias

engineContainsAlias

deleteEntry

engineDeleteEntry

getCertificate

engineGetCertificate

getCertificateAlias

engineGetCertificateAlias

getCertificateChain

engineGetCertificateChain

getCreationDate

engineGetCreationDate

getKey

engineGetKey

isCertificateEntry

engineIsCertificateEntry

isKeyEntry

engineIsKeyEntry

load

engineLoad

setCertificateEntry

engineSetCertificateEntry

setKeyEntry

engineSetKeyEntry

size

engineSize

store

engineStore

Many of the methods of our new class are simple passthroughs to the remote server. If the handle to the remote server is held in the instance variable rks, a typical method looks like this:

Class Definition

public Date engineGetCreationDate(String alias) {
	return rks.getCreationDate(alias);
}

The methods that could be implemented in this manner are:

Class Definition

engineGetKey()
engineGetCertificateChain()
engineGetCertificate()
engineGetCreationDate()
engineAliases()
engineContainsAlias()
engineSize()
engineIsKeyEntry()
engineIsCertificateEntry()
engineGetCertificateAlias()

On the other hand, many methods should probably throw an exception--especially those methods that are designed to alter the keystore. In an architecture such as this one, changes to the keystore should probably be done through the database itself--or at least through a different server than the server used by all employees in the corporation. So many functions may look simply like this:

Class Definition

public void engineSetKeyEntry(String alias, Key key,
						char[] passphrase, Certificate chain[])
							throws KeyStoreException {
	throw new KeyStoreException("Can't change the keystore");
}

Methods that could be implemented in this manner are:

Class Definition

engineSetKeyEntry()
engineSetCertificateEntry()
engineDeleteEntry()
engineStore()

Note that we did not include the engineLoad() method in the above list. The engineLoad() method is useful to us, because it allows the application to require a password from the user before a connection to the remote server can be made. This differs slightly from normal programming for this class. Typically, the engineLoad() method is called with the input stream from which to read the keystore. In this case, the engineLoad() method is expected to be called with a null input stream, and sets up the connection to the remote server itself:

Class Definition

public void engineLoad(InputStream is, char[] password)
		throws IOException, NoSuchAlgorithmException, 
				CertificateException {
	rks = Naming.lookup("rmi://KSServer/DistributedKeyServer");
	if (!rks.authenticate(password)) {
		rks = null;
		throw new IOException("Incorrect password");
	}
}

Since the keystore database in this architecture cannot be written through the server, there is some question as to whether a password should be required to access the keystore at all (since there are individual passwords on the private keys). Every employee will potentially have access to the password (unless it is embedded into the application itself); you can decide if a password really adds security in that case. If no password is desired, the engineLoad() method could be empty and the connection to the remote server could be made in the constructor.

On the server side, implementation of the required methods is simply a matter of making appropriate database calls:

Class Definition

public int engineSize() {
	int sz = -1;
	try {
		Connection conn = connectToDatabase();
		Statement st = conn.createStatement();
		boolean restype = st.execute("select count(*) from entries");
		if (restype) {
			ResultSet rs = st.getResultSet();
			sz = Integer.parseInt(rs.getString(1));
		}
		st.cancel();
	} catch (Exception e) {
		...
	}
	finally {
		return sz;	
	} 
}

This architecture works well because it allows the passwords for each of the private keys to be held in the database itself, so retrievals of private keys can easily test the password via a simple string comparison. Implementations of file-based keystores are more problematic: if the file is readable by the user, obviously the password cannot be stored in the file as a simple string. File-based keystores must store their passwords and their private keys in encrypted form, perhaps using the encryption APIs we'll examine in Chapter 13, "Encryption". Assuming that the database machine is secured, such encryption is not required in this architecture.

There are unlimited possibilities in the implementation of a keystore. One technique might be to create a floppy for each employee that contains only that employee's entry and to write a keystore class that looks for key entries from the file on the floppy and for certificate entries from a global file somewhere.[3] This type of implementation is very simple. The new keystore can contain two instances of the Sun KeyStore class that have read in both files, and it can use object delegation to implement all of its methods.

[3]Of course, we don't want to use a floppy for this--we want to use a Java-enabled smart card, though of course we don't all have smart card readers on our computers. At least, not yet...

Note that this type of two-tiered system is really the ideal. If the private keys are transmitted over the network, as in our previous case, then internal spies on the network might snoop the password used to retrieve the key or the private key that is sent back. If the private key is held locally, however, and only the public keys are retrieved from the remote key store, you have a much better implementation.

11.3.1. Installing a KeyStore Class

In order to use an alternate keystore implementation, you must install your new class into a security provider. If necessary, you'll need to establish a convention by which the input stream that is opened for the load() method is created--unless your keystore does not require one at all (as, for example, our RMI-based keystore would not).

The Policy class uses the keystore in a predictable manner. Given this entry in the java.policy file:

Class Definition

grant signedBy, "sdo", codeBase "http://piccolo/" {
		...
}

the Policy class uses the keystore to look up the alias for sdo, retrieve sdo's public key, and use that public key to verify any signature that comes from the site piccolo. Remember, however, that the Sun implementation of the Policy class requires an entry in the java.policy file that specifies the URL from which to load the keystore.



Library Navigation Links

Copyright © 2001 O'Reilly & Associates. All rights reserved.