Transaction Signing
Once the keys are provisioned, the device is ready to perform a Transaction Signing operation (that is, to approve or decline a transaction based on details sent by the HID authentication platform). How the application is notified that a transaction is to be signed depends on its deployment. One option is to get a push notification from the server.
Transaction Signing Workflow
The mobile application signs a transaction as follows:
- Create an instance of the Device (DeviceFactory.getDevice).
-
Retrieve the transaction identifier (transactionId) for the transaction that will be processed. This identifier can be retrieved from the:
-
Push notification payload received by the application. This is the tds member of the payload
-
List of pending transactions for a specific container retrieved from the server (Container.retrieveTransactionsIds)
-
- Get public information (ServerActionInfo) from the transaction identifier (transactionId) (Device.retrieveTransactionInfo). There is no communication with the server at this point.
The returned ServerActionInfo instance provides the:
ServerActionInfo.getContainer() - the container associated with this transaction
ServerActionInfo.getUniqueIdentifier() - the transaction Unique Identifier
This unique identifier corresponds to the server challenge identifier (cid) and can be used to correlate transactions between the client and server.
ServerActionInfo.getProtectionKey() - the session transport key, which can be used to determine the corresponding key protection policy (such as if the password is required)
- Check if the Session Transport Key is protected by a password and prompt the user as required.
- Get transaction details from the server (ServerActionInfo.getTransaction).
- Get the Transaction details (Transaction.toString) and the list of allowed statuses (Transaction.getAllowedStatuses) that will be displayed to the end user so that they can decide which action to take (“approve” or “decline” the Transaction).
- Display the transaction to the end user and retrieve the end user’s selection among the available statuses.
- Then request the end user to provide their Transaction Signing Protecting password and send the final status to the HID authentication platform (Transaction.setStatus).
var device : Device? = null
// Get Device instance
try {
device = DeviceFactory.getDevice(ctx, connectionConfiguration)
} catch (ex: Exception) {
when(ex) {
is UnsupportedDeviceException, is LostCredentialsException, is InternalException, is InvalidParameterException -> {
ex.printStackTrace()
}
}
}
var txInfo : ServerActionInfo? = null
// Get the public information of the transaction
// the ID is obtainable : either through the push message, or through the retrieveTransactionIds API of the container.
try {
txInfo = device?.retrieveActionInfo(txId!!.toCharArray())
} catch (ex: Exception) {
when(ex) {
is InternalException, is InvalidContainerException, is InvalidParameterException -> {
ex.printStackTrace()
}
else -> throw ex
}
}
// The public information includes the container and the transaction protection key.
// We can check whether password is needed by getting the key and policy
// we assume it is not
val container = txInfo?.container
val txProtectKey = txInfo?.protectionKey
// Retrieve the transaction details
// we assume session key is not password protected (password null)
var tx: Transaction? = null
try {
tx = txInfo?.getAction(null, null) as Transaction
} catch (ex: Exception) {
when(ex) {
is PasswordExpiredException -> {
ex.printStackTrace()
}
is AuthenticationException, is InvalidParameterException, is UnsupportedDeviceException, is ServerOperationFailedException, is
TransactionExpiredException, is RemoteException, is LostCredentialsException, is InternalException -> {
ex.printStackTrace()
}
else -> throw ex
}
}
// The transaction contains the list of allowed response status
val allowedStatus = tx?.allowedStatuses
// Display transaction details to end user and request status
// Here we can check whether the signing key is protected by a password
// Display transaction details to end user and request status
// Here we can check whether the signing key is protected by a password
var signingKeyPolicy: ProtectionPolicy? = null
var containerPassword: String? = null
try {
signingKeyPolicy = tx!!.signingKey.protectionPolicy
} catch (ex: Exception) {
when(ex) {
is InternalException, is UnsupportedDeviceException, is UnsafeDeviceException -> {
ex.printStackTrace()
}
else -> throw ex
}
}
if (ProtectionPolicy.PolicyType.PASSWORD.toString() == signingKeyPolicy!!.type) {
// Prompt the end-user for the signing key password
containerPassword = userPassword
}
// We can now sign the transaction with a selected status
val status = allowedStatus?.get(0)
// we assume session key is not password protected (password null)
try {
result = tx?.setStatus(status, containerPassword?.toCharArray() ?: null, null, null)!!
}
catch (ex: Exception) {
when(ex) {
is AuthenticationException -> { // Password is incorrect
ex.printStackTrace()
}
is PasswordExpiredException -> { // !!! PasswordExpiredException if expired password is given (changePassword required). !!!
ex.printStackTrace()
}
is TransactionExpiredException, is RemoteException, is LostCredentialsException,
is InternalException ,is PasswordRequiredException, is FingerprintAuthenticationRequiredException,
is ServerOperationFailedException, is InvalidParameterException -> {
ex.printStackTrace()
}
else -> throw ex
}
}
// Get Device instance
Device device = null;
try {
device = DeviceFactory.getDevice(this.ctx, connectionConfiguration);
} catch (UnsupportedDeviceException | LostCredentialsException | InternalException | InvalidParameterException e) {
e.printStackTrace();
}
// Get the public information of the transaction
// the ID is obtainable : either through the push message, or through the retrieveTransactionIds API of the container.
ServerActionInfo txInfo = null;
try {
txInfo = device.retrieveActionInfo(txId.toCharArray());
} catch (InternalException | InvalidContainerException | InvalidParameterException e) {
e.printStackTrace();
}
// The public information includes the container and the transaction protection key.
// We can check whether password is needed by getting the key and policy
// we assume it is not
Container container = txInfo.getContainer();
Key txProtectKey = txInfo.getProtectionKey();
// Retrieve the transaction details
Transaction tx = null;
try {
tx = (Transaction) txInfo.getAction(null, null);
Log.i(this.LOG_TAG, "Tx=" + tx.toString());
} catch (PasswordExpiredException e) {
e.printStackTrace();
} catch (AuthenticationException | InvalidParameterException | UnsupportedDeviceException | ServerOperationFailedException |
TransactionExpiredException | RemoteException | LostCredentialsException | InternalException e) {
e.printStackTrace();
}
// The transaction contains the list of allowed response status
String[] allowedStatus = tx.getAllowedStatuses();
// Display transaction details to end user and request status
// Here we can check whether the signing key is protected by a password
ProtectionPolicy signingKeyPolicy = null;
String containerPassword = null;
try {
signingKeyPolicy = tx.getSigningKey().getProtectionPolicy();
} catch (InternalException | UnsupportedDeviceException | UnsafeDeviceException e) {
e.printStackTrace();
}
if (ProtectionPolicy.PolicyType.PASSWORD.toString().equals(signingKeyPolicy.getType())) {
// Prompt the end-user for the signing key password
containerPassword = userPassword;
}
// We can now sign the transaction with a selected status
String status = allowedStatus[0];
// we assume session key is not password protected (password null)
try {
result = tx.setStatus(status, containerPassword.toCharArray(), null, null);
} catch (AuthenticationException e) { // Password is incorrect
e.printStackTrace();
} catch (PasswordExpiredException e) { // !!! PasswordExpiredException if expired password is given (changePassword required). !!!
e.printStackTrace();
} catch (TransactionExpiredException | RemoteException | LostCredentialsException | InternalException | PasswordRequiredException | FingerprintAuthenticationRequiredException | ServerOperationFailedException | InvalidParameterException e) {
e.printStackTrace();
}