From 9c2aacb0b859c59712795857d6e49462da7f2320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20=C4=8Eurech?= <1719814+hvge@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:26:52 +0200 Subject: [PATCH] Better Core Password support in SDK API (#470) * Apple: Fix #465: Allow core password usage in high level API - Added more tests covering the core password usage. - Added method that securely clear also immutable password. * Android: Fix #465: Allow core Password usage in high level API - cc7 update adds data cleanup when byte array is copied from JVM to JNI * Android: Fixed javadoc warnings --- cc7 | 2 +- docs/Migration-from-1.6-to-1.7.md | 35 ++- docs/PowerAuth-SDK-for-Android.md | 240 ++++++++++++++++- docs/PowerAuth-SDK-for-iOS.md | 182 ++++++++++++- include/PowerAuth/Password.h | 7 + .../support/PowerAuthTestHelper.java | 16 ++ .../integration/tests/ActivationHelper.java | 141 +++++++--- .../tests/StandardActivationTest.java | 20 ++ .../sdk/PowerAuthAuthenticationHelper.java | 65 +++++ .../sdk/PowerAuthAuthenticationTests.java | 59 ++++- .../sdk/PowerAuthAuthentication.java | 177 +++++++++++-- .../security/powerauth/sdk/PowerAuthSDK.java | 247 ++++++++++++++++-- .../PowerAuth2.xcodeproj/project.pbxproj | 14 +- .../PowerAuth2/PowerAuthAuthentication.h | 86 +++++- .../PowerAuth2/PowerAuthAuthentication.m | 167 ++++++++---- proj-xcode/PowerAuth2/PowerAuthSDK.h | 133 ++++++++-- proj-xcode/PowerAuth2/PowerAuthSDK.m | 95 +++++-- .../private/PowerAuthAuthentication+Private.h | 3 - .../PowerAuthAuthentication.h | 64 +---- .../PowerAuthAuthentication.m | 73 ++---- .../private/PowerAuthAuthentication+Private.h | 5 +- .../PowerAuthAuthenticationTests.m | 36 +-- .../PowerAuthAuthentication.h | 64 +---- .../PowerAuthAuthentication.m | 73 ++---- .../private/PowerAuthAuthentication+Private.h | 5 +- .../PowerAuthCorePasswordHelper.h | 23 ++ .../PowerAuthCorePasswordHelper.m | 32 +++ .../PowerAuthSDKDefaultTests.m | 161 ++++++++++-- .../PowerAuthSDKProtocolUpgradeTests.m | 6 +- .../PowerAuthSDKSharedTests.m | 10 +- .../PowerAuthSdkTestHelper.h | 27 +- .../PowerAuthSdkTestHelper.m | 75 ++++-- .../PowerAuthAuthenticationTests.m | 119 +++++++-- .../PowerAuth2TestsHostApp/ContentView.swift | 15 ++ .../PowerAuthCore/PowerAuthCorePassword.h | 58 +++- .../PowerAuthCore/PowerAuthCorePassword.mm | 56 ++-- .../PowerAuthCorePasswordTests.m | 4 +- src/PowerAuth/Password.cpp | 11 +- 38 files changed, 2058 insertions(+), 548 deletions(-) create mode 100644 proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationHelper.java create mode 100644 proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.h create mode 100644 proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.m diff --git a/cc7 b/cc7 index 4b310947..8e1fbade 160000 --- a/cc7 +++ b/cc7 @@ -1 +1 @@ -Subproject commit 4b310947575ad4d9077b238f81eb2b08d8e2c7fa +Subproject commit 8e1fbadede0ff47d44b5ca0420d77eaca168c643 diff --git a/docs/Migration-from-1.6-to-1.7.md b/docs/Migration-from-1.6-to-1.7.md index 58c4d24c..b7733163 100644 --- a/docs/Migration-from-1.6-to-1.7.md +++ b/docs/Migration-from-1.6-to-1.7.md @@ -21,7 +21,7 @@ PowerAuth Mobile SDK in version `1.7.0` is a maintenance release that brings mul - `PowerAuthAuthentication.possessionWithBiometry()` - create authentication object for signing with possession and biometry factors. - `PowerAuthAuthentication.commitWithPassword()` - create authentication object for activation commit purpose. - `PowerAuthAuthentication.commitWithPasswordAndBiometry()` - create authentication object for activation commit purpose. - - `getPassword()` (e.g. `password` in Kotlin) is a new replacement for getting value of deprecated `usePassword`. + - `getPassword()` (e.g. `password` in Kotlin) is a new replacement for getting value of deprecated `usePassword`. The function returns `Password` object since SDK version 1.7.2. If you see no deprecation warnings in your application code, then please add the following lines into your `build.gradle` file: ```gradle @@ -68,6 +68,8 @@ PowerAuth Mobile SDK in version `1.7.0` is a maintenance release that brings mul - `IOException` is no longer reported from SDK's internal networking. Now all such exceptions are wrapped into `PowerAuthErrorException` with `NETWORK_ERROR` code set. +- Please read also [Changes introduced in 1.7.2](#changes-in-172) version. + ## iOS & tvOS ### API changes @@ -111,11 +113,9 @@ PowerAuth Mobile SDK in version `1.7.0` is a maintenance release that brings mul - PowerAuth mobile SDK is now using custom "User-Agent" for all HTTP requests initiated from the library. - You can see how's user agent string constructed by reading a new `userAgent` property of `PowerAuthClientConfiguration` object. - - To set the previous networking behavior, you can set `nil` - -### Other changes in 1.7.2+ - -- Changed value returned from `PowerAuthCorePassword.validatePasswordComplexity()` function, including the prototype of the validation block. + - To set the previous networking behavior, you can set `nil` + +- Please read also [Changes introduced in 1.7.2](#changes-in-172) version. ## iOS & tvOS App Extensions @@ -132,3 +132,26 @@ PowerAuth Mobile SDK in version `1.7.0` is a maintenance release that brings mul - `PowerAuthWatchSDK.activationId` property is now deprecated. Please use `activationIdentifier` as a replacement. - All asynchronous methods from `PowerAuthTokenStore` protocol now returns objects conforming to `PowerAuthOperationTask` and therefore the returned operation can be canceled directly. - `PowerAuthTokenStore.cancelTask()` is now deprecated. You can cancel the returned asynchronous operation directly. + + +## Changes in 1.7.2+ + +### Android + +The following interfaces are marked as deprecated since 1.7.2 version: + +- `PowerAuthSDK.validatePasswordCorrect()` function is now deprecated. You can use `validatePassword()` function as a replacement. + +- Direct access to `PowerAuthAuthentication.usePassword` property is no longer possible. Application written in Kotlin will report warning, due to mapping to new, but already deprecated `setUsePassword()` or `getUsePassword()` functions. To test whether knowledge factor is set in authentication object, use `getPassword()` function. The function was introduced in version 1.7.0, but it's returned value is now `Password` object, instead of `String`. + +### iOS & tvOS + +- Changed value returned from `PowerAuthCorePassword.validatePasswordComplexity()` function, including the prototype of the validation block. + +The following interfaces are marked as deprecated since 1.7.2 version: + +- `PowerAuthSDK.validatePasswordCorrect(_, callback:)` is deprecated, use `validatePassword(password:, callback:)` as a replacement. + +- `PowerAuthSDK.addBiometryFactor(_, callback)` is deprecated, use `addBiometryFactor(password:, callback:)` as a replacement. + +- Using `PowerAuthAuthentication.usePassword` property is now deprecated. To test whether authentication has the knowledge factor set, use `password` property, which contains nullable `PowerAuthCorePassword` object. diff --git a/docs/PowerAuth-SDK-for-Android.md b/docs/PowerAuth-SDK-for-Android.md index a9953a36..b7a7d005 100644 --- a/docs/PowerAuth-SDK-for-Android.md +++ b/docs/PowerAuth-SDK-for-Android.md @@ -19,6 +19,7 @@ - [Symmetric Offline Multi-Factor Signature](#symmetric-offline-multi-factor-signature) - [Verify Server-Signed Data](#verify-server-signed-data) - [Password Change](#password-change) +- [Working with passwords securely](#working-with-passwords-securely) - [Biometric Authentication Setup](#biometric-authentication-setup) - [Device Activation Removal](#activation-removal) - [End-To-End Encryption](#end-to-end-encryption) @@ -1255,7 +1256,7 @@ For this purpose, you can use the following code: val oldPassword = "1234" // [2] Validate password on the server -powerAuthSDK.validatePasswordCorrect(context, oldPassword, object: IValidatePasswordListener { +powerAuthSDK.validatePassword(context, oldPassword, object: IValidatePasswordListener { override fun onPasswordValid() { // Proceed to the new password setup } @@ -1276,7 +1277,7 @@ powerAuthSDK.changePasswordUnsafe(oldPassword, newPassword) String oldPassword = "1234"; // [2] Validate password on the server -powerAuthSDK.validatePasswordCorrect(context, oldPassword, new IValidatePasswordListener() { +powerAuthSDK.validatePassword(context, oldPassword, new IValidatePasswordListener() { @Override public void onPasswordValid() { // Proceed to the new password setup @@ -1300,6 +1301,241 @@ powerAuthSDK.changePasswordUnsafe(oldPassword, newPassword); **Now, beware!** Since the device does not know the actual old password, you need to make sure that the old password is validated before you use it in `unsafeChangePassword`. In case you provide the wrong old password, it will be used to decrypt the original data, and these data will be encrypted using a new password. As a result, the activation data will be broken and irreversibly lost. + +## 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. + +### Problem explanation + +If you store the user's password in simpe string then there's 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 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 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: + +- 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 clears 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). + + +**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. + + +### 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): + + +```kotlin +// Change password from "oldPassword" to "newPassword". +val oldPass = Password("oldPassword") +val newPass = Password("newPassword") +powerAuthSDK.changePassword(context, oldPass, newPass, object: IChangePasswordListener { + override fun onPasswordChangeSucceed() { + // Password was changed + } + + override fun onPasswordChangeFailed(t: Throwable) { + // Error occurred + } +}) +``` +```java +// Change password from "oldPassword" to "newPassword". +Password oldPass = new Password("oldPassword"); +Password newPass = new Password("newPassword") +powerAuthSDK.changePassword(context, oldPass, newPass, new IChangePasswordListener() { + @Override + public void onPasswordChangeSucceed() { + // Password was changed + } + + @Override + public void onPasswordChangeFailed(Throwable t) { + // Error occurred + } +}) +``` + + + +### 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: + +- 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. + +- Use `Password()` object that allows you to manipulate with the content of the PIN + +Here's the simple pseudo-controller example: + +```kotlin +class EnterPinScene(val desiredPinLength: Int = 4) { + + private var pinInstance: Password? = null + private val pin: Password get() = pinInstance ?: throw IllegalStateException() + + fun onEnterScene() { + // Allocate password when entering to the scene. + // Constructor with no parameters create mutable Password. + pinInstance = Password() + } + + fun onLeaveScene() { + // Dereference and destroy the password object, when 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 + // started with this object at input. + pinInstance?.destroy() + pinInstance = null + } + + fun onDeleteButtonAction() { + pin.removeLastCharacter() + } + + 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. + // See https://stackoverflow.com/questions/9834964/char-to-unicode-more-than-uffff-in-java + pin.addCharacter(pinCharacter.code) + if (pin.length() == desiredPinLength) { + onContinueAction(pin) + } + } + + fun onPinButtonActionSimplified(pinIndex: Int) { + // This is a simplified version of onPinButtonAction() that use + // simple PIN button index as input. + if (pinIndex < 0 || pinIndex > 9) { + throw IllegalArgumentException("Wrong PIN index") + } + // You don't need to add 48 (code for character "0") to the index, + // unless your previous implementation was using number characters. + pin.addCharacter(48 + pinIndex) + if (pin.length() == desiredPinLength) { + onContinueAction(pin) + } + } + + fun onContinueAction(pin: Password) { + // Do something with 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: + + +```kotlin +val passwordString = "nbusr123" +val password = Password(passwordString) +``` +```java +final String passwordString = "nbusr123"; +final Password password = new Password(passwordString); +``` + + +### Create 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: + + +```kotlin +val passwordData = Base64.decode("bmJ1c3IxMjMK", Base64.NO_WRAP) +val password = Password(passwordData) +``` +```java +final byte[] passwordData = Base64.decode("bmJ1c3IxMjMK", Base64.NO_WRAP); +final Password password = new Password(passwordData); +``` + + + +### Compare two passwords + +To compare two passwords, use `isEqual(to:)` method: + + +```kotlin +val password1 = Password("1234") +val password2 = Password("Hello") +val password3 = Password() +password3.addCharacter(0x31) +password3.addCharacter(0x32) +password3.addCharacter(0x33) +password3.addCharacter(0x34) +print("${password1.isEqualToPassword(password2)}") // false +print("${password1 == password3}") // true +``` +```java +final Password password1 = new Password("1234") +final Password password2 = new Password("Hello") +final Password password3 = new Password() +password3.addCharacter(0x31) +password3.addCharacter(0x32) +password3.addCharacter(0x33) +password3.addCharacter(0x34) +Log.d("TAG", (password1.isEqualToPassword(password2)).toString()) // false +Log.d("TAG", (password1.equals(password2)).toString()) // true +``` + + +### Validate password complexity + +The `Password` object doesn't provide functions that validate password complexity, but allows you to implement such functionality on your own: + +```kotlin +enum class PasswordComplexity(val value: Int) { + WEAK(0), + GOOD(1), + STRONG(2); + + companion object { + fun fromInt(value: Int): PasswordComplexity = values().first { it.value == value } + } +} + +// 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 +// passphrase copy to foundation objects' buffers. +fun superPasswordValidator(password: ByteArray): PasswordComplexity { + // This is just an example, please do not use such trivial validation in your + // production application :) + if (password.size < 4) { + return PasswordComplexity.WEAK + } else if (password.size < 8) { + return PasswordComplexity.GOOD + } + return PasswordComplexity.STRONG +} + +// Convenient wrapper to validatePasswordComplexity() method +fun Password.validateComplexity(): PasswordComplexity { + val resultValue = validatePasswordComplexity { passwordBytes -> + superPasswordValidator(passwordBytes).value + } + return PasswordComplexity.fromInt(resultValue) +} +``` + + +You can use our [Passphrase meter](https://github.com/wultra/passphrase-meter) library as a proper password validation solution. + + + ## Biometric Authentication Setup PowerAuth SDK for Android provides an abstraction on top of the base Biometric Authentication support. While the authentication / data signing itself is handled using the `PowerAuthAuthentication` object used in [regular request signing](#data-signing), other biometry-related processes require their own API. diff --git a/docs/PowerAuth-SDK-for-iOS.md b/docs/PowerAuth-SDK-for-iOS.md index e04ba7cc..14a8f287 100644 --- a/docs/PowerAuth-SDK-for-iOS.md +++ b/docs/PowerAuth-SDK-for-iOS.md @@ -25,6 +25,7 @@ - [Symmetric Offline Multi-Factor Signature](#symmetric-offline-multi-factor-signature) - [Verify Server-Signed Data](#verify-server-signed-data) - [Password Change](#password-change) +- [Working with passwords securely](#working-with-passwords-securely) - [Biometry Setup](#biometry-setup) - [Device Activation Removal](#activation-removal) - [End-To-End Encryption](#end-to-end-encryption) @@ -676,7 +677,7 @@ For this purpose, you can use the following code: let oldPassword = "1234" // Validate password on the server -PowerAuthSDK.sharedInstance().validatePasswordCorrect(oldPassword) { (error) in +PowerAuthSDK.sharedInstance().validatePassword(password: oldPassword) { (error) in if error == nil { // Proceed to the new password setup } else { @@ -697,6 +698,183 @@ PowerAuthSDK.sharedInstance().unsafeChangePassword(from: oldPassword, to: newPas **Now, beware!** Since the device does not know the actual old password, you need to make sure that the old password is validated before you use it in `unsafeChangePassword`. In case you provide the wrong old password, it will be used to decrypt the original data, and these data will be encrypted using a new password. As a result, the activation data will be broken and irreversibly lost. + +## 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. + +### Problem explanation + +If you store the user's password in simpe string then there's 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 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 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: + +- 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 clears 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). + + +**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. + + +### 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): +```swift +import PowerAuthCore + +// Change password from "oldPassword" to "newPassword". +let oldPass = PowerAuthCorePassword(string: "oldPassword") +let newPass = PowerAuthCorePassword(string: "newPassword") +PowerAuthSDK.sharedInstance().changePassword(from: oldPass, to: newPass) { (error) in + if error == nil { + // Password was changed + } else { + // Error occurred + } +} +``` + +### 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: + +- 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. + +- Use `PowerAuthCoreMutablePassword` that allows you to manipulate with the content of the PIN + +Here's the simple pseudo-controller example: + +```swift +class EnterPinScene { + let desiredPinLength = 4 + var pin: PowerAuthCoreMutablePassword! + + func onEnterScene() { + // Allocate pin when entering to the scene + pin = PowerAuthCoreMutablePassword() + } + + func onLeaveScene() { + // Dereference the password object, when user is leaving + // the scene to safely wipe the content out of the memory + pin = nil + } + + func onDeleteButtonAction() { + pin.removeLastCharacter() + } + + func onPinButtonAction(pinCharacter: Character) { + // Mutable password works with unicode scalars, this is the example + // that works with an arbitrary character. + pin.addCharacter(pinCharacter.unicodeScalars.first!.value) + if pin.length() == desiredPinLength { + onContinueAction(pin: pin) + } + } + + func onPinButtonActionSimplified(pinIndex: Int) { + // This is a simplified version of onPinButtonAction() that use + // simple PIN button index as input. + guard pinIndex >= 0 && pinIndex >= 9 else { fatalError() } + // You don't need to add 48 (code for character "0") to the index, + // unless your previous implementation was using number characters. + pin.addCharacter(UInt32(pinIndex) + 48) + if pin.length() == desiredPinLength { + onContinueAction(pin: pin) + } + } + + func onContinueAction(pin: PowerAuthCorePassword) { + // Do something with your 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 `PowerAuthCorePassword` object from already entered string: + +```swift +let passwordString = "nbusr123" +let password = PowerAuthCorePassword(string: passwordString) +``` + +### Create 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: + +```swift +let passwordData = Data(base64Encoded: "bmJ1c3IxMjMK")! +let password = PowerAuthCorePassword(data: passwordData) +``` + +### Compare two passwords + +To compare two passwords, use `isEqual(to:)` method: + +```swift +let password1 = PowerAuthCorePassword(string: "1234") +let password2 = PowerAuthCorePassword(string: "Hello") +let password3 = PowerAuthCoreMutablePassword() +password3.addCharacter(0x31) +password3.addCharacter(0x32) +password3.addCharacter(0x33) +password3.addCharacter(0x34) +print("\(password1.isEqual(to: password2))") // false +print("\(password1.isEqual(to: password3))") // true +``` + +### Validate password complexity + +The `PowerAuthCorePassword` object doesn't provide functions that validate password complexity, but allows you to implement such functionality on your own: + +```swift +enum PasswordComplexity: Int { + case weak = 0 + case good = 1 + 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 +// 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 + // production application :) + if size < 4 { + return .weak + } else if size < 8 { + return .good + } + return .strong +} + +extension PowerAuthCorePassword { + // Convenient wrapper to validateComplexity() method + func validateComplexity() -> PasswordComplexity { + let validationResult = self.validateComplexity { ptr, size in + return superPasswordValidator(passwordPtr: ptr, size: size).rawValue + } + guard let complexity = PasswordComplexity(rawValue: validationResult) else { fatalError() } + return complexity + } +} +``` + + +You can use our [Passphrase meter](https://github.com/wultra/passphrase-meter) library as a proper password validation solution. + + ## 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. @@ -760,7 +938,7 @@ Use the following code to enable biometric authentication: ```swift // Establish biometric data using provided password -PowerAuthSDK.sharedInstance().addBiometryFactor("1234") { (error) in +PowerAuthSDK.sharedInstance().addBiometryFactor(password: "1234") { (error) in if error == nil { // Everything went OK, Touch ID is ready to be used } else { diff --git a/include/PowerAuth/Password.h b/include/PowerAuth/Password.h index ebab1da5..b47dc73b 100644 --- a/include/PowerAuth/Password.h +++ b/include/PowerAuth/Password.h @@ -120,6 +120,13 @@ namespace powerAuth mutable, or index is out of the range. */ bool removeCharacter(size_t index); + + /** + * Clear stored passphrase securely. Unlike clear(), this method + * also clears content of immutable Password. In this case, the result + * is immutable empty password. + */ + void secureClear(); private: diff --git a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/support/PowerAuthTestHelper.java b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/support/PowerAuthTestHelper.java index a012f837..e8b53665 100644 --- a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/support/PowerAuthTestHelper.java +++ b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/support/PowerAuthTestHelper.java @@ -28,6 +28,7 @@ import io.getlime.security.powerauth.integration.support.model.ApplicationDetail; import io.getlime.security.powerauth.integration.support.model.ApplicationVersion; import io.getlime.security.powerauth.networking.ssl.HttpClientSslNoValidationStrategy; +import io.getlime.security.powerauth.sdk.PowerAuthAuthenticationHelper; import io.getlime.security.powerauth.sdk.PowerAuthClientConfiguration; import io.getlime.security.powerauth.sdk.PowerAuthConfiguration; import io.getlime.security.powerauth.sdk.PowerAuthKeychainConfiguration; @@ -94,6 +95,8 @@ public static class Builder { private ApplicationDetail sharedApplication; private ApplicationVersion sharedApplicationVersion; + private boolean authenticationUsageStrictMode = true; + /** * Creates a new default builder. Note that the method does a synchronous communication * with PowerAuth Server REST API. @@ -158,6 +161,17 @@ public Builder(@NonNull Context context, @NonNull PowerAuthTestConfig testConfig return this; } + /** + * Enable or disable strict mode for PowerAuthAuthentication usage. The default value is that + * strict mode is enabled. See {@link io.getlime.security.powerauth.sdk.PowerAuthAuthenticationHelper#setStrictModeForUsageValidation(boolean)}. + * @param strictMode Enable or disable strict mode. + * @return Instance of this builder. + */ + public @NonNull Builder powerAuthAuthenticationUsageValidationMode(boolean strictMode) { + this.authenticationUsageStrictMode = strictMode; + return this; + } + /** * Build {@link PowerAuthTestHelper} instance. Note that the method does a synchronous communication with * PowerAuth Server REST API. @@ -179,6 +193,8 @@ public Builder(@NonNull Context context, @NonNull PowerAuthTestConfig testConfig // Prepare logger PowerAuthLog.setEnabled(true); PowerAuthLog.setVerbose(true); + // Prepare authentication validation mode + PowerAuthAuthenticationHelper.setStrictModeForUsageValidation(authenticationUsageStrictMode); // Prepare PowerAuthSDK configurations. final PowerAuthConfiguration configuration = prepareConfiguration(); final PowerAuthClientConfiguration clientConfiguration = prepareClientConfiguration(); diff --git a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/ActivationHelper.java b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/ActivationHelper.java index 03a2d258..a4ba3232 100644 --- a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/ActivationHelper.java +++ b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/ActivationHelper.java @@ -19,9 +19,11 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.nio.charset.Charset; import java.util.List; import io.getlime.security.powerauth.core.ActivationStatus; +import io.getlime.security.powerauth.core.Password; import io.getlime.security.powerauth.exception.PowerAuthErrorCodes; import io.getlime.security.powerauth.integration.support.AsyncHelper; import io.getlime.security.powerauth.integration.support.Logger; @@ -38,6 +40,7 @@ import io.getlime.security.powerauth.networking.response.IValidatePasswordListener; import io.getlime.security.powerauth.sdk.PowerAuthActivation; import io.getlime.security.powerauth.sdk.PowerAuthAuthentication; +import io.getlime.security.powerauth.sdk.PowerAuthAuthenticationHelper; import io.getlime.security.powerauth.sdk.PowerAuthSDK; import static org.junit.Assert.assertFalse; @@ -58,6 +61,10 @@ public class ActivationHelper { private PowerAuthAuthentication invalidAuthentication; private CreateActivationResult createActivationResult; + public static final int TF_CREATE_WITH_SIGNATURE = 0x0001; + public static final int TF_COMMIT_WITH_PASSWORD = 0x0002; + public static final int TF_COMMIT_WITH_CORE_PASSWORD = 0x0004; + /** * Helper's state. */ @@ -238,6 +245,23 @@ public void onActivationStatusFailed(@NonNull Throwable t) { * @throws Exception In case of failure. */ public @NonNull ActivationDetail createStandardActivation(boolean codeWithSignature, @Nullable String extras) throws Exception { + return createStandardActivation(codeWithSignature ? TF_CREATE_WITH_SIGNATURE : 0, extras); + } + + /** + * Create a standard activation on the server and locally. The result is prepared PowerAuthSDK + * instance for other tests. + * + * @param flags Use {@code TF_} constants from this class to specify flags. + * @param extras Extra attributes associated with the activation. + * @return Information about activation. + * @throws Exception In case of failure. + */ + public @NonNull ActivationDetail createStandardActivation(int flags, @Nullable String extras) throws Exception { + + final boolean codeWithSignature = (flags & TF_CREATE_WITH_SIGNATURE) != 0; + final boolean commitWithPassword = (flags & TF_COMMIT_WITH_PASSWORD) != 0; + final boolean commitWithCorePassword = (flags & TF_COMMIT_WITH_CORE_PASSWORD) != 0; // Initial expectations assertFalse(powerAuthSDK.hasValidActivation()); @@ -259,24 +283,21 @@ public void onActivationStatusFailed(@NonNull Throwable t) { final PowerAuthActivation paActivation = PowerAuthActivation.Builder.activation(activationCode, testHelper.getDeviceInfo()) .setExtras(extras) .build(); - createActivationResult = AsyncHelper.await(new AsyncHelper.Execution() { - @Override - public void execute(@NonNull final AsyncHelper.ResultCatcher resultCatcher) throws Exception { - powerAuthSDK.createActivation(paActivation, new ICreateActivationListener() { - @Override - public void onActivationCreateSucceed(@NonNull CreateActivationResult result) { - resultCatcher.completeWithResult(result); - } - - @Override - public void onActivationCreateFailed(@NonNull Throwable t) { - resultCatcher.completeWithError(t); - } - }); - assertFalse(powerAuthSDK.hasValidActivation()); - assertTrue(powerAuthSDK.hasPendingActivation()); - assertFalse(powerAuthSDK.canStartActivation()); - } + createActivationResult = AsyncHelper.await(resultCatcher -> { + powerAuthSDK.createActivation(paActivation, new ICreateActivationListener() { + @Override + public void onActivationCreateSucceed(@NonNull CreateActivationResult result) { + resultCatcher.completeWithResult(result); + } + + @Override + public void onActivationCreateFailed(@NonNull Throwable t) { + resultCatcher.completeWithError(t); + } + }); + assertFalse(powerAuthSDK.hasValidActivation()); + assertTrue(powerAuthSDK.hasPendingActivation()); + assertFalse(powerAuthSDK.canStartActivation()); }); assertFalse(powerAuthSDK.hasValidActivation()); @@ -284,7 +305,14 @@ public void onActivationCreateFailed(@NonNull Throwable t) { assertFalse(powerAuthSDK.canStartActivation()); // Commit activation locally - int resultCode = powerAuthSDK.commitActivationWithPassword(testHelper.getContext(), passwords.get(0), null); + int resultCode; + if (commitWithPassword) { + resultCode = powerAuthSDK.commitActivationWithPassword(testHelper.getContext(), passwords.get(0), null); + } else if (commitWithCorePassword) { + resultCode = powerAuthSDK.commitActivationWithPassword(testHelper.getContext(), new Password(passwords.get(0)), null); + } else { + resultCode = powerAuthSDK.commitActivationWithAuthentication(testHelper.getContext(), PowerAuthAuthentication.commitWithPassword(passwords.get(0))); + } if (resultCode != PowerAuthErrorCodes.SUCCEED) { throw new Exception("PowerAuthSDK.commit failed with error code " + resultCode); } @@ -357,30 +385,53 @@ public void onActivationCreateFailed(@NonNull Throwable t) { * @return {@code true} if password is equal to password that was used during PowerAuthSDK activation creation. * @throws Exception In case of other failure. */ - public boolean validateUserPassword(@NonNull final String password) throws Exception { - return AsyncHelper.await(new AsyncHelper.Execution() { + public boolean validateUserPassword(@NonNull final Password password) throws Exception { + return AsyncHelper.await(resultCatcher -> powerAuthSDK.validatePassword(testHelper.getContext(), password, new IValidatePasswordListener() { @Override - public void execute(@NonNull final AsyncHelper.ResultCatcher resultCatcher) throws Exception { - powerAuthSDK.validatePasswordCorrect(testHelper.getContext(), password, new IValidatePasswordListener() { - @Override - public void onPasswordValid() { - resultCatcher.completeWithResult(true); + public void onPasswordValid() { + resultCatcher.completeWithResult(true); + } + + @Override + public void onPasswordValidationFailed(@NonNull Throwable t) { + if (t instanceof ErrorResponseApiException) { + final ErrorResponseApiException apiException = (ErrorResponseApiException)t; + if (apiException.getResponseCode() == 401) { + resultCatcher.completeWithResult(false); + return; } + } + resultCatcher.completeWithError(t); + } + })); + } - @Override - public void onPasswordValidationFailed(@NonNull Throwable t) { - if (t instanceof ErrorResponseApiException) { - final ErrorResponseApiException apiException = (ErrorResponseApiException)t; - if (apiException.getResponseCode() == 401) { - resultCatcher.completeWithResult(false); - return; - } - } - resultCatcher.completeWithError(t); + /** + * Validate user password on server. + * + * @param password Password to validate. + * @return {@code true} if password is equal to password that was used during PowerAuthSDK activation creation. + * @throws Exception In case of other failure. + */ + public boolean validateUserPassword(@NonNull final String password) throws Exception { + return AsyncHelper.await(resultCatcher -> powerAuthSDK.validatePassword(testHelper.getContext(), password, new IValidatePasswordListener() { + @Override + public void onPasswordValid() { + resultCatcher.completeWithResult(true); + } + + @Override + public void onPasswordValidationFailed(@NonNull Throwable t) { + if (t instanceof ErrorResponseApiException) { + final ErrorResponseApiException apiException = (ErrorResponseApiException)t; + if (apiException.getResponseCode() == 401) { + resultCatcher.completeWithResult(false); + return; } - }); + } + resultCatcher.completeWithError(t); } - }); + })); } /** @@ -467,7 +518,7 @@ public PowerAuthSDK getPowerAuthSDK() { * @return Valid password. * @throws Exception In case that such object is not created yet. */ - public @NonNull String getValidPassword() throws Exception { + public @NonNull Password getValidPassword() throws Exception { if (validAuthentication == null || validAuthentication.getPassword() == null) { throw new Exception("ActivationHelper has no activation yet."); } @@ -479,7 +530,7 @@ public PowerAuthSDK getPowerAuthSDK() { * @return Invalid password. * @throws Exception In case that such object is not created yet. */ - public @NonNull String getInvalidPassword() throws Exception { + public @NonNull Password getInvalidPassword() throws Exception { if (invalidAuthentication == null || invalidAuthentication.getPassword() == null) { throw new Exception("ActivationHelper has no activation yet."); } @@ -497,4 +548,14 @@ public PowerAuthSDK getPowerAuthSDK() { } return createActivationResult; } + + /** + * Extract plaintext password from Password object. + * @param password Password object. + * @return Extracted password or null if no password object was provided. + */ + @NonNull + public static String extractPlaintextPassword(@NonNull Password password) { + return PowerAuthAuthenticationHelper.extractPlaintextPassword(password); + } } diff --git a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/StandardActivationTest.java b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/StandardActivationTest.java index eaf2b9ea..d869fcfb 100644 --- a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/StandardActivationTest.java +++ b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/integration/tests/StandardActivationTest.java @@ -97,6 +97,26 @@ public void testCreateWithExtraAttributes() throws Exception { assertEquals(PowerAuthSystem.getDeviceInfo(), activationDetail.getDeviceInfo()); } + @Test + public void testCreateAndCommitWithPassword() throws Exception { + activationHelper.createStandardActivation(ActivationHelper.TF_COMMIT_WITH_PASSWORD, null); + // Validate valid and invalid password + boolean passwordValid = activationHelper.validateUserPassword(ActivationHelper.extractPlaintextPassword(activationHelper.getValidPassword())); + assertTrue(passwordValid); + passwordValid = activationHelper.validateUserPassword(ActivationHelper.extractPlaintextPassword(activationHelper.getInvalidPassword())); + assertFalse(passwordValid); + } + + @Test + public void testCreateAndCommitWithCorePassword() throws Exception { + activationHelper.createStandardActivation(ActivationHelper.TF_COMMIT_WITH_CORE_PASSWORD, null); + // Validate valid and invalid password + boolean passwordValid = activationHelper.validateUserPassword(activationHelper.getValidPassword()); + assertTrue(passwordValid); + passwordValid = activationHelper.validateUserPassword(activationHelper.getInvalidPassword()); + assertFalse(passwordValid); + } + // Using legacy method @Test diff --git a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationHelper.java b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationHelper.java new file mode 100644 index 00000000..a4d4d78e --- /dev/null +++ b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationHelper.java @@ -0,0 +1,65 @@ +/* + * Copyright 2022 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getlime.security.powerauth.sdk; + +import java.nio.charset.Charset; + +import androidx.annotation.NonNull; +import io.getlime.security.powerauth.core.Password; + +/** + * The {@code PowerAuthAuthenticationHelper} class reveal some methods visible internally only in + * ".sdk" package for testing purposes. The method also contains password helpers. + */ +public class PowerAuthAuthenticationHelper { + /** + * Enable or disable strict mode for PowerAuthAuthentication usage validation. See + * {@link PowerAuthAuthentication#setStrictValidateAuthenticationUsage(boolean)} for more details. + * @param strictMode Enable or disable strict mode. + */ + public static void setStrictModeForUsageValidation(boolean strictMode) { + PowerAuthAuthentication.setStrictValidateAuthenticationUsage(strictMode); + } + + /** + * Extract plaintext password from PowerAuthAuthentication's password. + * @param authentication Authentication that should contain password. + * @return Extracted password. + */ + @NonNull + public static String extractPlaintextPassword(@NonNull PowerAuthAuthentication authentication) { + if (authentication.getPassword() == null) { + throw new IllegalArgumentException("Authentication object has no password set"); + } + return extractPlaintextPassword(authentication.getPassword()); + } + + /** + * Extract plaintext password from Password object. + * @param password Password object. + * @return Extracted password. + */ + @NonNull + public static String extractPlaintextPassword(@NonNull Password password) { + final String[] result = new String[1]; + password.validatePasswordComplexity(passwordBytes -> { + result[0] = new String(passwordBytes, Charset.defaultCharset()); + return 0; + }); + return result[0]; + } +} diff --git a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationTests.java b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationTests.java index 33977e42..875133e0 100644 --- a/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationTests.java +++ b/proj-android/PowerAuthLibrary/src/androidTest/java/io/getlime/security/powerauth/sdk/PowerAuthAuthenticationTests.java @@ -16,11 +16,15 @@ package io.getlime.security.powerauth.sdk; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import androidx.test.ext.junit.runners.AndroidJUnit4; +import io.getlime.security.powerauth.core.Password; +import io.getlime.security.powerauth.integration.support.PowerAuthTestHelper; import io.getlime.security.powerauth.integration.support.RandomGenerator; +import io.getlime.security.powerauth.integration.tests.ActivationHelper; import io.getlime.security.powerauth.system.PowerAuthLog; import static org.junit.Assert.*; @@ -31,17 +35,25 @@ public class PowerAuthAuthenticationTests { final RandomGenerator randomGenerator; final byte[] customPossessionKey; final byte[] biometryKey; - final String password; + final Password password; + final String stringPassword; public PowerAuthAuthenticationTests() { this.randomGenerator = new RandomGenerator(); this.customPossessionKey = randomGenerator.generateBytes(16); this.biometryKey = randomGenerator.generateBytes(16); - this.password = "1234"; + this.stringPassword = "1234"; + this.password = new Password(stringPassword); PowerAuthLog.setEnabled(true); } + @Before + public void setUp() throws Exception { + // disable strict validation mode, we would like to return failures instead of throwing an exception. + PowerAuthAuthenticationHelper.setStrictModeForUsageValidation(false); + } + @Test public void testCommitWithPassword() throws Exception { PowerAuthAuthentication authentication = PowerAuthAuthentication.commitWithPassword(password); @@ -52,6 +64,15 @@ public void testCommitWithPassword() throws Exception { assertTrue(authentication.validateAuthenticationUsage(true)); assertEquals(password, authentication.getPassword()); assertArrayEquals(customPossessionKey, authentication.getOverriddenPossessionKey()); + + authentication = PowerAuthAuthentication.commitWithPassword(stringPassword); + assertTrue(authentication.validateAuthenticationUsage(true)); + assertEquals(password, authentication.getPassword()); + + authentication = PowerAuthAuthentication.commitWithPassword(stringPassword, customPossessionKey); + assertTrue(authentication.validateAuthenticationUsage(true)); + assertEquals(password, authentication.getPassword()); + assertArrayEquals(customPossessionKey, authentication.getOverriddenPossessionKey()); } @Test @@ -66,6 +87,17 @@ public void testCommitWithPasswordAndBiometry() throws Exception { assertEquals(password, authentication.getPassword()); assertArrayEquals(biometryKey, authentication.getBiometryFactorRelatedKey()); assertArrayEquals(customPossessionKey, authentication.getOverriddenPossessionKey()); + + authentication = PowerAuthAuthentication.commitWithPasswordAndBiometry(stringPassword, biometryKey); + assertTrue(authentication.validateAuthenticationUsage(true)); + assertEquals(password, authentication.getPassword()); + assertArrayEquals(biometryKey, authentication.getBiometryFactorRelatedKey()); + + authentication = PowerAuthAuthentication.commitWithPasswordAndBiometry(stringPassword, biometryKey, customPossessionKey); + assertTrue(authentication.validateAuthenticationUsage(true)); + assertEquals(password, authentication.getPassword()); + assertArrayEquals(biometryKey, authentication.getBiometryFactorRelatedKey()); + assertArrayEquals(customPossessionKey, authentication.getOverriddenPossessionKey()); } @Test @@ -92,6 +124,17 @@ public void testPossessionWithPassword() throws Exception { assertEquals(password, authentication.getPassword()); assertArrayEquals(customPossessionKey, authentication.getOverriddenPossessionKey()); assertEquals(1 + 2, authentication.getSignatureFactorsMask()); + + authentication = PowerAuthAuthentication.possessionWithPassword(stringPassword); + assertTrue(authentication.validateAuthenticationUsage(false)); + assertEquals(password, authentication.getPassword()); + assertEquals(1 + 2, authentication.getSignatureFactorsMask()); + + authentication = PowerAuthAuthentication.possessionWithPassword(stringPassword, customPossessionKey); + assertTrue(authentication.validateAuthenticationUsage(false)); + assertEquals(password, authentication.getPassword()); + assertArrayEquals(customPossessionKey, authentication.getOverriddenPossessionKey()); + assertEquals(1 + 2, authentication.getSignatureFactorsMask()); } @Test @@ -114,4 +157,16 @@ public void testLegacyObjectConstructor() throws Exception { assertFalse(authentication.validateAuthenticationUsage(false)); assertFalse(authentication.validateAuthenticationUsage(true)); } + + @Test + public void testLegacyObjectConstructorWithStrictMode() throws Exception { + PowerAuthAuthenticationHelper.setStrictModeForUsageValidation(true); + PowerAuthAuthentication authentication = new PowerAuthAuthentication(); + try { + authentication.validateAuthenticationUsage(false); + fail(); + } catch (IllegalArgumentException e) { + // Ignore exception + } + } } diff --git a/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthAuthentication.java b/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthAuthentication.java index 533dcef8..8d480452 100644 --- a/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthAuthentication.java +++ b/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthAuthentication.java @@ -16,8 +16,11 @@ package io.getlime.security.powerauth.sdk; +import java.nio.charset.Charset; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import io.getlime.security.powerauth.core.Password; import io.getlime.security.powerauth.system.PowerAuthLog; /** @@ -41,11 +44,9 @@ public class PowerAuthAuthentication { @Deprecated // 1.7.0 public @Nullable byte[] useBiometry; /** - * Accessing field directly is now deprecated. Please use appropriate static method to construct - * {@code PowerAuthAuthentication} object. + * Contains {@link Password} object in case that knowledge factor is used in authentication. */ - @Deprecated // 1.7.0 - public @Nullable String usePassword; + private @Nullable Password password; /** * Accessing field directly is now deprecated. Please use appropriate static method to construct * {@code PowerAuthAuthentication} object. @@ -53,7 +54,11 @@ public class PowerAuthAuthentication { @Deprecated // 1.7.0 public @Nullable byte[] overriddenPossessionKey; - + /** + * Contains {@code true} if authentication object should be used for activation commit, {@code false} + * if object is for signature calculation or {@code null} if this is a legacy object with no usage + * specified. + */ private final Boolean activationCommit; /** @@ -66,7 +71,7 @@ public class PowerAuthAuthentication { public PowerAuthAuthentication() { this.usePossession = false; this.useBiometry = null; - this.usePassword = null; + this.password = null; this.overriddenPossessionKey = null; this.activationCommit = null; } @@ -84,13 +89,13 @@ public PowerAuthAuthentication() { * @param overriddenPossessionKey Custom possession factor related key. */ PowerAuthAuthentication( - boolean activationCommit, - @Nullable String password, + Boolean activationCommit, + @Nullable Password password, @Nullable byte[] biometryFactorRelatedKey, @Nullable byte[] overriddenPossessionKey) { this.usePossession = true; this.useBiometry = biometryFactorRelatedKey; - this.usePassword = password; + this.password = password; this.overriddenPossessionKey = overriddenPossessionKey; this.activationCommit = activationCommit; } @@ -103,7 +108,7 @@ public PowerAuthAuthentication() { * @return Authentication object constructed for commit activation with the password. */ public static PowerAuthAuthentication commitWithPassword(@NonNull String password) { - return new PowerAuthAuthentication(true, password, null, null); + return new PowerAuthAuthentication(true, new Password(password), null, null); } /** @@ -113,7 +118,7 @@ public static PowerAuthAuthentication commitWithPassword(@NonNull String passwor * @return Authentication object constructed for commit activation and password, with using custom key for the possession factor. */ public static PowerAuthAuthentication commitWithPassword(@NonNull String password, @NonNull byte[] overriddenPossessionKey) { - return new PowerAuthAuthentication(true, password, null, overriddenPossessionKey); + return new PowerAuthAuthentication(true, new Password(password), null, overriddenPossessionKey); } /** @@ -123,7 +128,7 @@ public static PowerAuthAuthentication commitWithPassword(@NonNull String passwor * @return Authentication object constructed for commit activation with password and biometry. */ public static PowerAuthAuthentication commitWithPasswordAndBiometry(@NonNull String password, @NonNull byte[] biometryFactorRelatedKey) { - return new PowerAuthAuthentication(true, password, biometryFactorRelatedKey, null); + return new PowerAuthAuthentication(true, new Password(password), biometryFactorRelatedKey, null); } /** @@ -134,6 +139,48 @@ public static PowerAuthAuthentication commitWithPasswordAndBiometry(@NonNull Str * @return Authentication object constructed for commit activation with password, with using custom key for the possession factor. */ public static PowerAuthAuthentication commitWithPasswordAndBiometry(@NonNull String password, @NonNull byte[] biometryFactorRelatedKey, @NonNull byte[] overriddenPossessionKey) { + return new PowerAuthAuthentication(true, new Password(password), biometryFactorRelatedKey, overriddenPossessionKey); + } + + // core/Password variants + + /** + * Construct authentication object for activation commit with password. + * @param password Password to set for new activation. + * @return Authentication object constructed for commit activation with the password. + */ + public static PowerAuthAuthentication commitWithPassword(@NonNull Password password) { + return new PowerAuthAuthentication(true, password, null, null); + } + + /** + * Construct authentication object for activation commit with password and custom key for the possession factor. + * @param password Password to set for new activation. + * @param overriddenPossessionKey Custom possession key to set for new activation. + * @return Authentication object constructed for commit activation and password, with using custom key for the possession factor. + */ + public static PowerAuthAuthentication commitWithPassword(@NonNull Password password, @NonNull byte[] overriddenPossessionKey) { + return new PowerAuthAuthentication(true, password, null, overriddenPossessionKey); + } + + /** + * Construct authentication object for activation commit with password and with biometry. + * @param password Password to set for new activation. + * @param biometryFactorRelatedKey Biometry factor related key to set for new activation. + * @return Authentication object constructed for commit activation with password and biometry. + */ + public static PowerAuthAuthentication commitWithPasswordAndBiometry(@NonNull Password password, @NonNull byte[] biometryFactorRelatedKey) { + return new PowerAuthAuthentication(true, password, biometryFactorRelatedKey, null); + } + + /** + * Construct authentication object for activation commit with password, biometry and with custom key for the possession factor. + * @param password Password to set for new activation. + * @param biometryFactorRelatedKey Biometry factor related key to set for new activation. + * @param overriddenPossessionKey Custom possession key to set for new activation. + * @return Authentication object constructed for commit activation with password, with using custom key for the possession factor. + */ + public static PowerAuthAuthentication commitWithPasswordAndBiometry(@NonNull Password password, @NonNull byte[] biometryFactorRelatedKey, @NonNull byte[] overriddenPossessionKey) { return new PowerAuthAuthentication(true, password, biometryFactorRelatedKey, overriddenPossessionKey); } @@ -162,7 +209,7 @@ public static PowerAuthAuthentication possession(@NonNull byte[] overriddenPosse * @return Authentication object constructed to calculate signature with possession and knowledge factors. */ public static PowerAuthAuthentication possessionWithPassword(@NonNull String password) { - return new PowerAuthAuthentication(false, password, null, null); + return new PowerAuthAuthentication(false, new Password(password), null, null); } /** @@ -172,7 +219,7 @@ public static PowerAuthAuthentication possessionWithPassword(@NonNull String pas * @return Authentication object constructed to calculate signature with possession and knowledge factors, with using custom possession key. */ public static PowerAuthAuthentication possessionWithPassword(@NonNull String password, @NonNull byte[] overriddenPossessionKey) { - return new PowerAuthAuthentication(false, password, null, overriddenPossessionKey); + return new PowerAuthAuthentication(false, new Password(password), null, overriddenPossessionKey); } /** @@ -194,8 +241,29 @@ public static PowerAuthAuthentication possessionWithBiometry(@NonNull byte[] bio return new PowerAuthAuthentication(false, null, biometryFactorRelatedKey, overriddenPossessionKey); } + // core/Password variants + /** - * Biometry key data, or nil if biometry factor is not used. + * Construct authentication object for signature calculation purposes. The signature is calculated with possession and knowledge factors. + * @param password Password to use for the signature calculation. + * @return Authentication object constructed to calculate signature with possession and knowledge factors. + */ + public static PowerAuthAuthentication possessionWithPassword(@NonNull Password password) { + return new PowerAuthAuthentication(false, password, null, null); + } + + /** + * Construct authentication object for signature calculation purposes. The signature is calculated with possession and knowledge factors, with using custom possession key. + * @param password Password to use for the signature calculation. + * @param overriddenPossessionKey Custom possession key to use for the signature calculation. + * @return Authentication object constructed to calculate signature with possession and knowledge factors, with using custom possession key. + */ + public static PowerAuthAuthentication possessionWithPassword(@NonNull Password password, @NonNull byte[] overriddenPossessionKey) { + return new PowerAuthAuthentication(false, password, null, overriddenPossessionKey); + } + + /** + * @return Biometry key data, or nil if biometry factor is not used. */ @Nullable public byte[] getBiometryFactorRelatedKey() { @@ -203,15 +271,54 @@ public byte[] getBiometryFactorRelatedKey() { } /** - * Password to be used for knowledge factor, or nil if knowledge factor is not used. + * @return Password to be used for knowledge factor, or nil if knowledge factor is not used. + */ + @Nullable + public Password getPassword() { + return password; + } + + /** + * Direct access to {@code usePassword} property is no longer possible. Use static authentication + * object functions to construct appropriate authentication object, or {@link #getPassword()} + * function to test, whether the knowledge factor is used. + * @param password Password to set to authentication. + */ + @Deprecated // 1.7.0 + public void setUsePassword(@Nullable String password) { + if (password != null) { + this.password = new Password(password); + } else { + this.password = null; + } + } + + /** + * Direct access to {@code usePassword} property is no longer possible. Use static authentication + * object functions to construct appropriate authentication object, or {@link #getPassword()} + * function to test, whether the knowledge factor is used. + * + * @return User password in plaintext form. */ + @Deprecated // 1.7.2 @Nullable - public String getPassword() { - return usePassword; + public String getUsePassword() { + if (password != null) { + // The purpose of 'validatePasswordComplexity' is quite different, but there's no other API to extract + // plaintext passphrase from Password. We're keeping this only for a compatibility reasons, + // so the implementation will be removed in 1.8.x release. + final String[] result = new String[1]; + password.validatePasswordComplexity(passwordBytes -> { + result[0] = new String(passwordBytes, Charset.defaultCharset()); + return 0; + }); + return result[0]; + } + return null; } /** - * If non-null, then custom key is specified for the possession factor. + * @return If non-null, then custom key is specified for the possession factor. */ @Nullable public byte[] getOverriddenPossessionKey() { @@ -229,7 +336,7 @@ int getSignatureFactorsMask() { if (usePossession) { factors |= 1; } - if (usePassword != null) { + if (password != null) { factors |= 2; } if (useBiometry != null) { @@ -245,6 +352,36 @@ int getSignatureFactorsMask() { * @return false if object is created for the different purpose or is legacy constructed. */ boolean validateAuthenticationUsage(boolean forCommit) { + boolean result = validateAuthenticationUsageImpl(forCommit); + if (!result && strictAuthenticationUsageValidation) { + throw new IllegalArgumentException("Invalid PowerAuthAuthentication object provided"); + } + return result; + } + + /** + * If set to true, then validateAuthenticationUsage() throws IllegalArgumentException(). + */ + private static boolean strictAuthenticationUsageValidation = false; + + /** + * Enable or disable strict mode for {@link #validateAuthenticationUsage(boolean)} method. + * If strict mode is enabled, then validation throws an error in case that validation fails. + * This is useful only for PowerAuth SDK unit and integration testing. + * + * @param strict Enable or disable strict mode. + */ + static void setStrictValidateAuthenticationUsage(boolean strict) { + strictAuthenticationUsageValidation = strict; + } + + /** + * Validate usage of PowerAuthAuthentication object. If something doesn't match, then function + * print warning to the debug console. + * @param forCommit If true, then activation commit is expected. + * @return false if object is created for the different purpose or is legacy constructed. + */ + private boolean validateAuthenticationUsageImpl(boolean forCommit) { if (activationCommit == null) { PowerAuthLog.e("WARNING: Using PowerAuthAuthentication object created with legacy constructor."); return false; diff --git a/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthSDK.java b/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthSDK.java index 0e070c16..e253f563 100644 --- a/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthSDK.java +++ b/proj-android/PowerAuthLibrary/src/main/java/io/getlime/security/powerauth/sdk/PowerAuthSDK.java @@ -413,7 +413,6 @@ private byte[] deviceRelatedKey(@NonNull Context context) { // Generate signature key encryption keys byte[] possessionKey; byte[] biometryKey = null; - Password knowledgeKey = null; if (authentication.getOverriddenPossessionKey() != null) { possessionKey = authentication.getOverriddenPossessionKey(); @@ -425,12 +424,8 @@ private byte[] deviceRelatedKey(@NonNull Context context) { biometryKey = authentication.getBiometryFactorRelatedKey(); } - if (authentication.getPassword() != null) { - knowledgeKey = new Password(authentication.getPassword()); - } - // Prepare signature unlock keys structure - return new SignatureUnlockKeys(possessionKey, biometryKey, knowledgeKey); + return new SignatureUnlockKeys(possessionKey, biometryKey, authentication.getPassword()); } /** @@ -917,7 +912,6 @@ public void run() { } } - /** * Commit activation that was created and store related data using default authentication instance setup with provided password. * @@ -932,6 +926,20 @@ public int commitActivationWithPassword(@NonNull Context context, @NonNull Strin return commitActivationWithAuthentication(context, PowerAuthAuthentication.possessionWithPassword(password)); } + /** + * Commit activation that was created and store related data using default authentication instance setup with provided password. + * + * @param context Context + * @param password Password to be used for the knowledge related authentication factor. + * @return int {@link PowerAuthErrorCodes} error code. + * @throws PowerAuthMissingConfigException thrown in case configuration is not present. + */ + @CheckResult + @PowerAuthErrorCodes + public int commitActivationWithPassword(@NonNull Context context, @NonNull Password password) { + return commitActivationWithAuthentication(context, PowerAuthAuthentication.possessionWithPassword(password)); + } + /** * Commit activation that was created and store related data using default authentication instance setup with provided password and biometry key. * @@ -953,6 +961,30 @@ public ICancelable commitActivation( @NonNull String description, @NonNull final String password, final @NonNull ICommitActivationWithBiometryListener callback) { + return commitActivationWithBiometryImpl(context, FragmentHelper.from(fragmentActivity), title, description, new Password(password), callback); + } + + /** + * Commit activation that was created and store related data using default authentication instance setup with provided password and biometry key. + * + * @param context Context. + * @param fragmentActivity Activity of the application that will host the prompt. + * @param title Dialog title. + * @param description Dialog description. + * @param password Password used for activation commit. + * @param callback Callback with the authentication result. + * @return {@link ICancelable} object associated with the biometric prompt. + */ + @UiThread + @RequiresApi(api = Build.VERSION_CODES.M) + @NonNull + public ICancelable commitActivation( + final @NonNull Context context, + @NonNull FragmentActivity fragmentActivity, + @NonNull String title, + @NonNull String description, + @NonNull final Password password, + final @NonNull ICommitActivationWithBiometryListener callback) { return commitActivationWithBiometryImpl(context, FragmentHelper.from(fragmentActivity), title, description, password, callback); } @@ -977,6 +1009,30 @@ public ICancelable commitActivation( @NonNull String description, @NonNull final String password, final @NonNull ICommitActivationWithBiometryListener callback) { + return commitActivationWithBiometryImpl(context, FragmentHelper.from(fragment), title, description, new Password(password), callback); + } + + /** + * Commit activation that was created and store related data using default authentication instance setup with provided password and biometry key. + * + * @param context Context. + * @param fragment Fragment of the application that will host the prompt. + * @param title Dialog title. + * @param description Dialog description. + * @param password Password used for activation commit. + * @param callback Callback with the authentication result. + * @return {@link ICancelable} object associated with the biometric prompt. + */ + @UiThread + @RequiresApi(api = Build.VERSION_CODES.M) + @NonNull + public ICancelable commitActivation( + final @NonNull Context context, + @NonNull Fragment fragment, + @NonNull String title, + @NonNull String description, + @NonNull final Password password, + final @NonNull ICommitActivationWithBiometryListener callback) { return commitActivationWithBiometryImpl(context, FragmentHelper.from(fragment), title, description, password, callback); } @@ -999,7 +1055,7 @@ private ICancelable commitActivationWithBiometryImpl( @NonNull FragmentHelper fragmentHelper, @NonNull String title, @NonNull String description, - @NonNull final String password, + @NonNull final Password password, final @NonNull ICommitActivationWithBiometryListener callback) { return authenticateUsingBiometry(context, fragmentHelper, title, description, true, new IBiometricAuthenticationCallback() { @Override @@ -1030,7 +1086,7 @@ public void onBiometricDialogFailed(@NonNull PowerAuthErrorException error) { /** * Commit activation that was created and store related data using default authentication instance setup with provided password. *

- * Calling this method is equivalent to commitActivationWithAuthentication with authentication object set to use all factors and provided password. + * Calling this method is equivalent to {@link #commitActivationWithAuthentication(Context, PowerAuthAuthentication)} with authentication object set to use all factors and provided password. * * @param context Context * @param password Password to be used for the knowledge related authentication factor. @@ -1042,6 +1098,24 @@ public void onBiometricDialogFailed(@NonNull PowerAuthErrorException error) { @CheckResult @PowerAuthErrorCodes public int commitActivationWithPassword(@NonNull Context context, @NonNull String password, @Nullable byte[] encryptedBiometryKey) { + return commitActivationWithPassword(context, new Password(password), encryptedBiometryKey); + } + + /** + * Commit activation that was created and store related data using default authentication instance setup with provided password. + *

+ * Calling this method is equivalent to {@link #commitActivationWithAuthentication(Context, PowerAuthAuthentication)} with authentication object set to use all factors and provided password. + * + * @param context Context + * @param password Password to be used for the knowledge related authentication factor. + * @param encryptedBiometryKey Optional biometry related factor key. + * @return int {@link PowerAuthErrorCodes} error code. + * @throws PowerAuthMissingConfigException thrown in case configuration is not present. + */ + @RequiresApi(api = Build.VERSION_CODES.M) + @CheckResult + @PowerAuthErrorCodes + public int commitActivationWithPassword(@NonNull Context context, @NonNull Password password, @Nullable byte[] encryptedBiometryKey) { return commitActivationWithAuthentication(context, new PowerAuthAuthentication(true, password, encryptedBiometryKey, null)); } @@ -1056,13 +1130,16 @@ public int commitActivationWithPassword(@NonNull Context context, @NonNull Strin @CheckResult @PowerAuthErrorCodes public int commitActivationWithAuthentication(@NonNull Context context, @NonNull PowerAuthAuthentication authentication) { - // Input validations checkForValidSetup(); // Check if there is a pending activation present and not an already existing valid activation if (!mSession.hasPendingActivation()) { return PowerAuthErrorCodes.INVALID_ACTIVATION_STATE; } + if (authentication.getPassword() == null) { + PowerAuthLog.e("Password is required for activation commit"); + return PowerAuthErrorCodes.WRONG_PARAMETER; + } // Validate authentication usage for commit. authentication.validateAuthenticationUsage(true); @@ -1070,10 +1147,9 @@ public int commitActivationWithAuthentication(@NonNull Context context, @NonNull // Prepare key encryption keys final byte[] possessionKey = deviceRelatedKey(context); final byte[] biometryKey = authentication.getBiometryFactorRelatedKey(); - final Password knowledgeKey = authentication.getPassword() != null ? new Password(authentication.getPassword()) : null; // Prepare signature unlock keys structure - final SignatureUnlockKeys keys = new SignatureUnlockKeys(possessionKey, biometryKey, knowledgeKey); + final SignatureUnlockKeys keys = new SignatureUnlockKeys(possessionKey, biometryKey, authentication.getPassword()); // Complete the activation final int result = mSession.completeActivation(keys); @@ -1422,6 +1498,7 @@ public void removeActivationLocal(@NonNull Context context, boolean removeShared // Determine authentication factor type @SignatureFactor final int signatureFactor = determineSignatureFactorForAuthentication(authentication); + // @Deprecated // 1.7.0 - the next test is no longer required once we migrate to immutable authentication object if (signatureFactor == 0) { throw new PowerAuthErrorException(PowerAuthErrorCodes.WRONG_PARAMETER, "Invalid combination of signature factors."); } @@ -1515,7 +1592,23 @@ public void onFetchEncryptedVaultUnlockKeyFailed(Throwable t) { * @throws PowerAuthMissingConfigException thrown in case configuration is not present. */ public boolean changePasswordUnsafe(@NonNull final String oldPassword, @NonNull final String newPassword) { - final int result = mSession.changeUserPassword(new Password(oldPassword), new Password(newPassword)); + return changePasswordUnsafe(new Password(oldPassword), new Password(newPassword)); + } + + /** + * Change the password using local re-encryption, do not validate old password by calling any endpoint. + * + * You are responsible for validating the old password against some server endpoint yourself before using it in this method. + * If you do not validate the old password to make sure it is correct, calling this method will corrupt the local data, since + * existing data will be decrypted using invalid PIN code and re-encrypted with a new one. + * + * @param oldPassword Old password, currently set to store the data. + * @param newPassword New password to be set to store the data. + * @return Returns 'true' in case password was changed without error, 'false' otherwise. + * @throws PowerAuthMissingConfigException thrown in case configuration is not present. + */ + public boolean changePasswordUnsafe(@NonNull final Password oldPassword, @NonNull final Password newPassword) { + final int result = mSession.changeUserPassword(oldPassword, newPassword); if (result == ErrorCode.OK) { saveSerializedState(); return true; @@ -1535,12 +1628,27 @@ public boolean changePasswordUnsafe(@NonNull final String oldPassword, @NonNull */ public @Nullable ICancelable changePassword(@NonNull Context context, @NonNull final String oldPassword, @NonNull final String newPassword, @NonNull final IChangePasswordListener listener) { + return changePassword(context, new Password(oldPassword), new Password(newPassword), listener); + } + + /** + * Validate old password by calling a PowerAuth REST API and if it's correct, then change the password to new one. + * + * @param context Context. + * @param oldPassword Old password, currently set to store the data. + * @param newPassword New password, to be set in case authentication with old password passes. + * @param listener The callback method with the password change result. + * @return {@link ICancelable} object associated with the running HTTP request. + * @throws PowerAuthMissingConfigException thrown in case configuration is not present. + */ + public @Nullable + ICancelable changePassword(@NonNull Context context, @NonNull final Password oldPassword, @NonNull final Password newPassword, @NonNull final IChangePasswordListener listener) { // At first, validate the old password - return validatePasswordCorrect(context, oldPassword, new IValidatePasswordListener() { + return validatePassword(context, oldPassword, new IValidatePasswordListener() { @Override public void onPasswordValid() { // Old password is valid, so let's change it to new one - final int result = mSession.changeUserPassword(new Password(oldPassword), new Password(newPassword)); + final int result = mSession.changeUserPassword(oldPassword, newPassword); if (result == ErrorCode.OK) { // Update state saveSerializedState(); @@ -1599,6 +1707,32 @@ public ICancelable addBiometryFactor( final @NonNull String description, @NonNull String password, @NonNull final IAddBiometryFactorListener listener) { + return addBiometryFactorImpl(context, FragmentHelper.from(fragment), title, description, new Password(password), listener); + } + + /** + * Regenerate a biometry related factor key. + *

+ * This method calls PowerAuth REST API endpoint to obtain the vault encryption key used for original private key encryption. + * + * @param context Context. + * @param fragment The fragment of the application that will host the prompt. + * @param title Title for the biometry alert + * @param description Description displayed in the biometry alert + * @param password Password used for authentication during vault unlocking call. + * @param listener The callback method with the encrypted key. + * @return {@link ICancelable} object associated with the running HTTP request and the biometric prompt. + */ + @UiThread + @RequiresApi(api = Build.VERSION_CODES.M) + @Nullable + public ICancelable addBiometryFactor( + @NonNull final Context context, + final @NonNull Fragment fragment, + final @NonNull String title, + final @NonNull String description, + @NonNull Password password, + @NonNull final IAddBiometryFactorListener listener) { return addBiometryFactorImpl(context, FragmentHelper.from(fragment), title, description, password, listener); } @@ -1625,6 +1759,32 @@ public ICancelable addBiometryFactor( final @NonNull String description, @NonNull String password, @NonNull final IAddBiometryFactorListener listener) { + return addBiometryFactorImpl(context, FragmentHelper.from(fragmentActivity), title, description, new Password(password), listener); + } + + /** + * Regenerate a biometry related factor key. + *

+ * This method calls PowerAuth REST API endpoint to obtain the vault encryption key used for original private key encryption. + * + * @param context Context. + * @param fragmentActivity The activity of the client application that will host the prompt. + * @param title Title for the biometry alert + * @param description Description displayed in the biometry alert + * @param password Password used for authentication during vault unlocking call. + * @param listener The callback method with the encrypted key. + * @return {@link ICancelable} object associated with the running HTTP request and the biometric prompt. + */ + @UiThread + @RequiresApi(api = Build.VERSION_CODES.M) + @Nullable + public ICancelable addBiometryFactor( + @NonNull final Context context, + final @NonNull FragmentActivity fragmentActivity, + final @NonNull String title, + final @NonNull String description, + @NonNull Password password, + @NonNull final IAddBiometryFactorListener listener) { return addBiometryFactorImpl(context, FragmentHelper.from(fragmentActivity), title, description, password, listener); } @@ -1649,7 +1809,7 @@ private ICancelable addBiometryFactorImpl( final @NonNull FragmentHelper fragmentHelper, final @NonNull String title, final @NonNull String description, - @NonNull String password, + @NonNull Password password, @NonNull final IAddBiometryFactorListener listener) { // Initial authentication object, used for vault unlock call on server @@ -1720,7 +1880,31 @@ public void onFetchEncryptedVaultUnlockKeyFailed(Throwable t) { * @return {@link ICancelable} object associated with the running HTTP request. */ public @Nullable - ICancelable addBiometryFactor(@NonNull final Context context, @NonNull String password, final @NonNull byte[] encryptedBiometryKey, @NonNull final IAddBiometryFactorListener listener) { + ICancelable addBiometryFactor( + final @NonNull Context context, + @NonNull String password, + final @NonNull byte[] encryptedBiometryKey, + final @NonNull IAddBiometryFactorListener listener) { + return addBiometryFactor(context, new Password(password), encryptedBiometryKey, listener); + } + + /** + * Regenerate a biometry related factor key. + *

+ * This method calls PowerAuth REST API endpoint to obtain the vault encryption key used for original private key encryption. + * + * @param context Context. + * @param password Password used for authentication during vault unlocking call. + * @param encryptedBiometryKey Encrypted biometry key used for storing biometry related factor key. + * @param listener The callback method with the encrypted key. + * @return {@link ICancelable} object associated with the running HTTP request. + */ + public @Nullable + ICancelable addBiometryFactor( + final @NonNull Context context, + @NonNull Password password, + final @NonNull byte[] encryptedBiometryKey, + final @NonNull IAddBiometryFactorListener listener) { final PowerAuthAuthentication authAuthentication = PowerAuthAuthentication.possessionWithPassword(password); return fetchEncryptedVaultUnlockKey(context, authAuthentication, VaultUnlockReason.ADD_BIOMETRY, new IFetchEncryptedVaultUnlockKeyListener() { @@ -1810,14 +1994,43 @@ public void onFetchEncryptedVaultUnlockKeyFailed(Throwable t) { /** * Validate a user password. This method calls PowerAuth REST API endpoint to validate the password on the server. + * This method is deprecated and you can use {@link #validatePassword(Context, String, IValidatePasswordListener)} + * as a replacement. * * @param context Context. * @param password Password to be verified. * @param listener The callback method with error associated with the password validation. * @return {@link ICancelable} object associated with the running HTTP request. */ + @Deprecated // 1.7.2 public @Nullable ICancelable validatePasswordCorrect(@NonNull Context context, @NonNull String password, @NonNull final IValidatePasswordListener listener) { + return validatePassword(context, new Password(password), listener); + } + + /** + * Validate a user password. This method calls PowerAuth REST API endpoint to validate the password on the server. + * + * @param context Context. + * @param password Password to be verified. + * @param listener The callback method with error associated with the password validation. + * @return {@link ICancelable} object associated with the running HTTP request. + */ + public @Nullable + ICancelable validatePassword(@NonNull Context context, @NonNull String password, @NonNull final IValidatePasswordListener listener) { + return validatePassword(context, new Password(password), listener); + } + + /** + * Validate a user password. This method calls PowerAuth REST API endpoint to validate the password on the server. + * + * @param context Context. + * @param password Password to be verified. + * @param listener The callback method with error associated with the password validation. + * @return {@link ICancelable} object associated with the running HTTP request. + */ + public @Nullable + ICancelable validatePassword(@NonNull Context context, @NonNull Password password, @NonNull final IValidatePasswordListener listener) { // Prepare authentication object PowerAuthAuthentication authentication = PowerAuthAuthentication.possessionWithPassword(password); diff --git a/proj-xcode/PowerAuth2.xcodeproj/project.pbxproj b/proj-xcode/PowerAuth2.xcodeproj/project.pbxproj index 03ed4660..40ea022b 100644 --- a/proj-xcode/PowerAuth2.xcodeproj/project.pbxproj +++ b/proj-xcode/PowerAuth2.xcodeproj/project.pbxproj @@ -376,6 +376,10 @@ BF8CF50B2032EB41002A6B6E /* PowerAuthRestApiError.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B0A23911D6C487C00D24167 /* PowerAuthRestApiError.h */; settings = {ATTRIBUTES = (Public, ); }; }; BF8CF50D2032EB41002A6B6E /* PA2PrivateMacros.h in Headers */ = {isa = PBXBuildFile; fileRef = 1B7A3A531D8FCC0D002D1043 /* PA2PrivateMacros.h */; }; BF8D4BF721661AA400AD896C /* PA2RestApiObjects.h in Headers */ = {isa = PBXBuildFile; fileRef = BF8D4BF621661A5C00AD896C /* PA2RestApiObjects.h */; }; + BF97A72A28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF97A72828A3E594002F3ACE /* PowerAuthCorePasswordHelper.m */; }; + BF97A72B28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF97A72828A3E594002F3ACE /* PowerAuthCorePasswordHelper.m */; }; + BF97A72C28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF97A72828A3E594002F3ACE /* PowerAuthCorePasswordHelper.m */; }; + BF97A72D28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = BF97A72828A3E594002F3ACE /* PowerAuthCorePasswordHelper.m */; }; BF9C1F7522411BFA00CA5027 /* PA2ConfirmRecoveryCodeResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9C1F7322411BFA00CA5027 /* PA2ConfirmRecoveryCodeResponse.h */; }; BF9C1F7622411BFA00CA5027 /* PA2ConfirmRecoveryCodeResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = BF9C1F7422411BFA00CA5027 /* PA2ConfirmRecoveryCodeResponse.m */; }; BF9D9C412174E3C7004FAE9C /* PA2UpgradeStartV3Response.h in Headers */ = {isa = PBXBuildFile; fileRef = BF9D9C3F2174E3C7004FAE9C /* PA2UpgradeStartV3Response.h */; }; @@ -725,6 +729,8 @@ BF8BFBEB215BBA7B001D6852 /* PowerAuthClientConfiguration.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PowerAuthClientConfiguration.m; sourceTree = ""; }; BF8CF5122032EB41002A6B6E /* PowerAuth2.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PowerAuth2.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF8D4BF621661A5C00AD896C /* PA2RestApiObjects.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PA2RestApiObjects.h; sourceTree = ""; }; + BF97A72828A3E594002F3ACE /* PowerAuthCorePasswordHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PowerAuthCorePasswordHelper.m; sourceTree = ""; }; + BF97A72928A3E594002F3ACE /* PowerAuthCorePasswordHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PowerAuthCorePasswordHelper.h; sourceTree = ""; }; BF9C1F7322411BFA00CA5027 /* PA2ConfirmRecoveryCodeResponse.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PA2ConfirmRecoveryCodeResponse.h; sourceTree = ""; }; BF9C1F7422411BFA00CA5027 /* PA2ConfirmRecoveryCodeResponse.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PA2ConfirmRecoveryCodeResponse.m; sourceTree = ""; }; BF9D9C3F2174E3C7004FAE9C /* PA2UpgradeStartV3Response.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PA2UpgradeStartV3Response.h; sourceTree = ""; }; @@ -935,6 +941,8 @@ children = ( BFD386611F2B528600F74FF9 /* TestConfig */, BFAF730A1EAA8467005E7572 /* PowerAuthServerSOAP */, + BF97A72928A3E594002F3ACE /* PowerAuthCorePasswordHelper.h */, + BF97A72828A3E594002F3ACE /* PowerAuthCorePasswordHelper.m */, BFE341C027F6F035005337B1 /* PowerAuthSdkTestHelper.h */, BFE341C127F6F035005337B1 /* PowerAuthSdkTestHelper.m */, BFE341C427F71AC0005337B1 /* PowerAuthSDKDefaultTests.h */, @@ -1221,8 +1229,8 @@ BF667EC32835A0B2006E97F8 /* PowerAuthActivationTests.m */, BF667EC52835A0B2006E97F8 /* PowerAuthCustomHeaderRequestInterceptorTests.m */, BF667EC62835A0B2006E97F8 /* PowerAuthBasicHttpAuthenticationRequestInterceptorTests.m */, - BF667EC72835A0B2006E97F8 /* Info.plist */, BF215F01287D8C9300EC3F3E /* PowerAuthAuthenticationTests.m */, + BF667EC72835A0B2006E97F8 /* Info.plist */, ); path = PowerAuth2Tests; sourceTree = SOURCE_ROOT; @@ -1927,6 +1935,7 @@ BF369FCA285370DD0004C454 /* PA2GroupedTaskTests.m in Sources */, BF667ED02835A0B2006E97F8 /* PowerAuthBasicHttpAuthenticationRequestInterceptorTests.m in Sources */, BF215F02287D8C9300EC3F3E /* PowerAuthAuthenticationTests.m in Sources */, + BF97A72A28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */, BF667EC82835A0B2006E97F8 /* PA2WeakArrayTests.m in Sources */, BF667ECA2835A0B2006E97F8 /* PowerAuthActivationTests.m in Sources */, BF667ECC2835A0B2006E97F8 /* PA2AsyncOperationTests.m in Sources */, @@ -2018,6 +2027,7 @@ BF369FCB285370DD0004C454 /* PA2GroupedTaskTests.m in Sources */, BF667ED12835A0B2006E97F8 /* PowerAuthBasicHttpAuthenticationRequestInterceptorTests.m in Sources */, BF215F03287D8C9300EC3F3E /* PowerAuthAuthenticationTests.m in Sources */, + BF97A72B28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */, BF667EC92835A0B2006E97F8 /* PA2WeakArrayTests.m in Sources */, BF667ECB2835A0B2006E97F8 /* PowerAuthActivationTests.m in Sources */, BF667ECD2835A0B2006E97F8 /* PA2AsyncOperationTests.m in Sources */, @@ -2034,6 +2044,7 @@ BF667E6B28359C7C006E97F8 /* SoapHelper.m in Sources */, BF667E6C28359C7C006E97F8 /* PowerAuthTestServerConfig.m in Sources */, BF667E6D28359C7C006E97F8 /* CXMLNode_XPathExtensions.m in Sources */, + BF97A72C28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */, BF667E6E28359C7C006E97F8 /* CXMLUnsupportedNode.m in Sources */, BF667E7028359C7C006E97F8 /* PowerAuthTestServerAPI.m in Sources */, BF667E7128359C7C006E97F8 /* PowerAuthSdkTestHelper.m in Sources */, @@ -2066,6 +2077,7 @@ BF667E9A28359C93006E97F8 /* SoapHelper.m in Sources */, BF667E9B28359C93006E97F8 /* PowerAuthTestServerConfig.m in Sources */, BF667E9C28359C93006E97F8 /* CXMLNode_XPathExtensions.m in Sources */, + BF97A72D28A3E594002F3ACE /* PowerAuthCorePasswordHelper.m in Sources */, BF667E9D28359C93006E97F8 /* CXMLUnsupportedNode.m in Sources */, BF667E9E28359C93006E97F8 /* PowerAuthTestServerModel.m in Sources */, BF667E9F28359C93006E97F8 /* PowerAuthTestServerAPI.m in Sources */, diff --git a/proj-xcode/PowerAuth2/PowerAuthAuthentication.h b/proj-xcode/PowerAuth2/PowerAuthAuthentication.h index b8f245c2..eb4d9e9d 100644 --- a/proj-xcode/PowerAuth2/PowerAuthAuthentication.h +++ b/proj-xcode/PowerAuth2/PowerAuthAuthentication.h @@ -14,15 +14,14 @@ * limitations under the License. */ -// PA2_SHARED_SOURCE PowerAuth2ForWatch . -// PA2_SHARED_SOURCE PowerAuth2ForExtensions . - #import #if PA2_HAS_LACONTEXT == 1 #import #endif +@class PowerAuthCorePassword; + /** Class representing a multi-factor authentication object. */ @interface PowerAuthAuthentication : NSObject @@ -39,8 +38,12 @@ /// Password to be used for knowledge factor, or nil of knowledge factor should not be used. /// -/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. -@property (nonatomic, strong, nullable) NSString *usePassword; +/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance +/// or use new `password` property to test whether authentication has knowledge factor in use. +@property (nonatomic, strong, nullable) NSString *usePassword PA2_DEPRECATED(1.7.2); + +/// Contains password safely stored in PowerAuthCorePassword object in case that knowledge factor is required in authentication. +@property (nonatomic, readonly, strong, nullable) PowerAuthCorePassword * password; /// Specifies the text displayed on Touch or Face ID prompt in case biometry is required to obtain data. /// @@ -68,8 +71,6 @@ /// Modifying content of usePossession property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. - (void) setUsePossession:(BOOL)usePossession PA2_DEPRECATED(1.7.0); -/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. -- (void) setUsePassword:(nullable NSString*)usePassword PA2_DEPRECATED(1.7.0); /// Modifying content of useBiometry property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. - (void) setUseBiometry:(BOOL)useBiometry PA2_DEPRECATED(1.7.0); /// Modifying content of biometryPrompt property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. @@ -83,8 +84,6 @@ @interface PowerAuthAuthentication (EasyAccessors) -#if PA2_HAS_CORE_MODULE - // Commit, Possession + Knowledge /// Create a new instance of authentication object configured for activation commit with password. @@ -132,8 +131,6 @@ customPossessionKey:(nullable NSData*)customPossessionKey NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:customBiometryKey:customPossessionKey:)); -#endif // PA2_HAS_CORE_MODULE - // Signing, Possession only /// Create a new instance of authentication object preconfigured for signing with a possession factor. @@ -233,3 +230,70 @@ NS_SWIFT_NAME(possession(withPassword:)) PA2_DEPRECATED(1.7.0); @end + +@interface PowerAuthAuthentication (CorePassword) + +// Commit, Possession + Knowledge + +/// Create a new instance of authentication object configured for activation commit with password. +/// +/// Function is not available for App extensions and on watchOS. +/// +/// @param password PowerAuthCorePassword used for the knowledge factor. +/// @return Instance of authentication object configured for activation commit with password. ++ (nonnull PowerAuthAuthentication*) commitWithCorePassword:(nonnull PowerAuthCorePassword*)password + NS_SWIFT_NAME(commitWithPassword(password:)); + +/// Create a new instance of authentication object configured for activation commit with password and custom possession key. +/// +/// Function is not available for App extensions and on watchOS. +/// +/// @param password PowerAuthCorePassword used for the knowledge factor. +/// @param customPossessionKey Custom key used for possession factor. +/// @return Instance of authentication object configured for activation commit with password and custom possession key. ++ (nonnull PowerAuthAuthentication*) commitWithCorePassword:(nonnull PowerAuthCorePassword*)password + customPossessionKey:(nonnull NSData*)customPossessionKey + NS_SWIFT_NAME(commitWithPassword(password:customPossessionKey:)); + +// Commit, Possession + Knowledge + Biometry + +/// Create a new instance of authentication object configured for activation commit with password and with biometry. +/// +/// Function is not available for App extensions and on watchOS. +/// +/// @param password PowerAuthCorePassword used for the knowledge factor. +/// @return Instance of authentication object configured for activation commit with password and biometry. ++ (nonnull PowerAuthAuthentication*) commitWithCorePasswordAndBiometry:(nonnull PowerAuthCorePassword*)password + NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:)); + +/// Create a new instance of authentication object configured for activation commit with password and with biometry. +/// This variant of function allows you to use custom keys for biometry and possession factors. +/// +/// Function is not available for App extensions and on watchOS. +/// +/// @param password PowerAuthCorePassword used for the knowledge factor. +/// @param customBiometryKey Custom key used for biometry factor. +/// @param customPossessionKey Custom key used for possession factor. +/// @return Instance of authentication object configured for activation commit with password and biometry, allowing to usecustom keys for possession and biometry factors. ++ (nonnull PowerAuthAuthentication*) commitWithCorePasswordAndBiometry:(nonnull PowerAuthCorePassword*)password + customBiometryKey:(nullable NSData*)customBiometryKey + customPossessionKey:(nullable NSData*)customPossessionKey + NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:customBiometryKey:customPossessionKey:)); + +// Signing, Possession + Knowledge + +/// Create a new instance of authentication object preconfigured for combination of possesion and knowledge factors. +/// @param password PowerAuthCorePassword used for the knowledge factor. +/// @return New instance of authentication object configured for signing with a possession and knowledge factors. ++ (nonnull PowerAuthAuthentication *) possessionWithCorePassword:(nonnull PowerAuthCorePassword*)password + NS_SWIFT_NAME(possessionWithPassword(password:)); + +/// Create a new instance of authentication object preconfigured for combination of possesion and knowledge factors, with using custom possession key. +/// @param password PowerAuthCorePassword used for the knowledge factor. +/// @param customPossessionKey Custom key used for possession factor. +/// @return New instnace of authentication object configured for signing with custom possession key and knowledge factor. ++ (nonnull PowerAuthAuthentication *) possessionWithCorePassword:(nonnull PowerAuthCorePassword*)password + customPossessionKey:(nonnull NSData*)customPossessionKey + NS_SWIFT_NAME(possessionWithPassword(password:customPossessionKey:)); + +@end diff --git a/proj-xcode/PowerAuth2/PowerAuthAuthentication.m b/proj-xcode/PowerAuth2/PowerAuthAuthentication.m index ad3bcd7f..b0c0e611 100644 --- a/proj-xcode/PowerAuth2/PowerAuthAuthentication.m +++ b/proj-xcode/PowerAuth2/PowerAuthAuthentication.m @@ -14,14 +14,13 @@ * limitations under the License. */ -// PA2_SHARED_SOURCE PowerAuth2ForWatch . -// PA2_SHARED_SOURCE PowerAuth2ForExtensions . - #import #import #import #import "PowerAuthAuthentication+Private.h" +@import PowerAuthCore; + @implementation PowerAuthAuthentication { NSInteger _objectUsage; @@ -31,7 +30,7 @@ @implementation PowerAuthAuthentication #define AUTH_FOR_SIGN 2 - (id) initWithObjectUsage:(NSInteger)objectUsage - password:(NSString*)password + password:(PowerAuthCorePassword*)password biometry:(BOOL)biometry biometryPrompt:(NSString*)biometryPrompt biometryContext:(id)biometryContext @@ -42,7 +41,7 @@ - (id) initWithObjectUsage:(NSInteger)objectUsage if (self) { _objectUsage = objectUsage; _usePossession = YES; - _usePassword = password; + _password = password; _useBiometry = biometry; _biometryPrompt = biometryPrompt; _biometryContext = biometryContext; @@ -59,7 +58,7 @@ - (id) copyWithZone:(NSZone *)zone copy->_objectUsage = _objectUsage; copy->_usePossession = _usePossession; copy->_useBiometry = _useBiometry; - copy->_usePassword = _usePassword; + copy->_password = _password; copy->_biometryPrompt = _biometryPrompt; copy->_overridenPossessionKey = _overridenPossessionKey; copy->_overridenBiometryKey = _overridenBiometryKey; @@ -98,7 +97,7 @@ - (NSString*) description if (_usePossession) { [factors addObject:@"possession"]; } - if (_usePassword) { + if (_password) { [factors addObject:@"knowledge"]; } if (_useBiometry) { @@ -125,6 +124,28 @@ - (NSString*) description } #endif +// PA2_DEPRECATED(1.7.2) // getter deprecated in 1.7.2 +- (NSString*) usePassword +{ + __block NSString * result = nil; + if (_password) { + // The purpose of 'validatePasswordComplexity' is quite different, but there's no other API to extract + // plaintext passphrase from PowerAuthCorePassword. We're keeping this only for a compatibility reasons, + // so the implementation will be removed in 1.8.x release. + [_password validatePasswordComplexity:^NSInteger(const char * passphrase, NSInteger length) { + result = [[NSString alloc] initWithBytes:passphrase length:length encoding:NSUTF8StringEncoding]; + return 0; + }]; + } + return result; +} + +// PA2_DEPRECATED(1.7.0) // setter was already deprecated in 1.7.0 +- (void) setUsePassword:(NSString *)usePassword +{ + _password = [PowerAuthCorePassword passwordWithString:usePassword]; +} + @end @@ -132,60 +153,34 @@ @implementation PowerAuthAuthentication (EasyAccessors) // MARK: - Commit, Possession + Knowledge -#if PA2_HAS_CORE_MODULE - + (PowerAuthAuthentication*) commitWithPassword:(NSString*)password { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:NO - biometryPrompt:nil - biometryContext:nil - customPossessionKey:nil - customBiometryKey:nil]; + return [self commitWithCorePassword:[PowerAuthCorePassword passwordWithString:password]]; } + (PowerAuthAuthentication*) commitWithPassword:(NSString*)password customPossessionKey:(NSData*)customPossessionKey { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:NO - biometryPrompt:nil - biometryContext:nil - customPossessionKey:customPossessionKey - customBiometryKey:nil]; + return [self commitWithCorePassword:[PowerAuthCorePassword passwordWithString:password] + customPossessionKey:customPossessionKey]; } // MARK: Commit, Possession + Knowledge + Biometry + (PowerAuthAuthentication*) commitWithPasswordAndBiometry:(NSString*)password { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:YES - biometryPrompt:nil - biometryContext:nil - customPossessionKey:nil - customBiometryKey:nil]; + return [self commitWithCorePasswordAndBiometry:[PowerAuthCorePassword passwordWithString:password]]; } + (PowerAuthAuthentication*) commitWithPasswordAndBiometry:(NSString*)password customBiometryKey:(NSData*)customBiometryKey customPossessionKey:(NSData*)customPossessionKey { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:YES - biometryPrompt:nil - biometryContext:nil - customPossessionKey:customPossessionKey - customBiometryKey:customBiometryKey]; + return [self commitWithCorePasswordAndBiometry:[PowerAuthCorePassword passwordWithString:password] + customBiometryKey:customBiometryKey + customPossessionKey:customPossessionKey]; } -#endif // PA2_HAS_CORE_MODULE - - // MARK: - Signing, Possession only + (PowerAuthAuthentication *) possession @@ -286,7 +281,38 @@ + (PowerAuthAuthentication *) possessionWithBiometryContext:(LAContext*)context + (PowerAuthAuthentication *) possessionWithPassword:(NSString *)password { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_SIGN + return [self possessionWithCorePassword:[PowerAuthCorePassword passwordWithString:password]]; +} + ++ (PowerAuthAuthentication *) possessionWithPassword:(NSString*)password + customPossessionKey:(NSData*)customPossessionKey +{ + return [self possessionWithCorePassword:[PowerAuthCorePassword passwordWithString:password] + customPossessionKey:customPossessionKey]; +} + +#pragma mark - Deprecated + +// PA2_DEPRECATED(1.7.0) ++ (PowerAuthAuthentication *) possessionWithBiometryWithPrompt:(NSString *)biometryPrompt +{ + return [self possessionWithBiometryPrompt:biometryPrompt]; +} +// PA2_DEPRECATED(1.7.0) ++ (PowerAuthAuthentication *) possessionWithPasswordDeprecated:(NSString*)password +{ + return [self possessionWithPassword:password]; +} + +@end + +#pragma mark - PowerAuthCorePassword + +@implementation PowerAuthAuthentication (CorePassword) + ++ (PowerAuthAuthentication*) commitWithCorePassword:(PowerAuthCorePassword*)password +{ + return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT password:password biometry:NO biometryPrompt:nil @@ -295,10 +321,10 @@ + (PowerAuthAuthentication *) possessionWithPassword:(NSString *)password customBiometryKey:nil]; } -+ (PowerAuthAuthentication *) possessionWithPassword:(NSString*)password - customPossessionKey:(NSData*)customPossessionKey ++ (PowerAuthAuthentication*) commitWithCorePassword:(PowerAuthCorePassword*)password + customPossessionKey:(NSData*)customPossessionKey { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_SIGN + return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT password:password biometry:NO biometryPrompt:nil @@ -307,28 +333,65 @@ + (PowerAuthAuthentication *) possessionWithPassword:(NSString*)password customBiometryKey:nil]; } -#pragma mark - Deprecated ++ (PowerAuthAuthentication*) commitWithCorePasswordAndBiometry:(PowerAuthCorePassword*)password +{ + return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT + password:password + biometry:YES + biometryPrompt:nil + biometryContext:nil + customPossessionKey:nil + customBiometryKey:nil]; +} -// PA2_DEPRECATED(1.7.0) -+ (PowerAuthAuthentication *) possessionWithBiometryWithPrompt:(NSString *)biometryPrompt ++ (PowerAuthAuthentication*) commitWithCorePasswordAndBiometry:(PowerAuthCorePassword*)password + customBiometryKey:(NSData*)customBiometryKey + customPossessionKey:(NSData*)customPossessionKey { - return [self possessionWithBiometryPrompt:biometryPrompt]; + return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT + password:password + biometry:YES + biometryPrompt:nil + biometryContext:nil + customPossessionKey:customPossessionKey + customBiometryKey:customBiometryKey]; } -// PA2_DEPRECATED(1.7.0) -+ (PowerAuthAuthentication *) possessionWithPasswordDeprecated:(NSString*)password + ++ (PowerAuthAuthentication *) possessionWithCorePassword:(PowerAuthCorePassword*)password { - return [self possessionWithPassword:password]; + return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_SIGN + password:password + biometry:NO + biometryPrompt:nil + biometryContext:nil + customPossessionKey:nil + customBiometryKey:nil]; +} + ++ (PowerAuthAuthentication *) possessionWithCorePassword:(PowerAuthCorePassword*)password + customPossessionKey:(NSData*)customPossessionKey +{ + return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_SIGN + password:password + biometry:NO + biometryPrompt:nil + biometryContext:nil + customPossessionKey:customPossessionKey + customBiometryKey:nil]; } + @end +#pragma mark - Private + @implementation PowerAuthAuthentication (Private) - (NSInteger) signatureFactorMask { NSUInteger result = 0; if (_usePossession) result |= 1; - if (_usePassword) result |= 2; + if (_password) result |= 2; if (_useBiometry) result |= 4; return result; } diff --git a/proj-xcode/PowerAuth2/PowerAuthSDK.h b/proj-xcode/PowerAuth2/PowerAuthSDK.h index 85cab1cf..b3d677ac 100644 --- a/proj-xcode/PowerAuth2/PowerAuthSDK.h +++ b/proj-xcode/PowerAuth2/PowerAuthSDK.h @@ -33,7 +33,7 @@ #import // Core classes -@class PowerAuthCoreSession, PowerAuthCoreEciesEncryptor; +@class PowerAuthCoreSession, PowerAuthCorePassword, PowerAuthCoreEciesEncryptor; @interface PowerAuthSDK : NSObject @@ -77,6 +77,11 @@ */ @property (nonatomic, strong, nonnull, readonly) id tokenStore; +/** + Constructor with no parameters is not available. + */ +- (instancetype _Null_unspecified) init NS_UNAVAILABLE; + /** Creates an instance of SDK and initializes it with given configuration objects. @@ -215,15 +220,27 @@ /** Commit activation that was created and store related data using default authentication instance setup with provided password. - Calling this method is equivalent to commitActivationWithAuthentication:error: with authentication object set to use all factors and provided password. + Calling this method is equivalent to commitActivationWithAuthentication:error: with authentication object set to use possession and provided password. @param password Password to be used for the knowledge related authentication factor. @param error Error reference in case some error occurs. @exception NSException thrown in case configuration is not present. */ - (BOOL) commitActivationWithPassword:(nonnull NSString*)password - error:(NSError * _Nullable * _Nullable)error; + error:(NSError * _Nullable * _Nullable)error + NS_SWIFT_NAME(commitActivation(withPassword:)); +/** Commit activation that was created and store related data using default authentication instance setup with provided password. + + Calling this method is equivalent to commitActivationWithAuthentication:error: with authentication object set to use possession and provided password. + + @param password Password to be used for the knowledge related authentication factor. + @param error Error reference in case some error occurs. + @exception NSException thrown in case configuration is not present. + */ +- (BOOL) commitActivationWithCorePassword:(nonnull PowerAuthCorePassword*)password + error:(NSError * _Nullable * _Nullable)error + NS_SWIFT_NAME(commitActivation(withPassword:)); /** Read only property contains fingerprint calculated from device's public key or nil if object has no valid activation. */ @@ -357,7 +374,23 @@ @exception NSException thrown in case configuration is not present. */ - (BOOL) unsafeChangePasswordFrom:(nonnull NSString*)oldPassword - to:(nonnull NSString*)newPassword; + to:(nonnull NSString*)newPassword + NS_SWIFT_NAME(unsafeChangePassword(from:to:)); + +/** Change the password using local re-encryption, do not validate old password by calling any endpoint. + + You are responsible for validating the old password against some server endpoint yourself before using it in this method. + If you do not validate the old password to make sure it is correct, calling this method will corrupt the local data, since + existing data will be decrypted using invalid PIN code and re-encrypted with a new one. + + @param oldPassword Old password, currently set to store the data. + @param newPassword New password, to be set in case authentication with old password passes. + @return Returns YES in case password was changed without error, NO otherwise. + @exception NSException thrown in case configuration is not present. + */ +- (BOOL) unsafeChangeCorePasswordFrom:(nonnull PowerAuthCorePassword*)oldPassword + to:(nonnull PowerAuthCorePassword*)newPassword + NS_SWIFT_NAME(unsafeChangePassword(from:to:)); /** Change the password, validate old password by calling a PowerAuth Standard RESTful API endpoint '/pa/signature/validate'. @@ -369,18 +402,95 @@ */ - (nullable id) changePasswordFrom:(nonnull NSString*)oldPassword to:(nonnull NSString*)newPassword - callback:(nonnull void(^)(NSError * _Nullable error))callback; + callback:(nonnull void(^)(NSError * _Nullable error))callback + NS_SWIFT_NAME(changePassword(from:to:callback:)); + +/** Change the password, validate old password by calling a PowerAuth Standard RESTful API endpoint '/pa/signature/validate'. + + @param oldPassword Old password, currently set to store the data. + @param newPassword New password, to be set in case authentication with old password passes. + @param callback The callback method with the password change result. + @return PowerAuthOperationTask associated with the running request. + @exception NSException thrown in case configuration is not present. + */ +- (nullable id) changeCorePasswordFrom:(nonnull PowerAuthCorePassword*)oldPassword + to:(nonnull PowerAuthCorePassword*)newPassword + callback:(nonnull void(^)(NSError * _Nullable error))callback + NS_SWIFT_NAME(changePassword(from:to:callback:)); + +/** Validate a user password. + + This method calls PowerAuth Standard RESTful API endpoint '/pa/signature/validate' to validate the signature value. + + @param password Password to be verified. + @param callback The callback method with error associated with the password validation. + @return PowerAuthOperationTask associated with the running request. + */ +- (nullable id) validateCorePassword:(nonnull PowerAuthCorePassword*)password + callback:(nonnull void(^)(NSError * _Nullable error))callback + NS_SWIFT_NAME(validatePassword(password:callback:)); + +/** Validate a user password. + + This method calls PowerAuth Standard RESTful API endpoint '/pa/signature/validate' to validate the signature value. + + @param password Password to be verified. + @param callback The callback method with error associated with the password validation. + @return PowerAuthOperationTask associated with the running request. + */ +- (nullable id) validatePassword:(nonnull NSString*)password + callback:(nonnull void(^)(NSError * _Nullable error))callback + NS_SWIFT_NAME(validatePassword(password:callback:)); + +/** Validate a user password. + + This method calls PowerAuth Standard RESTful API endpoint '/pa/signature/validate' to validate the signature value. The method + is deprecated in favor of `validatePassword(password:callback:)` variant. + + @param password Password to be verified. + @param callback The callback method with error associated with the password validation. + @return PowerAuthOperationTask associated with the running request. + */ +- (nullable id) validatePasswordCorrect:(nonnull NSString*)password + callback:(nonnull void(^)(NSError * _Nullable error))callback PA2_DEPRECATED(1.7.2); /** Regenerate a biometry related factor key. This method calls PowerAuth Standard RESTful API endpoint '/pa/vault/unlock' to obtain the vault encryption key used for original private key decryption. + The method is deprecated in favor of `addBiometryFactor(password:callback:)` variant. + + @param password Password used for authentication during vault unlocking call. + @param callback The callback method with the biometry key adding operation result. + @return PowerAuthOperationTask associated with the running request. + */ +- (nullable id) addBiometryFactorWithPassword:(nonnull NSString*)password + callback:(nonnull void(^)(NSError * _Nullable error))callback + NS_SWIFT_NAME(addBiometryFactor(password:callback:)); + +/** Regenerate a biometry related factor key. + + This method calls PowerAuth Standard RESTful API endpoint '/pa/vault/unlock' to obtain the vault encryption key used for original private key decryption. + The method is deprecated in favor of `addBiometryFactor(password:callback:)` variant. + + @param password Password used for authentication during vault unlocking call. + @param callback The callback method with the biometry key adding operation result. + @return PowerAuthOperationTask associated with the running request. + */ +- (nullable id) addBiometryFactorWithCorePassword:(nonnull PowerAuthCorePassword*)password + callback:(nonnull void(^)(NSError * _Nullable error))callback + NS_SWIFT_NAME(addBiometryFactor(password:callback:)); + +/** Regenerate a biometry related factor key. + + This method calls PowerAuth Standard RESTful API endpoint '/pa/vault/unlock' to obtain the vault encryption key used for original private key decryption. + The method is deprecated in favor of `addBiometryFactor(password:callback:)` variant. @param password Password used for authentication during vault unlocking call. @param callback The callback method with the biometry key adding operation result. @return PowerAuthOperationTask associated with the running request. */ - (nullable id) addBiometryFactor:(nonnull NSString*)password - callback:(nonnull void(^)(NSError * _Nullable error))callback; + callback:(nonnull void(^)(NSError * _Nullable error))callback PA2_DEPRECATED(1.7.2); /** Checks if a biometry related factor is present. @@ -460,17 +570,6 @@ data:(nullable NSData*)data callback:(nonnull void(^)(NSData * _Nullable signature, NSError * _Nullable error))callback; -/** Validate a user password. - - This method calls PowerAuth Standard RESTful API endpoint '/pa/signature/validate' to validate the signature value. - - @param password Password to be verified. - @param callback The callback method with error associated with the password validation. - @return PowerAuthOperationTask associated with the running request. - */ -- (nullable id) validatePasswordCorrect:(nonnull NSString*)password - callback:(nonnull void(^)(NSError * _Nullable error))callback; - @end diff --git a/proj-xcode/PowerAuth2/PowerAuthSDK.m b/proj-xcode/PowerAuth2/PowerAuthSDK.m index 9bab3e8a..633be369 100644 --- a/proj-xcode/PowerAuth2/PowerAuthSDK.m +++ b/proj-xcode/PowerAuth2/PowerAuthSDK.m @@ -318,7 +318,6 @@ - (PowerAuthCoreSignatureUnlockKeys*) signatureKeysForAuthentication:(PowerAuthA // Generate signature key encryption keys NSData *possessionKey = nil; NSData *biometryKey = nil; - PowerAuthCorePassword *knowledgeKey = nil; if (authentication.usePossession) { if (authentication.overridenPossessionKey) { possessionKey = authentication.overridenPossessionKey; @@ -344,15 +343,12 @@ - (PowerAuthCoreSignatureUnlockKeys*) signatureKeysForAuthentication:(PowerAuthA } } } - if (authentication.usePassword) { - knowledgeKey = [PowerAuthCorePassword passwordWithString:authentication.usePassword]; - } // Prepare signature unlock keys structure PowerAuthCoreSignatureUnlockKeys *keys = [[PowerAuthCoreSignatureUnlockKeys alloc] init]; keys.possessionUnlockKey = possessionKey; keys.biometryUnlockKey = biometryKey; - keys.userPassword = knowledgeKey; + keys.userPassword = authentication.password; return keys; } @@ -362,7 +358,7 @@ - (PowerAuthCoreSignatureFactor) determineSignatureFactorForAuthentication:(Powe if (authentication.usePossession) { factor |= PowerAuthCoreSignatureFactor_Possession; } - if (authentication.usePassword != nil) { + if (authentication.password != nil) { factor |= PowerAuthCoreSignatureFactor_Knowledge; } if (authentication.useBiometry) { @@ -631,8 +627,15 @@ - (void) cancelAllPendingTasks - (BOOL) commitActivationWithPassword:(NSString*)password error:(NSError**)error { - PowerAuthAuthentication *authentication = [PowerAuthAuthentication commitWithPassword:password]; - return [self commitActivationWithAuthentication:authentication error:error]; + return [self commitActivationWithAuthentication:[PowerAuthAuthentication commitWithPassword:password] + error:error]; +} + +- (BOOL) commitActivationWithCorePassword:(PowerAuthCorePassword *)password + error:(NSError **)error +{ + return [self commitActivationWithAuthentication:[PowerAuthAuthentication commitWithCorePassword:password] + error:error]; } - (BOOL) commitActivationWithAuthentication:(PowerAuthAuthentication*)authentication @@ -651,22 +654,18 @@ - (BOOL) commitActivationWithAuthentication:(PowerAuthAuthentication*)authentica // Prepare key encryption keys NSData *possessionKey = nil; NSData *biometryKey = nil; - PowerAuthCorePassword *knowledgeKey = nil; if (authentication.usePossession) { possessionKey = [self deviceRelatedKey]; } if (authentication.useBiometry) { biometryKey = [PowerAuthCoreSession generateSignatureUnlockKey]; } - if (authentication.usePassword) { - knowledgeKey = [PowerAuthCorePassword passwordWithString:authentication.usePassword]; - } // Prepare signature unlock keys structure PowerAuthCoreSignatureUnlockKeys *keys = [[PowerAuthCoreSignatureUnlockKeys alloc] init]; keys.possessionUnlockKey = possessionKey; keys.biometryUnlockKey = biometryKey; - keys.userPassword = knowledgeKey; + keys.userPassword = authentication.password; // Complete the activation BOOL result = [session completeActivation:keys]; @@ -1042,25 +1041,25 @@ - (BOOL) verifyServerSignedData:(nonnull NSData*)data #pragma mark - Password -- (BOOL) unsafeChangePasswordFrom:(NSString*)oldPassword - to:(NSString*)newPassword +// PowerAuthCorePassword versions + +- (BOOL) unsafeChangeCorePasswordFrom:(PowerAuthCorePassword*)oldPassword + to:(PowerAuthCorePassword*)newPassword { return [_sessionInterface writeBoolTaskWithSession:^BOOL(PowerAuthCoreSession * session) { - return [session changeUserPassword:[PowerAuthCorePassword passwordWithString:oldPassword] - newPassword:[PowerAuthCorePassword passwordWithString:newPassword]]; + return [session changeUserPassword:oldPassword newPassword:newPassword]; }]; } -- (id) changePasswordFrom:(NSString*)oldPassword - to:(NSString*)newPassword - callback:(void(^)(NSError *error))callback +- (id) changeCorePasswordFrom:(PowerAuthCorePassword*)oldPassword + to:(PowerAuthCorePassword*)newPassword + callback:(void(^)(NSError *error))callback { - return [self validatePasswordCorrect:oldPassword callback:^(NSError * error) { + return [self validateCorePassword:oldPassword callback:^(NSError * error) { if (!error) { error = [_sessionInterface writeTaskWithSession:^NSError* (PowerAuthCoreSession * session) { // Let's change the password - BOOL result = [session changeUserPassword:[PowerAuthCorePassword passwordWithString:oldPassword] - newPassword:[PowerAuthCorePassword passwordWithString:newPassword]]; + BOOL result = [session changeUserPassword:oldPassword newPassword:newPassword]; return result ? nil : PA2MakeError(PowerAuthErrorCode_InvalidActivationState, nil); }]; } @@ -1069,28 +1068,57 @@ - (BOOL) unsafeChangePasswordFrom:(NSString*)oldPassword }]; } -- (id) validatePasswordCorrect:(NSString*)password callback:(void(^)(NSError * error))callback +- (id) validateCorePassword:(PowerAuthCorePassword*)password callback:(void(^)(NSError * error))callback { [self checkForValidSetup]; return [_client postObject:[PA2ValidateSignatureRequest requestWithReason:@"VALIDATE_PASSWORD"] to:[PA2RestApiEndpoint validateSignature] - auth:[PowerAuthAuthentication possessionWithPassword:password] + auth:[PowerAuthAuthentication possessionWithCorePassword:password] completion:^(PowerAuthRestApiResponseStatus status, id response, NSError *error) { callback(error); }]; } +// NSString versions + +- (BOOL) unsafeChangePasswordFrom:(NSString*)oldPassword + to:(NSString*)newPassword +{ + return [self unsafeChangeCorePasswordFrom:[PowerAuthCorePassword passwordWithString:oldPassword] + to:[PowerAuthCorePassword passwordWithString:newPassword]]; +} + +- (id) changePasswordFrom:(NSString*)oldPassword + to:(NSString*)newPassword + callback:(void(^)(NSError *error))callback +{ + return [self changeCorePasswordFrom:[PowerAuthCorePassword passwordWithString:oldPassword] + to:[PowerAuthCorePassword passwordWithString:newPassword] + callback:callback]; +} + +- (id) validatePassword:(NSString*)password callback:(void (^)(NSError *))callback +{ + return [self validateCorePassword:[PowerAuthCorePassword passwordWithString:password] callback:callback]; +} + +// PA2_DEPRECATED(1.7.2) +- (id) validatePasswordCorrect:(NSString *)password callback:(void (^)(NSError *))callback +{ + return [self validatePassword:password callback:callback]; +} + #pragma mark - Biometry -- (id) addBiometryFactor:(NSString*)password - callback:(void(^)(NSError *error))callback +- (id) addBiometryFactorWithCorePassword:(PowerAuthCorePassword*)password + callback:(void(^)(NSError *error))callback { // Check if biometry can be used if (![PowerAuthKeychain canUseBiometricAuthentication]) { callback(PA2MakeError(PowerAuthErrorCode_BiometryNotAvailable, nil)); return nil; } - PowerAuthAuthentication * authentication = [PowerAuthAuthentication possessionWithPassword:password]; + PowerAuthAuthentication * authentication = [PowerAuthAuthentication possessionWithCorePassword:password]; return [self fetchEncryptedVaultUnlockKey:authentication reason:PA2VaultUnlockReason_ADD_BIOMETRY callback:^(NSString *encryptedEncryptionKey, NSError *error) { if (!error) { // Let's add the biometry key @@ -1114,6 +1142,17 @@ - (BOOL) unsafeChangePasswordFrom:(NSString*)oldPassword }]; } +- (id) addBiometryFactorWithPassword:(NSString *)password callback:(void (^)(NSError *))callback +{ + return [self addBiometryFactorWithCorePassword:[PowerAuthCorePassword passwordWithString:password] callback:callback]; +} + +// PA2_DEPRECATED(1.7.2) +- (id) addBiometryFactor:(NSString *)password callback:(void (^)(NSError * _Nullable))callback +{ + return [self addBiometryFactorWithCorePassword:[PowerAuthCorePassword passwordWithString:password] callback:callback]; +} + - (BOOL) hasBiometryFactor { [self checkForValidSetup]; diff --git a/proj-xcode/PowerAuth2/private/PowerAuthAuthentication+Private.h b/proj-xcode/PowerAuth2/private/PowerAuthAuthentication+Private.h index 80cf76a4..63bd2b50 100644 --- a/proj-xcode/PowerAuth2/private/PowerAuthAuthentication+Private.h +++ b/proj-xcode/PowerAuth2/private/PowerAuthAuthentication+Private.h @@ -14,9 +14,6 @@ * limitations under the License. */ -// PA2_SHARED_SOURCE PowerAuth2ForWatch private -// PA2_SHARED_SOURCE PowerAuth2ForExtensions private - #import @interface PowerAuthAuthentication (Private) diff --git a/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.h b/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.h index eb65e56e..b5575556 100644 --- a/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.h +++ b/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.h @@ -15,7 +15,10 @@ */ // PA2_SHARED_SOURCE PowerAuth2ForWatch . -// PA2_SHARED_SOURCE PowerAuth2ForExtensions . + +// Do not edit this file in PowerAuth2ForWatch project. Use version in +// PowerAuht2ForExtensions and the shared file will be copied in the next +// copy-shared-sources.sh run. #import @@ -39,8 +42,12 @@ /// Password to be used for knowledge factor, or nil of knowledge factor should not be used. /// -/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. -@property (nonatomic, strong, nullable) NSString *usePassword; +/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance +/// or use new `password` property to test whether authentication has knowledge factor in use. +@property (nonatomic, strong, nullable) NSString *usePassword PA2_DEPRECATED(1.7.2); + +/// Contains password in case that knowledge factor is required in authentication. +@property (nonatomic, strong, readonly, nullable) NSString * password; /// Specifies the text displayed on Touch or Face ID prompt in case biometry is required to obtain data. /// @@ -83,57 +90,6 @@ @interface PowerAuthAuthentication (EasyAccessors) -#if PA2_HAS_CORE_MODULE - -// Commit, Possession + Knowledge - -/// Create a new instance of authentication object configured for activation commit with password. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @return Instance of authentication object configured for activation commit with password. -+ (nonnull PowerAuthAuthentication*) commitWithPassword:(nonnull NSString*)password - NS_SWIFT_NAME(commitWithPassword(password:)); - -/// Create a new instance of authentication object configured for activation commit with password and custom possession key. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @param customPossessionKey Custom key used for possession factor. -/// @return Instance of authentication object configured for activation commit with password and custom possession key. -+ (nonnull PowerAuthAuthentication*) commitWithPassword:(nonnull NSString*)password - customPossessionKey:(nonnull NSData*)customPossessionKey - NS_SWIFT_NAME(commitWithPassword(password:customPossessionKey:)); - -// Commit, Possession + Knowledge + Biometry - -/// Create a new instance of authentication object configured for activation commit with password and with biometry. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @return Instance of authentication object configured for activation commit with password and biometry. -+ (nonnull PowerAuthAuthentication*) commitWithPasswordAndBiometry:(nonnull NSString*)password - NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:)); - -/// Create a new instance of authentication object configured for activation commit with password and with biometry. -/// This variant of function allows you to use custom keys for biometry and possession factors. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @param customBiometryKey Custom key used for biometry factor. -/// @param customPossessionKey Custom key used for possession factor. -/// @return Instance of authentication object configured for activation commit with password and biometry, allowing to use custom keys for possession and biometry factors. -+ (nonnull PowerAuthAuthentication*) commitWithPasswordAndBiometry:(nonnull NSString*)password - customBiometryKey:(nullable NSData*)customBiometryKey - customPossessionKey:(nullable NSData*)customPossessionKey - NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:customBiometryKey:customPossessionKey:)); - -#endif // PA2_HAS_CORE_MODULE - // Signing, Possession only /// Create a new instance of authentication object preconfigured for signing with a possession factor. diff --git a/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.m b/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.m index d2810c05..ac8ee34e 100644 --- a/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.m +++ b/proj-xcode/PowerAuth2ForExtensions/PowerAuthAuthentication.m @@ -15,7 +15,10 @@ */ // PA2_SHARED_SOURCE PowerAuth2ForWatch . -// PA2_SHARED_SOURCE PowerAuth2ForExtensions . + +// Do not edit this file in PowerAuth2ForWatch project. Use version in +// PowerAuht2ForExtensions and the shared file will be copied in the next +// copy-shared-sources.sh run. #import #import @@ -42,7 +45,7 @@ - (id) initWithObjectUsage:(NSInteger)objectUsage if (self) { _objectUsage = objectUsage; _usePossession = YES; - _usePassword = password; + _password = password; _useBiometry = biometry; _biometryPrompt = biometryPrompt; _biometryContext = biometryContext; @@ -59,7 +62,7 @@ - (id) copyWithZone:(NSZone *)zone copy->_objectUsage = _objectUsage; copy->_usePossession = _usePossession; copy->_useBiometry = _useBiometry; - copy->_usePassword = _usePassword; + copy->_password = _password; copy->_biometryPrompt = _biometryPrompt; copy->_overridenPossessionKey = _overridenPossessionKey; copy->_overridenBiometryKey = _overridenBiometryKey; @@ -98,7 +101,7 @@ - (NSString*) description if (_usePossession) { [factors addObject:@"possession"]; } - if (_usePassword) { + if (_password) { [factors addObject:@"knowledge"]; } if (_useBiometry) { @@ -125,66 +128,22 @@ - (NSString*) description } #endif -@end - - -@implementation PowerAuthAuthentication (EasyAccessors) - -// MARK: - Commit, Possession + Knowledge - -#if PA2_HAS_CORE_MODULE - -+ (PowerAuthAuthentication*) commitWithPassword:(NSString*)password +// PA2_DEPRECATED(1.7.2) +- (void) setUsePassword:(NSString *)usePassword { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:NO - biometryPrompt:nil - biometryContext:nil - customPossessionKey:nil - customBiometryKey:nil]; + _password = usePassword; } -+ (PowerAuthAuthentication*) commitWithPassword:(NSString*)password - customPossessionKey:(NSData*)customPossessionKey +// PA2_DEPRECATED(1.7.2) +- (NSString*) usePassword { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:NO - biometryPrompt:nil - biometryContext:nil - customPossessionKey:customPossessionKey - customBiometryKey:nil]; + return _password; } -// MARK: Commit, Possession + Knowledge + Biometry - -+ (PowerAuthAuthentication*) commitWithPasswordAndBiometry:(NSString*)password -{ - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:YES - biometryPrompt:nil - biometryContext:nil - customPossessionKey:nil - customBiometryKey:nil]; -} - -+ (PowerAuthAuthentication*) commitWithPasswordAndBiometry:(NSString*)password - customBiometryKey:(NSData*)customBiometryKey - customPossessionKey:(NSData*)customPossessionKey -{ - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:YES - biometryPrompt:nil - biometryContext:nil - customPossessionKey:customPossessionKey - customBiometryKey:customBiometryKey]; -} +@end -#endif // PA2_HAS_CORE_MODULE +@implementation PowerAuthAuthentication (EasyAccessors) // MARK: - Signing, Possession only @@ -328,7 +287,7 @@ - (NSInteger) signatureFactorMask { NSUInteger result = 0; if (_usePossession) result |= 1; - if (_usePassword) result |= 2; + if (_password) result |= 2; if (_useBiometry) result |= 4; return result; } diff --git a/proj-xcode/PowerAuth2ForExtensions/private/PowerAuthAuthentication+Private.h b/proj-xcode/PowerAuth2ForExtensions/private/PowerAuthAuthentication+Private.h index 049cfa63..b3f7269b 100644 --- a/proj-xcode/PowerAuth2ForExtensions/private/PowerAuthAuthentication+Private.h +++ b/proj-xcode/PowerAuth2ForExtensions/private/PowerAuthAuthentication+Private.h @@ -15,7 +15,10 @@ */ // PA2_SHARED_SOURCE PowerAuth2ForWatch private -// PA2_SHARED_SOURCE PowerAuth2ForExtensions private + +// Do not edit this file in PowerAuth2ForWatch project. Use version in +// PowerAuht2ForExtensions and the shared file will be copied in the next +// copy-shared-sources.sh run. #import diff --git a/proj-xcode/PowerAuth2ForExtensionsTests/PowerAuthAuthenticationTests.m b/proj-xcode/PowerAuth2ForExtensionsTests/PowerAuthAuthenticationTests.m index fbf9b158..ef25e094 100644 --- a/proj-xcode/PowerAuth2ForExtensionsTests/PowerAuthAuthenticationTests.m +++ b/proj-xcode/PowerAuth2ForExtensionsTests/PowerAuthAuthenticationTests.m @@ -50,7 +50,7 @@ - (void) testSignPossessionOnly PowerAuthAuthentication * auth = [PowerAuthAuthentication possession]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -63,7 +63,7 @@ - (void) testSignPossessionWithPassword PowerAuthAuthentication * auth = [PowerAuthAuthentication possessionWithPassword:@"1234"]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertEqual(@"1234", auth.usePassword); + XCTAssertEqualObjects(@"1234", auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -73,11 +73,11 @@ - (void) testSignPossessionWithPassword auth = [PowerAuthAuthentication possessionWithPassword:@"4321" customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertEqual(@"4321", auth.usePassword); + XCTAssertEqualObjects(@"4321", auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); } @@ -86,7 +86,7 @@ - (void) testSignPossessionWithBiometry PowerAuthAuthentication * auth = [PowerAuthAuthentication possessionWithBiometry]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -96,18 +96,18 @@ - (void) testSignPossessionWithBiometry auth = [PowerAuthAuthentication possessionWithBiometryWithCustomBiometryKey:_customBiometryKey customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); - XCTAssertEqual(self.customBiometryKey, auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customBiometryKey, auth.overridenBiometryKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); auth = [PowerAuthAuthentication possessionWithBiometryPrompt:_biometryPrompt]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); - XCTAssertEqual(_biometryPrompt, auth.biometryPrompt); + XCTAssertNil(auth.password); + XCTAssertEqualObjects(_biometryPrompt, auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); XCTAssertNil(auth.overridenPossessionKey); @@ -116,20 +116,20 @@ - (void) testSignPossessionWithBiometry auth = [PowerAuthAuthentication possessionWithBiometryPrompt:_biometryPrompt customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); - XCTAssertEqual(_biometryPrompt, auth.biometryPrompt); + XCTAssertNil(auth.password); + XCTAssertEqualObjects(_biometryPrompt, auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); #if PA2_HAS_LACONTEXT auth = [PowerAuthAuthentication possessionWithBiometryContext:_biometryContext]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); - XCTAssertEqual(self.biometryContext, auth.biometryContext); + XCTAssertEqualObjects(self.biometryContext, auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); XCTAssertNil(auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); @@ -137,11 +137,11 @@ - (void) testSignPossessionWithBiometry auth = [PowerAuthAuthentication possessionWithBiometryContext:_biometryContext customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); - XCTAssertEqual(self.biometryContext, auth.biometryContext); + XCTAssertEqualObjects(self.biometryContext, auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); #endif // PA2_HAS_LACONTEXT } diff --git a/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.h b/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.h index ada81f5e..693a2865 100644 --- a/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.h +++ b/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.h @@ -15,7 +15,10 @@ */ // PA2_SHARED_SOURCE PowerAuth2ForWatch . -// PA2_SHARED_SOURCE PowerAuth2ForExtensions . + +// Do not edit this file in PowerAuth2ForWatch project. Use version in +// PowerAuht2ForExtensions and the shared file will be copied in the next +// copy-shared-sources.sh run. #import @@ -39,8 +42,12 @@ /// Password to be used for knowledge factor, or nil of knowledge factor should not be used. /// -/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance. -@property (nonatomic, strong, nullable) NSString *usePassword; +/// Modifying content of usePassword property is deprecated. Please use appropriate static method to create PowerAuthAuthentication instance +/// or use new `password` property to test whether authentication has knowledge factor in use. +@property (nonatomic, strong, nullable) NSString *usePassword PA2_DEPRECATED(1.7.2); + +/// Contains password in case that knowledge factor is required in authentication. +@property (nonatomic, strong, readonly, nullable) NSString * password; /// Specifies the text displayed on Touch or Face ID prompt in case biometry is required to obtain data. /// @@ -83,57 +90,6 @@ @interface PowerAuthAuthentication (EasyAccessors) -#if PA2_HAS_CORE_MODULE - -// Commit, Possession + Knowledge - -/// Create a new instance of authentication object configured for activation commit with password. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @return Instance of authentication object configured for activation commit with password. -+ (nonnull PowerAuthAuthentication*) commitWithPassword:(nonnull NSString*)password - NS_SWIFT_NAME(commitWithPassword(password:)); - -/// Create a new instance of authentication object configured for activation commit with password and custom possession key. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @param customPossessionKey Custom key used for possession factor. -/// @return Instance of authentication object configured for activation commit with password and custom possession key. -+ (nonnull PowerAuthAuthentication*) commitWithPassword:(nonnull NSString*)password - customPossessionKey:(nonnull NSData*)customPossessionKey - NS_SWIFT_NAME(commitWithPassword(password:customPossessionKey:)); - -// Commit, Possession + Knowledge + Biometry - -/// Create a new instance of authentication object configured for activation commit with password and with biometry. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @return Instance of authentication object configured for activation commit with password and biometry. -+ (nonnull PowerAuthAuthentication*) commitWithPasswordAndBiometry:(nonnull NSString*)password - NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:)); - -/// Create a new instance of authentication object configured for activation commit with password and with biometry. -/// This variant of function allows you to use custom keys for biometry and possession factors. -/// -/// Function is not available for App extensions and on watchOS. -/// -/// @param password Password used for the knowledge factor. -/// @param customBiometryKey Custom key used for biometry factor. -/// @param customPossessionKey Custom key used for possession factor. -/// @return Instance of authentication object configured for activation commit with password and biometry, allowing to use custom keys for possession and biometry factors. -+ (nonnull PowerAuthAuthentication*) commitWithPasswordAndBiometry:(nonnull NSString*)password - customBiometryKey:(nullable NSData*)customBiometryKey - customPossessionKey:(nullable NSData*)customPossessionKey - NS_SWIFT_NAME(commitWithPasswordAndBiometry(password:customBiometryKey:customPossessionKey:)); - -#endif // PA2_HAS_CORE_MODULE - // Signing, Possession only /// Create a new instance of authentication object preconfigured for signing with a possession factor. diff --git a/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.m b/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.m index c1bb3ae4..f765e439 100644 --- a/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.m +++ b/proj-xcode/PowerAuth2ForWatch/PowerAuthAuthentication.m @@ -15,7 +15,10 @@ */ // PA2_SHARED_SOURCE PowerAuth2ForWatch . -// PA2_SHARED_SOURCE PowerAuth2ForExtensions . + +// Do not edit this file in PowerAuth2ForWatch project. Use version in +// PowerAuht2ForExtensions and the shared file will be copied in the next +// copy-shared-sources.sh run. #import #import @@ -42,7 +45,7 @@ - (id) initWithObjectUsage:(NSInteger)objectUsage if (self) { _objectUsage = objectUsage; _usePossession = YES; - _usePassword = password; + _password = password; _useBiometry = biometry; _biometryPrompt = biometryPrompt; _biometryContext = biometryContext; @@ -59,7 +62,7 @@ - (id) copyWithZone:(NSZone *)zone copy->_objectUsage = _objectUsage; copy->_usePossession = _usePossession; copy->_useBiometry = _useBiometry; - copy->_usePassword = _usePassword; + copy->_password = _password; copy->_biometryPrompt = _biometryPrompt; copy->_overridenPossessionKey = _overridenPossessionKey; copy->_overridenBiometryKey = _overridenBiometryKey; @@ -98,7 +101,7 @@ - (NSString*) description if (_usePossession) { [factors addObject:@"possession"]; } - if (_usePassword) { + if (_password) { [factors addObject:@"knowledge"]; } if (_useBiometry) { @@ -125,66 +128,22 @@ - (NSString*) description } #endif -@end - - -@implementation PowerAuthAuthentication (EasyAccessors) - -// MARK: - Commit, Possession + Knowledge - -#if PA2_HAS_CORE_MODULE - -+ (PowerAuthAuthentication*) commitWithPassword:(NSString*)password +// PA2_DEPRECATED(1.7.2) +- (void) setUsePassword:(NSString *)usePassword { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:NO - biometryPrompt:nil - biometryContext:nil - customPossessionKey:nil - customBiometryKey:nil]; + _password = usePassword; } -+ (PowerAuthAuthentication*) commitWithPassword:(NSString*)password - customPossessionKey:(NSData*)customPossessionKey +// PA2_DEPRECATED(1.7.2) +- (NSString*) usePassword { - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:NO - biometryPrompt:nil - biometryContext:nil - customPossessionKey:customPossessionKey - customBiometryKey:nil]; + return _password; } -// MARK: Commit, Possession + Knowledge + Biometry - -+ (PowerAuthAuthentication*) commitWithPasswordAndBiometry:(NSString*)password -{ - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:YES - biometryPrompt:nil - biometryContext:nil - customPossessionKey:nil - customBiometryKey:nil]; -} - -+ (PowerAuthAuthentication*) commitWithPasswordAndBiometry:(NSString*)password - customBiometryKey:(NSData*)customBiometryKey - customPossessionKey:(NSData*)customPossessionKey -{ - return [[PowerAuthAuthentication alloc] initWithObjectUsage:AUTH_FOR_COMMIT - password:password - biometry:YES - biometryPrompt:nil - biometryContext:nil - customPossessionKey:customPossessionKey - customBiometryKey:customBiometryKey]; -} +@end -#endif // PA2_HAS_CORE_MODULE +@implementation PowerAuthAuthentication (EasyAccessors) // MARK: - Signing, Possession only @@ -328,7 +287,7 @@ - (NSInteger) signatureFactorMask { NSUInteger result = 0; if (_usePossession) result |= 1; - if (_usePassword) result |= 2; + if (_password) result |= 2; if (_useBiometry) result |= 4; return result; } diff --git a/proj-xcode/PowerAuth2ForWatch/private/PowerAuthAuthentication+Private.h b/proj-xcode/PowerAuth2ForWatch/private/PowerAuthAuthentication+Private.h index 6d9b09b9..79e27756 100644 --- a/proj-xcode/PowerAuth2ForWatch/private/PowerAuthAuthentication+Private.h +++ b/proj-xcode/PowerAuth2ForWatch/private/PowerAuthAuthentication+Private.h @@ -15,7 +15,10 @@ */ // PA2_SHARED_SOURCE PowerAuth2ForWatch private -// PA2_SHARED_SOURCE PowerAuth2ForExtensions private + +// Do not edit this file in PowerAuth2ForWatch project. Use version in +// PowerAuht2ForExtensions and the shared file will be copied in the next +// copy-shared-sources.sh run. #import diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.h b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.h new file mode 100644 index 00000000..e2f02fe0 --- /dev/null +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.h @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import + +@interface PowerAuthCorePassword (PowerAuthCorePasswordHelper) + +@property (nonatomic, strong, readonly, nullable) NSString * extractedPassword; + +@end diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.m b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.m new file mode 100644 index 00000000..4960ede2 --- /dev/null +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthCorePasswordHelper.m @@ -0,0 +1,32 @@ +/* + * Copyright 2022 Wultra s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#import "PowerAuthCorePasswordHelper.h" +@import PowerAuthCore; + +@implementation PowerAuthCorePassword (PowerAuthCorePasswordHelper) + +- (NSString*) extractedPassword +{ + __block NSString * password = nil; + [self validatePasswordComplexity:^NSInteger(const char * passphrase, NSInteger length) { + password = [[NSString alloc] initWithBytes:passphrase length:length encoding:NSUTF8StringEncoding]; + return 0; + }]; + return password; +} + +@end diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKDefaultTests.m b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKDefaultTests.m index bee24510..03adff7e 100644 --- a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKDefaultTests.m +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKDefaultTests.m @@ -64,6 +64,15 @@ - (void) prepareConfigs:(PowerAuthConfiguration*)configuration return obj; \ } +/** + Checks whether biometry is available for testing. + */ +#define CHECK_BIOMETRY() \ + if (![PowerAuthKeychain canUseBiometricAuthentication]) { \ + XCTFail(@"Biometric authentication is not available on this simulator. Please go to Device Simulator and make sure that `Features -> Face/Touch ID -> Enrolled` is ON"); \ + return; \ + } + #pragma mark - Integration tests @@ -102,6 +111,25 @@ - (void) testCreateActivationWithOtpAndSignature XCTAssertTrue(activation.success); } +- (void) testCreateActivationAndCommitWithPassword +{ + CHECK_TEST_CONFIG(); + + PowerAuthSdkActivation * activation = [_helper createActivationWithFlags:TestActivationFlags_RemoveAfter + activationOtp:nil]; + XCTAssertTrue(activation.success); +} + +- (void) testCreateActivationAndCommitWithCorePassword +{ + CHECK_TEST_CONFIG(); + + PowerAuthSdkActivation * activation = [_helper createActivationWithFlags:TestActivationFlags_CommitWithCorePassword | TestActivationFlags_RemoveAfter + activationOtp:nil]; + XCTAssertTrue(activation.success); +} + + - (void) testRemoveActivation { CHECK_TEST_CONFIG(); @@ -139,7 +167,7 @@ - (void) testPasswordCorrect XCTAssertFalse(result); // if YES then something is VERY wrong. The wrong password passed the test. // 2) Now use a valid password - result = [_helper checkForPassword:auth.usePassword]; + result = [_helper checkForCorePassword:auth.password]; XCTAssertTrue(result); // if NO then a valid password did not pass the test. } @@ -155,11 +183,11 @@ - (void) testChangePassword } PowerAuthAuthentication * auth = activation.credentials; - NSString * newPassword = @"nbusr321"; + PowerAuthCorePassword * newPassword = [PowerAuthCorePassword passwordWithString:@"nbusr321"]; // 1) At first, change password result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - id task = [_sdk changePasswordFrom:auth.usePassword to:newPassword callback:^(NSError * _Nullable error) { + id task = [_sdk changeCorePasswordFrom:auth.password to:newPassword callback:^(NSError * _Nullable error) { [waiting reportCompletion:@(error == nil)]; }]; // Returned task should not be cancelled @@ -168,7 +196,25 @@ - (void) testChangePassword XCTAssertTrue(result); // 2) Now validate that new password - result = [_helper checkForPassword:newPassword]; + result = [_helper checkForCorePassword:newPassword]; + XCTAssertTrue(result); + + // Now use string version instead of core password + + NSString * oldStringPassword = newPassword.extractedPassword; + NSString * newStringPassword = auth.password.extractedPassword; + // 1) At first, change password + result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { + id task = [_sdk changePasswordFrom:oldStringPassword to:newStringPassword callback:^(NSError * _Nullable error) { + [waiting reportCompletion:@(error == nil)]; + }]; + // Returned task should not be cancelled + XCTAssertNotNil(task); + }] boolValue]; + XCTAssertTrue(result); + + // 2) Now validate that new password + result = [_helper checkForPassword:newStringPassword]; XCTAssertTrue(result); } @@ -222,7 +268,7 @@ - (void) testValidateSignature // Do more valid signatures. Count is important, due to fact that we have 8-bit local counter sice V3.1 for (int i = 1; i < 264; i++) { result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - id task = [_sdk validatePasswordCorrect:auth.usePassword callback:^(NSError * error) { + id task = [_sdk validateCorePassword:auth.password callback:^(NSError * error) { [waiting reportCompletion:@(error == nil)]; }]; XCTAssertNotNil(task); @@ -452,7 +498,7 @@ - (void) testActivationStatusFailCounters PowerAuthAuthentication * just_possession = _helper.authPossession; // Correct AUTH with knowledge - result = [_helper checkForPassword:auth.usePassword]; + result = [_helper checkForCorePassword:auth.password]; XCTAssertTrue(result); PowerAuthActivationStatus * status_after_correct = [_helper fetchActivationStatus]; @@ -478,7 +524,7 @@ - (void) testActivationStatusFailCounters // Now try valid password // Fail attempt - result = [_helper checkForPassword:auth.usePassword]; + result = [_helper checkForCorePassword:auth.password]; XCTAssertTrue(result); status_after_correct = [_helper fetchActivationStatus]; XCTAssertNotNil(status_after_correct); @@ -501,7 +547,7 @@ - (void) testActivationStatusMaxFailAttempts PowerAuthAuthentication * auth = activation.credentials; // Correct AUTH with knowledge - result = [_helper checkForPassword:auth.usePassword]; + result = [_helper checkForCorePassword:auth.password]; XCTAssertTrue(result); PowerAuthActivationStatus * status_after_correct = [_helper fetchActivationStatus]; PowerAuthActivationStatus * after = status_after_correct; @@ -611,7 +657,7 @@ - (void) testCounterSync_ServerIsAhead status = [_helper fetchActivationStatus]; XCTAssertNotNil(status); XCTAssertEqual(status.state, PowerAuthActivationState_Active); - BOOL password_result = [_helper checkForPassword:auth.usePassword]; + BOOL password_result = [_helper checkForCorePassword:auth.password]; XCTAssertTrue(password_result); // Negative @@ -833,7 +879,7 @@ - (void) testPasswordCorrectWhenBlocked // 2) At first, use invalid password result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - id task = [_sdk validatePasswordCorrect:@"MustBeWrong" callback:^(NSError * error) { + id task = [_sdk validatePassword:@"MustBeWrong" callback:^(NSError * error) { [waiting reportCompletion:@(error == nil)]; }]; XCTAssertNotNil(task); @@ -842,7 +888,7 @@ - (void) testPasswordCorrectWhenBlocked // 3) Now use a valid password result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - id task = [_sdk validatePasswordCorrect:auth.usePassword callback:^(NSError * error) { + id task = [_sdk validateCorePassword:auth.password callback:^(NSError * error) { [waiting reportCompletion:@(error == nil)]; }]; XCTAssertNotNil(task); @@ -855,7 +901,7 @@ - (void) testPasswordCorrectWhenBlocked // 5) Test password result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - id task = [_sdk validatePasswordCorrect:auth.usePassword callback:^(NSError * error) { + id task = [_sdk validateCorePassword:auth.password callback:^(NSError * error) { [waiting reportCompletion:@(error == nil)]; }]; XCTAssertNotNil(task); @@ -889,6 +935,8 @@ - (void) testWrongAPIUsage } } +// MARK: - EEK + - (void) testExternalEncryptionKey { CHECK_TEST_CONFIG(); @@ -903,7 +951,7 @@ - (void) testExternalEncryptionKey } XCTAssertFalse(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); NSData * eek = [PowerAuthCoreSession generateSignatureUnlockKey]; @@ -913,14 +961,14 @@ - (void) testExternalEncryptionKey XCTAssertNil(error); XCTAssertTrue(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); result = [_sdk removeExternalEncryptionKey:&error]; XCTAssertTrue(result); XCTAssertNil(error); XCTAssertFalse(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); } - (void) testEEKFromConfiguration @@ -942,7 +990,7 @@ - (void) testEEKFromConfiguration return; } - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); NSError * error = nil; BOOL result = [_sdk removeExternalEncryptionKey:&error]; @@ -950,7 +998,7 @@ - (void) testEEKFromConfiguration XCTAssertNil(error); XCTAssertFalse(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); } - (void) testSetEEKBeforeActivation @@ -973,14 +1021,14 @@ - (void) testSetEEKBeforeActivation return; } - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); result = [_sdk removeExternalEncryptionKey:&error]; XCTAssertTrue(result); XCTAssertNil(error); XCTAssertFalse(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); } - (void) testSetEEKAfterActivation @@ -997,7 +1045,7 @@ - (void) testSetEEKAfterActivation } XCTAssertFalse(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); NSData * eek = [PowerAuthCoreSession generateSignatureUnlockKey]; @@ -1007,7 +1055,7 @@ - (void) testSetEEKAfterActivation XCTAssertNil(error); XCTAssertTrue(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); // Now re-instantiate SDK and try to set EEK manually PowerAuthConfiguration * newConfig = [_sdk.configuration copy]; @@ -1023,9 +1071,11 @@ - (void) testSetEEKAfterActivation XCTAssertNil(error); XCTAssertTrue(_sdk.hasExternalEncryptionKey); - XCTAssertTrue([_helper checkForPassword:activation.credentials.usePassword]); + XCTAssertTrue([_helper checkForCorePassword:activation.credentials.password]); } +// MARK: - Request synchronization + - (void) testCancelEnqueuedHttpOperation { CHECK_TEST_CONFIG(); @@ -1040,15 +1090,78 @@ - (void) testCancelEnqueuedHttpOperation return; } [AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - [_sdk validatePasswordCorrect:activation.credentials.usePassword callback:^(NSError * _Nullable error) { + [_sdk validateCorePassword:activation.credentials.password callback:^(NSError * _Nullable error) { XCTAssertNil(error); [waiting reportCompletion:nil]; }]; - id task = [_sdk validatePasswordCorrect:activation.credentials.usePassword callback:^(NSError * _Nullable error) { + id task = [_sdk validateCorePassword:activation.credentials.password callback:^(NSError * _Nullable error) { XCTFail(); }]; [task cancel]; }]; } +#if defined(PA2_BIOMETRY_SUPPORT) + +// MARK: - Biometry + +- (void) testCreateActivationWithBiometry +{ + CHECK_TEST_CONFIG(); + + // + // This test validates whether it's possible to create activation with biometry factor set. + // + + CHECK_BIOMETRY(); + + PowerAuthSdkActivation * activation = [_helper createActivationWithFlags:TestActivationFlags_CommitWithBiometry activationOtp:nil]; + if (!activation) { + return; + } + XCTAssertTrue([_sdk hasBiometryFactor]); +} + +- (void) testAddingBiometryFactor +{ + CHECK_TEST_CONFIG(); + + // + // This test validates whether it's possible to add biometry factor later. + // + + CHECK_BIOMETRY(); + + PowerAuthSdkActivation * activation = [_helper createActivation:YES]; + if (!activation) { + return; + } + + XCTAssertFalse([_sdk hasBiometryFactor]); + + [AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { + [_sdk addBiometryFactorWithCorePassword:activation.credentials.password callback:^(NSError * _Nullable error) { + XCTAssertNil(error); + [waiting reportCompletion:nil]; + }]; + }]; + + XCTAssertTrue([_sdk hasBiometryFactor]); + + XCTAssertTrue([_sdk removeBiometryFactor]); + + XCTAssertFalse([_sdk hasBiometryFactor]); + + [AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { + [_sdk addBiometryFactorWithPassword:activation.credentials.password.extractedPassword callback:^(NSError * _Nullable error) { + XCTAssertNil(error); + [waiting reportCompletion:nil]; + }]; + }]; + + XCTAssertTrue([_sdk hasBiometryFactor]); +} + +#endif // PA2_BIOMETRY_SUPPORT + @end diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKProtocolUpgradeTests.m b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKProtocolUpgradeTests.m index 3b337d4e..32a7d995 100644 --- a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKProtocolUpgradeTests.m +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKProtocolUpgradeTests.m @@ -132,7 +132,7 @@ - (void)setUp - (NSError*) checkForPassword:(NSString*)password { NSError * result = [AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - PA2TestsOperationTask task = [_sdk validatePasswordCorrect:password callback:^(NSError * error) { + PA2TestsOperationTask task = [_sdk validatePassword:password callback:^(NSError * error) { [waiting reportCompletion:error]; }]; XCTAssertNotNil(task); @@ -176,7 +176,7 @@ - (void) createOldActivation NSString * activationStateData = [[_helper sessionCoreSerializedState] base64EncodedStringWithOptions:0]; NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; - [defaults setObject:auth.usePassword forKey:s_PossessionFactorKey]; + [defaults setObject:auth.password.extractedPassword forKey:s_PossessionFactorKey]; [defaults setObject:activationData.activationId forKey:s_ActivationIdKey]; [defaults setObject:activationStateData forKey:s_StateDataKey]; [defaults synchronize]; @@ -186,7 +186,7 @@ - (void) createOldActivation NSLog(@"======================================================================="); NSLog(@"Upgrade params (for old SDK step):"); - NSLog(@" - password %@", auth.usePassword); + NSLog(@" - password %@", auth.password.extractedPassword); NSLog(@" - act-id %@", activationData.activationId); NSLog(@"======================================================================="); } diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKSharedTests.m b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKSharedTests.m index c85b0afd..2a6b2a8b 100644 --- a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKSharedTests.m +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSDKSharedTests.m @@ -147,14 +147,14 @@ - (void) testConcurrentSignatureCalculations }]; } else { completionCount += 2; - [self.sdk validatePasswordCorrect:credentials.usePassword callback:^(NSError * error) { + [self.sdk validateCorePassword:credentials.password callback:^(NSError * error) { XCTAssertNil(error); [waiting extendWaitingTime]; if (!--completionCount) { [waiting reportCompletion:nil]; } }]; - id paTask = [self.sdk validatePasswordCorrect:credentials.usePassword callback:^(NSError * error) { + id paTask = [self.sdk validateCorePassword:credentials.password callback:^(NSError * error) { XCTAssertNil(error); [_waitForQueuesTask extendWaitingTime]; if (!--completionCount) { @@ -188,14 +188,14 @@ - (void) testConcurrentSignatureCalculations }]; } else { completionCount += 2; - [self.sdk validatePasswordCorrect:credentials.usePassword callback:^(NSError * error) { + [self.sdk validateCorePassword:credentials.password callback:^(NSError * error) { XCTAssertNil(error); [waiting extendWaitingTime]; if (!--completionCount) { [waiting reportCompletion:nil]; } }]; - [self.sdk validatePasswordCorrect:credentials.usePassword callback:^(NSError * error) { + [self.sdk validateCorePassword:credentials.password callback:^(NSError * error) { XCTAssertNil(error); [_waitForQueuesTask extendWaitingTime]; if (!--completionCount) { @@ -264,7 +264,7 @@ - (void) testConcurrentActivation XCTAssertEqual(PowerAuthExternalPendingOperationType_Activation, extOp2.externalOperationType); XCTAssertTrue([_app1 isEqualToString:extOp2.externalApplicationId]); - BOOL result = [self.sdk commitActivationWithPassword:credentials.usePassword error:nil]; + BOOL result = [self.sdk commitActivationWithCorePassword:credentials.password error:nil]; XCTAssertTrue(result); XCTAssertTrue([self.sdk hasValidActivation]); diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.h b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.h index 455d8a4e..31978f9c 100644 --- a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.h +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.h @@ -17,11 +17,23 @@ #import #import "PowerAuthTestServerAPI.h" #import "PowerAuthTestServerConfig.h" +#import "PowerAuthCorePasswordHelper.h" #import "AsyncHelper.h" @import PowerAuth2; @import PowerAuthCore; +#import // Expose SDK macros, to allow platform specific #if-defs + +typedef NS_OPTIONS(NSUInteger, TestActivationFlags) { + TestActivationFlags_None = 0, + TestActivationFlags_UseSignature = 1 << 0, + TestActivationFlags_CommitWithPlainPassword = 1 << 1, + TestActivationFlags_CommitWithCorePassword = 1 << 2, + TestActivationFlags_CommitWithBiometry = 1 << 3, + TestActivationFlags_RemoveAfter = 1 << 31, +}; + /** Object containing activation data. */ @@ -113,6 +125,9 @@ - (PowerAuthSdkActivation*) createActivation:(BOOL)useSignature activationOtp:(NSString*)activationOtp; +- (PowerAuthSdkActivation*) createActivationWithFlags:(TestActivationFlags)flags + activationOtp:(NSString*)activationOtp; + /** Prepare activation on server. */ @@ -173,6 +188,10 @@ */ - (BOOL) checkForPassword:(NSString*)password; +/** + Validates password on server. Returns YES if password is valid. + */ +- (BOOL) checkForCorePassword:(PowerAuthCorePassword*)password; /** Converts factors from auth object to string. @@ -194,10 +213,16 @@ /** - Creates a new PowerAuthAuthentication object with default configuration. + Creates a new PowerAuthAuthentication object for commit with default configuration. */ - (PowerAuthAuthentication*) createAuthentication; +/** + Creates a new PowerAuthAuthentication object for commit with biometry. + */ +- (PowerAuthAuthentication*) createAuthenticationWithBiometry; + + // Tokens /** diff --git a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.m b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.m index 5dfd23df..ee08ac53 100644 --- a/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.m +++ b/proj-xcode/PowerAuth2IntegrationTests/PowerAuthSdkTestHelper.m @@ -224,15 +224,13 @@ - (PATSInitActivationResponse*) prepareActivation:(BOOL)useSignature otp:activationOtp]; } -/** - Returns object with activation data, authentication object, - and result of activation. You can configure whether the activation can use optional signature during the activation - and whether the activation should be removed automatically after the creation. - */ -- (PowerAuthSdkActivation*) createActivation:(BOOL)useSignature - activationOtp:(NSString*)activationOtp - removeAfter:(BOOL)removeAfter +- (PowerAuthSdkActivation*) createActivationWithFlags:(TestActivationFlags)flags activationOtp:(NSString *)activationOtp { + BOOL useSignature = (flags & TestActivationFlags_UseSignature) != 0; + BOOL removeAfter = (flags & TestActivationFlags_RemoveAfter) != 0; + BOOL commitWithPass = (flags & TestActivationFlags_CommitWithPlainPassword) != 0; + BOOL commitWithCorePass = (flags & TestActivationFlags_CommitWithCorePassword) != 0; + BOOL commitWithBio = (flags & TestActivationFlags_CommitWithBiometry) != 0; _currentActivation = nil; XCTAssertFalse([_sdk hasPendingActivation]); @@ -293,8 +291,15 @@ - (PowerAuthSdkActivation*) createActivation:(BOOL)useSignature XCTAssertFalse([_sdk hasValidActivation]); // 3) CLIENT: Now it's time to commit activation locally - PowerAuthAuthentication * auth = [self createAuthentication]; - result = [_sdk commitActivationWithAuthentication:auth error:&error]; + PowerAuthAuthentication * auth = commitWithBio ? [self createAuthenticationWithBiometry] : [self createAuthentication]; + if (commitWithPass) { + result = [_sdk commitActivationWithPassword:auth.password.extractedPassword error:&error]; + } else if (commitWithCorePass) { + result = [_sdk commitActivationWithCorePassword:auth.password error:&error]; + } else { + // By default, use authentication for commit + result = [_sdk commitActivationWithAuthentication:auth error:&error]; + } if (!result) { return nil; } @@ -366,6 +371,15 @@ - (PowerAuthSdkActivation*) createActivation:(BOOL)useSignature return _currentActivation; } +- (PowerAuthSdkActivation*) createActivation:(BOOL)useSignature + activationOtp:(NSString*)activationOtp + removeAfter:(BOOL)removeAfter +{ + TestActivationFlags flags = (useSignature ? TestActivationFlags_UseSignature : 0) | + (removeAfter ? TestActivationFlags_RemoveAfter : 0); + return [self createActivationWithFlags:flags activationOtp:activationOtp]; +} + - (PowerAuthSdkActivation*) createActivation:(BOOL)useSignature removeAfter:(BOOL)removeAfter { return [self createActivation:useSignature activationOtp:nil removeAfter:removeAfter]; @@ -473,23 +487,52 @@ - (PowerAuthActivationStatus*) fetchActivationStatus #pragma mark - Utils +- (NSArray*) veryStrongPasswords +{ + return @[ @"supersecure", @"nbusr123", @"8520", @"pa55w0rd" ]; +} + /** Creates a new PowerAuthAuthentication object with default configuration. */ - (PowerAuthAuthentication*) createAuthentication { - NSArray * veryCleverPasswords = @[ @"supersecure", @"nbusr123", @"8520", @"pa55w0rd" ]; + NSArray * veryCleverPasswords = [self veryStrongPasswords]; NSString * newPassword = veryCleverPasswords[arc4random_uniform((uint32_t)veryCleverPasswords.count)]; return [PowerAuthAuthentication commitWithPassword:newPassword]; } +/** + Creates a new PowerAuthAuthentication object with default configuration. + */ +- (PowerAuthAuthentication*) createAuthenticationWithBiometry +{ + NSArray * veryCleverPasswords = [self veryStrongPasswords]; + NSString * newPassword = veryCleverPasswords[arc4random_uniform((uint32_t)veryCleverPasswords.count)]; + return [PowerAuthAuthentication commitWithPasswordAndBiometry:newPassword]; +} + /** Validates password on server. Returns YES if password is valid. */ - (BOOL) checkForPassword:(NSString*)password { BOOL result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { - id task = [_sdk validatePasswordCorrect:password callback:^(NSError * error) { + id task = [_sdk validatePassword:password callback:^(NSError * error) { + [waiting reportCompletion:@(error == nil)]; + }]; + XCTAssertNotNil(task); + }] boolValue]; + return result; +} + +/** + Validates password on server. Returns YES if password is valid. + */ +- (BOOL) checkForCorePassword:(PowerAuthCorePassword*)password +{ + BOOL result = [[AsyncHelper synchronizeAsynchronousBlock:^(AsyncHelper *waiting) { + id task = [_sdk validateCorePassword:password callback:^(NSError * error) { [waiting reportCompletion:@(error == nil)]; }]; XCTAssertNotNil(task); @@ -589,7 +632,7 @@ - (NSString*) authToString:(PowerAuthAuthentication*)auth if (auth.usePossession) { [components addObject:@"POSSESSION"]; } - if (auth.usePassword) { + if (auth.password) { [components addObject:@"KNOWLEDGE"]; } if (auth.useBiometry) { @@ -740,16 +783,16 @@ @implementation PowerAuthAuthentication (TestHelper) - (PowerAuthAuthentication*) copyForSigning { - if (self.usePassword == nil) { + if (self.password == nil) { @throw [NSException exceptionWithName:@"TestError" reason:@"Wrong PowerAuthAuthentication object" userInfo:nil]; } - return [PowerAuthAuthentication possessionWithPassword:self.usePassword]; + return [PowerAuthAuthentication possessionWithCorePassword:self.password]; } - (PowerAuthAuthentication*) copyCrippledForSigning { // cripple auth object - if (self.usePassword) { + if (self.password) { return [PowerAuthAuthentication possession]; } else { return [PowerAuthAuthentication possessionWithPassword:@"alwaysBadPassword"]; diff --git a/proj-xcode/PowerAuth2Tests/PowerAuthAuthenticationTests.m b/proj-xcode/PowerAuth2Tests/PowerAuthAuthenticationTests.m index b8a2eb7a..1448f28e 100644 --- a/proj-xcode/PowerAuth2Tests/PowerAuthAuthenticationTests.m +++ b/proj-xcode/PowerAuth2Tests/PowerAuthAuthenticationTests.m @@ -19,6 +19,7 @@ #import "PowerAuthAuthentication+Private.h" #import "PowerAuthMacros.h" +#import "PowerAuthCorePasswordHelper.h" @interface PowerAuthAuthenticationTests : XCTestCase @property (nonatomic, strong) NSData * customBiometryKey; @@ -50,7 +51,7 @@ - (void) testCommitWithPassword PowerAuthAuthentication * auth = [PowerAuthAuthentication commitWithPassword:@"1234"]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertEqual(@"1234", auth.usePassword); + XCTAssertEqualObjects(@"1234", auth.password.extractedPassword); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -60,11 +61,33 @@ - (void) testCommitWithPassword auth = [PowerAuthAuthentication commitWithPassword:@"4321" customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertEqual(@"4321", auth.usePassword); + XCTAssertEqualObjects(@"4321", auth.password.extractedPassword); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertTrue([auth validateUsage:YES]); + + // core password variants + + auth = [PowerAuthAuthentication commitWithCorePassword:[PowerAuthCorePassword passwordWithString:@"1234"]]; + XCTAssertTrue(auth.usePossession); + XCTAssertFalse(auth.useBiometry); + XCTAssertEqualObjects(@"1234", auth.password.extractedPassword); + XCTAssertNil(auth.biometryPrompt); + XCTAssertContextNil(auth.biometryContext); + XCTAssertNil(auth.overridenBiometryKey); + XCTAssertNil(auth.overridenPossessionKey); + XCTAssertTrue([auth validateUsage:YES]); + + auth = [PowerAuthAuthentication commitWithCorePassword:[PowerAuthCorePassword passwordWithString:@"4321"] customPossessionKey:_customPossessionKey]; + XCTAssertTrue(auth.usePossession); + XCTAssertFalse(auth.useBiometry); + XCTAssertEqualObjects(@"4321", auth.password.extractedPassword); + XCTAssertNil(auth.biometryPrompt); + XCTAssertContextNil(auth.biometryContext); + XCTAssertNil(auth.overridenBiometryKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:YES]); } @@ -73,7 +96,7 @@ - (void) testCommitWithPasswordAndBiometry PowerAuthAuthentication * auth = [PowerAuthAuthentication commitWithPasswordAndBiometry:@"1234"]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertEqual(@"1234", auth.usePassword); + XCTAssertEqualObjects(@"1234", auth.password.extractedPassword); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -83,11 +106,33 @@ - (void) testCommitWithPasswordAndBiometry auth = [PowerAuthAuthentication commitWithPasswordAndBiometry:@"4321" customBiometryKey:_customBiometryKey customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertEqual(@"4321", auth.usePassword); + XCTAssertEqualObjects(@"4321", auth.password.extractedPassword); + XCTAssertNil(auth.biometryPrompt); + XCTAssertContextNil(auth.biometryContext); + XCTAssertEqualObjects(self.customBiometryKey, auth.overridenBiometryKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertTrue([auth validateUsage:YES]); + + // core password variants + + auth = [PowerAuthAuthentication commitWithCorePasswordAndBiometry:[PowerAuthCorePassword passwordWithString:@"1234"]]; + XCTAssertTrue(auth.usePossession); + XCTAssertTrue(auth.useBiometry); + XCTAssertEqualObjects(@"1234", auth.password.extractedPassword); + XCTAssertNil(auth.biometryPrompt); + XCTAssertContextNil(auth.biometryContext); + XCTAssertNil(auth.overridenBiometryKey); + XCTAssertNil(auth.overridenPossessionKey); + XCTAssertTrue([auth validateUsage:YES]); + + auth = [PowerAuthAuthentication commitWithCorePasswordAndBiometry:[PowerAuthCorePassword passwordWithString:@"4321"] customBiometryKey:_customBiometryKey customPossessionKey:_customPossessionKey]; + XCTAssertTrue(auth.usePossession); + XCTAssertTrue(auth.useBiometry); + XCTAssertEqualObjects(@"4321", auth.password.extractedPassword); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); - XCTAssertEqual(self.customBiometryKey, auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customBiometryKey, auth.overridenBiometryKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:YES]); } @@ -96,7 +141,7 @@ - (void) testSignPossessionOnly PowerAuthAuthentication * auth = [PowerAuthAuthentication possession]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -109,7 +154,7 @@ - (void) testSignPossessionWithPassword PowerAuthAuthentication * auth = [PowerAuthAuthentication possessionWithPassword:@"1234"]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertEqual(@"1234", auth.usePassword); + XCTAssertEqualObjects(@"1234", auth.password.extractedPassword); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -119,11 +164,33 @@ - (void) testSignPossessionWithPassword auth = [PowerAuthAuthentication possessionWithPassword:@"4321" customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertFalse(auth.useBiometry); - XCTAssertEqual(@"4321", auth.usePassword); + XCTAssertEqualObjects(@"4321", auth.password.extractedPassword); + XCTAssertNil(auth.biometryPrompt); + XCTAssertContextNil(auth.biometryContext); + XCTAssertNil(auth.overridenBiometryKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertTrue([auth validateUsage:NO]); + + // core password variants + + auth = [PowerAuthAuthentication possessionWithCorePassword:[PowerAuthCorePassword passwordWithString:@"1234"]]; + XCTAssertTrue(auth.usePossession); + XCTAssertFalse(auth.useBiometry); + XCTAssertEqualObjects(@"1234", auth.password.extractedPassword); + XCTAssertNil(auth.biometryPrompt); + XCTAssertContextNil(auth.biometryContext); + XCTAssertNil(auth.overridenBiometryKey); + XCTAssertNil(auth.overridenPossessionKey); + XCTAssertTrue([auth validateUsage:NO]); + + auth = [PowerAuthAuthentication possessionWithCorePassword:[PowerAuthCorePassword passwordWithString:@"4321"] customPossessionKey:_customPossessionKey]; + XCTAssertTrue(auth.usePossession); + XCTAssertFalse(auth.useBiometry); + XCTAssertEqualObjects(@"4321", auth.password.extractedPassword); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); } @@ -132,7 +199,7 @@ - (void) testSignPossessionWithBiometry PowerAuthAuthentication * auth = [PowerAuthAuthentication possessionWithBiometry]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); @@ -142,18 +209,18 @@ - (void) testSignPossessionWithBiometry auth = [PowerAuthAuthentication possessionWithBiometryWithCustomBiometryKey:_customBiometryKey customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); - XCTAssertEqual(self.customBiometryKey, auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customBiometryKey, auth.overridenBiometryKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); auth = [PowerAuthAuthentication possessionWithBiometryPrompt:_biometryPrompt]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); - XCTAssertEqual(_biometryPrompt, auth.biometryPrompt); + XCTAssertNil(auth.password); + XCTAssertEqualObjects(_biometryPrompt, auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); XCTAssertNil(auth.overridenPossessionKey); @@ -162,20 +229,20 @@ - (void) testSignPossessionWithBiometry auth = [PowerAuthAuthentication possessionWithBiometryPrompt:_biometryPrompt customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); - XCTAssertEqual(_biometryPrompt, auth.biometryPrompt); + XCTAssertNil(auth.password); + XCTAssertEqualObjects(_biometryPrompt, auth.biometryPrompt); XCTAssertContextNil(auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); #if PA2_HAS_LACONTEXT auth = [PowerAuthAuthentication possessionWithBiometryContext:_biometryContext]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); - XCTAssertEqual(self.biometryContext, auth.biometryContext); + XCTAssertEqualObjects(self.biometryContext, auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); XCTAssertNil(auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); @@ -183,11 +250,11 @@ - (void) testSignPossessionWithBiometry auth = [PowerAuthAuthentication possessionWithBiometryContext:_biometryContext customPossessionKey:_customPossessionKey]; XCTAssertTrue(auth.usePossession); XCTAssertTrue(auth.useBiometry); - XCTAssertNil(auth.usePassword); + XCTAssertNil(auth.password); XCTAssertNil(auth.biometryPrompt); - XCTAssertEqual(self.biometryContext, auth.biometryContext); + XCTAssertEqualObjects(self.biometryContext, auth.biometryContext); XCTAssertNil(auth.overridenBiometryKey); - XCTAssertEqual(self.customPossessionKey, auth.overridenPossessionKey); + XCTAssertEqualObjects(self.customPossessionKey, auth.overridenPossessionKey); XCTAssertTrue([auth validateUsage:NO]); #endif // PA2_HAS_LACONTEXT } @@ -198,6 +265,8 @@ - (void) testWrongUsage XCTAssertFalse([auth validateUsage:YES]); auth = [PowerAuthAuthentication commitWithPassword:@"Hello"]; XCTAssertFalse([auth validateUsage:NO]); + auth = [PowerAuthAuthentication commitWithCorePassword:[PowerAuthCorePassword passwordWithString:@"Hello"]]; + XCTAssertFalse([auth validateUsage:NO]); } - (void) testLegacyObject diff --git a/proj-xcode/PowerAuth2TestsHostApp/ContentView.swift b/proj-xcode/PowerAuth2TestsHostApp/ContentView.swift index b9d8dea2..17a65d56 100644 --- a/proj-xcode/PowerAuth2TestsHostApp/ContentView.swift +++ b/proj-xcode/PowerAuth2TestsHostApp/ContentView.swift @@ -16,6 +16,9 @@ import SwiftUI +import PowerAuth2 +import PowerAuthCore + struct ContentView: View { var body: some View { Text("Executing PowerAuth integration tests.") @@ -28,3 +31,15 @@ struct ContentView_Previews: PreviewProvider { ContentView() } } + +/// This function is useful only for test whether our Objective-C API +/// is properly exposed to Swift. You can prototype any code you want +/// here to see, whether our API makes sense in Swift. +/// +/// Please revert your changes in this file before you commit & push +/// the rest of your work into the repository. +fileprivate func dummyFunction() { + let config = PowerAuthConfiguration() + let sdk = PowerAuthSDK(configuration: config)! + _ = sdk.hasBiometryFactor() +} diff --git a/proj-xcode/PowerAuthCore/PowerAuthCorePassword.h b/proj-xcode/PowerAuthCore/PowerAuthCorePassword.h index 84021aca..3900398e 100644 --- a/proj-xcode/PowerAuthCore/PowerAuthCorePassword.h +++ b/proj-xcode/PowerAuthCore/PowerAuthCorePassword.h @@ -70,19 +70,38 @@ */ @interface PowerAuthCorePassword : NSObject +/** + Constructor with no parameters is not available. + */ +- (nonnull instancetype) init NS_UNAVAILABLE; + +/** + Initialize PowerAuthCorePassword object with UTF8 data from the given string. The method is useful + for scenarios, when you have the full password already prepared and you want to pass it to the Session + as a parameter. + */ +- (nonnull instancetype) initWithString:(nonnull NSString*)string; + +/** + Initialize PowerAuthCorePassword object with the content copied from given data object. + The password object will contain an immutable passphrase, created exactly from the bytes, + provided by the data object. + */ +- (nonnull instancetype) initWithData:(nonnull NSData*)data; + /** Returns a new instance of PowerAuthCorePassword object, initialized with UTF8 data from the given string. The method is useful for scenarios, when you have the full password already prepared and you want to pass it to the Session as a parameter. */ -+ (nullable instancetype) passwordWithString:(nonnull NSString*)string; ++ (nonnull instancetype) passwordWithString:(nonnull NSString*)string; /** Creates a new instance of PowerAuthCorePassword object, initialized with the content copied from given data object. The password object will contain an immutable passphrase, created exactly from the bytes, provided by the data object. */ -+ (nullable instancetype) passwordWithData:(nonnull NSData*)data; ++ (nonnull instancetype) passwordWithData:(nonnull NSData*)data; /** Returns length of the password (in bytes). @@ -96,15 +115,21 @@ - (BOOL) isEqualToPassword:(nullable PowerAuthCorePassword*)password; /** - The method validates stored passphrase with using provided validation block. The raw bytes of - the passphrase are revealed to the block, which can decide whether the passphrase's complexity - is sufficient or not. It's not recommended to copy the plaintext password to another memory - location, to minimize traces of the password in the memory. + The method allows you to validate stored passphrase with using provided validation block. + The raw characters and the length of the passphrase are revealed to the block, and the validation + block can decide whether the passphrase's complexity is sufficient or not. + + The provided pointer to the passhphrase is always null terminated, so it's safe to pass it + to functions that accept the null terminated string. On opposite to that, it's not recommended + to use this validation function on passwords created form an arbitrary data. - Returns value provided by the validation block. The meaning of returned integer depends on + It's not recommended to copy the plaintext password to another memory location, to minimize traces + of the password in the memory. + + @return Returns value provided by the validation block. The meaning of returned integer depends on validation block's implementation. */ -- (NSInteger) validatePasswordComplexity:(NSInteger (NS_NOESCAPE ^_Nonnull)(const UInt8 * _Nonnull passphrase, NSUInteger length))validationBlock; +- (NSInteger) validatePasswordComplexity:(NSInteger (NS_NOESCAPE ^_Nonnull)(const char * _Nonnull passphrase, NSInteger length))validationBlock; @end @@ -117,10 +142,25 @@ */ @interface PowerAuthCoreMutablePassword : PowerAuthCorePassword +/** + Initialize PowerAuthCoreMutablePassword object with empty passphrase. + */ +- (nonnull instancetype) init; + +/** + Mutable password cannot be created with predefined string. + */ +- (nonnull instancetype) initWithString:(nonnull NSString*)string NS_UNAVAILABLE; + +/** + Mutable password cannot be created with predefined data. + */ +- (nonnull instancetype) initWithData:(nonnull NSData*)data NS_UNAVAILABLE; + /** Returns a new insntace of PowerAuthCoreMutablePassword object. */ -+ (nullable instancetype) mutablePassword; ++ (nonnull instancetype) mutablePassword; /** Clears current content of the password diff --git a/proj-xcode/PowerAuthCore/PowerAuthCorePassword.mm b/proj-xcode/PowerAuthCore/PowerAuthCorePassword.mm index 02433c80..28e0ed5d 100644 --- a/proj-xcode/PowerAuthCore/PowerAuthCorePassword.mm +++ b/proj-xcode/PowerAuthCore/PowerAuthCorePassword.mm @@ -26,22 +26,41 @@ @implementation PowerAuthCorePassword io::getlime::powerAuth::Password _password; } -+ (instancetype) passwordWithString:(NSString *)string +- (instancetype) initWithString:(NSString *)string { - PowerAuthCorePassword * pass = [[PowerAuthCorePassword alloc] init]; - if (pass) { - pass->_password.initAsImmutable(cc7::MakeRange(string.UTF8String)); + self = [super init]; + if (self) { + _password.initAsImmutable(cc7::MakeRange(string.UTF8String)); } - return pass; + return self; } -+ (instancetype) passwordWithData:(NSData *)data +- (instancetype) initWithData:(NSData *)data { - PowerAuthCorePassword * pass = [[PowerAuthCorePassword alloc] init]; - if (pass) { - pass->_password.initAsImmutable(cc7::ByteRange(data.bytes, data.length)); + self = [super init]; + if (self) { + _password.initAsImmutable(cc7::ByteRange(data.bytes, data.length)); } - return pass; + return self; +} + +- (instancetype) initMutable +{ + self = [super init]; + if (self) { + _password.initAsMutable(); + } + return self; +} + ++ (instancetype) passwordWithString:(NSString *)string +{ + return [[PowerAuthCorePassword alloc] initWithString:string]; +} + ++ (instancetype) passwordWithData:(NSData *)data +{ + return [[PowerAuthCorePassword alloc] initWithData:data]; } - (NSUInteger) length @@ -70,10 +89,15 @@ - (BOOL) isEqual:(id)object return NO; } -- (NSInteger) validatePasswordComplexity:(NSInteger (NS_NOESCAPE ^)(const UInt8* passphrase, NSUInteger length))validationBlock +- (NSInteger) validatePasswordComplexity:(NSInteger (NS_NOESCAPE ^)(const char* passphrase, NSInteger length))validationBlock { auto plaintext = _password.passwordData(); - return validationBlock(plaintext.data(), plaintext.size()); + auto size = plaintext.size(); + // Append null terminator in case that consumer would like to use the pointer + // in functions that accept c-style strings. The validation block still gets + // the correct size. + plaintext.append(0); + return validationBlock((const char*)plaintext.data(), size); } @end @@ -98,13 +122,9 @@ @implementation PowerAuthCorePassword (Private) @implementation PowerAuthCoreMutablePassword -- (id) init +- (instancetype) init { - self = [super init]; - if (self) { - _password.initAsMutable(); - } - return self; + return [super initMutable]; } + (instancetype) mutablePassword diff --git a/proj-xcode/PowerAuthCoreTests/PowerAuthCorePasswordTests.m b/proj-xcode/PowerAuthCoreTests/PowerAuthCorePasswordTests.m index 62ce0280..e18cf29f 100644 --- a/proj-xcode/PowerAuthCoreTests/PowerAuthCorePasswordTests.m +++ b/proj-xcode/PowerAuthCoreTests/PowerAuthCorePasswordTests.m @@ -163,7 +163,7 @@ - (void) testPasswordNotEqual - (NSString*) extractStringFromPassword:(PowerAuthCorePassword*)password { __block NSString * stringPassword = nil; - [password validatePasswordComplexity:^NSInteger(const UInt8 * _Nonnull passphrase, NSUInteger length) { + [password validatePasswordComplexity:^NSInteger(const char * _Nonnull passphrase, NSInteger length) { stringPassword = [[NSString alloc] initWithBytes:passphrase length:length encoding:NSUTF8StringEncoding]; return 0; }]; @@ -173,7 +173,7 @@ - (NSString*) extractStringFromPassword:(PowerAuthCorePassword*)password - (NSData*) extractBytesFromPassword:(PowerAuthCorePassword*)password { __block NSData * passwordData = nil; - [password validatePasswordComplexity:^NSInteger(const UInt8 * _Nonnull passphrase, NSUInteger length) { + [password validatePasswordComplexity:^NSInteger(const char * _Nonnull passphrase, NSInteger length) { passwordData = [NSData dataWithBytes:passphrase length:length]; return 0; }]; diff --git a/src/PowerAuth/Password.cpp b/src/PowerAuth/Password.cpp index 3956a0b5..febab8bd 100644 --- a/src/PowerAuth/Password.cpp +++ b/src/PowerAuth/Password.cpp @@ -119,7 +119,7 @@ namespace powerAuth cc7::ByteArray Password::passwordData() const { - // Pre-allos result ByteArray with actual stored data size. + // Pre-allocate ByteArray with actual stored password size. const size_t data_size = _pass.size() - randomKeySize; cc7::ByteArray plaintext(data_size); // Reveal plaintext bytes to result ByteArray @@ -227,6 +227,15 @@ namespace powerAuth return false; } + void Password::secureClear() + { + if (isMutable()) { + initAsMutable(); + } else { + initAsImmutable(cc7::ByteRange()); + } + } + // MARK: - Private interface - size_t Password::indexToPos(size_t index)