The security check is used to determine the overall security of the device and app. To achieve this the root/jailbreak
state of the operating system is checked and whether the process of the app is hooked or not. In addition, information
like system version, patch level, etc. is collected. With these data the XignIn-Manager determines whether the security
of the device and app meets the configured security standards. The XignSys SDK performs security checks before a
communication with the XignIn-Manager is established. In addition, the check can also be invoked by
using XignCommon.executeSecurityCheck or its synchronous counterpart.
Important Servicekonto.NRW
Due to the higher security requirements of the Servicekonto.NRW setup the BSI (Federal Office for Information Security) requires that these checks must also be performed outside the processes of the XignSys SDK. First, the app must conduct a security check every time it comes into the foreground. Additionally, while the app is in the foreground, a security must be performed periodically. Each check has to be executed at a randomly chosen time between 30 and 180 Minutes after the last check.
private func executeSecurityCheck() {
do {
// Perform a security check synchronous and retrieves the result.
let result: SecurityCheckResult = try XignSdk.shared.xignCommon.executeSecurityCheckSynchronous()
// The content of the `SecurityCheckResult`:
let date: Date = result.date // The current version of the check executed.
let checkVersion: Int32 = result.checkVersion // The current version of the check executed.
let osVersion: String = result.osVersion // Current os version.
let isRooted: Bool = result.isRooted // `true` if device is potentially rooted.
let isHooked: Bool = result.isHooked // `true` if device is potentially hooked.
} catch {
// Handle the error in case the security check failed.
YourImplementation.handleTheError()
}
}
While the XignSys SDK runs in the log mode XignLog.Level.verbose a security check produces logging statements like:
...
2022-03-15 18:11:16.558858+0100 XignsysIosSdkDemo[4790:2136535] -canOpenURL: failed for URL: "undecimus://" - error: "This app is not allowed to query for scheme undecimus"
2022-03-15 18:11:16.559425+0100 XignsysIosSdkDemo[4790:2136535] -canOpenURL: failed for URL: "sileo://" - error: "The operation couldn’t be completed. (OSStatus error -10814.)"
2022-03-15 18:11:16.559628+0100 XignsysIosSdkDemo[4790:2136535] -canOpenURL: failed for URL: "zbra://" - error: "The operation couldn’t be completed. (OSStatus error -10814.)"
...
An output
like -canOpenURL: failed for URL: "sileo://" - error: "The operation couldn’t be completed. (OSStatus error -10814.)"
indicates the absence of an app that utilizes the displayed scheme, i.e. most likely has root privileges. Therefore,
such output can be considered successful. However, the output
-canOpenURL: failed for URL: "undecimus://" - error: "This app is not allowed to query for scheme undecimus"
signalizes that the app is not allowed to query for the mentioned scheme. This reduces the efficiency of the security
check. If that is the case, please add the missing scheme(s) to the apps info.plist as described in the
chapter installation to ensure that the security check can work to its full extent.
private fun executeSecurityCheck() {
try {
// Perform a security check synchronous and retrieve the result.
val result: SecurityCheckResult = XignSdk.shared.xignCommon.executeSecurityCheckSynchronous()
// The content of the `SecurityCheckResult`:
val date: ZonedDateTime = result.date // The date and time the check was executed.
val checkVersion: Int = result.checkVersion // The current version of the check executed.
val isRooted: Boolean = result.isRooted // `true` if device is potentially rooted.
val isHooked: Boolean = result.isHooked // `true` if device is potentially hooked.
val androidSdkVersion: Int = result.androidSdkVersion // Android SDK version at the time
// of execution of the security check.
val securityPatch: String = result.securityPatch // Current security patch version of the device.
} catch (e: Exception) {
// Handle the error in case the security check failed.
YourImplementation.handleTheException()
}
}
While the XignSys SDK runs in the log mode XignLog.LogLevel.VERBOSE a security check produces logging statements like:
...
2022-03-15 18:44:27.312 7553-7641/com.xignsys.sdk.demo I/RootBeer: LOOKING FOR BINARY: /data/local/su Absent :(
2022-03-15 18:44:27.312 7553-7641/com.xignsys.sdk.demo I/RootBeer: LOOKING FOR BINARY: /data/local/bin/su Absent :(
2022-03-15 18:44:27.312 7553-7641/com.xignsys.sdk.demo I/RootBeer: LOOKING FOR BINARY: /data/local/xbin/su Absent :(
...
These outputs are produced by the library RootBeer that the XignSys SDK uses during a security check and indicate an
absence of binaries that allow executing commands with root privileges. Such an output can be considered successful and
is only mentioned here for clarification purposes; no further handling is needed.
If the XignIn-Manager configuration states that some information contained in the security report (collection of
security checks) violates the security standards the current process is canceled. In this case
a XignSdkSecurityInsufficientError (XignSdkSecurityInsufficientException) is thrown, which states the reason for the
rejection. The following example shows how this error can be caught and the additional information be retrieved; for
other errors please look up the chapter error handling.
internal func handleSecurityInsufficientError(
authenticationInitializationData: AuthenticationInitializationData
) throws {
let authenticator: Authenticator = XignSdk.shared.authenticator
do {
// Submit the `AuthenticationInitializationData` to the XignIn-Manager in order to start
// an authentication.
let request: AuthenticateRequest = try authenticator.startAuthenticationSynchronous(
data: authenticationInitializationData
)
} catch let xignSecurityInsufficientError as XignSdkSecurityInsufficientError {
// Receives the list of errors that occurred that describes why the device was rejected.
let insufficientErrorList = xignSecurityInsufficientError.information.errors
// For the purpose of this example only the first element will be handled.
let insufficientError = insufficientErrorList.first!
switch insufficientError.errorCode {
case .osRejected:
// Represents an error were the os version does not comply with security requirements.
throw YourError.yourErrorCase
case .versionBlacklisted:
// Represents an error were the os version is blacklisted.
throw YourError.yourErrorCase
case .osVersionTooLow:
// Represents an error were the os version is too low.
throw YourError.yourErrorCase
case .osVersionTooHigh:
// Represents an error were the os version is too high.
throw YourError.yourErrorCase
case .osPatchTooLow:
// Notifies that the patch version is too low.
throw YourError.yourErrorCase
case .osPatchTooHigh:
// Represents an error were the patch version is too high.
throw YourError.yourErrorCase
case .rejectedBecauseRooted:
// Represents an error were the device was rejected because it is potentially rooted.
throw YourError.yourErrorCase
case .rejectedBecauseHooked:
// Represents an error were the device was rejected because it is potentially hooked.
throw YourError.yourErrorCase
case .patchVersionBlacklisted:
// Represents an error were the patch version is blacklisted.
throw YourError.yourErrorCase
case .unknown:
// Represents an error were the server does not yet know protocol version.
throw YourError.yourErrorCase
}
} catch let xignError as XignSdkError {
let errorCode = StartAuthenticationErrorCodes.from(xignError.errorCode)
// Handle the `errorCode` according to its documentation.
throw YourError.yourErrorCase
} catch {
// Should not happen normally, because the SDK wraps all of its errors within a
// XignSdkError. A general error should be displayed here, just in case.
throw YourError.yourErrorCase
}
}
internal fun handleSecurityInsufficientError(
authenticationInitializationData: AuthenticationInitializationData
) {
val authenticator: Authenticator = XignSdk.shared.authenticator
try {
// Submit the `AuthenticationInitializationData` to the XignIn-Manager in order to start
// an authentication.
val request: AuthenticateRequest = authenticator.startAuthenticationSynchronous(
authenticationInitializationData
)
} catch (xignSecurityInsufficientError: XignSdkSecurityInsufficientException) {
// Receives the list of errors that occurred that describes why the device was rejected.
val insufficientErrorList = xignSecurityInsufficientError.information.errors
// For the purpose of this example only the first element will be handled.
val insufficientError = insufficientErrorList.first()
when (insufficientError.errorCode) {
OS_REJECTED -> {
// Represents an error were the os version does not comply with security requirements.
throw YourException()
}
VERSION_BLACKLISTED -> {
// Represents an error were the os version is blacklisted.
throw YourException()
}
OS_VERSION_TOO_LOW -> {
// Represents an error were the os version is too low.
throw YourException()
}
OS_VERSION_TOO_HIGH -> {
// Represents an error were the os version is too high.
throw YourException()
}
OS_PATCH_TOO_LOW -> {
// Notifies that the patch version is too low.
throw YourException()
}
OS_PATCH_TOO_HIGH -> {
// Represents an error were the patch version is too high.
throw YourException()
}
REJECTED_BECAUSE_ROOTED -> {
// Represents an error were the device was rejected because it is potentially rooted.
throw YourException()
}
REJECTED_BECAUSE_HOOKED -> {
// Represents an error were the device was rejected because it is potentially hooked.
throw YourException()
}
OS_PATCH_BLACKLISTED -> {
// Represents an error were the patch version is blacklisted.
throw YourException()
}
UNKNOWN -> {
// Represents an error were the server does not yet know protocol version.
throw YourException()
}
}
} catch (xignError: XignSdkException) {
val errorCode = StartAuthenticationErrorCodes.from(xignError.errorCode)
// Handle the `errorCode` according to its documentation.
throw YourException()
} catch (t: Throwable) {
// Should not happen normally, because the SDK wraps all of its errors within a
// XignSdkError. A general error should be displayed here, just in case.
throw YourException()
}
}