@onekeyfe/react-native-bundle-update 3.0.38 → 3.0.40
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/ReactNativeBundleUpdate.podspec +13 -3
- package/android/build.gradle +11 -4
- package/android/src/main/java/com/margelo/nitro/reactnativebundleupdate/ReactNativeBundleUpdate.kt +82 -211
- package/ios/ReactNativeBundleUpdate.swift +109 -302
- package/package.json +1 -1
- package/ios/Frameworks/Gopenpgp.xcframework/Info.plist +0 -52
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Gopenpgp +0 -0
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Armor.objc.h +0 -96
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Constants.objc.h +0 -197
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Crypto.objc.h +0 -1963
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Gopenpgp.h +0 -23
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Mime.objc.h +0 -59
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Mobile.objc.h +0 -252
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Profile.objc.h +0 -107
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Universe.objc.h +0 -29
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/ref.h +0 -35
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Info.plist +0 -20
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Modules/module.modulemap +0 -13
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Gopenpgp +0 -0
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Armor.objc.h +0 -96
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Constants.objc.h +0 -197
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Crypto.objc.h +0 -1963
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Gopenpgp.h +0 -23
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Mime.objc.h +0 -59
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Mobile.objc.h +0 -252
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Profile.objc.h +0 -107
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Universe.objc.h +0 -29
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/ref.h +0 -35
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Info.plist +0 -20
- package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Modules/module.modulemap +0 -13
|
@@ -1,80 +1,12 @@
|
|
|
1
1
|
import NitroModules
|
|
2
2
|
import ReactNativeNativeLogger
|
|
3
3
|
import Foundation
|
|
4
|
-
import
|
|
5
|
-
import
|
|
4
|
+
import ReactNativeBundleCrypto
|
|
5
|
+
import ReactNativeRangeDownloader
|
|
6
6
|
import SSZipArchive
|
|
7
7
|
import MMKV
|
|
8
8
|
import UIKit
|
|
9
9
|
|
|
10
|
-
// OneKey GPG public key for signature verification
|
|
11
|
-
private let GPG_PUBLIC_KEY = """
|
|
12
|
-
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
13
|
-
|
|
14
|
-
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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-
62iuVE8f3YzSZGJPs2mmepC/OHsFAmJATGwCGwMFCQeGH0QFCwkIBwIGFQoJCAsC
|
|
27
|
-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
-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
-
D12KYAycDwanbmtoUp25gcKqCrlNeSE1EXmPlBzyiNzxJutE1DGlvbY3rbuNZLQi
|
|
30
|
-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
-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
-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
-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
-
fpqe0vKYUlT092joT0o6nT2MzmLmHUW0kDqD9p6JEJEZUZpqcSRE84eMTFNyu966
|
|
35
|
-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
-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
-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
-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
-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
-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
-
VLQnPjip0ZGeHeSX2ersXmmp7Ny2zqZr858ZIdLpamkEg6hRi5LWOOK4clnKzTLe
|
|
42
|
-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
-
IGKl+tUayKEEtq7+CrxWHwCXY+wFeeJFm2yhEMqeAZlVpsvGgtfWevQwHaRcld99
|
|
44
|
-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
-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
-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
-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
-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
-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
-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
-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
-
CPgkWQr9luk/3KjSntMjh9ySx5VJbAi2IX2X/w6Ze9hky3DeEdxRRlV0meTTGupP
|
|
53
|
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
|
|
74
|
-
=HlTQ
|
|
75
|
-
-----END PGP PUBLIC KEY BLOCK-----
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
10
|
// Public static store for AppDelegate access (called before JS starts)
|
|
79
11
|
@objcMembers
|
|
80
12
|
public class BundleUpdateStore: NSObject {
|
|
@@ -297,7 +229,7 @@ public class BundleUpdateStore: NSObject {
|
|
|
297
229
|
OneKeyLog.error("BundleUpdate", "[web-embed-verify] failed to hash file: \(relativePath)")
|
|
298
230
|
return false
|
|
299
231
|
}
|
|
300
|
-
if !
|
|
232
|
+
if !BundleCryptoCore.secureEqualHex(expected, actual) {
|
|
301
233
|
OneKeyLog.error("BundleUpdate", "[web-embed-verify] sha256 mismatch for \(relativePath)")
|
|
302
234
|
return false
|
|
303
235
|
}
|
|
@@ -344,75 +276,16 @@ public class BundleUpdateStore: NSObject {
|
|
|
344
276
|
}
|
|
345
277
|
}
|
|
346
278
|
|
|
279
|
+
/// Thin wrapper over the shared BundleCryptoCore.sha256OfFile. The streaming
|
|
280
|
+
/// CommonCrypto implementation now lives in react-native-bundle-crypto; this
|
|
281
|
+
/// preserves the existing per-thread failureReason channel
|
|
282
|
+
/// (lastSHA256FailureReason) that analytics call sites read after a nil
|
|
283
|
+
/// result. failureReason taxonomy (FILE_NOT_FOUND / FILE_DISAPPEARED /
|
|
284
|
+
/// IO_<code>) is identical because the core was ported verbatim from here.
|
|
347
285
|
public static func calculateSHA256(_ filePath: String) -> String? {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
setSHA256Failure("FILE_NOT_FOUND")
|
|
352
|
-
OneKeyLog.error("BundleUpdate", "calculateSHA256: file not found: \(filePath)")
|
|
353
|
-
return nil
|
|
354
|
-
}
|
|
355
|
-
guard let fileHandle = FileHandle(forReadingAtPath: filePath) else {
|
|
356
|
-
setSHA256Failure("FILE_DISAPPEARED")
|
|
357
|
-
OneKeyLog.error("BundleUpdate", "calculateSHA256: open failed (file disappeared between stat and open): \(filePath)")
|
|
358
|
-
return nil
|
|
359
|
-
}
|
|
360
|
-
defer { fileHandle.closeFile() }
|
|
361
|
-
|
|
362
|
-
do {
|
|
363
|
-
var context = CC_SHA256_CTX()
|
|
364
|
-
CC_SHA256_Init(&context)
|
|
365
|
-
var threwError: Error?
|
|
366
|
-
// safeRead routes reads through FileHandle.read(upToCount:) on
|
|
367
|
-
// iOS 13.4+ — that variant surfaces disk failures as throwing
|
|
368
|
-
// NSError, which we catch here as Swift `Error`. On pre-13.4
|
|
369
|
-
// OS versions safeRead falls back to readData(ofLength:),
|
|
370
|
-
// which raises NSFileHandleOperationException; Swift cannot
|
|
371
|
-
// catch ObjC NSExceptions via try/catch, so on those legacy
|
|
372
|
-
// versions a read failure will still abort the process. We
|
|
373
|
-
// accept that on the floor since 13.4+ has been the deployment
|
|
374
|
-
// target for years.
|
|
375
|
-
while autoreleasepool(invoking: {
|
|
376
|
-
do {
|
|
377
|
-
let data = try Self.safeRead(fileHandle: fileHandle, length: 8192)
|
|
378
|
-
if data.count > 0 {
|
|
379
|
-
data.withUnsafeBytes { CC_SHA256_Update(&context, $0.baseAddress, CC_LONG(data.count)) }
|
|
380
|
-
return true
|
|
381
|
-
}
|
|
382
|
-
return false
|
|
383
|
-
} catch {
|
|
384
|
-
threwError = error
|
|
385
|
-
return false
|
|
386
|
-
}
|
|
387
|
-
}) {}
|
|
388
|
-
if let err = threwError {
|
|
389
|
-
let nsErr = err as NSError
|
|
390
|
-
// Keep the failure tag low-cardinality (`IO_<code>`) so it
|
|
391
|
-
// matches the doc on lastSHA256FailureReason and stays under
|
|
392
|
-
// the analytics bucket cap. The full `domain code description`
|
|
393
|
-
// detail is still logged below for local debugging.
|
|
394
|
-
setSHA256Failure("IO_\(nsErr.code)")
|
|
395
|
-
OneKeyLog.error("BundleUpdate", "calculateSHA256: read failed: \(nsErr.domain) \(nsErr.code) \(nsErr.localizedDescription)")
|
|
396
|
-
return nil
|
|
397
|
-
}
|
|
398
|
-
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
|
|
399
|
-
CC_SHA256_Final(&hash, &context)
|
|
400
|
-
return hash.map { String(format: "%02x", $0) }.joined()
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
/// Raises throwing wrapper around FileHandle.read(upToCount:) so disk
|
|
405
|
-
/// I/O failures (truncated file, unmounted volume) become catchable Swift
|
|
406
|
-
/// errors rather than NSFileHandleOperationException.
|
|
407
|
-
private static func safeRead(fileHandle: FileHandle, length: Int) throws -> Data {
|
|
408
|
-
if #available(iOS 13.4, macOS 10.15.4, *) {
|
|
409
|
-
return try fileHandle.read(upToCount: length) ?? Data()
|
|
410
|
-
} else {
|
|
411
|
-
// Pre-iOS 13.4 fallback: classic readData(ofLength:) does not
|
|
412
|
-
// throw, but raises NSException; we cannot bridge that here so
|
|
413
|
-
// accept the legacy behavior on these old OS versions only.
|
|
414
|
-
return fileHandle.readData(ofLength: length)
|
|
415
|
-
}
|
|
286
|
+
let result = BundleCryptoCore.sha256OfFile(filePath)
|
|
287
|
+
setSHA256Failure(result.sha256 == nil ? (result.failureReason ?? "MISMATCH") : nil)
|
|
288
|
+
return result.sha256
|
|
416
289
|
}
|
|
417
290
|
|
|
418
291
|
public static func getNativeVersion() -> String? {
|
|
@@ -643,93 +516,15 @@ public class BundleUpdateStore: NSObject {
|
|
|
643
516
|
public static func readMetadataFileSha256(_ signature: String) -> String? {
|
|
644
517
|
guard !signature.isEmpty else { return nil }
|
|
645
518
|
|
|
646
|
-
// GPG cleartext signature verification is required
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
/// Verify a PGP cleartext-signed message and extract the sha256 from the signed JSON body.
|
|
655
|
-
/// Uses Gopenpgp framework (vendored xcframework).
|
|
656
|
-
/// Returns nil if verification fails.
|
|
657
|
-
public static func verifyGPGAndExtractSha256(_ signature: String) -> String? {
|
|
658
|
-
// Check if this looks like a PGP signed message
|
|
659
|
-
guard signature.contains("-----BEGIN PGP SIGNED MESSAGE-----") else {
|
|
660
|
-
return nil
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// 1. Load public key
|
|
664
|
-
guard let pubKey = CryptoKey(fromArmored: GPG_PUBLIC_KEY) else {
|
|
665
|
-
OneKeyLog.error("BundleUpdate", "Failed to parse GPG public key")
|
|
666
|
-
return nil
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// 2. Get PGP handle
|
|
670
|
-
guard let pgp = CryptoPGP() else {
|
|
671
|
-
OneKeyLog.error("BundleUpdate", "Failed to create PGPHandle")
|
|
672
|
-
return nil
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
// 3. Build verify handle: pgp.verify().verificationKey(pubKey).new()
|
|
676
|
-
guard let verifyBuilder = pgp.verify() else {
|
|
677
|
-
OneKeyLog.error("BundleUpdate", "Failed to get verify builder")
|
|
678
|
-
return nil
|
|
679
|
-
}
|
|
680
|
-
guard let builderWithKey = verifyBuilder.verificationKey(pubKey) else {
|
|
681
|
-
OneKeyLog.error("BundleUpdate", "Failed to set verification key")
|
|
682
|
-
return nil
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
let verifyHandle: any CryptoPGPVerifyProtocol
|
|
686
|
-
do {
|
|
687
|
-
verifyHandle = try builderWithKey.new()
|
|
688
|
-
} catch {
|
|
689
|
-
OneKeyLog.error("BundleUpdate", "Failed to create verify handle: \(error.localizedDescription)")
|
|
690
|
-
return nil
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// 4. Verify cleartext
|
|
694
|
-
guard let signatureData = signature.data(using: .utf8) else {
|
|
695
|
-
return nil
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
let cleartextResult: CryptoVerifyCleartextResult
|
|
699
|
-
do {
|
|
700
|
-
cleartextResult = try verifyHandle.verifyCleartext(signatureData)
|
|
701
|
-
} catch {
|
|
702
|
-
OneKeyLog.error("BundleUpdate", "GPG verification error: \(error.localizedDescription)")
|
|
703
|
-
return nil
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// 5. Check signature error
|
|
707
|
-
do {
|
|
708
|
-
try cleartextResult.signatureError()
|
|
709
|
-
} catch {
|
|
710
|
-
OneKeyLog.error("BundleUpdate", "GPG signature invalid: \(error.localizedDescription)")
|
|
711
|
-
return nil
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// 6. Get cleartext
|
|
715
|
-
guard let cleartextData = cleartextResult.cleartext() else {
|
|
716
|
-
OneKeyLog.error("BundleUpdate", "Failed to extract cleartext from GPG result")
|
|
717
|
-
return nil
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
guard let text = String(data: cleartextData, encoding: .utf8) else {
|
|
519
|
+
// GPG cleartext signature verification is required. The actual PGP
|
|
520
|
+
// verification + JSON-body sha256 extraction lives in the shared
|
|
521
|
+
// BundleCryptoCore (react-native-bundle-crypto); preserve the previous
|
|
522
|
+
// "failure => nil" semantics here.
|
|
523
|
+
let r = BundleCryptoCore.verifyGpgCleartext(signature)
|
|
524
|
+
guard r.valid, let sha256 = r.sha256 else {
|
|
525
|
+
OneKeyLog.error("BundleUpdate", "readMetadataFileSha256: GPG verification failed (\(r.reason ?? "unknown")), rejecting unsigned content")
|
|
721
526
|
return nil
|
|
722
527
|
}
|
|
723
|
-
|
|
724
|
-
// 7. Parse JSON and extract sha256
|
|
725
|
-
guard let jsonData = text.data(using: .utf8),
|
|
726
|
-
let json = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any],
|
|
727
|
-
let sha256 = json["sha256"] as? String else {
|
|
728
|
-
OneKeyLog.error("BundleUpdate", "Failed to parse cleartext JSON")
|
|
729
|
-
return nil
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
OneKeyLog.info("BundleUpdate", "GPG verification succeeded, sha256: \(sha256)")
|
|
733
528
|
return sha256
|
|
734
529
|
}
|
|
735
530
|
|
|
@@ -742,68 +537,30 @@ public class BundleUpdateStore: NSObject {
|
|
|
742
537
|
return false
|
|
743
538
|
}
|
|
744
539
|
guard let calculatedSha256 = calculateSHA256(metadataFilePath) else { return false }
|
|
745
|
-
return
|
|
540
|
+
return BundleCryptoCore.secureEqualHex(calculatedSha256, extractedSha256)
|
|
746
541
|
}
|
|
747
542
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if let attrs = enumerator.fileAttributes,
|
|
756
|
-
attrs[.type] as? FileAttributeType == .typeSymbolicLink {
|
|
757
|
-
OneKeyLog.error("BundleUpdate", "Symlink detected in extracted bundle: \(file)")
|
|
758
|
-
return false
|
|
759
|
-
}
|
|
760
|
-
let resolvedPath = (fullPath as NSString).resolvingSymlinksInPath
|
|
761
|
-
if !resolvedPath.hasPrefix(resolvedDestination) {
|
|
762
|
-
OneKeyLog.error("BundleUpdate", "Path traversal detected in extracted bundle: \(file)")
|
|
763
|
-
return false
|
|
764
|
-
}
|
|
543
|
+
/// Maps the verified metadata's relativePath -> sha256 entries into the
|
|
544
|
+
/// plain BundleCryptoCore.DirHash list consumed by verifyDirAgainstHashes.
|
|
545
|
+
/// Metadata parsing is orchestration and stays here; the crypto core only
|
|
546
|
+
/// hashes/compares.
|
|
547
|
+
private static func dirHashEntries(from metadata: [String: Any]) -> [BundleCryptoCore.DirHash] {
|
|
548
|
+
fileMetadataEntries(from: metadata).map {
|
|
549
|
+
BundleCryptoCore.DirHash(relativePath: $0.key, sha256: $0.value)
|
|
765
550
|
}
|
|
766
|
-
return true
|
|
767
551
|
}
|
|
768
552
|
|
|
769
553
|
public static func validateAllFilesInDir(_ dirPath: String, metadata: [String: Any], appVersion: String, bundleVersion: String) -> Bool {
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
var isDir: ObjCBool = false
|
|
781
|
-
if fm.fileExists(atPath: fullPath, isDirectory: &isDir), isDir.boolValue { continue }
|
|
782
|
-
|
|
783
|
-
let relativePath = fullPath.replacingOccurrences(of: jsBundleDir, with: "")
|
|
784
|
-
guard let expectedSHA256 = fileEntries[relativePath] else {
|
|
785
|
-
OneKeyLog.error("BundleUpdate", "[bundle-verify] File on disk not found in metadata: \(relativePath)")
|
|
786
|
-
return false
|
|
787
|
-
}
|
|
788
|
-
guard let actualSHA256 = calculateSHA256(fullPath) else {
|
|
789
|
-
OneKeyLog.error("BundleUpdate", "[bundle-verify] Failed to calculate SHA256 for file: \(relativePath)")
|
|
790
|
-
return false
|
|
791
|
-
}
|
|
792
|
-
if !expectedSHA256.secureCompare(actualSHA256) {
|
|
793
|
-
OneKeyLog.error("BundleUpdate", "[bundle-verify] SHA256 mismatch for \(relativePath)")
|
|
794
|
-
return false
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Verify completeness
|
|
799
|
-
for key in fileEntries.keys {
|
|
800
|
-
let expectedFilePath = jsBundleDir + key
|
|
801
|
-
if !fm.fileExists(atPath: expectedFilePath) {
|
|
802
|
-
OneKeyLog.error("BundleUpdate", "[bundle-verify] File listed in metadata but missing on disk: \(key)")
|
|
803
|
-
return false
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
return true
|
|
554
|
+
// The full per-file enumeration + sha256 + constant-time compare logic
|
|
555
|
+
// now lives in the shared BundleCryptoCore.verifyDirAgainstHashes. The
|
|
556
|
+
// dirPath passed by every caller is bundleDir()/<appVersion>-<bundleVersion>,
|
|
557
|
+
// which the core normalizes the same way the old jsBundleDir prefix did,
|
|
558
|
+
// so relativePath derivation (and thus the verification result) is
|
|
559
|
+
// identical. metadata -> entries mapping stays here as orchestration.
|
|
560
|
+
return BundleCryptoCore.verifyDirAgainstHashes(
|
|
561
|
+
dirPath: dirPath,
|
|
562
|
+
entries: dirHashEntries(from: metadata),
|
|
563
|
+
)
|
|
807
564
|
}
|
|
808
565
|
|
|
809
566
|
static func validateBundlePairCompatibility(
|
|
@@ -1012,7 +769,7 @@ public class BundleUpdateStore: NSObject {
|
|
|
1012
769
|
OneKeyLog.error("BundleUpdate", "[entry-verify] failed to hash entry: \(entry)")
|
|
1013
770
|
return false
|
|
1014
771
|
}
|
|
1015
|
-
if !
|
|
772
|
+
if !BundleCryptoCore.secureEqualHex(expected, actual) {
|
|
1016
773
|
OneKeyLog.error("BundleUpdate", "[entry-verify] sha256 mismatch for entry: \(entry)")
|
|
1017
774
|
return false
|
|
1018
775
|
}
|
|
@@ -1090,20 +847,6 @@ public class BundleUpdateStore: NSObject {
|
|
|
1090
847
|
}
|
|
1091
848
|
}
|
|
1092
849
|
|
|
1093
|
-
// Constant-time string comparison to prevent timing attacks on hash comparisons
|
|
1094
|
-
private extension String {
|
|
1095
|
-
func secureCompare(_ other: String) -> Bool {
|
|
1096
|
-
let lhs = Array(self.utf8)
|
|
1097
|
-
let rhs = Array(other.utf8)
|
|
1098
|
-
guard lhs.count == rhs.count else { return false }
|
|
1099
|
-
var result: UInt8 = 0
|
|
1100
|
-
for i in 0..<lhs.count {
|
|
1101
|
-
result |= lhs[i] ^ rhs[i]
|
|
1102
|
-
}
|
|
1103
|
-
return result == 0
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
850
|
// Version string sanitization
|
|
1108
851
|
private extension String {
|
|
1109
852
|
var isSafeVersionString: Bool {
|
|
@@ -1475,6 +1218,69 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
1475
1218
|
}
|
|
1476
1219
|
}
|
|
1477
1220
|
|
|
1221
|
+
// === Concurrent + background multi-range download (shared core) ===
|
|
1222
|
+
// Delegate to react-native-range-downloader's RangeDownloader (the
|
|
1223
|
+
// 8-range background session that originated in this module). On
|
|
1224
|
+
// success the bundle is fully assembled at filePath (downloads
|
|
1225
|
+
// continue even if the app is suspended). A `.fallback` outcome —
|
|
1226
|
+
// Range unsupported, server returned 200, file too small, or a
|
|
1227
|
+
// transient error — makes us hand off to the single-stream path
|
|
1228
|
+
// below; the range downloader keeps its `.segN` files for the next
|
|
1229
|
+
// attempt (its background session resumes them by taskId).
|
|
1230
|
+
self.sendEvent(type: "update/start")
|
|
1231
|
+
self.stateQueue.sync { self.activeDownloadFilePath = filePath }
|
|
1232
|
+
|
|
1233
|
+
// Stable, unique task id for this bundle. Same value across retry
|
|
1234
|
+
// attempts so the range downloader can resume its `.segN` files, and
|
|
1235
|
+
// it lets the progress listener below filter events to THIS download.
|
|
1236
|
+
let rangeTaskId = "\(appVersion)-\(bundleVersion)"
|
|
1237
|
+
|
|
1238
|
+
// Bridge RangeDownloader progress events back into this module's
|
|
1239
|
+
// `update/downloading` stream. We only react to "progress" events on
|
|
1240
|
+
// the bundle channel for our own task id; "start"/"complete"/
|
|
1241
|
+
// "fallback" are surfaced by this method's own sendEvent calls and
|
|
1242
|
+
// the download outcome below, so we don't double-emit them here.
|
|
1243
|
+
let progressListenerId = RangeDownloader.shared.addListener { [weak self] event in
|
|
1244
|
+
guard event.channel.stringValue == DownloadChannel.bundle.stringValue,
|
|
1245
|
+
event.taskId == rangeTaskId,
|
|
1246
|
+
event.type == "progress" else { return }
|
|
1247
|
+
self?.sendEvent(type: "update/downloading", progress: Int(event.progress))
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
let (rangeOutcome, _, rangeReason) = await RangeDownloader.shared.download(
|
|
1251
|
+
channel: .bundle,
|
|
1252
|
+
taskId: rangeTaskId,
|
|
1253
|
+
urlString: downloadUrl,
|
|
1254
|
+
filePath: filePath,
|
|
1255
|
+
// SHA256 is verified by this module right after assembly (below),
|
|
1256
|
+
// so we don't double-hash inside the range downloader.
|
|
1257
|
+
expectedSha256: nil,
|
|
1258
|
+
segmentCount: nil,
|
|
1259
|
+
minConcurrentBytes: nil
|
|
1260
|
+
)
|
|
1261
|
+
RangeDownloader.shared.removeListener(progressListenerId)
|
|
1262
|
+
|
|
1263
|
+
if rangeOutcome.stringValue == RangeDownloadOutcome.fallback.stringValue {
|
|
1264
|
+
// Concurrent path unavailable (Range unsupported / file too small /
|
|
1265
|
+
// server returned 200 / transient error). Hand off to the
|
|
1266
|
+
// single-stream path, leaving a clean slot.
|
|
1267
|
+
OneKeyLog.info("BundleUpdate", "downloadBundle: concurrent fallback (\(rangeReason ?? "")), using single-stream")
|
|
1268
|
+
RangeDownloader.shared.discardArtifacts(filePath: filePath)
|
|
1269
|
+
// fall through to the single-stream path below
|
|
1270
|
+
} else {
|
|
1271
|
+
OneKeyLog.info("BundleUpdate", "downloadBundle: concurrent finished, verifying SHA256...")
|
|
1272
|
+
if !self.verifyBundleSHA256(filePath, sha256: sha256) {
|
|
1273
|
+
let reason = BundleUpdateStore.lastSHA256FailureReason() ?? "MISMATCH"
|
|
1274
|
+
try? FileManager.default.removeItem(atPath: filePath)
|
|
1275
|
+
self.sendEvent(type: "update/error", message: "SHA256_\(reason)")
|
|
1276
|
+
throw NSError(domain: "BundleUpdate", code: -1, userInfo: [NSLocalizedDescriptionKey: "Bundle SHA256 verification failed: \(reason)"])
|
|
1277
|
+
}
|
|
1278
|
+
try? FileManager.default.removeItem(atPath: resumeDataPath)
|
|
1279
|
+
self.sendEvent(type: "update/complete")
|
|
1280
|
+
OneKeyLog.info("BundleUpdate", "downloadBundle: concurrent completed successfully, appVersion=\(appVersion), bundleVersion=\(bundleVersion)")
|
|
1281
|
+
return result
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1478
1284
|
// Download the file
|
|
1479
1285
|
guard let url = URL(string: downloadUrl) else {
|
|
1480
1286
|
OneKeyLog.error("BundleUpdate", "downloadBundle: invalid URL: \(downloadUrl)")
|
|
@@ -1495,8 +1301,8 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
1495
1301
|
return data
|
|
1496
1302
|
}()
|
|
1497
1303
|
|
|
1498
|
-
|
|
1499
|
-
OneKeyLog.info("BundleUpdate", "downloadBundle: starting download (resumeBytes=\(persistedResumeData?.count ?? 0))...")
|
|
1304
|
+
// update/start already emitted before the concurrent attempt above.
|
|
1305
|
+
OneKeyLog.info("BundleUpdate", "downloadBundle: starting single-stream download (resumeBytes=\(persistedResumeData?.count ?? 0))...")
|
|
1500
1306
|
|
|
1501
1307
|
let request = URLRequest(url: url)
|
|
1502
1308
|
|
|
@@ -1627,7 +1433,7 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
1627
1433
|
OneKeyLog.error("BundleUpdate", "verifyBundleSHA256: failed to calculate SHA256 for: \(bundlePath)")
|
|
1628
1434
|
return false
|
|
1629
1435
|
}
|
|
1630
|
-
let isValid =
|
|
1436
|
+
let isValid = BundleCryptoCore.secureEqualHex(calculated, sha256)
|
|
1631
1437
|
OneKeyLog.debug("BundleUpdate", "verifyBundleSHA256: path=\(bundlePath), expected=\(sha256.prefix(16))..., calculated=\(calculated.prefix(16))..., valid=\(isValid)")
|
|
1632
1438
|
return isValid
|
|
1633
1439
|
}
|
|
@@ -1668,7 +1474,7 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
1668
1474
|
if !skipGPG {
|
|
1669
1475
|
OneKeyLog.info("BundleUpdate", "verifyBundleASC: verifying SHA256 of downloaded file...")
|
|
1670
1476
|
let calculated = BundleUpdateStore.calculateSHA256(filePath)
|
|
1671
|
-
let isValid = calculated != nil && calculated
|
|
1477
|
+
let isValid = calculated != nil && BundleCryptoCore.secureEqualHex(calculated!, sha256)
|
|
1672
1478
|
if !isValid {
|
|
1673
1479
|
// Promote the SHA256 subtype (FILE_TRUNCATED / OOM /
|
|
1674
1480
|
// IO_<class> / MISMATCH) into the thrown message so
|
|
@@ -1720,7 +1526,7 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
1720
1526
|
|
|
1721
1527
|
// Validate extracted paths (symlinks, path traversal)
|
|
1722
1528
|
OneKeyLog.info("BundleUpdate", "verifyBundleASC: validating extracted path safety...")
|
|
1723
|
-
if !
|
|
1529
|
+
if !BundleCryptoCore.validateExtractedPathSafety(destination) {
|
|
1724
1530
|
OneKeyLog.error("BundleUpdate", "verifyBundleASC: path traversal or symlink attack detected")
|
|
1725
1531
|
try? FileManager.default.removeItem(atPath: destination)
|
|
1726
1532
|
throw NSError(domain: "BundleUpdate", code: -1, userInfo: [NSLocalizedDescriptionKey: "Path traversal or symlink attack detected"])
|
|
@@ -1782,7 +1588,7 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
1782
1588
|
OneKeyLog.error("BundleUpdate", "verifyBundle: failed to calculate SHA256 for file=\(filePath)")
|
|
1783
1589
|
throw NSError(domain: "BundleUpdate", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to calculate SHA256"])
|
|
1784
1590
|
}
|
|
1785
|
-
guard
|
|
1591
|
+
guard BundleCryptoCore.secureEqualHex(calculated, sha256) else {
|
|
1786
1592
|
OneKeyLog.error("BundleUpdate", "verifyBundle: SHA256 mismatch, expected=\(sha256.prefix(16))..., got=\(calculated.prefix(16))...")
|
|
1787
1593
|
throw NSError(domain: "BundleUpdate", code: -1, userInfo: [NSLocalizedDescriptionKey: "SHA256 verification failed"])
|
|
1788
1594
|
}
|
|
@@ -2110,7 +1916,8 @@ class ReactNativeBundleUpdate: HybridReactNativeBundleUpdateSpec {
|
|
|
2110
1916
|
=A/Ii
|
|
2111
1917
|
-----END PGP SIGNATURE-----
|
|
2112
1918
|
"""
|
|
2113
|
-
let
|
|
1919
|
+
let gpg = BundleCryptoCore.verifyGpgCleartext(testSignature)
|
|
1920
|
+
let result = gpg.valid ? gpg.sha256 : nil
|
|
2114
1921
|
let isValid = result == "2ada9c871104fc40649fa3de67a7d8e33faadc18e9abd587e8bb85be0a003eba"
|
|
2115
1922
|
OneKeyLog.info("BundleUpdate", "testVerification: GPG verification result: \(isValid)")
|
|
2116
1923
|
return isValid
|
package/package.json
CHANGED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
-
<plist version="1.0">
|
|
4
|
-
<dict>
|
|
5
|
-
<key>AvailableLibraries</key>
|
|
6
|
-
<array>
|
|
7
|
-
<dict>
|
|
8
|
-
<key>BinaryPath</key>
|
|
9
|
-
<string>Gopenpgp.framework/Gopenpgp</string>
|
|
10
|
-
<key>LibraryIdentifier</key>
|
|
11
|
-
<string>ios-arm64_x86_64-simulator</string>
|
|
12
|
-
<key>LibraryPath</key>
|
|
13
|
-
<string>Gopenpgp.framework</string>
|
|
14
|
-
<key>SupportedArchitectures</key>
|
|
15
|
-
<array>
|
|
16
|
-
<string>arm64</string>
|
|
17
|
-
<string>x86_64</string>
|
|
18
|
-
</array>
|
|
19
|
-
<key>SupportedPlatform</key>
|
|
20
|
-
<string>ios</string>
|
|
21
|
-
<key>SupportedPlatformVariant</key>
|
|
22
|
-
<string>simulator</string>
|
|
23
|
-
<key>MinimumOSVersion</key>
|
|
24
|
-
<string>15.5</string>
|
|
25
|
-
</dict>
|
|
26
|
-
<dict>
|
|
27
|
-
<key>BinaryPath</key>
|
|
28
|
-
<string>Gopenpgp.framework/Gopenpgp</string>
|
|
29
|
-
<key>LibraryIdentifier</key>
|
|
30
|
-
<string>ios-arm64</string>
|
|
31
|
-
<key>LibraryPath</key>
|
|
32
|
-
<string>Gopenpgp.framework</string>
|
|
33
|
-
<key>SupportedArchitectures</key>
|
|
34
|
-
<array>
|
|
35
|
-
<string>arm64</string>
|
|
36
|
-
</array>
|
|
37
|
-
<key>SupportedPlatform</key>
|
|
38
|
-
<string>ios</string>
|
|
39
|
-
<key>MinimumOSVersion</key>
|
|
40
|
-
<string>15.5</string>
|
|
41
|
-
</dict>
|
|
42
|
-
</array>
|
|
43
|
-
<key>CFBundlePackageType</key>
|
|
44
|
-
<string>XFWK</string>
|
|
45
|
-
<key>RCTNewArchEnabled</key>
|
|
46
|
-
<true/>
|
|
47
|
-
<key>XCFrameworkFormatVersion</key>
|
|
48
|
-
<string>1.0</string>
|
|
49
|
-
<key>MinimumOSVersion</key>
|
|
50
|
-
<string>15.5</string>
|
|
51
|
-
</dict>
|
|
52
|
-
</plist>
|