Custom Key Managers in JSSE

来源:互联网 发布:网络翻译招聘 编辑:程序博客网 时间:2024/06/05 00:07

JSSE provides the SSL capability to Java. The reference implementation is bootstrapped into JDK versions 1.4 and above. The implementation provides the crypographic services that covers the requirements in most simple cases. It includes a X509 Key manager and a X509 Trust manager for key management and trust management of X509 certificates respectively. The key manager is used during the SSL handshake to select a certificate that best identifies the client to the SSL service. The default Key manager provided in the implementation uses the certificate request attributes - key type (RSA/DSA/etc) and the preferred issuers - to select a key entry from the keystore. If there are multiple key entries that match the attributes, the first one is presented to the server. This scheme works fine if the application is interacting with one SSL service, but it fails if the application is interacting with multiple SSL services each identifying the client differently.

For example, lets say , the application is communicating with a FTPS site and a webservice - each needing a different client certificate. The application needs to present the service with the required certicate. The default key manager will present the first certificate that matches the certificate-request attributes. If both services prefer the same key types and issuer DNs - which is very likely within a given organization, the default key selection will select the first key entry in the key store that matches the creteria and cause one of the hand shakes to fail. A custom key manager with a more "resource-aware" key selection scheme is required in this case. The JSSE manual does broach the subject of customizing key managers and trust managers, but the documentation is somewhat arcane or atleast not very demonstrative in its intent (- IMHO, ofcourse!). This article demonstrates the implementation of a custom key manager using two schemes

  • Using a custom provider
  • Initializing the SSL Context with a custom key manager.

Custom Providers


Subclasses of java.security.Provider are registered with the JVM to provide security services. Security services include key factories, digital signature algorithms, key generation algorithms, keystore creation and management , etc. A provider can provide one or more cryptographic services. To implement a custom key manager, we register a provider that only provides the key management service. The key management interface defined by JSSE is javax.net.ssl.KeyManagerKeyManagers are created by the factory - javax.net.ssl.KeyManagerFactory based on the default algorithm defined in the system property ssl.KeyManagerFactory.algorithm. With no customization, the Sun JVM uses the default key manager algorithm - "SunX509" which is provided by the sun provider - com.sun.net.ssl.internal.ssl.Provider.

To use a custom key management scheme for the SSL handshake in an application, the following needs to be done -

  1. Define a Key selection scheme. Implement javax.net.ssl.X509KeyManager with the required key selection scheme.
  2. Define a custom Key Manager SPI (Service provider interface), that creates the custom key manager during the JSSE lifecycle.
  3. Define the main class of the provider package. Create a subclass of Provider with the properties describing the service set to the SPI above.
  4. Deploy the provider package in the classpath of the application.
  5. Deploy the provider in the application using dynamic registration. (Or deploy it in the JVM by changing security.properties to include the provider)
  6. Set the system property ssl.KeyManagerFactory.algorithm to the name of the provider implemented.

The above steps are described in a somewhat more general manner in the JCA guide. The following sections use the provider mechanism to create a custom key manager.

Implementing the Key manager

The customization in the key management is in the selection of the certificates only. Hence, we can simply contain the default key manager in a custom key manager and delegate most of the key management to the default key manager. The scheme of key selection is based on the name of the host which which the SSL handshake is taking place. The key manager is initialized with a java.util.Map containing the host names and the corresponding key identifier (alias). Hostnames with no key alias will not be presented with a client certificate. Key selection is not done for hosts whose names are not found in the map. The key selection is for such hosts are delegated to the default key manager.

The bulk of the implementation will be in the chooseClientAlias method of the X509KeyManager interface. The rest of the methods have a cursory implementation where the call is simply delegated to the contained key manager.

  public class CustomKeyManager implements X509KeyManager  {      private X509KeyManager defaultKeyManager;      private Properties serverMap;      public CustomKeyManager(X509KeyManager defaultKeyManager, Properties serverMap)      {          this.defaultKeyManager = defaultKeyManager;          this.serverMap = serverMap;      }      public String chooseClientAlias(String[] keyType, Principal[] issuers, Socket socket)      {          SocketAddress socketAddress = socket.getRemoteSocketAddress();          String hostName = ((InetSocketAddress)SocketAddress).getHostName().toUpperCase();          String alias = null;          if(serverMap.containsKey(hostName))          {              alias = serverMap.getProperty(hostName.toUpperCase());              if(alias != null &&                   alias.length() ==0)              {                  alias = null;              }          }          else          {              alias = defaultKeyManager.chooseClientAlias(keyType, issuers, socket);          }          return alias;      }      .      .      .  }  

The key manager is initialized with a "real" X509 key manager and a table containing the server-key mapping. The chooseClientAlias obtains the server name from the socket. The key alias is looked up in the table for the server. If the server name is found in the table, the corresponding alias is returned. If the alias is empty, a null reference is returned so that no certificate is presented to the server during the handshake.

Implementing the SPI

JSSE initializes the key manager using the SPI that implements javax.net.ssl.KeyManagerSpi. (The next section will how how it finds the Spi). The SPI that initializes the key manager is called CustomKeyManagerSpi. The KeyManagerSpidefines a number of "engine" methods. We will only implement two of these methods - engineInit and engineGetKeyManagers. The former does most of the initialization while the latter returns an instance of the key manager.

The key manager is created with a default X509KeyManager and a map containing the server names and key aliases. The implementation uses two custom system properties -

  • alt.keymanager.alg : The default algorithm for key management provided in the cryptographic provider. For Sun JVMs, this can be set to "SunX509".
  • alt.server_key_map.file: The absolute path to the property file containing the server - key mapping.

The server map file contains entries with the server name as the key and the key alias as the value. If a server does not have a key alias, no client certificate is presented to the server during the hand shake. The excerpt below shows two servers - serverA and serverB. Server B does not require client certs -

# Key mapping file. # Entries contain the server names and the corresponding client cert## Server A is requires the key in serverA.keyEntry2  serverA = serverA.keyEntry2# Server B does not need any key  serverB =  

The engineInit reads the two system properties and creates the required Properties and the X509KeyManager. The Properties containing the key-host mapping and the X509KeyManager provided by the bootstrapped cryptographic provider is used for creating the CustomKeyManager.

      ..      ..      public void engineInit(KeyStore ks, char[] password)            throws KeyStoreException, NoSuchAlgorithmException,  UnrecoverableKeyException      {        // VVVVVVV Section I VVVVVVV        String defaultAlg = System.getProperty("alt.keymanager.alg");        if(defaultAlg == null ||                defaultAlg.trim().length() == 0)        {            throw new NoSuchAlgorithmException("The system property alt.keymanager.alg "+                    "should be set to the JVM's default keymanagment algorithm");        }        String keyMapFile = System.getProperty("alt.server_key_map.file");        // ^^^^^^^ Section I ^^^^^^^        // VVVVVVV Section II VVVVVVV        Properties serverMap = new Properties();        FileInputStream inputStream = null;        try        {            inputStream = new FileInputStream(new File(keyMapFile));            serverMap.load(inputStream);        }        catch(Exception e)        {            throw new UnrecoverableKeyException("Cannot read key map file "+keyMapFile);        }        finally        {            // close the input stream            .            .            .        }        // ^^^^^^^ Section II ^^^^^^^        // Get the key manager factory for the default algorithm.         KeyManagerFactory factory = KeyManagerFactory.getInstance(defaultAlg);        factory.init(ks, password);        // Get the first X509KeyManager in the list        KeyManager[] keyManagers = factory.getKeyManagers();        if(keyManagers ==  null ||         keyManagers.length == 0)        {            throw new NoSuchAlgorithmException("The default algorithm :"+                    defaultAlg+" produced no key managers");        }        X509KeyManager x509KeyManager = null;        for(int i=0;i<keyManagers.length; i++)        {            if(keyManagers[i] instanceof X509KeyManager)            {                x509KeyManager = (X509KeyManager)keyManagers[i];                break;            }        }        if(x509KeyManager == null)        {            throw new NoSuchAlgorithmException("The default algorithm :"+                    defaultAlg+" did not produce a X509 Key manager");        }                    this.keyManager = new CustomKeyManager(x509KeyManager, serverMap);    }    public void engineGetKeyManagers()    {        return new KeyManager[] { this.keyManager};    }

The "Section I" of the code excerpt above, retrieves the system properties that refer to the platform provided key manager algorithm and the config file containing the server - key mapping. The "Section II" of the code creates a Propertiesinstance from the config file. The rest of the code obtains the default key manager provided by the platform and creates an instance of CustomKeyManager.

The engineGetKeyManagers, returns the only key manager created by the SPI in the engineInit method.

Implementing the Provider

In the previous sections, we actually implemented a "Cryptographic provider package" (in the JSSE parlance).The Provider is the "main" class of the package through which the JSSE engine deciphers the type of cryptographic service provided and invokes the corresponding SPI. Lets say the provider package is called "custom" , it implements only the Key management function. The main class is really short and simple -

  public final class CustomProvider extends Provider  {      public CustomProvider()      {          super("custom",1.0, "Provider for  custom key manager");          put("KeyManagerFactory.custom", "mypackage.CustomKeyManagerSpi");      }  }  

The property used to indicate the cryptographic service is "KeyManagerFactory" with the algorithm name as the qualifier. We chose to use "custom" as the provider name and the algorigthm name. Hence, the property "KeyManagerFactory.custom" is set to the SPI we implemented above.The Cryptography architecture document provides the naming convention for the cryptographic service.

Deploying the Provider package

Deploying the provider package involves -

  1. Adding the provider package to the classpath.
  2. Adding the provider to the provider list - this can be done by editing the security.properties for the JRE and adding the entry for the provider in the or using the API Security.addProvider() to register dynamically.
  3. Set all the required system properties
    • ssl.KeyManagerFactory.algorithm should be set to the "custom" so that the our custom provider is invoked.
    • alt.keymanager.alg set to the platform's default key management algorithm ("SunX509" for Sun provided JVMs).
    • alt.server_key_map.file set to the config file
  4. Set the keystore and truststore properties as required by the JSSE provider

Resources

  1. JShell Provider : This is a fully implemented provider based on the schem above. You can download the provider from here.

Limitations

  • This implementation cannot differentiate services on the same host. The creterion for key selection is based on host name only.

Initializing SSLContext with a Custom key manager


Custom providers can be used when the application cannot be changed to use a custom Key manager. But if the application creates its own SSLContext, then the key manager can be set up at the time the SSL context is being created. See the code excerpt below

  public class MyApplication  {  .  .  .  public SSLContext createSSLContext()  {      SSLContext sslContext = SSLContext.getInstance("SSL");      sslContext.init(createCustomKeyManangers(),null,null);      return sslContext;  }   

SSLContext is a factory used to create SSLSocketFactorys. The sockets use the key manager and trust manager from the ssl context. In the code excerpt, the createCustomKeyManangers (not shown) returns an array of one element containing an instance of the custom key manager. The creation of the key manager is shown in the previous section.

Limitations

  • This approach works only if the SSL components in the application can be changed. It will not work with out-of-the box applications that cannot be changed.
原文: http://www.angelfire.com/or/abhilash/site/articles/jsse-km/customKeyManager.html

原创粉丝点击