Container Creation
Before being able to sign a transaction or generate an OTP with the HID Approve SDK, the mobile application has to provision its keys.
Typically, provisioning is triggered by the web application by invoking the HID authentication platform. The provisioning data returned by the server is received by the mobile application by scanning a QR code, or entered manually by the end user.
During provisioning, cryptographic keys designed to address the use cases above, are generated and stored securely. These keys are protected against anti-cloning (that is, they cannot be extracted and used outside the mobile device) and can be further protected with a password or fingerprint.
For further details, see:
-
The HID Approve Security White Paper (available upon request).
Typical Use Case
The mobile application provisions keys as follows:
- Create an instance of the Device (DeviceFactory.getDevice).
- Connection timeout
- Number of connection retries
-
Connection delegates for hostname verification and certificate trust verification (an instance of javax.net.ssl.X509TrustManager and javax.net.ssl.HostnameVerifier).
During this operation, the application provides the SDK with the required server connection configuration (class ConnectionConfiguration):
- The connection configuration can be changed later using Device.setConnectionConfiguration or Container.setConnectionConfiguration.
-
The ConnectionConfiguration values are optional.
- Create a key container (Device.createContainer).
- Activation code (activationCode) – generated by the HID authentication platform upon customer request (for example, if the end user is using the bank web application to activate a device, the web application requests the server to generate a code).
- If the specified policy used to protect the generated keys requires a password, the passed EventListener.onEventReceived callback will be invoked with a PasswordPromptEvent to request a mandatory password from the application during the activation process.
- Push ID (pushId) – device identifier provided by the notification service. This identifier is communicated to the server to allow it to send notifications.Note: If the Push ID value is unknown or not available at the time of activation, then this value should be left blank (empty string) and later updated on the server when the information is available.
- The device friendly name as displayed in the HID authentication platform (deviceFriendlyName).
- The container friendly name that could be displayed by the application (containerFriendlyName).
- After the container is created, it is possible to Change Password.
The default values are:
Property | Value |
---|---|
timeout | 30 sec |
retry | 0 |
trustManager | System |
hostnameVerifier | System |
The application passes an instance of ContainerInitialization to the method with the required data.
In the simplest use case, the following information is required:
The application can obtain the code by scanning a QR code, or any other means chosen by the integrator.
The application can customize:
At the end of provisioning, the device is provisioned with the keys.
// Get Device instance with default connection configuration
// context is specific to the platform. On Android, it is an instance of android.content.Context.
val connectionConfiguration = ConnectionConfiguration()
var currentDevice: Device? = null
try {
currentDevice = DeviceFactory.getDevice(context, connectionConfiguration)
} catch (ex: Exception) {
when(ex) {
is InvalidParameterException, is LostCredentialsException, is UnsupportedDeviceException, is InternalException -> {
ex.printStackTrace()
}
else -> throw ex
}
}
// From the device instance, we can now create a container
val containerInitialization = ContainerInitialization()
// QR code or payload of push notification
containerInitialization.activationCode = scanContent
// Identifier given by the push notification system
containerInitialization.pushId = pushId;
// The EventListener callback will be invoked for the app to provide a password to protect keys, in case the server enforces protection by a password.
// The password creation is app responsibility.
// It will also be used to inform the user about the progress of the container's creation.
class MyEventListener: EventListener {
override fun onEventReceived(event: Event?): EventResult? {
if (event is PasswordPromptEvent) {
// Password prompt message
// If your container is configured to be protected by a password, this is when the user will be requested to enter it through the PasswordPromptResult API
// EventResult.Code.Continue: operation can continue
// EventResult.Code.Cancel: operation is cancelled by the user.
// EventResult.Code.Abort: operation is aborted by the app
val prompt = PasswordPromptResult(EventResult.Code.Continue)
// Password policy is provided in the event in the password policy ( Refer to Protection Policy for more explanation ) . For example :
Log.d(LOG_TAG, "Event Received: PasswordPromptEvent - maxLowerCase : " + event.passwordPolicy.maxLowerCase)
Log.d(LOG_TAG, "Event Received: PasswordPromptEvent - minLowerCase : " + event.passwordPolicy.minLowerCase)
// If the password does not respect the policy, the createContainer will fail with an invalidPasswordException.
prompt.password = userPassword.toCharArray()
return prompt
}
else {
// Progress message
// the parameters array provide the state of the provisioning ( SDKConstants.PARAM_SYNCEVENT_MESSAGE ), and the percent fo progression ( SDKConstants.PARAM_SYNCEVENT_PERCENT )
val params: Array<Parameter> = event!!.parameters
var message: CharArray? = null
var percent: CharArray? = null
for(param in params) {
if( param.id.equals( SDKConstants.PARAM_SYNCEVENT_MESSAGE )) { message = param.value }
if( param.id.equals( SDKConstants.PARAM_SYNCEVENT_PERCENT )) { percent = param.value }
}
val text:String = message?.let { String(it) } + " (" + percent?.let { String(it) } + ")"
Log.d(LOG_TAG, "Event Received: " + text)
val result: EventResult? = null
// Returning a result is not necessary. This is only a progression message.
return result
}
}
}
// we assume session key is not password protected (password null)
var container: Container? = null
try {
if (currentDevice != null) {
container = currentDevice.createContainer(containerInitialization, null, MyEventListener())
}
} catch ( e : UnsupportedDeviceException) {
e.printStackTrace()
} catch ( e : InternalException) {
e.printStackTrace()
} catch ( e : AuthenticationException) {
e.printStackTrace()
} catch ( e : InvalidPasswordException) {
e.printStackTrace()
} catch ( e : RemoteException) {
e.printStackTrace()
} catch ( e : UnsafeDeviceException ) {
e.printStackTrace()
} catch ( e : ServerProtocolException ) {
e.printStackTrace()
} catch ( e : FingerprintAuthenticationRequiredException ) {
e.printStackTrace()
} catch ( e : FingerprintNotEnrolledException ) {
e.printStackTrace()
} catch ( e : GooglePlayServicesObsoleteException ) {
e.printStackTrace()
} catch ( e : PasswordCancelledException ) {
e.printStackTrace()
} catch ( e : ServerOperationFailedException ) {
e.printStackTrace()
} catch ( e : InvalidParameterException ) {
e.printStackTrace()
} finally {
// Good practice, the reset operation will nullify sensitive datas.
containerInitialization.reset()
}
// Get Device instance with default connection configuration
// Context is specific to the platform. On Android, it is an instance of android.content.Context.
Device device = null;
try {
device = DeviceFactory.getDevice(context, new ConnectionConfiguration());
} catch ( InvalidParameterException | LostCredentialsException | UnsupportedDeviceException | InternalException e) {
e.printStackTrace();
}
// From the device instance, we can now create a container
ContainerInitialization containerInitialization = new ContainerInitialization();
// QR code or payload of push notification
containerInitialization.activationCode = scanContent;
// Identifier given by the push notification system
containerInitialization.pushId = pushId;
// The EventListener callback will be invoked for the app to provide a password to protect keys, in case the server enforces protection by a password.
// The password creation is app responsibility.
// It will also be used to inform the user about the progress of the container's creation.
EventListener MyEventListener = new EventListener() {
@Override
public EventResult onEventReceived(Event event) {
if (event instanceof PasswordPromptEvent) {
// Password prompt message
// If your container is configured to be protected by a password, this is when the user will be requested to enter it through the PasswordPromptResult API
// EventResult.Code.Continue: operation can continue
// EventResult.Code.Cancel: operation is cancelled by the user.
// EventResult.Code.Abort: operation is aborted by the app
PasswordPromptResult prompt = new PasswordPromptResult(EventResult.Code.Continue);
// Password policy is provided in the event in the password policy ( Refer to Protection Policy for more explanation ) . For example :
Log.d(LOG_TAG, "Event Received: PasswordPromptEvent - maxLowerCase : " + ((PasswordPromptEvent) event).getPasswordPolicy().getMaxLowerCase());
Log.d(LOG_TAG, "Event Received: PasswordPromptEvent - minLowerCase : " + ((PasswordPromptEvent) event).getPasswordPolicy().getMinLowerCase());
// If the password does not respect the policy, the createContainer will fail with an invalidPasswordException.
prompt.setPassword(userPassword.toCharArray());
return prompt;
} else {
// Progress message
// the parameters array provide the state of the provisionning ( SDKConstants.PARAM_SYNCEVENT_MESSAGE ), and the percent fo progression ( SDKConstants.PARAM_SYNCEVENT_PERCENT )
Parameter[] params = event.getParameters();
char[] message = null;
char[] percent = null;
for (int i = 0; i < params.length; i++) {
if (SDKConstants.PARAM_SYNCEVENT_MESSAGE.equalsIgnoreCase(params[i].getId()))
message = params[i].getValue();
if (SDKConstants.PARAM_SYNCEVENT_PERCENT.equalsIgnoreCase(params[i].getId()))
percent = params[i].getValue();
}
String text = new String(message) + " (" + new String(percent) + ")";
Log.d(LOG_TAG, "Event Received: " + text);
// Returning a result is not necessary. This is only a progression message.
return null;
}
}
} ;
// we assume session key is not password protected (password null)
Container container = null;
try {
container = device.createContainer(containerInitialization, null, MyEventListener);
} catch (UnsupportedDeviceException e) {
e.printStackTrace();
} catch (InternalException e) {
e.printStackTrace();
} catch (AuthenticationException e) {
e.printStackTrace();
} catch (InvalidPasswordException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
} catch (UnsafeDeviceException e) {
e.printStackTrace();
} catch (ServerProtocolException e) {
e.printStackTrace();
} catch (FingerprintAuthenticationRequiredException e) {
e.printStackTrace();
} catch (FingerprintNotEnrolledException e) {
e.printStackTrace();
} catch (GooglePlayServicesObsoleteException e) {
e.printStackTrace();
} catch (PasswordCancelledException e) {
e.printStackTrace();
} catch (ServerOperationFailedException e) {
e.printStackTrace();
} catch (InvalidParameterException e) {
e.printStackTrace();
}
finally {
// Good practice, the reset operation will nullify sensitive datas.
containerInitialization.reset();
}
Degraded Use Case
If the application is unable to get an activation code (for example, the device does not have a camera to scan the QR code), then it can prompt the end user to enter the required information (for example, provided by the HID authentication platform, and displayed in the customer web application):
- User identifier (userId) – the user code in the HID authentication platform.
- Invite code (inviteCode) – this is a short code provided by the server to ensure the Container creation is genuine.
- Server URL (serverUrl) – the HID authentication platform short URL in the format <host>[:<port>]/<domain>.
Container Replacement
A container is uniquely identified by the user identifier, the server URL and the server domain used to create it.
If a container exists, and you provision a new one with the same user identifier, server URL and server domain, the previous one will be deleted.
Otherwise a new container will be provisioned for the user.