react-native-sop-docs/08-security/secure-storage.md

471 lines
12 KiB
Markdown

# Security
## Secure Storage
### Installation
```bash
yarn add react-native-keychain
```
### Secure Storage Implementation
```typescript
// src/utils/secureStorage.ts
import * as Keychain from 'react-native-keychain';
export class SecureStorage {
static async setItem(key: string, value: string): Promise<void> {
try {
await Keychain.setInternetCredentials(key, key, value);
} catch (error) {
console.error('Error storing secure item:', error);
throw error;
}
}
static async getItem(key: string): Promise<string | null> {
try {
const credentials = await Keychain.getInternetCredentials(key);
return credentials ? credentials.password : null;
} catch (error) {
console.error('Error retrieving secure item:', error);
return null;
}
}
static async removeItem(key: string): Promise<void> {
try {
await Keychain.resetInternetCredentials(key);
} catch (error) {
console.error('Error removing secure item:', error);
throw error;
}
}
static async clear(): Promise<void> {
try {
await Keychain.resetGenericPassword();
} catch (error) {
console.error('Error clearing secure storage:', error);
throw error;
}
}
// Biometric authentication
static async setItemWithBiometrics(key: string, value: string): Promise<void> {
try {
await Keychain.setInternetCredentials(key, key, value, {
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
authenticationType: Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS,
});
} catch (error) {
console.error('Error storing item with biometrics:', error);
throw error;
}
}
static async getItemWithBiometrics(key: string): Promise<string | null> {
try {
const credentials = await Keychain.getInternetCredentials(key, {
authenticationType: Keychain.AUTHENTICATION_TYPE.DEVICE_PASSCODE_OR_BIOMETRICS,
showModal: true,
kLocalizedFallbackTitle: 'Please use your passcode',
});
return credentials ? credentials.password : null;
} catch (error) {
console.error('Error retrieving item with biometrics:', error);
return null;
}
}
}
```
## Data Encryption
### Encryption Utilities
```typescript
// src/utils/encryption.ts
import CryptoJS from 'crypto-js';
export class EncryptionService {
private static readonly SECRET_KEY = 'your-secret-key-here'; // Should be from secure config
static encrypt(text: string): string {
try {
return CryptoJS.AES.encrypt(text, this.SECRET_KEY).toString();
} catch (error) {
console.error('Encryption error:', error);
throw new Error('Failed to encrypt data');
}
}
static decrypt(encryptedText: string): string {
try {
const bytes = CryptoJS.AES.decrypt(encryptedText, this.SECRET_KEY);
return bytes.toString(CryptoJS.enc.Utf8);
} catch (error) {
console.error('Decryption error:', error);
throw new Error('Failed to decrypt data');
}
}
static hash(text: string): string {
return CryptoJS.SHA256(text).toString();
}
static generateSalt(): string {
return CryptoJS.lib.WordArray.random(128/8).toString();
}
static hashWithSalt(text: string, salt: string): string {
return CryptoJS.SHA256(text + salt).toString();
}
}
```
## API Security
### Request Interceptors
```typescript
// src/services/secureAPI.ts
import axios, {AxiosRequestConfig, AxiosResponse} from 'axios';
import {SecureStorage} from '@utils/secureStorage';
import {EncryptionService} from '@utils/encryption';
const secureAPI = axios.create({
baseURL: 'https://api.saayam.com',
timeout: 10000,
});
// Request interceptor for authentication
secureAPI.interceptors.request.use(
async (config: AxiosRequestConfig) => {
// Add authentication token
const token = await SecureStorage.getItem('auth_token');
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
// Add request signature
const timestamp = Date.now().toString();
const signature = EncryptionService.hash(
`${config.method}${config.url}${timestamp}`
);
config.headers = {
...config.headers,
'X-Timestamp': timestamp,
'X-Signature': signature,
};
// Encrypt sensitive data
if (config.data && config.method === 'post') {
config.data = {
...config.data,
encrypted: true,
payload: EncryptionService.encrypt(JSON.stringify(config.data)),
};
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor for decryption
secureAPI.interceptors.response.use(
(response: AxiosResponse) => {
// Decrypt response if needed
if (response.data?.encrypted) {
try {
response.data = JSON.parse(
EncryptionService.decrypt(response.data.payload)
);
} catch (error) {
console.error('Failed to decrypt response:', error);
}
}
return response;
},
async (error) => {
// Handle token refresh
if (error.response?.status === 401) {
try {
const refreshToken = await SecureStorage.getItem('refresh_token');
if (refreshToken) {
const response = await axios.post('/auth/refresh', {
refreshToken,
});
const newToken = response.data.token;
await SecureStorage.setItem('auth_token', newToken);
// Retry original request
error.config.headers.Authorization = `Bearer ${newToken}`;
return secureAPI.request(error.config);
}
} catch (refreshError) {
// Redirect to login
await SecureStorage.clear();
// Navigate to login screen
}
}
return Promise.reject(error);
}
);
export default secureAPI;
```
## Input Validation
### Validation Schemas
```typescript
// src/utils/validation.ts
import * as yup from 'yup';
export const validationSchemas = {
email: yup
.string()
.email('Invalid email format')
.required('Email is required')
.matches(
/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/,
'Invalid email format'
),
password: yup
.string()
.required('Password is required')
.min(8, 'Password must be at least 8 characters')
.matches(
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
'Password must contain uppercase, lowercase, number and special character'
),
phone: yup
.string()
.required('Phone number is required')
.matches(
/^\+?[1-9]\d{1,14}$/,
'Invalid phone number format'
),
amount: yup
.number()
.required('Amount is required')
.positive('Amount must be positive')
.max(1000000, 'Amount cannot exceed 1,000,000'),
};
export const sanitizeInput = (input: string): string => {
return input
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
.replace(/[<>]/g, '')
.trim();
};
export const validateAndSanitize = (
value: string,
schema: yup.StringSchema
): {isValid: boolean; sanitized: string; error?: string} => {
const sanitized = sanitizeInput(value);
try {
schema.validateSync(sanitized);
return {isValid: true, sanitized};
} catch (error) {
return {
isValid: false,
sanitized,
error: error instanceof Error ? error.message : 'Validation failed',
};
}
};
```
## Biometric Authentication
### Biometric Setup
```typescript
// src/utils/biometricAuth.ts
import TouchID from 'react-native-touch-id';
import {Alert, Platform} from 'react-native';
export class BiometricAuth {
static async isSupported(): Promise<boolean> {
try {
const biometryType = await TouchID.isSupported();
return !!biometryType;
} catch (error) {
return false;
}
}
static async getSupportedType(): Promise<string | null> {
try {
const biometryType = await TouchID.isSupported();
return biometryType;
} catch (error) {
return null;
}
}
static async authenticate(reason: string = 'Authenticate to continue'): Promise<boolean> {
try {
const optionalConfigObject = {
title: 'Authentication Required',
subtitle: reason,
description: 'This app uses biometric authentication to protect your data',
fallbackLabel: 'Use Passcode',
cancelLabel: 'Cancel',
disableDeviceFallback: false,
showModal: true,
kLocalizedFallbackTitle: 'Use Passcode',
};
await TouchID.authenticate(reason, optionalConfigObject);
return true;
} catch (error: any) {
console.error('Biometric authentication failed:', error);
if (error.name === 'LAErrorUserFallback') {
// User chose to use passcode
return this.authenticateWithPasscode();
}
return false;
}
}
private static async authenticateWithPasscode(): Promise<boolean> {
return new Promise((resolve) => {
Alert.prompt(
'Enter Passcode',
'Please enter your device passcode',
[
{text: 'Cancel', onPress: () => resolve(false)},
{
text: 'OK',
onPress: (passcode) => {
// In a real app, you would validate the passcode
resolve(!!passcode);
},
},
],
'secure-text'
);
});
}
}
```
## Certificate Pinning
### SSL Pinning Implementation
```typescript
// src/utils/certificatePinning.ts
import {NetworkingModule} from 'react-native';
export class CertificatePinning {
private static readonly PINNED_CERTIFICATES = [
'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=', // Your server's certificate hash
];
static setupPinning() {
if (Platform.OS === 'ios') {
// iOS certificate pinning setup
NetworkingModule.addRequestInterceptor((request: any) => {
request.trusty = {
hosts: [
{
host: 'api.saayam.com',
certificates: this.PINNED_CERTIFICATES,
},
],
};
return request;
});
}
}
static validateCertificate(hostname: string, certificate: string): boolean {
return this.PINNED_CERTIFICATES.includes(certificate);
}
}
```
## Security Headers
### Request Security Headers
```typescript
// src/utils/securityHeaders.ts
export const getSecurityHeaders = () => ({
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Strict-Transport-Security': 'max-age=31536000; includeSubDomains',
'Content-Security-Policy': "default-src 'self'",
'Referrer-Policy': 'strict-origin-when-cross-origin',
});
```
## Security Best Practices
### Security Checklist
```typescript
// src/utils/securityAudit.ts
export class SecurityAudit {
static performSecurityCheck(): {passed: boolean; issues: string[]} {
const issues: string[] = [];
// Check for debug mode in production
if (__DEV__ && process.env.NODE_ENV === 'production') {
issues.push('Debug mode is enabled in production');
}
// Check for console logs in production
if (process.env.NODE_ENV === 'production' && console.log.toString().includes('native code')) {
issues.push('Console logs are not disabled in production');
}
// Check for secure storage usage
if (!this.isUsingSecureStorage()) {
issues.push('Sensitive data is not stored securely');
}
// Check for HTTPS usage
if (!this.isUsingHTTPS()) {
issues.push('API calls are not using HTTPS');
}
return {
passed: issues.length === 0,
issues,
};
}
private static isUsingSecureStorage(): boolean {
// Check if secure storage is properly implemented
return true; // Implement actual check
}
private static isUsingHTTPS(): boolean {
// Check if all API endpoints use HTTPS
return true; // Implement actual check
}
}
```