Mini howto cookbook: Integrating Encap in a Java MIDlet
Abstract
This article describes a minimalistic approach of integrating Encap activation and authentication into a Java MIDlet. The Java MIDlet talks to a service provider server application that requires the user to log in using an OTP security token, which in this case is embedded into the Java MIDlet. Using embedded OTP is convenient for the user, secure, and cost efficient. The source code for a helper class is provided. This fits well into a scenario where the MIDlet GUI skeleton is already implemented.
Requirements
● The logo and name of the service provider configuration is assumed to be compiled into the Java MIDlet
● The service provider configuration is push, which in a normally is used to start the application automatically, and to pass information through the Java MIDlet to the authentication server. This makes it possible to use existing infrastructure for e.g. logging into an internet bank or similar.
● Java MIDP 2 development environment
Dependencies
The EncapHelper class has the following dependencies that must be available on the classpath in order to compile successfully:
Name | Description | Source |
msec-server-common-1.3.3.jar | Common constants used in Encap client and server, Java 1.1 | Encap as, www.encap.no |
msec-api-core-1.3.3.jar | Encap embedded client API, Java 1.1 | Encap as, www.encap.no |
msec-api-midp-1.3.3.jar | Encap embedded client API for Java MIDP-2, using kSOAP and IAIK crypto. | Encap as, www.encap.no |
ksoap2-j2me-core-2.1.0.jar | kSOAP for Java Microedition. | http://sourceforge.net/projects/ksoap2
|
jce-full-me-20080505.jar (iaik_jce_full_me.jar) | IAIK JCE ME - Crypto library (Basic, non-US version) | http://jce.iaik.tugraz.at/sic/Products/Core-Crypto-Toolkits/JCE-ME |
Furthermore the Encap helper requires the URL to the Encap activation/authentication server, and the listening SMS port. Contact Encap as for more information.
Scenarios
The following is a sample scenario that can be used for development and proof of concept.
● Download/installation: The user downloads and installs the MIDlet application with embedded Encap mSec libraries from some known URL that the developer has published.
● Activation: The user enters the Encap wizard for download/installation/activation from the service provider's web pages, skips the download/installation step in the wizard, starts the Java MIDlet with embedded Encap, selects Activation in the MIDlet, enters the activation code from the web page, and chooses PIN in the MIDlet.
● Login: The user starts the Java MIDlet with embedded Encap, selects "Login", enters user ID and password, enters Encap PIN, the MIDlet authenticates towards Encap server, receives OTP that it forwards to the service provider, and the service provider grants access to the protected page.
Classes and methods
The following classes include a minimalistic approach, i.e. they do not include processing of all events from the Encap mSec API, but just enough to make it work.
● EncapHelper – glue code called by the MIDlet that interfaces the the Encap mSec API.
● EncapHelperListener – the methods that the MIDlet must implement in order to receive events from the EncapHelper.
The mSec API will create new Thread when required to do network I/O, and pass thre result through the handle(Callback[] callbacks) method, in a similar style as for server side JAAS. The following describes the flow for the success scenarios.
Initialization
- The MIDlet calls the constructor of the EncapHelper with the listener to be notified by EncapHelper – in the following, we assume that the MIDlet implements the listener.
Activation
- The MIDlet calls the waitForStartMessage() method of EncapHelper that waits for incoming SMS from server.
- The MIDlet calls the getInitParams() method of EncapHelper with the received SMS as parameter.
- The EncapHelper calls the MIDlet's listener inputActivationCode() method, that should prompt the user for the activation code.
- The MIDlet calls the EncapHelper's continueWithActivationId() method with the activation code from the user as argument. This causes the Encap mSec API to contact the server to verify the activation code, and call the EncapHelper's handle() method with a PinCallback to request the user PIN.
- The EncapHelper calls the MIDlet's inputChoosePin() method to request the user to choose and confirm PIN.
- The MIDlet calls EncapHelper's activate() method with the user PIN as argument. This causes Encap mSec API to contact the server to complete activation, and call the EncapHelper's handle() method with a ResultCallback.
- The EncapHelper calls the MIDlet's activationSuccess() method which indicates that the mSec API has now completed activation of the embedded security token, which can furthermore be used for authentication.
Authentication
- The MIDlet calls the waitForStartMessage() method of EncapHelper that waits for incoming SMS from server.
- The MIDlet calls the getInitParams() method of EncapHelper with the received SMS as parameter.
- The EncapHelper calls the MIDlet's inputPin() method to request the user to choose and confirm PIN.
- The MIDlet calls EncapHelper's authenticate() method with the user PIN as argument. This causes Encap mSec API to contact the server to complete activation, and call the EncapHelper's handle() method with a ResultCallback.
- The EncapHelper calls the MIDlet's authenticationSuccess() method with an OTP as argument. The mSec API has now completed authentication with the embedded security token, which can furthermore be used for authentication. The OTP can be sent as authentication token towards the service provider. The OTP is present to provide compatibility with stand alone OTP devices, the user is already authenticated in the mobile channel.
Source code
EncapHelperListener.java
* Copyright (C) Arne Riiber 2010
* All rights reserved.
*/
package no.riiber.microedition.midlet;
/**
*
* @author arne
* @since Jul 7, 2009
* @since Jun 12, 2010 5:53:56 PM
*/
interface EncapHelperListener {
/**
* Activation failed with the specified symbolic message
* @param message The reason why activation failed
*/
void activationFailed(String message);
/**
* Activation success with the specified result
* @param result The result that can be displayed to user (if any)
*/
void activationSuccess(String result);
/**
* Authentication failed with the specified symbolic message
* @param message The reason why activation failed
*/
void authenticationFailed(String message);
/** Authentication success with result from server
*
* @param result (OTP to display or URL)
*/
void authenticationSuccess(String result);
/**
* Do input activation code from user
*
* @param length Number of characters.
* @param isNumeric If true, input digits only. If false, any characters.
*/
void inputActivationCode(int length, boolean isNumeric);
/**
* Do input PIN from user
*
* @param length Number of characters.
* @param isNumeric If true, input digits only. If false, any characters.
*/
void inputPin(int length, boolean isNumeric);
/**
* Do input from user to choose PIN (and confirm PIN)
*
* @param length Number of characters.
* @param isNumeric If true, input digits only. If false, any characters.
*/
void inputChoosePin(int length, boolean isNumeric);
}
EncapHelper.java
/*
* Copyright (C) Arne Riiber 2010
* All rights reserved.
*/
package no.riiber.microedition.midlet;
import encap.msec.client.api.Activater;
import encap.msec.client.api.Authenticater;
import encap.msec.client.api.MSec;
import encap.msec.client.api.StartParams;
import encap.msec.client.api.callback.ActivationIdCallback;
import encap.msec.client.api.callback.Callback;
import encap.msec.client.api.callback.PinCallback;
import encap.msec.client.api.callback.ResultCallback;
import encap.msec.client.platform.communication.MIDPCommunication;
import encap.msec.client.platform.crypto.MIDPCrypto;
import encap.msec.client.platform.storage.MIDPPersistentStorage;
import encap.msec.client.storage.Config;
import encap.msec.client.storage.IntegrityViolatedException;
import encap.msec.common.CommonConstants;
import javax.microedition.io.Connector;
import javax.wireless.messaging.BinaryMessage;
import javax.wireless.messaging.Message;
import javax.wireless.messaging.MessageConnection;
/**
* EncapHelper - The necessary glue to call Encap server for authentication
*
* @author arne
* @since Jul 3, 2009
*/
class EncapHelper implements encap.msec.client.api.callback.CallbackHandler {
private String smsPort = "16999"; // Contact Encap as for advice
private String APPLICATION_ID = "bankId";
private String url = "http://localhost:8080/mSecFront"; // Contact Encap as for advice
private Authenticater authenticater;
private Activater activater;
private EncapHelperListener encapHelperListener;
/** holds pin len + format for retry pin */
private Config config;
public EncapHelper(
EncapHelperListener encapHelperListener) throws IntegrityViolatedException {
//#debug
System.out.println(this.getClass());
this.encapHelperListener = encapHelperListener;
MIDPCommunication communication = new MIDPCommunication();
//communication.setClientVersion(clientVersion);
MIDPPersistentStorage persistentStorage = new MIDPPersistentStorage();
MIDPCrypto crypto = new MIDPCrypto();
this.authenticater = MSec.getAuthenticater(this, communication, persistentStorage, crypto);
this.activater = MSec.getActivater(this, communication, persistentStorage, crypto);
}
/**
* Call Encap server to get challenge
*
* @param command - Incoming message from server
*/
public void getInitParams(String command) {
//#debug
System.out.println("getInitParams(" + command+ ")");
StartParams startParams = extractParams(command);
//new StartParams(command);
startParams.setAppId(APPLICATION_ID);
startParams.setUrl(url);
String functionCmd = startParams.getCommand();
if (functionCmd.equals(CommonConstants.AUTH_CMD)) {
startParams.setSecurityObject(startParams.getSessionId());
String serviceProviderId = startParams.getServiceProviderId();
//#debug
System.out.println("Calling MSec API: authenticater.getInitParams(" + startParams + ")");
authenticater.getInitParams(startParams); // non-blocking
// Result passed in handle(callback)
} else if (functionCmd.equals(CommonConstants.ACTIVATE_CMD)) {
startParams.setSecurityObject(startParams.getSessionId());
String serviceProviderId = startParams.getServiceProviderId();
//#debug
System.out.println("Calling MSec API: activater.getInitParams(" + startParams + ")");
activater.getInitParams(startParams); // non-blocking
// Result passed in handle(callback)
} else {
// ignore
//#debug
System.out.println("Ignoring command: " + command);
}
}
/**
* Handle response from MSec API
*
* @param callbacks
* @throws java.lang.Exception
*/
public void handle(Callback[] callbacks) throws Exception {
//#debug
System.out.println("handle(" + callbacks[0] + ")");
if (callbacks[0] instanceof ActivationIdCallback) {
ActivationIdCallback callback = (ActivationIdCallback)callbacks[0];
this.config = callback.getConfig();
encapHelperListener.inputActivationCode(
config.getActivationCodeLength(),
CommonConstants.NUMERIC.equals(config.getActivationCodeFormat())
);
} else if (callbacks[0] instanceof PinCallback) {
PinCallback callback = (PinCallback) callbacks[0];
this.config = callback.getConfig();
if (callback.getCommand().equals(CommonConstants.ACTIVATE_CMD)) {
encapHelperListener.inputChoosePin(
config.getPinLength(),
CommonConstants.NUMERIC.equals(config.getPinFormat())
);
} else {
encapHelperListener.inputPin(
config.getPinLength(),
CommonConstants.NUMERIC.equals(config.getPinFormat())
);
}
} else if (callbacks[0] instanceof ResultCallback) {
ResultCallback resultCallback = (ResultCallback) callbacks[0];
if (resultCallback.getCommand().equals(CommonConstants.AUTH_CMD)) {
if (resultCallback.getServerResponse().getStatus() == CommonConstants.SUCCESS) {
String securityCode = new String(resultCallback.getServerResponse().getSecureObject());
encapHelperListener.authenticationSuccess(securityCode);
} else if (resultCallback.getServerResponse().getStatus() == CommonConstants.FAIL_RETRY) {
// no callback.getConfig() available
//encapHelperListener.authenticationRetryInputPin(...)
encapHelperListener.inputPin(
config.getPinLength(),
CommonConstants.NUMERIC.equals(config.getPinFormat())
);
} else {
// error
encapHelperListener.authenticationFailed(resultCallback.getServerResponse().getAdditionalInfo());
}
} else if (resultCallback.getCommand().equals(CommonConstants.ACTIVATE_CMD)) {
if (resultCallback.getServerResponse().getStatus() == CommonConstants.SUCCESS) {
encapHelperListener.activationSuccess(new String(resultCallback.getServerResponse().getSecureObject()));
} else {
// error
encapHelperListener.activationFailed(resultCallback.getServerResponse().getAdditionalInfo());
}
} else {
//#debug
System.out.println("Ignoring ResultCallback: " + callbacks[0]);
}
} else {
//#debug
System.out.println("Ignoring callback: " + callbacks[0]);
}
}
/**
* Continue activation with the specified activation code (activation ID).
*
* @param activationId Activation Code from user
*/
public void continueWithActivationId(String activationId) {
//#debug
System.out.println("continueWithActivationId(" + activationId + ")");
activater.continueWithActivationId(activationId);
}
/**
* Call this method from GUI to activate with user PIN
* @param pin
*/
void activate(byte[] pin) {
//#debug
System.out.println("activate(pin.length=" + new String(pin).length() + ")");
activater.activate(pin);
}
/**
* Call this method from GUI to authenticate with user PIN
* @param pin
*/
public void authenticate(byte[] pin) {
//#debug
System.out.println("authenticate(pin.length=" + new String(pin).length() + ")");
authenticater.authenticate(pin);
}
/**
* Wait for incoming message (SMS) from server
*
* @return Message from server
* @throws java.lang.Exception
*/
public String waitForStartMessage() throws Exception {
MessageConnection messageConnection = null;
try {
String addr = "sms://:" + smsPort;
//#debug
System.out.println("waitForStartMessage: Listening on "+ addr);
messageConnection = (MessageConnection) Connector.open(addr);
// block until message ready or connection closed
Message message = messageConnection.receive();
byte[] data = ((BinaryMessage) message).getPayloadData();
String startMessage = new String(data);
//#debug
System.out.println("waitForStartMessage: BinaryMessage received: " + startMessage);
return startMessage;
} catch (Exception e) {
System.err.println(e);
throw e;
} finally {
if (messageConnection != null) {
try {
messageConnection.close();
} catch (Exception e) {
}
}
}
}
//
/**
* An utility to extract parameters from an URL style encoded string.
* <p/>
* The parameters are encoded on format name1=value1&name2=value2 as is.
* The parameter name and value can not contain '=' or '&'.
*/
private static StartParams extractParams(String str) {
String pval; // parameter value
String pname; // parameter name
boolean finished = false;
StartParams start = new StartParams();
while (!finished) {
pname = str.substring(0, str.indexOf('='));
if (str.indexOf('&') != -1) {
// more parameters follow (a=1&b=2&...)
pval = str.substring(str.indexOf('=') + 1, str.indexOf('&'));
str = str.substring(str.indexOf('&') + 1);
} else {
// no more parameters (a=1&b=2)
pval = str.substring(str.indexOf('=') + 1);
finished = true;
}
if(pname.equals(CommonConstants.PARAM_CMD)){
start.setCommand(pval);
} else if(pname.equals(CommonConstants.PARAM_SESSION_ID)){
start.setSessionId(pval);
} else if(pname.equals(CommonConstants.PARAM_SPCODE)){
start.setServiceProviderId(pval);
} else if(pname.equals(CommonConstants.ACTIVATION_CODE_FORMAT)){
start.setActivationCodeFormat(pval);
} else if(pname.equals(CommonConstants.ACTIVATION_CODE_LENGTH)){
start.setActivationCodeLength(pval);
} else if(pname.equals(CommonConstants.PARAM_ACTIVATIONCODE)){
start.setActivationCode(pval);
} else if(pname.equals(CommonConstants.PARAM_ACTIVATIONCODE)){
start.setActivationCode(pval);
}
}
return start;
}
}
2010-06-13 Arne Riiber