Code example: AuthenticationUserExitHandler API

Retrieve the owner PrivateKey and PublicKey pair for SAML token decryption by invoking the UserExitKeyPair API

Important: A compressed file with user exit code samples is available in the Members\resources\samples\userexits installation directory.
AuthenticationInboundService.java
            /*
 * *****************************************************************************
 * 
 * IBM Confidential
 * 
 * OCO Source Materials
 * 
 * 5725Q72
 * 
 * (C) Copyright IBM Corp. 2013, 2014
 * 
 * The source code for this program is not published or otherwise divested of its trade secrets, irrespective of what
 * has been deposited with the U.S. Copyright Office.
 * 
 * The sample contained herein is provided to you "AS IS".
 * 
 * It is furnished by IBM as a simple example and has not been thoroughly tested under all conditions. IBM, therefore,
 * cannot guarantee its reliability, serviceability or functionality.
 * 
 * This sample may include the names of individuals, companies, brands and products in order to illustrate concepts as
 * completely as possible. All of these names are fictitious and any similarity to the names and addresses used by
 * actual persons or business enterprises is entirely coincidental.
 * 
 * ******************************************************************************
 */
package com.ibm.b2b.test.comms.userexits.authentication;

import java.io.ByteArrayInputStream;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

import org.apache.commons.lang.Validate;
import org.w3c.dom.Element;

import com.ibm.b2b.userexits.spi.AuthenticationExitException;
import com.ibm.b2b.userexits.spi.AuthenticationUserExitHandler;
import com.ibm.b2b.userexits.spi.BusinessDocExitException;
import com.ibm.b2b.userexits.spi.BusinessDocument;
import com.ibm.b2b.userexits.spi.UserExitKeyPair;
import com.ibm.misc.BASE64Decoder;

/**
 * This is just a sample authentication service that can be used as a template to authenticate the certificate inbound
 * and then decrypt the SAML token by fetching the key pair for the given alias from MEG keystore.
 * 
 * @author Praveen S Rao
 * 
 */
public class AuthenticationInboundService implements AuthenticationUserExitHandler {

    private static final String SOURCECLASS = AuthenticationInboundService.class.getCanonicalName();

    private static final Logger LOGGER = Logger.getLogger( SOURCECLASS );

    /*
     * (non-Javadoc)
     * 
     * @see com.ibm.b2b.userexits.spi.AuthenticationUserExitHandler#invoke(com.ibm.b2b.userexits.spi.BusinessDocument)
     */
    public BusinessDocument invoke( BusinessDocument bdo ) throws AuthenticationExitException {

        Validate.notNull( bdo );

        final String sourceMethod = "invoke";

        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.entering( SOURCECLASS, sourceMethod, bdo.getTransactionID() );
        }

        try {

            sendCertificateForAuthentication( bdo.getSignCertificate() );

            decryptAndVerifySAMLToken( bdo );

        } catch ( Exception t ) {
            throw new AuthenticationExitException( t );
        }

        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.exiting( SOURCECLASS, sourceMethod, bdo.getTransactionID() );
        }

        return bdo;
    }

    private void decryptAndVerifySAMLToken( BusinessDocument bdo ) throws BusinessDocExitException {
        final String sourceMethod = "decryptAndVerifySAMLToken";
        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.entering( SOURCECLASS, sourceMethod );
        }

        PrivateKey decryptionKey = null;
        Certificate certToBeMatched = null;
        try {

            // extract SAML token from security element header. Have set actor as ebms here.
            Element encryptedSAMLAssertion = bdo.getSecurityElement( new QName(
                "urn:oasis:names:tc:SAML:2.0:assertion", "EncryptedAssertion" ), "ebms" );

            // get keypair for alias
            UserExitKeyPair keyPair = bdo.getOwnerKeyPair( "myalias" );

            if ( keyPair == null ) {
                throw new BusinessDocExitException( "Decryption keypair could not be found" );
            }

            BASE64Decoder base64Decoder = new BASE64Decoder();

            // get private key encoded data
            byte[] privateKeyData = base64Decoder.decodeBuffer( keyPair.getPrivateKey() );

            // get keyfactory instance
            KeyFactory kf = KeyFactory.getInstance( "RSA" );
            // create a pkcs8 key spec
            PKCS8EncodedKeySpec ks = new PKCS8EncodedKeySpec( privateKeyData );

            // generate private key from key factory and key spec
            decryptionKey = kf.generatePrivate( ks );

            // get certificate encoded data
            byte[] certData = base64Decoder.decodeBuffer( keyPair.getPublicCert() );

            // get certificate factory instance
            CertificateFactory cf = CertificateFactory.getInstance( "X509" );
            certToBeMatched = cf.generateCertificate( new ByteArrayInputStream( certData ) );

            // match recipient info
            matchRecipientInfo( encryptedSAMLAssertion, certToBeMatched );

            // do SAML token decryption
            Element decrytpedSAMLAssertion = doSAMLDecryption( encryptedSAMLAssertion, decryptionKey );

            // set the decrypted SAML token into the BDO so that backend can understand
            bdo.setSAMLAssertion( decrytpedSAMLAssertion );

        } catch ( Exception e ) {
            throw new BusinessDocExitException( e );
        } finally {
            // zeroise key data
            if ( decryptionKey != null ) {
                Arrays.fill( decryptionKey.getEncoded(), (byte) 0 );
            }
        }

        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.exiting( SOURCECLASS, sourceMethod );
        }
    }

    private Element doSAMLDecryption( Element encryptedSAMLAssertion, PrivateKey decryptionKey ) {
        final String sourceMethod = "doSAMLDecryption";
        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.entering( SOURCECLASS, sourceMethod );
        }

        // do saml decryption

        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.exiting( SOURCECLASS, sourceMethod );
        }

        return null;
    }

    private void matchRecipientInfo( Element encryptedSAMLAssertion, Certificate certToBeMatched )
        throws BusinessDocExitException {
        final String sourceMethod = "matchRecipientInfo";
        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.entering( SOURCECLASS, sourceMethod );
        }

        boolean recipientMatch = "1".equals( "1" );

        if ( !recipientMatch ) {
            throw new BusinessDocExitException( "RecipientInfo not found" );
        }

        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.exiting( SOURCECLASS, sourceMethod );
        }
    }

    private void sendCertificateForAuthentication( Certificate signCertificate ) {
        final String sourceMethod = "sendCertificateForAuthentication";
        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.entering( SOURCECLASS, sourceMethod );
        }

        // send certificate to a third party authentication store

        if ( LOGGER.isLoggable( Level.FINER ) ) {
            LOGGER.exiting( SOURCECLASS, sourceMethod );
        }
    }

}