Skip to content
Closed

Dev #10

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
486 changes: 362 additions & 124 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions Shield.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ Pod::Spec.new do |s|
s.private_header_files = "ios/**/*.h"

s.dependency "TrustKit"
s.frameworks = "DeviceCheck"
install_modules_dependencies(s)
end
1 change: 1 addition & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,5 @@ dependencies {
implementation "com.squareup.okhttp3:okhttp:4.9.2"
implementation "androidx.security:security-crypto:1.1.0-alpha06"
implementation "androidx.biometric:biometric:1.2.0-alpha05"
implementation "com.google.android.play:integrity:1.4.0"
}
93 changes: 93 additions & 0 deletions android/src/main/java/com/shield/ShieldModule.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.shield

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.Arguments
import java.io.File
import java.io.BufferedReader
import java.io.InputStreamReader
Expand All @@ -14,9 +15,12 @@ import android.content.ClipboardManager
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.core.content.ContextCompat
import com.facebook.react.bridge.UiThreadUtil
import com.google.android.play.core.integrity.IntegrityManagerFactory
import com.google.android.play.core.integrity.IntegrityTokenRequest

class ShieldModule(reactContext: ReactApplicationContext) :
NativeShieldSpec(reactContext) {
Expand Down Expand Up @@ -267,6 +271,62 @@ class ShieldModule(reactContext: ReactApplicationContext) :
}
}

override fun getAllSecureKeys(promise: com.facebook.react.bridge.Promise) {
try {
val keys = Arguments.createArray()
for (key in getEncryptedSharedPreferences().all.keys) {
keys.pushString(key)
}
promise.resolve(keys)
} catch (e: Exception) {
promise.reject("SECURE_STORAGE_ERROR", e)
}
}

override fun clearAllSecureStorage(promise: com.facebook.react.bridge.Promise) {
try {
getEncryptedSharedPreferences().edit().clear().apply()
promise.resolve(true)
} catch (e: Exception) {
promise.reject("SECURE_STORAGE_ERROR", e)
}
}

override fun getBiometricStrength(promise: com.facebook.react.bridge.Promise) {
val manager = BiometricManager.from(reactApplicationContext)
when {
manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG) == BiometricManager.BIOMETRIC_SUCCESS ->
promise.resolve("strong")
manager.canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS ->
promise.resolve("weak")
else -> promise.resolve("none")
}
}

override fun getRootReasons(): com.facebook.react.bridge.WritableArray {
val reasons = Arguments.createArray()
if (checkRootMethod1()) reasons.pushString("build_tags")
if (checkRootMethod2()) reasons.pushString("su_binary")
if (checkRootMethod3()) reasons.pushString("su_command")
if (checkDangerousPackages()) reasons.pushString("dangerous_packages")
if (checkSystemMountFlags()) reasons.pushString("mount_flags")
return reasons
}

override fun requestIntegrityToken(nonce: String, promise: com.facebook.react.bridge.Promise) {
try {
val integrityManager = IntegrityManagerFactory.create(reactApplicationContext)
val request = IntegrityTokenRequest.builder()
.setNonce(nonce)
.build()
integrityManager.requestIntegrityToken(request)
.addOnSuccessListener { response -> promise.resolve(response.token()) }
.addOnFailureListener { e -> promise.reject("INTEGRITY_ERROR", e.message, e) }
} catch (e: Exception) {
promise.reject("INTEGRITY_ERROR", e.message, e)
}
}

private fun checkRootMethod1(): Boolean {
val buildTags = android.os.Build.TAGS
return buildTags != null && buildTags.contains("test-keys")
Expand Down Expand Up @@ -304,6 +364,39 @@ class ShieldModule(reactContext: ReactApplicationContext) :
}
}

private fun checkDangerousPackages(): Boolean {
val suspiciousPackages = arrayOf(
"com.topjohnwu.magisk",
"eu.chainfire.supersu",
"com.koushikdutta.superuser",
"com.noshufou.android.su",
"com.thirdparty.superuser",
"com.yellowes.su",
"com.zachspong.temprootremovejb",
"com.ramdroid.appquarantine"
)
val pm = reactApplicationContext.packageManager
for (pkg in suspiciousPackages) {
try {
pm.getPackageInfo(pkg, 0)
return true
} catch (_: PackageManager.NameNotFoundException) {}
}
return false
}

private fun checkSystemMountFlags(): Boolean {
try {
val reader = BufferedReader(InputStreamReader(File("/proc/mounts").inputStream()))
var line: String?
while (reader.readLine().also { line = it } != null) {
val l = line ?: continue
if (l.contains("/system") && l.contains(" rw,")) return true
}
} catch (_: Exception) {}
return false
}

companion object {
const val NAME = "Shield"
}
Expand Down
170 changes: 159 additions & 11 deletions ios/Shield.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#import "Shield.h"
#import <DeviceCheck/DeviceCheck.h>
#import <LocalAuthentication/LocalAuthentication.h>
#import <TrustKit/TrustKit.h>
#import <UIKit/UIKit.h>
Expand Down Expand Up @@ -218,18 +219,20 @@
publicKeyHashes:(NSArray *)publicKeyHashes
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
// TrustKit does not support runtime reconfiguration without exception.
// In a fully dynamic scenario, developers should store pins in JS and pass
// them to addSSLPinning on app launch. This method serves as a stub
// to align with the Android API where OkHttp allows factory overrides.
resolve(nil);
// TrustKit does not support runtime reconfiguration after initialization.
// To update pins, store the new hashes and call addSSLPinning on next app
// launch. On Android, OkHttp factory overrides allow runtime updates.
reject(@"SSL_PIN_UPDATE_UNSUPPORTED",
@"TrustKit pins cannot be updated at runtime on iOS. "
@"Store updated hashes and call addSSLPinning on the next app launch.",
nil);
}

- (void)preventScreenshot:(BOOL)prevent
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
dispatch_async(dispatch_get_main_queue(), ^{
UIWindow *window = [UIApplication sharedApplication].keyWindow;

Check warning on line 235 in ios/Shield.mm

View workflow job for this annotation

GitHub Actions / build-ios

'keyWindow' is deprecated: first deprecated in iOS 13.0 - Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes [-Wdeprecated-declarations]

Check warning on line 235 in ios/Shield.mm

View workflow job for this annotation

GitHub Actions / build-ios

'keyWindow' is deprecated: first deprecated in iOS 13.0 - Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes [-Wdeprecated-declarations]
if (!window) {
reject(@"NO_WINDOW", @"Key window is null", nil);
return;
Expand Down Expand Up @@ -301,7 +304,7 @@

// Background Blur Logic
- (void)appWillResignActive {
UIWindow *window = [UIApplication sharedApplication].keyWindow;

Check warning on line 307 in ios/Shield.mm

View workflow job for this annotation

GitHub Actions / build-ios

'keyWindow' is deprecated: first deprecated in iOS 13.0 - Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes [-Wdeprecated-declarations]

Check warning on line 307 in ios/Shield.mm

View workflow job for this annotation

GitHub Actions / build-ios

'keyWindow' is deprecated: first deprecated in iOS 13.0 - Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes [-Wdeprecated-declarations]
if (![window viewWithTag:12345]) {
UIBlurEffect *blurEffect =
[UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
Expand All @@ -316,19 +319,13 @@
}

- (void)appDidBecomeActive {
UIWindow *window = [UIApplication sharedApplication].keyWindow;

Check warning on line 322 in ios/Shield.mm

View workflow job for this annotation

GitHub Actions / build-ios

'keyWindow' is deprecated: first deprecated in iOS 13.0 - Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes [-Wdeprecated-declarations]

Check warning on line 322 in ios/Shield.mm

View workflow job for this annotation

GitHub Actions / build-ios

'keyWindow' is deprecated: first deprecated in iOS 13.0 - Should not be used for applications that support multiple scenes as it returns a key window across all connected scenes [-Wdeprecated-declarations]
UIView *blurView = [window viewWithTag:12345];
if (blurView) {
[blurView removeFromSuperview];
}
}

- (void)appDidBecomeActive {
UIWindow *window = [UIApplication sharedApplication].keyWindow;
UIView *blurView = [window viewWithTag:12345];
[blurView removeFromSuperview];
}

// Secure Storage Implementation (Keychain)

- (void)setSecureString:(NSString *)key
Expand Down Expand Up @@ -420,6 +417,157 @@
}
}

// ─── Secure Storage Helpers ───────────────────────────────────────────────────

- (void)getAllSecureKeys:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service,
(__bridge id)kSecReturnAttributes : @YES,
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll
};

CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);

if (status == errSecSuccess) {
NSArray *items = (__bridge_transfer NSArray *)result;
NSMutableArray *keys = [NSMutableArray array];
for (NSDictionary *item in items) {
NSString *account = item[(__bridge id)kSecAttrAccount];
if (account) [keys addObject:account];
}
resolve(keys);
} else if (status == errSecItemNotFound) {
resolve(@[]);
} else {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:status
userInfo:nil];
reject(@"SECURE_STORAGE_ERROR", @"Failed to enumerate keys", error);
}
}

- (void)clearAllSecureStorage:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
NSString *service = [[NSBundle mainBundle] bundleIdentifier];
NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : service
};

OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
if (status == errSecSuccess || status == errSecItemNotFound) {
resolve(@(YES));
} else {
NSError *error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:status
userInfo:nil];
reject(@"SECURE_STORAGE_ERROR", @"Failed to clear storage", error);
}
}

// ─── Biometric Strength ───────────────────────────────────────────────────────

- (void)getBiometricStrength:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
LAContext *context = [[LAContext alloc] init];
NSError *error = nil;
if ([context canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
error:&error]) {
// On iOS, Face ID and Touch ID are both classified as strong biometrics.
resolve(@"strong");
} else {
resolve(@"none");
}
}

// ─── Jailbreak Reason Codes ──────────────────────────────────────────────────

- (NSArray *)collectJailbreakReasons {
NSMutableArray *reasons = [NSMutableArray array];

NSArray *jailbreakPaths = @[
@"/Applications/Cydia.app",
@"/Applications/Sileo.app",
@"/Applications/Zebra.app",
@"/Library/MobileSubstrate/MobileSubstrate.dylib",
@"/bin/bash",
@"/usr/sbin/sshd",
@"/etc/apt",
@"/private/var/lib/apt/",
@"/usr/bin/ssh",
];
for (NSString *path in jailbreakPaths) {
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) {
[reasons addObject:@"jailbreak_files"];
break;
}
}

NSError *writeError;
[@"check" writeToFile:@"/private/jailbreak_rw_test.txt"
atomically:YES
encoding:NSUTF8StringEncoding
error:&writeError];
if (!writeError) {
[reasons addObject:@"sandbox_escape"];
[[NSFileManager defaultManager] removeItemAtPath:@"/private/jailbreak_rw_test.txt" error:nil];
}

if ([[UIApplication sharedApplication]
canOpenURL:[NSURL URLWithString:@"cydia://package/com.example.package"]]) {
[reasons addObject:@"cydia_scheme"];
}

uint32_t count = _dyld_image_count();
for (uint32_t i = 0; i < count; i++) {
const char *name = _dyld_get_image_name(i);
if (name) {
NSString *imageName = [NSString stringWithUTF8String:name];
if ([imageName containsString:@"MobileSubstrate"] ||
[imageName containsString:@"Substrate"]) {
[reasons addObject:@"substrate_loaded"];
break;
}
}
}

return [reasons copy];
}

- (NSArray *)getRootReasons {
return [self collectJailbreakReasons];
}

// ─── Platform Integrity Attestation ─────────────────────────────────────────

- (void)requestIntegrityToken:(NSString *)nonce
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject {
if (@available(iOS 11.0, *)) {
DCDevice *device = [DCDevice currentDevice];
if (![device isSupported]) {
reject(@"INTEGRITY_NOT_SUPPORTED",
@"DeviceCheck is not supported on this device", nil);
return;
}
[device generateTokenWithCompletionHandler:^(NSData *token, NSError *error) {
if (error) {
reject(@"INTEGRITY_ERROR", error.localizedDescription, error);
} else {
resolve([token base64EncodedStringWithOptions:0]);
}
}];
} else {
reject(@"INTEGRITY_NOT_SUPPORTED", @"DeviceCheck requires iOS 11+", nil);
}
}

// ─────────────────────────────────────────────────────────────────────────────

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeShieldSpecJSI>(params);
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@think-grid-labs/react-native-shield",
"version": "0.1.0",
"version": "0.2.1",
"description": "All-in-one security suite",
"main": "./lib/module/index.js",
"types": "./lib/typescript/src/index.d.ts",
Expand Down Expand Up @@ -160,4 +160,4 @@
],
"version": "0.57.0"
}
}
}
13 changes: 13 additions & 0 deletions src/NativeShield.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@ export interface Spec extends TurboModule {
setSecureString(key: string, value: string): Promise<boolean>;
getSecureString(key: string): Promise<string | null>;
removeSecureString(key: string): Promise<boolean>;

// Enhanced integrity detection
getRootReasons(): string[];

// Secure storage helpers
getAllSecureKeys(): Promise<string[]>;
clearAllSecureStorage(): Promise<boolean>;

// Biometric capability
getBiometricStrength(): Promise<string>;

// Platform attestation
requestIntegrityToken(nonce: string): Promise<string>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('Shield');
Loading
Loading