import java.security.cert.; public class KeyStoreHandler {
KeyStore ks; private char[] pw;
Well use this to look up the keystore in the default location. You can specify a password if you like, but this will also
work if you pass null in which case the keystore isnt verified.
public KeyStoreHandlerchar[] pw { Make a private copy so the original can be collected so
that other objects cant locate it. if pw = null {
this.pw = new char[pw.length]; System.arraycopypw, 0, this.pw, 0, pw.length;
} else this.pw = null;
Load from the default location try {
ks = KeyStore.getInstanceKeyStore.getDefaultType ; String fname = System.getPropertyuser.home +
File.separator + .keystore; FileInputStream fis = new FileInputStreamfname;
ks.loadfis, pw; } catch Exception e {
throw new IllegalArgumentExceptione.toString ; }
} public KeyStore getKeyStore {
return ks; }
Store to the default location public void store throws FileNotFoundException,
KeyStoreException, IOException, NoSuchAlgorithmException,
CertificateException { If we didnt read with a password, we cant store
if pw == null { throw new IllegalArgumentExceptionCant store wo pw;
} FileOutputStream fos = new FileOutputStream
System.getPropertyuser.home + File.separator + .keystore;
ks.storefos, pw; fos.close ;
} public static void mainString args[] {
try { KeyStore ks = new KeyStoreHandlernull.getKeyStore ;
if ks.isKeyEntryargs[0] { System.out.printlnargs[0] +
is a key entry in the keystore; char c[] = new char[args[1].length ];
args[1].getChars0, c.length, c, 0; System.out.printlnThe private key for + args[0] +
is + ks.getKeyargs[0], c; java.security.cert.Certificate certs[] =
ks.getCertificateChainargs[0]; if certs[0] instanceof X509Certificate {
X509Certificate x509 = X509Certificate certs[0]; System.out.printlnargs[0] + is really +
x509.getSubjectDN ; }
if certs[certs.length − 1] instanceof X509Certificate {
X509Certificate x509 = X509Certificate certs[certs.length − 1];
System.out.printlnargs[0] + was verified by + x509.getIssuerDN ;
} }
else if ks.isCertificateEntryargs[0] { System.out.printlnargs[0] +
is a certificate entry in the keystore; java.security.cert.Certificate c =
ks.getCertificateargs[0]; if c instanceof X509Certificate {
X509Certificate x509 = X509Certificate c; System.out.printlnargs[0] + is really +
x509.getSubjectDN ; System.out.printlnargs[0] + was verified by +
x509.getIssuerDN ; }
} else {
System.out.printlnargs[0] + is unknown to this keystore;
} } catch Exception e {
e.printStackTrace ; }
} }
Well use this class in the rest of the book to manage the default keystore. Its main
method for testing 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 users home directory. After weve read in the data, the first thing we do is determine if the entry that were 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
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.
10.4 A Key Management Example
Now well proceed to a framework for enterprise−wide key management. Figure 10−1 shows the role of the keystore in the creation and execution of a signed jar file. The
jarsigner utility consults the keystore 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 the Java Plug−in. When the jar file is
read on the remote system, the keystore is consulted in order to retrieve the public key of the entity that signed the jar file so that the jar files signature can be verified.
Figure 10−1. the keytool database in a signed JAR file
Note that the two keystores in this example are probably separate files on separate machines. They probably have completely different entries as well −− even for the entry that represents the signer. The signers entry in
her own database must have the private key of the signer while the signers entry in the users database needs only a certificate for the signer. However, the keystore could in this and all examples be a shared database.
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 signers 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 keystore 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 will 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 shouldnt need to read each entry into memory to find the one entry you are interested in using.
Hence, for many applications, youll want to provide your own implementation of the KeyStore
class. Well 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 by 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 10−2. Here, the application uses the security provider architecture to instantiate a new keystore object of a class that well 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 10−2. A distributed keystore example
Without getting bogged down in the details of the network and database programming required for this architecture, lets look at how the
KeyStore class itself would be designed.
Implementing a keystore requires that we write a KeyStoreSpi
class, just as with any 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 10−1.
Table 10−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:
public Date engineGetCreationDateString alias { return rks.getCreationDatealias;
}
The methods that could be implemented in this manner are:
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. Many functions may look simply like this:
public void engineSetKeyEntryString alias, Key key, char[] passphrase, Certificate chain[]
throws KeyStoreException { throw new KeyStoreExceptionCant change the keystore;
}
Methods that could be implemented in this manner are:
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:
public void engineLoadInputStream is, char[] password throws IOException, NoSuchAlgorithmException,
CertificateException { RemoteKeyServer rks = RemoteKeyServer
Naming.lookuprmi:KSServerDistributedKeyServer; if rks.authenticatepassword {
rks = null; throw new IOExceptionIncorrect 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:
public int engineSize { int sz = −1;
try { Connection conn = connectToDatabase ;
Statement st = conn.createStatement ; boolean restype = st.executeselect count from entries;
if restype { ResultSet rs = st.getResultSet ;
sz = Integer.parseIntrs.getString1; }
st.cancel ; } catch Exception e {