react-native-instantpay-code-push 1.1.7 → 1.1.9

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.
Files changed (43) hide show
  1. package/InstantpayCodePush.podspec +4 -0
  2. package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushModule.kt +20 -2
  3. package/android/src/main/java/com/instantpaycodepush/IpayCodePush.kt +5 -0
  4. package/android/src/main/java/com/instantpaycodepush/SignatureVerifier.kt +139 -0
  5. package/ios/BundleFileStorageService.swift +1269 -0
  6. package/ios/BundleMetadata.swift +208 -0
  7. package/ios/DecompressService.swift +116 -0
  8. package/ios/FileManagerService.swift +104 -0
  9. package/ios/HashUtils.swift +73 -0
  10. package/ios/InstantpayCodePush-Bridging-Header.h +16 -0
  11. package/ios/InstantpayCodePush.h +39 -1
  12. package/ios/InstantpayCodePush.mm +332 -4
  13. package/ios/IpayCodePushHelper.swift +57 -0
  14. package/ios/IpayCodePushImpl.swift +297 -0
  15. package/ios/NotificationExtension.swift +13 -0
  16. package/ios/SignatureVerifier.swift +358 -0
  17. package/ios/URLSessionDownloadService.swift +251 -0
  18. package/ios/VersionedPreferencesService.swift +93 -0
  19. package/ios/ZipDecompressionStrategy.swift +175 -0
  20. package/lib/module/NativeInstantpayCodePush.js.map +1 -1
  21. package/lib/module/error.js +6 -0
  22. package/lib/module/error.js.map +1 -1
  23. package/lib/module/index.js +12 -2
  24. package/lib/module/index.js.map +1 -1
  25. package/lib/module/native.js +10 -0
  26. package/lib/module/native.js.map +1 -1
  27. package/lib/module/types.js +1 -1
  28. package/lib/module/types.js.map +1 -1
  29. package/lib/typescript/src/NativeInstantpayCodePush.d.ts +1 -0
  30. package/lib/typescript/src/NativeInstantpayCodePush.d.ts.map +1 -1
  31. package/lib/typescript/src/error.d.ts +6 -0
  32. package/lib/typescript/src/error.d.ts.map +1 -1
  33. package/lib/typescript/src/index.d.ts +10 -0
  34. package/lib/typescript/src/index.d.ts.map +1 -1
  35. package/lib/typescript/src/native.d.ts +6 -0
  36. package/lib/typescript/src/native.d.ts.map +1 -1
  37. package/lib/typescript/src/types.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/NativeInstantpayCodePush.ts +1 -0
  40. package/src/error.ts +7 -0
  41. package/src/index.tsx +13 -0
  42. package/src/native.ts +11 -0
  43. package/src/types.ts +2 -1
@@ -16,5 +16,9 @@ Pod::Spec.new do |s|
16
16
  s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
17
17
  s.private_header_files = "ios/**/*.h"
18
18
 
19
+ # SWCompression dependency for ZIP/TAR/GZIP/Brotli extraction support
20
+ # Native Compression framework is used for GZIP and Brotli decompression
21
+ s.dependency "SWCompression", "~> 4.8.0"
22
+
19
23
  install_modules_dependencies(s)
20
24
  end
@@ -16,6 +16,7 @@ import kotlinx.coroutines.launch
16
16
 
17
17
  import android.os.Handler
18
18
  import android.os.Looper
19
+ import org.json.JSONObject
19
20
 
20
21
 
21
22
  @ReactModule(name = InstantpayCodePushModule.NAME)
@@ -86,11 +87,28 @@ class InstantpayCodePushModule(reactContext: ReactApplicationContext) : NativeIn
86
87
 
87
88
  val fileHash = params.getString("fileHash")
88
89
 
90
+ //Verify Bundle Url Signature
91
+ val decryptSignatureData = SignatureVerifier.decryptSignatureUrl(fileUrl)
92
+
93
+ if (decryptSignatureData.isEmpty()){
94
+ promise.reject("INVALID_URL_SIGNATURE", "Invalid 'fileUrl' Signature: $fileUrl")
95
+ return@launch
96
+ }
97
+
98
+ val planSignatureData = JSONObject(decryptSignatureData)
99
+
100
+ if(planSignatureData.getString("fileHash") != fileHash){
101
+ promise.reject("INVALID_URL_SIGNATURE", "Invalid 'fileUrl' Hash: $fileUrl")
102
+ return@launch
103
+ }
104
+
105
+ val fileUrlPlan = planSignatureData.getString("bundleUrl")
106
+
89
107
  val impl = getInstance()
90
108
 
91
109
  impl.updateBundle(
92
110
  bundleId,
93
- fileUrl,
111
+ fileUrlPlan,
94
112
  fileHash,
95
113
  ) { progress ->
96
114
  // Post to Main thread for React Native event emission
@@ -127,6 +145,7 @@ class InstantpayCodePushModule(reactContext: ReactApplicationContext) : NativeIn
127
145
  constants["APP_VERSION"] = IpayCodePush.getAppVersion(mReactApplicationContext)
128
146
  constants["CHANNEL"] = IpayCodePush.getChannel(mReactApplicationContext)
129
147
  constants["FINGERPRINT_HASH"] = IpayCodePush.getFingerprintHash(mReactApplicationContext)
148
+ constants["KEYSTORE_PUBLIC_KEY"] = IpayCodePush.getKeyStorePublicKey()
130
149
  return constants
131
150
  }
132
151
 
@@ -178,5 +197,4 @@ class InstantpayCodePushModule(reactContext: ReactApplicationContext) : NativeIn
178
197
  val impl = getInstance()
179
198
  return impl.getBaseURL()
180
199
  }
181
-
182
200
  }
@@ -97,5 +97,10 @@ class IpayCodePush {
97
97
  } else {
98
98
  null
99
99
  }
100
+
101
+ /**
102
+ * Get Public Key From KeyStore
103
+ */
104
+ fun getKeyStorePublicKey(): String = SignatureVerifier.getPublicKeyBase64()
100
105
  }
101
106
  }
@@ -1,12 +1,20 @@
1
1
  package com.instantpaycodepush
2
2
 
3
3
  import android.content.Context
4
+ import android.net.Uri
5
+ import android.security.keystore.KeyGenParameterSpec
6
+ import android.security.keystore.KeyProperties
4
7
  import android.util.Base64
5
8
  import java.io.File
6
9
  import java.security.KeyFactory
10
+ import java.security.KeyPairGenerator
11
+ import java.security.KeyStore
7
12
  import java.security.PublicKey
8
13
  import java.security.Signature
9
14
  import java.security.spec.X509EncodedKeySpec
15
+ import javax.crypto.Cipher
16
+ import javax.crypto.spec.IvParameterSpec
17
+ import javax.crypto.spec.SecretKeySpec
10
18
 
11
19
  /**
12
20
  * Prefix for signed file hash format.
@@ -71,6 +79,13 @@ sealed class SignatureVerificationException(
71
79
  ) : SignatureVerificationException(
72
80
  "Security framework error during verification: ${cause.message}",
73
81
  )
82
+
83
+ class KeyStoreFailed(
84
+ cause: Throwable,
85
+ ) :
86
+ SignatureVerificationException(
87
+ "Failed to generate KeyStore: ${cause.message}",
88
+ )
74
89
  }
75
90
 
76
91
 
@@ -350,4 +365,128 @@ object SignatureVerifier {
350
365
  throw SignatureVerificationException.InvalidSignatureFormat()
351
366
  }
352
367
  }
368
+
369
+ private const val KEY_ALIAS = "com.ipaycodepush.security.rsa"
370
+
371
+ /**
372
+ * Generate a new EC key pair entry in the Android Keystore by
373
+ * using the KeyPairGenerator API. The private key can only be
374
+ * used for signing or verification and only with SHA-256 or
375
+ * SHA-512 as the message digest.
376
+ */
377
+ private fun generateKeyStore(){
378
+
379
+ val existingKeyStore = KeyStore.getInstance("AndroidKeyStore")
380
+ existingKeyStore.load(null)
381
+
382
+ if (existingKeyStore.containsAlias(KEY_ALIAS)) return
383
+
384
+ val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore")
385
+
386
+ val spec = KeyGenParameterSpec.Builder(
387
+ KEY_ALIAS,
388
+ KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
389
+ )
390
+ .setKeySize(2048)
391
+ .setEncryptionPaddings(
392
+ KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1
393
+ )
394
+ .setDigests(
395
+ KeyProperties.DIGEST_SHA256,
396
+ KeyProperties.DIGEST_SHA512
397
+ )
398
+ .build()
399
+
400
+ kpg.initialize(spec)
401
+ kpg.generateKeyPair()
402
+ }
403
+
404
+ /**
405
+ * Get Public Key from KeyStore
406
+ */
407
+ fun getPublicKeyBase64(): String{
408
+
409
+ try {
410
+ val keyStore = KeyStore.getInstance("AndroidKeyStore")
411
+ keyStore.load(null)
412
+
413
+ // Generate key if not exists
414
+ if (!keyStore.containsAlias(KEY_ALIAS)) {
415
+ generateKeyStore()
416
+ }
417
+
418
+ // Get public key
419
+ val entry = keyStore.getEntry(
420
+ KEY_ALIAS,
421
+ null
422
+ ) as KeyStore.PrivateKeyEntry
423
+
424
+ return Base64.encodeToString(
425
+ entry.certificate.publicKey.encoded,
426
+ Base64.NO_WRAP
427
+ )
428
+ }
429
+ catch (e: Exception) {
430
+ CommonHelper.logPrint(CommonHelper.ERROR_LOG, CLASS_TAG, "Failed to generate keystore Error Message: ${e.message} and Raw Error : $e")
431
+ throw SignatureVerificationException.KeyStoreFailed(e)
432
+ }
433
+ }
434
+
435
+ /**
436
+ * Decrypt signature from bundle Url.
437
+ * @param bundleUrl The signed url
438
+ * @return plan Url to downlaod Bundle
439
+ */
440
+ fun decryptSignatureUrl(bundleUrl: String?): String {
441
+
442
+ try {
443
+
444
+ val parseUri = Uri.parse(bundleUrl)
445
+
446
+ val signatureData = parseUri.getQueryParameter("signatureData")
447
+
448
+ val encryptedKey = parseUri.getQueryParameter("itemData")
449
+
450
+ val rawIv = parseUri.getQueryParameter("raw")
451
+
452
+ //Decrypt AES Key (RSA)
453
+ val ks = KeyStore.getInstance("AndroidKeyStore")
454
+ ks.load(null)
455
+
456
+ val privateKey = (ks.getEntry(
457
+ KEY_ALIAS,
458
+ null
459
+ ) as KeyStore.PrivateKeyEntry).privateKey
460
+
461
+ val rsaCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding")
462
+ rsaCipher.init(Cipher.DECRYPT_MODE, privateKey)
463
+
464
+ val aesKeyBytes = rsaCipher.doFinal(
465
+ Base64.decode(encryptedKey, Base64.DEFAULT)
466
+ )
467
+
468
+ //Decrypt Payload (AES)
469
+ val secretKey = SecretKeySpec(aesKeyBytes, "AES")
470
+
471
+ val aesCipher = Cipher.getInstance("AES/CBC/PKCS7Padding")
472
+ aesCipher.init(
473
+ Cipher.DECRYPT_MODE,
474
+ secretKey,
475
+ IvParameterSpec(Base64.decode(rawIv, Base64.DEFAULT))
476
+ )
477
+
478
+ val finalData = aesCipher.doFinal(
479
+ Base64.decode(signatureData, Base64.DEFAULT)
480
+ )
481
+
482
+ val planData = String(finalData, Charsets.UTF_8)
483
+
484
+ return planData
485
+ }
486
+ catch (e: Exception){
487
+ CommonHelper.logPrint(CommonHelper.ERROR_LOG, CLASS_TAG, "Failed to decrypt bundleUrl Signature Url; Error Message: ${e.message} and Raw Error : $e")
488
+ return ""
489
+ }
490
+ }
491
+
353
492
  }