Transaction Signing

View this page for | |

Once the keys are provisioned, the device is ready to perform a Transaction Signing operation (that is, to approve or decline an operation 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.

For more information about creating a transaction from the Web App/backend integration, refer to the bcauthorize endpoint.

The mobile application signs a transaction as follows:

  1. Create an instance of the Device (DeviceFactory.getDevice).
  2. 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)

    • Scanning a QR code encoded with a userID-less transaction

  3. Get public information (ServerActionInfo) from the transaction identifier (transactionId) (Device.retrieveTransactionInfo).

    For a userID-less transaction, an InexplicitContainerException containing the list of possible userIDs is raised if several containers exist for the targeted server. The integrating application will need to select a userID from the getParameters list and call Device.retrieveTransactionInfo with the userID parameter.

    There is no communication with the server at this point.

    The returned ServerActionInfo instance provides the:

  4. Check if the Session Transport Key is protected by a password and prompt the user as required.
  5. Get transaction details from the server (ServerActionInfo.GetAction).
  6. 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).
  7. Display the transaction to the end user and retrieve the end user’s selection among the available statuses.
  8. Then request the end user to provide their Transaction Signing Protecting password and send the final status with optional mobile context data to the HID authentication platform (Transaction.setStatus).

    For integrations with the HID Authentication Service, see User Authentication with HID Approve and the bcauthorize endpoint.

Sample Transaction Signing on Android

Copy
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
    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 and context data
    val status = allowedStatus?.get(0
    // optional parameter can be used to pass mobile context base64 data (otherwise leave empty or null)
    var params = arrayOfNulls<Parameter>(0)
    params = arrayOf(
        Parameter(SDKConstants.PARAM_TX_MOBILE_CONTEXT, sContextB64.toCharArray()))

    // we assume session key is not password protected (password null)
    try {
        result = tx?.setStatus(status, containerPassword?.toCharArray() ?: null, null, params)!!
    }
    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
        }
    }
Copy
// 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;
    }
    
    // optional parameter can be used to pass mobile context base64 data (otherwise leave empty or null)
    Parameter[] params = new Parameter[0];
    params = new Parameter[]{new Parameter(SDKConstants.PARAM_TX_MOBILE_CONTEXT, sContextB64.toCharArray())};
    
    // We can now sign the transaction with a selected status and context data
    String status = allowedStatus[0];

    // we assume session key is not password protected (password null)
    try {
        result = tx.setStatus(status, containerPassword.toCharArray(), null, params);
    } 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();
    }