Sunday, October 5, 2014

How to register a custom deployer in Carbon

Deployers in Axis2 are used to track the new file additions, file updates and file deletes. Writing an custom deployer is not a difficult task. A deployer is an implementation of org.apache.axis2.deployment.Deployer interface. You can find more details on how to write a deployer on : http://wso2.com/library/3708/

Once you write your custom deployer, you have to register it. Following  is how to register a custom deployer.

Add the deployer details to the component.xml file

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<component xmlns="http://products.wso2.org/carbon">
   <deployers>
       <deployer>
           <directory>sample</directory>
           <extension>xml</extension>
           <class>
               org.wso2.carbon.samples.deployer.CustomDeployer
           </class>
       </deployer>
   </deployers>
</component>

As the information given in the above configuration, a directory named ‘sample’ in the location ‘repository/deployment/server’ will be monitored. Whenever file with extension ‘xml’  is added, modified or removed deployed() method will be called.

Add the following entry to the <configuration> <instructions> list of the maven-bundle-plugin in your pox.xml file.

<Axis2Deployer>CustomDeployer</Axis2Deployer>

To be responsive for real time file additions, updates and deletions configuration ‘hotupdate’ in Axis2.xml has to be set to true.

<parameter name="hotupdate" locked="false">true</parameter>

You can find a sample code in : https://github.com/jsdjayanga/How-to-register-a-custom-deployer-in-Carbon

Friday, October 3, 2014

Internal synchronization of carbon kernel (Holding transports until the kernel get ready)

Starting up sequence of internal components of the carbon kernel is crucial for the kernel to operate  properly. Most importantly kernel should not start accepting external messages until it is ready to process messages. So it is needed to delay the activation of transports until the kernel is ready. Carbon kernel is made up of OSGi based components. According to the OSGi  standards there is no definite order in which the bundles get activated.


To overcome the this sequencing problem, In carbon kernel there is special component which handles this synchronization. ‘StartupFinalizerServiceComponent’ an OSGi component, which delays the activation of transports.


If all the required services are ready by the bundle activation time, then the ‘StartupFinalizerServiceComponent’ call the ‘completeInitialization()’ method which performs the initialization of transports. But if the required services are not available at the bundle activation time, transports will not get activated. And it will wait until the required services are available.


‘StartupFinalizerServiceComponent’ is a ServiceListener. Each time a service change happens serviceChanged() method is called, and this will check for the required service list. Once all the required services are available, it calls the ‘completeInitialization()’ and activate the transports.

Tuesday, September 30, 2014

How to register a servlet from a Carbon Component

There are three ways to register a servlet in carbon.
Specifying the servlet details in the web.xml file
Specifying the servlet details in the component.xml file
Registering the servlet with httpService in your component

You can find the sample code in : https://github.com/jsdjayanga/How-to-register-a-servlet-from-a-Carbon-Component

Specifying the servlet details in the web.xml file

Specifying the servlet details in the web.xml file is not recommended when working with the carbon framework, as it has less control over the servlet when it is directly specified in the web.xml

From the remaining two, neither is bad, its totally up to the developer to decide what is best for a given scenario.

Specifying the servlet details in the component.xml file

Specifying the servlet details in the component.xml file is the easiest way of doing this.

In this approach, you need to have your HttpServlet implementation. Then you have to specify the details about your servlet in the component.xml file. Following is how you should specify details


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<component xmlns="http://products.wso2.org/carbon">
   <servlets>
       <servlet id="SampleServlet">
           <servlet-name>sampleServlet</servlet-name>
           <url-pattern>/sampleservlet</url-pattern>
           <display-name>Sample Servlet</display-name>
           <servlet-class>
               org.wso2.carbon.samples.xmlbased.SampleServlet
           </servlet-class>
       </servlet>
   </servlets>
</component>

Once you restart the servlet, with your compiled .jar in the dropins directory (repository/components/dropins), all the request to the  http://ip:port/sampleservlet will be routed to your custom servlet (org.wso2.carbon.samples.xmlbased.SampleServlet).


Registering the servlet with httpService

Registering the servlet with httpService allows dynamically register and unregister services. This allows you to have more control over the availability of the servlet.

In this approach, you need to have your HttpServlet implementation. Then you have to register your servlet with the org.osgi.service.http.HttpService once your bundle get activated.


httpService.registerServlet("/sampledynamicservlet", new SampleDynamicServlet(), null, null);

Then onwards, requests received for the http://ip:port/sampledynamicservlet will be routed to your custom servlet.

In this approach you can unregister your servlet, this cause the http://ip:port/sampledynamicservlet to be unavailable.


httpService.unregister("/sampledynamicservlet");


Custom Authenticator for WSO2 Identity Server (WSO2IS) with Custom Claims

WSO2IS is one of the best Identity Servers, which enables you to offload your identity and user entitlement management burden totally from your application. It comes with many features, supports many industry standards and most importantly it allows you to extent it according to your security requirements.

In this post I am going to show you how to write your own Authenticator, which uses some custom claim to validate users and how to invoke your custom authenticator with your web app.

Create your Custom Authenticator Bundle

WSO2IS is based OSGi, so if you want to add a new authenticator you have to crate an OSGi bungle. Following is the source of the OSGi bundle you have to prepare.

This bundle will consist of three files,
1. CustomAuthenticatorServiceComponent
2. CustomAuthenticator
3. CustomAuthenticatorConstants

CustomAuthenticatorServiceComponent is an OSGi service component it basically registers the CustomAuthenticator (service). CustomAuthenticator is an implementation of org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator, which actually provides our custom authentication.


1. CustomAuthenticatorServiceComponent


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package org.wso2.carbon.identity.application.authenticator.customauth.internal;

import java.util.Hashtable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.service.component.ComponentContext;
import org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator;
import org.wso2.carbon.identity.application.authenticator.customauth.CustomAuthenticator;
import org.wso2.carbon.user.core.service.RealmService;

/**
 * @scr.component name="identity.application.authenticator.customauth.component" immediate="true"
 * @scr.reference name="realm.service"
 * interface="org.wso2.carbon.user.core.service.RealmService"cardinality="1..1"
 * policy="dynamic" bind="setRealmService" unbind="unsetRealmService"
 */
public class CustomAuthenticatorServiceComponent {

    private static Log log = LogFactory.getLog(CustomAuthenticatorServiceComponent.class);

    private static RealmService realmService;
    
    protected void activate(ComponentContext ctxt) {

        CustomAuthenticator customAuth = new CustomAuthenticator();
     Hashtable<String, String> props = new Hashtable<String, String>();
     
        ctxt.getBundleContext().registerService(ApplicationAuthenticator.class.getName(), customAuth, props);
        
        if (log.isDebugEnabled()) {
            log.info("CustomAuthenticator bundle is activated");
        }
    }

    protected void deactivate(ComponentContext ctxt) {
        if (log.isDebugEnabled()) {
            log.info("CustomAuthenticator bundle is deactivated");
        }
    }
    
    protected void setRealmService(RealmService realmService) {
        log.debug("Setting the Realm Service");
        CustomAuthenticatorServiceComponent.realmService = realmService;
    }

    protected void unsetRealmService(RealmService realmService) {
        log.debug("UnSetting the Realm Service");
        CustomAuthenticatorServiceComponent.realmService = null;
    }

    public static RealmService getRealmService() {
        return realmService;
    }

}


2. CustomAuthenticator

This is where your actual authentication logic is implemented


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package org.wso2.carbon.identity.application.authenticator.customauth;

import java.io.IOException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorFlowStatus;
import org.wso2.carbon.identity.application.authentication.framework.LocalApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.InvalidCredentialsException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.authenticator.customauth.internal.CustomAuthenticatorServiceComponent;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.core.UserStoreManager;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

/**
 * Username Password based Authenticator
 * 
 */
public class CustomAuthenticator extends AbstractApplicationAuthenticator
        implements LocalApplicationAuthenticator {

    private static final long serialVersionUID = 192277307414921623L;

    private static Log log = LogFactory.getLog(CustomAuthenticator.class);

 @Override
 public boolean canHandle(HttpServletRequest request) {
        String userName = request.getParameter("username");
  String password = request.getParameter("password");

  if (userName != null && password != null) {
   return true;
  }

  return false;
 }

    @Override
    public AuthenticatorFlowStatus process(HttpServletRequest request,
                                           HttpServletResponse response, AuthenticationContext context)
            throws AuthenticationFailedException, LogoutFailedException {

        if (context.isLogoutRequest()) {
            return AuthenticatorFlowStatus.SUCCESS_COMPLETED;
        } else {
            return super.process(request, response, context);
        }
    }

 @Override
 protected void initiateAuthenticationRequest(HttpServletRequest request,
   HttpServletResponse response, AuthenticationContext context)
   throws AuthenticationFailedException {

  String loginPage = ConfigurationFacade.getInstance().getAuthenticationEndpointURL();
  String queryParams = FrameworkUtils
    .getQueryStringWithFrameworkContextId(context.getQueryParams(),
      context.getCallerSessionKey(),
      context.getContextIdentifier());
  
  try {
      String retryParam = "";
            
            if (context.isRetrying()) {
                retryParam = "&authFailure=true&authFailureMsg=login.fail.message";
            }
      
            response.sendRedirect(response.encodeRedirectURL(loginPage + ("?" + queryParams))
                    + "&authenticators=" + getName() + ":" + "LOCAL" + retryParam);
  } catch (IOException e) {
   throw new AuthenticationFailedException(e.getMessage(), e);
  }
 }

 @Override
 protected void processAuthenticationResponse(HttpServletRequest request,
   HttpServletResponse response, AuthenticationContext context)
   throws AuthenticationFailedException {

  String username = request.getParameter("username");
  String password = request.getParameter("password");

  boolean isAuthenticated = false;

  // Check the authentication
  try {
   int tenantId = IdentityUtil.getTenantIdOFUser(username);
            UserRealm userRealm = CustomAuthenticatorServiceComponent.getRealmService()
                    .getTenantUserRealm(tenantId);
            
            if (userRealm != null) {
                UserStoreManager userStoreManager = (UserStoreManager)userRealm.getUserStoreManager();
                isAuthenticated = userStoreManager.authenticate(MultitenantUtils.getTenantAwareUsername(username),password);

                Map<String, String> parameterMap = getAuthenticatorConfig().getParameterMap();
                String blockSPLoginClaim = null;
                if(parameterMap != null) {
                    blockSPLoginClaim = parameterMap.get("BlockSPLoginClaim");
                }
                if (blockSPLoginClaim == null) {
                    blockSPLoginClaim = "http://wso2.org/claims/blockSPLogin";
                }
                if(log.isDebugEnabled()) {
                    log.debug("BlockSPLoginClaim has been set as : " + blockSPLoginClaim);
                }

                String blockSPLogin = userStoreManager.getUserClaimValue(MultitenantUtils.getTenantAwareUsername(username),
                        blockSPLoginClaim, null);

                boolean isBlockSpLogin = Boolean.parseBoolean(blockSPLogin);
                if (isAuthenticated && isBlockSpLogin) {
                    if (log.isDebugEnabled()) {
                        log.debug("user authentication failed due to user is blocked for the SP");
                    }
                    throw new AuthenticationFailedException("SPs are blocked");
                }
            } else {
                throw new AuthenticationFailedException("Cannot find the user realm for the given tenant: " + tenantId);
            }
  } catch (IdentityException e) {
   log.error("CustomAuthentication failed while trying to get the tenant ID of the use", e);
   throw new AuthenticationFailedException(e.getMessage(), e);
  } catch (org.wso2.carbon.user.api.UserStoreException e) {
   log.error("CustomAuthentication failed while trying to authenticate", e);
   throw new AuthenticationFailedException(e.getMessage(), e);
  }

  if (!isAuthenticated) {
   if (log.isDebugEnabled()) {
    log.debug("user authentication failed due to invalid credentials.");
            }

            throw new InvalidCredentialsException();
  }

  context.setSubject(username);
  String rememberMe = request.getParameter("chkRemember");

  if (rememberMe != null && "on".equals(rememberMe)) {
   context.setRememberMe(true);
  }
 }
 
 @Override
 protected boolean retryAuthenticationEnabled() {
  return true;
 }
 
 @Override
 public String getContextIdentifier(HttpServletRequest request) {
  return request.getParameter("sessionDataKey");
 }

 @Override
 public String getFriendlyName() {
  return CustomAuthenticatorConstants.AUTHENTICATOR_FRIENDLY_NAME;
 }

 @Override
 public String getName() {
  return CustomAuthenticatorConstants.AUTHENTICATOR_NAME;
 }
}

3. CustomAuthenticatorConstants

This is a helper class to just to hold the constants you are using in your authenticaator


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package org.wso2.carbon.identity.application.authenticator.customauth;

/**
 * Constants used by the CustomAuthenticator
 *
 */
public abstract class CustomAuthenticatorConstants {
 
 public static final String AUTHENTICATOR_NAME = "CustomAuthenticator";
 public static final String AUTHENTICATOR_FRIENDLY_NAME = "custom";
 public static final String AUTHENTICATOR_STATUS = "CustomAuthenticatorStatus";
}

Once you are done with these files, your authenticator is ready. Now you can build you OSGi bundle and place the bundle inside <CRBON_HOME>/repository/components/dropins.

*sample pom.xml file [3]

Create new Claim

Now you have to create a new claim in WSO2IS. To do this, log into the management console of WSO2IS and do the steps described in [1]. In this example, I am going to create new claim "Block SP Login".

So, goto configuration section of the management console click on "Claim Management", then select "http://wso2.org/claims" Dialect

Click on "Add New Claim Mapping", and fill the details related to your claim.

Display Name   Block SP Login
Description   Block SP Login
Claim Uri http://wso2.org/claims/blockSPLogin
Mapped Attribute (s)  localityName
Regular Expression  
Display Order   0
Supported by Default  true
Required   false
Read-only   false

Now, your new claim is ready in WSO2IS. As you select "Supported by Default" as true, this claim will be available in your user profile. So you will see this field appear, when you try to create a user, but this field in not mandatory as you didn't specify it as "Required"

Change application-authentication.xml

There is another configuration change you have to do, as it is going to take the claim name from the configuration file (CustomAuthenticator.java, 107-114). Add the information about the your new claim into repository/conf/security/application-authentication.xml


1
2
3
<AuthenticatorConfig name="CustomAuthenticator" enabled="true">
<Parameter name="BlockSPLoginClaim">http://wso2.org/claims/blockSPLogin</Parameter>
</AuthenticatorConfig> 

If you check the code CustomAuthenticator.java line,107-128. You will see in the processAuthenticationResponse, in addition to authenticating the user from the user store, it checks for the new claim,

So, this finishes the, basic steps to setup your custom authentication. Now you have to setup new Service Provider in WSO2IS and set you custom authentication to it. So that when ever your SP try to authenticate a user from WSO2IS, it will use your custom authenticator.

Create Service Provider and set the Authenticator

Follow the basic steps given in [2] to create a new Service Provider.

Then, goto, "Inbound Authentication Configuration"->"SAML2 Web SSO Configuration", and make the following changes,


1
2
3
4
5
6
Issuer* = <name of you SP>
Assertion Consumer URL = <http://localhost:8080/your-app/samlsso-home.jsp>
Enable Response Signing = true
Enable Assertion Signing = true 
Enable Single Logout = true
Enable Attribute Profile = true

Then goto, "Local & Outbound Authentication Configuration" section,
select "Local Authentication" as the authentication type, and select your authenticator, here "custom".

Now you have completed all the steps needed to setup your custom autheticator with your custom claims

You can now start the WSO2IS, and start using your service. Meanwhile, change the value of the "Block SP Login" of a particular user and see the effect.


[1] https://docs.wso2.com/display/IS500/Adding+New+Claim+Mapping
[2] https://docs.wso2.com/display/IS500/Adding+a+Service+Provider
[3] https://drive.google.com/file/d/0B25Kjdxz8EhCQktfdG5MYkFnTUk/view?usp=sharing


Friday, June 13, 2014

Mounting a remote repository (WSO2 GREG) to WSO2 ESB

WSO2 Governance Registry [1] is basically a metadata repository which basically helps to store and manage metadata.  WSO2 Enterprise Service Bus (WSO2 ESB) [2] is an integration middle-ware tool which is virtually capable of interconnecting ANYTHING.

There several ways of mounting a remote repository to a WSO2 product (In this case WSO2 EB). You can find more information on [3]. In this post I am trying to explain, how to mount a remote repository to WSO2 ESB via JDBC-based configuration.

In this approach you have to move the local DB of the WSO2 GREG to an external DB. So any change you do the registry will be reflected in the external DB. In this example I will be using a MYSQL database.

Moving WSO2 GREG repository to external DB
  1. Create a new database schema (regdb), a new user (wso2carbon) with password (wso2carbon) and grant all permissions to wso2carbon.
  2.  Change the data source details of WSO2_CARBON_DB in master-datasources.xml file, which is located in GREG_HOME/repository/conf/datasources/, with your DB information.
    eg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <datasource>
        <name>WSO2_CARBON_DB</name>
        <description>The datasource used for registry and user manager</description>
        <jndiConfig>
            <name>jdbc/WSO2CarbonDB</name>
        </jndiConfig>
        <definition type="RDBMS">
            <configuration>
                <url>jdbc:mysql://localhost:3306/regdb</url>
                <username>wso2carbon</username>
                <password>wso2carbon</password>
                <driverClassName>com.mysql.jdbc.Driver</driverClassName>
                <maxActive>50</maxActive>
                <maxWait>60000</maxWait>
                <testOnBorrow>true</testOnBorrow>
                <validationQuery>SELECT 1</validationQuery>
                <validationInterval>30000</validationInterval>
            </configuration>
        </definition>
    </datasource>
    
  3. Start the server with -Dseup argument
    eg:
    ./wso2server.sh -Dseup

    This will setup all the tables in the DB and all the initial configurations needed. And WSO2 GREG is now ready with external registry.

Mounting remote repository to WSO2 ESB
  1. Add a new data source to the master-datasources.xml file, which is located in ESB_HOME/repository/conf/datasources/. NOTE: This entry is exactly same as the record we entered in WSO2 GREG, except for the <name> and <jndiConfig>/<name>
    eg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <datasource>
        <name>WSO2_REG_DB</name>
        <description>The datasource used for registry and user manager</description>
        <jndiConfig>
            <name>jdbc/WSO2RegDB</name>
        </jndiConfig>
        <definition type="RDBMS">
            <configuration>
                <url>jdbc:mysql://localhost:3306/regdb</url>
                <username>wso2carbon</username>
                <password>wso2carbon</password>
                <driverClassName>com.mysql.jdbc.Driver</driverClassName>
                <maxActive>50</maxActive>
                <maxWait>60000</maxWait>
                <testOnBorrow>true</testOnBorrow>
                <validationQuery>SELECT 1</validationQuery>
                <validationInterval>30000</validationInterval>
            </configuration>
        </definition>
    </datasource>
    
  2. Add a new record <dbConfig> to registry.xml, which is located at ESB_HOME/repository/conf/
    eg:

    1
    2
    3
    <dbConfig name="wso2remoteregistry">
        <dataSource>jdbc/WSO2RegDB</dataSource>
    </dbConfig>
    
  3. Uncomment the <remoteInstance> and <mount> sections in the registry.xml file and update with the correct details.
    eg:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    <remoteInstance url="https://localhost:9443/registry">
        <id>instanceid</id>
        <dbConfig>wso2remoteregistry</dbConfig>
        <readOnly>false</readOnly>
        <enableCache>true</enableCache>
        <registryRoot>/</registryRoot>
        <cacheId>wso2carbon@jdbc:mysql://localhost:3306/regdb</cacheId>
    </remoteInstance>
    
    <mount path="/_system/config/nodes" overwrite="true">
        <instanceId>instanceid</instanceId>
        <targetPath>/_system/nodes</targetPath>
    </mount>
    

  4. Start the WSO2 ESB. If you are running both WSO2 GREG and WSO2 ESB on same machine, you will have to set port offsets on one of them.eg:
    ./wso2server.sh -DportOffset=2
Once you start the WSO2 ESB, you should be able to access the remote repository from the WSO2 ESB.

To verify this go to resource browser of the admin console of the WSO2 ESB, https://localhost:9445, which you can find on the following URL if you start with postOffset=2

Then browse resources,
  1. You should find the mounted remote repository in _system/config/nodes with a folder icon having a blue arrow in it
  2.  You should find the mounted remote repository details on _system/local/repository/components/org.wso2.carbon.registry/mount

References:
[1] http://wso2.com/products/governance-registry/
[2] http://wso2.com/products/enterprise-service-bus/
[3] https://docs.wso2.org/display/Governance460/Remote+Instance+and+Mount+Configuration+Details

Friday, May 23, 2014

Simple SecurePasswordVault in Java

There are some instances, you want to store your passwords in files to be used by the programs or scripts. But storing your passwords in plain text is not a good idea. Use the SecurePasswordVault to encrypt your passwords before storing and get it decrypted when you want to use it.

You can use the SecurePasswordVault described here to store any number of encrypted passwords. Passwords are stored as key value pairs.

Key - any name given by the user for the password
Value - encrypted password

SecurePasswordVault will create a file with the given name in the working directory if it doesn't exist. If a file exist then the information in that file will be read.

Passwords are encrypted using the MAC address of the network card. SecurePasswordVault will use the first network card MAC which is not the loop back interface. So the encrypted file can only be decrypted with that particular MAC address. If you want to reset the pass word details, just delete the password file and run the SecurePasswordVault.

You can download the sample code from the following Github repository
https://github.com/jsdjayanga/secure_password

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
package com.wso2.devgov;

import org.bouncycastle.util.encoders.Base64;

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.*;

/**
* Created by jayanga on 3/31/14.
*/
public class SecurePasswordVault {

    private static final int AES_KEY_LEN = 32;
    private static final int PASSWORD_LEN = 256;
    
    private static boolean initialized;
    private final String secureFile;
    private final byte[] networkHardwareHaddress;
    private Map<String, String> secureDataMap;
    private List<String> secureDataList;

    SecretKeySpec secretKey;

    public SecurePasswordVault(String filename, String[] secureData) throws IOException {

        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        initialized = false;
        secureFile = filename;
        networkHardwareHaddress = SecurePasswordVault.readNetworkHardwareAddress();
        secureDataMap = new HashMap<String, String>();

        this.secureDataList = new ArrayList<String>(secureData.length);
        Collections.addAll(secureDataList, secureData);

        byte[] key = new byte[AES_KEY_LEN];
        Arrays.fill(key, (byte)0);

        for(int index = 0; index < networkHardwareHaddress.length; index++){
            key[index] = networkHardwareHaddress[index];
        }

        secretKey = new SecretKeySpec(key, "AES");

        if (!isInitialized()){
            readSecureData(secureDataList);
            persistSecureData();
        }

        readSecureDataFromFile();
    }
    
    private boolean isInitialized(){
        if (initialized == true){
            return true;
        }else{
            File file = new File(secureFile);
            if (file.exists()){
                initialized = true;
                return initialized;
            }
        }
        return false;
    }

    private static byte[] readNetworkHardwareAddress() throws SocketException {
        Enumeration<NetworkInterface> networkInterfaceEnumeration = NetworkInterface.getNetworkInterfaces();
        if (networkInterfaceEnumeration != null){
            NetworkInterface networkInterface = null;
            while (networkInterfaceEnumeration.hasMoreElements()){
                networkInterface = networkInterfaceEnumeration.nextElement();
                if (!networkInterface.isLoopback()){
                    break;
                }
            }

            if (networkInterface == null){
                networkInterface = networkInterfaceEnumeration.nextElement();
            }

            byte[] hwaddr = networkInterface.getHardwareAddress();

            return hwaddr;
        }else{
            throw new RuntimeException("Cannot initialize. Failed to generate unique id.");
        }
    }

    private byte[] encrypt(String word) {
        byte[] password = new byte[PASSWORD_LEN];
        Arrays.fill(password, (byte)0);

        byte[] pw = new byte[0];

        try {
            pw = word.getBytes("UTF-8");

            for(int index = 0; index < pw.length; index++){
                password[index] = pw[index];
            }

            byte[] cipherText = new byte[password.length];

            Cipher cipher = null;
            try {
                cipher = Cipher.getInstance("AES/ECB/NoPadding");

                try {
                    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

                    int ctLen = 0;
                    try {
                        ctLen = cipher.update(password, 0, password.length, cipherText, 0);
                        ctLen += cipher.doFinal(cipherText, ctLen);

                        return cipherText;
                    } catch (ShortBufferException e) {
                        e.printStackTrace();
                    } catch (BadPaddingException e) {
                        e.printStackTrace();
                    } catch (IllegalBlockSizeException e) {
                        e.printStackTrace();
                    }
                } catch (InvalidKeyException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (NoSuchPaddingException e) {
                e.printStackTrace();
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }

        return null;
    }

    private String decrypt(byte[] cipherText) {
        byte[] plainText = new byte[PASSWORD_LEN];

        Cipher cipher = null;
        try {
            cipher = Cipher.getInstance("AES/ECB/NoPadding");

            try {
                cipher.init(Cipher.DECRYPT_MODE, secretKey);

                int plainTextLen = 0;
                try {
                    plainTextLen = cipher.update(cipherText, 0, PASSWORD_LEN, plainText, 0);

                    try {
                        plainTextLen += cipher.doFinal(plainText, plainTextLen);
                        String password = new String(plainText);
                        return password.trim();

                    } catch (IllegalBlockSizeException e) {
                        e.printStackTrace();
                    } catch (BadPaddingException e) {
                        e.printStackTrace();
                    }
                } catch (ShortBufferException e) {
                    e.printStackTrace();
                }


            } catch (InvalidKeyException e) {
                e.printStackTrace();
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        }

        return null;
    }

    public void readSecureData(List<String> secureDataList) throws IOException {
        BufferedReader bufferRead = new BufferedReader(new InputStreamReader(System.in));

        for(int index = 0; index < secureDataList.size(); index++){
            System.out.println("Please enter the value for :" + secureDataList.get(index));

            String value = new String(Base64.encode(encrypt(bufferRead.readLine())));
            secureDataMap.put(secureDataList.get(index), value);
        }
    }

    public String getSecureData(String key) {
        String value = secureDataMap.get(key);
        if (value != null){
            return decrypt(Base64.decode(value.getBytes()));
        }

        throw new RuntimeException("Given key is unknown. [key=" + key + "]");
    }

    private void readSecureDataFromFile() throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(secureFile));

        String line;
        while ((line = br.readLine()) != null){
            int dividerPoint = line.indexOf("=");
            if (dividerPoint > 0){
                secureDataMap.put(line.substring(0, dividerPoint), line.substring(dividerPoint + 1));
            }
        }
    }

    private void persistSecureData() throws IOException {
        FileWriter fileWriter = new FileWriter(secureFile);

        for(String key : secureDataMap.keySet()){
            fileWriter.append(key + "=" + secureDataMap.get(key) + "\n");
        }

        fileWriter.close();
    }
}