Tuesday, June 17, 2014

Secure passwords in Password Callback Handler using WSO2 Carbon Secure Vault


In WSO2 carbon products, password Callback handler class can be used to provide passwords needed for Rampart engine to build username tokens and create signatures when sending messages. Apache Rampart is the Axis2 module which providers WS-Security feature to Axis2 Web Services. You can find a detailed explanation of password callback from here.

Following is a sample callback handler class

public class PWCBHandler implements CallbackHandler {

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {

        for (Callback callback : callbacks) {
            WSPasswordCallback pwcb = (WSPasswordCallback) callback;
            String id = pwcb.getIdentifer();
            int usage = pwcb.getUsage();
            if (usage == WSPasswordCallback.USERNAME_TOKEN) {
                // Logic to get the password to build the username token
                if ("admin".equals(id)) {
                    pwcb.setPassword("admin");
                }
            } else if (usage == WSPasswordCallback.SIGNATURE || usage == WSPasswordCallback.DECRYPT) {
                // Logic to get the private key password for signature or decryption
                if ("client".equals(id)) {
                    pwcb.setPassword("apache");
                }
                if ("service".equals(id)) {
                    pwcb.setPassword("apache");
                }
            }
        }
    }
}





WSO2 Carbon is shipped with a Secure Vault implementation which is a modified version of synapse Secure Vault. It can be used to avoid the hard coding of password in above example and retrieve it from file in secured manner.

Following example will show you how to configure WSO2 Secure Vault for Password Callback Handler with WSO2 Identity Server 5.0.0


Step1 : Download the WSO2 Identity Server 5.0.0 from here 
Step2 : Create a config file named test_conf1.xml in <carbon_home>/repository/conf directory and add following text


<testconf>
   <module serverURL="local://services/" remote="false">
       <password>admin</password>
   </module> 
</testconf>

Step3Add following line to <carbon_home>/repository/conf/security/cipher-tool.properties file


testconf.module.password=test_conf1.xml//testconf/module/password,true

Step4 : Add following line to <carbon_home>/repository/conf/security/cipher-text.properties file


testconf.module.password=[admin]

Step5 : Go to <carbon_home>/bin directory and execute "sh ciphertool.sh -Dconfigure" command. Then it will ask you to enter the primary key store password. Type "wso2carbon" as the password

Step6 : Then test_conf1.xml file will be updated as follows


<<?xml version="1.0" encoding="UTF-8" standalone="no"?><testconf xmlns:svns="http://org.wso2.securevault/configuration">
   <module remote="false" serverURL="local://services/">
       <password svns:secretAlias="testconf.module.password">password</password>
   </module>
 </testconf>

you can see the cipher-text.properties file and the password should encrypted as follows


testconf.module.password=PFQC+qjKxmDePuiR5kSSTOx6suR48UKbDpcEEZ57TcXsHIlnP+I6E2ZXOBtZ91Fk+z3b8vWV84GB\nzn9q+ZQZ0XmdTUzNTMFMV/rpkT3OVhN9MUCjlHIORhcNMt9oWiVKaQ5tO2AmFg5IIqvG/FO51q3o\nx+L8a2sF3JH9G1m203s\=
Step7 : Following class can be used to resolve the password
/*
 * Copyright (c) 2006, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.sample.securevault;


import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.utils.CarbonUtils;
import org.wso2.securevault.SecretResolver;
import org.wso2.securevault.SecretResolverFactory;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class TestConf {
    private static final Log log = LogFactory.getLog(TestConf.class);
    private String password;
    private String serverURL;
    private String remote;
    public TestConf() {
        InputStream fileInputStream = null;
        String configPath = CarbonUtils.getCarbonHome()+ File.separator + "repository" + File.separator + "conf" +
       File.separator + "conf-test1.xml";
       File registryXML = new File(configPath);
        if (registryXML.exists()) {
            try {
                fileInputStream = new FileInputStream(registryXML);
                StAXOMBuilder builder = new StAXOMBuilder(fileInputStream);
                builder.setNamespaceURIInterning(true);
                OMElement configElement = builder.getDocumentElement();
                //Initialize the SecretResolver providing the configuration element.
               SecretResolver secretResolver = SecretResolverFactory.create(configElement, false);
                OMElement module = configElement.getFirstChildWithName(new QName("module"));
                if (module != null) {
                   //same entry used in cipher-text.properties and cipher-tool.properties.
                    String secretAlias = "testconf.module.password";
                  //Resolved the secret password.
                    if (secretResolver != null && secretResolver.isInitialized()) {
                        if (secretResolver.isTokenProtected(secretAlias)) {
                            password = secretResolver.resolve(secretAlias);
                       } else {
                           password = module.getFirstChildWithName(new QName("password")).getText();
                        }
                   }
                    serverURL = module.getAttributeValue(new QName("serverURL"));
                    remote = module.getAttributeValue(new QName("remote"));
               }
            } catch (XMLStreamException e) {
                log.error("Unable to parse conf-test1.xml", e);
            } catch (IOException e) {
               log.error("Unable to read conf-test1.xml", e);
            } finally {
                if (fileInputStream != null) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        log.error("Failed to close the FileInputStream, file : " + configPath);
                    }
                }
           }
        }
    }


    public String getPassword() {
        return password;
    }

    public String getServerURL() {
        return serverURL;
    }

    public boolean isRemote() {
        return Boolean.valueOf(remote);
    }


}




You can checkout the complete code from here 
Step8 : Use maven to build the files. (mvn clean install)

Go to target directory and copy org.wso2.samples.pwcb-1.0.0.jar file to <carbon_home>/repository/lib directory and start the server. Then you will ask to enter the key store  password. It is "wso2carbon"

Now you are done.