Now that we understand the pieces that make up a key management system, we can look at the topic of key management itself. From an administrative perspective, the primary tool that provides key management for Java 1.2 is the keytool utility. Keytool operates upon a file (or other storage system) containing a set of private keys and certificates for those keys. The keytool file contains a set of entries; each entry may have the following attributes:
An alias. This is a name you can use to reference the entity in the database. For example, an alias for my entry might be sdo, or ScottOaks.
One or more certificates that vouch for the identity of the entry. These certificates also provide the public key for the entry.
Optionally, a private key. If present, the private key can be protected by a password.
We'd be tempted to call the entries in this database identities, but that's potentially confusing: the entries stored in the keytool database are not instances of the Identity class (although we could create an identity object based on the information retrieved from the database).
Figure 11-1 shows the role of the keytool database in the creation and execution of a signed JAR file. Thejarsigner utility consults the keytool database for the private key of the entity that is signing the JAR file. Once the signed JAR file is produced, it is placed on a web server, where it can be downloaded into an appletviewer or other Java-enabled browser.[2] When the JAR file is read on the remote system, the keytool database is consulted in order to retrieve the public key of the entity that signed the JAR file so that the JAR file's signature can be verified.
[2]As we mentioned, however, Netscape Navigator, Internet Explorer, and HotJava at present all use a different key management system than the keytool database, so the appletviewer is the best example here.
Note that the two keytool databases in this example are (probably) separate databases, on separate machines. They probably have completely different entries as well--even for the entry that represents the signer. The signer's entry in her own database must have the private key of the signer, while the signer's entry in the user's database needs only a certificate (public key) for the signer. However, the keytool database could (in this and all examples) be a shared database--but more about that later. The default keytool database is the file .keystore that is held in the user's home directory.
The class that implements the keytool database is the KeyStore class (java.security.KeyStore):
Represent a set of private keys, aliases (entities), and their corresponding certificates. A keystore object is typically one that has been read in from disk; that is, the keystore object is an in-memory representation of the keytool database.
The KeyStore class is an engine class; there is a corresponding KeyStoreSpi class that you can use to write your own keystore (more about that a little later). By default, the Sun security provider implements a keystore called JKS (for Java KeyStore). Hence, instances of the KeyStore class are predictably obtained via this method:
Return an instance of the KeyStore class that implements the given algorithm, supplied by the given provider, if applicable. In the Sun security provider, the default algorithm name is "JKS".
If you do not want to hardwire the name of the keystore algorithm into your application, you may use this method to return the string that should be passed to the getInstance() method:
Return the default keystore algorithm for the environment. This value is obtained by looking for a property called keystore.type in the java.security file.
When the keystore object is created, it is initially empty. Although the getInstance() method has constructed the object, it is not expected that the object's constructor will read in a keystore from any particular location. The interaction between the keystore object and the keytool database comes via these two methods:
Initialize the keystore from the data provided over the given input stream. The integrity of the keystore is typically protected by using a message digest: when the keystore is stored, a message digest that represents the data in the keystore is also stored. Before the digest is created, the password is added to the digest data; this means that the digest cannot be re-created from a tampered keystore without knowledge of the password. The password for this method can be null, in which case the keystore is loaded and not verified.
This use of the password is a property of the Sun implementation of the KeyStore class; the password could be used for anything else (including encrypting the entire keystore) if you were to write your own implementation. To call this parameter a password is somewhat misleading (although that's what the javadoc documentation calls it), since Sun's implementation lets you read the entire keystore without it. The Sun implementation of the KeyStore class requires another password to access each private key in the keystore, so this isn't a potential security hole; all you're reading is public certificates.
You cannot require a password for load() to succeed, since the Sun implementation of the Policy class calls this method without a password when it constructs the information needed for the access controller. You may, of course, provide your own implementation of the Policy class that provides a password if desired.
In the Sun implementation, if the class required to support the underlying message digest is not available, a NoSuchAlgorithmException is thrown. An error in reading the data results in an IOException, and generic format errors in the data result in a CertificateException.
Store the keystore to the given output stream. The password is typically included in a digest calculation of the keystore; this digest is then written to the output stream as well (but again, your own implementation of this class could use the password differently).
The Sun implementation of this method may throw an IOException if the output stream cannot be read, a NoSuchAlgorithmException if the class used to create the digest cannot be found, or a CertificateException if the keystore object contains a certificate that cannot be parsed.
There is no default file that holds the keystore. Within the core Java API, the only class that opens the keystore is PolicyFile, and that opens the keystore that is listed in the java.policy file. The tools that use the keystore (the jarsigner and keytool tools) allow you to use a command-line argument to specify the file that contains the keystore; by default, that file is .keystore in the user's home directory. This is the convention your own programs will need to use. If your application needs to open the keystore (for example, to obtain a private key to sign an object), it should provide either a command-line argument or a property to specify the name of the file to open. By convention, we'll use the .keystore file in the user's home directory in our examples.
While we mentioned that the keystore may not be encrypted, the private keys themselves typically are encrypted so that if someone gains access to the keystore file, they do not have access to the private keys in that file without the password used to encrypt those keys. If you provide a keystore implementation that supplies keys from a protected location, you do not necessarily need to store the private keys in encrypted format. When private keys are delivered over the network, you probably want to make sure that the transmission of those keys is encrypted so that no one can snoop the network and discover the private key.
A keystore is arranged in terms of alias names. Aliases are arbitrarily assigned to an entry; while the name embedded in the certificate for a particular entry may be a long, complicated, distinguished name, the alias for that entry can provide a shorter, easier-to-remember name. There are a number of simple methods in the KeyStore class that deal with these alias names:
Return the name of the algorithm that this keystore implements.
Return the name of the provider that supplied this keystore implementation.
Return the date on which the entry referenced by the given alias was created.
Delete the entry referenced by the given alias from the keystore.
Return an enumeration of all the aliases in the keystore.
Indicate whether the keystore contains an entry referenced by the given alias.
Return the number of entries/aliases in the keystore.
Note that this list has a method to delete an entry but not one to create an entry--creating an entry in the keystore depends upon the type of entry you want to create.
The keystore holds two types of entries: certificate entries and key entries. A certificate entry is an entry that contains only a public key (encapsulated in a certificate) and can be used only to verify a digital signature, while a key entry is an entry that contains both a private and a public key and can be used to create and to verify a digital signature. Hence, you may think of a key entry as a signer and a certificate entry as an identity, although those classes are not used in the keystore interface (they may be used in the keystore implementation).
There are two basic differences between key entries and certificate entries:
A key entry contains a private key, while a certificate entry does not.
A key entry may contain a chain of certificates that verifies it, while a certificate entry contains a single certificate.
For a given alias, you can determine what type of entry it represents via these two methods:
Indicate whether the given alias represents a key entry or a certificate entry.
For a given alias, you cannot retrieve an object that represents the entire entry. You may use these methods to retrieve information about the entry represented by an alias:
Return the private key for the entry associated with the given alias. For a certificate entry, this method returns null. An UnrecoverableKeyException is thrown if the key cannot be retrieved (e.g., if the key has been damaged).
Retrieving a private key typically requires a password; this may or may not be the same password that was used to read the entire keystore. This allows private keys to be stored encrypted so they cannot be read without the appropriate password. If the class that provides encryption cannot be found, this method throws a NoSuchAlgorithmException.
Return the certificate chain that verifies the entry associated with the given alias, which must represent a key entry. For an alias that represents a certificate entry, this method returns null.
Return the certificate associated with the given alias. If the alias represents a key entry, the certificate returned is the user's certificate (that is, the first certificate in the entry's certificate chain); certificate entries have only a single certificate.
Return the alias that corresponds to the entry that matches the given certificate (using the equals() method of certificate comparison). If no matches occur, null is returned.
Finally, in order to create or modify an entry, you may use one of these methods. All of these methods create a new entry if the given alias does not exist:
Assign the given private key and certificate chain to the key entry represented by the given alias, creating a new key entry if necessary. Any previous private key and certificate chain for this entry are lost; if the previous entry was a certificate entry, it now becomes a key entry.
A KeyStoreException is thrown if the key entry cannot be encrypted by the internal encrypting algorithm of the keystore. In the Sun implementation, when the key is passed in as a series of bytes, it is not encrypted--in this case, you are expected to have performed the encryption yourself.
Assign the given certificate to the certificate entry represented by the given alias. If an entry for this alias already exists and is a key entry, a KeyStoreException is thrown. Otherwise, if an entry for this alias already exists, it is overwritten.
These are the basic methods by which we can manage a keystore. We'll see examples of many of these methods throughout the rest of this book; for now, let's look at a simple example that looks up a given entry in the keystore:
public class KeyStoreLookup { public static void main(String args[]) { try { KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); String fname = System.getProperty("user.home") + File.separator + ".keystore"; FileInputStream fis = new FileInputStream(fname); ks.load(fis, null); if (ks.isKeyEntry(args[0])) { System.out.println(args[0] + " is a key entry in the keystore"); char c[] = new char[args[1].length()]; args[1].getChars(0, c.length, c, 0); System.out.println("The private key for" + args[0] + " is " + ks.getKey(args[0], c)); Certificate certs[] = ks.getCertificateChain(args[0]); if (certs[0] instanceof X509Certificate) { X509Certificate x509 = (X509Certificate) certs[0]; System.out.println(args[0] + " is really " + x509.getSubjectDN()); } if (certs[certs.length - 1] instanceof X509Certificate) { X509Certificate x509 = (X509Certificate) certs[certs.length - 1]; System.out.println(args[0] + " was verified by " + x509.getIssuerDN()); } } else if (ks.isCertificateEntry(args[0])) { System.out.println(args[0] + " is a certificate entry in the keystore"); Certificate c = ks.getCertificate(args[0]); if (c instanceof X509Certificate) { X509Certificate x509 = (X509Certificate) c; System.out.println(args[0] + " is really " + x509.getSubjectDN()); System.out.println(args[0] + " was verified by " + x509.getIssuerDN()); } } else { System.out.println(args[0] + " is unknown to this keystore"); } } catch (Exception e) { e.printStackTrace(); } } }
This program expects two arguments: the name of the entity in the keystore for which information is desired, and the password that was used to encrypt the private key.
There are a number of points to pick out from this example. First, note that we constructed the keystore using the convention we mentioned earlier--the .keystore file in the user's home directory.
After we've read in the data, the first thing we do is determine if the entry that we're interested in is a key entry or a certificate entry--mostly so that we can handle the certificates for these entries differently. In the case of a key entry, we obtain the entire certificate chain, and use the first entry in that chain to print out the Distinguished Name (DN) for the entry, while the last entry in the chain is used to print out the DN for the last certificate authority in the chain. For a certificate entry, our task is simpler: there is a single certificate, and we simply print out its information.
Copyright © 2001 O'Reilly & Associates. All rights reserved.