react-native-safeguard
Version:
A comprehensive security library for React Native applications that helps protect against various security threats including root detection, malware, tampering, and more.
194 lines (163 loc) • 7.8 kB
JavaScript
const {
withMainActivity,
withAppDelegate,
createRunOncePlugin,
} = require('@expo/config-plugins');
const {
mergeContents,
} = require('@expo/config-plugins/build/utils/generateCode');
/**
* Formats a Java/Kotlin map entry from a JS object
*/
function formatSecurityConfigMap(configMap, isKotlin = false) {
const entries = Object.entries(configMap)
.map(([key, value]) =>
isKotlin ? `"${key}" to "${value}"` : `"${key}", "${value}"`
)
.join(',\n\t\t');
return isKotlin
? `\toverride val securityConfigMap: Map<String, String> = mapOf(\n\t\t${entries}\n\t)`
: `\t@Override\n public Map<String, String> securityConfigMap = new HashMap<String, String>() {{\n ${entries};\n }};`;
}
function withSafeguardAndroid(_config, { securityConfigAndroid = {} } = {}) {
return withMainActivity(_config, async (config) => {
const isKotlin = config.modResults.language === 'kt';
const activitySuperClass = isKotlin
? 'class MainActivity : com.safeguard.SafeguardReactActivity() {'
: 'public class MainActivity extends com.safeguard.SafeguardReactActivity {';
const activityClassMatcher = isKotlin
? 'class MainActivity : ReactActivity() {'
: /public class MainActivity extends (.+) {/;
config.modResults.contents = config.modResults.contents.replace(
activityClassMatcher,
activitySuperClass
);
// Add securityConfigMap override
const securityConfigSnippet = formatSecurityConfigMap(
securityConfigAndroid,
isKotlin
);
config.modResults.contents = mergeContents({
tag: 'safeguard-android/security-config',
src: config.modResults.contents,
newSrc: securityConfigSnippet,
anchor: /override fun onCreate(.+) {/, // Insert before onCreate
offset: 0,
comment: '//',
}).contents;
return config;
});
}
function withSafeguardIOS(_config, { securityConfigiOS = {} } = {}) {
return withAppDelegate(_config, async (config) => {
const securityLevelDefaults = {
ROOT_CHECK_STATE: 'DISABLED',
DEVELOPER_OPTIONS_CHECK_STATE: 'WARNING',
SIGNATURE_VERIFICATION_CHECK_STATE: 'WARNING',
NETWORK_SECURITY_CHECK_STATE: 'WARNING',
SCREEN_SHARING_CHECK_STATE: 'WARNING',
APP_SPOOFING_CHECK_STATE: 'WARNING',
REVERSE_ENGINEERING_CHECK_STATE: 'WARNING',
KEYLOGGER_CHECK_STATE: 'WARNING',
ONGOING_CALL_CHECK_STATE: 'WARNING',
CERTIFICATE_MATCHING_CHECK_STATE: 'WARNING',
SIGNATURE_ERROR_DEBUG: false,
EXPECTED_SIGNATURE: '',
EXPECTED_PACKAGE_NAME: '',
CRITICAL_DIALOG_TITLE: 'Security Alert',
WARNING_DIALOG_TITLE: 'Security Warning',
CRITICAL_DIALOG_POSITIVE_BUTTON: 'Quit',
WARNING_DIALOG_POSITIVE_BUTTON: 'Continue',
CRITICAL_DIALOG_NEGATIVE_BUTTON: 'Cancel',
WARNING_DIALOG_NEGATIVE_BUTTON: 'Cancel',
};
const securityLevelMap = {
DISABLED: 'SGSecurityLevelDisable',
WARNING: 'SGSecurityLevelWarning',
ERROR: 'SGSecurityLevelError',
};
const finalLevels = { ...securityLevelDefaults, ...securityConfigiOS };
// Add import
config.modResults.contents = mergeContents({
tag: 'safeguard-ios/import',
src: config.modResults.contents,
newSrc: '#import <SafeGuardiOS/SGSecurityChecker.h>',
anchor: /#import "AppDelegate.h"/,
offset: 1,
comment: '//',
}).contents;
// Add applicationDidBecomeActive method
const applicationDidBecomeActiveMethod = `-(void)applicationDidBecomeActive:(UIApplication *)application {
[self.securityChecker performAllSecurityChecks];
}`;
config.modResults.contents = mergeContents({
tag: 'safeguard-ios/application-did-become-active',
src: config.modResults.contents,
newSrc: applicationDidBecomeActiveMethod,
anchor: /@implementation AppDelegate/,
offset: 1,
comment: '//',
}).contents;
// Add property declaration
const propertyDeclaration =
'@interface AppDelegate ()\n\n@property(nonatomic, strong) SGSecurityChecker *securityChecker;\n\n@end';
config.modResults.contents = mergeContents({
tag: 'safeguard-ios/property',
src: config.modResults.contents,
newSrc: propertyDeclaration,
anchor: /@implementation AppDelegate/,
offset: 0,
comment: '//',
}).contents;
// Add security configuration
const securitySetup = `
SGSecurityConfiguration *securityConfig = [[SGSecurityConfiguration alloc] init];
securityConfig.rootDetectionLevel = ${securityLevelMap[finalLevels.ROOT_CHECK_STATE]};
securityConfig.developerOptionsLevel = ${securityLevelMap[finalLevels.DEVELOPER_OPTIONS_CHECK_STATE]};
securityConfig.signatureVerificationLevel = ${securityLevelMap[finalLevels.SIGNATURE_VERIFICATION_CHECK_STATE]};
securityConfig.signatureErrorDebug = ${finalLevels.SIGNATURE_ERROR_DEBUG ? 'YES' : 'NO'};
securityConfig.networkSecurityLevel = ${securityLevelMap[finalLevels.NETWORK_SECURITY_CHECK_STATE]};
securityConfig.screenSharingLevel = ${securityLevelMap[finalLevels.SCREEN_SHARING_CHECK_STATE]};
securityConfig.spoofingLevel = ${securityLevelMap[finalLevels.APP_SPOOFING_CHECK_STATE]};
securityConfig.reverseEngineerLevel = ${securityLevelMap[finalLevels.REVERSE_ENGINEERING_CHECK_STATE]};
securityConfig.keyLoggersLevel = ${securityLevelMap[finalLevels.KEYLOGGER_CHECK_STATE]};
securityConfig.audioCallLevel = ${securityLevelMap[finalLevels.ONGOING_CALL_CHECK_STATE]};
securityConfig.expectedBundleIdentifier = @"${finalLevels.EXPECTED_BUNDLE_IDENTIFIER}";
securityConfig.expectedSignature = @"${finalLevels.EXPECTED_SIGNATURE}";
self.securityChecker = [[SGSecurityChecker alloc] initWithConfiguration:securityConfig];
self.securityChecker.alertHandler = ^(NSString *title, NSString *message, SGSecurityLevel level, void(^completion)(BOOL shouldQuit)) {
dispatch_async(dispatch_get_main_queue(), ^{
NSString *alertTitle = level == SGSecurityLevelError ? @"${finalLevels.CRITICAL_DIALOG_TITLE}" : @"${finalLevels.WARNING_DIALOG_TITLE}";
NSString *positiveButtonTitle = level == SGSecurityLevelError ? @"${finalLevels.CRITICAL_DIALOG_POSITIVE_BUTTON}" : @"${finalLevels.WARNING_DIALOG_POSITIVE_BUTTON}";
UIAlertController *alert = [UIAlertController alertControllerWithTitle:alertTitle
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:positiveButtonTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
if (completion) {
completion(level == SGSecurityLevelError);
}
}];
[alert addAction:okAction];
UIViewController *rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
[rootViewController presentViewController:alert animated:YES completion:nil];
});
};`;
config.modResults.contents = mergeContents({
tag: 'safeguard-ios/security-setup',
src: config.modResults.contents,
newSrc: securitySetup,
anchor: /self\.initialProps = @{};/,
offset: 1,
comment: '//',
}).contents;
return config;
});
}
const withSafeguard = (config, props = {}) => {
config = withSafeguardAndroid(config, props);
config = withSafeguardIOS(config, props);
return config;
};
module.exports = createRunOncePlugin(withSafeguard, 'withSafeguard', '1.0.0');