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.
- package/InstantpayCodePush.podspec +4 -0
- package/android/src/main/java/com/instantpaycodepush/InstantpayCodePushModule.kt +20 -2
- package/android/src/main/java/com/instantpaycodepush/IpayCodePush.kt +5 -0
- package/android/src/main/java/com/instantpaycodepush/SignatureVerifier.kt +139 -0
- package/ios/BundleFileStorageService.swift +1269 -0
- package/ios/BundleMetadata.swift +208 -0
- package/ios/DecompressService.swift +116 -0
- package/ios/FileManagerService.swift +104 -0
- package/ios/HashUtils.swift +73 -0
- package/ios/InstantpayCodePush-Bridging-Header.h +16 -0
- package/ios/InstantpayCodePush.h +39 -1
- package/ios/InstantpayCodePush.mm +332 -4
- package/ios/IpayCodePushHelper.swift +57 -0
- package/ios/IpayCodePushImpl.swift +297 -0
- package/ios/NotificationExtension.swift +13 -0
- package/ios/SignatureVerifier.swift +358 -0
- package/ios/URLSessionDownloadService.swift +251 -0
- package/ios/VersionedPreferencesService.swift +93 -0
- package/ios/ZipDecompressionStrategy.swift +175 -0
- package/lib/module/NativeInstantpayCodePush.js.map +1 -1
- package/lib/module/error.js +6 -0
- package/lib/module/error.js.map +1 -1
- package/lib/module/index.js +12 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/native.js +10 -0
- package/lib/module/native.js.map +1 -1
- package/lib/module/types.js +1 -1
- package/lib/module/types.js.map +1 -1
- package/lib/typescript/src/NativeInstantpayCodePush.d.ts +1 -0
- package/lib/typescript/src/NativeInstantpayCodePush.d.ts.map +1 -1
- package/lib/typescript/src/error.d.ts +6 -0
- package/lib/typescript/src/error.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +10 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/native.d.ts +6 -0
- package/lib/typescript/src/native.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/NativeInstantpayCodePush.ts +1 -0
- package/src/error.ts +7 -0
- package/src/index.tsx +13 -0
- package/src/native.ts +11 -0
- 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
|
-
|
|
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
|
}
|
|
@@ -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
|
}
|