Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add getAllGenericPasswords to iOS and Android #477

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 57 additions & 1 deletion RNKeychainManager/RNKeychainManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -484,7 +484,7 @@ - (OSStatus)deleteCredentialsForServer:(NSString *)server
(__bridge NSString *)kSecAttrServer: server,
(__bridge NSString *)kSecReturnAttributes: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecReturnData: (__bridge id)kCFBooleanTrue,
(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note current master of react-native-keychain is broken due to missing a comma here (also fixed in this PR)

(__bridge NSString *)kSecMatchLimit: (__bridge NSString *)kSecMatchLimitOne,
(__bridge NSString *)kSecUseOperationPrompt: authenticationPrompt
};

Expand Down Expand Up @@ -594,4 +594,60 @@ - (OSStatus)deleteCredentialsForServer:(NSString *)server
}
}

// https://github.com/mCodex/react-native-sensitive-info/blob/495dd7f08c077f5744e56803e45f54787df3dab3/ios/RNSensitiveInfo/RNSensitiveInfo.m#L291
RCT_EXPORT_METHOD(getAllGenericPasswords:(NSDictionary * __nullable)options
resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
{
NSString * keychainService = [RCTConvert NSString:options[@"service"]];

NSMutableArray* finalResult = [[NSMutableArray alloc] init];

NSMutableDictionary *query = [NSMutableDictionary dictionaryWithObjectsAndKeys:
(__bridge id)kSecAttrSynchronizableAny, kSecAttrSynchronizable,
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnAttributes,
(__bridge id)kSecMatchLimitAll, (__bridge id)kSecMatchLimit,
(__bridge id)kCFBooleanTrue, (__bridge id)kSecReturnData,
nil];

if (keychainService) {
[query setObject:keychainService forKey:(NSString *)kSecAttrService];
}

NSArray *secItemClasses = [NSArray arrayWithObjects:
(__bridge id)kSecClassGenericPassword,
nil];

for (id secItemClass in secItemClasses) {
[query setObject:secItemClass forKey:(__bridge id)kSecClass];

CFTypeRef result = NULL;

SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);

if(result != NULL){
for (NSDictionary* item in (__bridge id)result) {
NSMutableDictionary *finalItem = [[NSMutableDictionary alloc] init];

@try
{
[finalItem setObject:(NSString*)[item objectForKey:(__bridge id)(kSecAttrService)] forKey:@"service"];
[finalItem setObject:(NSString*)[item objectForKey:(__bridge id)(kSecAttrAccount)] forKey:@"key"];
[finalItem setObject:[[NSString alloc] initWithData:[item objectForKey:(__bridge id)(kSecValueData)] encoding:NSUTF8StringEncoding] forKey:@"value"];

[finalResult addObject: finalItem];
}
@catch(NSException *exception){} // Ignore items with weird keys or values
}
}
}

if(finalResult != nil){
resolve(finalResult);
} else {
reject(@"no_events", @"There were no events", @[[NSNull null]]);
}
}


@end
60 changes: 60 additions & 0 deletions android/src/main/java/com/oblador/keychain/KeychainModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableNativeArray;
import com.oblador.keychain.PrefsStorage.ResultSet;
import com.oblador.keychain.cipherStorage.CipherStorage;
import com.oblador.keychain.cipherStorage.CipherStorage.DecryptionResult;
Expand Down Expand Up @@ -350,6 +352,64 @@ private Collection<String> doGetAllGenericPasswordServices() throws KeyStoreAcce
return result;
}

@ReactMethod
public void getAllGenericPasswords(@Nullable final ReadableMap options,
@NonNull final Promise promise) {
try {
Collection<String> services = doGetAllGenericPasswordServices();
WritableArray array = new WritableNativeArray();

// get the best storage
final String accessControl = getAccessControlOrDefault(options);
final boolean useBiometry = getUseBiometry(accessControl);
final CipherStorage current = getCipherStorageForCurrentAPILevel(useBiometry);
final String rules = getSecurityRulesOrDefault(options);

final PromptInfo promptInfo = getPromptInfo(options);

for (String service : services) {
try {
if (service.equals(current.getDefaultAliasServiceName())) {
service = EMPTY_STRING;
}

final ResultSet resultSet = prefsStorage.getEncryptedEntry(service);

if (resultSet == null) {
Log.e(KEYCHAIN_MODULE, "No entry found for service: " + service);
continue;
}

final DecryptionResult decryptionResult = decryptCredentials(service, current, resultSet, rules, promptInfo);

final WritableMap credentials = Arguments.createMap();
credentials.putString(Maps.SERVICE, service);
credentials.putString(Maps.USERNAME, decryptionResult.username);
credentials.putString(Maps.PASSWORD, decryptionResult.password);
credentials.putString(Maps.STORAGE, current.getCipherStorageName());

array.pushMap(credentials);
} catch (Throwable fail) {
Log.e(KEYCHAIN_MODULE, fail.getMessage(), fail);
}
}

promise.resolve(array);
} catch (KeyStoreAccessException e) {
Log.e(KEYCHAIN_MODULE, e.getMessage());

promise.reject(Errors.E_KEYSTORE_ACCESS_ERROR, e);
} catch (CryptoFailedException e) {
Log.e(KEYCHAIN_MODULE, e.getMessage());

promise.reject(Errors.E_CRYPTO_FAILED, e);
} catch (Throwable fail) {
Log.e(KEYCHAIN_MODULE, fail.getMessage(), fail);

promise.reject(Errors.E_UNKNOWN_ERROR, fail);
}
}

@ReactMethod
public void getGenericPasswordForOptions(@Nullable final ReadableMap options,
@NonNull final Promise promise) {
Expand Down
13 changes: 13 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,18 @@ export async function getAllGenericPasswordServices(): Promise<string[]> {
return RNKeychainManager.getAllGenericPasswordServices();
}

/**
* Get all items
* @param {object} options A keychain options object.
* @return {Promise} Resolves to an array of `{ service, username, password, storage }` when successful
*/
export function getAllGenericPasswords(
serviceOrOptions?: string | Options
): Promise<UserCredentials[]> {
const options = normalizeOptions(serviceOrOptions);
return RNKeychainManager.getAllGenericPasswords(options);
}

/**
* Checks if we have a login combination for `server`.
* @param {string} server URL to server.
Expand Down Expand Up @@ -387,4 +399,5 @@ export default {
resetGenericPassword,
requestSharedWebCredentials,
setSharedWebCredentials,
getAllGenericPasswords,
};
4 changes: 4 additions & 0 deletions test_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
canImplyAuthentication,
getGenericPassword,
getAllGenericPasswordServices,
getAllGenericPasswords,
getInternetCredentials,
getSupportedBiometryType,
hasInternetCredentials,
Expand Down Expand Up @@ -105,6 +106,9 @@ getGenericPassword('service');
getAllGenericPasswordServices().then((result) => {
(result: string[]);
});
getAllGenericPasswords().then((result) => {
(result: UserCredentials[]);
});

resetGenericPassword().then((result) => {
(result: boolean);
Expand Down
2 changes: 2 additions & 0 deletions typings/react-native-keychain.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ declare module 'react-native-keychain' {

function getAllGenericPasswordServices(): Promise<string[]>;

function getAllGenericPasswords(options?: Options): Promise<UserCredentials[]>;

function hasInternetCredentials(server: string): Promise<false | Result>;

function setInternetCredentials(
Expand Down