diff --git a/docs/Migration-from-1.7-to-1.8.md b/docs/Migration-from-1.7-to-1.8.md index 93f9c0d4..4a014710 100644 --- a/docs/Migration-from-1.7-to-1.8.md +++ b/docs/Migration-from-1.7-to-1.8.md @@ -12,7 +12,7 @@ PowerAuth Mobile SDK in version `1.8.0` provides the following improvements: ### Legacy SDK configuration -In case you need to still use the legacy setup to configure older version of PowerAuth mobile SDK, then you can use `get-legacy-config.swift` script available at `scripts` folder. For example: +In case you need to still use the legacy setup to configure the older version of PowerAuth mobile SDK, then you can use the `get-legacy-config.swift` script available in the `scripts` folder. For example: ```bash # clone the mobile library @@ -40,30 +40,30 @@ Legacy PowerAuth configuration: ).build(); ``` -- `PowerAuthSDK.Builder.build()` now require to use application's context to build instance of `PowerAuthSDK`. If you don't have such context available, then please use the following code in your application's `onCreate()` method: +- `PowerAuthSDK.Builder.build()` now requires to use application's context to build an instance of `PowerAuthSDK`. If you don't have such context available, then please use the following code in your application's `onCreate()` method: ```kotlin PowerAuthAppLifecycleListener.getInstance().registerForActivityLifecycleCallbacks(this) // "this" is Application ``` -- The following methods are now deprecated in `PowerAuthAuthentication` class: +- The following methods are now deprecated in the `PowerAuthAuthentication` class: - All variants of `commitWithPassword()` are now replaced with `persistWithPassword()` - All variants of `commitWithPasswordAndBiometry()` are now `persistWithPasswordAndBiometry()` -- The following methods are now deprecated in `PowerAuthSDK` class: +- The following methods are now deprecated in the `PowerAuthSDK` class: - `commitActivationWithAuthentication()` is now `persistActivationWithAuthentication()` - All variants of `commitActivationWithPassword()` are now `persistActivationWithPassword()` - All variants of `commitActivation()` are now `persistActivation()` - - All variants of `authenticateUsingBiometry()` are now replaced with `authenticateUsingBiometrics()` with `IAuthenticateWithBiometricsListener` interface returning `PowerAuthAuthentication` in success. + - All variants of `authenticateUsingBiometry()` are now replaced with `authenticateUsingBiometrics()` with the `IAuthenticateWithBiometricsListener` interface returning `PowerAuthAuthentication` in success. - The `ICommitActivationWithBiometryListener` is now deprecated and you can use `IPersistActivationWithBiometricsListener` as a replacement. -- The `PowerAuthAuthentication` object is now immutable object. +- The `PowerAuthAuthentication` object is now an immutable object. - `PowerAuthErrorCodes` now contains the following new error codes: - - `TIME_SYNCHRONIZATION` indicating a problem with the time synchronization. - - `BIOMETRY_NOT_ENROLLED` indicating that device has no enrolled biometry. + - `TIME_SYNCHRONIZATION` indicates a problem with the time synchronization. + - `BIOMETRY_NOT_ENROLLED` indicating that the device has no enrolled biometry. -- The biometry-related methods in `PowerAuthSDK` are no longer annotated as `@RequiresApi(api = Build.VERSION_CODES.M)`. This change may lead to a several dead code branches in your code if you still support devices older than Android 6.0. +- The biometry-related methods in `PowerAuthSDK` are no longer annotated as `@RequiresApi(api = Build.VERSION_CODES.M)`. This change may lead to several dead code branches in your code if you still support devices older than Android 6.0. - Removed all interfaces deprecated in release `1.7.x` @@ -74,10 +74,10 @@ Legacy PowerAuth configuration: If the `PowerAuthErrorException` is related to a biometric authentication failure, then the new `additionalInformation` property will contain an instance of the `BiometricErrorInfo` class. It's recommended to test whether the reason for the failure was presented to the user in the authentication dialog or in a custom error dialog provided by the PowerAuth mobile SDK. For example: ```kotlin -// Authenticate user with biometry and obtain encrypted biometry factor related key. +// Authenticate user with biometry and obtain encrypted biometry factor-related key. powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the biometric sensor on your device to continue", object: IAuthenticateWithBiometricsListener { override fun onBiometricDialogCancelled(userCancel: Boolean) { - // User or system cancelled the operation + // User or system canceled the operation } override fun onBiometricDialogSuccess(authentication: PowerAuthAuthentication) { @@ -96,17 +96,17 @@ powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the val localizedMessage = biometricErrorInfo.getLocalizedErrorMessage(context, null) } } else { - // Other reason for failure + // Other reasons for failure } } }) ``` -See also [Disable Error Dialog After Failed Biometry](PowerAuth-SDK-for-Android.md#disable-error-dialog-after-failed-biometry) chapter for more details. +See also the [Disable Error Dialog After Failed Biometry](PowerAuth-SDK-for-Android.md#disable-error-dialog-after-failed-biometry) chapter for more details. #### Synchronized time -The requirement for the time synchronized with the server has the following impact to your code: +The requirement for the time synchronized with the server has the following impact on your code: - If you use custom **End-To-End Encryption** in your application, then it's recommended to make sure the time is synchronized with the server: ```kotlin @@ -124,7 +124,7 @@ The requirement for the time synchronized with the server has the following impa } ``` -- If you use **Token-Based Authentication**, then you should use the new API provided by `PowerAuthTokenStore` that guarantee that time is synchronized before the token header is calculated: +- If you use **Token-Based Authentication**, then you should use the new API provided by `PowerAuthTokenStore` that guarantees that time is synchronized before the token header is calculated: ```kotlin val task = powerAuthSDK.tokenStore.generateAuthorizationHeader(context, "MyToken", object : IGenerateTokenHeaderListener { override fun onGenerateTokenHeaderSucceeded(header: PowerAuthAuthorizationHttpHeader) { @@ -167,7 +167,7 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob ### API changes - `PowerAuthConfiguration` - class now supports only the simplified configuration. - - Use new object constructor with all required parameters: + - Use a new object constructor with all required parameters: ```swift let config = PowerAuthConfiguration( instanceId: "your-instance-id", @@ -188,9 +188,9 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob - `.commitWithPasswordAndBiometry(password:)` is now `.persistithPasswordAndBiometry(password:)` - `.commitWithPasswordAndBiometry(password:customBiometryKey:customPossessionKey:)` is now `.persistWithPasswordAndBiometry(password:customBiometryKey:customPossessionKey:)` -- The `PowerAuthAuthentication` object is now immutable and no longer implements `NSCopying` protocol. +- The `PowerAuthAuthentication` object is now immutable and no longer implements the `NSCopying` protocol. -- `PowerAuthErrorCode` now contains new `.timeSynchronization` case indicating a problem with the time synchronization. +- `PowerAuthErrorCode` now contains a new `.timeSynchronization` case indicating a problem with the time synchronization. - Removed all interfaces deprecated in release `1.7.x` @@ -200,7 +200,7 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob #### Synchronized time -The requirement for the time synchronized with the server has the following impact to your code: +The requirement for the time synchronized with the server has the following impact on your code: - If you use custom **End-To-End Encryption** in your application, then it's recommended to make sure the time is synchronized with the server: ```swift @@ -214,7 +214,7 @@ The requirement for the time synchronized with the server has the following impa }, callbackQueue: .main) } ``` -- If you use **Token-Based Authentication**, then you should use the new API provided by `PowerAuthTokenStore` that guarantee that time is synchronized before the token header is calculated: +- If you use **Token-Based Authentication**, then you should use the new API provided by `PowerAuthTokenStore` that guarantees that time is synchronized before the token header is calculated: ```swift powerAuthSdk.tokenStore.generateAuthorizationHeader(withName: "MyToken") { header, error in if let header = header { @@ -254,7 +254,7 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob ### API changes - `PowerAuthConfiguration` - class now supports only the simplified configuration. - - Use new object constructor with all required parameters: + - Use a new object constructor with all required parameters: ```swift let config = PowerAuthConfiguration( instanceId: "your-instance-id", @@ -264,7 +264,7 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob ``` - Removed `applicationKey`, `applicationSecret`, `masterServerPublicKey`, `disableAutomaticProtocolUpgrade` properties. -- The `PowerAuthAuthentication` object is now immutable object and no longer implement `NSCopying` protocol. +- The `PowerAuthAuthentication` object is now an immutable object and no longer implements the `NSCopying` protocol. - Removed all interfaces deprecated in release `1.7.x` @@ -275,7 +275,7 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob ### API changes - `PowerAuthConfiguration` - class now supports only the simplified configuration. - - Use new object constructor with all required parameters: + - Use the new object constructor with all required parameters: ```swift let config = PowerAuthConfiguration( instanceId: "your-instance-id", @@ -285,7 +285,7 @@ Visit [Synchronized Time](https://developers.wultra.com/components/powerauth-mob ``` - Removed `applicationKey`, `applicationSecret`, `masterServerPublicKey`, `disableAutomaticProtocolUpgrade` properties. -- The `PowerAuthAuthentication` object is now immutable object and no longer implement `NSCopying` protocol. +- The `PowerAuthAuthentication` object is now an immutable object and no longer implements the `NSCopying` protocol. - Removed all interfaces deprecated in release `1.7.x` diff --git a/docs/PowerAuth-SDK-for-Android.md b/docs/PowerAuth-SDK-for-Android.md index d1da50f5..cb471666 100644 --- a/docs/PowerAuth-SDK-for-Android.md +++ b/docs/PowerAuth-SDK-for-Android.md @@ -136,7 +136,7 @@ try { -If you don't provide application's context to `build()` method, then PowerAuthSDK will fail to register its components for application's lifecycle callbacks. To fix this, please use the following code in your application's `onCreate()` method: +If you don't provide the application's context to the `build()` method, then PowerAuthSDK will fail to register its components for the application's lifecycle callbacks. To fix this, please use the following code in your application's `onCreate()` method: ```kotlin PowerAuthAppLifecycleListener.getInstance().registerForActivityLifecycleCallbacks(this) // "this" is Application @@ -149,11 +149,11 @@ The `PowerAuthConfiguration.Builder` class provides the following additional met - `offlineSignatureComponentLength()` - Alters the default component length for the [offline signature](#symmetric-offline-multi-factor-signature). The values between 4 and 8 are allowed. The default value is 8. - `externalEncryptionKey()` - See [External Encryption Key](#external-encryption-key) chapter for more details. -- `disableAutomaticProtocolUpgrade()` - Disables the automatic protocol upgrade. This option should be used only for the debugging purposes. +- `disableAutomaticProtocolUpgrade()` - Disables the automatic protocol upgrade. This option should be used only for debugging purposes. ### Activation Data Protection -By default, the PowerAuth Mobile SDK for Android encrypts the local activation data with a symmetric key generated by the Android KeyStore on the Android 6 and newer devices. On older devices, or if the device has an unreliable KeyStore implementation, then the fallback to unencrypted storage, based on private [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences) is used. If your application requires a higher level of activation data protection, then you can enforce the level of protection in `PowerAuthKeychainConfiguration`: +By default, the PowerAuth Mobile SDK for Android encrypts the local activation data with a symmetric key generated by the Android KeyStore on Android 6 and newer devices. On older devices, or if the device has an unreliable KeyStore implementation, then the fallback to unencrypted storage, based on private [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences) is used. If your application requires a higher level of activation data protection, then you can enforce the level of protection in `PowerAuthKeychainConfiguration`: ```kotlin @@ -198,19 +198,19 @@ val keychainProtectionLevel = KeychainFactory.getKeychainProtectionSupportedOnDe The following levels of keychain protection are defined: -- `NONE` - The content of the keychain is not encrypted and therefore not protected. This level of the protection is typically reported on devices older than Android Marshmallow, or in case that the device has faulty KeyStore implementation. +- `NONE` - The content of the keychain is not encrypted and therefore not protected. This level of protection is typically reported on devices older than Android Marshmallow, or in case the device has faulty KeyStore implementation. -- `SOFTWARE` - The content of the keychain is encrypted with key generated by Android KeyStore, but the key is protected only on the operating system level. The security of the key material relies solely on software measures, which means that a compromise of the Android OS (such as root exploit) might up revealing this key. +- `SOFTWARE` - The content of the keychain is encrypted with a key generated by Android KeyStore, but the key is protected only on the operating system level. The security of the key material relies solely on software measures, which means that a compromise of the Android OS (such as a "root" exploit) might up revealing this key. -- `HARDWARE` - The content of the keychain is encrypted with key generated by Android KeyStore and the key is stored and managed by [Trusted Execution Environment](https://en.wikipedia.org/wiki/Trusted_execution_environment). +- `HARDWARE` - The content of the keychain is encrypted with a key generated by Android KeyStore and the key is stored and managed by [Trusted Execution Environment](https://en.wikipedia.org/wiki/Trusted_execution_environment). -- `STRONGBOX` - The content of the keychain is encrypted with key generated by Android KeyStore and the key is stored inside of Secure Element (e.g. StrongBox). This is the highest level of Keychain protection currently available, but not enabled by default. See [note below](#strongbox-support-note). +- `STRONGBOX` - The content of the keychain is encrypted with a key generated by Android KeyStore and the key is stored inside of Secure Element (e.g. StrongBox). This is the highest level of Keychain protection currently available, but not enabled by default. See [note below](#strongbox-support-note). -Be aware, that enforcing the required level of protection must be properly reflected in your application's user interface. That means that you should inform the user in case that the device has an insufficient capabilities to run your application securely. +Be aware, that enforcing the required level of protection must be properly reflected in your application's user interface. That means that you should inform the user in case the device has insufficient capabilities to run your application securely. #### StrongBox Support Note -The StrongBox backed keys are by default turned-off due to poor reliability and low performance of StrongBox implementations on the current Android devices. If you want to turn support on in your application, then use the following code at your application's startup: +The StrongBox-backed keys are by default turned off due to poor reliability and low performance of StrongBox implementations on the current Android devices. If you want to turn support on in your application, then use the following code at your application's startup: ```kotlin @@ -256,14 +256,14 @@ try { // Invalid activation code } -// Create a new activation with given activation object +// Create a new activation with the given activation object powerAuthSDK.createActivation(activation, object: ICreateActivationListener { override fun onActivationCreateSucceed(result: CreateActivationResult) { val fingerprint = result.activationFingerprint val activationRecovery = result.recoveryData // No error occurred, proceed to credentials entry (PIN prompt, Enable "Fingerprint Authentication" switch, ...) and persist // The 'fingerprint' value represents the combination of device and server public keys - it may be used as visual confirmation - // If server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. + // If the server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. } override fun onActivationCreateFailed(t: Throwable) { @@ -275,7 +275,7 @@ powerAuthSDK.createActivation(activation, object: ICreateActivationListener { String deviceName = "Petr's Leagoo T5C"; String activationCode = "VVVVV-VVVVV-VVVVV-VTFVA"; // let user type or QR-scan this value -// Create activation object with given activation code. +// Create an activation object with the given activation code. final PowerAuthActivation activation; try { activation = PowerAuthActivation.Builder.activation(activationCode, deviceName).build(); @@ -283,7 +283,7 @@ try { // Invalid activation code } -// Create a new activation with given activation object +// Create a new activation with the given activation object powerAuthSDK.createActivation(activation, new ICreateActivationListener() { @Override public void onActivationCreateSucceed(CreateActivationResult result) { @@ -291,7 +291,7 @@ powerAuthSDK.createActivation(activation, new ICreateActivationListener() { final RecoveryData activationRecovery = result.getRecoveryData(); // No error occurred, proceed to credentials entry (PIN prompt, Enable "Fingerprint Authentication" switch, ...) and persist // The 'fingerprint' value represents the combination of device and server public keys - it may be used as visual confirmation - // If server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. + // If the server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. } @Override @@ -302,7 +302,7 @@ powerAuthSDK.createActivation(activation, new ICreateActivationListener() { ``` -If the received activation result also contains recovery data, then you should display that values to the user. To do that, please read the [Getting Recovery Data](#getting-recovery-data) section of this document, which describes how to treat that sensitive information. This is relevant for all types of activation you use. +If the received activation result also contains recovery data, then you should display those values to the user. To do that, please read the [Getting Recovery Data](#getting-recovery-data) section of this document, which describes how to treat that sensitive information. This is relevant for all types of activation you use. #### Additional Activation OTP @@ -314,7 +314,7 @@ val deviceName = "Petr's iPhone 7" // or UIDevice.current.name val activationCode = "VVVVV-VVVVV-VVVVV-VTFVA" // let user type or QR-scan this value val activationOtp = "12345" -// Create activation object with given activation code and OTP. +// Create an activation object with the given activation code and OTP. val activation: PowerAuthActivation try { activation = PowerAuthActivation.Builder.activation(activationCode, deviceName) @@ -359,7 +359,7 @@ Use the following code to create an activation using custom credentials: val deviceName = "Juraj's JiaYu S3" val credentials = mapOf("username" to "john.doe@example.com", "password" to "YBzBEM") -// Create activation object with given credentials. +// Create an activation object with the given credentials. val activation: PowerAuthActivation try { activation = PowerAuthActivation.Builder.customActivation(credentials, deviceName).build() @@ -367,14 +367,14 @@ try { // Credentials dictionary is empty } -// Create a new activation with given activation object +// Create a new activation with the given activation object powerAuthSDK.createActivation(activation, object: ICreateActivationListener { override fun onActivationCreateSucceed(result: CreateActivationResult) { val fingerprint = result.activationFingerprint val activationRecovery = result.recoveryData // No error occurred, proceed to credentials entry (PIN prompt, Enable "Biometric Authentication" switch, ...) and persist // The 'fingerprint' value represents the combination of device and server public keys - it may be used as visual confirmation - // If server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. + // If the server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. } override fun onActivationCreateFailed(t: Throwable) { @@ -389,7 +389,7 @@ Map credentials = new HashMap<>(); credentials.put("username", "john.doe@example.com"); credentials.put("password", "YBzBEM"); -// Create activation object with given credentials. +// Create an activation object with the given credentials. final PowerAuthActivation activation; try { activation = PowerAuthActivation.Builder.customActivation(credentials, deviceName).build(); @@ -397,7 +397,7 @@ try { // Credentials dictionary is empty } -// Create a new activation with given activation object +// Create a new activation with the given activation object powerAuthSDK.createActivation(activation, new ICreateActivationListener() { @Override public void onActivationCreateSucceed(CreateActivationResult result) { @@ -405,7 +405,7 @@ powerAuthSDK.createActivation(activation, new ICreateActivationListener() { final RecoveryData activationRecovery = result.getRecoveryData(); // No error occurred, proceed to credentials entry (PIN prompt, Enable "Biometric Authentication" switch, ...) and persist // The 'fingerprint' value represents the combination of device and server public keys - it may be used as visual confirmation - // If server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. + // If the server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. } @Override @@ -416,14 +416,14 @@ powerAuthSDK.createActivation(activation, new ICreateActivationListener() { ``` -Note that by using weak identity attributes to create an activation, the resulting activation is confirming a "blurry identity". This may greatly limit the legal weight and usability of a signature. We recommend using a strong identity verification before activation can actually be created. +Note that by using weak identity attributes to create an activation, the resulting activation confirms a "blurry identity". This may greatly limit the legal weight and usability of a signature. We recommend using a strong identity verification before activation can actually be created. ### Activation via Recovery Code -If PowerAuth Server is configured to support [Recovery Codes](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md), then also you can create an activation via the recovery code and PUK. +If the PowerAuth Server is configured to support [Recovery Codes](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md), then also you can create an activation via the recovery code and PUK. -Use the following code to create an activation using recovery code: +Use the following code to create an activation using the recovery code: ```kotlin @@ -431,7 +431,7 @@ val deviceName = "John Tramonta" val recoveryCode = "55555-55555-55555-55YMA" // User's input val puk = "0123456789" // User's input. You should validate RC & PUK with using ActivationCodeUtil -// Create activation object with given recovery code and PUK. +// Create an activation object with the given recovery code and PUK. val activation: PowerAuthActivation try { activation = PowerAuthActivation.Builder.recoveryActivation(recoveryCode, puk, deviceName).build(); @@ -439,25 +439,25 @@ try { // Invalid recovery code or PUK } -// Create a new activation with given activation object +// Create a new activation with the given activation object powerAuthSDK.createActivation(activation, object: ICreateActivationListener { override fun onActivationCreateSucceed(result: CreateActivationResult) { val fingerprint = result.activationFingerprint val activationRecovery = result.recoveryData // No error occurred, proceed to credentials entry (PIN prompt, Enable "Biometric Authentication" switch, ...) and persist // The 'fingerprint' value represents the combination of device and server public keys - it may be used as visual confirmation - // If server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. + // If the server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. } override fun onActivationCreateFailed(t: Throwable) { // Error occurred, report it to the user - // On top of a regular error processing, you should handle a special situation, when server gives an additional information + // On top of regular error processing, you should handle a special situation, when the server gives additional information // about which PUK must be used for the recovery. The information is valid only when recovery code from a postcard is applied. if (t is ErrorResponseApiException) { val errorResponse = t.errorResponse val currentRecoveryPukIndex = t.currentRecoveryPukIndex if (currentRecoveryPukIndex > 0) { - // The PUK index is known, you should inform user that it has to rewrite PUK from a specific position. + // The PUK index is known, you should inform the user that it has to rewrite PUK from a specific position. } } } @@ -468,7 +468,7 @@ final String deviceName = "John Tramonta" final String recoveryCode = "55555-55555-55555-55YMA" // User's input final String puk = "0123456789" // User's input. You should validate RC & PUK with using ActivationCodeUtil -// Create activation object with given recovery code and PUK. +// Create an activation object with the given recovery code and PUK. final PowerAuthActivation activation; try { activation = PowerAuthActivation.Builder.recoveryActivation(recoveryCode, puk, deviceName).build(); @@ -476,7 +476,7 @@ try { // Invalid recovery code or PUK } -// Create a new activation with given activation object +// Create a new activation with the given activation object powerAuthSDK.createActivation(activation, new ICreateActivationListener() { @Override public void onActivationCreateSucceed(CreateActivationResult result) { @@ -484,20 +484,20 @@ powerAuthSDK.createActivation(activation, new ICreateActivationListener() { final RecoveryData activationRecovery = result.getRecoveryData(); // No error occurred, proceed to credentials entry (PIN prompt, Enable "Biometric Authentication" switch, ...) and persist // The 'fingerprint' value represents the combination of device and server public keys - it may be used as visual confirmation - // If server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. + // If the server supports recovery codes for activation, then `activationRecovery` contains object with information about activation recovery. } @Override public void onActivationCreateFailed(Throwable t) { // Error occurred, report it to the user - // On top of a regular error processing, you should handle a special situation, when server gives an additional information - // about which PUK must be used for the recovery. The information is valid only when recovery code from a postcard is applied. + // On top of regular error processing, you should handle a special situation, when the server gives an additional information + // about which PUK must be used for the recovery. The information is valid only when a recovery code from a postcard is applied. if (t instanceof ErrorResponseApiException) { ErrorResponseApiException exception = (ErrorResponseApiException) t; Error errorResponse = exception.getErrorResponse(); int currentRecoveryPukIndex = exception.getCurrentRecoveryPukIndex(); if (currentRecoveryPukIndex > 0) { - // The PUK index is known, you should inform user that it has to rewrite PUK from a specific position. + // The PUK index is known, you should inform the user that it has to rewrite PUK from a specific position. } } } @@ -507,7 +507,7 @@ powerAuthSDK.createActivation(activation, new ICreateActivationListener() { ### Customize Activation -You can set an additional properties to `PowerAuthActivation` object, before any type of activation is created. For example: +You can set additional properties to `PowerAuthActivation` object before any type of activation is created. For example: ```kotlin @@ -580,14 +580,14 @@ After you create an activation using one of the methods mentioned above, you nee // Persist activation using given PIN val result = powerAuthSDK.persistActivationWithPassword(context, pin) if (result != PowerAuthErrorCodes.SUCCEED) { - // happens only in case SDK was not configured or activation is not in state to be persisted + // happens only in case SDK was not configured or activation is not in the state to be persisted } ``` ```java // Persist activation using given PIN int result = powerAuthSDK.persistActivationWithPassword(context, pin); if (result != PowerAuthErrorCodes.SUCCEED) { - // happens only in case SDK was not configured or activation is not in state to be persisted + // happens only in case SDK was not configured or activation is not in the state to be persisted } ``` @@ -639,14 +639,14 @@ Also, you can use the following code to create activation with the best granular val authentication = PowerAuthAuthentication.persistWithPasswordAndBiometry(pin, biometryFactorRelatedKey) val result = powerAuthSDK.persistActivationWithAuthentication(context, authentication) if (result != PowerAuthErrorCodes.SUCCEED) { - // happens only in case SDK was not configured or activation is not in state to be persisted + // happens only in case SDK was not configured or activation is not in the state to be persisted } ``` ```java PowerAuthAuthentication authentication = PowerAuthAuthentication.persistWithPasswordAndBiometry(pin, biometryFactorRelatedKey); int result = powerAuthSDK.persistActivationWithAuthentication(context, authentication); if (result != PowerAuthErrorCodes.SUCCEED) { - // happens only in case SDK was not configured or activation is not in state to be persisted + // happens only in case SDK was not configured or activation is not in the state to be persisted } ``` @@ -655,7 +655,7 @@ Note that you currently need to obtain the biometry factor-related key yourself ### Validating User Inputs -The mobile SDK is providing a couple of functions in `ActivationCodeUtil` class, helping with user input validation. You can: +The mobile SDK provides a couple of functions in `ActivationCodeUtil` class, helping with user input validation. You can: - Parse activation code when it's scanned from QR code - Validate a whole code at once @@ -759,7 +759,7 @@ boolean isValid = ActivationCodeUtil.validateRecoveryPuk("0123456789"); #### Auto-Correcting Typed Characters -You can implement auto-correcting of typed characters with using `ActivationCodeUtil.validateAndCorrectTypedCharacter()` function in screens, where user is supposed to enter an activation or recovery code. This technique is possible due to the fact that Base32 is constructed so that it doesn't contain visually confusing characters. For example, `1` (number one) and `I` (capital I) are confusing, so only `I` is allowed. The benefit is that the provided function can correct typed `1` and translate it to `I`. +You can implement auto-correcting of typed characters by using `ActivationCodeUtil.validateAndCorrectTypedCharacter()` function in screens, where the user is supposed to enter an activation or recovery code. This technique is possible due to the fact that Base32 is constructed so that it doesn't contain visually confusing characters. For example, `1` (number one) and `I` (capital I) are confusing, so only `I` is allowed. The benefit is that the provided function can correct typed `1` and translate it to `I`. Here's an example how to iterate over the string and validate it character by character: @@ -811,7 +811,7 @@ fun validateTypedCharacters(input: String): String? { ## Requesting Activation Status -To obtain a detailed activation status information, use the following code: +To obtain detailed activation status information, use the following code: ```kotlin @@ -941,7 +941,7 @@ To get more information about activation lifecycle, check the [Activation States The main feature of the PowerAuth protocol is data signing. PowerAuth has two types of signatures: -- **Symmetric Multi-Factor Signature**: Suitable for most operations, such as login, new payment or confirming changes in settings. +- **Symmetric Multi-Factor Signature**: Suitable for most operations, such as login, new payment, or confirming changes in settings. - **Asymmetric Private Key Signature**: Suitable for documents where a strong one-sided signature is desired. - **Symmetric Offline Multi-Factor Signature**: Suitable for very secure operations, where the signature is validated over the out-of-band channel. - **Verify server signed data**: Suitable for receiving arbitrary data from the server. @@ -955,7 +955,7 @@ To sign request data, you need to first obtain user credentials (password, PIN c // 1FA signature, uses device related key only. val oneFactor = PowerAuthAuthentication.possession() -// 2FA signature, uses device related key and user PIN code. +// 2FA signature - uses device related key and user PIN code. val twoFactorPassword = PowerAuthAuthentication.possessionWithPassword("1234") // 2FA signature, uses biometry factor-related key as a 2nd. factor. @@ -966,7 +966,7 @@ val twoFactorBiometry = PowerAuthAuthentication.possessionWithBiometry(biometryF // 1FA signature, uses device related key only. PowerAuthAuthentication oneFactor = PowerAuthAuthentication.possession(); -// 2FA signature, uses device related key and user PIN code. +// 2FA signature - uses device related key and user PIN code. PowerAuthAuthentication twoFactorPassword = PowerAuthAuthentication.possessionWithPassword("1234"); // 2FA signature, uses biometry factor-related key as a 2nd. factor. @@ -979,7 +979,7 @@ When signing `POST`, `PUT` or `DELETE` requests, use request body bytes (UTF-8) ```kotlin -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device related key and user PIN code val authentication = PowerAuthAuthentication.possessionWithPassword("1234") // Sign POST call with provided data made to URI with custom identifier "/payment/create" @@ -992,7 +992,7 @@ if (header.isValid) { } ``` ```java -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code PowerAuthAuthentication authentication = PowerAuthAuthentication.possessionWithPassword("1234"); // Sign POST call with provided data made to URI with custom identifier "/payment/create" @@ -1010,7 +1010,7 @@ When signing `GET` requests, use the same code as above with normalized request ```kotlin -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code val authentication = PowerAuthAuthentication.possessionWithPassword("1234") // Sign GET call with provided query parameters made to URI with custom identifier "/payment/create" @@ -1025,7 +1025,7 @@ if (header.isValid) { } ``` ```java -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code PowerAuthAuthentication authentication = PowerAuthAuthentication.possessionWithPassword("1234"); // Sign GET call with provided query parameters made to URI with custom identifier "/payment/create" @@ -1060,7 +1060,7 @@ if (!header.isValid) { // Add HTTP header in the request builder builder.header(header.getKey(), header.getValue()) -// Build the request, send it and process response... +// Build the request, send it, and process the response... // ... ``` ```java @@ -1077,20 +1077,20 @@ if (!header.isValid()) { // Add HTTP header in the request builder builder.header(header.getKey(), header.getValue()); -// Build the request, send it and process response... +// Build the request, send it, and process the response... // ... ``` #### Request Synchronization -It is recommended that your application executes only one signed request at the time. The reason for that is that our signature scheme is using a counter as a representation of logical time. In other words, the order of request validation on the server is very important. If you issue more that one signed request at the same time, then the order is not guaranteed and therefore one from the requests may fail. On top of that, Mobile SDK itself is using this type of signatures for its own purposes. For example, if you ask for token, then the SDK is using signed request to obtain the token's data. To deal with this problem, Mobile SDK is providing a custom serial `Executor`, which can be used for signed requests execution: +It is recommended that your application executes only one signed request at the time. The reason for that is that our signature scheme is using a counter as a representation of logical time. In other words, the order of request validation on the server is very important. If you issue more than one signed request at the same time, then the order is not guaranteed, and therefore one of the requests may fail. On top of that, Mobile SDK itself is using this type of signatures for its own purposes. For example, if you ask for token, then the SDK is using signed request to obtain the token's data. To deal with this problem, Mobile SDK is providing a custom serial `Executor`, which can be used for signed requests execution: ```kotlin powerAuthSDK.serialExecutor.execute { // Recommended practice: - // 1. You have to calculate PowerAuth signature here. + // 1. You have to calculate the PowerAuth signature here. // 2. In case that you start yet another asynchronous operation from run(), // then you have to wait for that operation's execution. } @@ -1101,7 +1101,7 @@ serialExecutor.execute(new Runnable() { @Override public void run() { // Recommended practice: - // 1. You have to calculate PowerAuth signature here. + // 1. You have to calculate the PowerAuth signature here. // 2. In case that you start yet another asynchronous operation from run(), // then you have to wait for that operation's execution. } @@ -1166,7 +1166,7 @@ val claims = mapOf( "last_name" to "Appleseed" ) -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device related key and user PIN code val authentication = PowerAuthAuthentication.possessionWithPassword("1234") // Unlock the secure vault, fetch the private key and perform data signing @@ -1183,7 +1183,7 @@ powerAuthSDK.signJwtWithDevicePrivateKey(context, authentication, claims, object ### Symmetric Offline Multi-Factor Signature -This type of signature is very similar to [Symmetric Multi-Factor Signature](#symmetric-multi-factor-signature) but the result is provided in the form of a simple, human-readable string (unlike the online version, where the result is HTTP header). To calculate the signature, you need a typical `PowerAuthAuthentication` object to define all required factors, nonce and data to sign. The `nonce` and `data` should also be transmitted to the application over the OOB channel (for example, by scanning a QR code). Then the signature calculation is straightforward: +This type of signature is very similar to [Symmetric Multi-Factor Signature](#symmetric-multi-factor-signature) but the result is provided in the form of a simple, human-readable string (unlike the online version, where the result is an HTTP header). To calculate the signature, you need a typical `PowerAuthAuthentication` object to define all required factors, nonce and data to sign. The `nonce` and `data` should also be transmitted to the application over the OOB channel (for example, by scanning a QR code). Then the signature calculation is straightforward: ```kotlin @@ -1228,7 +1228,7 @@ if (powerAuthSDK.verifyServerSignedData(data, signature, true)) { } // Validate data signed with the personalized server key if (powerAuthSDK.verifyServerSignedData(data, signature, false)) { - // data is signed with server's private key + // data is signed with the server's private key } ``` ```java @@ -1238,7 +1238,7 @@ if (powerAuthSDK.verifyServerSignedData(data, signature, true)) { } // Validate data signed with the personalized server key if (powerAuthSDK.verifyServerSignedData(data, signature, false)) { - // data is signed with server's private key + // data is signed with the server's private key } ``` @@ -1343,31 +1343,31 @@ powerAuthSDK.changePasswordUnsafe(oldPassword, newPassword); ## Working with passwords securely -PowerAuth mobile SDK uses `io.getlime.security.powerauth.core.Password` object behind the scene, to store user's password or PIN securely. The object automatically wipes out the plaintext password on its destroy, so there are no traces of sensitive data left in the memory. You can easily enhance your application's runtime security by adopting this object in your code and this chapter explains in detail how to do it. +PowerAuth mobile SDK uses the `io.getlime.security.powerauth.core.Password` object behind the scenes, to store the user's password or PIN securely. The object automatically wipes out the plaintext password on its destroy, so there are no traces of sensitive data left in the memory. You can easily enhance your application's runtime security by adopting this object in your code and this chapter explains in detail how to do it. ### Problem explanation -If you store the user's password in simple string, there is a high probabilty that the content of the string will remain in the memory until the same region is reused by the underlying memory allocator. This is due the fact that the general memory allocator doesn't cleanup the region of memory being freed. It just update its linked-list of free memory regions for future reuse, so the content of allocated object typically remains intact. This has the following implications to your application: +If you store the user's password in a simple string, there is a high probability that the content of the string will remain in the memory until the same region is reused by the underlying memory allocator. This is because the general memory allocator doesn't clean up the region of memory being freed. It just updates its linked-list of free memory regions for future reuse, so the content of the allocated object typically remains intact. This has the following implications for your application: -- If your application is using system keyboard to enter the password or PIN, then the sensitive data will remain in memory in multiple copies for a while. +- If your application is using a system keyboard to enter the password or PIN, then the sensitive data will remain in memory in multiple copies for a while. - If the device's memory is not stressed enough, then the application may remain in memory active for days. -The situation that the user's password stays in memory for days may be critical in situations when the attacker has the device in possession. For example, if device is lost or is in repair shop. To minimize the risks, the `Password` object does the following things: +The situation that the user's password stays in memory for days may be critical in situations when the attacker has the device in possession. For example, if the device is lost or is in a repair shop. To minimize the risks, the `Password` object does the following things: -- Always keeps user's password scrambled with a random data, so it cannot be easily found by simple string search. The password in plaintext is revealed only for a short and well defined time when it's needed for the cryptographic operation. +- Always keeps the user's password scrambled with random data, so it cannot be easily found by simple string search. The password in plaintext is revealed only for a short and well-defined time when it's needed for the cryptographic operation. -- Always clears buffer with the sensitive data before the object's destruction. +- Always clear buffer with the sensitive data before the object's destruction. -- Doesn't provide a simple interface to reveal the password in plaintext1) and therefore it minimizes the risks of revealing the password by accident (like print it to the log). +- Doesn't provide a simple interface to reveal the password in plaintext1) and therefore it minimizes the risks of revealing the password by accident (like printing it to the log). -**Note 1:** There's `validatePasswordComplexity()` function that reveal the password in plaintext for the limited time for the complexity validation purposes. The straightforward naming of the function allows you to find all its usages in your code and properly validate all codepaths. +**Note 1:** There's a `validatePasswordComplexity()` function that reveals the password in plaintext for a limited time for complexity validation purposes. The straightforward naming of the function allows you to find all its usages in your code and properly validate all code paths. ### Special password object usage -PowerAuth mobile SDK allows you to use both strings and special password objects at input, so it's up to you which way fits best for your purposes. For simplicity, this documentation is using strings for the passwords, but all code examples can be changed to utilize `Password` object as well. For example, this is the modified code for [Password Change](#password-change): +PowerAuth mobile SDK allows you to use both strings and special password objects at input, so it's up to you which way fits best for your purposes. For simplicity, this documentation uses strings for the passwords, but all code examples can be changed to utilize the `Password` object as well. For example, this is the modified code for [Password Change](#password-change): ```kotlin @@ -1405,13 +1405,13 @@ powerAuthSDK.changePassword(context, oldPass, newPass, new IChangePasswordListen ### Entering PIN -If your application is using system numberic keyboard to enter user's PIN then you can migrate to `Password` object right now. We recommend you to do the following things: +If your application is using a system numeric keyboard to enter the user's PIN then you can migrate to the `Password` object right now. We recommend you do the following things: - Implement your own PIN keyboard UI -- Make sure that password object is allocated and referenced only in the PIN keyboard controller and is deallocated when user leaves the controller. +- Make sure that the password object is allocated and referenced only in the PIN keyboard controller and is deallocated when the user leaves the controller. -- Use `Password()` object that allows you to manipulate with the content of the PIN +- Use the `Password()` object that allows you to manipulate the content of the PIN Here's the simple pseudo-controller example: @@ -1423,12 +1423,12 @@ class EnterPinScene(val desiredPinLength: Int = 4) { fun onEnterScene() { // Allocate password when entering to the scene. - // Constructor with no parameters create mutable Password. + // Constructor with no parameters creates a mutable Password. pinInstance = Password() } fun onLeaveScene() { - // Dereference and destroy the password object, when user is leaving + // Dereference and destroy the password object, when the user is leaving // the scene to safely wipe the content out of the memory. // // Make sure that this is done only after PowerAuth SDK finishes all operations @@ -1442,9 +1442,9 @@ class EnterPinScene(val desiredPinLength: Int = 4) { } fun onPinButtonAction(pinCharacter: Char) { - // Mutable password works with unicode scalars, this is the example - // that works with an arbitrary character up to code-point 0xFFFF. - // To add an arbitrary unicode character, you need to convert it to code point first. + // Mutable password works with Unicode scalars, this is the example + // that works with an arbitrary character up to code point 0xFFFF. + // To add an arbitrary Unicode character, you need to convert it to code point first. // See https://stackoverflow.com/questions/9834964/char-to-unicode-more-than-uffff-in-java pin.addCharacter(pinCharacter.code) if (pin.length() == desiredPinLength) { @@ -1467,14 +1467,14 @@ class EnterPinScene(val desiredPinLength: Int = 4) { } fun onContinueAction(pin: Password) { - // Do something with entered pin... + // Do something with the entered pin... } } ``` ### Entering arbitrary password -Unfortunately, there's no simple solution for this scenario. It's quite difficult to re-implement the whole keyboard on your own, so we recommend you to keep using the system keyboard. You can still create the `Password` object from already entered string: +Unfortunately, there's no simple solution for this scenario. It's quite difficult to re-implement the whole keyboard on your own, so we recommend you keep using the system keyboard. You can still create the `Password` object from an already entered string: ```kotlin @@ -1487,9 +1487,9 @@ final Password password = new Password(passwordString); ``` -### Create password from data +### Create a password from the data -In case that passphrase is somehow created externally in form of array of bytes, then you can instantiate it from the `Data` object directly: +In case that passphrase is somehow created externally in the form of an array of bytes, then you can instantiate it from the `Data` object directly: ```kotlin @@ -1505,7 +1505,7 @@ final Password password = new Password(passwordData); ### Compare two passwords -To compare two passwords, use `isEqual(to:)` method: +To compare two passwords, use the `isEqual(to:)` method: ```kotlin @@ -1583,9 +1583,9 @@ PowerAuth SDK for Android provides an abstraction on top of the base Biometric A You have to check for Biometric Authentication on three levels: -- **System Availability**: If biometric scanner is present on the system. +- **System Availability**: If a biometric scanner is present on the system. - **Activation Availability**: If biometry factor data are available for given activation. -- **Application Availability**: If user decided to use biometric authentication for given app. _(optional)_ +- **Application Availability**: If the user decides to use biometric authentication for the given app. _(optional)_ PowerAuth SDK for Android provides code for the first and second of these checks. @@ -1659,7 +1659,7 @@ switch (BiometricAuthentication.getBiometryType(context)) { case BiometryType.NONE: // Biometry is not supported on the system. case BiometryType.GENERIC: - // It's not possible to determine exact type of biometry. + // It's not possible to determine the exact type of biometry. // This happens on Android 10+ systems, when the device supports // more than one type of biometric authentication. In this case, // you should use generic terms, like "Authenticate with biometry" @@ -1687,29 +1687,29 @@ boolean hasBiometryFactor = powerAuthSDK.hasBiometryFactor(context); ``` -The last check is fully under your control. By keeping the biometric settings flag, for example, a `BOOL` in `SharedPreferences`, you are able to show user an expected biometric authentication status (in a disabled state, though) even in the case biometric authentication is not enabled or when no fingers are enrolled on the device. +The last check is fully under your control. By keeping the biometric settings flag, for example, a `BOOL` in `SharedPreferences`, you can show the user an expected biometric authentication status (in a disabled state, though) even in the case biometric authentication is not enabled or when no fingers are enrolled on the device. ### Enable Biometric Authentication -In case an activation does not yet have biometry-related factor data, and you would like to enable biometric authentication support, the device must first retrieve the original private key from the secure vault for the purpose of key derivation. As a result, you have to use a successful 2FA with a password to enable biometric authentication support. +In case an activation does not yet have biometry-related factor data, and you would like to enable biometric authentication support, the device must first retrieve the original private key from the secure vault for key derivation. As a result, you have to use a successful 2FA with a password to enable biometric authentication support. Use the following code to enable biometric authentication using biometric authentication: ```kotlin -// Establish biometric data using provided password +// Establish biometric data using the provided password powerAuthSDK.addBiometryFactor(context, fragment, "Enable Biometric Authentication", "To enable biometric authentication, use the biometric sensor on your device.", "1234", object: IAddBiometryFactorListener { override fun onAddBiometryFactorSucceed() { // Everything went OK, biometric authentication is ready to be used } override fun onAddBiometryFactorFailed(error: PowerAuthErrorException) { - // Error occurred, report it to user + // Error occurred, report it to the user } }) ``` ```java -// Establish biometric data using provided password +// Establish biometric data using the provided password powerAuthSDK.addBiometryFactor(context, fragment, "Enable Biometric Authentication", "To enable biometric authentication, use the biometric sensor on your device.", "1234", new IAddBiometryFactorListener() { @Override public void onAddBiometryFactorSucceed() { @@ -1718,13 +1718,13 @@ powerAuthSDK.addBiometryFactor(context, fragment, "Enable Biometric Authenticati @Override public void onAddBiometryFactorFailed(@NonNull PowerAuthErrorException error) { - // Error occurred, report it to user + // Error occurred, report it to the user } }); ``` -By default, PowerAuth SDK asks user to authenticate with the biometric sensor also during the setup procedure (or during the [activation persist](#persisting-activation-data)). To alter this behavior, use the following code to change `PowerAuthKeychainConfiguration` provided to `PowerAuthSDK` instance: +By default, PowerAuth SDK asks the user to authenticate with the biometric sensor also during the setup procedure (or during the [activation persist](#persisting-activation-data)). To alter this behavior, use the following code to change the `PowerAuthKeychainConfiguration` provided to the `PowerAuthSDK` instance: ```kotlin @@ -1748,12 +1748,12 @@ PowerAuthSDK powerAuthSDK = new PowerAuthSDK.Builder(configuration) -Note that the RSA key-pair is internally generated for the configuration above. That may take more time on older devices than the default configuration. Your application should display a waiting indicator on its own, because SDK doesn't display an authentication dialog during the key-pair generation. +Note that the RSA key pair is internally generated for the configuration above. That may take more time on older devices than the default configuration. Your application should display a waiting indicator on its own because SDK doesn't display an authentication dialog during the key-pair generation. ### Disable Biometric Authentication -You can remove biometry related factor data used by biometric authentication support by simply removing the related key locally, using this one-liner: +You can remove biometric-related factor data used by biometric authentication support by simply removing the related key locally, using this one-liner: ```kotlin @@ -1767,18 +1767,18 @@ powerAuthSDK.removeBiometryFactor(context); ### Fetching the Biometry Factor-Related Key for Authentication -In order to obtain an encrypted biometry factor-related key for the purpose of authentication, call the following code: +To obtain an encrypted biometry factor-related key for authentication, call the following code: ```kotlin // Authenticate user with biometry and obtain encrypted biometry factor related key. powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the biometric sensor on your device to continue", object: IAuthenticateWithBiometricsListener { override fun onBiometricDialogCancelled(userCancel: Boolean) { - // User cancelled the operation + // User canceled the operation } override fun onBiometricDialogSuccess(authentication: PowerAuthAuthentication) { - // User authenticated use the provided authentication object for other tasks. + // User authenticated to use the provided authentication object for other tasks. } override fun onBiometricDialogFailed(error: PowerAuthErrorException) { @@ -1794,7 +1794,7 @@ powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the val localizedMessage = biometricErrorInfo.getLocalizedErrorMessage(context, null) } } else { - // Other reason for failure + // Other reasons for failure } } }) @@ -1804,12 +1804,12 @@ powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the biometric sensor on your device to continue", new IAuthenticateWithBiometricsListener() { @Override public void onBiometricDialogCancelled(boolean userCancel) { - // User cancelled the operation + // User canceled the operation } @Override public void onBiometricDialogSuccess(PowerAuthAuthentication authentication) { - // User authenticated use the provided authentication object for other tasks. + // User authenticated to use the provided authentication object for other tasks. } @Override @@ -1826,7 +1826,7 @@ powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the String localizedMessage = biometricErrorInfo.getLocalizedErrorMessage(context, null); } } else { - // Other reason for failure + // Other reasons for failure } } }); @@ -1834,7 +1834,7 @@ powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the -Note that if the biometric authentication fails with too many attempts in a row (e.g. biometry is temporarily or permanently locked out), then PowerAuth SDK will generate an invalid biometry factor related key and the success is reported. This is an intended behavior and as the result, it typically lead to unsuccessful authentication on the server and increased counter of failed attempts. The purpose of this is to limit the number of attempts for attacker to deceive the biometry sensor. +Note that if the biometric authentication fails with too many attempts in a row (e.g. biometry is temporarily or permanently locked out), then PowerAuth SDK will generate an invalid biometry factor related key, and the success is reported. This is an intended behavior and as a result, it typically leads to unsuccessful authentication on the server and an increased counter of failed attempts. The purpose of this is to limit the number of attempts for attackers to deceive the biometry sensor. ### Facial Biometrics on Android @@ -1849,11 +1849,11 @@ The Android source codes contain a list of devices with strong biometry support. ### Biometry Factor-Related Key Lifetime -By default, the biometry factor-related key is invalidated after the biometry enrolled in the system is changed. For example, if the user adds or removes the finger or enrolls with a new face, then the biometry factor-related key is no longer available for the signing operation. To change this behavior, you have to provide `PowerAuthKeychainConfiguration` object with `linkBiometricItemsToCurrentSet` parameter set to `false` and use that configuration for the `PowerAuthSDK` instance construction: +By default, the biometry factor-related key is invalidated after the biometry enrolled in the system is changed. For example, if the user adds or removes the finger or enrolls with a new face, then the biometry factor-related key is no longer available for the signing operation. To change this behavior, you have to provide the `PowerAuthKeychainConfiguration` object with the `linkBiometricItemsToCurrentSet` parameter set to `false` and use that configuration for the `PowerAuthSDK` instance construction: ```kotlin -// Use false for 'linkBiometricItemsToCurrentSet' parameter. +// Use false for the 'linkBiometricItemsToCurrentSet' parameter. val keychainConfig = PowerAuthKeychainConfiguration.Builder() .linkBiometricItemsToCurrentSet(false) .build() @@ -1863,7 +1863,7 @@ val powerAuthSDK = PowerAuthSDK.Builder(configuration) .build(getApplicationContext()) ``` ```java -// Use false for 'linkBiometricItemsToCurrentSet' parameter. +// Use false for the 'linkBiometricItemsToCurrentSet' parameter. PowerAuthKeychainConfiguration keychainConfig = new PowerAuthKeychainConfiguration.Builder() .linkBiometricItemsToCurrentSet(false) .build(); @@ -1878,7 +1878,7 @@ Be aware that the configuration above is effective only for the new keys. So, if ### Biometric Authentication Details -The `BiometricAuthentication` class is a high level interface that provides interfaces related to the biometric authentication for the SDK, or for the application purposes. The class hides all technical details, so it can be safely used also on the systems that doesn't provide biometric interfaces, or if the system has no biometric sensor available. The implementation under the hood uses `androidx.biometric.BiometricPrompt` and `androidx.biometric.BiometricManager` classes. +The `BiometricAuthentication` class is a high-level interface that provides interfaces related to biometric authentication for the SDK or for application purposes. The class hides all technical details, so it can be safely used also on systems that don't provide biometric interfaces, or if the system has no biometric sensor available. The implementation under the hood uses `androidx.biometric.BiometricPrompt` and `androidx.biometric.BiometricManager` classes. #### Customize Biometric Dialog Resources @@ -1927,7 +1927,7 @@ When the error dialog is disabled, your application should inform the user of th // Authenticate user with biometry and obtain encrypted biometry factor related key. powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the biometric sensor on your device to continue", object: IAuthenticateWithBiometricsListener { override fun onBiometricDialogCancelled(userCancel: Boolean) { - // User or system cancelled the operation + // User or system canceled the operation } override fun onBiometricDialogSuccess(authentication: PowerAuthAuthentication) { @@ -1938,11 +1938,11 @@ powerAuthSDK.authenticateUsingBiometrics(context, fragment, "Sign in", "Use the val biometricErrorInfo = error.additionalInformation as? BiometricErrorInfo if (biometricErrorInfo != null) { if (biometricErrorInfo.isErrorPresentationRequired) { - // Application should present reason of biometric authentication failure to the user + // Application should present the reason of biometric authentication failure to the user val localizedMessage = biometricErrorInfo.getLocalizedErrorMessage(context, null) } } else { - // Other reason for failure + // Other reasons for failure } } }) @@ -1954,11 +1954,11 @@ Note that you still should [Customize Biometric Dialog Resources](#customize-bio #### Biometric Authentication Confirmation -On Android 10+ systems, it's possible to configure `BiometricPrompt` to ask for an additional confirmation after the user is successfully authenticated. The default behavior for PowerAuth Mobile SDK is that such confirmation is not required. To change this behavior, you have to provide `PowerAuthKeychainConfiguration` object with `confirmBiometricAuthentication` parameter set to `true` and use that configuration for the `PowerAuthSDK` instance construction: +On Android 10+ systems, it's possible to configure `BiometricPrompt` to ask for an additional confirmation after the user is successfully authenticated. The default behavior for PowerAuth Mobile SDK is that such confirmation is not required. To change this behavior, you have to provide the `PowerAuthKeychainConfiguration` object with the `confirmBiometricAuthentication` parameter set to `true` and use that configuration for the `PowerAuthSDK` instance construction: ```kotlin -// Use true for 'confirmBiometricAuthentication' parameter. +// Use true for the 'confirmBiometricAuthentication' parameter. val keychainConfig = PowerAuthKeychainConfiguration.Builder() .confirmBiometricAuthentication(true) .build() @@ -1968,7 +1968,7 @@ val powerAuthSDK = PowerAuthSDK.Builder(configuration) .build(context) ``` ```java -// Use true for 'confirmBiometricAuthentication' parameter. +// Use true for the 'confirmBiometricAuthentication' parameter. PowerAuthKeychainConfiguration keychainConfig = new PowerAuthKeychainConfiguration.Builder() .confirmBiometricAuthentication(true) .build(); @@ -1985,7 +1985,7 @@ You can remove activation using several ways - the choice depends on the desired ### Simple Device-Only Removal -You can clear activation data anytime from the `SharedPreferences`. The benefit of this method is that it does not require help from the server, and the user does not have to be logged in. The issue with this removal method is simple: The activation still remains active on the server-side. This, however, does not have to be an issue in your case. +You can clear activation data anytime from the `SharedPreferences`. The benefit of this method is that it does not require help from the server, and the user does not have to be logged in. The issue with this removal method is simple: The activation still remains active on the server side. This, however, does not have to be an issue in your case. To remove only data related to PowerAuth SDK for Android, use the following code: @@ -2000,14 +2000,14 @@ powerAuthSDK.removeActivationLocal(context); ### Removal via Authenticated Session -Suppose your server uses an authenticated session for keeping the users logged in. In that case, you can combine the previous method with calling your proprietary endpoint to remove activation for the currently logged-in user. The advantage of this method is that activation does not remain active on the server. The issue is that the user has to be logged in (the session must be active and must have activation ID stored) and that you have to publish your own method to handle this use case. +Suppose your server uses an authenticated session to keep the users logged in. In that case, you can combine the previous method with calling your proprietary endpoint to remove activation for the currently logged-in user. The advantage of this method is that activation does not remain active on the server. The issue is that the user has to be logged in (the session must be active and must have an activation ID stored) and that you have to publish your own method to handle this use case. The code for this activation removal method is as follows: ```kotlin // Use custom call to proprietary server endpoint to remove activation. -// User must be logged in at this moment, so that session can find +//The user must be logged in at this moment, so that the session can find // associated activation ID this.httpClient.post(null, "/custom/activation/remove", object: ICustomListener { override fun onSucceed() { @@ -2015,13 +2015,13 @@ this.httpClient.post(null, "/custom/activation/remove", object: ICustomListener } override fun onFailed(t: Throwable) { - // Error occurred, report it to user + // Error occurred, report it to the user } }) ``` ```java // Use custom call to proprietary server endpoint to remove activation. -// User must be logged in at this moment, so that session can find +//The user must be logged in at this moment, so that the session can find // associated activation ID this.httpClient.post(null, "/custom/activation/remove", new ICustomListener() { @Override @@ -2031,7 +2031,7 @@ this.httpClient.post(null, "/custom/activation/remove", new ICustomListener() { @Override public void onFailed(Throwable t) { - // Error occurred, report it to user + // Error occurred, report it to the user } }); ``` @@ -2041,13 +2041,13 @@ this.httpClient.post(null, "/custom/activation/remove", new ICustomListener() { PowerAuth Standard RESTful API has a default endpoint `/pa/v3/activation/remove` for an activation removal. This endpoint uses a signature verification for looking up the activation to be removed. The benefit of this method is that it is already present in both PowerAuth SDK for Android and PowerAuth Standard RESTful API - nothing has to be programmed. Also, the user does not have to be logged in to use it. However, the user has to authenticate using 2FA with either password or biometric authentication. -Use the following code for an activation removal using signed request: +Use the following code for an activation removal using a signed request: ```kotlin val authentication = PowerAuthAuthentication.possessionWithPassword("1234") -// Remove activation using provided authentication object +// Remove activation using the provided authentication object powerAuthSDK.removeActivationWithAuthentication(context, authentication, object: IActivationRemoveListener { override fun onActivationRemoveSucceed() { // OK, activation was removed @@ -2061,7 +2061,7 @@ powerAuthSDK.removeActivationWithAuthentication(context, authentication, object: ```java PowerAuthAuthentication authentication = PowerAuthAuthentication.possessionWithPassword("1234"); -// Remove activation using provided authentication object +// Remove activation using the provided authentication object powerAuthSDK.removeActivationWithAuthentication(context, authentication, new IActivationRemoveListener() { @Override public void onActivationRemoveSucceed() { @@ -2084,7 +2084,7 @@ Currently, PowerAuth SDK supports two basic modes of end-to-end encryption, base - In an "activation" scope, the encryptor can be acquired only if `PowerAuthSDK` has a valid activation. The encryptor created for this mode is cryptographically bound to the parameters agreed during the activation process. You can combine this encryption with [PowerAuth Symmetric Multi-Factor Signature](#symmetric-multi-factor-signature) in "encrypt-then-sign" mode. -For both scenarios, you need to acquire `EciesEncryptor` object, which will then provide interface for the request encryption and the response decryption. The object currently provides only low level encryption and decryption methods, so you need to implement your own JSON (de)serialization and request and response processing. +For both scenarios, you need to acquire an `EciesEncryptor` object, which will then provide an interface for the request encryption and the response decryption. The object currently provides only low-level encryption and decryption methods, so you need to implement your own JSON (de)serialization and request and response processing. The following steps are typically required for a full E2EE request and response processing: @@ -2096,9 +2096,9 @@ The following steps are typically required for a full E2EE request and response val encryptor = powerAuthSDK.getEciesEncryptorForActivationScope(context) ``` -1. Serialize your request payload, if needed, into a sequence of bytes. This step typically means that you need to serialize your model object into a JSON formatted sequence of bytes. +1. Serialize your request payload, if needed, into a sequence of bytes. This step typically means that you need to serialize your model object into a JSON-formatted sequence of bytes. -1. Make sure that PowerAuth SDK instance has [time synchronized with the server](#synchronized-time): +1. Make sure that the PowerAuth SDK instance has [time synchronized with the server](#synchronized-time): ```kotlin val timeService = powerAuthSDK.timeSynchronizationService if (!timeService.isTimeSynchronized) { @@ -2122,7 +2122,7 @@ The following steps are typically required for a full E2EE request and response } ``` -1. Construct a JSON from provided cryptogram object. The dictionary with the following keys is expected: +1. Construct a JSON from the provided cryptogram object. The dictionary with the following keys is expected: - `ephemeralPublicKey` property fill with `cryptogram.getKeyBase64()` - `encryptedData` property fill with `cryptogram.getBodyBase64()` - `mac` property fill with `cryptogram.getMacBase64()` @@ -2133,7 +2133,7 @@ The following steps are typically required for a full E2EE request and response ```json { "ephemeralPublicKey" : "BASE64-DATA-BLOB", - "encryptedData": "BASE64-DATA-BLOB", + "encryptedData" : "BASE64-DATA-BLOB", "mac" : "BASE64-DATA-BLOB", "nonce" : "BASE64-NONCE", "timestamp" : 1694172789256 @@ -2147,7 +2147,7 @@ The following steps are typically required for a full E2EE request and response val httpHeaderName = metadata.httpHeaderKey val httpHeaderValue = metadata.httpHeaderValue ``` - Note, that if an "activation" scoped encryptor is combined with PowerAuth Symmetric Multi-Factor signature, then this step is not required. The signature's header already contains all information required for proper request decryption on the server. + Note, that if an "activation" scoped encryptor is combined with PowerAuth Symmetric Multi-Factor signature, then this step is not required. The signature's header already contains all the information required for proper request decryption on the server. 1. Fire your HTTP request and wait for a response - In case that non-200 HTTP status code is received, then the error processing is identical to a standard RESTful response defined in our protocol. So, you can expect a JSON object with `"error"` and `"message"` properties in the response. @@ -2155,7 +2155,7 @@ The following steps are typically required for a full E2EE request and response 1. Decrypt the response. The received JSON typically looks like this: ```json { - "encryptedData": "BASE64-DATA-BLOB", + "encryptedData" : "BASE64-DATA-BLOB", "mac" : "BASE64-DATA-BLOB", "nonce" : "BASE64-NONCE", "timestamp" : 1694172789256 @@ -2173,35 +2173,35 @@ The following steps are typically required for a full E2EE request and response 1. And finally, you can process your received response. -As you can see, the E2EE is quite a non-trivial task. We recommend contacting us before using an application-specific E2EE. We can provide you more support on a per-scenario basis, especially if we first understand what you try to achieve with end-to-end encryption in your application. +As you can see, the E2EE is quite a non-trivial task. We recommend contacting us before using an application-specific E2EE. We can provide you with more support on a per-scenario basis, especially if we first understand what you are trying to achieve with end-to-end encryption in your application. ## Secure Vault -PowerAuth SDK for Android has basic support for an encrypted secure vault. At this moment, the only supported method allows your application to establish an encryption / decryption key with a given index. The index represents a "key number" - your identifier for a given key. Different business logic purposes should have encryption keys with a different index value. +PowerAuth SDK for Android has basic support for an encrypted secure vault. At this moment, the only supported method allows your application to establish an encryption / decryption key with a given index. The index represents a "key number" - your identifier for a given key. Different business logic purposes should have encryption keys with different index values. -On a server side, all secure vault related work is concentrated in a `/pa/v3/vault/unlock` endpoint of PowerAuth Standard RESTful API. In order to receive data from this response, call must be authenticated with at least 2FA (using password or PIN). +On the server side, all secure vault-related work is concentrated in a `/pa/v3/vault/unlock` endpoint of PowerAuth Standard RESTful API. To receive data from this response, the call must be authenticated with at least 2FA (using a password or PIN). -Secure vault mechanism does not support biometry by default. Use PIN code or password based authentication for unlocking the secure vault, or ask your server developers to enable biometry for vault unlock call by configuring PowerAuth Server instance. +The secure vault mechanism does not support biometry by default. Use PIN code or password-based authentication for unlocking the secure vault, or ask your server developers to enable biometry for vault unlock call by configuring the PowerAuth Server instance. ### Obtaining Encryption Key -In order to obtain an encryption key with a given index, use the following code: +To obtain an encryption key with a given index, use the following code: ```kotlin -// 2FA signature. It uses device related key and user PIN code. +// 2FA signature. It uses device-related key and user PIN code. val authentication = PowerAuthAuthentication.possessionWithPassword("1234") // Select custom key index val index = 1000L -// Fetch encryption key with given index +// Fetch the encryption key with the given index powerAuthSDK.fetchEncryptionKey(context, authentication, index, object: IFetchEncryptionKeyListener { override fun onFetchEncryptionKeySucceed(encryptedEncryptionKey: ByteArray) { - // ... use encryption key to encrypt or decrypt data + // ... use the encryption key to encrypt or decrypt data } override fun onFetchEncryptionKeyFailed(t: Throwable) { @@ -2210,17 +2210,17 @@ powerAuthSDK.fetchEncryptionKey(context, authentication, index, object: IFetchEn }) ``` ```java -// 2FA signature. It uses device related key and user PIN code. +// 2FA signature. It uses device device-related key and user PIN code. PowerAuthAuthentication authentication = PowerAuthAuthentication.possessionWithPassword("1234"); // Select custom key index long index = 1000L; -// Fetch encryption key with given index +// Fetch the encryption key with the given index powerAuthSDK.fetchEncryptionKey(context, authentication, index, new IFetchEncryptionKeyListener() { @Override public void onFetchEncryptionKeySucceed(byte[] encryptedEncryptionKey) { - // ... use encryption key to encrypt or decrypt data + // ... use the encryption key to encrypt or decrypt data } @Override @@ -2233,7 +2233,7 @@ powerAuthSDK.fetchEncryptionKey(context, authentication, index, new IFetchEncryp ## Recovery Codes -The recovery codes allow your users to recover their activation in case that mobile device is lost or stolen. Before you start, please read the [Activation Recovery](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md) document, available in our [powerauth-crypto](https://github.com/wultra/powerauth-crypto) repository. +The recovery codes allow your users to recover their activation in case their device is lost or stolen. Before you start, please read the [Activation Recovery](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md) document, available in our [powerauth-crypto](https://github.com/wultra/powerauth-crypto) repository. To recover an activation, the user has to re-type two separate values: @@ -2243,20 +2243,20 @@ To recover an activation, the user has to re-type two separate values: PowerAuth currently supports two basic types of recovery codes: 1. Recovery Code bound to a previous PowerAuth activation. - - This type of code can be obtained only in an already activated application. + - This type of code can be obtained only in an already-activated application. - This type of code has only one PUK available, so only one recovery operation is possible. - The activation associated with the code is removed once the recovery operation succeeds. 2. Recovery Code delivered via OOB channel, typically in the form of a securely printed postcard, delivered by the post service. - This type of code has typically more than one PUK associated with the code, so it can be used multiple times. - - The user has to keep that postcard in safe and secure place, and mark already used PUKs. + - The user has to keep that postcard in a safe and secure place, and mark already used PUKs. - The code delivery must be confirmed by the user before the code can be used for a recovery operation. -The feature is not automatically available. It must be enabled and configured on PowerAuth Server. If it's so, then your mobile application can use several methods related to this feature. +The feature is not automatically available. It must be enabled and configured on the PowerAuth Server. If it's so, then your mobile application can use several methods related to this feature. ### Getting Recovery Data -If the recovery data was received during the activation process, then you can later display that information to the user. To check existence of recovery data and get that information, use the following code: +If the recovery data was received during the activation process, then you can later display that information to the user. To check the existence of recovery data and get that information, use the following code: ```kotlin @@ -2265,7 +2265,7 @@ if (!powerAuthSDK.hasActivationRecoveryData()) { return } -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code val authentication = PowerAuthAuthentication.possessionWithPassword("1234") powerAuthSDK.getActivationRecoveryData(context, authentication, object: IGetRecoveryDataListener { @@ -2286,7 +2286,7 @@ if (!powerAuthSDK.hasActivationRecoveryData()) { return; } -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code PowerAuthAuthentication authentication = PowerAuthAuthentication.possessionWithPassword("1234"); powerAuthSDK.getActivationRecoveryData(context, authentication, new IGetRecoveryDataListener() { @@ -2311,14 +2311,14 @@ The obtained information is very sensitive, so you should be very careful how yo - You should never print the values to the debug log. - You should never send the values over the network. - You should never copy the values to the clipboard. -- You should require PIN code every time to display the values on the screen. -- You should warn user that taking screenshot of the values is not recommended. +- You should require a PIN code every time to display the values on the screen. +- You should warn the user that taking a screenshot of the values is not recommended. - Do not cache the values in RAM. You should inform the user that: - Making a screenshot when values are displayed on the screen is dangerous. -- The user should write down that values on paper and keep it as much safe as possible for future use. +- The user should write down those values on paper and keep it as safe as possible for future use. ### Confirm Recovery Postcard @@ -2376,11 +2376,11 @@ The `alreadyConfirmed` boolean indicates that the code was already confirmed in The tokens are simple, locally cached objects, producing timestamp-based authorization headers. Be aware that tokens are NOT a replacement for general PowerAuth signatures. They are helpful in situations when the signatures are too heavy or too complicated for implementation. Each token has the following properties: -- It needs PowerAuth signature for its creation (e.g., you need to provide `PowerAuthAuthentication` object) +- It needs a PowerAuth signature for its creation (e.g., you need to provide a `PowerAuthAuthentication` object) - It has a unique identifier on the server. This identifier is not exposed to the public API, but you can reveal that value in the debugger. -- It has symbolic name (e.g., "MyToken") defined by the application programmer to identify already created tokens. +- It has a symbolic name (e.g., "MyToken") defined by the application programmer to identify already created tokens. - It can generate timestamp-based authorization HTTP headers. -- It can be used concurrently. Token's private data doesn't change in time. +- It can be used concurrently. Token's private data doesn't change over time. - The token is associated with the `PowerAuthSDK` instance. So, you can use the same symbolic name in multiple SDK instances, and each created token will be unique. - Tokens are persisted in the `KeychainFactory` service and cached in the memory. - Once the parent `PowerAuthSDK` instance loses its activation, all its tokens are removed from the local database. @@ -2391,7 +2391,7 @@ To get an access token, you can use the following code: ```kotlin -// 1FA signature, uses device related key +// 1FA signature - uses device-related key val authentication = PowerAuthAuthentication.possession() val cancelableTask = powerAuthSDK.tokenStore.requestAccessToken(context, "MyToken", authentication, object: IGetTokenListener { override fun onGetTokenSucceeded(powerAuthToken: PowerAuthToken) { @@ -2404,7 +2404,7 @@ val cancelableTask = powerAuthSDK.tokenStore.requestAccessToken(context, "MyToke }) ``` ```java -// 1FA signature, uses device related key +// 1FA signature - uses device-related key final PowerAuthAuthentication authentication = PowerAuthAuthentication.possession(); final PowerAuthTokenStore tokenStore = powerAuthSDK.getTokenStore(); final ICancelable task = tokenStore.requestAccessToken(context, "MyToken", authentication, new IGetTokenListener() { @@ -2421,7 +2421,7 @@ final ICancelable task = tokenStore.requestAccessToken(context, "MyToken", authe ``` -The request is performed synchronously or asynchronously depending on whether the token is locally cached on the device. You can test this situation by calling `tokenStore.hasLocalToken(context, "MyToken")`. If operation is asynchronous, then `requestAccessToken()` returns cancellable task. +The request is performed synchronously or asynchronously depending on whether the token is locally cached on the device. You can test this situation by calling `tokenStore.hasLocalToken(context, "MyToken")`. If the operation is asynchronous, then `requestAccessToken()` returns a cancellable task. ### Generating Authorization Header Use the following code to generate an authorization header: @@ -2445,7 +2445,7 @@ Once you have a `PowerAuthToken` object, then you can use also a synchronous cod ```kotlin val header: PowerAuthAuthorizationHttpHeader = token.generateHeader() if (header.isValid) { - // Header is valid, you can construct HTTP header... + // Header is valid, you can construct an HTTP header... val httpHeaderKey = header.key val httpHeaderValue = header.value } else { @@ -2455,7 +2455,7 @@ if (header.isValid) { ```java PowerAuthAuthorizationHttpHeader header = token.generateHeader(); if (header.isValid()) { - // Header is valid, you can construct HTTP header... + // Header is valid, you can construct an HTTP header... String httpHeaderKey = header.key; String httpHeaderValue = header.value; } else { @@ -2501,7 +2501,7 @@ powerAuthSDK.getTokenStore().removeAccessToken(context, "MyToken", new IRemoveTo ### Removing Token Locally -To remove token locally, you can simply use the following code: +To remove the token locally, you can simply use the following code: ```kotlin @@ -2527,7 +2527,7 @@ The `PowerAuthSDK` allows you to specify an external encryption key (called EEK The external encryption key has to be set before the activation is created, or can be added later. The internal state of `PowerAuthSDK` contains information that the factor keys are protected with EEK, so EEK must be known at the time of PowerAuth signature is calculated. You have three options on how to configure the key: 1. Assign EEK into `PowerAuthConfiguration.Builder` at the time of `PowerAuthSDK` object creation. - - This is the most convenient way of using EEK, but the key must be known at the time of `PowerAuthSDK` instantiation. + - This is the most convenient way of using EEK, but the key must be known at the time of the `PowerAuthSDK` instantiation. - Once the `PowerAuthSDK` instance creates a new activation, then the factor keys will be automatically protected with EEK. 2. Use `PowerAuthSDK.setExternalEncryptionKey()` to set EEK after the `PowerAuthSDK` instance is created. @@ -2535,11 +2535,11 @@ The external encryption key has to be set before the activation is created, or c - You can set the key in any `PowerAuthSDK` state, but be aware that the method will fail in case the instance has a valid activation that doesn't use EEK. - It's safe to set the same EEK multiple times. -3. Use `PowerAuthSDK.addExternalEncryptionKey()` to add EEK and protect the factor keys in case that `PowerAuthSDK` has already a valid activation. +3. Use `PowerAuthSDK.addExternalEncryptionKey()` to add EEK and protect the factor keys in case `PowerAuthSDK` has already a valid activation. - This method is useful in case `PowerAuthSDK` already has a valid activation, but it doesn't use EEK yet. - - The method automatically adds EEK into the internal configuration structure, but be aware, that all future `PowerAuthSDK` usages (e.g. after app restart) require to set EEK by configuration, or by the `setExternalEncryptionKey()` method. + - The method automatically adds EEK into the internal configuration structure, but be aware, that all future `PowerAuthSDK` usages (e.g. after app restart) require setting EEK by configuration, or by the `setExternalEncryptionKey()` method. -You can remove EEK from an existing activation if the key is no longer required. To do this, use `PowerAuthSDK.removeExternalEncryptionKey()` method. Be aware, that EEK must be set by configuration, or by the `setExternalEncryptionKey()` method before you call the remove method. You can also use the `PowerAuthSDK.hasExternalEncryptionKey()` function to test whether the key is already set and in use. +You can remove EEK from an existing activation if the key is no longer required. To do this, use the `PowerAuthSDK.removeExternalEncryptionKey()` method. Be aware, that EEK must be set by configuration, or by the `setExternalEncryptionKey()` method before you call the remove method. You can also use the `PowerAuthSDK.hasExternalEncryptionKey()` function to test whether the key is already set and in use. ## Synchronized Time @@ -2593,7 +2593,7 @@ if (timeService.isTimeSynchronized) { } ``` -The time service provides an additional information about time, such as how precisely the time is synchronized with the server: +The time service provides additional information about time, such as how precisely the time is synchronized with the server: ```kotlin if (timeService.isTimeSynchronized) { @@ -2602,20 +2602,20 @@ if (timeService.isTimeSynchronized) { } ``` -The precision value represents a maximum absolute deviation of synchronized time against the actual time on the server. For example, a value `500` means that time provided by `currentTime` method may be 0.5 seconds ahead or behind of the actual time on the server. If the precision is not sufficient for your purpose, for example, if you need to display a real-time countdown in your application, then try to synchronize the time manually. The precision basically depends on how quickly is the synchronization response received and processed from the server. A faster response results in higher precision. +The precision value represents a maximum absolute deviation of synchronized time against the actual time on the server. For example, a value `500` means that the time provided by the `currentTime` method may be 0.5 seconds ahead or behind the actual time on the server. If the precision is not sufficient for your purpose, for example, if you need to display a real-time countdown in your application, then try to synchronize the time manually. The precision basically depends on how quickly is the synchronization response received and processed from the server. A faster response results in higher precision. ## Common SDK Tasks ### Error Handling -The PowerAuth SDK is using the following types of exceptions: +The PowerAuth SDK uses the following types of exceptions: -- `PowerAuthMissingConfigException` - is typically thrown immediately when `PowerAuthSDK` instance is initialized with an invalid configuration. +- `PowerAuthMissingConfigException` - is typically thrown immediately when the `PowerAuthSDK` instance is initialized with an invalid configuration. - `FailedApiException` - is typically returned to callbacks when an asynchronous HTTP request ends on error. -- `ErrorResponseApiException` - is typically returned to callbacks when an asynchronous HTTP request ends on error and the error model object is present in the response. -- `PowerAuthErrorException` - typically covers all other erroneous situations. You can investigate a detailed reason of failure by getting the integer, from set of `PowerAuthErrorCodes` constants. +- `ErrorResponseApiException` - is typically returned to callbacks when an asynchronous HTTP request ends on an error and the error model object is present in the response. +- `PowerAuthErrorException` - typically covers all other erroneous situations. You can investigate a detailed reason for failure by getting the integer, from the set of `PowerAuthErrorCodes` constants. -Here's an example for a typical error handling procedure: +Here's an example of a typical error-handling procedure: ```kotlin @@ -2638,7 +2638,7 @@ when (t) { PowerAuthErrorCodes.BIOMETRY_LOCKOUT -> Log.d(TAG, "The biometric authentication is locked out due to too many failed attempts.") PowerAuthErrorCodes.OPERATION_CANCELED -> Log.d(TAG, "Error code for cancelled operations") PowerAuthErrorCodes.ENCRYPTION_ERROR -> Log.d(TAG, "Error code for errors related to end-to-end encryption") - PowerAuthErrorCodes.INVALID_TOKEN -> Log.d(TAG, "Error code for errors related to token based auth.") + PowerAuthErrorCodes.INVALID_TOKEN -> Log.d(TAG, "Error code for errors related to token-based auth.") PowerAuthErrorCodes.PROTOCOL_UPGRADE -> Log.d(TAG, "Error code for error that occurs when protocol upgrade fails at unrecoverable error.") PowerAuthErrorCodes.PENDING_PROTOCOL_UPGRADE -> Log.d(TAG, "The operation is temporarily unavailable, due to pending protocol upgrade.") PowerAuthErrorCodes.TIME_SYNCHRONIZATION -> Log.d(TAG, "Failed to synchronize time with the server.") @@ -2648,7 +2648,7 @@ when (t) { when (additionalInfo) { is BiometricErrorInfo -> { if (additionalInfo.isErrorPresentationRequired) { - // Application should display error dialog after failed biometric authentication. This is relevant only + //The application should display an error dialog after failed biometric authentication. This is relevant only // if you disabled the biometric error dialog provided by PowerAuth mobile SDK. } } @@ -2705,7 +2705,7 @@ if (t instanceof PowerAuthErrorException) { case PowerAuthErrorCodes.ENCRYPTION_ERROR: android.util.Log.d(TAG,"Error code for errors related to end-to-end encryption"); break; case PowerAuthErrorCodes.INVALID_TOKEN: - android.util.Log.d(TAG,"Error code for errors related to token based auth."); break; + android.util.Log.d(TAG,"Error code for errors related to token-based auth."); break; case PowerAuthErrorCodes.PROTOCOL_UPGRADE: android.util.Log.d(TAG,"Error code for error that occurs when protocol upgrade fails at unrecoverable error."); break; case PowerAuthErrorCodes.PENDING_PROTOCOL_UPGRADE: @@ -2717,7 +2717,7 @@ if (t instanceof PowerAuthErrorException) { if (exception.getAdditionalInformation() instanceof BiometricErrorInfo) { BiometricErrorInfo biometricErrorInfo = (BiometricErrorInfo) exception.getAdditionalInformation(); if (biometricErrorInfo.isErrorPresentationRequired()) { - // Application should display error dialog after failed biometric authentication. This is relevant only + //The application should display an error dialog after failed biometric authentication. This is relevant only // if you disabled the biometric error dialog provided by PowerAuth mobile SDK. } } @@ -2739,13 +2739,13 @@ if (t instanceof PowerAuthErrorException) { ``` -Note that you typically don't need to handle all error codes reported in the `PowerAuthErrorException`, or report all that situations to the user. Most of the codes are informational and help the developers properly integrate SDK into the application. A good example is `INVALID_ACTIVATION_STATE`, which typically means that your application's logic is broken and you're using PowerAuthSDK in an unexpected way. +Note that you typically don't need to handle all error codes reported in the `PowerAuthErrorException`, or report all those situations to the user. Most of the codes are informational and help the developers properly integrate SDK into the application. A good example is `INVALID_ACTIVATION_STATE`, which typically means that your application's logic is broken and you're using PowerAuthSDK in an unexpected way. Here's the list of important error codes, which the application should properly handle: - `BIOMETRY_CANCEL` is reported when the user cancels the biometric authentication dialog -- `PROTOCOL_UPGRADE` is reported when SDK failed to upgrade itself to a newer protocol version. The code may be reported from `PowerAuthSDK.fetchActivationStatusWithCallback()`. This is an unrecoverable error resulting in the broken activation on the device, so the best situation is to inform user about the situation and remove the activation locally. -- `PENDING_PROTOCOL_UPGRADE` is reported when the requested SDK operation cannot be completed due to a pending PowerAuth protocol upgrade. You can retry the operation later. The code is typically reported in the situations when SDK is performing protocol upgrade on the background (as a part of activation status fetch), and the application want's to calculate PowerAuth signature in parallel operation. Such kind of concurrency is forbidden since SDK version `1.0.0` +- `PROTOCOL_UPGRADE` is reported when SDK fails to upgrade itself to a newer protocol version. The code may be reported from `PowerAuthSDK.fetchActivationStatusWithCallback()`. This is an unrecoverable error resulting in the broken activation on the device, so the best situation is to inform the user about the situation and remove the activation locally. +- `PENDING_PROTOCOL_UPGRADE` is reported when the requested SDK operation cannot be completed due to a pending PowerAuth protocol upgrade. You can retry the operation later. The code is typically reported in situations when SDK is performing protocol upgrade in the background (as a part of activation status fetch), and the application wants to calculate the PowerAuth signature in parallel operation. Such kind of concurrency is forbidden since SDK version `1.0.0` ### Working with Invalid SSL Certificates @@ -2754,26 +2754,26 @@ Sometimes, you may need to develop or test your application against a service th ```kotlin -// Set `HttpClientSslNoValidationStrategy as the defauld client SSL certificate validation strategy +// Set `HttpClientSslNoValidationStrategy as the default client SSL certificate validation strategy val clientConfiguration = PowerAuthClientConfiguration.Builder() .clientValidationStrategy(HttpClientSslNoValidationStrategy()) .build() ``` ```java -// Set `HttpClientSslNoValidationStrategy as the defauld client SSL certificate validation strategy +// Set `HttpClientSslNoValidationStrategy as the default client SSL certificate validation strategy final PowerAuthClientConfiguration clientConfiguration = new PowerAuthClientConfiguration.Builder() .clientValidationStrategy(new HttpClientSslNoValidationStrategy()) .build(); ``` -Be aware, that using this option will lead to use an unsafe implementation of `HostnameVerifier` and `X509TrustManager` SSL client validation. This is useful for debug/testing purposes only, e.g. when untrusted self-signed SSL certificate is used on server side. +Be aware, that using this option will lead to the use of an unsafe implementation of `HostnameVerifier` and `X509TrustManager` SSL client validation. This is useful for debug/testing purposes only, e.g. when an untrusted self-signed SSL certificate is used on the server side. -It's strictly recommended to use this option only in debug flavours of your application. Deploying to production may cause "Security alert" in Google Developer Console. Please see [this](https://support.google.com/faqs/answer/7188426) and [this](https://support.google.com/faqs/answer/6346016) Google Help Center articles for more details. Beginning 1 March 2017, Google Play will block publishing of any new apps or updates that use such unsafe implementation of `HostnameVerifier`. +It's strictly recommended to use this option only in debug flavors of your application. Deploying to production may cause a "Security alert" in the Google Developer Console. Please see [this](https://support.google.com/faqs/answer/7188426) and [this](https://support.google.com/faqs/answer/6346016) Google Help Center articles for more details. Beginning 1 March 2017, Google Play will block the publishing of any new apps or updates that use such unsafe implementation of `HostnameVerifier`. -How to solve this problem for debug/production flavours in the Gradle build script: +How to solve this problem for debug/production flavors in the Gradle build script: -1. Define boolean type `buildConfigField` in the flavour configuration. +1. Define the boolean type `buildConfigField` in the flavor configuration. ```gradle productFlavors { production { @@ -2785,7 +2785,7 @@ How to solve this problem for debug/production flavours in the Gradle build scri } ``` -2. In code use this conditional initialization for `PowerAuthClientConfiguration.Builder` builder. +2. In code use this conditional initialization for the `PowerAuthClientConfiguration.Builder` builder. ```kotlin val clientBuilder = PowerAuthClientConfiguration.Builder() @@ -2817,7 +2817,7 @@ PowerAuthLog.setEnabled(true); ``` -To turn-on even more detailed log, use the following code: +To turn on an even more detailed log, use the following code: ```kotlin @@ -2828,7 +2828,7 @@ PowerAuthLog.setVerbose(true); ``` -Note that it's highly recommended to turn-on this feature only for `DEBUG` build of your application. For example: +Note that it's highly recommended to turn on this feature only for the `DEBUG` build of your application. For example: ```kotlin @@ -2909,7 +2909,7 @@ The obtained `UserInfo` object contains the following properties: | `gender` | `String` | The user's gender | | `birthdate` | `Date` | The user's birthday | | `zoneInfo` | `String` | The user's time zone, e.g. `Europe/Paris` or `America/Los_Angeles` | -| `locale` | `String` | The end-user's locale, represented as a BCP47 language tag3 | +| `locale` | `String` | The end-users locale, represented as a BCP47 language tag3 | | `address` | `UserAddress` | The user's preferred postal address | | `updatedAt` | `Date` | The time the user's information was last updated | | `allClaims` | `Map` | The full collection of claims received from the server | @@ -2927,12 +2927,12 @@ If the `address` is provided, then `UserAddress` contains the following properti | `allClaims` | `Map` | Full collection of claims received from the server | > Notes: -> 1. Value is false also when claim is not present in `allClaims` dictionary +> 1. Value is false also when a claim is not present in the `allClaims` dictionary > 2. Phone number is typically in E.164 format, for example `+1 (425) 555-1212` or `+56 (2) 687 2400` > 3. This is typically an ISO 639-1 Alpha-2 language code in lowercase and an ISO 3166-1 Alpha-2 country code in uppercase, separated by a dash. For example, `en-US` or `fr-CA` -Be aware that all properties in `UserInfo` and `UserAddress` objects are optional and the availability of information depends on actual implementation on the server. +Be aware that all properties in the `UserInfo` and `UserAddress` objects are optional and the availability of information depends on actual implementation on the server. @@ -2942,7 +2942,7 @@ Choosing a weak passphrase in applications with high-security demands can be pot ### Debug Build Detection -It is sometimes useful to switch PowerAuth SDK to a DEBUG build configuration to get more logs from the library. The DEBUG build is usually helpful during the application development, but on the other hand, it's highly unwanted in production applications. For this purpose, the `PowerAuthSDK.hasDebugFeatures()` method provides information whether the PowerAuth JNI library was compiled in DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG PowerAuth: +It is sometimes useful to switch PowerAuth SDK to a DEBUG build configuration to get more logs from the library. The DEBUG build is usually helpful during application development, but on the other hand, it's highly unwanted in production applications. For this purpose, the `PowerAuthSDK.hasDebugFeatures()` method provides information on whether the PowerAuth JNI library was compiled in the DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG PowerAuth: ```kotlin @@ -2967,7 +2967,7 @@ if (!BuildConfig.DEBUG) { The `PowerAuthClientConfiguration` can contain multiple request interceptor objects, allowing you to adjust all HTTP requests created by SDK, before execution. Currently, you can use the following two classes: -- `BasicHttpAuthenticationRequestInterceptor` to add basic HTTP authentication header to all requests +- `BasicHttpAuthenticationRequestInterceptor` to add a basic HTTP authentication header to all requests - `CustomHeaderRequestInterceptor` to add a custom HTTP header to all requests For example: @@ -2991,7 +2991,7 @@ We don't recommend implementing the `HttpRequestInterceptor` interface on your o ### Custom User-Agent -The `PowerAuthClientConfiguration` contains `userAgent` property that allows you to set a custom value for "User-Agent" HTTP request header for all requests initiated by the library: +The `PowerAuthClientConfiguration` contains the `userAgent` property that allows you to set a custom value for the "User-Agent" HTTP request header for all requests initiated by the library: ```kotlin diff --git a/docs/PowerAuth-SDK-for-iOS-Extensions.md b/docs/PowerAuth-SDK-for-iOS-Extensions.md index b08ecbf8..b0c97e4b 100644 --- a/docs/PowerAuth-SDK-for-iOS-Extensions.md +++ b/docs/PowerAuth-SDK-for-iOS-Extensions.md @@ -26,7 +26,7 @@ Related documents: ## Installation -This chapter describes how to get PowerAuth SDK for iOS and tvOS Extensions up and running in your app. In current version, you can choose between CocoaPods and manual library integration. Both types of installation will lead to your app extension linked with a dynamic library, provided by the `PowerAuth2ForExtensions.[xc]framework`. +This chapter describes how to get PowerAuth SDK for iOS and tvOS Extensions up and running in your app. In the current version, you can choose between CocoaPods and manual library integration. Both types of installation will lead to your app extension linked with a dynamic library, provided by the `PowerAuth2ForExtensions.[xc]framework`. To distinguish between SDKs, the following short terms will be used in this document: @@ -40,7 +40,7 @@ To distinguish between SDKs, the following short terms will be used in this docu $ gem install cocoapods ``` -To integrate PowerAuth library into your Xcode project using CocoaPods, specify it in your `Podfile`: +To integrate the PowerAuth library into your Xcode project using CocoaPods, specify it in your `Podfile`: ```ruby platform :ios, '11.0' @@ -60,20 +60,20 @@ $ pod install ### Manual -If you prefer not to use CocoaPods as dependency manager, you can integrate Extensions SDK into your project manually as a git [submodule](http://git-scm.com/docs/git-submodule). +If you prefer not to use CocoaPods as a dependency manager, you can integrate Extensions SDK into your project manually as a git [submodule](http://git-scm.com/docs/git-submodule). #### Git Submodules -The integration process is quite similar to integration of our IOS library: +The integration process is quite similar to the integration of our iOS library: -1. Open up Terminal.app and go to your top-level project directory and add the library as a submodule: +1. Open up the Terminal.app and go to your top-level project directory and add the library as a submodule: ```sh $ git submodule add https://github.com/wultra/powerauth-mobile-sdk.git PowerAuthLib $ git submodule update --init --recursive ``` - First command will clone PowerAuth SDK into `PowerAuthLib` folder and second, will update all nested submodules. We're expecting that you already did this when you integrated PowerAuth into your application. + The first command will clone PowerAuth SDK into the `PowerAuthLib` folder and the second, will update all nested submodules. We're expecting that you already did this when you integrated PowerAuth into your application. -2. Open the new `PowerAuthLib` folder, and go to `proj-xcode` sub-folder +2. Open the new `PowerAuthLib` folder, and go to the `proj-xcode` sub-folder 3. Drag the `PowerAuthExtensionSdk.xcodeproj` project file into **Project Navigator** of your application's Xcode project. It should appear nested underneath your application's blue project icon. 4. Select your application project in the Project Navigator to navigate to the target configuration window and select the extension's target under the **TARGETS** heading in the sidebar. 5. Now select **Build Phases** tab and expand **Target Dependencies** section. Click on the "Plus Sign" and choose **"PowerAuth2ForExtensions"** framework from the **"PowerAuthExtensionSdk"** project. @@ -83,15 +83,15 @@ The integration process is quite similar to integration of our IOS library: ## Configuration -The Extensions SDK shares several source codes and configuration principles with main iOS SDK. So, you can prepare the same set of constants as you're already using in your IOS application. The SDK provides just a limited functionality for app extension (for example, you cannot create an activation or calculate a full PowerAuth signature from an extension) and to do that it requires access to an activation data, created in the main application. +The Extensions SDK shares several source codes and configuration principles with the main iOS SDK. So, you can prepare the same set of constants as you're already using in your IOS application. The SDK provides just a limited functionality for app extension (for example, you cannot create an activation or calculate a full PowerAuth signature from an extension) and to do that it requires access to an activation data, created in the main application. ### Prepare Data Sharing -The App Extension normally doesn't have an access to data created by the main application, so the first step is to setup a data sharing for your project. +The App Extension normally doesn't have access to data created by the main application, so the first step is to set up data sharing for your project. #### Keychain Sharing -iOS SDK is storing its most sensitive data into the iOS keychain, so you need to configure the keychain sharing first. If you're not familiar with keychain sharing, then don't worry about that, the keychain is shared only between the vendor's applications. So the sensitive information is not exposed to 3rd party applications. +iOS SDK stores its most sensitive data into the iOS keychain, so you need to configure the keychain sharing first. If you're not familiar with keychain sharing, then don't worry about that, the keychain is shared only between the vendor's applications. So the sensitive information is not exposed to 3rd party applications. 1. Select your application project in the **Project Navigator** to navigate to the target configuration window and select the applications's target under the **TARGETS** heading in the sidebar. 2. Now select **Signing & Capabilities** tab and click **+ Capability** button. @@ -99,7 +99,7 @@ iOS SDK is storing its most sensitive data into the iOS keychain, so you need to 4. Click "+" in just created **Keychain Sharing** capability and Xcode will predefine first **Keychain Group** to your application's bundle name. Let's call this value as `KEYCHAIN_GROUP_NAME` -The predefined group is usually beneficial, because iOS is by default using that group for storing all keychain entries created in the application. So, If your application is already using PowerAuth and you're going to just add a support for extension, then this is the most simple way to setup a keychain sharing. +The predefined group is usually beneficial because iOS is by default using that group for storing all keychain entries created in the application. So, If your application is already using PowerAuth and you're going to just add extension support, then this is the most simple way to set up a keychain sharing. Now you have to do a similar setup for your application's extension: @@ -108,27 +108,27 @@ Now you have to do a similar setup for your application's extension: 6. Select **Signing & Capabilities** tab and click **+ Capability** button. 7. Find and add **Keychain Sharing** capability. 8. Click "+" in just created **Keychain Sharing** capability and add the same `KEYCHAIN_GROUP_NAME` as you did for the application's target. -9. (optional) Repeat steps 4 to 6 for all other extensions which suppose to use Extensions SDK. +9. (optional) Repeat steps 4 to 6 for all other extensions which supposed to use Extensions SDK. -Now you need to know your **Team ID** (the unique identifier assigned to your team by Apple). Unfortunately, the identifier is not simply visible in Xcode, sou you'll have to log in into the Apple's [development portal](http://developer.apple.com) and look for that identifier in your membership details page. +Now you need to know your **Team ID** (the unique identifier assigned to your team by Apple). Unfortunately, the identifier is not simply visible in Xcode, so you'll have to log in to Apple's [development portal](http://developer.apple.com) and look for that identifier on your membership details page. If you know the Team ID, then the final `KEYCHAIN_GROUP_IDENTIFIER` constant is composed as `TEAM_ID.KEYCHAIN_GROUP_NAME`. So, it should look like: `KTT00000MR.com.powerauth.demo.App`. #### App Groups -The PowerAuth SDK for iOS is using one boolean flag stored in the `UserDefaults` facility, to determine whether the application has been reinstalled. Unfortunately, `UserDefaults.standard` created by the application cannot be shared with the app extension, so you have to create a new application group to share that data. +The PowerAuth SDK for iOS is using one boolean flag stored in the `UserDefaults` facility, to determine whether the application has been reinstalled. Unfortunately, the `UserDefaults.standard` created by the application cannot be shared with the app extension, so you have to create a new application group to share that data. 1. Select your application project in the **Project Navigator** to navigate to the target configuration window and select the applications's target under the **TARGETS** heading in the sidebar. 2. Now select **Signing & Capabilities** tab and click **+ Capability** button. 3. Find and add **App Groups** capability. -3. Click "+" in just created **App Groups** capability and add group with desired identifier and turn this particular group ON (e.g. make sure that checkmark close to group's name is selected). Let's call this value as `APP_GROUP_IDENTIFIER`. If group already exists, then just click the checkmark to turn it ON. -4. Now switch to application's extension target, select **Capabilities** tab and also expand **App Groups** section. -5. Turn "ON" **App Groups** for extension and add app group with the same name as you did in step 3. +3. Click "+" in just created **App Groups** capability add a group with the desired identifier and turn this particular group ON (e.g. make sure that the checkmark close to the group's name is selected). Let's call this value `APP_GROUP_IDENTIFIER`. If the group already exists, then just click the checkmark to turn it ON. +4. Now switch to the application's extension target, select the **Capabilities** tab, and also expand the **App Groups** section. +5. Turn "ON" **App Groups** for extension and add an app group with the same name as you did in step 3. You can optionally check a troubleshooting section if you need to [migrate the keychain initialization flag](#userdefaults-migration) from standard user defaults to a shared one. -While all previous steps are optional, they are highly recommended. If the keychain is properly shared, then the Extension SDK can determine the status of the PowerAuth activation just from the content of keychain data. But still this has a drawback, because the keychain data persists between the application's reinstallation. As you can see, in couple of rare usage scenarios the extension may get an inaccurate information about the activation. +While all previous steps are optional, they are highly recommended. If the keychain is properly shared, then the Extension SDK can determine the status of the PowerAuth activation just from the content of keychain data. But still, this has a drawback, because the keychain data persists between the application's reinstallation. As you can see, in a couple of rare usage scenarios the extension may get inaccurate information about the activation. ### Configure PowerAuth for Extension @@ -151,7 +151,7 @@ class TodayViewController: UIViewController, NCWidgetProviding { config.appKey = "sbG8gd...MTIzNA==" config.appSecret = "aGVsbG...MTIzNA==" config.masterServerPublicKey = "MTIzNDU2Nz...jc4OTAxMg==" - // URL is optional, current version of Extensions SDK doesn't perform own networking. + // URL is optional, the current version of Extensions SDK doesn't perform its own networking. config.baseEndpointUrl = "https://localhost:8080/demo-server" let keychainConfig = PowerAuthKeychainConfiguration.sharedInstance() @@ -166,15 +166,15 @@ class TodayViewController: UIViewController, NCWidgetProviding { ``` -**IMPORTANT:** The configuration used above must match configuration used in the application otherwise your extension will never get a proper activation status. +**IMPORTANT:** The configuration used above must match the configuration used in the application otherwise your extension will never get a proper activation status. -The Extensions SDK doesn't provide a shared instance for `PowerAuthExtensionSDK` class and therefore you have to manage that instance on your own. The example above shows a beginning of simple controller implementing extension for Today Widget. For all other code examples, we're going to use `this.powerAuthExt` as properly initialized instance of `PowerAuthExtensionSDK` object. +The Extensions SDK doesn't provide a shared instance for the `PowerAuthExtensionSDK` class and therefore you have to manage that instance on your own. The example above shows the beginning of a simple controller implementing an extension for the Today Widget. For all other code examples, we're going to use `this.powerAuthExt` as a properly initialized instance of the `PowerAuthExtensionSDK` object. ## Getting Device Activation Status -Unlike the iOS SDK, the Extension SDK provides only a limited information about activation status. You can actually check only whether there's locally stored activation or not: +Unlike the iOS SDK, the Extension SDK provides only limited information about activation status. You can check only whether there's locally stored activation or not: ```swift if this.powerAuthExt.hasValidActivation() { @@ -186,57 +186,57 @@ if this.powerAuthExt.hasValidActivation() { ## Token-Based Authentication -**WARNING:** Before you start using access tokens, please visit our [wiki page for powerauth-crypto](https://github.com/wultra/powerauth-crypto/blob/develop/docs/MAC-Token-Based-Authentication.md) for more information about this feature. You can also check documentation about tokens available in [PowerAuth SDK for iOS](./PowerAuth-SDK-for-iOS.md#token-based-authentication). +**WARNING:** Before you start using access tokens, please visit our [wiki page for powerauth-crypto](https://github.com/wultra/powerauth-crypto/blob/develop/docs/MAC-Token-Based-Authentication.md) for more information about this feature. You can also check the documentation about tokens available in [PowerAuth SDK for iOS](./PowerAuth-SDK-for-iOS.md#token-based-authentication). ### Getting Token -To get an access token, you can use following code: +To get an access token, you can use the following code: ```swift if let token = this.powerAuthExt.tokenStore.localToken(withName: "MyToken") { - // you have a token which can generate authorization headers + // you have a token that can generate authorization headers } ``` -Note that token store also provides `requestAccessToken()` method, but that always returns `PowerAuthErrorCode.invalidToken` error. Unlike the iOS SDK API, you cannot get a token from the server from app extension. Only main application can do that and once the token is available, then it's also available for the app extension. Check PowerAuth SDK for iOS [documentation for more details](./PowerAuth-SDK-for-iOS.md#getting-token). +Note that the token store also provides the `requestAccessToken()` method, but that always returns the `PowerAuthErrorCode.invalidToken` error. Unlike the iOS SDK API, you cannot get a token from the server from the app extension. Only the main application can do that and once the token is available, then it's also available for the app extension. Check PowerAuth SDK for iOS [documentation for more details](./PowerAuth-SDK-for-iOS.md#getting-token). ### Generating Authorization Header -Once you have a `PowerAuthToken` object, use following code to generate an authorization header: +Once you have a `PowerAuthToken` object, use the following code to generate an authorization header: ```swift if let header = token.generateHeader() { let httpHeader = [ header.key : header.value ] // now you can attach that httpHeader to your HTTP request } else { - // in case of nil, token is no longer valid + // in case of nil, the token is no longer valid } ``` ### Removing Token Locally -The token store exposes `removeLocalToken()` method, but the implementation does nothing. +The token store exposes the `removeLocalToken()` method, but the implementation does nothing. ### Removing Token From the Server -The token store exposes `removeAccessToken()` method, but the implementation always returns `PowerAuthErrorCode.invalidToken` error. +The token store exposes the `removeAccessToken()` method, but the implementation always returns the `PowerAuthErrorCode.invalidToken` error. ## Common SDK Tasks ### Error Handling -You can follow the same practices as for iOS SDK because Extensions SDK codebase is sharing the same error constants with a full PowerAuth SDK for iOS. +You can follow the same practices as for iOS SDK because the Extensions SDK codebase shares the same error constants with a full PowerAuth SDK for iOS. ### Debug Build Detection It is sometimes useful to switch Extensions SDK to a DEBUG build configuration, to get more logs from the library: - **CocoaPods:** we currently don't provide DEBUG pod. This will be resolved in some future versions of Extensions SDK. -- **Manual installation:** Xcode is matching build configuration across all nested projects, so you usually don't need to care about the configuration switching. +- **Manual installation:** Xcode matches build configuration across all nested projects, so you usually don't need to care about the configuration switching. -The DEBUG build is usually helpful during the application development, but on other side, it's highly unwanted in production applications. For this purpose, the `PowerAuthSystem.isInDebug()` method provides an information, whether the PowerAuth for Extensions library was compiled in DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG library: +The DEBUG build is usually helpful during application development, but on the other side, it's highly unwanted in production applications. For this purpose, the `PowerAuthSystem.isInDebug()` method provides information on whether the PowerAuth for Extensions library was compiled in DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG library: ```swift #if YOUR_APPSTORE_BUILD_FLAG @@ -249,11 +249,11 @@ The DEBUG build is usually helpful during the application development, but on ot ## Troubleshooting -This section of document contains a various workarounds and tips for Extensions SDK usage. +This section of the document contains various workarounds and tips for Extensions SDK usage. ### UserDefaults Migration -If your previous version of application did not use shared data between application and the extension, then you probably need to migrate keychain status flag from `UserDefaults.standard` to shared one. We recommend to perform this migration at the main application's startup code and **BEFORE** the `PowerAuthSDK` object is configured and used: +If your previous version of the application did not use shared data between the application and the extension, then you probably need to migrate the keychain status flag from `UserDefaults.standard` to a shared one. We recommend performing this migration at the main application's startup code and **BEFORE** the `PowerAuthSDK` object is configured and used: ```swift private func migrateUserDefaults() { diff --git a/docs/PowerAuth-SDK-for-iOS.md b/docs/PowerAuth-SDK-for-iOS.md index dfa1827f..2c7a3473 100644 --- a/docs/PowerAuth-SDK-for-iOS.md +++ b/docs/PowerAuth-SDK-for-iOS.md @@ -80,7 +80,7 @@ To simplify the documentation, we'll use **iOS** for the rest of the documentati $ gem install cocoapods ``` -To integrate PowerAuth library into your Xcode project using CocoaPods, specify it in your `Podfile`: +To integrate the PowerAuth library into your Xcode project using CocoaPods, specify it in your `Podfile`: ```ruby platform :ios, '11.0' @@ -111,23 +111,23 @@ If you prefer not to use CocoaPods as a dependency manager, you can integrate Po $ git submodule add https://github.com/wultra/powerauth-mobile-sdk.git PowerAuthLib $ git submodule update --init --recursive ``` - The first command will clone PowerAuth SDK into `PowerAuthLib` folder, and the second will update all nested submodules. + The first command will clone PowerAuth SDK into the `PowerAuthLib` folder, and the second will update all nested submodules. -2. Open the new `PowerAuthLib` folder, and go to `proj-xcode` sub-folder +2. Open the new `PowerAuthLib` folder, and go to the `proj-xcode` sub-folder 3. Drag the `PowerAuthLib.xcodeproj` project file into **Project Navigator** of your application's Xcode project. It should appear nested underneath your application's blue project icon. 4. Select your application project in the Project Navigator to navigate to the target configuration window and select the extension's target under the **TARGETS** heading in the sidebar. -5. Now select **Build Phases** tab and expand the **Target Dependencies** section. Click on the "Plus Sign" and choose **"PowerAuth2"** framework from the **"PowerAuthLib"** project. -6. Next, in the same **Build Phases** tab, expand **Link With Libraries** section. Click on the "Plus Sign" and choose **"PowerAuth2.framework"** from the **"PowerAuthLib"** project. +5. Now select **Build Phases** tab and expand the **Target Dependencies** section. Click on the "Plus Sign" and choose the **"PowerAuth2"** framework from the **"PowerAuthLib"** project. +6. Next, in the same **Build Phases** tab, expand **Link With Libraries** section. Click on the "Plus Sign" and choose the **"PowerAuth2.framework"** from the **"PowerAuthLib"** project. ### Carthage We provide limited and experimental support for the [Carthage dependency manager](https://github.com/Carthage/Carthage). The current problem with Carthage is that we cannot specify which Xcode project and which scheme has to be used for a particular library build. It kind of works automatically, but the build process is extremely slow. So, if you still want to try to integrate our library with Carthage, try the following tips: -- Add `github "wultra/powerauth-mobile-sdk" "develop"` into your `Cartfile`. You can alternatively use any `release/X.Y.x` branch, greater or equal than `release/1.6.x`. +- Add `github "wultra/powerauth-mobile-sdk" "develop"` into your `Cartfile`. You can alternatively use any `release/X.Y.x` branch, greater or equal to `release/1.6.x`. - It's recommended to force Carthage to use submodules for the library code checkouts. - It's recommended to force Carthage to use XCFrameworks. -- It's recommended to update only iOS platform (if possible). So try to run something like this: `carthage update --use-xcframeworks --use-submodules --platform ios` -- If build fails on broken project `PowerAuthLib.xcodeproj` then go to `{your_project}/Carthage/Checkouts/powerauth-mobile-sdk/proj-xcode` and delete `PowerAuthLib.xcodeproj` folder. This is because git doesn't delete empty folders by default and we have removed that XCode project from the source control. +- It's recommended to update only the iOS platform (if possible). So try to run something like this: `carthage update --use-xcframeworks --use-submodules --platform ios` +- If the build fails on broken project `PowerAuthLib.xcodeproj` then go to `{your_project}/Carthage/Checkouts/powerauth-mobile-sdk/proj-xcode` and delete the `PowerAuthLib.xcodeproj` folder. This is because git doesn't delete empty folders by default and we have removed that XCode project from the source control. - Drop `PowerAuth2.xcframework` and `PowerAuthCore.xcframework` into your project. ## Configuration @@ -177,8 +177,8 @@ func application(_ application: UIApplication, didFinishLaunchingWithOptions lau - `offlineSignatureComponentLength` - Alters the default component length for the [offline signature](#symmetric-offline-multi-factor-signature). The values between 4 and 8 are allowed. The default value is 8. - `externalEncryptionKey` - See [External Encryption Key](#external-encryption-key) chapter for more details. -- `keychainKey_Biometry` - Specifies 'key' used to store this PowerAuthSDK instance biometry related key in the biometry key keychain. If not set, then `instanceId` is applied. -- `disableAutomaticProtocolUpgrade` - If set to `true`, then automatic protocol upgrade is disabled. This option should be used only for the debugging purposes. +- `keychainKey_Biometry` - Specifies the 'key' used to store this PowerAuthSDK instance biometry-related key in the biometry key keychain. If not set, then `instanceId` is applied. +- `disableAutomaticProtocolUpgrade` - If set to `true`, then automatic protocol upgrade is disabled. This option should be used only for debugging purposes. ## Activation @@ -194,7 +194,7 @@ Use the following code to create an activation once you have an activation code: let deviceName = "Petr's iPhone 7" // or UIDevice.current.name let activationCode = "VVVVV-VVVVV-VVVVV-VTFVA" // let user type or QR-scan this value -// Create activation object with given activation code. +// Create an activation object with the given activation code. guard let activation = try? PowerAuthActivation(activationCode: activationCode, name: deviceName) else { // Activation code is invalid } @@ -203,15 +203,15 @@ guard let activation = try? PowerAuthActivation(activationCode: activationCode, powerAuthSDK.createActivation(activation) { (result, error) in if error == nil { // No error occurred, proceed to credentials entry (PIN prompt, Enable Touch ID switch, ...) and persist - // The 'result' contains 'activationFingerprint' property, representing the device public key - it may be used as visual confirmation - // If server supports recovery codes for activations, then `activationRecovery` property contains object with information about activation recovery. + // The 'result' contains the 'activationFingerprint' property, representing the device public key - it may be used as visual confirmation + // If the server supports recovery codes for activations, then the `activationRecovery` property contains an object with information about activation recovery. } else { // Error occurred, report it to the user } } ``` -If the received activation result also contains recovery data, then you should display that values to the user. To do that, please read the [Getting Recovery Data](#getting-recovery-data) section of this document, which describes how to treat that sensitive information. This is relevant for all types of activation you use. +If the received activation result also contains recovery data, then you should display those values to the user. To do that, please read the [Getting Recovery Data](#getting-recovery-data) section of this document, which describes how to treat that sensitive information. This is relevant for all types of activation you use. #### Additional Activation OTP @@ -222,7 +222,7 @@ let deviceName = "Petr's iPhone 7" // or UIDevice.current.name let activationCode = "VVVVV-VVVVV-VVVVV-VTFVA" // let user type or QR-scan this value let activationOtp = "12345" -// Create activation object with given activation code. +// Create an activation object with the given activation code. guard let activation = try? PowerAuthActivation(activationCode: activationCode, name: deviceName)? .with(additionalActivationOtp: activationOtp) else { // Activation code is invalid @@ -248,7 +248,7 @@ let credentials = [ "password": "YBzBEM" ] -// Create activation object with given credentials. +// Create an activation object with the given credentials. guard let activation = try? PowerAuthActivation(identityAttributes: credentials, name: deviceName) else { // Activation credentials are empty } @@ -258,21 +258,21 @@ powerAuthSDK.createActivation(activation) { (result, error) in if error == nil { // No error occurred, proceed to credentials entry (PIN prompt, Enable Touch ID switch, ...) and persist // The 'result' contains 'activationFingerprint' property, representing the device public key - it may be used as visual confirmation - // If server supports recovery codes for activations, then `activationRecovery` property contains object with information about activation recovery. + // If the server supports recovery codes for activations, then the `activationRecovery` property contains an object with information about activation recovery. } else { // Error occurred, report it to the user } } ``` -Note that by using weak identity attributes to create an activation, the resulting activation is confirming a "blurry identity". This may greatly limit the legal weight and usability of a signature. We recommend using a strong identity verification before activation can actually be created. +Note that by using weak identity attributes to create an activation, the resulting activation confirms a "blurry identity". This may greatly limit the legal weight and usability of a signature. We recommend using a strong identity verification before activation can actually be created. ### Activation via Recovery Code -If PowerAuth Server is configured to support [Recovery Codes](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md), then also you can create an activation via the recovery code and PUK. +If the PowerAuth Server is configured to support [Recovery Codes](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md), then also you can create an activation via the recovery code and PUK. -Use the following code to create an activation using recovery code: +Use the following code to create an activation using the recovery code: ```swift let deviceName = "John Tramonta" // or UIDevice.current.name @@ -288,28 +288,28 @@ guard let activation = try? PowerAuthActivation(recoveryCode: recoveryCode, reco powerAuthSDK.createActivation(activation) { (result, error) in if let error = error { // Error occurred, report it to the user - // On top of a regular error processing, you should handle a special situation, when server gives an additional information - // about which PUK must be used for the recovery. The information is valid only when recovery code from a postcard is applied. + // On top of regular error processing, you should handle a special situation, when the server gives an additional information + // about which PUK must be used for the recovery. The information is valid only when a recovery code from a postcard is applied. if let responseError = (error.userInfo[PowerAuthErrorDomain] as? PowerAuthRestApiErrorResponse)?.responseObject { let currentRecoveryPukIndex = responseError.currentRecoveryPukIndex if currentRecoveryPukIndex > 0 { - // The PUK index is known, you should inform user that it has to rewrite PUK from a specific position. + // The PUK index is known, you should inform the user that it has to rewrite PUK from a specific position. } } } else { // No error occurred, proceed to credentials entry (PIN prompt, Enable Touch ID switch, ...) and persist - // The 'result' contains 'activationFingerprint' property, representing the device public key - it may be used as visual confirmation - // If server supports recovery codes for activations, then `activationRecovery` property contains object with information about activation recovery. + // The 'result' contains the 'activationFingerprint' property, representing the device public key - it may be used as visual confirmation + // If the server supports recovery codes for activations, then the `activationRecovery` property contains an object with information about activation recovery. } } ``` ### Customize Activation -You can set an additional properties to `PowerAuthActivation` object, before any type of activation is created. For example: +You can set additional properties to the `PowerAuthActivation` object before any type of activation is created. For example: ```swift -// Custom attributes that can be processed before the activation is created on PowerAuth Server. +// Custom attributes that can be processed before the activation is created on the PowerAuth Server. // The dictionary may contain only values that can be serialized to JSON. let customAttributes: [String:Any] = [ "isNowPrimaryActivation" : true, @@ -319,7 +319,7 @@ let customAttributes: [String:Any] = [ ] ] -// Extra flags that will be associated with the activation record on PowerAuth Server. +// Extra flags that will be associated with the activation record on the PowerAuth Server. let extraFlags = "EXTRA_FLAGS" // Now create the activation object with all that extra data @@ -337,13 +337,13 @@ powerAuthSDK.createActivation(activation) { (result, error) in ### Persisting Activation Data -After you create an activation using one of the methods mentioned above, you need to persist the activation - to use provided user credentials to store the activation data on the device. Use the following code to do this: +After you create an activation using one of the methods mentioned above, you need to persist the activation - to use the provided user credentials to store the activation data on the device. Use the following code to do this: ```swift do { try powerAuthSDK.persistActivation(withPassword: "1234") } catch _ { - // happens only in case SDK was not configured or activation is not in state to be persisted + // happens only in case SDK was not configured or activation is not in a state to be persisted } ``` @@ -355,14 +355,14 @@ do { try powerAuthSDK.persistActivation(with: auth) } catch _ { - // happens only in case SDK was not configured or activation is not in state to be persisted + // happens only in case SDK was not configured or activation is not in a state to be persisted } ``` ### Validating User Inputs -The mobile SDK is providing a couple of functions in `PowerAuthActivationCodeUtil` interface, helping with user input validation. You can: +The mobile SDK provides a couple of functions in the `PowerAuthActivationCodeUtil` interface, helping with user input validation. You can: - Parse activation code when it's scanned from QR code - Validate a whole code at once @@ -371,7 +371,7 @@ The mobile SDK is providing a couple of functions in `PowerAuthActivationCodeUti #### Validating Scanned QR Code -To validate an activation code scanned from QR code, you can use `PowerAuthActivationCodeUtil.parse(fromActivationCode:)` function. You have to provide the code with or without the signature part. For example: +To validate an activation code scanned from the QR code, you can use the `PowerAuthActivationCodeUtil.parse(fromActivationCode:)` function. You have to provide the code with or without the signature part. For example: ```swift let scannedCode = "VVVVV-VVVVV-VVVVV-VTFVA#aGVsbG8......gd29ybGQ=" @@ -398,7 +398,7 @@ if !powerAuthSDK.verifyServerSignedData(otp.activationCode.data(using: .utf8)!, #### Validating Entered Activation Code -To validate an activation code at once, you can call `PowerAuthActivationCodeUtil.validateActivationCode()` function. You have to provide the code without the signature part. For example: +To validate an activation code at once, you can call the `PowerAuthActivationCodeUtil.validateActivationCode()` function. You have to provide the code without the signature part. For example: ```swift let isValid = PowerAuthActivationCodeUtil.validateActivationCode("VVVVV-VVVVV-VVVVV-VTFVA") @@ -409,14 +409,14 @@ If your application is using your own validation, then you should switch to func #### Validating Recovery Code and PUK -To validate a recovery code at once, you can call `PowerAuthActivationCodeUtil.validateRecoveryCode()` function. You can provide the whole code, which may or may not contain `"R:"` prefix. So, you can validate manually entered codes, but also codes scanned from QR. For example: +To validate a recovery code at once, you can call the `PowerAuthActivationCodeUtil.validateRecoveryCode()` function. You can provide the whole code, which may or may not contain `"R:"` prefix. So, you can validate manually entered codes, but also codes scanned from QR. For example: ```swift let isValid1 = PowerAuthActivationCodeUtil.validateRecoveryCode("VVVVV-VVVVV-VVVVV-VTFVA") let isValid2 = PowerAuthActivationCodeUtil.validateRecoveryCode("R:VVVVV-VVVVV-VVVVV-VTFVA") ``` -To validate PUK at once, you can call `PowerAuthActivationCodeUtil.validateRecoveryPuk()` function: +To validate PUK at once, you can call the `PowerAuthActivationCodeUtil.validateRecoveryPuk()` function: ```swift let isValid = PowerAuthActivationCodeUtil.validateRecoveryPuk("0123456789") @@ -424,9 +424,9 @@ let isValid = PowerAuthActivationCodeUtil.validateRecoveryPuk("0123456789") #### Auto-Correcting Typed Characters -You can implement auto-correcting of typed characters with using `PowerAuthActivationCodeUtil.validateAndCorrectTypedCharacter()` function in screens, where user is supposed to enter an activation or recovery code. This technique is possible due to the fact that Base32 is constructed so that it doesn't contain visually confusing characters. For example, `1` (number one) and `I` (capital I) are confusing, so only `I` is allowed. The benefit is that the provided function can correct typed `1` and translate it to `I`. +You can implement auto-correcting of typed characters by using the `PowerAuthActivationCodeUtil.validateAndCorrectTypedCharacter()` function on screens, where the user is supposed to enter an activation or recovery code. This technique is possible because Base32 is constructed so that it doesn't contain visually confusing characters. For example, `1` (number one) and `I` (capital I) are confusing, so only `I` is allowed. The benefit is that the provided function can correct typed `1` and translate it to `I`. -Here's an example how to iterate over the string and validate it character by character: +Here's an example of how to iterate over the string and validate it character by character: ```swift /// Returns corrected character or nil in case of error. @@ -447,7 +447,7 @@ func validateAndCorrectCharacters(_ string: String) -> String? { ## Requesting Activation Status -To obtain a detailed activation status information, use the following code: +To obtain detailed activation status information, use the following code: ```swift // Check if there is some activation on the device @@ -472,7 +472,7 @@ if powerAuthSDK.hasValidActivation() { print("Activation is blocked") case .removed: // Activation is no longer valid on the server. - // You can inform user about this situation and remove + // You can inform the user about this situation and remove // activation locally. print("Activation is no longer valid") powerAuthSDK.removeActivationLocal() @@ -506,7 +506,7 @@ if powerAuthSDK.hasValidActivation() { } } else { - // No activation present on device + // No activation present on the device } ``` @@ -525,26 +525,26 @@ The main feature of the PowerAuth protocol is data signing. PowerAuth has three ### Symmetric Multi-Factor Signature -To sign request data, you need to first obtain user credentials (password, PIN code, Touch ID scan) from the user. The task of obtaining the user credentials is used in more use-cases covered by the SDK. The core class is `PowerAuthAuthentication` that holds information about the used authentication factors: +To sign request data, you need to first obtain user credentials (password, PIN code, Touch ID scan) from the user. The task of obtaining the user credentials is used in more use cases covered by the SDK. The core class is `PowerAuthAuthentication` that holds information about the used authentication factors: ```swift -// 1FA signature, uses device related key only. +// 1FA signature - uses device-related key only. let oneFactor = PowerAuthAuthentication.possession() -// 2FA signature, uses device related key and user PIN code. +// 2FA signature - uses device-related key and user PIN code. let twoFactorPassword = PowerAuthAuthentication.possessionWithPassword(password: "1234") -// 2FA signature, uses biometry factor-related key as a 2nd. factor. +// 2FA signature - uses biometry factor-related key as a 2nd. factor. let twoFactorBiometry = PowerAuthAuthentication.possessionWithBiometry() // Alternative biometry authentications with prompt let twoFactorBiometryPrompt = PowerAuthAuthentication.possessionWithBiometry(prompt: "Please authenticate with biometry to log-in.") ``` -When signing `POST`, `PUT` or `DELETE` requests, use request body bytes (UTF-8) as request data and the following code: +When signing `POST`, `PUT`, or `DELETE` requests, use request body bytes (UTF-8) as request data and the following code: ```swift -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") // Sign POST call with provided data made to URI with custom identifier "/payment/create" @@ -553,14 +553,14 @@ do { let httpHeaderKey = signature.key let httpHeaderValue = signature.value } catch _ { - // In case of invalid configuration, invalid activation state or corrupted state data + // In case of invalid configuration, invalid activation state, or corrupted state data } ``` -When signing `GET` requests, use the same code as above with normalized request data as described in specification, or (preferably) use the following helper method: +When signing `GET` requests, use the same code as above with normalized request data as described in the specification, or (preferably) use the following helper method: ```swift -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") // Sign GET call with provided query parameters made to URI with custom identifier "/payment/create" @@ -574,15 +574,15 @@ do { let httpHeaderKey = signature.key let httpHeaderValue = signature.value } catch _ { - // In case of invalid configuration, invalid activation state or corrupted state data + // In case of invalid configuration, invalid activation state, or corrupted state data } ``` #### Request Synchronization -It is recommended that your application executes only one signed request at the time. The reason for that is that our signature scheme is using a counter as a representation of logical time. In other words, the order of request validation on the server is very important. If you issue more that one signed request at the same time, then the order is not guaranteed and therefore one from the requests may fail. On top of that, Mobile SDK itself is using this type of signatures for its own purposes. For example, if you ask for token, then the SDK is using signed request to obtain the token's data. To deal with this problem, Mobile SDK is providing a few methods which helps with the signed requests synchronization. +It is recommended that your application executes only one signed request at a time. The reason for that is that our signature scheme uses a counter as a representation of logical time. In other words, the order of request validation on the server is very important. If you issue more than one signed request at the same time, then the order is not guaranteed, and therefore one of the requests may fail. On top of that, Mobile SDK itself is using this type of signature for its purposes. For example, if you ask for a token, then the SDK is using a signed request to obtain the token's data. To deal with this problem, Mobile SDK is providing a few methods that help with the signed requests synchronization. -If your networking is based on `OperationQueue`, then you can add your own `Operation` objects directly to the internal queue. Be aware that PowerAuth signature, must be calculated as a part of operation's execution. For example: +If your networking is based on `OperationQueue`, then you can add your own `Operation` objects directly to the internal queue. Be aware that the PowerAuth signature must be calculated as a part of the operation's execution. For example: ```swift let httpOperation: Operation = YourHttpOperation(...) @@ -591,13 +591,13 @@ guard powerAuthSDK.executeOperation(onSerialQueue: httpOperation) else { } ``` -In case of custom networking, you can use method to execute any block on the serial queue. In this case, PowerAuth signature must be calculated as a part of block's execution. For example: +In the case of custom networking, you can use the method to execute any block on the serial queue. In this case, the PowerAuth signature must be calculated as a part of the block's execution. For example: ```swift powerAuthSDK.executeBlock(onSerialQueue: { internalTask in yourNetworking.post(yourRequest, completionHandler: { (data, response, error) in // Your response processing... - // No matter what happens, you have to call task.cancel() at the end + // No matter what happens, you have to call the task.cancel() at the end internalTask.cancel() }, cancelationHandler: { // In case that your networking cancels the request, the given task @@ -609,15 +609,15 @@ powerAuthSDK.executeBlock(onSerialQueue: { internalTask in ### Asymmetric Private Key Signature -Asymmetric Private Key Signature uses a private key stored in the PowerAuth secure vault. In order to unlock the secure vault and retrieve the private key, the user has to first authenticate using the symmetric multi-factor signature with at least two factors. This mechanism protects the private key on the device - the server plays a role of a "doorkeeper" and holds the vault unlock key. +Asymmetric Private Key Signature uses a private key stored in the PowerAuth secure vault. To unlock the secure vault and retrieve the private key, the user has to first authenticate using the symmetric multi-factor signature with at least two factors. This mechanism protects the private key on the device - the server plays the role of a "doorkeeper" and holds the vault unlock key. This process is completely transparent on the SDK level. To compute an asymmetric private key signature, request user credentials (password, PIN) and use the following code: ```swift -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") -// Unlock the secure vault, fetch the private key and perform data signing +// Unlock the secure vault, fetch the private key, and perform data signing powerAuthSDK.signData(withDevicePrivateKey: auth, data: data) { (signature, error) in if error == nil { // Send data and signature to the server @@ -629,7 +629,7 @@ powerAuthSDK.signData(withDevicePrivateKey: auth, data: data) { (signature, erro ### Producing Signed JWT with Provided Claims -The asymmetric private key signatures described above can be used to sign claims provided by the developer and construct a signed JWT (signed using ES256 algorithm). +The asymmetric private key signatures described above can be used to sign claims provided by the developer and construct a signed JWT (signed using the ES256 algorithm). ```swift // Construct claims array @@ -639,10 +639,10 @@ let claims = [ "last_name": "Appleseed" ] -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") -// Unlock the secure vault, fetch the private key and perform data signing +// Unlock the secure vault, fetch the private key, and perform data signing powerAuthSDK.signJwt(withDevicePrivateKey: auth, claims: claims) { (jwt, error) in if let jwt { // Use JWT value @@ -654,44 +654,44 @@ powerAuthSDK.signJwt(withDevicePrivateKey: auth, claims: claims) { (jwt, error) ### Symmetric Offline Multi-Factor Signature -This type of signature is very similar to [Symmetric Multi-Factor Signature](#symmetric-multi-factor-signature), but the result is provided in the form of a simple, human-readable string (unlike the online version, where the result is HTTP header). To calculate the signature, you need a typical `PowerAuthAuthentication` object to define all required factors, nonce, and data to sign. The `nonce` and `data` should also be transmitted to the application over the OOB channel (for example, by scanning a QR code). Then the signature calculation is straightforward: +This type of signature is very similar to [Symmetric Multi-Factor Signature](#symmetric-multi-factor-signature), but the result is provided in the form of a simple, human-readable string (unlike the online version, where the result is an HTTP header). To calculate the signature, you need a typical `PowerAuthAuthentication` object to define all required factors, nonce, and data to sign. The `nonce` and `data` should also be transmitted to the application over the OOB channel (for example, by scanning a QR code). Then the signature calculation is straightforward: ```swift -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") do { let signature = try powerAuthSDK.offlineSignature(with: auth, uriId: "/confirm/offline/operation", body: data, nonce: nonce) print("Signature is " + signature) } catch _ { - // In case of invalid configuration, invalid activation state or other error + // In case of invalid configuration, invalid activation state, or other error } ``` The application has to show that calculated signature to the user now, and the user has to re-type that code into the web application for verification. -You can alter the lenght of the signature components in `offlineSignatureComponentLength` property of `PowerAuthConfiguration` object. +You can alter the length of the signature components in the `offlineSignatureComponentLength` property of the `PowerAuthConfiguration` object. ### Verify Server-Signed Data -This task is useful whenever you need to receive arbitrary data from the server and you need to be able to verify that the server has issued the data. The PowerAuthSDK provides a high-level method for validating data and associated signature: +This task is useful whenever you need to receive arbitrary data from the server and you need to be able to verify that the server has issued the data. The PowerAuthSDK provides a high-level method for validating data and associated signatures: ```swift // Validate data signed with the master server key if powerAuthSDK.verifyServerSignedData(data, signature: signature, masterKey: true) { - // data is signed with server's private master key + // data is signed with the server's private master key } // Validate data signed with the personalized server key if powerAuthSDK.verifyServerSignedData(data, signature: signature, masterKey: false) { - // data is signed with server's private key + // data is signed with the server's private key } ``` ## Password Change -Since the device does not know the password and is unable to verify the password without the help of the server-side, you need to first call an endpoint that verifies a signature computed with the password. SDK offers two ways to do that. +Since the device does not know the password and is unable to verify the password without the help of the server side, you need to first call an endpoint that verifies a signature computed with the password. SDK offers two ways to do that. The safe but typically slower way is to use the following code: @@ -706,13 +706,13 @@ powerAuthSDK.changePassword(from: "oldPassword", to: "newPassword") { (error) in } ``` -This method calls `/pa/v3/signature/validate` under the hood with a 2FA signature with provided original password to verify the password correctness. +This method calls `/pa/v3/signature/validate` under the hood with a 2FA signature with the provided original password to verify the password correctness. However, using this method does not usually fit the typical UI workflow of a password change. The method may be used in cases where an old password and a new password are on a single screen, and therefore are both available at the same time. In most mobile apps, however, the user first visits a screen to enter an old password, and then (if the password is OK), the user proceeds to the two-screen flow of a new password setup (select password, confirm password). In other words, the workflow works like this: 1. Show a screen to enter an old password. 2. Check the old password on the server. -3. If the old password is OK, then let the user chose and confirm a new one. +3. If the old password is OK, then let the user choose and confirm a new one. 4. Change the password by re-encrypting the activation data. For this purpose, you can use the following code: @@ -732,7 +732,7 @@ powerAuthSDK.validatePassword(password: oldPassword) { (error) in // ... -// Ask for new password +// Ask for a new password let newPassword = "2468" // Change the password locally @@ -746,31 +746,31 @@ powerAuthSDK.unsafeChangePassword(from: oldPassword, to: newPassword) ## Working with passwords securely -PowerAuth mobile SDK uses `PowerAuthCorePassword` object behind the scene, to store user's password or PIN securely. The object automatically wipes out the plaintext password on its destroy, so there are no traces of sensitive data left in the memory. You can easily enhance your application's runtime security by adopting this object in your code and this chapter explains in detail how to do it. +PowerAuth mobile SDK uses the `PowerAuthCorePassword` object behind the scene, to store the user's password or PIN securely. The object automatically wipes out the plaintext password on its destroy, so there are no traces of sensitive data left in the memory. You can easily enhance your application's runtime security by adopting this object in your code and this chapter explains in detail how to do it. ### Problem explanation -If you store the user's password in simple string, there is a high probabilty that the content of the string will remain in the memory until the same region is reused by the underlying memory allocator. This is due the fact that the general memory allocator doesn't cleanup the region of memory being freed. It just update its linked-list of free memory regions for future reuse, so the content of allocated object typically remains intact. This has the following implications to your application: +If you store the user's password in a simple string, there is a high probability that the content of the string will remain in the memory until the same region is reused by the underlying memory allocator. This is because the general memory allocator doesn't clean up the region of memory being freed. It just updates its linked list of free memory regions for future reuse, so the content of the allocated object typically remains intact. This has the following implications for your application: -- If your application is using system keyboard to enter the password or PIN, then the sensitive data will remain in memory in multiple copies for a while. +- If your application is using a system keyboard to enter the password or PIN, then the sensitive data will remain in memory in multiple copies for a while. - If the device's memory is not stressed enough, then the application may remain in memory active for days. -The situation that the user's password stays in memory for days may be critical in situations when the attacker has the device in possession. For example, if device is lost or is in repair shop. To minimize the risks, the `PowerAuthCorePassword` object does the following things: +The situation that the user's password stays in memory for days may be critical in situations when the attacker has the device in possession. For example, if the device is lost or is in a repair shop. To minimize the risks, the `PowerAuthCorePassword` object does the following things: -- Always keeps user's password scrambled with a random data, so it cannot be easily found by simple string search. The password in plaintext is revealed only for a short and well defined time when it's needed for the cryptographic operation. +- Always keeps the user's password scrambled with random data, so it cannot be easily found by simple string search. The password in plaintext is revealed only for a short and well-defined time when it's needed for the cryptographic operation. -- Always clears buffer with the sensitive data before the object's deinitialization. +- Always clear the buffer with the sensitive data before the object's deinitialization. -- Doesn't provide a simple interface to reveal the password in plaintext1) and therefore it minimizes the risks of revealing the password by accident (like print it to the log). +- Doesn't provide a simple interface to reveal the password in plaintext1) and therefore it minimizes the risks of revealing the password by accident (like printing it to the log). -**Note 1:** There's `validatePasswordComplexity()` function that reveal the password in plaintext for the limited time for the complexity validation purposes. The straightforward naming of the function allows you to find all its usages in your code and properly validate all codepaths. +**Note 1:** There's a `validatePasswordComplexity()` function that reveals the password in plaintext for a limited time for complexity validation purposes. The straightforward naming of the function allows you to find all its usages in your code and properly validate all code paths. ### Special password object usage -PowerAuth mobile SDK allows you to use both strings and special password objects at input, so it's up to you which way fits best for your purposes. For simplicity, this documentation is using strings for the passwords, but all code examples can be changed to utilize `PowerAuthCorePassword` object as well. For example, this is the modified code for [Password Change](#password-change): +PowerAuth mobile SDK allows you to use both strings and special password objects at input, so it's up to you which way fits best for your purposes. For simplicity, this documentation uses strings for the passwords, but all code examples can be changed to utilize the `PowerAuthCorePassword` object as well. For example, this is the modified code for [Password Change](#password-change): ```swift import PowerAuthCore @@ -789,13 +789,13 @@ powerAuthSDK.changePassword(from: oldPass, to: newPass) { (error) in ### Entering PIN -If your application is using system numberic keyboard to enter user's PIN then you can migrate to `PowerAuthCorePassword` object right now. We recommend you to do the following things: +If your application is using a system numeric keyboard to enter the user's PIN then you can migrate to the `PowerAuthCorePassword` object right now. We recommend you do the following things: - Implement your own PIN keyboard UI -- Make sure that password object is allocated and referenced only in the PIN keyboard controller and is deallocated when user leaves the controller. +- Make sure that the password object is allocated and referenced only in the PIN keyboard controller and is deallocated when the user leaves the controller. -- Use `PowerAuthCoreMutablePassword` that allows you to manipulate with the content of the PIN +- Use `PowerAuthCoreMutablePassword` that allows you to manipulate the content of the PIN Here's the simple pseudo-controller example: @@ -805,12 +805,12 @@ class EnterPinScene { var pin: PowerAuthCoreMutablePassword! func onEnterScene() { - // Allocate pin when entering to the scene + // Allocate the pin when entering the scene pin = PowerAuthCoreMutablePassword() } func onLeaveScene() { - // Dereference the password object, when user is leaving + // Dereference of the password object, when the user is leaving // the scene to safely wipe the content out of the memory pin = nil } @@ -848,16 +848,16 @@ class EnterPinScene { ### Entering arbitrary password -Unfortunately, there's no simple solution for this scenario. It's quite difficult to re-implement the whole keyboard on your own, so we recommend you to keep using the system keyboard. You can still create the `PowerAuthCorePassword` object from already entered string: +Unfortunately, there's no simple solution for this scenario. It's quite difficult to re-implement the whole keyboard on your own, so we recommend you keep using the system keyboard. You can still create the `PowerAuthCorePassword` object from an already entered string: ```swift let passwordString = "nbusr123" let password = PowerAuthCorePassword(string: passwordString) ``` -### Create password from data +### Create a password from data -In case that passphrase is somehow created externally in form of array of bytes, then you can instantiate it from the `Data` object directly: +In case that passphrase is somehow created externally in the form of an array of bytes, then you can instantiate it from the `Data` object directly: ```swift let passwordData = Data(base64Encoded: "bmJ1c3IxMjMK")! @@ -891,8 +891,8 @@ enum PasswordComplexity: Int { case strong = 2 } -// This is an actual complexity validator that also accepts pointer at its input. You should avoid -// converting provided memory into Data or String due to fact, that it will lead to an uncontrolled +// This is an actual complexity validator that also accepts a pointer at its input. You should avoid +// converting provided memory into Data or String due to the fact, that it will lead to an uncontrolled // passphrase copy to foundation objects' buffers. func superPasswordValidator(passwordPtr: UnsafePointer, size: Int) -> PasswordComplexity { // This is just an example, please do not use such trivial validation in your @@ -923,7 +923,7 @@ You can use our [Passphrase meter](https://github.com/wultra/passphrase-meter) l ## Biometry Setup -PowerAuth SDK for iOS provides an abstraction on top of the base Touch and Face ID support. While the authentication / data signing itself is nicely and transparently embedded in the `PowerAuthAuthentication` object used in [regular request signing](#data-signing), other biometry-related processes require their own API. This part of the documentation is not relevant for the **tvOS** platform. +PowerAuth SDK for iOS provides an abstraction on top of the base Touch and Face ID support. While the authentication / data signing itself is nicely and transparently embedded in the `PowerAuthAuthentication` object used in [regular request signing](#data-signing), other biometry-related processes require their own API. This part of the documentation is not relevant to the **tvOS** platform. ### Check Biometry Status @@ -933,14 +933,14 @@ You have to check for biometry on three levels: - If Touch ID is present on the system and if an iOS version is 9+ - If Face ID is present on the system and if an iOS version is 11+ - **Activation Availability**: If biometry factor data are available for given activation. -- **Application Availability**: If user decided to use Touch ID for given app. _(optional)_ +- **Application Availability**: If the user decides to use Touch ID for a given app. _(optional)_ PowerAuth SDK for iOS provides code for the first two of these checks. To check if you can use biometry on the system, use the following code from the `PowerAuthKeychain` class: ```swift -// Is biometry available and is enrolled on the system? +// Is biometry available and is enrolled in the system? let canUseBiometry = PowerAuthKeychain.canUseBiometricAuthentication // Or alternative, to get supported biometry type @@ -951,7 +951,7 @@ switch supportedBiometry { case .none: print("Biometry is not supported or not enrolled") } -// Or more complex, with full information about type and current status +// Or more complex, with full information about the type and current status let biometryInfo = PowerAuthKeychain.biometricAuthenticationInfo switch biometryInfo.biometryType { case .touchID: print("Touch ID is available on device.") @@ -983,19 +983,19 @@ In case an activation does not yet have biometry-related factor data, and you wo Use the following code to enable biometric authentication: ```swift -// Establish biometric data using provided password +// Establish biometric data using the provided password powerAuthSDK.addBiometryFactor(password: "1234") { (error) in if error == nil { // Everything went OK, Touch ID is ready to be used } else { - // Error occurred, report it to user + // Error occurred, report it to the user } } ``` ### Disable Biometry -You can remove biometry related factor data used by Touch or Face ID support by simply removing the related key locally, using this one-liner: +You can remove biometry-related factor data used by Touch or Face ID support by simply removing the related key locally, using this one-liner: ```swift // Remove biometric data @@ -1004,17 +1004,17 @@ powerAuthSDK.removeBiometryFactor() ### Fetch Biometry Credentials In Advance -You can acquire biometry credentials in advance in case that business processes require computing two or more different PowerAuth biometry signatures in one interaction with the user. To achieve this, the application must acquire the custom-created `PowerAuthAuthentication` object first and then use it for the required signature calculations. It's recommended to keep this instance referenced only for a limited time, required for all future signature calculations. +You can acquire biometry credentials in advance in case business processes require computing two or more different PowerAuth biometry signatures in one interaction with the user. To achieve this, the application must acquire the custom-created `PowerAuthAuthentication` object first and then use it for the required signature calculations. It's recommended to keep this instance referenced only for a limited time, required for all future signature calculations. Be aware, that you must not execute the next HTTP request signed with the same credentials when the previous one fails with the 401 HTTP status code. If you do, then you risk blocking the user's activation on the server. -In order to obtain biometry credentials for the future signature calculation, call the following code: +To obtain biometry credentials for the future signature calculation, call the following code: ```swift // Authenticate user with biometry and obtain PowerAuthAuthentication credentials for future signature calculation. powerAuthSDK.authenticateUsingBiometry(withPrompt: "Authenticate to sign in") { authentication, error in if let authentication = authentication { - // Success, you can use provided PowerAuthAuthentication object for the signature calculation. + // Success, you can use the provided PowerAuthAuthentication object for the signature calculation. // The provided authentication object is preconfigured for possession+biometry factors } guard let error = error as NSError?, error.domain == PowerAuthErrorDomain else { @@ -1030,7 +1030,7 @@ powerAuthSDK.authenticateUsingBiometry(withPrompt: "Authenticate to sign in") { ### Biometry Factor-Related Key Lifetime -By default, the biometry factor-related key is **NOT** invalidated after the biometry enrolled in the system is changed. For example, if the user adds or removes the finger or enrolls with a new face, then the biometry factor-related key is still available for the signing operation. To change this behavior, you have to provide `PowerAuthKeychainConfiguration` object with `linkBiometricItemsToCurrentSet` parameter set to `true` and use that configuration for the `PowerAuthSDK` instance construction: +By default, the biometry factor-related key is **NOT** invalidated after the biometry enrolled in the system is changed. For example, if the user adds or removes the finger or enrolls with a new face, then the biometry factor-related key is still available for the signing operation. To change this behavior, you have to provide the `PowerAuthKeychainConfiguration` object with the `linkBiometricItemsToCurrentSet` parameter set to `true` and use that configuration for the `PowerAuthSDK` instance construction: ```swift // Prepare your PA config @@ -1038,7 +1038,7 @@ let configuration = PowerAuthConfiguration() // ... // Prepare PowerAuthKeychainConfiguration -// Set true to 'linkBiometricItemsToCurrentSet' property. +// Set true to the 'linkBiometricItemsToCurrentSet' property. let keychainConfiguration = PowerAuthKeychainConfiguration() keychainConfiguration.linkBiometricItemsToCurrentSet = true @@ -1052,7 +1052,7 @@ Be aware that the configuration above is effective only for the new keys. So, if ### Fallback biometry to device passcode -By default, the fallback from biometric authentication to authenticate with device's passcode is not allowed. To change this behavior, you have to provide `PowerAuthKeychainConfiguration` object with `allowBiometricAuthenticationFallbackToDevicePasscode` parameter set to `true` and use that configuration for the `PowerAuthSDK` instance construction: +By default, the fallback from biometric authentication to authenticate with the device's passcode is not allowed. To change this behavior, you have to provide the `PowerAuthKeychainConfiguration` object with the `allowBiometricAuthenticationFallbackToDevicePasscode` parameter set to `true` and use that configuration for the `PowerAuthSDK` instance construction: ```swift // Prepare your PA config @@ -1060,7 +1060,7 @@ let configuration = PowerAuthConfiguration() // ... // Prepare PowerAuthKeychainConfiguration -// Set true to 'allowBiometricAuthenticationFallbackToDevicePasscode' property. +// Set true to the 'allowBiometricAuthenticationFallbackToDevicePasscode' property. let keychainConfiguration = PowerAuthKeychainConfiguration() keychainConfiguration.allowBiometricAuthenticationFallbackToDevicePasscode = true @@ -1068,15 +1068,15 @@ keychainConfiguration.allowBiometricAuthenticationFallbackToDevicePasscode = tru let powerAuthSDK = PowerAuthSDK(configuration: configuration, keychainConfiguration: keychainConfiguration, clientConfiguration: nil) ``` -Once the configuration above is used, then `linkBiometricItemsToCurrentSet` option has no effect on the biometry factor-related key lifetime. +Once the configuration above is used, then the `linkBiometricItemsToCurrentSet` option does not affect the biometry factor-related key lifetime. -It's not recommended to allow fallback to device passcode if your application falls under EU banking regulations or your application needs to distinguish between the biometric and the knowledge-factor based signatures. This is due to the fact that if the biometry factor-related key is unlocked with the device's passcode, then it's no longer a biometric signature. +It's not recommended to allow fallback to device passcodes if your application falls under EU banking regulations or your application needs to distinguish between the biometric and the knowledge-factor-based signatures. This is because if the biometry factor-related key is unlocked with the device's passcode, then it's no longer a biometric signature. ### LAContext support -In case you require advanced customization to system biometric dialog, then you can use your own `LAContext` instance set to `PowerAuthAuthentication` or in some functions. For example: +In case you require advanced customization to the system biometric dialog, then you can use your own `LAContext` instance set to `PowerAuthAuthentication` or in some functions. For example: ```swift // Prepare LAContext @@ -1090,7 +1090,7 @@ powerAuthSDK.removeActivation(with: authentication) { error in // ... } -// Fetch biometry key in advance, with using LAContext +// Fetch the biometry key in advance using LAContext powerAuthSDK.authenticateUsingBiometry(withContext: laContext) { authentication, error in if let authentication = authentication { // success @@ -1100,8 +1100,8 @@ powerAuthSDK.authenticateUsingBiometry(withContext: laContext) { authentication, The usage of `LAContext` has the following limitations: -- It's effective from iOS 11 because on the older operating systems the context doesn't support essential properties, such as `localizedReason`. -- Don't alter `interactionNotAllowed` property. If you do, then the internal SDK implementation rejects the context and error is reported. +- It's effective from iOS 11 because on the older operating systems, the context doesn't support essential properties, such as `localizedReason`. +- Don't alter the `interactionNotAllowed` property. If you do, then the internal SDK implementation rejects the context, and an error is reported. Be aware that PowerAuth automatically invalidates the application provided `LAContext` after use. This is because once the context is successfully evaluated then it can be used for a quite long time to fetch the data protected with the biometry with no prompt displayed. The exact time of validity is undocumented, but our experiments show that iOS prompts for biometric authentication again after more than 5 minutes. @@ -1109,7 +1109,7 @@ If you plan to pre-authorize `LAContext` and use it for multiple biometry signat - Make sure that you make context invalid once it's no longer needed. - Multiple signatures in a row could be problematic if your application falls under EU banking regulations. -- It would be difficult to prove that the user authorized the request if your application contains a bug and do the signature on the user's behalf or with the wrong context. +- It would be difficult to prove that the user authorized the request if your application contains a bug and does the signature on the user's behalf or with the wrong context. If you still insist to re-use `LAContext` then you have to alter `PowerAuthKeychainConfiguration` and set `invalidateLocalAuthenticationContextAfterUse` to `false`. @@ -1119,10 +1119,10 @@ If you still insist to re-use `LAContext` then you have to alter `PowerAuthKeych ### Biometry lockout -Note that if the biometric authentication fails with too many attempts in a row (e.g. biometry is locked out), then PowerAuth SDK will generate an invalid biometry factor related key and the success is reported back to the application. This is an intended behavior and as the result, it typically lead to unsuccessful authentication on the server and increased counter of failed attempts. The purpose of this is to limit the number of attempts for attacker to deceive the biometry sensor. +Note that if the biometric authentication fails with too many attempts in a row (e.g. biometry is locked out), then PowerAuth SDK will generate an invalid biometry factor-related key, and the success is reported back to the application. This is an intended behavior and as a result, it typically leads to unsuccessful authentication on the server and an increased counter of failed attempts. The purpose of this is to limit the number of attempts for attackers to deceive the biometry sensor. -### Thread blocking operation +### Thread-blocking operation Be aware that if you try to calculate PowerAuth Symmetric Signature with a biometric factor, then the call to the SDK function will block the calling thread while the biometric authentication dialog is displayed. So, it's not recommended to do such an operation on the main thread. For example: @@ -1136,14 +1136,14 @@ let header = try? sdk.requestSignature(with: authentication, method: "POST", uri It's not recommended to calculate more than one signature with the biometric factor at the same time, or in a row at a quick pace. Both scenarios are considered an issue in the application's logic. -To prevent the first case, PowerAuth mobile SDK is using a global mutex that guarantees that only one attempt to get the biometry-protected data at the time is performed. If your application issue another signing operation while the system dialog is displayed, then this attempt ends with `.biometryCancel` error. +To prevent the first case, PowerAuth mobile SDK is using a global mutex that guarantees that only one attempt to get the biometry-protected data at the time is performed. If your application issues another signing operation while the system dialog is displayed, then this attempt ends with `.biometryCancel` error. -There's another similar issue on devices supporting FaceID. The FaceID technology behaves slightly differently than TouchID and if you try to use biometry too soon after previous successful authentication, then the 2nd attempt will fail with an authentication error. This is undocumented, but we believe it's related to the animation presented to the user after successful authentication. You simply cannot request another biometric authentication while the animation is still playing. +There's another similar issue on devices supporting FaceID. The FaceID technology behaves slightly differently than TouchID and if you try to use biometry too soon after a previous successful authentication, then the 2nd attempt will fail with an authentication error. This is undocumented, but we believe it's related to the animation presented to the user after successful authentication. You simply cannot request another biometric authentication while the animation is still playing. ### Use pre-authorized LAContext -If you want more control over biometric authentication UI flow, then you can prepare `LAContext` and evaluate it on your own. The pre-authorized context can be then used to construct `PowerAuthAuthentication`: +If you want more control over the biometric authentication UI flow, then you can prepare `LAContext` and evaluate it on your own. The pre-authorized context can be then used to construct `PowerAuthAuthentication`: ```swift let context = LAContext() @@ -1153,11 +1153,11 @@ context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason return } let authentication = PowerAuthAuthentication.possessionWithBiometry(context: context) - // Now you can use authentication object in any SDK function that accept authentication with biometric factor. + // Now you can use the authentication object in any SDK function that accepts authentication with a biometric factor. } ``` -Be aware that the example above doesn't handle all quirks related to the PowerAuth protocol, so you should prefer to use `authenticateUsingBiometry()` function instead: +Be aware that the example above doesn't handle all quirks related to the PowerAuth protocol, so you should prefer to use the `authenticateUsingBiometry()` function instead: ```swift let context = LAContext() @@ -1171,7 +1171,7 @@ powerAuthSDK.authenticateUsingBiometry(withContext: context) { authentication, e } else if nsError.powerAuthErrorCode == .biometryFallback { // fallback button pressed } - // If you're interested in exact failure reason, then extract + // If you're interested in the exact failure reason, then extract // the underlying LAError. if let laError = nsError.userInfo[NSUnderlyingErrorKey] as? LAError { // Investigate error codes... @@ -1190,7 +1190,7 @@ You can remove activation using several ways - the choice depends on the desired ### Simple Device-Only Removal -You can clear activation data anytime from the Keychain. The benefit of this method is that it does not require help from the server, and the user does not have to be logged in. The issue with this removal method is simple: The activation still remains active on the server-side. This, however, does not have to be an issue in your case. +You can clear activation data anytime from the Keychain. The benefit of this method is that it does not require help from the server, and the user does not have to be logged in. The issue with this removal method is simple: The activation still remains active on the server side. This, however, does not have to be an issue in your case. To remove only data related to PowerAuth SDK for iOS, use the `PowerAuthKeychain` class: @@ -1200,13 +1200,13 @@ powerAuthSDK.removeActivationLocal() ### Removal via Authenticated Session -Suppose your server uses an authenticated session for keeping the users logged in. In that case, you can combine the previous method with calling your proprietary endpoint to remove activation for the currently logged-in user. The advantage of this method is that activation does not remain active on the server. The issue is that the user has to be logged in (the session must be active and must have activation ID stored) and that you have to publish your own method to handle this use case. +Suppose your server uses an authenticated session to keep the users logged in. In that case, you can combine the previous method with calling your proprietary endpoint to remove activation for the currently logged-in user. The advantage of this method is that activation does not remain active on the server. The issue is that the user has to be logged in (the session must be active and must have an activation ID stored) and that you have to publish your own method to handle this use case. The code for this activation removal method is as follows: ```swift // Use custom call to proprietary server endpoint to remove activation. -// User must be logged in at this moment, so that session can find +// The user must be logged in at this moment, so that the session can find // associated activation ID self.httpClient.post(null, "/custom/activation/remove") { (error) in if error == nil { @@ -1220,15 +1220,15 @@ self.httpClient.post(null, "/custom/activation/remove") { (error) in ### Removal via Signed Request -PowerAuth Standard RESTful API has a default endpoint `/pa/v3/activation/remove` for an activation removal. This endpoint uses a signature verification for looking up the activation to be removed. The benefit of this method is that it is already present in both PowerAuth SDK for iOS and PowerAuth Standard RESTful API - nothing has to be programmed. Also, the user does not have to be logged in to use it. However, the user has to authenticate using 2FA with either password or biometry. +PowerAuth Standard RESTful API has a default endpoint `/pa/v3/activation/remove` for an activation removal. This endpoint uses a signature verification for looking up the activation to be removed. The benefit of this method is that it is already present in both PowerAuth SDK for iOS and PowerAuth Standard RESTful API - nothing has to be programmed. Also, the user does not have to be logged in to use it. However, the user has to authenticate using 2FA with either a password or biometry. -Use the following code for an activation removal using signed request: +Use the following code for an activation removal using a signed request: ```swift -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") -// Remove activation using provided authentication object +// Remove activation using the provided authentication object powerAuthSDK.removeActivation(with: auth) { (error) in if error == nil { // OK, activation was removed @@ -1245,7 +1245,7 @@ Currently, PowerAuth SDK supports two basic modes of end-to-end encryption, base - In an "application" scope, the encryptor can be acquired and used during the whole lifetime of the application. - In an "activation" scope, the encryptor can be acquired only if `PowerAuthSDK` has a valid activation. The encryptor created for this mode is cryptographically bound to the parameters agreed during the activation process. You can combine this encryption with [PowerAuth Symmetric Multi-Factor Signature](#symmetric-multi-factor-signature) in "encrypt-then-sign" mode. -For both scenarios, you need to acquire the `PowerAuthCoreEciesEncryptor` object, which will then provide interface for the request encryption and the response decryption. The object currently provides only low-level encryption and decryption methods, so you need to implement your own JSON (de)serialization and request and response processing. +For both scenarios, you need to acquire the `PowerAuthCoreEciesEncryptor` object, which will then provide an interface for the request encryption and the response decryption. The object currently provides only low-level encryption and decryption methods, so you need to implement your own JSON (de)serialization and request and response processing. The following steps are typically required for a full E2EE request and response processing: @@ -1260,7 +1260,7 @@ The following steps are typically required for a full E2EE request and response guard let encryptor = powerAuthSDK.eciesEncryptorForActivationScope() else { ...failure... } ``` -1. Make sure that PowerAuth SDK instance has [time synchronized with the server](#synchronized-time): +1. Make sure that the PowerAuth SDK instance has [time synchronized with the server](#synchronized-time): ```swift let timeService = powerAuthSDK.timeSynchronizationService if !timeService.isTimeSynchronized { @@ -1272,14 +1272,14 @@ The following steps are typically required for a full E2EE request and response } ``` -1. Serialize your request payload, if needed, into a sequence of bytes. This step typically means that you need to serialize your model object into a JSON formatted sequence of bytes. +1. Serialize your request payload, if needed, into a sequence of bytes. This step typically means that you need to serialize your model object into a JSON-formatted sequence of bytes. 1. Encrypt your payload: ```swift guard let cryptogram = encryptor.encryptRequest(payloadData) else { ...failure... } ``` -1. Construct a JSON from provided cryptogram object. The dictionary with the following keys is expected: +1. Construct a JSON from the provided cryptogram object. The dictionary with the following keys is expected: - `ephemeralPublicKey` property fill with `cryptogram.keyBase64` - `encryptedData` property fill with `cryptogram.bodyBase64` - `mac` property fill with `cryptogram.macBase64` @@ -1304,7 +1304,7 @@ The following steps are typically required for a full E2EE request and response let httpHeaderName = metadata.httpHeaderKey let httpHeaderValue = metadata.httpHeaderValue ``` - Note that if an "activation" scoped encryptor is combined with PowerAuth Symmetric Multi-Factor signature, then this step is not required. The signature's header already contains all information required for proper request decryption on the server. + Note that if an "activation" scoped encryptor is combined with PowerAuth Symmetric Multi-Factor signature, then this step is not required. The signature's header already contains all the information required for proper request decryption on the server. 1. Fire your HTTP request and wait for a response - In case that non-200 HTTP status code is received, then the error processing is identical to a standard RESTful response defined in our protocol. So, you can expect a JSON object with `"error"` and `"message"` properties in the response. @@ -1313,8 +1313,8 @@ The following steps are typically required for a full E2EE request and response ```json { "encryptedData": "BASE64-DATA-BLOB", - "mac" : "BASE64-DATA-BLOB", - "nonce" : "BASE64-NONCE", + "mac": "BASE64-DATA-BLOB", + "nonce": "BASE64-NONCE", "timestamp": 1694172789256 } ``` @@ -1331,33 +1331,33 @@ The following steps are typically required for a full E2EE request and response 1. And finally, you can process your received response. -As you can see, the E2EE is quite a non-trivial task. We recommend contacting us before using an application-specific E2EE. We can provide you more support on a per-scenario basis, especially if we first understand what you try to achieve with end-to-end encryption in your application. +As you can see, the E2EE is quite a non-trivial task. We recommend contacting us before using an application-specific E2EE. We can provide you with more support on a per-scenario basis, especially if we first understand what you are trying to achieve with end-to-end encryption in your application. ## Secure Vault -PowerAuth SDK for iOS has basic support for an encrypted secure vault. At this moment, the only supported method allows your application to establish an encryption / decryption key with a given index. The index represents a "key number" - your identifier for a given key. Different business logic purposes should have encryption keys with a different index value. +PowerAuth SDK for iOS has basic support for an encrypted secure vault. At this moment, the only supported method allows your application to establish an encryption / decryption key with a given index. The index represents a "key number" - your identifier for a given key. Different business logic purposes should have encryption keys with different index values. -On a server-side, all secure vault-related work is concentrated in a `/pa/v3/vault/unlock` endpoint of PowerAuth Standard RESTful API. In order to receive data from this response, the call must be authenticated with at least 2FA (using password or PIN). +On the server side, all secure vault-related work is concentrated in a `/pa/v3/vault/unlock` endpoint of PowerAuth Standard RESTful API. In order to receive data from this response, the call must be authenticated with at least 2FA (using a password or PIN). -Secure vault mechanism does not support biometry by default. Use PIN code or password based authentication for unlocking the secure vault, or ask your server developers to enable biometry for vault unlock call by configuring PowerAuth Server instance. +The secure vault mechanism does not support biometry by default. Use PIN code or password-based authentication for unlocking the secure vault, or ask your server developers to enable biometry for vault unlock calls by configuring the PowerAuth Server instance. ### Obtaining Encryption Key -In order to obtain an encryption key with a given index, use the following code: +To obtain an encryption key with a given index, use the following code: ```swift -// 2FA signature. It uses device-related key and user PIN code. +// 2FA signature. It uses a device-related key and user PIN code. let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") // Select custom key index let index = UInt64(1000) -// Fetch encryption key with given index +// Fetch the encryption key with the given index powerAuthSDK.fetchEncryptionKey(auth, index: index) { (encryptionKey, error) in if error == nil { - // ... use encryption key to encrypt or decrypt data + // ... use the encryption key to encrypt or decrypt data } else { // Report error } @@ -1366,7 +1366,7 @@ powerAuthSDK.fetchEncryptionKey(auth, index: index) { (encryptionKey, error) in ## Recovery Codes -The recovery codes allow your users to recover their activation in case that mobile device is lost or stolen. Before you start, please read the [Activation Recovery](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md) document, available in our [powerauth-crypto](https://github.com/wultra/powerauth-crypto) repository. +The recovery codes allow your users to recover their activation in case their device is lost or stolen. Before you start, please read the [Activation Recovery](https://github.com/wultra/powerauth-crypto/blob/develop/docs/Activation-Recovery.md) document, available in our [powerauth-crypto](https://github.com/wultra/powerauth-crypto) repository. To recover an activation, the user has to re-type two separate values: @@ -1376,20 +1376,20 @@ To recover an activation, the user has to re-type two separate values: PowerAuth currently supports two basic types of recovery codes: 1. Recovery Code bound to a previous PowerAuth activation. - - This type of code can be obtained only in an already activated application. + - This type of code can be obtained only in an already-activated application. - This type of code has only one PUK available, so only one recovery operation is possible. - The activation associated with the code is removed once the recovery operation succeeds. 2. Recovery Code delivered via OOB channel, typically in the form of a securely printed postcard, delivered by the post service. - This type of code has typically more than one PUK associated with the code, so it can be used multiple times. - - The user has to keep that postcard in safe and secure place, and mark already used PUKs. + - The user has to keep that postcard in a safe and secure place, and mark already used PUKs. - The code delivery must be confirmed by the user before the code can be used for a recovery operation. -The feature is not automatically available. It must be enabled and configured on PowerAuth Server. If it's so, then your mobile application can use several methods related to this feature. +The feature is not automatically available. It must be enabled and configured on the PowerAuth Server. If it's so, then your mobile application can use several methods related to this feature. ### Getting Recovery Data -If the recovery data was received during the activation process, then you can later display that information to the user. To check existence of recovery data and get that information, use the following code: +If the recovery data was received during the activation process, then you can later display that information to the user. To check the existence of recovery data and get that information, use the following code: ```swift guard powerAuthSdk.hasActivationRecoveryData() else { @@ -1397,7 +1397,7 @@ guard powerAuthSdk.hasActivationRecoveryData() else { return } -// 2FA signature, uses device related key and user PIN code +// 2FA signature - uses device-related key and user PIN code let auth = PowerAuthAuthentication.possessionWithPassword(password: "1234") powerAuthSdk.activationRecoveryData(auth) { recoveryData, error in @@ -1417,14 +1417,14 @@ The obtained information is very sensitive, so you should be very careful how yo - You should never print the values to the debug log. - You should never send the values over the network. - You should never copy the values to the clipboard. -- You should require PIN code every time to display the values on the screen. -- You should warn user that taking screenshot of the values is not recommended. +- You should require a PIN code every time to display the values on the screen. +- You should warn the user that taking a screenshot of the values is not recommended. - Do not cache the values in RAM. You should inform the user that: - Making a screenshot when values are displayed on the screen is dangerous. -- The user should write down that values on paper and keep it as much safe as possible for future use. +- The user should write down those values on paper and keep it as safe as possible for future use. ### Confirm Recovery Postcard @@ -1459,11 +1459,11 @@ The `alreadyConfirmed` boolean indicates that the code was already confirmed in The tokens are simple, locally cached objects, producing timestamp-based authorization headers. Be aware that tokens are NOT a replacement for general PowerAuth signatures. They are helpful in situations when the signatures are too heavy or too complicated for implementation. Each token has the following properties: -- It needs PowerAuth signature for its creation (e.g., you need to provide `PowerAuthAuthentication` object) -- It has a unique identifier on the server. This identifier is not exposed to the public API, but DEBUG version of SDK can reveal that identifier in the debugger (e.g., you can use `po tokenObject` to print object's description) +- It needs a PowerAuth signature for its creation (e.g., you need to provide `PowerAuthAuthentication` object) +- It has a unique identifier on the server. This identifier is not exposed to the public API, but the DEBUG version of SDK can reveal that identifier in the debugger (e.g., you can use `po tokenObject` to print the object's description) - It has a symbolic name (e.g. "MyToken") defined by the application programmer to identify already created tokens. - It can generate timestamp-based authorization HTTP headers. -- It can be used concurrently. Token's private data doesn't change in time. +- It can be used concurrently. Token's private data doesn't change over time. - The token is associated with the `PowerAuthSDK` instance. So, you can use the same symbolic name in multiple SDK instances, and each created token will be unique. - Tokens are persisted in the keychain and cached in the memory. - Once the parent `PowerAuthSDK` instance loses its activation, all its tokens are removed from the local database. @@ -1473,21 +1473,21 @@ The tokens are simple, locally cached objects, producing timestamp-based authori To get an access token, you can use the following code: ```swift -// 1FA signature, uses device related key +// 1FA signature - uses device-related key let auth = PowerAuthAuthentication.possession() let tokenStore = powerAuthSDK.tokenStore let task = tokenStore.requestAccessToken(withName: "MyToken", authentication: auth) { (token, error) in if let token = token { - // now you can generate header + // now you can generate a header } else { // handle error } } ``` -The request is performed synchronously or asynchronously depending on whether the token is locally cached on the device. You can test this situation by calling `tokenStore.hasLocalToken(withName: "MyToken")`. If operation is asynchronous, then `requestAccessToken()` returns cancellable task. +The request is performed synchronously or asynchronously depending on whether the token is locally cached on the device. You can test this situation by calling `tokenStore.hasLocalToken(withName: "MyToken")`. If an operation is asynchronous, then `requestAccessToken()` returns a cancellable task. ### Generating Authorization Header @@ -1499,7 +1499,7 @@ let task = tokenStore.generateAuthorizationHeader(withName: "MyToken") { header, let httpHeader = [ header.key : header.value ] // now you can attach that httpHeader to your HTTP request } else { - // failure, token is no longer valid, or failed to synchronize time + // failure, the token is no longer valid, or failed to synchronize time // with the server. } } @@ -1512,7 +1512,7 @@ if let header = token.generateHeader() { let httpHeader = [ header.key : header.value ] // now you can attach that httpHeader to your HTTP request } else { - // in case of nil, token is no longer valid + // in case of nil, the token is no longer valid } ``` @@ -1538,7 +1538,7 @@ tokenStore.removeAccessToken(withName: "MyToken") { (removed, error) in ### Removing Token Locally -To remove token locally, you can simply use the following code: +To remove the token locally, you can simply use the following code: ```swift let tokenStore = powerAuthSDK.tokenStore @@ -1553,13 +1553,13 @@ Note that by removing tokens locally, you will lose control of the tokens stored ## Apple Watch Support -This part of the documentation describes how to add support for Apple Watch into your PowerAuth powered iOS application. This part of the documentation is not relevant for the **tvOS** platform. +This part of the documentation describes how to add support for Apple Watch to your PowerAuth-powered iOS application. This part of the documentation is not relevant to the **tvOS** platform. ### Prepare Watch Connectivity -The PowerAuth SDK for iOS is using the [WatchConnectivity framework](https://developer.apple.com/documentation/watchconnectivity) to achieve data synchronization between iPhone and Apple Watch devices. If you're not familiar with this framework, take a look at least at [WCSession](https://developer.apple.com/documentation/watchconnectivity/wcsession) and [WCSessionDelegate](https://developer.apple.com/documentation/watchconnectivity/wcsessiondelegate) interfaces before you start. +The PowerAuth SDK for iOS uses the [WatchConnectivity framework](https://developer.apple.com/documentation/watchconnectivity) to achieve data synchronization between iPhone and Apple Watch devices. If you're not familiar with this framework, take a look at least at [WCSession](https://developer.apple.com/documentation/watchconnectivity/wcsession) and [WCSessionDelegate](https://developer.apple.com/documentation/watchconnectivity/wcsessiondelegate) interfaces before you start. -The PowerAuth SDK doesn't manage the state of the `WCSession` and it doesn't set the delegate to the session's singleton instance. It's up to you to properly configure and activate the default session, but the application has to cooperate with PowerAuth SDK to process the messages received from the counterpart device. To do this, PowerAuth SDKs on both sides are providing `PowerAuthWCSessionManager` class which can help you process all incoming messages. Here's an example, how you can implement simple `SessionManager` for IOS: +The PowerAuth SDK doesn't manage the state of the `WCSession` and it doesn't set the delegate to the session's singleton instance. It's up to you to properly configure and activate the default session, but the application has to cooperate with PowerAuth SDK to process the messages received from the counterpart device. To do this, PowerAuth SDKs on both sides are providing the `PowerAuthWCSessionManager` class which can help you process all incoming messages. Here's an example of how you can implement simple `SessionManager` for IOS: ```swift import Foundation @@ -1572,7 +1572,7 @@ class SessionManager: NSObject, WCSessionDelegate { private let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil - // Returns false, when session is not available on the device. + // Returns false, when the session is not available on the device. func activateSession() -> Bool { session?.delegate = self session?.activate() @@ -1583,7 +1583,7 @@ class SessionManager: NSObject, WCSessionDelegate { func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { if activationState == .activated { - // now you can use WCSession for communication, send status of session to watch, etc... + // now you can use WCSession for communication, send the status of the session to watch, etc... } } @@ -1600,7 +1600,7 @@ class SessionManager: NSObject, WCSessionDelegate { if PowerAuthWCSessionManager.sharedInstance.processReceivedMessageData(messageData, replyHandler: nil) { return // processed... } - // Other SDKs, or your own messages can be handler here... + // Other SDKs or your own messages can be handled here... print("SessionManager.didReceiveMessageData did not process message.") } @@ -1609,7 +1609,7 @@ class SessionManager: NSObject, WCSessionDelegate { if PowerAuthWCSessionManager.sharedInstance.processReceivedMessageData(messageData, replyHandler: replyHandler) { return // processed... } - // Other SDKs, or your own messages can be handler here... + // Other SDKs or your own messages can be handled here... print("SessionManager.didReceiveMessageData did not process message. Responding with empty data") replyHandler(Data()) } @@ -1619,7 +1619,7 @@ class SessionManager: NSObject, WCSessionDelegate { if PowerAuthWCSessionManager.sharedInstance.processReceivedUserInfo(userInfo) { return // processed... } - // Other SDKs, or your own messages can be handler here... + // Other SDKs or your own messages can be handled here... print("SessionManager.didReceiveUserInfo did not process message.") } } @@ -1629,19 +1629,19 @@ class SessionManager: NSObject, WCSessionDelegate { The code above is very similar to its [watchOS counterpart](./PowerAuth-SDK-for-watchOS.md#prepare-watch-connectivity). -The example is implementing only a minimum set of methods from `WCSessionDelegate` protocol to make message passing work. The important part is that at some point, both applications (iOS and watchOS) have to call `SessionManager.shared.activateSession()` to make the transfers possible. Once you activate your session on the device, you can use all APIs related to the communication. +The example is implementing only a minimum set of methods from the `WCSessionDelegate` protocol to make message passing work. The important part is that at some point, both applications (iOS and watchOS) have to call `SessionManager.shared.activateSession()` to make the transfers possible. Once you activate your session on the device, you can use all APIs related to the communication. ### WCSession Activation Sequence -In this chapter, we will discuss the right initialization sequence for various interoperating objects during your application's startup. We recommend to follow those rules to make communication between iOS & watchOS reliable. +In this chapter, we will discuss the right initialization sequence for various interoperating objects during your application's startup. We recommend following those rules to make communication between iOS & watchOS reliable. #### Implementation Summary On the application's startup: -1. Instantiate and configure all `PowerAuthSDK` instances and especially ones to be synchronized with Apple Watch. -2. Activate `WCSession`, so get the default instance, assign your delegate and call `activate()` +1. Instantiate and configure all `PowerAuthSDK` instances especially ones to be synchronized with Apple Watch. +2. Activate `WCSession`, so get the default instance, assign your delegate, and call `activate()` 3. Wait for the session's activation in your delegate 4. Now you can use watch-related methods from PowerAuth SDK for iOS. For example, you can use a lazy method to send the status of activation to the watch device. @@ -1651,24 +1651,24 @@ Due to our internal implementation details, each `PowerAuthSDK` instance is regi Once you activate `WCSession`, your `WCSessionDelegate` is going to receive messages (on the background thread) from the counterpart watch application. We are highlighting the importance of the right activation sequence because your watchOS application can wake up its iOS counterpart. Therefore, it is highly possible that some messages will be available right at the application's startup. If you don't follow the guidelines and forget to prepare your `PowerAuthSDK` instances before the `WCSession` is activated, then a couple of messages may be lost. -Fortunately, the situation on watchOS side is much easier because all incoming messages are processed in one special service class, which is always available. +Fortunately, the situation on the watchOS side is much easier because all incoming messages are processed in one special service class, which is always available. ### Sending Activation Status to Watch -Before you start using PowerAuth on watchOS, it is recommended to send information about PowerAuth activation from iPhone to Apple Watch. The information transmitted to the watch is very limited. In fact, on the watchOS side, you can check only whether the activation on iPhone is locally present or not. It is recommended to keep this status up to date as much as possible. That typically means that you may send status every time you complete or remove the activation. +Before you start using PowerAuth on watchOS, it is recommended to send information about PowerAuth activation from iPhone to Apple Watch. The information transmitted to the watch is very limited. In fact, on the watchOS side, you can check only whether the activation on the iPhone is locally present or not. It is recommended to keep this status up to date as much as possible. That typically means that you may send the status every time you complete or remove the activation. -To send current status of the activation, use the following code: +To send the current status of the activation, use the following code: ```swift if !powerAuthSDK.sendActivationStatusToWatch() { // send message has not been issued, WCSession is probably not available / active } else { - // message has been issued and it's guaranteed that it will be delivered to watch + // message has been issued and it's guaranteed that it will be delivered to the watch } ``` -There's also asynchronous version, but watch device has to be reachable at the time of the call: +There's also an asynchronous version, but the watch device has to be reachable at the time of the call: ```swift powerAuthSDK.sendActivationStatusToWatch { (error) in @@ -1679,7 +1679,7 @@ powerAuthSDK.sendActivationStatusToWatch { (error) in ``` -Sending status of `PowerAuthSDK` instance that is currently without activation also effectively removes all associated tokens from Apple Watch. +Sending the status of the `PowerAuthSDK` instance that is currently without activation also effectively removes all associated tokens from Apple Watch. ### Sending Token to Watch @@ -1691,7 +1691,7 @@ if let token = tokenStore.localToken(withName: "MyToken") { if !token.sendToWatch() { // send message has not been issued, WCSession is probably not available / active } else { - // message has been issued and it's guaranteed that it will be delivered to watch + // message has been issued and it's guaranteed that it will be delivered to the watch } } ``` @@ -1710,19 +1710,19 @@ if let token = tokenStore.localToken(withName: "MyToken") { ### Removing Token from Watch -You can remotely remove token from a paired Apple Watch: +You can remotely remove the token from a paired Apple Watch: ```swift if let token = tokenStore.localToken(withName: "MyToken") { if !token.removeFromWatch() { // send message has not been issued, WCSession is probably not available / active } else { - // message has been issued and it's guaranteed that it will be delivered to watch + // message has been issued and it's guaranteed that it will be delivered to the watch } } ``` -There is also an asynchronous version, but paired watch has to be reachable at the time of the call: +There is also an asynchronous version, but the paired watch has to be reachable at the time of the call: ```swift if let token = tokenStore.localToken(withName: "MyToken") { @@ -1740,8 +1740,8 @@ The `PowerAuthSDK` allows you to specify an external encryption key (called EEK The external encryption key has to be set before the activation is created, or can be added later. The internal state of `PowerAuthSDK` contains information that the factor keys are protected with EEK, so EEK must be known at the time of PowerAuth signature is calculated. You have three options on how to configure the key: -1. Assign EEK into `externalEncryptionKey` property of `PowerAuthConfiguration` in the time of `PowerAuthSDK` object creation. - - This is the most convenient way of using EEK, but the key must be known at the time of `PowerAuthSDK` instantiation. +1. Assign EEK into `externalEncryptionKey` property of `PowerAuthConfiguration` at the time of `PowerAuthSDK` object creation. + - This is the most convenient way of using EEK, but the key must be known at the time of the `PowerAuthSDK` instantiation. - Once the `PowerAuthSDK` instance creates a new activation, then the factor keys will be automatically protected with EEK. 2. Use `PowerAuthSDK.setExternalEncryptionKey()` to set EEK after the `PowerAuthSDK` instance is created. @@ -1749,24 +1749,24 @@ The external encryption key has to be set before the activation is created, or c - You can set the key in any `PowerAuthSDK` state, but be aware that the method will fail in case the instance has a valid activation that doesn't use EEK. - It's safe to set the same EEK multiple times. -3. Use `PowerAuthSDK.addExternalEncryptionKey()` to add EEK and protect the factor keys in case that `PowerAuthSDK` has already a valid activation. +3. Use `PowerAuthSDK.addExternalEncryptionKey()` to add EEK and protect the factor keys in case `PowerAuthSDK` has already a valid activation. - This method is useful in case `PowerAuthSDK` already has a valid activation, but it doesn't use EEK yet. - - The method automatically adds EEK into the internal configuration structure, but be aware, that all future `PowerAuthSDK` usages (e.g. after app restart) require to set EEK by configuration, or by the `setExternalEncryptionKey()` method. + - The method automatically adds EEK into the internal configuration structure, but be aware, that all future `PowerAuthSDK` usages (e.g. after app restart) require setting EEK by configuration, or by the `setExternalEncryptionKey()` method. -You can remove EEK from an existing activation if the key is no longer required. To do this, use `PowerAuthSDK.removeExternalEncryptionKey()` method. Be aware, that EEK must be set by configuration, or by the `setExternalEncryptionKey()` method before you call the remove method. You can also use the `PowerAuthSDK.hasExternalEncryptionKey` property to test whether the key is already set and in use. +You can remove EEK from an existing activation if the key is no longer required. To do this, use the `PowerAuthSDK.removeExternalEncryptionKey()` method. Be aware, that EEK must be set by configuration, or by the `setExternalEncryptionKey()` method before you call the remove method. You can also use the `PowerAuthSDK.hasExternalEncryptionKey` property to test whether the key is already set and in use. ## Share Activation Data -This chapter explains how to share `PowerAuthSDK` activation state between multiple applications from the same vendor. Before you start, you should read [Prepare Data Sharing](PowerAuth-SDK-for-iOS-Extensions.md#prepare-data-sharing) chapter from PowerAuth SDK for iOS Extensions to configure *Keychain Sharing* and *App Groups* in your Xcode project. +This chapter explains how to share the `PowerAuthSDK` activation state between multiple applications from the same vendor. Before you start, you should read [Prepare Data Sharing](PowerAuth-SDK-for-iOS-Extensions.md#prepare-data-sharing) chapter from PowerAuth SDK for iOS Extensions to configure *Keychain Sharing* and *App Groups* in your Xcode project. -This feature is not supported on macOS Catalyst platform. +This feature is not supported on the macOS Catalyst platform. ### Configure Activation Data Sharing -To share activation's state just assign an instance of `PowerAuthSharingConfiguration` object into `PowerAuthConfiguration`: +To share the activation's state just assign an instance of the `PowerAuthSharingConfiguration` object into `PowerAuthConfiguration`: ```swift // Prepare the configuration @@ -1789,16 +1789,16 @@ let powerAuthSDK = PowerAuthSDK(configuration) The `PowerAuthSharingConfiguration` object contains the following properties: -- `appGroup` is a name of app group shared between your applications. -- `appIdentifier` is an identifier unique across your all applications that suppose to use the shared activation data. You can use your applications' bundle identifiers or any other identifier that can be then processed in all your applications. Due to technical limitations, the length of identifier must not exceed 127 bytes, if represented in UTF-8. +- `appGroup` is the name of the app group shared between your applications. +- `appIdentifier` is an identifier unique across your all applications that are supposed to use the shared activation data. You can use your applications' bundle identifiers or any other identifier that can be then processed in all your applications. Due to technical limitations, the length of the identifier must not exceed 127 bytes, if represented in UTF-8. - `keychainAccessGroup` is an access group for keychain sharing. -Unlike the regular configuration the `instanceId` value in `PowerAuthConfiguration` should not be based on application's bundle identifier. This is due the fact that all your applications must use the same identifier, so it's recommended to use some predefined constant string. +Unlike the regular configuration the `instanceId` value in `PowerAuthConfiguration` should not be based on the application's bundle identifier. This is because all your applications must use the same identifier, so it's recommended to use some predefined constant string. ### External pending operations -Some operations, such as activation process, must be exclusively finished in application that initiated the operation. For example, if you start an activation process in one app, then all other applications that use the same shared activation data may receive a failure with `PowerAuthErrorCode.externalPendingOperation` error code until the operation is finished. To prevent such errors you can determine this state in advance: +Some operations, such as the activation process, must be exclusively finished in the application that initiated the operation. For example, if you start an activation process in one app, then all other applications that use the same shared activation data may receive a failure with the `PowerAuthErrorCode.externalPendingOperation` error code until the operation is finished. To prevent such errors you can determine this state in advance: ```swift if let externalOperation = powerAuthSDK.externalPendingOperation { @@ -1860,7 +1860,7 @@ To get the synchronized time, use the following code: if timeService.isTimeSynchronized { // Get synchronized timestamp let timestamp = timeService.currentTime() - // If date object is required, then use the following snippet + // If a date object is required, then use the following snippet let date = Date(timeIntervalSince1970: timestamp) } else { // Time is not synchronized yet. If you call currentTime() then @@ -1869,7 +1869,7 @@ if timeService.isTimeSynchronized { } ``` -The time service provides an additional information about time, such as how precisely the time is synchronized with the server: +The time service provides additional information about time, such as how precisely the time is synchronized with the server: ```swift if timeService.isTimeSynchronized { @@ -1878,13 +1878,13 @@ if timeService.isTimeSynchronized { } ``` -The precision value represents a maximum absolute deviation of synchronized time against the actual time on the server. For example, a value `0.5` means that time provided by `currentTime()` method may be 0.5 seconds ahead or behind of the actual time on the server. If the precision is not sufficient for your purpose, for example, if you need to display a real-time countdown in your application, then try to synchronize the time manually. The precision basically depends on how quickly is the synchronization response received and processed from the server. A faster response results in higher precision. +The precision value represents a maximum absolute deviation of synchronized time against the actual time on the server. For example, a value `0.5` means that the time provided by the `currentTime()` method maybe 0.5 seconds ahead or behind the actual time on the server. If the precision is not sufficient for your purpose, for example, if you need to display a real-time countdown in your application, then try to synchronize the time manually. The precision basically depends on how quickly is the synchronization response received and processed from the server. A faster response results in higher precision. ## Common SDK Tasks ### Error Handling -Most of the SDK methods return an error object of an `NSError` class in case something goes wrong. Of course, it is your responsibility to handle errors these objects represent. There are two ways how you can obtain an error object from PowerAuth SDK for iOS. +Most of the SDK methods return an error object of an `NSError` class in case something goes wrong. Of course, it is your responsibility to handle the errors these objects represent. There are two ways how you can obtain an error object from PowerAuth SDK for iOS. In most cases, you receive an error object via a callback, like in this example: @@ -1894,7 +1894,7 @@ powerAuthSDK.fetchActivationStatus { (status, error) in } ``` -In other cases, you receive error via an exception, like in this example: +In other cases, you receive an error via an exception, like in this example: ```swift do { @@ -1908,7 +1908,7 @@ do { The original Objective-C code uses a method with the `BOOL` return type that passes `NSError**` (pointer to error object) as a method parameter. This syntax is automatically converted to exceptions when using code in Swift. -Errors that are caused by PowerAuth SDK for iOS use `PowerAuthErrorDomain` and `PowerAuthErrorCode` enumeration available via `NSError.powerAuthErrorCode` property. Use these values to determine the type of error. In principle, all errors should be handled in a very similar manner. Use this code snippet for inspiration: +Errors that are caused by PowerAuth SDK for iOS use the `PowerAuthErrorDomain` and `PowerAuthErrorCode` enumeration available via the `NSError.powerAuthErrorCode` property. Use these values to determine the type of error. In principle, all errors should be handled in a very similar manner. Use this code snippet for inspiration: ```swift if error == nil { @@ -1990,15 +1990,15 @@ if error == nil { } ``` -Note that you typically don't need to handle all error codes reported in the `Error` object, or report all that situations to the user. Most of the codes are informational and help the developers properly integrate SDK into the application. A good example is `PowerAuthErrorCode.invalidActivationState`, which typically means that your application's logic is broken and you're using PowerAuthSDK in an unexpected way. +Note that you typically don't need to handle all error codes reported in the `Error` object, or report all those situations to the user. Most of the codes are informational and help the developers properly integrate SDK into the application. A good example is `PowerAuthErrorCode.invalidActivationState`, which typically means that your application's logic is broken and you're using PowerAuthSDK in an unexpected way. Here's the list of important error codes, which the application should properly handle: - `PowerAuthErrorCode.biometryCancel` is reported when the user cancels the biometric authentication dialog - `PowerAuthErrorCode.biometryFallback` is reported when the user cancels the biometric authentication dialog with a fallback button -- `PowerAuthErrorCode.protocolUpgrade` is reported when SDK failed to upgrade itself to a newer protocol version. The code may be reported from `PowerAuthSDK.fetchActivationStatus()`. This is an unrecoverable error resulting in the broken activation on the device, so the best situation is to inform the user about the situation and remove the activation locally. -- `PowerAuthErrorCode.pendingProtocolUpgrade` is reported when the requested SDK operation cannot be completed due to a pending PowerAuth protocol upgrade. You can retry the operation later. The code is typically reported in the situations when SDK is performing protocol upgrade on the background (as a part of activation status fetch), and the application want's to calculate PowerAuth signature in parallel operation. Such kind of concurrency is forbidden since SDK version `1.0.0` -- `PowerAuthErrorCode.externalPendingOperation` is reported when the requested operation collide with the same operation type already started in the external application. +- `PowerAuthErrorCode.protocolUpgrade` is reported when SDK fails to upgrade itself to a newer protocol version. The code may be reported from `PowerAuthSDK.fetchActivationStatus()`. This is an unrecoverable error resulting in the broken activation on the device, so the best situation is to inform the user about the situation and remove the activation locally. +- `PowerAuthErrorCode.pendingProtocolUpgrade` is reported when the requested SDK operation cannot be completed due to a pending PowerAuth protocol upgrade. You can retry the operation later. The code is typically reported in situations when SDK is performing protocol upgrade in the background (as a part of activation status fetch), and the application wants to calculate the PowerAuth signature in parallel operation. Such kind of concurrency is forbidden since SDK version `1.0.0` +- `PowerAuthErrorCode.externalPendingOperation` is reported when the requested operation collides with the same operation type already started in the external application. ### Working with Invalid SSL Certificates @@ -2031,14 +2031,14 @@ The debug log is by default turned off. To turn it on, use the following code: PowerAuthLogSetEnabled(true) ``` -To turn-on even more detailed log, use the following code: +To turn on an even more detailed log, use the following code: ```swift PowerAuthLogSetVerbose(true) ``` -Note that the functions above are effective only if PowerAuth SDK is compiled in `DEBUG` build configuration. +Note that the functions above are effective only if PowerAuth SDK is compiled in the `DEBUG` build configuration. ## Additional Features @@ -2102,7 +2102,7 @@ The obtained `PowerAuthUserInfo` object contains the following properties: | `gender` | `String` | The user's gender | | `birthdate` | `Date` | The user's birthday | | `zoneInfo` | `String` | The user's time zone, e.g. `Europe/Paris` or `America/Los_Angeles` | -| `locale` | `String` | The end-user's locale, represented as a BCP47 language tag3 | +| `locale` | `String` | The end-users locale, represented as a BCP47 language tag3 | | `address` | `PowerAuthUserAddress` | The user's preferred postal address | | `updatedAt` | `Date` | The time the user's information was last updated | | `allClaims` | `[String : Any]` | The full collection of standard claims received from the server | @@ -2120,12 +2120,12 @@ If the `address` is provided, then `PowerAuthUserAddress` contains the following | `allClaims` | `[String : Any]` | Full collection of standard claims received from the server | > Notes: -> 1. Value is false also when claim is not present in `allClaims` dictionary +> 1. Value is false also when the claim is not present in the `allClaims` dictionary > 2. Phone number is typically in E.164 format, for example `+1 (425) 555-1212` or `+56 (2) 687 2400` > 3. This is typically an ISO 639-1 Alpha-2 language code in lowercase and an ISO 3166-1 Alpha-2 country code in uppercase, separated by a dash. For example, `en-US` or `fr-CA` -Be aware that all properties in `PowerAuthUserInfo` and `PowerAuthUserAddress` objects are optional and the availability of information depends on actual implementation on the server. +Be aware that all properties in the `PowerAuthUserInfo` and `PowerAuthUserAddress` objects are optional and the availability of information depends on actual implementation on the server. ### Password Strength Indicator @@ -2136,10 +2136,10 @@ Choosing a weak passphrase in applications with high-security demands can be pot It is sometimes useful to switch PowerAuth SDK to a DEBUG build configuration to get more logs from the library: -- **CocoaPods:** a majority of the SDK is distributed as source codes, so it will match your application's build configuration. Only a low-level C++ codes and several wrapper classes on top of those are precompiled into a static library. -- **Manual installation:** Xcode is matching build configuration across all nested projects, so you usually don't need to care about the configuration switching. +- **CocoaPods:** A majority of the SDK is distributed as source codes, so it will match your application's build configuration. Only low-level C++ codes and several wrapper classes on top of those are precompiled into a static library. +- **Manual installation:** Xcode matches build configuration across all nested projects, so you usually don't need to care about the configuration switching. -The DEBUG build is usually helpful during the application development, but on the other hand, it's highly unwanted in production applications. For this purpose, the `PowerAuthSystem.isInDebug()` method provides information whether the PowerAuth library was compiled in DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG PowerAuth: +The DEBUG build is usually helpful during application development, but on the other hand, it's highly unwanted in production applications. For this purpose, the `PowerAuthSystem.isInDebug()` method provides information on whether the PowerAuth library was compiled in DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG PowerAuth: ```swift #if YOUR_APPSTORE_BUILD_FLAG @@ -2154,7 +2154,7 @@ The DEBUG build is usually helpful during the application development, but on th The `PowerAuthClientConfiguration` can contain multiple request interceptor objects, allowing you to adjust all HTTP requests created by the SDK before their execution. Currently, you can use the following two classes: -- `PowerAuthBasicHttpAuthenticationRequestInterceptor` to add basic HTTP authentication header to all requests +- `PowerAuthBasicHttpAuthenticationRequestInterceptor` to add a basic HTTP authentication header to all requests - `PowerAuthCustomHeaderRequestInterceptor` to add a custom HTTP header to all requests For example: @@ -2166,18 +2166,18 @@ let clientConfig = PowerAuthClientConfiguration() clientConfig.requestInterceptors = [ basicAuth, customHeader ] ``` -We don't recommend implementing the `PowerAuthHttpRequestInterceptor` protocol on your own. The interface allows you to tweak the requests created in the `PowerAuthSDK` but also gives you an opportunity to break things. So, rather than create your own interceptor, contact us and describe what use-case is missing. Also, keep in mind that the interface may change in the future. We can guarantee the API stability of public classes implementing this interface, but not the stability of the interface itself. +We don't recommend implementing the `PowerAuthHttpRequestInterceptor` protocol on your own. The interface allows you to tweak the requests created in the `PowerAuthSDK` but also gives you an opportunity to break things. So, rather than create your own interceptor, contact us and describe what use case is missing. Also, keep in mind that the interface may change in the future. We can guarantee the API stability of public classes implementing this interface, but not the stability of the interface itself. ### Custom User-Agent -The `PowerAuthClientConfiguration` contains `userAgent` property that allows you to set a custom value for "User-Agent" HTTP request header for all requests initiated by the library: +The `PowerAuthClientConfiguration` contains the `userAgent` property that allows you to set a custom value for the "User-Agent" HTTP request header for all requests initiated by the library: ```swift let clientConfig = PowerAuthClientConfiguration() clientConfig.userAgent = "MyClient/1.0.0" ``` -The default value of the property is composed as "APP-EXECUTABLE/APP-VERSION PowerAuth2/PA-VERSION (OS/OS-VERSION, DEVICE-INFO)", for example: "MyApp/1.0 PowerAuth2/1.7.0 (iOS 15.2, iPhone12.1)". The information about application executable and version is get from main bundle and its `Info.plist`. +The default value of the property is composed as "APP-EXECUTABLE/APP-VERSION PowerAuth2/PA-VERSION (OS/OS-VERSION, DEVICE-INFO)", for example: "MyApp/1.0 PowerAuth2/1.7.0 (iOS 15.2, iPhone12.1)". The information about the application executable and version is obtained from the main bundle and its `Info.plist`. If you set `nil` to the `userAgent` property, then the default "User-Agent" provided by the operating system will be used. @@ -2186,7 +2186,7 @@ If you set `nil` to the `userAgent` property, then the default "User-Agent" prov ### tvOS support in CocoaPods -The tvOS SDK is not required by default since the SDK version 1.7.7. If your build or development machine doesn't have tvOS SDK installed, then the `PowerAuthCore` module is precompiled with no tvOS platform included in the final xcframework. Due to fact that CocoaPods keep various build artefacts in its cache, then this might be problematic in case you'll add support for tvOS later, during the development. To fix such possible issues, please remove `PowerAuthCore` pod from the cache: +The tvOS SDK is not required by default since the SDK version 1.7.7. If your build or development machine doesn't have tvOS SDK installed, then the `PowerAuthCore` module is precompiled with no tvOS platform included in the final xcframework. Since CocoaPods keep various build artifacts in its cache, then this might be problematic in case you'll add support for tvOS later, during the development. To fix such possible issues, please remove the `PowerAuthCore` pod from the cache: ```sh pod cache clean 'PowerAuthCore' --all diff --git a/docs/PowerAuth-SDK-for-watchOS.md b/docs/PowerAuth-SDK-for-watchOS.md index 80cdb231..c8ffe7a0 100644 --- a/docs/PowerAuth-SDK-for-watchOS.md +++ b/docs/PowerAuth-SDK-for-watchOS.md @@ -27,7 +27,7 @@ Related documents: ## Installation -This chapter describes how to get PowerAuth SDK for watchOS up and running in your app. In current version, you can choose between CocoaPods and manual library integration. Both types of installation will lead to your watchOS application linked with a dynamic library, provided by the `PowerAuth2ForWatch.[xc]framework`. +This chapter describes how to get PowerAuth SDK for watchOS up and running in your app. In the current version, you can choose between CocoaPods and manual library integration. Both types of installation will lead to your watchOS application linked with a dynamic library, provided by the `PowerAuth2ForWatch.[xc]framework`. To distinguish between SDKs, the following short terms will be used in this document: @@ -42,7 +42,7 @@ To distinguish between SDKs, the following short terms will be used in this docu $ gem install cocoapods ``` -To integrate PowerAuth library into your Xcode project using CocoaPods, specify it in your `Podfile`: +To integrate the PowerAuth library into your Xcode project using CocoaPods, specify it in your `Podfile`: ```ruby target 'YourAppTarget' do @@ -74,14 +74,14 @@ If you prefer not to use CocoaPods as dependency manager, you can integrate Watc The integration process is quite similar to integration of our library for IOS: -1. Open up Terminal.app and go to your top-level project directory and add the library as a submodule: +1. Open up the Terminal.app and go to your top-level project directory and add the library as a submodule: ```sh $ git submodule add https://github.com/wultra/powerauth-mobile-sdk.git PowerAuthLib $ git submodule update --init --recursive ``` - First command will clone PowerAuth SDK into `PowerAuthLib` folder and second, will update all nested submodules. We're expecting that you already did this when you integrated PowerAuth into your application. + The first command will clone PowerAuth SDK into the `PowerAuthLib` folder and second will update all nested submodules. We're expecting that you already did this when you integrated PowerAuth into your application. -2. Open the new `PowerAuthLib` folder, and go to `proj-xcode` sub-folder +2. Open the new `PowerAuthLib` folder, and go to the `proj-xcode` sub-folder 3. Drag the `PowerAuthExtensionSdk.xcodeproj` project file into **Project Navigator** of your application's Xcode project. It should appear nested underneath your application's blue project icon. 4. Select your application project in the Project Navigator to navigate to the target configuration window and select the watch app's target under the **TARGETS** heading in the sidebar. 5. Now select **Build Phases** tab and expand **Target Dependencies** section. Click on the "Plus Sign" and choose **"PowerAuth2ForWatch"** framework from the **"PowerAuthExtensionSdk"** project. @@ -89,13 +89,13 @@ The integration process is quite similar to integration of our library for IOS: ## Configuration -The Watch SDK shares several source codes and configuration principles with main iOS SDK. So, you can prepare the same set of constants as you're already using in your IOS application. The SDK provides just a limited functionality for watch app (for example, you cannot create an activation or calculate a full PowerAuth signature from a watch application) and to do that it requires that your application's code will participate on data synchronization. +The Watch SDK shares several source codes and configuration principles with the main iOS SDK. So, you can prepare the same set of constants as you're already using in your IOS application. The SDK provides just a limited functionality for the watch app (for example, you cannot create an activation or calculate a full PowerAuth signature from a watch application) and to do that it requires that your application's code will participate in data synchronization. ### Prepare Watch Connectivity -The PowerAuth SDK for watchOS is using [WatchConnectivity framework](https://developer.apple.com/documentation/watchconnectivity) to achieve data synchronization between iPhone and Apple Watch devices. If you're not familiar with this framework, then please take a look at least at [WCSession](https://developer.apple.com/documentation/watchconnectivity/wcsession) and [WCSessionDelegate](https://developer.apple.com/documentation/watchconnectivity/wcsessiondelegate) interfaces, before you start. +The PowerAuth SDK for watchOS is using [the WatchConnectivity framework](https://developer.apple.com/documentation/watchconnectivity) to achieve data synchronization between iPhone and Apple Watch devices. If you're not familiar with this framework, then please take a look at least at [WCSession](https://developer.apple.com/documentation/watchconnectivity/wcsession) and [WCSessionDelegate](https://developer.apple.com/documentation/watchconnectivity/wcsessiondelegate) interfaces, before you start. -The Watch SDK doesn't manage state of `WCSession` and doesn't set delegate to the session's singleton instance. It's up to you to properly configure and activate the session, but the application has to cooperate with our SDK to process the messages received from counterpart device. To do this, PowerAuth SDKs on both sides are providing `PowerAuthWCSessionManager` class which can process all incoming messages. Here's an example, how you can implement your simple `SessionManager` for watchOS: +The Watch SDK doesn't manage the state of `WCSession` and doesn't set the delegate to the session's singleton instance. It's up to you to properly configure and activate the session, but the application has to cooperate with our SDK to process the messages received from the counterpart device. To do this, PowerAuth SDKs on both sides are providing the `PowerAuthWCSessionManager` class which can process all incoming messages. Here's an example of how you can implement your simple `SessionManager` for watchOS: ```swift import Foundation @@ -126,7 +126,7 @@ class SessionManager: NSObject, WCSessionDelegate { if PowerAuthWCSessionManager.sharedInstance.processReceivedMessageData(messageData, replyHandler: nil) { return // processed... } - // Other SDKs, or your own messages can be handler here... + // Other SDKs or your own messages can be handled here... print("SessionManager.didReceiveMessageData did not process message.") } @@ -135,7 +135,7 @@ class SessionManager: NSObject, WCSessionDelegate { if PowerAuthWCSessionManager.sharedInstance.processReceivedMessageData(messageData, replyHandler: replyHandler) { return // processed... } - // Other SDKs, or your own messages can be handler here... + // Other SDKs or your own messages can be handled here... print("SessionManager.didReceiveMessageData did not process message. Responding with empty data") replyHandler(Data()) } @@ -145,7 +145,7 @@ class SessionManager: NSObject, WCSessionDelegate { if PowerAuthWCSessionManager.sharedInstance.processReceivedUserInfo(userInfo) { return // processed... } - // Other SDKs, or your own messages can be handler here... + // Other SDKs or your own messages can be handled here... print("SessionManager.didReceiveUserInfo did not process message.") } } @@ -155,7 +155,7 @@ class SessionManager: NSObject, WCSessionDelegate { The code above is very similar to its [iOS counterpart](./PowerAuth-SDK-for-iOS.md#prepare-watch-connectivity). -The example above is implementing only a minimum set of methods from `WCSessionDelegate` protocol to make message passing work. You also have to implement a very similar class for your IOS application. The important part is that at some point, both applications (iOS and watchOS) have to call `SessionManager.shared.activateSession()` to make the transfers possible. Once you activate your session on the device, you can use all APIs related to the communication. +The example above is implementing only a minimum set of methods from the `WCSessionDelegate` protocol to make message passing work. You also have to implement a very similar class for your IOS application. The important part is that at some point, both applications (iOS and watchOS) have to call `SessionManager.shared.activateSession()` to make the transfers possible. Once you activate your session on the device, you can use all APIs related to the communication. ### Configure PowerAuth for WatchKit @@ -178,7 +178,7 @@ class InterfaceController: WKInterfaceController { config.appKey = "sbG8gd...MTIzNA==" config.appSecret = "aGVsbG...MTIzNA==" config.masterServerPublicKey = "MTIzNDU2Nz...jc4OTAxMg==" - // URL is optional, current version of Watch SDK doesn't perform own networking. + // URL is optional, the current version of Watch SDK doesn't perform its own networking. config.baseEndpointUrl = "https://localhost:8080/demo-server" return PowerAuthWatchSDK(configuration: config)! @@ -189,15 +189,15 @@ class InterfaceController: WKInterfaceController { ``` -**IMPORTANT:** The configuration used above must match configuration used in the IOS application otherwise `PowerAuthWatchSDK` instance will never be synchronized with its iOS counterpart. Take a special care of `instanceId` property, which **has to match with value from iPhone**. By default, PowerAuth for iOS is using application's bundle-id, so don't make a mistake and don't use watchOS application's bundle identifier. +**IMPORTANT:** The configuration used above must match the configuration used in the IOS application otherwise `PowerAuthWatchSDK` instance will never be synchronized with its iOS counterpart. Take special care of the `instanceId` property, which **has to match with the value from iPhone**. By default, PowerAuth for iOS is using the application's bundle ID, so don't make a mistake and don't use the watchOS application's bundle identifier. -The Watch SDK doesn't provide a shared instance for `PowerAuthWatchSDK` class and therefore you have to manage that instance on your own. The example above shows a beginning of controller implementing simple WatchKit scene. For all other code examples, we're going to use `self.powerAuthWatch` as properly initialized instance of `PowerAuthWatchSDK` object. +The Watch SDK doesn't provide a shared instance for the `PowerAuthWatchSDK` class and therefore you have to manage that instance on your own. The example above shows the beginning of the controller implementing a simple WatchKit scene. For all other code examples, we're going to use `self.powerAuthWatch` as a properly initialized instance of the `PowerAuthWatchSDK` object. ## Getting Device Activation Status -Unlike the iOS SDK, the Watch SDK provides only a limited information about activation status. You can actually check only whether there's locally stored activation on iPhone, or not: +Unlike the iOS SDK, the Watch SDK provides only limited information about activation status. You can actually check only whether there's locally stored activation on iPhone, or not: ```swift if self.powerAuthWatch.hasValidActivation() { @@ -205,7 +205,7 @@ if self.powerAuthWatch.hasValidActivation() { } ``` -The `hasValidActivation()` method is synchronous and reflects only actual state stored locally on Apple Watch. To get update from iPhone, you can use following code: +The `hasValidActivation()` method is synchronous and reflects only the actual state stored locally on the Apple Watch. To get an update from your iPhone, you can use the following code: ```swift // Lazy version @@ -214,7 +214,7 @@ if !self.powerAuthWatch.updateActivationStatus() { // message has not been issued, WCSession is probably not available / active } else { // message has been issued and it's guaranteed that it will be delivered to iPhone - // The iPhone has to issue response in similar lazy way. + // The iPhone has to issue responses in a similar lazy way. } // Or asynchronous version... @@ -229,27 +229,27 @@ self.powerAuthWatch.updateActivationStatus { (activationId, error) in } ``` -The asynchronous `updateActivationStatus` method can be used only if `WCSession` reports that counterpart device is reachable. +The asynchronous `updateActivationStatus` method can be used only if `WCSession` reports that the counterpart device is reachable. ## Token-Based Authentication -WARNING: Before you start using access tokens, please visit our [wiki page for powerauth-crypto](https://github.com/wultra/powerauth-crypto/blob/develop/docs/MAC-Token-Based-Authentication.md) for more information about this feature. You can also check documentation about tokens available in [PowerAuth SDK for iOS](./PowerAuth-SDK-for-iOS.md#token-based-authentication). +WARNING: Before you start using access tokens, please visit our [wiki page for powerauth-crypto](https://github.com/wultra/powerauth-crypto/blob/develop/docs/MAC-Token-Based-Authentication.md) for more information about this feature. You can also visit the documentation about tokens available in [PowerAuth SDK for iOS](./PowerAuth-SDK-for-iOS.md#token-based-authentication). -The basic principles for working with tokens on watchOS are the same as for iOS applications, so the interface is practically identical to what you know from PowerAuth SDK for iOS. The main difference is that watchOS application cannot ask PowerAuth server to create a new token, but as a replacement, it can ask for token already stored on the iPhone. In fact, from point of watchOS application's view, the iOS application is just a kind of "remote server" providing tokens. +The basic principles for working with tokens on watchOS are the same as for iOS applications, so the interface is practically identical to what you know from PowerAuth SDK for iOS. The main difference is that the watchOS application cannot ask the PowerAuth server to create a new token, but as a replacement, it can ask for a token already stored on the iPhone. In fact, from the point of the watchOS application's view, the iOS application is just a kind of "remote server" providing tokens. ### Getting Token -To get an access token already stored on watch device, you can use following code: +To get an access token already stored on the watch device, you can use the following code: ```swift if let token = self.powerAuthWatch.tokenStore.localToken(withName: "MyToken") { - // you have a token which can generate authorization headers + // you have a token that can generate authorization headers } ``` ### Getting Token From iPhone -To get an access token already stored on iPhone, you can use following code: +To get an access token already stored on the iPhone, you can use the following code: ```swift self.powerAuthWatch.tokenStore.requestAccessToken(withName: "MyToken") { (token, error) in @@ -263,20 +263,20 @@ self.powerAuthWatch.tokenStore.requestAccessToken(withName: "MyToken") { (token, ### Generating Authorization Header -Once you have a `PowerAuthToken` object, use following code to generate an authorization header: +Once you have a `PowerAuthToken` object, use the following code to generate an authorization header: ```swift if let header = token.generateHeader() { let httpHeader = [ header.key : header.value ] // now you can attach that httpHeader to your HTTP request } else { - // in case of nil, token is no longer valid + // in case of nil, the token is no longer valid } ``` ### Removing Token Locally -To remove token locally, you can simply use following code: +To remove the token locally, you can simply use the following code: ```swift let tokenStore = self.powerAuthWatch.tokenStore @@ -286,26 +286,26 @@ tokenStore.removeLocalToken(withName: "MyToken") tokenStore.removeAllLocalTokens() ``` -Note that removing tokens locally on watch device has no effect on the same tokens stored on iPhone. +Note that removing tokens locally on a watch device does not affect the same tokens stored on an iPhone. ### Removing Token From iPhone -The token store available on watchOS exposes `removeAccessToken()` method, but the implementation always returns `PowerAuthErrorCode.invalidToken` error. This kind of operation is not supported. +The token store available on watchOS exposes the `removeAccessToken()` method, but the implementation always returns the `PowerAuthErrorCode.invalidToken` error. This kind of operation is not supported. ## Common SDK Tasks ### Error Handling -You can follow the same practices as for iOS SDK because Watch SDK codebase is sharing the same error constants with a full PowerAuth SDK for iOS. +You can follow the same practices as for iOS SDK because the Watch SDK codebase shares the same error constants with a full PowerAuth SDK for iOS. ### Debug Build Detection It is sometimes useful to switch Watch SDK to a DEBUG build configuration, to get more logs from the library: - **CocoaPods:** we currently don't provide DEBUG pod. This will be resolved in some future versions of Watch SDK. -- **Manual installation:** Xcode is matching build configuration across all nested projects, so you usually don't need to care about the configuration switching. +- **Manual installation:** Xcode matches build configuration across all nested projects, so you usually don't need to care about the configuration switching. -The DEBUG build is usually helpful during the application development, but on other side, it's highly unwanted in production applications. For this purpose, the `PowerAuthSystem.isInDebug()` method provides an information, whether the PowerAuth for watchOS library was compiled in DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG library: +The DEBUG build is usually helpful during the application development, but on the other side, it's highly unwanted in production applications. For this purpose, the `PowerAuthSystem.isInDebug()` method provides information on whether the PowerAuth for the watchOS library was compiled in the DEBUG configuration. It is a good practice to check this flag and crash the process when the production application is linked against the DEBUG library: ```swift #if YOUR_APPSTORE_BUILD_FLAG @@ -318,7 +318,7 @@ The DEBUG build is usually helpful during the application development, but on ot ## Troubleshooting -This section of document contains a various workarounds and tips for Watch SDK usage. +This section of the document contains various workarounds and tips for Watch SDK usage. ### WCSession Activation Sequence on iOS @@ -326,7 +326,7 @@ You should check recommendations about [WCSession's activation sequence on iOS]( ### Cocoapods Integration Fails -In case that `pod update` fails on various errors, try following workarounds: +In case `pod update` fails on various errors, try the following workarounds: - Update your pod tool at first: ```bash @@ -335,12 +335,12 @@ In case that `pod update` fails on various errors, try following workarounds: $ echo after `pod --version` ``` -- To reveal more details about the problem, try to run update with a verbose switch: +- To reveal more details about the problem, try to run an update with a verbose switch: ```bash $ pod update --verbose ``` -- Clean your pod cache. You can remove one specific pod from cache, or clean it all: +- Clean your pod cache. You can remove one specific pod from the cache, or clean it all: ```bash $ pod cache clean PowerAuth2 $ pod cache clean PowerAuth2ForWatch @@ -348,4 +348,4 @@ In case that `pod update` fails on various errors, try following workarounds: $ pod cache clean --all ``` -- Try to run `pod update` for twice. The Cocoapods tool is mystery and sometimes just doesn't work as you would expect. +- Try to run `pod update` for twice. The Cocoapods tool is a mystery and sometimes just doesn't work as you would expect. diff --git a/docs/Readme.md b/docs/Readme.md index 05c5b832..e5ef7492 100644 --- a/docs/Readme.md +++ b/docs/Readme.md @@ -1,6 +1,6 @@ # PowerAuth Mobile SDK -In order to connect to the [PowerAuth](https://www.wultra.com/product/powerauth-mobile-security-suite) service, mobile applications need to perform the required network and cryptographic processes, as described in the PowerAuth documentation. To simplify implementation of these processes, developers can use iOS and Android libraries from this repository. +To connect to the [PowerAuth](https://www.wultra.com/product/powerauth-mobile-security-suite) service, mobile applications need to perform the required network and cryptographic processes, as described in the PowerAuth documentation. To simplify the implementation of these processes, developers can use iOS and Android libraries from this repository. ## Integration Tutorials @@ -15,7 +15,7 @@ Related projects ## Migration guides -If you need to upgrade PowerAuth Mobile SDK to a newer version, you can check following migration guides: +If you need to upgrade PowerAuth Mobile SDK to a newer version, you can check the following migration guides: - [Migration from version `1.7.x` to `1.8.x`](Migration-from-1.7-to-1.8.md) - [Migration from version `1.6.x` to `1.7.x`](Migration-from-1.6-to-1.7.md) diff --git a/docs/Runtime-Security.md b/docs/Runtime-Security.md index 9c119dd6..86bcfaae 100644 --- a/docs/Runtime-Security.md +++ b/docs/Runtime-Security.md @@ -6,12 +6,12 @@ This chapter attempts to provide an explanation of the issues to allow more info ## Mobile OS Security -Any security-related code, such as cryptographic core, ultimately runs in a mobile operating system, such as iOS or Android. While Apple and Google do their best to build secure software, hackers always find a way to bypass system security features. General availability of jailbreak/rooting is a living testament to this, as well as the rise of Android mobile malware. +Any security-related code, such as cryptographic core, ultimately runs in a mobile operating system, such as iOS or Android. While Apple and Google do their best to build secure software, hackers always find a way to bypass system security features. The general availability of jailbreak/rooting is a living testament to this, as well as the rise of Android mobile malware. When running in a vulnerable OS, apps can be manipulated by an attacker (for example, via mobile malware, or techniques such as “trust-jacking”) that is either: - armed with a rooting framework (and hence can penetrate through the sandboxing features of a mobile OS, even on devices that were not previously rooted by the user), or … -- merely misusing some of the commonly available system interfaces, such as an ability to install own keyboards or screen readers in the system. +- merely misusing some of the commonly available system interfaces, such as the ability to install their own keyboards or screen readers in the system. ![ Weaknesses in Mobile Runtime ](./images/runtime-01.png) @@ -30,13 +30,13 @@ An example of such an attack may look like this: Note that the attack may be even performed without root on some devices and operating systems, using some insufficient system design. For example: -1. An attacker can prepare an app with the Accessibility service support on Android and tricks the user into activating such a service. +1. An attacker can prepare an app with Accessibility service support on Android and trick the user into activating such a service. 2. The attacker can record the PIN code using the accessibility service in the case of an incorrect PIN code implementation. 3. The attacker can launch the app from the background: - use the accessibility service to replay the PIN code - navigate through the app using taps and other gestures - type in the payment details - - replay the PIN code again and confirm payment + - replay the PIN code again and confirm the payment As a result, mobile apps with high-security requirements cannot rely on the OS security features. Instead, they need to protect themselves with advanced obfuscation, app integrity checks, and proactive anti-tampering features. @@ -46,16 +46,16 @@ There are several attack vectors that you should take into account: - **Rooting / Jailbreaking** - These terms represent a modification of mobile OS that essentially removes any built-in security measures, such as application sandboxing or permission control. While this change is usually harmless on its own, it is a strong enabler for several subsequent attack vectors, such as: - **Debugger Connection** - By connecting the debugger to the running application, the attacker gains control over any data visible in the application code (for example, can read labels and buttons titles, intercept touches, etc.) and can manipulate the application code execution (for example, modify outcomes of methods, call any methods, navigate the user interface, etc.) - - **App Repackaging** - By bypassing the application sandbox, an attacker may change or replace the application on the device with a fraudulent app version. This may be either a completely different app, or an original app with some additional malicious code that logs events to obtain data from the app, or changes the app behavior to execute undesired business logic. + - **App Repackaging** - By bypassing the application sandbox, an attacker may change or replace the application on the device with a fraudulent app version. This may be either a completely different app or an original app with some additional malicious code that logs events to obtain data from the app or changes the app behavior to execute undesired business logic. - **Framework Injection or Native Code Hooks** - The attacker does not have to modify the application code directly. Instead, the attacker can modify a system framework or a library and change the behavior of any application indirectly as a result. This allows an attacker to modify the behavior of any app on a device without knowing much about a particular app code or structure. - - **Device Cloning** - By bypassing the application sandbox, the attacker can obtain any data stored inside the application sandbox, including some records inside the protected space (such as records inside the iOS Keychain that are not protected, for example, using biometry). This alone does not allow bypassing the cryptographic algorithms such as PowerAuth - the data stored in the sandbox are further encrypted using a PIN code or stored in Secure Enclave in the case of biometry. The attacker may, however, get access to some secondary data, such as "possession factor" related signing keys, identifiers, or other data cached by the application. -- **Accessibility Services** (Android) - By tricking users into enabling the Accessibility service, the attacker can read anything that happens in the device screen, or even perform gestures and type the text. This indeed presents a significant problem to any application that needs to keep the user data secure. Information about accounts or transactions may leak through the accessibility services, as well as PIN codes or passwords (in the case of an incorrect text entry implementation). The fact that many well-known apps misuse the Accessibility service for some "acceptable" tasks does not help as well... -- **Malicious Keyboards** - Mobile operating systems allow installing own keyboard that may use custom logic to handle the user input. This obviously presents some possible security issues, especially when handling sensitive data, such as passwords or PIN codes. Custom keyboards can also be used to perform overlay attacks (the keyboard would occupy bigger than usual space to present a fake UI over the screen) on the Android operating system. + - **Device Cloning** - By bypassing the application sandbox, the attacker can obtain any data stored inside the application sandbox, including some records inside the protected space (such as records inside the iOS Keychain that are not protected, for example, using biometry). This alone does not allow bypassing cryptographic algorithms such as PowerAuth - the data stored in the sandbox are further encrypted using a PIN code or stored in Secure Enclave in the case of biometry. The attacker may, however, get access to some secondary data, such as "possession factor" related signing keys, identifiers, or other data cached by the application. +- **Accessibility Services** (Android) - By tricking users into enabling the Accessibility service, the attacker can read anything that happens on the device screen, or even perform gestures and type the text. This indeed presents a significant problem to any application that needs to keep the user data secure. Information about accounts or transactions may leak through the accessibility services, as well as PIN codes or passwords (in the case of an incorrect text entry implementation). The fact that many well-known apps misuse the Accessibility service for some "acceptable" tasks does not help as well... +- **Malicious Keyboards** - Mobile operating systems allow installing their own keyboard that may use custom logic to handle the user input. This obviously presents some possible security issues, especially when handling sensitive data, such as passwords or PIN codes. Custom keyboards can also be used to perform overlay attacks (the keyboard would occupy bigger than usual space to present a fake UI over the screen) on the Android operating system. Of course, the attacker can combine the vectors above quite creatively (for example, device cloning with PIN code theft) to amplify the damage. -Rooting and jailbreaking are generally terms used for a complete replacement of a legitimate OS with an OS that removes all protective mechanisms. However, the attacker does not need to do a full rooting or jailbreaking to gain access to some system permissions. Instead, the attacker can merely misuse some system vulnerability to escalate user permissions. In such a case, the result is in principle the same as if the attacker performed a full jailbreak or rooting: complete control over the end user's system. +Rooting and jailbreaking are generally terms used for a complete replacement of a legitimate OS with an OS that removes all protective mechanisms. However, the attacker does not need to do full rooting or jailbreaking to gain access to some system permissions. Instead, the attacker can merely misuse some system vulnerability to escalate user permissions. In such a case, the result is in principle the same as if the attacker performed a full jailbreak or rooting: complete control over the end user's system. ## Typical Remedies @@ -66,9 +66,9 @@ As a result, a new category of solutions emerged to provide less straightforward Unless you are looking for a strictly formal fix (meaning "making the pen-testers happy, while not fixing the actual issue"), we strongly suggest you deploy such technology. -We encourage you to use any system provided protection measures as much as possible while coding your application, such as: +We encourage you to use any system-provided protection measures as much as possible while coding your application, such as: - Built-in secure storage (iOS Keychain with Secure Enclave, Android KeyStore with StrongBox). -- App transport security to enforce newer TLS standards, check for the correctness of the certificate, and disallow any HTTP connections. +- App transport security to enforce newer TLS standards, check for the correctness of the certificate and disallow any HTTP connections. However, please note that on a compromised device, these features may be disabled or bypassed, and therefore, they will not help to make your application secure. diff --git a/docs/Supported-Versions.md b/docs/Supported-Versions.md index 5e6d317c..1e06cbe8 100644 --- a/docs/Supported-Versions.md +++ b/docs/Supported-Versions.md @@ -14,9 +14,9 @@ We currently support the following versions of mobile OS: On iOS: -- The used biometric type depends on the particular device capabilities. +- The used biometric type depends on the particular device's capabilities. On Android: -- Since Android 6.0, we offer the biometric authentication via the fingerprint authentication support. -- Since Android 9.0, we offer the biometric authentication via the newly introduced unified biometric authentication dialog. However, we had to fallback to the old fingerprint authentication on several devices where the new biometric support is broken (as a well-known issue). +- Since Android 6.0, we have offered biometric authentication via fingerprint authentication support. +- Since Android 9.0, we have offered biometric authentication via the newly introduced unified biometric authentication dialog. However, we had to fallback to the old fingerprint authentication on several devices where the new biometric support was broken (as a well-known issue).