Skip to content

Commit

Permalink
Fix #127: Implement secure password on iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
hvge committed Oct 25, 2022
1 parent 9677695 commit b7777c4
Show file tree
Hide file tree
Showing 27 changed files with 1,385 additions and 152 deletions.
2 changes: 1 addition & 1 deletion ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ target 'PowerAuth' do
use_react_native!(
:path => config["reactNativePath"]
)
pod 'PowerAuth2', '~> 1.7.3'
pod 'PowerAuth2', '~> 1.7.4'
end
28 changes: 18 additions & 10 deletions ios/PowerAuth.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@
BF1F65A928F810880093A45A /* PowerAuthData.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1F65A728F810880093A45A /* PowerAuthData.m */; };
BF1F65AB28F8464B0093A45A /* PowerAuthObjectRegister+JS.m in Sources */ = {isa = PBXBuildFile; fileRef = BF1F65AA28F8464B0093A45A /* PowerAuthObjectRegister+JS.m */; };
BF1F65AE28F9581F0093A45A /* Constants.h in Headers */ = {isa = PBXBuildFile; fileRef = BF1F65AC28F9581F0093A45A /* Constants.h */; };
BFAEDB6C29017F5E00E65B40 /* PowerAuthPasswordModule.h in Headers */ = {isa = PBXBuildFile; fileRef = BFAEDB6A29017F5E00E65B40 /* PowerAuthPasswordModule.h */; settings = {ATTRIBUTES = (Private, ); }; };
BFAEDB6D29017F5E00E65B40 /* PowerAuthPasswordModule.m in Sources */ = {isa = PBXBuildFile; fileRef = BFAEDB6B29017F5E00E65B40 /* PowerAuthPasswordModule.m */; };
CAC0BE336028DD00272CEB09 /* libPods-PowerAuth.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D3CE2470A643546DD46BA726 /* libPods-PowerAuth.a */; };
DCEDF08B249E59EF00918481 /* PowerAuth.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEDF089249E59EF00918481 /* PowerAuth.h */; settings = {ATTRIBUTES = (Private, ); }; };
DCEDF092249E5A8600918481 /* PowerAuth.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEDF091249E5A8600918481 /* PowerAuth.m */; };
DCEDF08B249E59EF00918481 /* PowerAuthModule.h in Headers */ = {isa = PBXBuildFile; fileRef = DCEDF089249E59EF00918481 /* PowerAuthModule.h */; settings = {ATTRIBUTES = (Private, ); }; };
DCEDF092249E5A8600918481 /* PowerAuthModule.m in Sources */ = {isa = PBXBuildFile; fileRef = DCEDF091249E5A8600918481 /* PowerAuthModule.m */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -35,11 +37,13 @@
BF1F65A728F810880093A45A /* PowerAuthData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PowerAuthData.m; sourceTree = "<group>"; };
BF1F65AA28F8464B0093A45A /* PowerAuthObjectRegister+JS.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "PowerAuthObjectRegister+JS.m"; sourceTree = "<group>"; };
BF1F65AC28F9581F0093A45A /* Constants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Constants.h; sourceTree = "<group>"; };
BFAEDB6A29017F5E00E65B40 /* PowerAuthPasswordModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PowerAuthPasswordModule.h; sourceTree = "<group>"; };
BFAEDB6B29017F5E00E65B40 /* PowerAuthPasswordModule.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PowerAuthPasswordModule.m; sourceTree = "<group>"; };
D3CE2470A643546DD46BA726 /* libPods-PowerAuth.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-PowerAuth.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DCEDF086249E59EF00918481 /* PowerAuth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PowerAuth.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DCEDF089249E59EF00918481 /* PowerAuth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PowerAuth.h; sourceTree = "<group>"; };
DCEDF089249E59EF00918481 /* PowerAuthModule.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PowerAuthModule.h; sourceTree = "<group>"; };
DCEDF08A249E59EF00918481 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
DCEDF091249E5A8600918481 /* PowerAuth.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PowerAuth.m; sourceTree = "<group>"; };
DCEDF091249E5A8600918481 /* PowerAuthModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PowerAuthModule.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -97,13 +101,15 @@
BF1F65A328F80CF80093A45A /* Utilities.m */,
BF1F659E28F6EF900093A45A /* Errors.h */,
BF1F659F28F6EF900093A45A /* Errors.m */,
BF1F65A628F810880093A45A /* PowerAuthData.h */,
BF1F65A728F810880093A45A /* PowerAuthData.m */,
BF1F659A28F587B40093A45A /* PowerAuthObjectRegister.h */,
BF1F659B28F587B40093A45A /* PowerAuthObjectRegister.m */,
BF1F65AA28F8464B0093A45A /* PowerAuthObjectRegister+JS.m */,
BF1F65A628F810880093A45A /* PowerAuthData.h */,
BF1F65A728F810880093A45A /* PowerAuthData.m */,
DCEDF089249E59EF00918481 /* PowerAuth.h */,
DCEDF091249E5A8600918481 /* PowerAuth.m */,
BFAEDB6A29017F5E00E65B40 /* PowerAuthPasswordModule.h */,
BFAEDB6B29017F5E00E65B40 /* PowerAuthPasswordModule.m */,
DCEDF089249E59EF00918481 /* PowerAuthModule.h */,
DCEDF091249E5A8600918481 /* PowerAuthModule.m */,
DCEDF08A249E59EF00918481 /* Info.plist */,
);
path = PowerAuth;
Expand All @@ -116,11 +122,12 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
DCEDF08B249E59EF00918481 /* PowerAuth.h in Headers */,
DCEDF08B249E59EF00918481 /* PowerAuthModule.h in Headers */,
BF1F65A828F810880093A45A /* PowerAuthData.h in Headers */,
BF1F65AE28F9581F0093A45A /* Constants.h in Headers */,
BF1F65A428F80CF80093A45A /* Utilities.h in Headers */,
BF1F65A028F6EF900093A45A /* Errors.h in Headers */,
BFAEDB6C29017F5E00E65B40 /* PowerAuthPasswordModule.h in Headers */,
BF1F659C28F587B40093A45A /* PowerAuthObjectRegister.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down Expand Up @@ -239,10 +246,11 @@
files = (
BF1F659D28F587B40093A45A /* PowerAuthObjectRegister.m in Sources */,
BF1F65A928F810880093A45A /* PowerAuthData.m in Sources */,
DCEDF092249E5A8600918481 /* PowerAuth.m in Sources */,
DCEDF092249E5A8600918481 /* PowerAuthModule.m in Sources */,
BF1F65A528F80CF80093A45A /* Utilities.m in Sources */,
BF1F65AB28F8464B0093A45A /* PowerAuthObjectRegister+JS.m in Sources */,
BF1F65A128F6EF900093A45A /* Errors.m in Sources */,
BFAEDB6D29017F5E00E65B40 /* PowerAuthPasswordModule.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
7 changes: 7 additions & 0 deletions ios/PowerAuth/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@
/// key in memory.
#define BIOMETRY_KEY_KEEP_ALIVE_TIME 10000

/// Time interval in milliseconds to keep password object valid
/// in memory.
#define PASSWORD_KEY_KEEP_ALIVE_TIME (5 * 60 * 1000)

/// Upper limit for Unicode Code Point
#define CODEPOINT_MAX 0x10FFFF

/// Default period in milliseconds for automatic objects cleanup job.
#define CLEANUP_PERIOD_DEFAULT 10000
/// Minimum allowed period for automatic objects cleanup job.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
#import <React/RCTInitializing.h>
#import "PowerAuthObjectRegister.h"

@interface PowerAuth: NSObject<RCTBridgeModule, RCTInitializing>
@interface PowerAuthModule: NSObject<RCTBridgeModule, RCTInitializing>

@end
103 changes: 84 additions & 19 deletions ios/PowerAuth/PowerAuth.m → ios/PowerAuth/PowerAuthModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

#import "PowerAuth.h"
#import "PowerAuthModule.h"
#import "PowerAuthData.h"
#import "Constants.h"

Expand All @@ -27,8 +27,9 @@
#import <PowerAuth2/PowerAuthKeychain.h>
#import <PowerAuth2/PowerAuthClientSslNoValidationStrategy.h>

@import PowerAuthCore;

@implementation PowerAuth
@implementation PowerAuthModule
{
PowerAuthObjectRegister * _objectRegister;
}
Expand Down Expand Up @@ -242,12 +243,12 @@ + (BOOL) requiresMainQueueSetup

RCT_REMAP_METHOD(commitActivation,
instanceId:(NSString*)instanceId
authentication:(NSDictionary*)authDict
authentication:(NSDictionary*)authentication
commitActivationResolver:(RCTPromiseResolveBlock)resolve
commitActivationRejecter:(RCTPromiseRejectBlock)reject)
{
PA_BLOCK_START
PowerAuthAuthentication *auth = [self constructAuthenticationFromDictionary:authDict reject:reject forCommit:YES];
PowerAuthAuthentication *auth = [self constructAuthenticationFromDictionary:authentication reject:reject forCommit:YES];
if (!auth) {
return;
}
Expand Down Expand Up @@ -410,26 +411,42 @@ + (BOOL) requiresMainQueueSetup

RCT_REMAP_METHOD(unsafeChangePassword,
instanceId:(NSString*)instanceId
oldPassword:(nonnull NSString*)oldPassword
to:(nonnull NSString*)newPassword
oldPassword:(id)oldPassword
to:(id)newPassword
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
PA_BLOCK_START
BOOL result = [powerAuth unsafeChangePasswordFrom:oldPassword to:newPassword];
PowerAuthCorePassword * coreOldPassword = [self resolvePassword:oldPassword reject:reject];
if (!coreOldPassword) {
return;
}
PowerAuthCorePassword * newCorePassword = [self resolvePassword:newPassword reject:reject];
if (!newCorePassword) {
return;
}
BOOL result = [powerAuth unsafeChangeCorePasswordFrom:coreOldPassword to:newCorePassword];
resolve(@(result));
PA_BLOCK_END
}

RCT_REMAP_METHOD(changePassword,
instanceId:(NSString*)instanceId
oldPassword:(nonnull NSString*)oldPassword
to:(nonnull NSString*)newPassword
oldPassword:(id)oldPassword
to:(id)newPassword
changePasswordResolve:(RCTPromiseResolveBlock)resolve
changePasswordReject:(RCTPromiseRejectBlock)reject)
{
PA_BLOCK_START
[powerAuth changePasswordFrom:oldPassword to:newPassword callback:^(NSError * error) {
PowerAuthCorePassword * coreOldPassword = [self resolvePassword:oldPassword reject:reject];
if (!coreOldPassword) {
return;
}
PowerAuthCorePassword * newCorePassword = [self resolvePassword:newPassword reject:reject];
if (!newCorePassword) {
return;
}
[powerAuth changeCorePasswordFrom:coreOldPassword to:newCorePassword callback:^(NSError * error) {
if (error) {
ProcessError(error, reject);
} else {
Expand All @@ -441,12 +458,16 @@ + (BOOL) requiresMainQueueSetup

RCT_REMAP_METHOD(addBiometryFactor,
instanceId:(NSString*)instanceId
password:(NSString*)password
password:(id)password
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
PA_BLOCK_START
[powerAuth addBiometryFactorWithPassword:password callback:^(NSError * error) {
PowerAuthCorePassword * corePassword = [self resolvePassword:password reject:reject];
if (!corePassword) {
return;
}
[powerAuth addBiometryFactorWithCorePassword:corePassword callback:^(NSError * error) {
if (error) {
ProcessError(error, reject);
} else {
Expand Down Expand Up @@ -565,12 +586,16 @@ + (BOOL) requiresMainQueueSetup

RCT_REMAP_METHOD(validatePassword,
instanceId:(NSString*)instanceId
password:(NSString*)password
password:(id)password
validatePasswordResolve:(RCTPromiseResolveBlock)resolve
validatePasswordReject:(RCTPromiseRejectBlock)reject)
{
PA_BLOCK_START
[powerAuth validatePassword:password callback:^(NSError * error) {
PowerAuthCorePassword * corePassword = [self resolvePassword:password reject:reject];
if (!corePassword) {
return;
}
[powerAuth validateCorePassword:corePassword callback:^(NSError * error) {
if (error) {
ProcessError(error, reject);
} else {
Expand Down Expand Up @@ -908,19 +933,27 @@ - (PowerAuthAuthentication*) constructAuthenticationFromDictionary:(NSDictionary
forCommit:(BOOL)commit
{
BOOL useBiometry = [RCTConvert BOOL:dict[@"useBiometry"]];
NSString * password = GetNSStringValueFromDict(dict, @"userPassword");
id userPassword = dict[@"userPassword"];
if (commit) {
// Activation commit
PowerAuthCorePassword * password = [self resolvePassword:userPassword reject:reject];
if (!password) {
return nil;
}
if (useBiometry) {
// All factors needs to be estabilished in activation.
return [PowerAuthAuthentication commitWithPasswordAndBiometry:password];
return [PowerAuthAuthentication commitWithCorePasswordAndBiometry:password];
} else {
return [PowerAuthAuthentication commitWithPassword:password];
return [PowerAuthAuthentication commitWithCorePassword:password];
}
} else {
// Data signing
if (password) {
return [PowerAuthAuthentication possessionWithPassword:password];
if (userPassword) {
PowerAuthCorePassword * password = [self resolvePassword:userPassword reject:reject];
if (!password) {
return nil;
}
return [PowerAuthAuthentication possessionWithCorePassword:password];
} else if (useBiometry) {
NSString * biometryKeyId = GetNSStringValueFromDict(dict, @"biometryKeyId");
if (biometryKeyId) {
Expand All @@ -944,6 +977,38 @@ - (PowerAuthAuthentication*) constructAuthenticationFromDictionary:(NSDictionary
}
}

/// Method translate object into PowerAuthCorePassword. If such conversion is not possible then use reject promise to
/// report an error.
/// @param anyPassword Object to convert into PowerAuthCorePassword.
/// @param reject Reject function to call in case of failure
/// @return PowerAuthCorePassword or nil if no such conversion is possible.
- (PowerAuthCorePassword*) resolvePassword:(id)anyPassword
reject:(RCTPromiseRejectBlock)reject
{
if ([anyPassword isKindOfClass:[NSString class]]) {
// Password is in form of string
return [PowerAuthCorePassword passwordWithString:anyPassword];
}
if ([anyPassword isKindOfClass:[NSDictionary class]]) {
// It appears that this is an object
id passwordObjectId = [(NSDictionary*)anyPassword objectForKey:@"passwordObjectId"];
if (!passwordObjectId) {
// Object identifier is not present in the object. This means that wrong object is passed to call,
// or PowerAuthPassword javascript object is not initialized yet.
reject(EC_WRONG_PARAMETER, @"PowerAuthPassword is not initialized", nil);
return nil;
}
PowerAuthCorePassword * password = [_objectRegister useObjectWithId:passwordObjectId expectedClass:[PowerAuthCorePassword class]];
if (!password) {
reject(EC_INVALID_NATIVE_OBJECT, @"PowerAuthPassword object is no longer valid", nil);
return nil;
}
return password;
}
reject(EC_WRONG_PARAMETER, @"PowerAuthPassword or string is required", nil);
return nil;
}

/// Method translates `PowerAuthActivationState` into string representation.
/// @param status State to translate.
- (NSString*) getStatusCode:(PowerAuthActivationState)status
Expand Down
14 changes: 13 additions & 1 deletion ios/PowerAuth/PowerAuthObjectRegister+JS.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
#import <React/RCTConvert.h>
#import <React/RCTInvalidating.h>

@import PowerAuthCore;

/*
This class category exports several debug methods to JavaScript.
The 'debug' methods are available only if library is compiled in DEBUG configuration.
Expand Down Expand Up @@ -75,6 +77,8 @@ + (BOOL) requiresMainQueueSetup
objectClass = [PowerAuthData class];
} else if ([@"number" isEqual:objectType]) {
objectClass = [NSNumber class];
} else if ([@"password" isEqual:objectType]) {
objectClass = [PowerAuthCoreMutablePassword class];
}
if ([@"create" isEqual:command]) {
// The "create" command creates a new instance of managed object
Expand Down Expand Up @@ -106,6 +110,8 @@ + (BOOL) requiresMainQueueSetup
instance = [[PowerAuthData alloc] initWithData:td cleanup:YES];
} else if ([@"number" isEqual:objectType]) {
instance = [[NSNumber alloc] initWithInt:42];
} else if ([@"password" isEqual:objectType]) {
instance = [PowerAuthCoreMutablePassword mutablePassword];
}
if (instance) {
NSString * objectId = [self registerObject:instance tag:objectTag policies:policies];
Expand All @@ -132,11 +138,17 @@ + (BOOL) requiresMainQueueSetup
return;
}
} else if ([@"find" isEqual:command]) {
// The "find" command just find the object in the register and returns true / false.
// The "find" command just find the object in the register and returns true / false if object still exists.
if (objectClass && objectId) {
resolve([self findObjectWithId:objectId expectedClass:objectClass] != nil ? @YES : @NO);
return;
}
} else if ([@"touch" isEqual:command]) {
// The "touch" command prolongs lifetime of object in the register and returns true / false if object still exists.
if (objectClass && objectId) {
resolve([self touchObjectWithId:objectId expectedClass:objectClass] != nil ? @YES : @NO);
return;
}
} else if ([@"setPeriod" isEqual:command]) {
[self setCleanupPeriod:[[RCTConvert NSNumber:options[@"cleanupPeriod"]] integerValue]];
resolve(nil);
Expand Down
8 changes: 8 additions & 0 deletions ios/PowerAuth/PowerAuthObjectRegister.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,14 @@
- (nullable id) findObjectWithId:(nonnull NSString*)objectId
expectedClass:(nonnull Class)expectedClass;

/**
Find object with given identifier and prolong its lifetime if `RP_KEEP_ALIVE` policy is specified.
The identifier may be auto-generated or application specific. This method doesn't increase object's
usage count, so it will not cause an object relase.
*/
- (nullable id) touchObjectWithId:(nonnull NSString*)objectId
expectedClass:(nonnull Class)expectedClass;

/**
Find object with given identifier and increase its usage counter.
*/
Expand Down

0 comments on commit b7777c4

Please sign in to comment.