At this point you should be fairly comfortable with what is required to create keys and certificates, but you are probably also wondering what you are supposed to do if you need them to be around for more than the duration of an example program. How can you store a private key safely and maintain the relationship it has with its associated certificates? What do you do if you need to pass a private key onto someone else? Is there a way of persisting a secret key for longer- term use?
This chapter looks at the keystore facilities offered in Java through the KeyStore class and some of the underlying variations in how KeyStore objects are implemented.
By the end of this chapter you should
Finally, you will also understand how to use the keytool command and integrate it with the work I covered in earlier chapters, as well as be aware of some of the other JVM features that make use of keystores.
Primarily, Java KeyStore objects are used to store private keys and their associated certificates. So, for the purposes of the examples of this chapter, you need some extra functionality in the Utils class to support this.
Fortunately, adding the extra functionality just means building on the certificate functionality you added in the last chapter and taking advantage of another Java class related to X500Principal ”the X500PrivateCredential class.
The javax.security.auth.x500.X500PrivateCredential is a simple value object that can be used to contain a private key, its associated X.509 certificate, and an optional alias. It also has a destroy() method on it, which allows the credential to be destroyed when it is no longer required. For the purposes of giving you some instant key/certificate pairs, X500PrivateCredential will do nicely .
Here is the Utils class for the chapter8 package:
package chapter8; import java.security.KeyPair; import java.security.PrivateKey; import java.security.cert.X509Certificate; import javax.security.auth.x500.X500PrivateCredential; /** * Chapter 8 Utils */ public class Utils extends chapter7.Utils { public static String ROOT_ALIAS = "root"; public static String INTERMEDIATE_ALIAS = "intermediate"; public static String END_ENTITY_ALIAS = "end"; /** * Generate a X500PrivateCredential for the root entity. */ public static X500PrivateCredential createRootCredential() throws Exception { KeyPair rootPair = generateRSAKeyPair(); X509Certificate rootCert = generateRootCert(rootPair); return new X500PrivateCredential( rootCert, rootPair.getPrivate(), ROOT_ALIAS); } /** * Generate a X500PrivateCredential for the intermediate entity. */ public static X500PrivateCredential createIntermediateCredential( PrivateKey caKey, X509Certificate caCert) throws Exception { KeyPair interPair = generateRSAKeyPair(); X509Certificate interCert = generateIntermediateCert( interPair.getPublic(), caKey, caCert); return new X500PrivateCredential( interCert, interPair.getPrivate(), INTERMEDIATE_ALIAS); } /** * Generate a X500PrivateCredential for the end entity. */ public static X500PrivateCredential createEndEntityCredential( PrivateKey caKey, X509Certificate caCert) throws Exception { KeyPair endPair = generateRSAKeyPair(); X509Certificate endCert = generateEndEntityCert( endPair.getPublic(), caKey, caCert); return new X500PrivateCredential( endCert, endPair.getPrivate(), END_ENTITY_ALIAS); } }
Type this in, compile it, and you are ready to proceed.
The java.security.KeyStore class was introduced in JDK 1.2. Like other JCA classes, it follows the getInstance() factory pattern with the type of the KeyStore being given as the first argument and a provider being specified if appropriate. As usual, the KeyStore.getInstance() method obeys the precedence rules appropriate to the configuration of the Java runtime you are using. If the requested type is not available, the call to getInstance() will throw a java.security.KeyStore exception. JDK 1.5 has also seen some changes to the KeyStore class in that it is now more flexible than before in how it can be used, and some nested classes have been added to the class to make this possible.
The KeyStore API supports the persisting of three types of entries:
Entries are stored under a given alias in the KeyStore object, and the alias, together with a password if required, is used to retrieve the entry that was saved. The API also allows you to find out some of the characteristics of the individual entries, as well as supporting the retrieval of some basic properties about the KeyStore object itself, such as its size and creation date.
Before you look at the API presented by the KeyStore class, it is worth discussing what types of keystore are available first. As you will see, although they follow the same basic API, there are some less than subtle differences in what they do, even when the type has the same name .
The range of the keystore types available is dependent on the providers you have installed. The Sun JCA and JCE both ship with some standard ones; other providers will contain variations on these and perhaps some extra. Variations do exist on the nonproprietary formats such as PKCS12 , so some care needs to be exercised if you are provider swapping and you are using a more open format.
In addition to the list of available installed types, every JVM has a notion of the default type for a keystore, which is set by the keystore.type property in the java.security file. This can be retrieved using the static method KeyStore.getDefaultType() and by default, if the property is not set, the method will return jks . With the Bouncy Castle provider installed, you have five basic keystore types to choose from, with some variations.
Let's have a look at the standard types that ship with the JDK first.
Standard JDK Keystore Types
There are three basic keystore types that ship with the JDK, which also have some minor variations:
As you can see from the list, one of these, PKCS12 , is also an open format. I'll go through the Bouncy Castle keystore types now. Note that PKCS12 is also listed there ”and it is not quite the same.
Bouncy Castle Keystore Types
The Bouncy Castle provider offers three types of keystore:
As you can see, the PKCS #12 implementation is different. Several other providers also have implementations of PKCS #12, which again differ , and then there are still more issues about importing them into other applications. At the moment I want to discuss the KeyStore API, so I will do that next , but I will discuss PKCS #12 issues later on in the chapter. For now just remember they exist.
As mentioned earlier, the KeyStore API changed with the introduction of JDK 1.5. First up I will start with the API as it stood pre-JDK 1.5 ”the two APIs overlap and the JDK 1.5 changes can be considered to be an extension of the original KeyStore API. There are still a lot of organizations using earlier JVMs than 1.5, so breaking the discussion of the API into two parts makes sense as well. It is likely that you will come across code written for the earlier one, or even a JVM where the 1.5 extensions cannot be used. So the list that follows should work for any JVM from JDK 1.2.
KeyStore.aliases()
The aliases() method returns a java.util.Enumeration of String objects representing the alias names present in the KeyStore . The method will throw a KeyStoreException if the KeyStore object has not been initialized .
KeyStore.containsAlias()
The boolean containsAlias() method returns true if the passed-in String represents an alias name present in the store, false otherwise. Note there are also methods called isKeyEntry() and isCertificateEntry() . They allow you to tell if a given alias represents a key or a certificate. The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.deleteEntry()
The deleteEntry() method deletes the entry with the alias name represented by the passed-in String from the contents of the store. The method will throw a KeyStoreException if the KeyStore object has not been initialized, or if the entry cannot be removed.
KeyStore.getCertificate()
The getCertificate() method takes a String alias name as an argument and returns the certificate associated with that alias. The certificate can be either a trusted certificate, or if the alias is actually the alias for a private key, it will return the certificate containing the public key associated with that private key. If the entry does not exist, the method returns null . The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.get CertificateAlias()
The getCertificateAlias() method takes a java.security.cert.Certificate as an argument and returns a String representing the alias name of the first entry found in the store that contains the passed-in certificate. Note that the alias name may be for a key entry if the certificate is for a private key that is present in the store. Otherwise, it will be for a trusted certificate entry. If no suitable entry exists, the method returns null . The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.getCertificateChain()
The getCertificateChain() method returns an array of java.security.cert.Certificate objects representing the certificate chain of the private key entry associated with the passed-in String alias name. The chain is ordered with the private key associated certificate first and the root authority certificate last. The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.get CreationDate()
The getCreationDate() method takes a single String representing an alias name as an argument and returns the creation date of the alias if it is available. Some formats such as PKCS12 may not carry this information inside them. If that is the case, getCreationDate() will usually just return the current time. The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.getKey()
The getKey() method takes a String representing an alias name and a char array representing a password, returning the java.security.Key object that is associated with that alias name. If the alias does not exist, or does not represent a key, then the method returns null . The method can throw one of three exceptions found in the java.security package. If the key cannot be recovered, say, because the password is wrong, the method throws an UnrecoverableKeyException . If the store has not been initialized, the method throws a KeyStoreException , and if the algorithm required to recover the key is not available, the method throws a NoSuchAlgorithm exception.
KeyStore.getType()
The getType() method simply returns the string representing the format type of the KeyStore object.
KeyStore.isCertificate Entry()
The isCertificateEntry() method returns true if the passed-in String represents an alias name associated with a trusted certificate, false otherwise. The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.isKeyEntry()
The isKeyEntry() method returns true if the passed in String represents an alias name associated with a key, false otherwise. The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.load()
The early version of the load() method takes an java.io.InputStream and an array of char as parameters and reads in the keystore contained in the InputStream using the password to check the integrity of the store. If no password is given, then the integrity of the store is not checked.
You must do a load() to initialize a KeyStore object. If you are creating one afresh, you can pass null as both the InputStream and password parameters as follows:
keyStore.load(null, null);
Note that in some cases, such as the UBER keystore type and some versions of PKCS #12, the password is actually used as more than just a means of doing an integrity check. It is also used as an encryption key, and although this was not, strictly speaking, the intention of the KeyStore API designers, it was most definitely the intention of the people implementing the keystore types that do it. For the most part you will not notice this difference, but some tools are written on the basis that a keystore can always be loaded without giving a password, and it will be fairly obvious that keystore types that encrypt themselves fully on writing will not work with tools that make this assumption. So, this is something to watch out for, both when you are writing your own tools and when you are using other people's.
A second load() method was added in JDK 1.5. You will look at that a bit later when you look at the KeyStore.LoadStoreParameter class.
KeyStore.setCertificate Entry()
The setCertificateEntry() method takes a string and a java.security.cert.Certificate as arguments and adds a trusted certificate entry for the passed-in certificate using the alias name represented by the String . If the KeyStore object has not been initialized, or the given alias already exists but does not represent a trusted certificate, or something else fails, the method will throw a KeyStoreException .
KeyStore.setKeyEntry()
There are two versions of the setKeyEntry() method. The first argument in both cases is a String representing an alias name, and if the alias name already exists, it will be overwritten by the new key entry. Both methods will throw a KeyStoreException if the KeyStore has not been initialized or in the event of some other failure.
The first version takes an additional three arguments to the alias name: a java.security. Key , a char array, and a java.security.cert.Certificate array, representing the key object to be saved; the password to be used for protecting the key; and the certificate chain for the key if there is one.
The second version allows you to pass in an already encrypted key. It takes the alias name and an extra two arguments: a byte array representing the encrypted key and an array of Certificate representing the certificate chain if there is one available. You might find yourself using this version if you are using a machine that has a hardware cryptography adapter in it for storing private keys, or you have just received a key from someone who is using one. What the byte array should contain is largely up to the provider you are working with, but in the case of private keys, it will normally be the DER encoding of an EncryptedPrivateKeyInfo object.
KeyStore.size()
The size() method returns the number of entries in the KeyStore object. The method will throw a KeyStoreException if the KeyStore object has not been initialized.
KeyStore.store()
As with the load() method there are now two versions of the store() method ”the second one being added in JDK 1.5. You will have a look at the second one later when you read about the KeyStore.LoadStoreParameter class , but the original version takes two parameters, an OutputStream , which is the stream the KeyStore is to write to, and an array of char representing a password to protect the encoded store.
The same footnote applies to store() as concerns load() . Although the word "protect" in this context implies that the password is used to generate an integrity check, in some cases, the password is also used as the source of an encryption key. You shouldn't ever notice the difference in this case. It is really only loading where this becomes an issue. However, it is important to keep this issue in mind and be aware of it.
This covers all the APIs you need to get up and running with a KeyStore . It is time to look at another example.
Try It Out: Using a JKS Keystore
This example shows the basic use of a keystore of the JKS type. Because it is the JKS type, it can store only private keys and trusted certificates. Therefore, the example demonstrates storing one of each. Have a look at what it does and try running it.
package chapter8; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Enumeration; import javax.security.auth.x500.X500PrivateCredential; /** * Example of basic use of KeyStore. */ public class JKSStoreExample { public static char[] keyPassword = "keyPassword".toCharArray(); public static KeyStore createKeyStore() throws Exception { KeyStore store = KeyStore.getInstance("JKS"); // initialize store.load(null, null); X500PrivateCredential rootCredential = Utils.createRootCredential(); X500PrivateCredential interCredential = Utils.createIntermediateCredential( rootCredential.getPrivateKey(), rootCredential.getCertificate()); X500PrivateCredential endCredential = Utils.createEndEntityCredential( interCredential.getPrivateKey(), interCredential.getCertificate()); Certificate[] chain = new Certificate[3]; chain[0] = endCredential.getCertificate(); chain[1] = interCredential.getCertificate(); chain[2] = rootCredential.getCertificate(); // set the entries store.setCertificateEntry( rootCredential.getAlias(), rootCredential.getCertificate()); store.setKeyEntry( endCredential.getAlias(), endCredential.getPrivateKey(), keyPassword, chain); return store; } public static void main(String[] args) throws Exception { KeyStore store = createKeyStore(); char[] password = "storePassword".toCharArray(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); // save the store store.store(bOut, password); // reload from scratch store = KeyStore.getInstance("JKS"); store.load(new ByteArrayInputStream(bOut.toByteArray()), password); Enumeration en = store.aliases(); while (en.hasMoreElements()) { String alias = (String)en.nextElement(); System.out.println("found " + alias + ", isCertificate? " + store.isCertificateEntry(alias)); } } }
Running the example should produce the following output:
found root, isCertificate? true found end, isCertificate? false
listing the two entries that have been added to the KeyStore object and printing isCertificate?true if the entry represents a trusted certificate.
How It Works
The example is divided up into two steps. The first is the keystore creation in the createKeyStore() method; the second is in the main driver where the keystore is reloaded and its contents listed.
Looking at the createKeyStore() method, note that KeyStore.getInstance() is called to create an uninitialized KeyStore object, and then the line
store.load(null, null);
is invoked to initialized the object. This line is important; if it is missing, you will get a KeyStoreException when the entries are added further down.
After the creation of the credentials for the two parties required to validate the end entity certificate and the creation of the end entity credentials, a certificate chain is built up in an array with the end entity first. The root certificate is then saved as a trusted certificate entry, and the private key for the end entity and its associated certificate chain are saved as well.
The KeyStore object is then returned to the main driver, where it is written out to an OutputStream and then reloaded from an InputStream , and KeyStore.aliases() is invoked so the example can list the aliases present in the KeyStore object.
An interesting point to note about the reloading: You can replace the line where the keystore is reloaded:
store.load(new ByteArrayInputStream(bOut.toByteArray()), password);
with this instead:
store.load(new ByteArrayInputStream(bOut.toByteArray()), null);
and the program will still work. The reason, as mentioned earlier, is that for most keystore types, not specifying the password is equivalent to saying you do not want to do an integrity check, and the JKS keystore type is one of those. Consequently, the call to store.load() with a null is just saying "load up the keystore but don't bother verifying the data has not been tampered with." This isn't something I'm trying to encourage , but it is an aspect of keystore behavior that you need to be aware of; otherwise, sometimes weird things will appear to happen.
This more or less describes the world as it was prior to JDK 1.5. Now it is time for you to look at some of the recent changes.
JDK 1.5 saw the introduction of a number of nested classes and interfaces to the KeyStore class. They can be divided into four groups. The first group is represented by the classes implementing the interface KeyStore.Entry , the second the classes implementing the interface KeyStore.ProtectionParameter , and the last two are groups with only one member made up of the KeyStore.Builder class and the KeyStore.LoadStoreParameter interface, respectively.
The most immediately useful of these is the classes implementing the KeyStore.Entry . They are also used in conjunction with the classes that implement KeyStore.ProtectionParameter , so let's look atthese two interfaces and their implementing objects first.
The KeyStore.ProtectionParameter interface is a marker interface for parameters that are used to provide information used to protect keystore entries. Objects implementing the class can carry passwords or other authorizing information that can be used as source material for integrity checks and encryption. The KeyStore class includes two nested classes that implement the KeyStore.ProtectionParameter interface: KeyStore.CallbackHandlerProtection and KeyStore.PasswordProtection .
KeyStore.Callback HandlerProtection
The KeyStore.CallbackHandlerProtection class is a value object that's constructed with a javax.security.auth.callback.CallbackHandler object. It has a single method on it ” getCallbackHandler() . You can use this method to retrieve the instance of CallbackHandler the object was constructed with.
CallbackHandler is actually an interface with a single method on it ” handle() , which takes an array of Callback objects. Callback is also an interface defined in javax.security.auth.callback , and if you have a look at the JavaDoc for it, you will see there are a large number of security- related classes that implement Callback in the same package for helping deal with identity, passwords, and the like. This protection mode is relatively new, though, so at this writing, few keystore types appear to support it, but you can probably expect that to change.
KeyStore.PasswordProtection
The KeyStore.PasswordProtection class is a value object with a single constructor that takes an array of char representing the password. It has a getPassword() method on it that returns a reference to the password char array the object contains and a destroy() method on it to erase the password.
Note the password passed to the constructor of the object may be null . If the password has been destroyed through a call to the destroy() method, a call to getPassword() will throw an IllegalStateException . Because this protection mode is just a replacement for the original use of the password in the older methods on the KeyStore API, it is well supported. You will look at its use in the example later.
The KeyStore.Entry interface is a marker interface for keystore entry objects that can be utilized using three methods that were added to the KeyStore API in JDK 1.5: KeyStore.getEntry() , KeyStore.setEntry() , and KeyStore.entryInstanceOf() . Three nested classes were also added to the KeyStore class that implement KeyStore.Entry . The class names are KeyStore.PrivateKeyEntry , KeyStore.SecretKeyEntry , and KeyStore.TrustedCertificateEntry . The chapter covers these after covering the new methods.
KeyStore.getEntry()
The getEntry() method takes two arguments: a String representing the alias name for the entry and an optional protection parameter object implementing KeyStore.ProtectionParameter . It returns an object implementing KeyStore.Entry if the given alias exists and the protection parameter is valid. The method will throw a NullPointerException if the alias name is null , an UnrecoverableEntryException if the passed in KeyStore.ProtectionParameter object is invalid, a NoSuchAlgorithmException if the algorithm required to recover the entry cannot be found, and a KeyStoreException if the KeyStore object is not initialized .
KeyStore.setEntry()
The setEntry() method takes three arguments: a String representing the alias name for the entry, an entry object implementing KeyStore.Entry , and an optional object implementing KeyStore.ProtectionParameter . The method will throw a NullPointerException if either the alias name or the entry object is null and a KeyStoreException if the KeyStore object is not initialized.
KeyStore.entryInstanceOf()
The boolean entryInstanceOf() method takes a String representing an alias name and a Class object representing a class or interface that is an extension of KeyStore.Entry . If the alias exists and its entry can be cast to the specified type, the method returns true ; otherwise , it returns false . The method will throw a NullPointerException if either the alias name or the type object is null , and a KeyStoreException if the KeyStore object is not initialized.
KeyStore.PrivateKeyEntry
The KeyStore.PrivateKeyEntry class enables the creation of value objects that take a java.security.PrivateKey object and an array of java.security.cert.Certificate objects representing the private key and the associated certificate chain of the entry you want to create. It has three methods: getPrivateKey() returns the private key the entry was constructed with, getCertificateChain() returns a copy of the certificate chain, and getCertificate() returns the certificate at position zero in the chain, which should be the certificate containing the public key for the private key in the entry. An interesting feature of the getCertificateChain() method is that it will return the array to suit the subclass of Certificate it contains, so, in the case of a chain made up of X509Certificate objects, getCertificateChain() will return a X509Certificate array.
KeyStore.SecretKeyEntry
The KeyStore.SecretKeyEntry class allows creation of value objects for carrying a javax.crypto.SecretKey . It has a single constructor that takes a SecretKey and a method, getSecretKey() , that retrieves it. Only some keystore types can handle storage of KeyStore.SecretKeyEntry objects; JKS and PKCS12 types cannot.
KeyStore.TrustedCertificateEntry
The KeyStore.TrustedCertificateEntry class allows creation of value objects that carry trusted certificate entries. It has a single constructor that takes a java.security.cert.Certificate object and a method, getTrustedCertificate() , which allows the retrieval of the Certificate object the entry contains.
Having looked at the KeyStore.ProtectionParameter and KeyStore.Entry classes, you are now at the stage where you can look at an example of the new way of doing things.
Try It Out: Using KeyStore.setEntry()
This example is using both the "new look" and a different keystore type. I've done this so that I can demonstrate the storage of secret keys using one of the Sun-based keystores. The JKS does not support this, but the JCEKS does. Have a look at it and see what it is doing.
package chapter8; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.security.KeyStore; import java.security.SecureRandom; import java.security.cert.Certificate; import java.util.Enumeration; import javax.crypto.SecretKey; import javax.security.auth.x500.X500PrivateCredential; /** * Example of using a JCEKS keystore with KeyStore.Entry and * KeyStore.ProtectionParameter objects. */ public class JCEKSStoreEntryExample { public static char[] keyPassword = "endPassword".toCharArray(); public static char[] secretKeyPassword = "secretPassword".toCharArray(); public static KeyStore createKeyStore() throws Exception { KeyStore store = KeyStore.getInstance("JCEKS"); // initialize store.load(null, null); X500PrivateCredential rootCredential = Utils.createRootCredential(); X500PrivateCredential interCredential = Utils.createIntermediateCredential( rootCredential.getPrivateKey(), rootCredential.getCertificate()); X500PrivateCredential endCredential = Utils.createEndEntityCredential( interCredential.getPrivateKey(), interCredential.getCertificate()); Certificate[] chain = new Certificate[3]; chain[0] = endCredential.getCertificate(); chain[1] = interCredential.getCertificate(); chain[2] = rootCredential.getCertificate(); SecretKey secret = Utils.createKeyForAES(256, new SecureRandom()); // set the entries store.setEntry(rootCredential.getAlias(), new KeyStore.TrustedCertificateEntry( rootCredential.getCertificate()), null); store.setEntry(endCredential.getAlias(), new KeyStore.PrivateKeyEntry( endCredential.getPrivateKey(), chain), new KeyStore.PasswordProtection( keyPassword)); store.setEntry("secret", new KeyStore.SecretKeyEntry(secret), new KeyStore.PasswordProtection(secretKeyPassword)); return store; } public static void main(String[] args) throws Exception { KeyStore store = createKeyStore(); char[] password = "storePassword".toCharArray(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); // save the store store.store(bOut, password); // reload from scratch store = KeyStore.getInstance("JCEKS"); store.load(new ByteArrayInputStream(bOut.toByteArray()), password); Enumeration en = store.aliases(); while (en.hasMoreElements()) { String alias = (String)en.nextElement(); System.out.println("found " + alias + ", isCertificate? " + store.isCertificateEntry(alias) + ", secret key entry? " + store.entryInstanceOf( alias, KeyStore.SecretKeyEntry.class)); } } }
Running the example produces the following output:
found root, isCertificate? true, secret key entry? false found end, isCertificate? false, secret key entry? false found secret, isCertificate? false, secret key entry? true
As you can see, you have managed to save the secret key as well, and it has been correctly identified as such when you rebuilt the store.
How It Works
This example is a similar structure to the previous one. The keystore is created in the createKeyStore() method, and then the alias names present are listed after the keystore has been written out and reloaded. Both the createKeyStore() method and the main driver show differences because of the new API that is being used, so I will go through those in order of execution.
The first difference is in the createKeyStore() method where not only is the example now generating a secret key, but it is using the KeyStore.setEntry() method to save each entry in the keystore. As you can see, this is all done using nested KeyStore classes that implement KeyStore.Entry to package the keys and certificates and using the KeyStore.PasswordProtection class to carry the password protecting the entry. I have given the certificate a null protection parameter, as the default behavior for a keystore implementation is to throw an exception if an attempt is made to password-protect a certificate. Other than that, it is fairly obvious what is going on.
The second difference is in the manner the entries get checked in the main driver. As well as using the older KeyStore.isCertificate() method, the example is also using the KeyStore.entryInstanceOf() method, and in a similar fashion to the instanceof keyword in Java, it simply checks the entry for the passed-in alias against the passed-in type, returning true if there is a match.
You can use both the BKS and UBER keystore types in the same fashion as the JCEKS . With flexibility that the KeyStore.Entry and KeyStore.ProtectionParameter changes lend the API, you can probably expect to see a much broader range of keystore implementations in the future.
The idea of the KeyStore.Builder class is that you can bundle the information required to create a keystore into a KeyStore.Builder but delay creation of the actual keystore until it is really required. This can be useful if you want to set up the keystore but delay actual construction until some later time, such as when a user can type in a password. The class also allows you to take an already created KeyStore object and pass it off to another object together with a protection parameter that might be required to access the entry.
The KeyStore.Builder class is also constructed using a factory pattern that behaves similar to the standard getInstance() method but, in this case, is called newInstance() . It also has two methods on it, getKeyStore() and getProtectionParameter() .
KeyStore.Builder.get KeyStore()
This returns the KeyStore object that the builder is encapsulating. It may return the same object every time or it might create a new keystore on each invocation ”it depends on which version of newInstance() was called. If the KeyStore.Builder object is unable to return a keystore, the method will throw a KeyStoreException . The getKeyStore() method needs to be invoked before it is possible to call the getProtectionParameter() method.
KeyStore.Builder.get ProtectionParameter()
The getProtectionParameter() class takes a single String as an argument representing the alias name for the entry the protection parameter is being requested for and returns the protection parameter if one exists. If the alias is null, the method will throw a NullPointerException ; if the getKeyStore() method has not yet been invoked, the method will throw an IllegalStateException ; and if some other problem occurs, the method will throw a KeyStoreException .
KeyStore.Builder.new Instance()
There are three variations on the static newInstance() method available on the class, two of which include a JCA provider name in their argument list. In cases where a provider name is required but is passed in as null , the usual precedence rules for finding a suitable KeyStore.Builder will apply.
The first one is for the purpose of encapsulating a KeyStore object and a protection parameter, and it takes a KeyStore object and a KeyStore.ProtectionParameter as arguments. The idea is that the protection parameter can be retrieved later and used to access one or more entries in the keystore that the builder was created with. Both calls to getKeyStore() , and getProtectionParameter() will return the objects the builder was created with. In this case, the method will throw a NullPointerException if either of its arguments is null and an IllegalArgumentException if the keystore passed in has not been initialized.
The second newInstance() method takes three arguments: a String indicating the type of the keystore to be built, a String giving the provider name to use, and a KeyStore.ProtectionParameter . In this case, each call to getKeyStore() will create a new KeyStore object that will be initialized using the new load() method that takes a KeyStore.LoadStoreParameter . The getProtectionParameter() method will return the parameter passed in when newInstance() is invoked. The method will throw a NullPointerException if either the type or protection parameter arguments are null .
The third newInstance() method takes a File parameter in addition to the three parameters the second newInstance() method does. A call to getKeyStore() on a builder created using this newInstance() method will return the same KeyStore object, with the object being constructed on the first call using the given type and provider and then the InputStream -based load method being invoked with a password being recovered from the protection parameter either directly or via a callback. Calls to getProtectionParameter() will return a KeyStore.PasswordProtection object that contains the password that was recovered during the KeyStore object construction. The method will throw a NullPointerException if any of the type, file, or protection parameter arguments are null . It will also throw an IllegalArgumentException if the file argument does not exist , does not refer to a regular file, or the protection parameter argument is not an instance of KeyStore.CallbackHandlerProtection or KeyStore.PasswordProtection .
A simple example would probably help here!
Try It Out: Using KeyStore.Builder
This example shows the use of the first newInstance() method on the KeyStore.Builder class. In this case, I am just using it to encapsulate a keystore with a password for a particular entry, although you could imagine if I was dealing with a personal credential file such as PKCS #12, which you will look at next , the password could be for the keystore as well as the entries. It's quite short ”have a look at it and try running it.
package chapter8; import java.security.KeyStore; /** * Basic example of use of KeyStore.Builder to create an object that * can be used recover a private key. */ public class JCEKSStoreBuilderExample { public static void main(String[] args) throws Exception { KeyStore store = JCEKSStoreEntryExample.createKeyStore(); char[] password = "storePassword".toCharArray(); // create the builder KeyStore.Builder builder = KeyStore.Builder.newInstance( store, new KeyStore.PasswordProtection( JCEKSStoreEntryExample.keyPassword)); // use the builder to recover the KeyStore and obtain the key store = builder.getKeyStore(); KeyStore.ProtectionParameter param = builder.getProtectionParameter( Utils.END_ENTITY_ALIAS); KeyStore.Entry entry = store.getEntry(Utils.END_ENTITY_ALIAS, param); System.out.println("recovered " + entry.getClass()); } }
Running the example produces the following message:
recovered class java.security.KeyStore$PrivateKeyEntry
indicating that the main driver has been able to recover the private key entry using the KeyStore.Builder object.
How It Works
In this case, all the work is being done in the main driver; the sample keystore being used is generated using the previous Try It Out ("Using KeyStore.setEntry()"), and then a KeyStore.Builder object is constructed using the keystore and the password for one of the entries. After that, the entry is recovered by first recovering the keystore using builder.getKeyStore() , and the protection parameter is recovered using builder.getProtectionParameter() . This then allows the example to recover the entry from the keystore.
A couple of minor notes: In this case, it doesn't matter what you pass to the builder to recover the protection parameter; you'll always get the same thing back ”even the string fred will work quite well. It would be a mistake to rely on this, though. There should be nothing to stop the code being invoked with a builder that might be more fully featured. The nice thing about this particular use of the builder is that rather than having to rely on a system property, or something similar, to get the password for a given entry to the method that needs to use it, the builder allows you to encapsulate both pieces of information in a manner that is also abstract enough that even if the underlying keystore changes quite radically , the code will continue to work.
With JDK 1.5 new versions of KeyStore.load() and KeyStore.store() were added to the KeyStore API. They differ from the older versions in two respects: They take only a single parameter, and they will throw an IllegalArgumentException if the parameter is not recognized.
The single parameter is of the type KeyStore.LoadStoreParameter , which is a marker interface with a single method on it: getProtectionParameter() . As you might expect by now, the getProtectionParameter() method returns an object of the type KeyStore.ProtectionParameter . As you can see, this allows KeyStore objects to be built on things other than streams but still forces them to fit the standard convention for how they are accessed. At this writing this is still quite a new idea, and it will be interesting to see what KeyStore provider writers do with it in the future.
The PKCS #12 format is defined in RSA Security's PKCS #12 standard and was primarily designed as a means of encoding personal credentials that consisted of private keys and certificates. It can be used in several ways that are based around the combination of two privacy modes and two integrity modes. The privacy mode can be one of password-based encryption (PBE), or through public key encryption. Likewise the integrity mode can be one of an HMAC based on a password and PBE or a digital signature. The format is important, as it is the most common way of dealing with private keys when you need to import them into another application such as a Web browser to introduce personal credentials or some other application that allows you to receive encrypted data or send signed information.
The encoding specified for the format is ASN.1 using the BER rules and the file structure is built around the PFX structure, which is defined as follows :
PFX ::= SEQUENCE { version INTEGER {v3(3)}(v3,...), authSafe ContentInfo, macData MacData OPTIONAL } MacData ::= SEQUENCE { mac DigestInfo, macSalt OCTET STRING, iterations INTEGER DEFAULT 1 }
The MacData structure is present only if PBE is used for the integrity mode of the PFX . It is very rare to see iterations set to its default value; normally it will be over 1,000.
The ContentInfo structure is defined in PKCS #7 as follows:
ContentInfo ::= SEQUENCE { contentType ContentType, content [0] EXPLICIT CONTENTS.&Type({Contents}{@contentType}) OPTIONAL }
which, in case you've forgotten, translates into English as: What you will find in the content field depends on what the value is in the contentType field. The type of the contentType field, ContentType , is derived from a set of OIDs and reduces to a restricted version of the following:
ContentType ::= OBJECT IDENTIFIER
You will have a closer look at ContentInfo in the next chapter when you read about Cryptographic Message Syntax (CMS), but for the sake of PKCS #12, two object identifiers are recognized in the contentType field of the ContentInfo structure contained in the PFX structure: data and signedData . These two object identifiers have the following definition:
pkcs-7 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 } data OBJECT IDENTIFIER ::= { pkcs-7 1 } signedData OBJECT IDENTIFIER ::= { pkcs-7 2 }
and which one you will find in the contentType field depends on whether the content of the PFX is integrity-protected using a password ”in which case it will be data , or integrity-protected using a digital signature ”in which case it will be signedData .
I will leave the signedData content type alone for the moment because you'll be looking at it in the next chapter and in practice you will only see PFX objects using both password-based integrity mode and password-based privacy mode. In any case, whether you encounter signedData , or just the data type the actual structure that contains the key and certificate information present in the file is an AuthenticatedSafe , which is defined as follows:
AuthenticatedSafe ::= SEQUENCE OF ContentInfo
And it is the encoding of the AuthenticatedSafe that is present in the content field of the relevant ContentInfo structure as an ASN.1 OCTET STRING . In the case of the password-based files, the relevant ContentInfo structure is the one contained in the PFX structure. The content field of each ContentInfo structure can contain either plaintext, password-encrypted data, or public key-encrypted data as OCTET STRING values based on the BER encoding of another structure ”the SafeContents .
The SafeContents structure and its associated structures are defined as follows:
SafeContents ::= SEQUENCE OF SafeBag SafeBag ::= SEQUENCE { bagId BAG-TYPE.&id ({PKCS12BagSet}) bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), bagAttributes SET OF PKCS12Attribute OPTIONAL } PKCS12Attribute ::= SEQUENCE { attrId ATTRIBUTE.&id ({PKCS12AttrSet}), attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId}) }
And as you might imagine, there is a range of bagId values that indicate the bagValue is an object containing a certificate, key, a PKCS #8 encrypted private key, or a CRL. As you can see, the PKCS12Attribute structure is pretty much the same as the Attribute structure you saw in Chapter 5, the only difference being a name change and a syntax update to reflect the removal of the ANY type from ASN.1 in 1994. There are two common attributes that appear in relation to SafeBag attributes, both of which are defined in PKCS #9: the "friendly name," which is a BMPString identifier associated with the object in the SafeBag , and the local key ID, which is an OCTET STRING associated with the key or certificate in the SafeBag the attribute is attached to.
You can recognize the friendly name and the local key ID by the following OIDs in the attrId field of the PKCS12Attribute structure:
pkcs-9-at-friendlyName OBJECT IDENTIFIER ::= {pkcs-9 20} pkcs-9-at-localKeyId OBJECT IDENTIFIER ::= {pkcs-9 21}
where pkcs-9 is defined as:
pkcs-9 OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 }
In the context of PKCS #12 the local key ID is the most important of these, because it is generally used to tell the program reading the file which end entity certificate is related to which private key.
So, a complete PFX is a loosely defined nested structure, commonly with the kind of layout you see in Figure 8-1. The definition affords a great deal of flexibility in implementation and this is where you start to run into difficulties ”if you think about it for a while, there are only some ways of constructing one of these files that will work with the Java KeyStore API, and many variations on the way.
Figure 8-1
As I hope you can see, to deal with the import and export issues you might run into with these files, it is a good idea to keep in mind some idea of how they function internally. The myriad of implementations out there often means the treating of PKCS #12 files as a "black box" does not always work.
So if you are using a keystore implementation of PKCS #12, where are the dragons lurking?
If you are using another tool to create them and then importing them into Java, the first point to be careful of is that, ideally , any keys in the imported file will have the friendly name attribute set, because some implementations rely on that to use as an alias name. It's certainly a lot easier to use one if it has a "friendly name" set; otherwise , the first thing you have to do is dump the aliases for the KeyStore object to find out what name the provider has assigned to it if it has been able to.
Usually PKCS #12 files you import, and ones you are planning to export, employ the same password for integrity checking as they do for encryption of individual entries in the AuthenticatedSafe . This is largely because PKCS #12 is meant for storing private credentials. In the case of the Bouncy Castle implementation, you dealt with this by ignoring individual entry passwords. Doing otherwise would have meant people would have unwittingly generated files they could not export. On the other hand, the JDK 1.5 implementation from Sun allows you to do this. This does not mean that either implementation is wrong ”but whether you want to take advantage of the features of either depends a lot on whether you are planning to export the files to other applications and what those applications will cope with.
You'll also find that storing multiple private keys or just trusted certificates in a PKCS #12 keystore might be problematic depending on the provider. Although the PKCS #9 friendly name attribute allows aliases to be added to certificates, not everyone supports this, with the result that alias information on lone certificates may well be lost, and they simply won't show up in the alias list for the keystore. The implementation might also refuse to store trusted certificates altogether. In any case, some of the non-Java applications you will need to import PKCS #12 files into may cope properly only with files that have a single private key and certificate chain.
Another issue you might find is how certificate chains get picked up from the PKCS #12 file. Some applications will happily reconstruct a certificate chain using the subject and issuer DNs in the certificates and whether the public key of one certificate validates another. Others insist on the presence of the AuthorityKeyIdentifier and SubjectKeyIdentifier extensions to correctly recover the chain. The safest bet when you are creating your own chains is to make sure both extensions are present in any certificates that aren't trust anchors as well ”they don't do any harm by being there.
A common problem you might encounter with a PKCS12 keystore is that not all software that claims to be able to read PKCS #12 files can read BER-encoded data. If you are using the Bouncy Castle PKCS12 implementation that does use BER, it may be necessary to transform the keystore file from BER to DER to get it to import. Fortunately, this is fairly easy to do, as the following code fragment shows:
ByteArrayOutputStream bOut = new ByteArrayOutputStream(); pkcs12Store .store(bOut, password); ASN1InputStream aIn = new ASN1InputStream(bOut.toByteArray()); bOut.reset(); DEROutputStream dOut = new DEROutputStream(bOut); dOut.writeObject(aIn.readObject()); byte[] derPKCS12 = bOut.toByteArray();
where derPKCS12 will end up being the DER encoding of the PKCS #12 PFX structure. If this is happening, it is unlikely the application will tell you that this is what the problem is ”if you start having "bizarre" password issues, the BER/DER issue is the most common cause.
Finally, you might find it simply impossible to read a PKCS #12 store that has been generated by another application. This is fairly unlikely now because the BC implementation is four years old and the Sun one is about the same, but if it does, I guess the best advice I can give is that it's time to start working with your favorite dump utility and asking around. The org.bouncycastle.asn1.util.ASN1Dump class is a good place to start, and it would be worth getting your hands on the source of a PKCS #12 implementation, such as that found in JDKPKCS12KeyStore.java in the org.bouncycastle.jce.provider package of the Bouncy Castle distribution.
Still, after all, for the most part the PKCS #12 implementations that are available under Java do the job. So that being said, let's have a look at an example.
Try It Out: Using a PKCS #12 Keystore
This example is very similar to the first one in this chapter ("Try It Out: Using a JKS Keystore"), only this time it is using a PKCS12 keystore type to store a trusted certificate entry and a private key and its associated certificate chain.
package chapter8; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.security.KeyStore; import java.security.cert.Certificate; import java.util.Enumeration; import javax.security.auth.x500.X500PrivateCredential; /** * Example of the creation of a PKCS #12 store */ public class PKCS12StoreExample { public static KeyStore createKeyStore() throws Exception { KeyStore store = KeyStore.getInstance("PKCS12", "BC"); // initialize store.load(null, null); X500PrivateCredential rootCredential = Utils.createRootCredential(); X500PrivateCredential interCredential = Utils.createIntermediateCredential( rootCredential.getPrivateKey(), rootCredential.getCertificate()); X500PrivateCredential endCredential = Utils.createEndEntityCredential( interCredential.getPrivateKey(), interCredential.getCertificate()); Certificate[] chain = new Certificate[3]; chain[0] = endCredential.getCertificate(); chain[1] = interCredential.getCertificate(); chain[2] = rootCredential.getCertificate(); // set the entries store.setCertificateEntry( rootCredential.getAlias(), rootCredential.getCertificate()); store.setKeyEntry( endCredential.getAlias(), endCredential.getPrivateKey(), null, chain); return store; } public static void main( String[] args) throws Exception { KeyStore store = createKeyStore(); char[] password = "storePassword".toCharArray(); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); store.store(bOut, password); store = KeyStore.getInstance("PKCS12", "BC"); store.load(new ByteArrayInputStream(bOut.toByteArray()), password); Enumeration en = store.aliases(); while (en.hasMoreElements()) { String alias = (String)en.nextElement(); System.out.println("found " + alias + ", isCertificate? " + store.isCertificateEntry(alias)); } } }
Running the example produces the following output:
found end, isCertificate? false found root, isCertificate? true
How It Works
There is not a lot to say about how this example works, but there are a few items of note.
The first is you will notice that the private key password is null . The reason is that with the Bouncy Castle implementation the password given when the keystore is saved is used both to generate the integrity check and to encrypt the data contained in the keystore.
The second issue to think about is what will happen if you are using JDK 1.5 and remove the specification of the BC provider. If you change the line in the main driver to just be
store = KeyStore.getInstance("PKCS12");
you will find the output changes to
found end, isCertificate? false
Because the parser in the JDK-provided implementation doesn't pick up the fact there is a certificate with the friendly name attribute set on it.
If you go one step further and change the use of KeyStore.getInstance() in the createKeyStore() method in the same fashion, you will find the example will throw an exception when it tries to add the certificate to the keystore. Comment that line out and you will find it throws an exception when it tries adding the key because the password is null . Change the lines adding the key entry to read
store.setKeyEntry( endCredential.getAlias(), endCredential.getPrivateKey(), "storePassword".toCharArray(), chain);
and you will find the example then works. Notice that I have used the same password as is used to save the KeyStore object. The Sun implementation will let you use different passwords for saving the key and saving the password. However, if you are creating the file for import into another application, you will normally find it will be unable to load the PKCS #12 file, because it will assume the password for protecting the keys is the same one used to create the HMAC integrity check.
As you will realize from the outline I gave of the PKCS #12 PFX format earlier, there is nothing in PKCS #12 that says the Sun implementation is not compliant compared to the Bouncy Castle implementation ”in some ways it is more so; it just happens to be different. As previously mentioned, you will find this is fairly common, especially when you introduce PKCS #12 files generated by other applications. Consequently, if you are using PKCS #12 files to pass credentials between applications, the first step is to make sure that the Java mechanisms you are using to load and save the files are compatible with the other applications you are using and that your use of the features of a particular Java implementation of PKCS #12 is restricted so that your users can reliably produce PKCS #12 files that will work across applications.
The JDK/JRE also comes with an application that can be very useful for examining keystores for the purposes of exporting keys, debugging, or just having a look at their contents. You will look at that now.
The keytool is a basic command-line tool for manipulating keystores. It provides a range of basic facilities for generation of keys with version 1 certificates, export and import of X.509 certificates, as well as the ability to produce certification requests .
You can find the keytool command in the bin directory of your Java distribution ”the same place as the java command.
The keytool command syntax follows the pattern of
keytool command options
where command is the command you are trying to get the keytool to perform and options is one or more options appropriate to the command . The keytool command can also pass options directly to the underlying JVM it is running in. If you need to do this, the option to use is -J , which has the syntax Jjava_option , where java_option is one of the interpreter options you can pass to the java command when you are executing a class file.
General Command Options
The following options apply to all keytool commands, other than -printcert , which can be used independently of a keystore:
In addition, the -v option can also be passed to most commands to print extra information.
Commands and Their Options
Most of the important options that can be used with commands have default values. If -alias is not provided, its value is considered to be mykey ; if -keyalg is not provided, its value is considered to be DSA ; if -keysize is not given it defaults to 1,024; and the default validity for certificate generation when -validity is not given is 90 days.
The keytool will also derive signature algorithms from the private key being used if no signature algorithm is provided using the -sigalg option. In the case of a DSA private key, the algorithm will always be "SHA1withDSA," and in the case of an RSA key, the algorithm will default to "MD5withRSA."
Keeping the default option values in mind, the commands available with the keytool and their individual options are as follows:
If the -keyalg option is not provided, a DSA private key will be generated. If the -keysize option is not provided, the key will be 1,024 bits in size . If sigalg is not provided, an algorithm will be derived from the type of the private key. The dname value is meant to be an X.500 name and is used to define both the issuer and the subject of the certificate. If the dname value is not provided, the keytool will prompt for one. If keypass is not present, the keytool will prompt for one; if the user simply presses , then the key will have the same password as the keystore. The certificate will be valid for the number of days indicated by the -validity option; otherwise, the certificate will be valid for 90 days.
If a certificate chain, or a single certificate, representing a certification reply is provided, the keytool will attempt to validate the chain before using it, and if the -trustcacerts option is given, the JVM's collection of CA certificates will be included in the set of possible trust anchors. If the certificate chain validates , it will replace the old one associated with the private key associated with the alias name alias . If validating the chain requires accepting a new trust anchor, the key tool will prompt the user to see whether this is really what is wanted. In the event the -noprompt option has been provided, the user will not be prompted and the new certificate representing the trust anchor for the imported certification reply will be accepted as trusted.
If the input to the import is a single certificate and the alias specified does not exist as an entry in the keystore, the keytool will assume you are trying to import a trusted certificate. If the certificate is not self-signed, the keytool will first try to validate the certificate using a self-signed certificate it knows about, including the JVM's collection of CA certificates if the -trustcacerts option is specified. If the certificate is self-signed, or otherwise unrecognized, the user will be prompted as to how to proceed ”unless -noprompt has been specified, in which case the new certificate will be accepted.
As you might imagine, you should not do these without a lot of thought. The consequences of accepting a bogus trust anchor could be quite far-reaching. Doing so may put you in the position where you accept a certificate path as valid when you should, in fact, reject it.
By default, this command just prints the MD5 fingerprints for the certificates present in each entry. If -v is specified, then more details are printed about each entry; otherwise, if -rfc is specified instead, each certificate found is printed in PEM format.
You probably noticed in the discussion of the -import command that the JVM has its own idea of what CAs it will trust. Take a look at that file now as a way of starting to make some use of the keytool.
Important |
Note the examples that follow assume you have correctly set up your command path so that you can simply enter keytool as a command without having to specify the full directory path in front of it. Make sure your environment is properly set up before you go further. |
In addition to the java.security file that lives in JDK_HOME / jre/lib/security , or JRE_HOME / lib/security depending on which type of distribution you are looking at, there is a also file called cacerts .
Change the directory so you are in the same directory as the java.security file. Providing you haven't changed the password on the cacerts file, you can have a look it by running the command:
keytool -list -keystore cacerts -storepass changeit
What you will see represents all the certificates your JVM is prepared to use as a trust anchor when you include -trustcacerts in the -import command. As you will see in Chapter 10, the cacerts file is also used by the SSL API, among others.
As it is just a regular JKS file, you can also use the keytool command to import your own trusted certificates into cacerts ”something you might want to do to add your own trust anchors, by running the command:
keytool -import -alias trust -file trust.cer -keystore cacerts -storepass changeit
where trust is the alias you want to assign to your new trust anchor and trust.cer is a file containing either the ASN.1 binary or PEM encoding of your trust anchor.
As you can see, the default password is changeit ”in this case, if you are going to be relying on the con ents of the cacerts file for path validation, a good piece of advice. If you are shipping a Java application that derives its security from the path validation that happens due to the cacerts file, be it using the default contents or through trust anchors you have added yourself, leaving the cacerts file with its default password may provide an opening for making mischief that someone may find impossible to resist. Don't forget you can use the -storepasswd command to do this. I'll say it once again.
Important |
If you rely on the integrity of the JVM's cacerts file to help secure your application, change the file's password to something other than the default. |
At this point it would be worth getting some experience with some of the more in-depth uses of the keytool command, but the cacerts file is hardly a good thing to be experimenting on. You will have a look at how you can to set yourself up and do so now.
By default the keytool works on a file with the name .keystore , which the command options that are used for generating keys and importing data will create for you if it does not already exist. The .keystore file normally resides in whatever your system considers to be your home directory. In this case, you will avoid using the default file by specifying a filename on the command line. Apart from the fact it means you will not overwrite anything you should not by mistake, which could be as bad, if not worse , than damaging your cacerts file, it will also allow you to have a look at some of the example KeyStore objects you have generated by saving them to disk and using the keytool command.
Generating Some Sample Keystore Files
This utility class uses the KeyStore generations methods you used in two previous Try It Outs: "Using a JKS Keystore" and "Using a PKCS #12 Keystore" to create two files using the keytool command.
package chapter8; import java.io.FileOutputStream; import java.security.KeyStore; /** * Create some keystore files in the current directory. */ public class KeyStoreFileUtility { public static void main( String[] args) throws Exception { char[] password = "storePassword".toCharArray(); // create and save a JKS store KeyStore store = JKSStoreExample.createKeyStore(); store.store(new FileOutputStream("keystore.jks"), password); // create and save a PKCS #12 store store = PKCS12StoreExample.createKeyStore(); store.store(new FileOutputStream("keystore.p12"), password); } }
Create a temporary directory to experiment in, change your directory to it, and run the class. Use your favorite directory list command and you will find the utility class has created two files ”one called keystore.jks that contains a JKS keystore and one called keystore.p12 that contains a PKCS12 keystore.
Try It Out: Using Some Keytool Commands
Having created the files, first try to list the contents of both keystores using the command
keytool -list -keystore keystore.jks -storepass storePassword
and
keytool -list -keystore keystore.p12 -storetype PKCS12 -storepass storePassword
The output for the keystore.jks will start something like the following:
Keystore type: jks Keystore provider: SUN Your keystore contains 2 entries
and the output for the keystore.p12 will start something like this instead:
Keystore type: PKCS12 Keystore provider: SunJSSE Your keystore contains 1 entry
If you remember from the PKCS12StoreExample program, the keystore actually has a certificate in it as well that the SunJSSE PKCS12 implementation does not pick up. You'll have a look at what to do about this when I describe what is going on.
Now try adding a key to the JKS keystore using -genkey using the following:
keytool -genkey -alias eric -keystore keystore.jks -storepass storePassword
As you have not provided the -dname option, the command will prompt you for input leading to an exchange that might look like the following, where what you might type is in bold:
What is your first and last name? [Unknown]: Eric Echidna What is the name of your organizational unit? [Unknown]: Monotremes What is the name of your organization? [Unknown]: The Legion of the Bouncy Castle What is the name of your City or Locality? [Unknown]: Melbourne What is the name of your State or Province? [Unknown]: Victoria What is the two-letter country code for this unit? [Unknown]: AU Is CN=Eric Echidna, OU=Monotremes, O=The Legion of the Bouncy Castle, L=Melbourne, ST=Victoria, C=AU correct? [no]: yes Enter key password for (RETURN if same as keystore password): newKey
If you execute the list command again on keystore.jks , you will now see the following header on the output:
Keystore type: jks Keystore provider: SUN Your keystore contains 3 entries
And the rest of the output lists the fingerprints for eric , end , and root . If you add a -v to the command, you will also see that the new certificate for the key entry eric is self-signed with the subject and issuer DNs being set to what you entered previously.
One last thing to try: exporting a certificate from the PKCS12 store and importing it into the JKS one. First you need to export a certificate ”in this case, the one associated with key entry end :
keytool -export -alias end -rfc -file end.pem -keystore keystore.p12 -storetype PKCS12 -storepass storePassword
which should produce the following output:
Certificate stored in file
Then you import the file into keystore.jks with
keytool -import -alias new -file end.pem -keystore keystore.jks -storepass storePassword
In this case, because the certificate won't be recognized as trusted, you'll get prompted with the certificate details and asked whether you are willing to trust the certificate. The resulting interaction will look something like the following:
Owner: CN=Test End Certificate Issuer: CN=Test Intermediate Certificate Serial number: 1 Valid from: startTime until: expiryTime Certificate fingerprints: MD5: 16 bytes of hex... SHA1: 20 bytes of hex... Trust this certificate? [no]: yes Certificate was added to keystore
indicating the certificate was added successfully. Note that this means that if you now do a list on keystore.jks , you will see that the entry now has been added as a trusted certificate entry ”in some circumstances not something to be taken lightly.
How It Works
In some respects, things work here because they pretty well follow the manual. However, there are some interesting things going on that aren't in the manual and might not be immediately obvious, and there are also a few other details that should be kept in mind, so you will look at those.
The initial attempt at listing the PKCS12 keystore showed you that you were using the JDK-provided PKCS12 implementation, so it did not pick up the certificate entry. In theory all you should have to do is add a -provider option giving you something like:
keytool -list -keystore keystore.p12 -storetype PKCS12 -storepass storePassword -provider org.bouncycastle.jce.provider.BouncyCastleProvider
telling the keytool to use the Bouncy Castle provider instead of the SunJSSE one. Try it; you will see it does not work. For some reason the keytool will use the SunJSSE only if you specify PKCS12 ” especially inconvenient if you are using JDK 1.4, where the SunJSSE implementation is read-only. I'm not sure why this particular quirk exists. It would be a great relief if it didn't, but it has been a problem long enough for us at Bouncy Castle to create two workarounds: PKCS12-DEF , which uses the Sun X.509 certificate factory, and BCPKCS12 , which uses the Bouncy Castle one. I recommend using PKCS12-DEF , as it gets around some compatibility issues that some of the Java tools have when it comes to dealing with non-Sun provider X.509 certificates. In any case, if you try instead
keytool -list -keystore keystore.p12 -storetype PKCS12-DEF -storepass storePassword
you will see the following:
Keystore type: PKCS12-DEF Keystore provider: BC Your keystore contains 2 entries
showing that the Bouncy Castle provider is now being used instead and the certificate entry is now being recognized. If you're using another provider that offers the PKCS12 type, you will probably find they have similar workarounds.
The next point is in your -genkey example. In this case, you have not specified a key algorithm, key size, or a length of time for the self-signed certificate to be valid. The result is that you have produced a DSA key of 1,024 bits with a certificate signed using "SHA1withDSA" and an expiry date of 90 days. You could have changed this by including options like -keyalg , -keysize , -sigalg , and -validity , to change the key algorithm, key size, signature algorithm used, or the lifetime of the certificate.
Naturally, the certificate is self-signed. If you wanted to get it validated by one of your trust anchors, or perhaps someone else's, you would need to use -certreq to generate a PKCS #10 certification request for you. After that, you could process the certification request using the methods you saw in Chapter 6 producing a CertPath , write it out in "PKCS7" format, and then use the -import command to replace the self-signed certificate with the certificate chain you just constructed .
Finally, have a look at the contents of the end.pem . You will see it looks like just it should ”a PEMencoded certificate. You can probably see that if you needed to import trust anchors from other applications, or generators of your own, it is fairly easy to do.
There are some other tools and features of JDK/JRE that also make use of keystore files: the jarsigner tool and the Java policy mechanisms. I won't go into detailed discussion about them here, as they are well documented in the Java tools document set and they are not immediately relevant to the topic of this book, but it is worth being aware of their existence, so I will give a brief outline so you have a place to start if you decide to do further reading.
The jarsigner is used to sign Java archive files (JAR) and to verify the signatures, if any, that are attached to them. The basic command syntax for the two modes is as follows :
jarsigner options jar-file key_entry_alias
to sign a JAR file and
jarsigner -verify options jar-file
to verify the signatures attached to one. You can find the jarsigner in the same bin directory you found the keytool command.
I'll leave it as an exercise to look at the provided documentation for the full list of options, but in terms of its keystore usage, the jarsigner also defaults to using the .keystore file in your home directory and supports the options -keypass , -keystore , -storepass , and -storetype , which have exactly the same meaning as they do with the keytool command.
You can see how this command could be useful. In the case of a cryptographic provider such as Bouncy Castle, it is the signature on the JAR file that is used to tell the JDK that the service provider it is being presented with is one of those authorized for use with the JCE. You can use it for shipping signature protected data as well as class files, and the ability to sign JAR files also forms the basis of some of the security features that the Java policy mechanisms make possible.
The JVM has a system policy file as well as a notion of a user policy file. The system policy file, called java.policy , can be found in the lib/security directory along with the cacerts file and the java.security file. The user policy file is called .java.policy and can be found in the system's idea of what the user's home directory is.
Policy files can be manipulated using the policytool , which is also in the same bin directory as the jarsigner , or by using a regular text editor. Policy files are used by the JVM's policy provider, a class that's responsible for seeing that the conditions laid out by the policy files are followed. There is extensive documentation on policy files and the policytool in the JDK documentation set, but briefly , a policy configuration file is simply a keystore entry followed by one or more grant entries.
In respect to signed code, the keystore entry tells the policy provider where to find the keystore that will be used for verifying the signatures on the JAR files. It has the following syntax:
keystore " keystore_url ", " keystore_type ", " keystore_provider "; keystorePasswordURL " password_url ";
where keystore_type and keystore_provider are optional ”although the keystore_type is required if the keystore_provider is specified. If keystore_type is left out, the policy provider will default to the return value of KeyStore.getDefaultType() .
The grant entry can then start with an optional signedBy clause as in:
grant signedBy "eric" { permission_list... }
granting the permissions detailed in permission_list to the class files that come from a source signed by the key entry with the name eric from the keystore file described by the keystore entry.
A lot more than this can go in a grant entry; however, you get the idea. You can do more to lock down a Java application than just change the password on the cacerts file. Properly used the Java policy mechanisms can go a long way in helping secure a Java application.
In this chapter, you looked at the use of the KeyStore class as well as its associated tool, the keytool . In addition to that, you also looked at the PKCS #12 format, which is often used for transferring private keys between applications, as well as some of the implementation issues that surround it.
Over the course of this chapter you have learned the following:
Finally, you should also have some idea how work you've done in previous chapters can be used in conjunction with the keytool , as well as be aware of some of the other JVM features and tools that make use of keystore files.
You're now at the point where you can make use of symmetric cryptography, asymmetric cryptography, MACs, digests, and digital signatures, as well as create and validate certificates in addition to being able to store credentials securely and export them if required. As it happens, just as there are standard mechanisms for transferring credentials, there are also standard protocols for defining messages containing encrypted and signed data. One of the most common of these is Cryptographic Message Syntax, or CMS, which is also used as the basis for securing other forms of messaging, such as e-mail, through S/MIME. In the next chapter, you look at example of how CMS and S/MIME can be used in Java and how processing of the CMS protocol ties in with the topics already covered.
1. |
What available keystore types support the storing of symmetric keys? |
|
2. |
What is one important thing to do if you are relying on the integrity of the JVM's cacerts file to help secure your application? |
|
3. |
You have imported a PKCS #12 file into a Java application but there doesn't appear to be an alias for the key entry you are looking for, or it is just appearing as some random string of hex. What is most likely to be the issue with the PKCS #12 file? |
|
4. |
You have generated a PKCS #12 file using a Java application you have written, but you are unable to import it into another third-party application. What are the three most likely problems with the PKCS #12 file you have generated? |
|
5. |
Using the keytool command, the org.bouncycastle.openssl.PEMReader class, the Utils class, and the Chapter 6 examples "Try It Out: Creating a Certificate from a Certification Request" and "Try It Out: Writing a CertPathM" as helpers, show how to create a certificate request using the keytool for a keystore key entry, create a PKCS7 -encoded certificate chain in response to the request, and update the key entry with the chain. |
Answers
1. |
What available keystore types support the storing of symmetric keys? Of the standard Java keystore types, the JCEKS supports storing symmetric keys. The Bouncy Castle provider also supports symmetric key storage with the BKS and UBER KeyStore type. |
2. |
What is one important thing to do if you are relying on the integrity of the JVM's cacerts file to help secure your application? Change the default password! |
3. |
You have imported a PKCS #12 file into a Java application, but there doesn't appear to be an alias for the key entry you are looking for, or it is just appearing as some random string of hex. What is most likely to be the issue with the PKCS #12 file? There is no "friendly name " attribute attached to the SafeBag containing the private key. If the file does import and you see a hex string, chances are it will be the value of the local key ID. Some applications still insist and having the "friendly name" attribute provided in order to successfully import a PKCS #12 file, so this can sometimes be a cause of failure as well. |
4. |
You have generated a PKCS #12 file using a Java application you have written, but you are unable to import it into another third-party application. What are the three most likely problems with the PKCS #12 file you have generated? The first one is that the PKCS #12 store you are using may have been saved as a BER file, but the application can only import DER files ”you'll need to convert the file. The second one is the PKCS #12 store you are using may allow you to have an integrity password that is different from the privacy password, and the application can deal only with files that have the same password for both integrity and privacy. The third, and last, one is that the application may expect the certificates, other than the trust anchor, making up the validation path for the key's certificate to have the AuthorityKeyIdentifier and SubjectKeyIdentifier extensions present, and if it cannot find them, it is unable to build the validation path and accept the key as usable. |
5. |
Using the keytool, the org.bouncycastle.openssl.PEMReader class, the Utils class, and the Chapter 6 examples "Try It Out: Creating a Certificate from a Certification Request" and "Try It Out: Writing a CertPath" as helpers, show how to create a certificate request using the keytool for a keystore key entry, create a PKCS7 encoded certificate chain in response to the request, and update the key entry with the chain. Most of the code for this you can cut and paste, but I'll reproduce one solution here. The first thing I'll do is provide the Java code and then just go through the command sequence. Here's the Java code for parsing the request and creating the certificate response. It reads the request from a file called pkcs10.req and generates the certificate path making up the response in PKCS7 format in a file called pkcs7.pth . package chapter8; import java.io.*; import java.math.BigInteger; import java.security.cert.*; import java.util.*; import javax.security.auth.x500.X500PrivateCredential; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.x509.X509V3CertificateGenerator; import org.bouncycastle.x509.extension.*; /** * Example showing the processing of a PEM encoded PKCS #10 encoded request * in a file called "pkcs10.req". A PKCS7 certificate path is generated as a * response in the file "pkcs7.pth". * * The certificate and its chain will be valid for 50 seconds. */ public class CertReqSolution { public static void main(String[] args) throws Exception { // create the CA certificates X500PrivateCredential rootCredential = Utils.createRootCredential(); X500PrivateCredential interCredential = Utils.createIntermediateCredential( rootCredential.getPrivateKey(), rootCredential.getCertificate()); // parse the request PEMReader pRd = new PEMReader( new InputStreamReader( new FileInputStream("pkcs10.req"))); PKCS10CertificationRequest request = (PKCS10CertificationRequest)pRd.readObject(); // get our validation certificate X509Certificate caCert = interCredential.getCertificate(); X509V3CertificateGenerator certGen = new X509V3CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); certGen.setIssuerDN(caCert.getSubjectX500Principal()); certGen.setNotBefore(new Date(System.currentTimeMillis())); certGen.setNotAfter(new Date(System.currentTimeMillis() + 50000)); certGen.setSubjectDN(request.getCertificationRequestInfo().getSubject()); certGen.setPublicKey(request.getPublicKey("BC")); certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); // provide some basic extensions and mark the certificate // as appropriate for signing and encipherment certGen.addExtension( X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert)); certGen.addExtension( X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure( request.getPublicKey("BC"))); certGen.addExtension( X509Extensions.BasicConstraints, true, new BasicConstraints(false)); certGen.addExtension( X509Extensions.KeyUsage, true, new KeyUsage( KeyUsage.digitalSignature KeyUsage.keyEncipherment)); // create the chain List chain = Arrays.asList( new Certificate[] { certGen.generateX509Certificate( interCredential.getPrivateKey(), "BC"), interCredential.getCertificate(), rootCredential.getCertificate() }); // create the CertPath CertificateFactory fact = CertificateFactory.getInstance("X.509", "BC"); CertPath path = fact.generateCertPath(chain); // write it out FileOutputStream fOut = new FileOutputStream("pkcs7.pth"); fOut.write(path.getEncoded("PKCS7")); fOut.close(); } } There's nothing new in this code, although if you do find some of it confusing, it would be worth having another look at the examples and descriptions in Chapter 6 before going any further. Compile up the code into your class hierarchy and you're ready to proceed. Now to use it with the keytool ”I've used test.jks as the keystore file here so that the commands avoid touching your .keystore file. Remember, both the java code you are using and the keytool will be operating on the current directory. First, generate a key to use, responding appropriately to the questions the keytool prompts you with: keytool -genkey -alias testKey -keystore test.jks -storepass testStore -keypass testKey Next, generate the certification request into the file pkcs10.req : keytool -certreq -alias testKey -keystore test.jks -storepass testStore -keypass testKey -file pkcs10.req Next, run the Java program to read the request and generate the response: java -cp your_class_hierarchy chapter8.CertReqSolution where your_class_hierarchy is wherever you compiled the class file to. Finally, import the certificate response: keytool -import -alias testKey -keystore test.jks -storepass testStore -keypass testKey -file pkcs7.pth You will be prompted to determine whether you want to trust the root certificate. Respond with a yes and you're done. Do a keytool -list -v -keystore test.jks -storepass testStore and you should see the certificate path has now been added to the entry. |
Introduction