11.2. The KeyStore Class
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.
Figure 11-1. The keytool database in a signed JAR file
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):
-
public class 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:
-
public static final KeyStore getInstance(String type)
-
public static final KeyStore getInstance(String type, String provider)
-
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:
-
public static final String getDefaultType()
-
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:
-
public final void load(InputStream is, char[] password)
-
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.
-
public final void store(OutputStream os, char[] password)
-
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:
-
public final String getType()
-
Return the name of the algorithm that this keystore implements.
-
public final String getProvider()
-
Return the name of the provider that supplied this keystore
implementation.
-
public final Date getCreationDate(String alias)
-
Return the date on which the entry referenced by the given alias was
created.
-
public final void deleteEntry(String alias)
-
Delete the entry referenced by the given alias from the keystore.
-
public final Enumeration aliases()
-
Return an enumeration of all the aliases in the keystore.
-
public final boolean containsAlias(String alias)
-
Indicate whether the keystore contains an entry referenced by the
given alias.
-
public final int size()
-
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:
-
public final boolean isKeyEntry(String alias)
-
public final boolean isCertificateEntry(String alias)
-
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:
-
public final Key getKey(String alias, char[] password)
-
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.
-
public final Certificate[] getCertificateChain(String alias)
-
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.
-
public final Certificate getCertificate(String alias)
-
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.
-
public final String getCertificateAlias(Certificate cert)
-
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:
-
public final void setKeyEntry(String alias, byte key[], Certificate chain[])
-
public final void setKeyEntry(String alias, Key k, char[] password, Certificate chain[])
-
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.
-
public final void setCertificateEntry(String alias, Certificate c)
-
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:
Class Definition
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.
 |  |  |
| 11.1. Overview of Key Management |  | 11.3. A Key Management Example |

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