Generating and consuming a dynamic X.509 token using a stacked JAAS login module

You can use the GenericSecurityTokenFactory SPIs to create X.509 tokens for use by the WS-Security runtime environment. These security tokens can be used for, but are not limited to, WSSAPIs and JAAS login modules.

About this task

When a GenericSecurityTokenFactory SPI is used to create an X.509 token that does not contain XML, the token can only be emitted by the X.509 generator and consumed by the X.509 consumer. Therefore, an X.509 token is considered a simple token, which is only intended for use by its respective token-specific generator or consumer. An X.509 token cannot be emitted by the GenericSecurityTokenLoginModule.

The GenericSecurityTokenFactory provides several SPIs that you can use to create X.509 tokens that can be emitted with the X509GenerateLoginModule or consumed by the X509ConsumeLoginModule. X.509 tokens that are created using a GenericSecurityTokenFactory SPI contain public and/or private keys that can be used to sign or encrypt an outbound message or decrypt or verify the signature of an inbound message.

You might want to use this type of token:
  • If you need to dynamically change the signing key for a message. For example, the X509GenerateCallbackHandler requires that the signing key be hardcoded at configuration time. If you need to override this hardcoded value, you can code a JAAS login module that is stacked over X509GenerateLoginModule such that the login module puts a simple X.509 token in the shared state. The X509GenerateLoginModule then uses the private key that is specified in the simple X.509 token to override the one configured in the callback handler.
  • If you need to allow multiple signature verifying certificates that do not appear in the SOAP message. These messages would include certificates that are referenced by attributes such as KEYID and X509IssuerSerial. The WS-Security engine does not dynamically resolve certificates that do not appear in the SOAP message. The certificate must be hardcoded in the X509ConsumeCallbackHandler, and you can only configure one certificate in the callback handler. If you choose to implement code that resolves the certificate yourself, you can put this code in a JAAS login module that is stacked over X509ConsumeLoginModule, and then put a simple X.509 token in the shared state that contains the desired public certificate. The X509ConsumeLoginModule then uses the public certificate that is specified in the simple X.509 token to override the one configured in the callback handler.

After an X.509 token is created, the public and private key in the token cannot be modified. Therefore, you must determine the type of token you want to create, and then issue commands similar to the ones specified in the following steps to create your token and JAAS login module.

Procedure

  1. Create a simple X.509 token

    Decide the type of simple X.509 token you want to create, and issue commands similar to one of the following series of commands.

    1. Create a simple X.509 token with a public certificate.
      import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
      import com.ibm.websphere.wssecurity.wssapi.token.X509Token;
      import java.security.cert.X509Certificate;
      ...
      
      X509Certificate publicCert = null;
      
      GenericSecurityTokenFactory factory = GenericSecurityTokenFactory.getInstance();
      
      //implement code to obtain the public certificate
      
      X509Token x509 = factory.getSimpleX509PublicToken(publicCert);
    2. Create a simple X.509 token with a private and optional public key.
      import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
      import com.ibm.websphere.wssecurity.wssapi.token.X509Token;
      import java.security.Key;
      import java.security.cert.X509Certificate;
      ...
      
      Key privateKey = null;
      X509Certificate publicCert = null;
      
      GenericSecurityTokenFactory factory = GenericSecurityTokenFactory.getInstance();
      
      //implement code to obtain the private key
      //optionally implement code to obtain the public certificate
      
      X509Token x509 = null;
      try {
       x509 = factory.getSimpleX509PrivateToken(publicCert , privateKey);
      } catch (WSSException ex) {
       //throws an exception if privateKey is null
      }
  2. Create a JAAS login module that can be stacked on top of X509GenerateLoginModule or X509ConsumeLoginModule.
    package test.tokens;
    import com.ibm.websphere.wssecurity.wssapi.token.GenericSecurityTokenFactory;
    import com.ibm.websphere.wssecurity.wssapi.WSSUtilFactory;
    import java.util.Map;
    
    import javax.security.auth.Subject;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.login.LoginException;
    import javax.security.auth.spi.LoginModule;
    
    import com.ibm.websphere.wssecurity.wssapi.token.X509Token;
    import java.security.KeyStore;
    import java.security.cert.X509Certificate;
    
    public class MyX509LoginModule implements LoginModule {
      //For the sake of readability, this login module does not
      //protect against all NPE's
    
      private Map _sharedState;
      private Map _options;
      private CallbackHandler _handler;
    
      public void initialize(Subject subject, CallbackHandler callbackHandler,
                  Map<String, ?> sharedState, Map<String, ?> options) {
    
        this._handler = callbackHandler;
        this._sharedState = sharedState;
        this._options = options;  
      }
    
      public boolean login() throws LoginException {
    
        GenericSecurityTokenFactory factory = null;
        WSSUtilFactory utilFactory = null;
        try {
          factory = GenericSecurityTokenFactory.getInstance();
          utilFactory = WSSUtilFactory.getInstance();
        } catch (Exception e) {
          throw new LoginException(e.toString());
        }
    
        X509Token x509 = null;
        try {
          x509 = getX509Token(factory, utilFactory);
        } catch (Exception e) {
          throw new LoginException("Exception: "+e.toString());
        }
    
        //if generating (signing/encrypting):
        factory.putGeneratorTokenToSharedState(this._sharedState, x509);
    
        //if consuming (decrypting/signature verification):
        factory.putConsumerTokenToSharedState(this._sharedState, x509);
    
        return true;
      }
    
      public X509Token getX509Token (GenericSecurityTokenFactory factory, 
    				 WSSUtilFactory utilFactory) throws Exception{
        //This sample uses a sample keystore
        final String KEYSTORE = "c:/WebSphere/AppServer/profiles/AppSrv01/etc/ws-security/samples/dsig-sender.ks";
        final String KEYSTORE_PASS = "client";
        final String ALIAS = "soaprequester";
        final String ALIAS_PASS = "client";
        final String KEYSTORE_TYPE = "jks";
    
        X509Certificate publicKey = null;
        java.security.Key privateKey = null;
        java.security.cert.Certificate cert = null;
        X509Token x509 = null;
    
        //if you need to get the inbound token so that you can do some processing 
        //to determine which certificate to obtain, get then OMElement for the inbound token
        Map wssContext = utilFactory.GetWSSContext(this._handler);
        org.apache.axiom.om.OMElement element = utilFactory.getProcessingElement(wssContext);
    
        //Open the keystore
        java.security.KeyStore keystore = utilFactory.getKeyStore(KEYSTORE_TYPE, KEYSTORE, KEYSTORE_PASS.toCharArray());
    
        //Get the entry from the keystore
        KeyStore.PasswordProtection pwdProtection = null;
        if (ALIAS_PASS != null) {
          pwdProtection = new KeyStore.PasswordProtection(ALIAS_PASS.toCharArray());
        }
        KeyStore.Entry entry = keystore.getEntry(ALIAS, pwdProtection);
    
        //Get the public and/or private key from the entry
        if (entry instanceof KeyStore.PrivateKeyEntry) {
          //entry is a private key
          KeyStore.PrivateKeyEntry pkentry = (KeyStore.PrivateKeyEntry)entry;
          cert = pkentry.getCertificate();
          privateKey = pkentry.getPrivateKey();
        } else if (entry instanceof KeyStore.TrustedCertificateEntry) {
          //entry is a public key
          KeyStore.TrustedCertificateEntry tcentry = (KeyStore.TrustedCertificateEntry)entry;
          cert = tcentry.getTrustedCertificate();           
        }
        if ((cert != null) && (cert instanceof X509Certificate)) {
          publicKey = (X509Certificate)cert;
        } else {
          throw new LoginException("Certificate is not X509Certificate");
        }
    
        x509 = factory.getSimpleX509Token(publicKey, privateKey);
    
        if (x509 == null) {
          throw new LoginException("X509Token is null");
        }
    
        return x509;
      }
    
    }
  3. Create a JAAS login configuration.
    Note: This step assumes that you are stacking on X509GenerateLoginModule to generate a token. If you want to stack on X509ConsumeLoginModule to consume a token, you must adjust the steps to accommodate this variation.
    1. In the administrative console, go to Security > Global security.
    2. Under Authentication, go to Java Authentication and Authorization Service > System logins.
    3. Click New, and under Alias, enter test.generate.x509.
    4. Under JAAS login modules, click New, and under Module class name, enter test.tokens.MyX509LoginModule. Select Use login module proxy, and click OK.
    5. Click New, and under Module class name, enter com.ibm.ws.wssecurity.wssapi.token.impl.X509GenerateLoginModule. Click OK.
    6. Click JAAS - System logins in the breadcrumbs to return to the JAAS system logins page.
  4. Configure your X.509 token generator to use the new JAAS configuration.
    Note: This step assumes that you are stacking on X509GenerateLoginModule to generate a token. If you want to stack on X509ConsumeLoginModule to consume a token, you must adjust the steps to accommodate this variation.
    1. In the administrative console, open the bindings configuration that you want to change.
    2. Select WS-Security > Authentication and protection.
    3. Under Authentication tokens, select the outbound X.509 token that you want to change.
    4. Under JAAS login, select test.generate.x509.
    5. Click OK, then Save.
  5. Restart the application server to apply the JAAS configuration changes.
  6. Test the service.