native-update 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapacitorNativeUpdate.podspec +18 -0
- package/LICENSE +21 -0
- package/Readme.md +451 -0
- package/android/build.gradle +92 -0
- package/android/gradle/wrapper/gradle-wrapper.properties +8 -0
- package/android/gradle.properties +17 -0
- package/android/proguard-rules.pro +29 -0
- package/android/settings.gradle +2 -0
- package/android/src/main/AndroidManifest.xml +34 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/AppReviewPlugin.kt +153 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/AppUpdatePlugin.kt +275 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundNotificationManager.kt +390 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateManager.kt +46 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdatePlugin.kt +333 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/BackgroundUpdateWorker.kt +251 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/CapacitorNativeUpdatePlugin.kt +265 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/LiveUpdatePlugin.kt +526 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/NotificationActionReceiver.kt +99 -0
- package/android/src/main/java/com/aoneahsan/nativeupdate/SecurityManager.kt +249 -0
- package/dist/esm/__tests__/bundle-manager.test.d.ts +1 -0
- package/dist/esm/__tests__/bundle-manager.test.js +123 -0
- package/dist/esm/__tests__/bundle-manager.test.js.map +1 -0
- package/dist/esm/__tests__/config.test.d.ts +1 -0
- package/dist/esm/__tests__/config.test.js +69 -0
- package/dist/esm/__tests__/config.test.js.map +1 -0
- package/dist/esm/__tests__/integration.test.d.ts +1 -0
- package/dist/esm/__tests__/integration.test.js +78 -0
- package/dist/esm/__tests__/integration.test.js.map +1 -0
- package/dist/esm/__tests__/security.test.d.ts +1 -0
- package/dist/esm/__tests__/security.test.js +54 -0
- package/dist/esm/__tests__/security.test.js.map +1 -0
- package/dist/esm/__tests__/version-manager.test.d.ts +1 -0
- package/dist/esm/__tests__/version-manager.test.js +45 -0
- package/dist/esm/__tests__/version-manager.test.js.map +1 -0
- package/dist/esm/app-review/app-review-manager.d.ts +24 -0
- package/dist/esm/app-review/app-review-manager.js +195 -0
- package/dist/esm/app-review/app-review-manager.js.map +1 -0
- package/dist/esm/app-review/index.d.ts +5 -0
- package/dist/esm/app-review/index.js +6 -0
- package/dist/esm/app-review/index.js.map +1 -0
- package/dist/esm/app-review/platform-review-handler.d.ts +20 -0
- package/dist/esm/app-review/platform-review-handler.js +138 -0
- package/dist/esm/app-review/platform-review-handler.js.map +1 -0
- package/dist/esm/app-review/review-conditions-checker.d.ts +22 -0
- package/dist/esm/app-review/review-conditions-checker.js +155 -0
- package/dist/esm/app-review/review-conditions-checker.js.map +1 -0
- package/dist/esm/app-review/review-rate-limiter.d.ts +23 -0
- package/dist/esm/app-review/review-rate-limiter.js +164 -0
- package/dist/esm/app-review/review-rate-limiter.js.map +1 -0
- package/dist/esm/app-review/types.d.ts +41 -0
- package/dist/esm/app-review/types.js +2 -0
- package/dist/esm/app-review/types.js.map +1 -0
- package/dist/esm/app-update/app-update-checker.d.ts +13 -0
- package/dist/esm/app-update/app-update-checker.js +104 -0
- package/dist/esm/app-update/app-update-checker.js.map +1 -0
- package/dist/esm/app-update/app-update-installer.d.ts +19 -0
- package/dist/esm/app-update/app-update-installer.js +123 -0
- package/dist/esm/app-update/app-update-installer.js.map +1 -0
- package/dist/esm/app-update/app-update-manager.d.ts +28 -0
- package/dist/esm/app-update/app-update-manager.js +199 -0
- package/dist/esm/app-update/app-update-manager.js.map +1 -0
- package/dist/esm/app-update/app-update-notifier.d.ts +14 -0
- package/dist/esm/app-update/app-update-notifier.js +100 -0
- package/dist/esm/app-update/app-update-notifier.js.map +1 -0
- package/dist/esm/app-update/index.d.ts +6 -0
- package/dist/esm/app-update/index.js +7 -0
- package/dist/esm/app-update/index.js.map +1 -0
- package/dist/esm/app-update/platform-app-update.d.ts +19 -0
- package/dist/esm/app-update/platform-app-update.js +129 -0
- package/dist/esm/app-update/platform-app-update.js.map +1 -0
- package/dist/esm/app-update/types.d.ts +58 -0
- package/dist/esm/app-update/types.js +12 -0
- package/dist/esm/app-update/types.js.map +1 -0
- package/dist/esm/background-update/background-scheduler.d.ts +17 -0
- package/dist/esm/background-update/background-scheduler.js +195 -0
- package/dist/esm/background-update/background-scheduler.js.map +1 -0
- package/dist/esm/background-update/index.d.ts +3 -0
- package/dist/esm/background-update/index.js +3 -0
- package/dist/esm/background-update/index.js.map +1 -0
- package/dist/esm/background-update/notification-manager.d.ts +29 -0
- package/dist/esm/background-update/notification-manager.js +89 -0
- package/dist/esm/background-update/notification-manager.js.map +1 -0
- package/dist/esm/core/analytics.d.ts +70 -0
- package/dist/esm/core/analytics.js +137 -0
- package/dist/esm/core/analytics.js.map +1 -0
- package/dist/esm/core/cache-manager.d.ts +72 -0
- package/dist/esm/core/cache-manager.js +275 -0
- package/dist/esm/core/cache-manager.js.map +1 -0
- package/dist/esm/core/config.d.ts +48 -0
- package/dist/esm/core/config.js +83 -0
- package/dist/esm/core/config.js.map +1 -0
- package/dist/esm/core/errors.d.ts +51 -0
- package/dist/esm/core/errors.js +80 -0
- package/dist/esm/core/errors.js.map +1 -0
- package/dist/esm/core/logger.d.ts +21 -0
- package/dist/esm/core/logger.js +109 -0
- package/dist/esm/core/logger.js.map +1 -0
- package/dist/esm/core/performance.d.ts +53 -0
- package/dist/esm/core/performance.js +140 -0
- package/dist/esm/core/performance.js.map +1 -0
- package/dist/esm/core/plugin-manager.d.ts +66 -0
- package/dist/esm/core/plugin-manager.js +148 -0
- package/dist/esm/core/plugin-manager.js.map +1 -0
- package/dist/esm/core/security.d.ts +93 -0
- package/dist/esm/core/security.js +315 -0
- package/dist/esm/core/security.js.map +1 -0
- package/dist/esm/definitions.d.ts +639 -0
- package/dist/esm/definitions.js +103 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +12 -0
- package/dist/esm/index.js +16 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/live-update/bundle-manager.d.ts +94 -0
- package/dist/esm/live-update/bundle-manager.js +310 -0
- package/dist/esm/live-update/bundle-manager.js.map +1 -0
- package/dist/esm/live-update/certificate-pinning.d.ts +38 -0
- package/dist/esm/live-update/certificate-pinning.js +78 -0
- package/dist/esm/live-update/certificate-pinning.js.map +1 -0
- package/dist/esm/live-update/download-manager.d.ts +67 -0
- package/dist/esm/live-update/download-manager.js +319 -0
- package/dist/esm/live-update/download-manager.js.map +1 -0
- package/dist/esm/live-update/update-manager.d.ts +52 -0
- package/dist/esm/live-update/update-manager.js +294 -0
- package/dist/esm/live-update/update-manager.js.map +1 -0
- package/dist/esm/live-update/version-manager.d.ts +84 -0
- package/dist/esm/live-update/version-manager.js +335 -0
- package/dist/esm/live-update/version-manager.js.map +1 -0
- package/dist/esm/plugin.d.ts +6 -0
- package/dist/esm/plugin.js +283 -0
- package/dist/esm/plugin.js.map +1 -0
- package/dist/esm/security/crypto.d.ts +25 -0
- package/dist/esm/security/crypto.js +70 -0
- package/dist/esm/security/crypto.js.map +1 -0
- package/dist/esm/security/validator.d.ts +60 -0
- package/dist/esm/security/validator.js +143 -0
- package/dist/esm/security/validator.js.map +1 -0
- package/dist/esm/web.d.ts +74 -0
- package/dist/esm/web.js +595 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs.js +2 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.esm.js +2 -0
- package/dist/plugin.esm.js.map +1 -0
- package/dist/plugin.js +3 -0
- package/dist/plugin.js.map +1 -0
- package/docs/APP_REVIEW_GUIDE.md +768 -0
- package/docs/BUNDLE_SIGNING.md +264 -0
- package/docs/LIVE_UPDATES_GUIDE.md +650 -0
- package/docs/MIGRATION.md +192 -0
- package/docs/NATIVE_UPDATES_GUIDE.md +694 -0
- package/docs/QUICK_START.md +606 -0
- package/docs/README.md +111 -0
- package/docs/REMAINING_FEATURES.md +139 -0
- package/docs/api/app-review-api.md +259 -0
- package/docs/api/app-update-api.md +238 -0
- package/docs/api/events-api.md +451 -0
- package/docs/api/live-update-api.md +265 -0
- package/docs/background-updates.md +392 -0
- package/docs/examples/advanced-scenarios.md +410 -0
- package/docs/examples/basic-usage.md +185 -0
- package/docs/features/app-reviews.md +975 -0
- package/docs/features/app-updates.md +785 -0
- package/docs/features/live-updates.md +633 -0
- package/docs/getting-started/configuration.md +468 -0
- package/docs/getting-started/installation.md +209 -0
- package/docs/getting-started/quick-start.md +379 -0
- package/docs/guides/deployment-guide.md +333 -0
- package/docs/guides/migration-from-codepush.md +142 -0
- package/docs/guides/security-best-practices.md +1057 -0
- package/docs/guides/testing-guide.md +373 -0
- package/docs/production-readiness.md +478 -0
- package/docs/security/certificate-pinning.md +122 -0
- package/docs/server-requirements.md +147 -0
- package/ios/Plugin/AppReview/AppReviewPlugin.swift +158 -0
- package/ios/Plugin/AppUpdate/AppUpdatePlugin.swift +234 -0
- package/ios/Plugin/BackgroundUpdate/BackgroundNotificationManager.swift +329 -0
- package/ios/Plugin/BackgroundUpdate/BackgroundUpdatePlugin.swift +396 -0
- package/ios/Plugin/CapacitorNativeUpdatePlugin.m +45 -0
- package/ios/Plugin/CapacitorNativeUpdatePlugin.swift +190 -0
- package/ios/Plugin/Info.plist +43 -0
- package/ios/Plugin/LiveUpdate/LiveUpdatePlugin.swift +689 -0
- package/ios/Plugin/LiveUpdate/WebViewConfiguration.swift +45 -0
- package/ios/Plugin/Security/SecurityManager.swift +289 -0
- package/package.json +90 -0
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
# Security Best Practices
|
|
2
|
+
|
|
3
|
+
Security is paramount when implementing update mechanisms in mobile applications. This guide covers comprehensive security measures to protect your app and users from potential threats while maintaining a seamless update experience.
|
|
4
|
+
|
|
5
|
+
## Security Overview
|
|
6
|
+
|
|
7
|
+
The plugin implements multiple layers of security:
|
|
8
|
+
|
|
9
|
+
- **Transport Security**: HTTPS enforcement and certificate pinning
|
|
10
|
+
- **Content Security**: Cryptographic signatures and checksums
|
|
11
|
+
- **Input Validation**: Sanitization and validation of all inputs
|
|
12
|
+
- **Storage Security**: Secure storage of sensitive data
|
|
13
|
+
- **Access Control**: Permission-based security model
|
|
14
|
+
|
|
15
|
+
## Core Security Principles
|
|
16
|
+
|
|
17
|
+
### 1. Never Trust Input
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Always validate and sanitize inputs
|
|
21
|
+
class SecurityValidator {
|
|
22
|
+
static validateVersion(version: string): boolean {
|
|
23
|
+
// Semantic version regex
|
|
24
|
+
const versionRegex =
|
|
25
|
+
/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/;
|
|
26
|
+
|
|
27
|
+
if (!versionRegex.test(version)) {
|
|
28
|
+
throw new Error('Invalid version format');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check for suspicious patterns
|
|
32
|
+
if (
|
|
33
|
+
version.includes('..') ||
|
|
34
|
+
version.includes('/') ||
|
|
35
|
+
version.includes('\\')
|
|
36
|
+
) {
|
|
37
|
+
throw new Error('Version contains invalid characters');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static validateUrl(url: string): boolean {
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(url);
|
|
46
|
+
|
|
47
|
+
// Only allow HTTPS in production
|
|
48
|
+
if (
|
|
49
|
+
parsed.protocol !== 'https:' &&
|
|
50
|
+
process.env.NODE_ENV === 'production'
|
|
51
|
+
) {
|
|
52
|
+
throw new Error('Only HTTPS URLs are allowed in production');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Check against allowlist
|
|
56
|
+
if (!this.isAllowedHost(parsed.hostname)) {
|
|
57
|
+
throw new Error('Host not in allowlist');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(`Invalid URL: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static validateBundleId(bundleId: string): boolean {
|
|
67
|
+
// Bundle ID should be alphanumeric with hyphens
|
|
68
|
+
const bundleRegex = /^[a-zA-Z0-9-]+$/;
|
|
69
|
+
|
|
70
|
+
if (!bundleRegex.test(bundleId)) {
|
|
71
|
+
throw new Error('Invalid bundle ID format');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Prevent path traversal
|
|
75
|
+
if (bundleId.includes('..') || bundleId.includes('/')) {
|
|
76
|
+
throw new Error('Bundle ID contains invalid characters');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 2. Implement Defense in Depth
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// Multiple security layers
|
|
88
|
+
class SecurityManager {
|
|
89
|
+
async validateUpdate(bundle: BundleInfo): Promise<ValidationResult> {
|
|
90
|
+
const validations = [
|
|
91
|
+
() => this.validateChecksum(bundle),
|
|
92
|
+
() => this.validateSignature(bundle),
|
|
93
|
+
() => this.validateSize(bundle),
|
|
94
|
+
() => this.validateMetadata(bundle),
|
|
95
|
+
() => this.validateContent(bundle),
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const validation of validations) {
|
|
99
|
+
const result = await validation();
|
|
100
|
+
if (!result.valid) {
|
|
101
|
+
return {
|
|
102
|
+
valid: false,
|
|
103
|
+
reason: result.reason,
|
|
104
|
+
securityLevel: 'HIGH',
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { valid: true };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private async validateChecksum(
|
|
113
|
+
bundle: BundleInfo
|
|
114
|
+
): Promise<ValidationResult> {
|
|
115
|
+
try {
|
|
116
|
+
const calculatedHash = await this.calculateChecksum(bundle.path);
|
|
117
|
+
|
|
118
|
+
if (calculatedHash !== bundle.checksum) {
|
|
119
|
+
// Log security event
|
|
120
|
+
await this.logSecurityEvent('CHECKSUM_MISMATCH', {
|
|
121
|
+
bundleId: bundle.bundleId,
|
|
122
|
+
expected: bundle.checksum,
|
|
123
|
+
actual: calculatedHash,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
valid: false,
|
|
128
|
+
reason: 'Checksum validation failed',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { valid: true };
|
|
133
|
+
} catch (error) {
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
reason: `Checksum validation error: ${error.message}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private async validateSignature(
|
|
142
|
+
bundle: BundleInfo
|
|
143
|
+
): Promise<ValidationResult> {
|
|
144
|
+
if (!bundle.signature) {
|
|
145
|
+
return {
|
|
146
|
+
valid: false,
|
|
147
|
+
reason: 'No signature provided',
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const publicKey = await this.getPublicKey();
|
|
153
|
+
const verified = await this.verifySignature(bundle, publicKey);
|
|
154
|
+
|
|
155
|
+
if (!verified) {
|
|
156
|
+
await this.logSecurityEvent('SIGNATURE_VERIFICATION_FAILED', {
|
|
157
|
+
bundleId: bundle.bundleId,
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
valid: false,
|
|
162
|
+
reason: 'Signature verification failed',
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { valid: true };
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
valid: false,
|
|
170
|
+
reason: `Signature validation error: ${error.message}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Transport Security
|
|
178
|
+
|
|
179
|
+
### HTTPS Enforcement
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Always use HTTPS in production
|
|
183
|
+
const secureConfig = {
|
|
184
|
+
liveUpdate: {
|
|
185
|
+
serverUrl: 'https://updates.yourserver.com', // Never use HTTP
|
|
186
|
+
enforceHttps: true,
|
|
187
|
+
},
|
|
188
|
+
security: {
|
|
189
|
+
enforceHttps: true,
|
|
190
|
+
allowInsecureConnections: false, // Only for development
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// URL validation
|
|
195
|
+
class TransportSecurity {
|
|
196
|
+
static validateUpdateUrl(url: string): boolean {
|
|
197
|
+
const parsed = new URL(url);
|
|
198
|
+
|
|
199
|
+
// Enforce HTTPS
|
|
200
|
+
if (parsed.protocol !== 'https:') {
|
|
201
|
+
throw new Error('Only HTTPS URLs are allowed');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Check for suspicious patterns
|
|
205
|
+
if (
|
|
206
|
+
url.includes('..') ||
|
|
207
|
+
(url.includes('localhost') && process.env.NODE_ENV === 'production')
|
|
208
|
+
) {
|
|
209
|
+
throw new Error('Suspicious URL pattern detected');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Certificate Pinning
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// Implement certificate pinning for maximum security
|
|
221
|
+
const certificatePinningConfig = {
|
|
222
|
+
security: {
|
|
223
|
+
certificatePinning: {
|
|
224
|
+
enabled: true,
|
|
225
|
+
certificates: [
|
|
226
|
+
// SHA-256 hash of your server's certificate
|
|
227
|
+
'sha256/YLh1dUR9y6Kja30RrAn7JKnbQG/uEtLMkBgFF2Fuihg=',
|
|
228
|
+
// Backup certificate
|
|
229
|
+
'sha256/Vjs8r4z+80wjNcr1YKepWQboSIRi63WsWXhIMN+eWys=',
|
|
230
|
+
],
|
|
231
|
+
includeSubdomains: true,
|
|
232
|
+
maxAge: 31536000, // 1 year
|
|
233
|
+
reportUri: 'https://yourserver.com/hpkp-report',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
// Certificate validation
|
|
239
|
+
class CertificatePinning {
|
|
240
|
+
private pinnedCertificates: string[] = [];
|
|
241
|
+
|
|
242
|
+
async validateCertificate(
|
|
243
|
+
hostname: string,
|
|
244
|
+
certificate: string
|
|
245
|
+
): Promise<boolean> {
|
|
246
|
+
// Calculate SHA-256 hash of certificate
|
|
247
|
+
const certHash = await this.calculateSHA256(certificate);
|
|
248
|
+
const formattedHash = `sha256/${certHash}`;
|
|
249
|
+
|
|
250
|
+
// Check against pinned certificates
|
|
251
|
+
if (!this.pinnedCertificates.includes(formattedHash)) {
|
|
252
|
+
// Log security incident
|
|
253
|
+
await this.logSecurityIncident('CERTIFICATE_PIN_MISMATCH', {
|
|
254
|
+
hostname,
|
|
255
|
+
expectedCerts: this.pinnedCertificates,
|
|
256
|
+
actualCert: formattedHash,
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Content Security
|
|
268
|
+
|
|
269
|
+
### Cryptographic Signatures
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Implement RSA-PSS signature verification
|
|
273
|
+
class SignatureVerification {
|
|
274
|
+
private publicKey: CryptoKey | null = null;
|
|
275
|
+
|
|
276
|
+
async initialize(publicKeyPem: string) {
|
|
277
|
+
try {
|
|
278
|
+
// Import public key
|
|
279
|
+
this.publicKey = await crypto.subtle.importKey(
|
|
280
|
+
'spki',
|
|
281
|
+
this.pemToArrayBuffer(publicKeyPem),
|
|
282
|
+
{
|
|
283
|
+
name: 'RSA-PSS',
|
|
284
|
+
hash: 'SHA-256',
|
|
285
|
+
},
|
|
286
|
+
false,
|
|
287
|
+
['verify']
|
|
288
|
+
);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
throw new Error(`Failed to import public key: ${error.message}`);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async verifyBundle(bundle: BundleInfo): Promise<boolean> {
|
|
295
|
+
if (!this.publicKey) {
|
|
296
|
+
throw new Error('Public key not initialized');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (!bundle.signature) {
|
|
300
|
+
throw new Error('No signature provided');
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
// Create message to verify (bundle metadata + content hash)
|
|
305
|
+
const message = this.createSignatureMessage(bundle);
|
|
306
|
+
const messageBuffer = new TextEncoder().encode(message);
|
|
307
|
+
|
|
308
|
+
// Decode signature
|
|
309
|
+
const signatureBuffer = this.base64ToArrayBuffer(bundle.signature);
|
|
310
|
+
|
|
311
|
+
// Verify signature
|
|
312
|
+
const verified = await crypto.subtle.verify(
|
|
313
|
+
{
|
|
314
|
+
name: 'RSA-PSS',
|
|
315
|
+
saltLength: 32,
|
|
316
|
+
},
|
|
317
|
+
this.publicKey,
|
|
318
|
+
signatureBuffer,
|
|
319
|
+
messageBuffer
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
return verified;
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error('Signature verification failed:', error);
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private createSignatureMessage(bundle: BundleInfo): string {
|
|
330
|
+
// Create deterministic message for signature
|
|
331
|
+
return JSON.stringify({
|
|
332
|
+
bundleId: bundle.bundleId,
|
|
333
|
+
version: bundle.version,
|
|
334
|
+
checksum: bundle.checksum,
|
|
335
|
+
size: bundle.size,
|
|
336
|
+
timestamp: bundle.downloadTime,
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Checksum Validation
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
// Implement strong checksum validation
|
|
346
|
+
class ChecksumValidator {
|
|
347
|
+
async validateBundle(bundle: BundleInfo): Promise<boolean> {
|
|
348
|
+
try {
|
|
349
|
+
// Calculate SHA-512 checksum
|
|
350
|
+
const calculatedChecksum = await this.calculateSHA512(bundle.path);
|
|
351
|
+
|
|
352
|
+
// Compare with provided checksum
|
|
353
|
+
if (calculatedChecksum !== bundle.checksum) {
|
|
354
|
+
await this.logSecurityEvent('CHECKSUM_MISMATCH', {
|
|
355
|
+
bundleId: bundle.bundleId,
|
|
356
|
+
expected: bundle.checksum,
|
|
357
|
+
calculated: calculatedChecksum,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return true;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
console.error('Checksum validation failed:', error);
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
private async calculateSHA512(filePath: string): Promise<string> {
|
|
371
|
+
// Platform-specific implementation
|
|
372
|
+
if (this.isWeb()) {
|
|
373
|
+
return this.calculateWebChecksum(filePath);
|
|
374
|
+
} else {
|
|
375
|
+
return this.calculateNativeChecksum(filePath);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
private async calculateWebChecksum(filePath: string): Promise<string> {
|
|
380
|
+
const response = await fetch(filePath);
|
|
381
|
+
const buffer = await response.arrayBuffer();
|
|
382
|
+
|
|
383
|
+
const hashBuffer = await crypto.subtle.digest('SHA-512', buffer);
|
|
384
|
+
return this.arrayBufferToHex(hashBuffer);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Input Validation and Sanitization
|
|
390
|
+
|
|
391
|
+
### Comprehensive Input Validation
|
|
392
|
+
|
|
393
|
+
```typescript
|
|
394
|
+
class InputValidator {
|
|
395
|
+
static validateUpdateRequest(request: UpdateRequest): ValidationResult {
|
|
396
|
+
const validations = [
|
|
397
|
+
() => this.validateVersion(request.version),
|
|
398
|
+
() => this.validateChannel(request.channel),
|
|
399
|
+
() => this.validateAppId(request.appId),
|
|
400
|
+
() => this.validateHeaders(request.headers),
|
|
401
|
+
() => this.validateParameters(request.parameters),
|
|
402
|
+
];
|
|
403
|
+
|
|
404
|
+
for (const validation of validations) {
|
|
405
|
+
const result = validation();
|
|
406
|
+
if (!result.valid) {
|
|
407
|
+
return result;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return { valid: true };
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private static validateVersion(version: string): ValidationResult {
|
|
415
|
+
// Check format
|
|
416
|
+
if (!version || typeof version !== 'string') {
|
|
417
|
+
return { valid: false, reason: 'Version must be a string' };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Check length
|
|
421
|
+
if (version.length > 50) {
|
|
422
|
+
return { valid: false, reason: 'Version too long' };
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Check semantic version format
|
|
426
|
+
const semverRegex =
|
|
427
|
+
/^(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z0-9.-]+))?(?:\+([a-zA-Z0-9.-]+))?$/;
|
|
428
|
+
if (!semverRegex.test(version)) {
|
|
429
|
+
return { valid: false, reason: 'Invalid semantic version format' };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Check for dangerous patterns
|
|
433
|
+
const dangerousPatterns = ['..', '/', '\\', '<', '>', '&', '"', "'"];
|
|
434
|
+
for (const pattern of dangerousPatterns) {
|
|
435
|
+
if (version.includes(pattern)) {
|
|
436
|
+
return {
|
|
437
|
+
valid: false,
|
|
438
|
+
reason: 'Version contains dangerous characters',
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return { valid: true };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private static validateChannel(channel: string): ValidationResult {
|
|
447
|
+
// Allowlist of valid channels
|
|
448
|
+
const validChannels = [
|
|
449
|
+
'production',
|
|
450
|
+
'staging',
|
|
451
|
+
'beta',
|
|
452
|
+
'alpha',
|
|
453
|
+
'development',
|
|
454
|
+
];
|
|
455
|
+
|
|
456
|
+
if (!validChannels.includes(channel)) {
|
|
457
|
+
return { valid: false, reason: 'Invalid channel' };
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return { valid: true };
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
private static validateAppId(appId: string): ValidationResult {
|
|
464
|
+
// App ID format: com.company.app
|
|
465
|
+
const appIdRegex = /^[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*$/;
|
|
466
|
+
|
|
467
|
+
if (!appIdRegex.test(appId)) {
|
|
468
|
+
return { valid: false, reason: 'Invalid app ID format' };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Check length
|
|
472
|
+
if (appId.length > 100) {
|
|
473
|
+
return { valid: false, reason: 'App ID too long' };
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return { valid: true };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### SQL Injection Prevention
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// Prevent SQL injection in server-side queries
|
|
485
|
+
class DatabaseSecurity {
|
|
486
|
+
async getUpdateInfo(
|
|
487
|
+
appId: string,
|
|
488
|
+
version: string
|
|
489
|
+
): Promise<UpdateInfo | null> {
|
|
490
|
+
// Use parameterized queries
|
|
491
|
+
const query = `
|
|
492
|
+
SELECT * FROM app_updates
|
|
493
|
+
WHERE app_id = ? AND version = ?
|
|
494
|
+
ORDER BY created_at DESC
|
|
495
|
+
LIMIT 1
|
|
496
|
+
`;
|
|
497
|
+
|
|
498
|
+
// Validate inputs before query
|
|
499
|
+
if (!InputValidator.validateAppId(appId).valid) {
|
|
500
|
+
throw new Error('Invalid app ID');
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (!InputValidator.validateVersion(version).valid) {
|
|
504
|
+
throw new Error('Invalid version');
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Execute with parameters
|
|
508
|
+
const result = await this.database.query(query, [appId, version]);
|
|
509
|
+
return result[0] || null;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Storage Security
|
|
515
|
+
|
|
516
|
+
### Secure Key Management
|
|
517
|
+
|
|
518
|
+
```typescript
|
|
519
|
+
class SecureKeyManager {
|
|
520
|
+
private static readonly KEY_ALIAS = 'update_keys';
|
|
521
|
+
|
|
522
|
+
async storePrivateKey(privateKey: string): Promise<void> {
|
|
523
|
+
if (this.isAndroid()) {
|
|
524
|
+
// Use Android Keystore
|
|
525
|
+
await this.storeInAndroidKeystore(privateKey);
|
|
526
|
+
} else if (this.isIOS()) {
|
|
527
|
+
// Use iOS Keychain
|
|
528
|
+
await this.storeInKeychainServices(privateKey);
|
|
529
|
+
} else {
|
|
530
|
+
// Web - use secure storage if available
|
|
531
|
+
await this.storeInSecureStorage(privateKey);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async getPrivateKey(): Promise<string | null> {
|
|
536
|
+
try {
|
|
537
|
+
if (this.isAndroid()) {
|
|
538
|
+
return await this.getFromAndroidKeystore();
|
|
539
|
+
} else if (this.isIOS()) {
|
|
540
|
+
return await this.getFromKeychainServices();
|
|
541
|
+
} else {
|
|
542
|
+
return await this.getFromSecureStorage();
|
|
543
|
+
}
|
|
544
|
+
} catch (error) {
|
|
545
|
+
console.error('Failed to retrieve private key:', error);
|
|
546
|
+
return null;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private async storeInAndroidKeystore(privateKey: string): Promise<void> {
|
|
551
|
+
// Use Android Keystore for secure storage
|
|
552
|
+
const keyStore = await this.getAndroidKeyStore();
|
|
553
|
+
await keyStore.store(this.KEY_ALIAS, privateKey, {
|
|
554
|
+
requireAuthentication: true,
|
|
555
|
+
encryptionRequired: true,
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private async storeInKeychainServices(privateKey: string): Promise<void> {
|
|
560
|
+
// Use iOS Keychain Services
|
|
561
|
+
const keychain = await this.getIOSKeychain();
|
|
562
|
+
await keychain.store(this.KEY_ALIAS, privateKey, {
|
|
563
|
+
accessibility: 'kSecAttrAccessibleWhenUnlockedThisDeviceOnly',
|
|
564
|
+
synchronizable: false,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### Secure Bundle Storage
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
class SecureBundleStorage {
|
|
574
|
+
private readonly BUNDLE_DIR = 'secure_bundles';
|
|
575
|
+
|
|
576
|
+
async storeBundleSecurely(
|
|
577
|
+
bundle: BundleInfo,
|
|
578
|
+
content: ArrayBuffer
|
|
579
|
+
): Promise<string> {
|
|
580
|
+
// Generate unique file name
|
|
581
|
+
const fileName = await this.generateSecureFileName(bundle);
|
|
582
|
+
const filePath = path.join(this.BUNDLE_DIR, fileName);
|
|
583
|
+
|
|
584
|
+
// Encrypt content before storage
|
|
585
|
+
const encryptedContent = await this.encryptBundle(content);
|
|
586
|
+
|
|
587
|
+
// Store with proper permissions
|
|
588
|
+
await this.storeWithRestrictedPermissions(filePath, encryptedContent);
|
|
589
|
+
|
|
590
|
+
// Create metadata file
|
|
591
|
+
await this.createMetadataFile(bundle, fileName);
|
|
592
|
+
|
|
593
|
+
return filePath;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
private async encryptBundle(content: ArrayBuffer): Promise<ArrayBuffer> {
|
|
597
|
+
// Generate random encryption key
|
|
598
|
+
const key = await crypto.subtle.generateKey(
|
|
599
|
+
{ name: 'AES-GCM', length: 256 },
|
|
600
|
+
false,
|
|
601
|
+
['encrypt', 'decrypt']
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// Generate random IV
|
|
605
|
+
const iv = crypto.getRandomValues(new Uint8Array(12));
|
|
606
|
+
|
|
607
|
+
// Encrypt content
|
|
608
|
+
const encrypted = await crypto.subtle.encrypt(
|
|
609
|
+
{ name: 'AES-GCM', iv },
|
|
610
|
+
key,
|
|
611
|
+
content
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
// Store key securely
|
|
615
|
+
await this.storeEncryptionKey(key);
|
|
616
|
+
|
|
617
|
+
// Combine IV and encrypted content
|
|
618
|
+
const result = new Uint8Array(iv.length + encrypted.byteLength);
|
|
619
|
+
result.set(iv);
|
|
620
|
+
result.set(new Uint8Array(encrypted), iv.length);
|
|
621
|
+
|
|
622
|
+
return result.buffer;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
private async storeWithRestrictedPermissions(
|
|
626
|
+
filePath: string,
|
|
627
|
+
content: ArrayBuffer
|
|
628
|
+
): Promise<void> {
|
|
629
|
+
// Platform-specific secure storage
|
|
630
|
+
if (this.isAndroid()) {
|
|
631
|
+
await this.storeAndroidSecure(filePath, content);
|
|
632
|
+
} else if (this.isIOS()) {
|
|
633
|
+
await this.storeIOSSecure(filePath, content);
|
|
634
|
+
} else {
|
|
635
|
+
await this.storeWebSecure(filePath, content);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
## Access Control and Permissions
|
|
642
|
+
|
|
643
|
+
### Permission-Based Security
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
class PermissionManager {
|
|
647
|
+
private permissions: Map<string, Permission> = new Map();
|
|
648
|
+
|
|
649
|
+
async requestUpdatePermission(context: UpdateContext): Promise<boolean> {
|
|
650
|
+
// Check if permission is already granted
|
|
651
|
+
if (await this.hasPermission('update', context)) {
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// Request permission based on platform
|
|
656
|
+
if (this.isAndroid()) {
|
|
657
|
+
return await this.requestAndroidPermission(context);
|
|
658
|
+
} else if (this.isIOS()) {
|
|
659
|
+
return await this.requestIOSPermission(context);
|
|
660
|
+
} else {
|
|
661
|
+
return await this.requestWebPermission(context);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
private async requestAndroidPermission(
|
|
666
|
+
context: UpdateContext
|
|
667
|
+
): Promise<boolean> {
|
|
668
|
+
// Request necessary Android permissions
|
|
669
|
+
const permissions = [
|
|
670
|
+
'android.permission.INTERNET',
|
|
671
|
+
'android.permission.ACCESS_NETWORK_STATE',
|
|
672
|
+
'android.permission.WRITE_EXTERNAL_STORAGE',
|
|
673
|
+
];
|
|
674
|
+
|
|
675
|
+
for (const permission of permissions) {
|
|
676
|
+
const granted = await this.requestSystemPermission(permission);
|
|
677
|
+
if (!granted) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
return true;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
async validatePermission(
|
|
686
|
+
action: string,
|
|
687
|
+
context: UpdateContext
|
|
688
|
+
): Promise<boolean> {
|
|
689
|
+
const permission = this.permissions.get(action);
|
|
690
|
+
|
|
691
|
+
if (!permission) {
|
|
692
|
+
throw new Error(`Permission not found: ${action}`);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Check conditions
|
|
696
|
+
for (const condition of permission.conditions) {
|
|
697
|
+
if (!(await condition.check(context))) {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return true;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
## Security Monitoring and Logging
|
|
708
|
+
|
|
709
|
+
### Security Event Logging
|
|
710
|
+
|
|
711
|
+
```typescript
|
|
712
|
+
class SecurityLogger {
|
|
713
|
+
private logLevel: 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' = 'INFO';
|
|
714
|
+
|
|
715
|
+
async logSecurityEvent(eventType: string, details: any): Promise<void> {
|
|
716
|
+
const event = {
|
|
717
|
+
timestamp: new Date().toISOString(),
|
|
718
|
+
type: eventType,
|
|
719
|
+
severity: this.getSeverity(eventType),
|
|
720
|
+
details: this.sanitizeDetails(details),
|
|
721
|
+
deviceInfo: await this.getDeviceInfo(),
|
|
722
|
+
appVersion: await this.getAppVersion(),
|
|
723
|
+
userId: await this.getUserId(), // Hash or anonymize
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
// Store locally
|
|
727
|
+
await this.storeLogLocally(event);
|
|
728
|
+
|
|
729
|
+
// Send to security monitoring service
|
|
730
|
+
if (event.severity === 'HIGH' || event.severity === 'CRITICAL') {
|
|
731
|
+
await this.sendToMonitoringService(event);
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
private getSeverity(
|
|
736
|
+
eventType: string
|
|
737
|
+
): 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL' {
|
|
738
|
+
const severityMap = {
|
|
739
|
+
CHECKSUM_MISMATCH: 'HIGH',
|
|
740
|
+
SIGNATURE_VERIFICATION_FAILED: 'CRITICAL',
|
|
741
|
+
CERTIFICATE_PIN_MISMATCH: 'CRITICAL',
|
|
742
|
+
INVALID_INPUT: 'MEDIUM',
|
|
743
|
+
PERMISSION_DENIED: 'HIGH',
|
|
744
|
+
SUSPICIOUS_ACTIVITY: 'HIGH',
|
|
745
|
+
RATE_LIMIT_EXCEEDED: 'MEDIUM',
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
return severityMap[eventType] || 'LOW';
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
private sanitizeDetails(details: any): any {
|
|
752
|
+
// Remove sensitive information
|
|
753
|
+
const sanitized = { ...details };
|
|
754
|
+
|
|
755
|
+
const sensitiveKeys = ['password', 'token', 'key', 'secret', 'privateKey'];
|
|
756
|
+
|
|
757
|
+
for (const key of sensitiveKeys) {
|
|
758
|
+
if (key in sanitized) {
|
|
759
|
+
sanitized[key] = '[REDACTED]';
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return sanitized;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
### Intrusion Detection
|
|
769
|
+
|
|
770
|
+
```typescript
|
|
771
|
+
class IntrusionDetection {
|
|
772
|
+
private suspiciousActivity: Map<string, number> = new Map();
|
|
773
|
+
private readonly MAX_ATTEMPTS = 5;
|
|
774
|
+
private readonly TIME_WINDOW = 300000; // 5 minutes
|
|
775
|
+
|
|
776
|
+
async checkSuspiciousActivity(
|
|
777
|
+
clientId: string,
|
|
778
|
+
action: string
|
|
779
|
+
): Promise<boolean> {
|
|
780
|
+
const key = `${clientId}:${action}`;
|
|
781
|
+
const attempts = this.suspiciousActivity.get(key) || 0;
|
|
782
|
+
|
|
783
|
+
if (attempts >= this.MAX_ATTEMPTS) {
|
|
784
|
+
await this.logSecurityEvent('RATE_LIMIT_EXCEEDED', {
|
|
785
|
+
clientId,
|
|
786
|
+
action,
|
|
787
|
+
attempts,
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
return true; // Suspicious
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Increment attempts
|
|
794
|
+
this.suspiciousActivity.set(key, attempts + 1);
|
|
795
|
+
|
|
796
|
+
// Reset after time window
|
|
797
|
+
setTimeout(() => {
|
|
798
|
+
this.suspiciousActivity.delete(key);
|
|
799
|
+
}, this.TIME_WINDOW);
|
|
800
|
+
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async detectAnomalies(updateRequest: UpdateRequest): Promise<boolean> {
|
|
805
|
+
const anomalies = [
|
|
806
|
+
this.checkFrequency(updateRequest),
|
|
807
|
+
this.checkRequestPatterns(updateRequest),
|
|
808
|
+
this.checkGeolocation(updateRequest),
|
|
809
|
+
this.checkDeviceFingerprint(updateRequest),
|
|
810
|
+
];
|
|
811
|
+
|
|
812
|
+
const suspiciousCount = (await Promise.all(anomalies)).filter(
|
|
813
|
+
Boolean
|
|
814
|
+
).length;
|
|
815
|
+
|
|
816
|
+
if (suspiciousCount >= 2) {
|
|
817
|
+
await this.logSecurityEvent('SUSPICIOUS_ACTIVITY', {
|
|
818
|
+
anomalies: suspiciousCount,
|
|
819
|
+
request: updateRequest,
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return false;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
## Incident Response
|
|
831
|
+
|
|
832
|
+
### Security Incident Handling
|
|
833
|
+
|
|
834
|
+
```typescript
|
|
835
|
+
class SecurityIncidentHandler {
|
|
836
|
+
async handleSecurityIncident(incident: SecurityIncident): Promise<void> {
|
|
837
|
+
// Classify incident
|
|
838
|
+
const classification = this.classifyIncident(incident);
|
|
839
|
+
|
|
840
|
+
// Immediate response
|
|
841
|
+
await this.immediateResponse(incident, classification);
|
|
842
|
+
|
|
843
|
+
// Investigate
|
|
844
|
+
await this.investigate(incident);
|
|
845
|
+
|
|
846
|
+
// Remediate
|
|
847
|
+
await this.remediate(incident);
|
|
848
|
+
|
|
849
|
+
// Document
|
|
850
|
+
await this.documentIncident(incident);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
private async immediateResponse(
|
|
854
|
+
incident: SecurityIncident,
|
|
855
|
+
classification: IncidentClassification
|
|
856
|
+
): Promise<void> {
|
|
857
|
+
switch (classification.severity) {
|
|
858
|
+
case 'CRITICAL':
|
|
859
|
+
// Disable updates immediately
|
|
860
|
+
await this.disableUpdates();
|
|
861
|
+
|
|
862
|
+
// Notify security team
|
|
863
|
+
await this.notifySecurityTeam(incident);
|
|
864
|
+
|
|
865
|
+
// Isolate affected systems
|
|
866
|
+
await this.isolateAffectedSystems(incident);
|
|
867
|
+
break;
|
|
868
|
+
|
|
869
|
+
case 'HIGH':
|
|
870
|
+
// Increase monitoring
|
|
871
|
+
await this.increaseMonitoring();
|
|
872
|
+
|
|
873
|
+
// Review recent updates
|
|
874
|
+
await this.reviewRecentUpdates();
|
|
875
|
+
break;
|
|
876
|
+
|
|
877
|
+
case 'MEDIUM':
|
|
878
|
+
// Log and monitor
|
|
879
|
+
await this.logAndMonitor(incident);
|
|
880
|
+
break;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
private async disableUpdates(): Promise<void> {
|
|
885
|
+
// Disable all update channels
|
|
886
|
+
await this.updateServerConfig({
|
|
887
|
+
updatesEnabled: false,
|
|
888
|
+
reason: 'Security incident response',
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
// Notify all clients
|
|
892
|
+
await this.broadcastSecurityAlert({
|
|
893
|
+
type: 'UPDATES_DISABLED',
|
|
894
|
+
message: 'Updates temporarily disabled due to security incident',
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private async remediate(incident: SecurityIncident): Promise<void> {
|
|
899
|
+
switch (incident.type) {
|
|
900
|
+
case 'COMPROMISED_SIGNING_KEY':
|
|
901
|
+
await this.rotateSigningKeys();
|
|
902
|
+
await this.revokeCompromisedCertificates();
|
|
903
|
+
break;
|
|
904
|
+
|
|
905
|
+
case 'MALICIOUS_UPDATE':
|
|
906
|
+
await this.rollbackToSafeVersion();
|
|
907
|
+
await this.blacklistMaliciousBundle();
|
|
908
|
+
break;
|
|
909
|
+
|
|
910
|
+
case 'SERVER_COMPROMISE':
|
|
911
|
+
await this.isolateServers();
|
|
912
|
+
await this.rebuildeFromCleanBackup();
|
|
913
|
+
break;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
```
|
|
918
|
+
|
|
919
|
+
## Production Security Checklist
|
|
920
|
+
|
|
921
|
+
### Pre-Deployment Security Audit
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
class SecurityAudit {
|
|
925
|
+
async performSecurityAudit(): Promise<AuditResult> {
|
|
926
|
+
const checks = [
|
|
927
|
+
this.checkCryptographicImplementation(),
|
|
928
|
+
this.checkInputValidation(),
|
|
929
|
+
this.checkTransportSecurity(),
|
|
930
|
+
this.checkStorageSecurity(),
|
|
931
|
+
this.checkPermissions(),
|
|
932
|
+
this.checkLogging(),
|
|
933
|
+
this.checkIncidentResponse(),
|
|
934
|
+
];
|
|
935
|
+
|
|
936
|
+
const results = await Promise.all(checks);
|
|
937
|
+
|
|
938
|
+
return {
|
|
939
|
+
passed: results.every((r) => r.passed),
|
|
940
|
+
results,
|
|
941
|
+
recommendations: this.generateRecommendations(results),
|
|
942
|
+
};
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
private async checkCryptographicImplementation(): Promise<CheckResult> {
|
|
946
|
+
const checks = [
|
|
947
|
+
this.verifySignatureAlgorithm(),
|
|
948
|
+
this.verifyHashAlgorithm(),
|
|
949
|
+
this.verifyKeyLength(),
|
|
950
|
+
this.verifyRandomness(),
|
|
951
|
+
];
|
|
952
|
+
|
|
953
|
+
const results = await Promise.all(checks);
|
|
954
|
+
|
|
955
|
+
return {
|
|
956
|
+
category: 'Cryptographic Implementation',
|
|
957
|
+
passed: results.every((r) => r.passed),
|
|
958
|
+
details: results,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
private async checkInputValidation(): Promise<CheckResult> {
|
|
963
|
+
const testCases = [
|
|
964
|
+
{ input: '../../../etc/passwd', expected: 'rejected' },
|
|
965
|
+
{ input: '<script>alert("xss")</script>', expected: 'rejected' },
|
|
966
|
+
{ input: 'DROP TABLE users;', expected: 'rejected' },
|
|
967
|
+
{ input: 'valid-version-1.0.0', expected: 'accepted' },
|
|
968
|
+
];
|
|
969
|
+
|
|
970
|
+
const results = [];
|
|
971
|
+
|
|
972
|
+
for (const testCase of testCases) {
|
|
973
|
+
const result = await this.testInputValidation(testCase.input);
|
|
974
|
+
results.push({
|
|
975
|
+
input: testCase.input,
|
|
976
|
+
expected: testCase.expected,
|
|
977
|
+
actual: result,
|
|
978
|
+
passed: result === testCase.expected,
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
return {
|
|
983
|
+
category: 'Input Validation',
|
|
984
|
+
passed: results.every((r) => r.passed),
|
|
985
|
+
details: results,
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
### Security Configuration Template
|
|
992
|
+
|
|
993
|
+
```typescript
|
|
994
|
+
// Production security configuration
|
|
995
|
+
const productionSecurityConfig = {
|
|
996
|
+
liveUpdate: {
|
|
997
|
+
// Enable all security features
|
|
998
|
+
requireSignature: true,
|
|
999
|
+
checksumAlgorithm: 'SHA-512',
|
|
1000
|
+
publicKey: process.env.UPDATE_PUBLIC_KEY,
|
|
1001
|
+
allowedHosts: ['updates.yourserver.com'],
|
|
1002
|
+
maxBundleSize: 50 * 1024 * 1024, // 50MB
|
|
1003
|
+
allowEmulator: false,
|
|
1004
|
+
},
|
|
1005
|
+
|
|
1006
|
+
security: {
|
|
1007
|
+
// Transport security
|
|
1008
|
+
enforceHttps: true,
|
|
1009
|
+
certificatePinning: {
|
|
1010
|
+
enabled: true,
|
|
1011
|
+
certificates: [
|
|
1012
|
+
process.env.CERT_HASH_PRIMARY,
|
|
1013
|
+
process.env.CERT_HASH_BACKUP,
|
|
1014
|
+
],
|
|
1015
|
+
},
|
|
1016
|
+
|
|
1017
|
+
// Input validation
|
|
1018
|
+
validateInputs: true,
|
|
1019
|
+
sanitizeInputs: true,
|
|
1020
|
+
|
|
1021
|
+
// Storage security
|
|
1022
|
+
secureStorage: true,
|
|
1023
|
+
encryptBundles: true,
|
|
1024
|
+
|
|
1025
|
+
// Monitoring
|
|
1026
|
+
logSecurityEvents: true,
|
|
1027
|
+
enableIntrusionDetection: true,
|
|
1028
|
+
|
|
1029
|
+
// Rate limiting
|
|
1030
|
+
enableRateLimit: true,
|
|
1031
|
+
maxRequestsPerMinute: 60,
|
|
1032
|
+
},
|
|
1033
|
+
|
|
1034
|
+
monitoring: {
|
|
1035
|
+
// Security monitoring
|
|
1036
|
+
securityEventEndpoint: process.env.SECURITY_MONITOR_URL,
|
|
1037
|
+
alertThreshold: 'HIGH',
|
|
1038
|
+
|
|
1039
|
+
// Performance monitoring
|
|
1040
|
+
performanceEndpoint: process.env.PERFORMANCE_MONITOR_URL,
|
|
1041
|
+
|
|
1042
|
+
// Error tracking
|
|
1043
|
+
errorTrackingEndpoint: process.env.ERROR_TRACKING_URL,
|
|
1044
|
+
},
|
|
1045
|
+
};
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
## Next Steps
|
|
1049
|
+
|
|
1050
|
+
- Review [Production Readiness](../production-readiness.md) checklist
|
|
1051
|
+
- Implement [Monitoring and Analytics](../examples/monitoring-setup.md)
|
|
1052
|
+
- Set up [Incident Response](../guides/incident-response.md) procedures
|
|
1053
|
+
- Configure [Update Server Security](../examples/server-security.md)
|
|
1054
|
+
|
|
1055
|
+
---
|
|
1056
|
+
|
|
1057
|
+
Made with ❤️ by Ahsan Mahmood
|