@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.
Files changed (30) hide show
  1. package/ReactNativeBundleUpdate.podspec +13 -3
  2. package/android/build.gradle +11 -4
  3. package/android/src/main/java/com/margelo/nitro/reactnativebundleupdate/ReactNativeBundleUpdate.kt +82 -211
  4. package/ios/ReactNativeBundleUpdate.swift +109 -302
  5. package/package.json +1 -1
  6. package/ios/Frameworks/Gopenpgp.xcframework/Info.plist +0 -52
  7. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Gopenpgp +0 -0
  8. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Armor.objc.h +0 -96
  9. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Constants.objc.h +0 -197
  10. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Crypto.objc.h +0 -1963
  11. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Gopenpgp.h +0 -23
  12. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Mime.objc.h +0 -59
  13. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Mobile.objc.h +0 -252
  14. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Profile.objc.h +0 -107
  15. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/Universe.objc.h +0 -29
  16. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Headers/ref.h +0 -35
  17. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Info.plist +0 -20
  18. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64/Gopenpgp.framework/Modules/module.modulemap +0 -13
  19. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Gopenpgp +0 -0
  20. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Armor.objc.h +0 -96
  21. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Constants.objc.h +0 -197
  22. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Crypto.objc.h +0 -1963
  23. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Gopenpgp.h +0 -23
  24. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Mime.objc.h +0 -59
  25. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Mobile.objc.h +0 -252
  26. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Profile.objc.h +0 -107
  27. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/Universe.objc.h +0 -29
  28. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Headers/ref.h +0 -35
  29. package/ios/Frameworks/Gopenpgp.xcframework/ios-arm64_x86_64-simulator/Gopenpgp.framework/Info.plist +0 -20
  30. 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 CommonCrypto
5
- import Gopenpgp
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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
- 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
- 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
- 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
- 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
- UTFBG3hk6JgsaXkFCwSmF95uATAaItv8aw6eY7RWv47rXhQch6PBMCir4+a/v7vs
31
- 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
- ttXQFm3dkOfD+67MLMO3dd7nPhxtjVEGi+43UH3/cdtmU4JFX3pyCQpKIlXTEGp2
33
- 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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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
- 5ousZOOqsCkl1J7rCeaIFowIEA3TzH0FWIQGahGiHN/+zwc7iSIL9gNEq4/AYJWK
45
- 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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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
- WMwdZUAvnaCVcs35IkFI2xyZZkMqdWdGeuf6QES85ZmAtuLgyk+I1XCbY8aeu0/O
66
- 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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 !expected.secureCompare(actual) {
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
- setSHA256Failure(nil)
349
- let fm = FileManager.default
350
- if !fm.fileExists(atPath: filePath) {
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
- guard let sha256 = verifyGPGAndExtractSha256(signature) else {
648
- OneKeyLog.error("BundleUpdate", "readMetadataFileSha256: GPG verification failed, rejecting unsigned content")
649
- return nil
650
- }
651
- return sha256
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 calculatedSha256.secureCompare(extractedSha256)
540
+ return BundleCryptoCore.secureEqualHex(calculatedSha256, extractedSha256)
746
541
  }
747
542
 
748
- public static func validateExtractedPathSafety(_ destination: String) -> Bool {
749
- let fm = FileManager.default
750
- let resolvedDestination = (destination as NSString).resolvingSymlinksInPath
751
-
752
- guard let enumerator = fm.enumerator(atPath: destination) else { return true }
753
- while let file = enumerator.nextObject() as? String {
754
- let fullPath = (destination as NSString).appendingPathComponent(file)
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
- let parentBundleDir = bundleDir()
771
- let folderName = "\(appVersion)-\(bundleVersion)"
772
- let jsBundleDir = (parentBundleDir as NSString).appendingPathComponent(folderName) + "/"
773
- let fm = FileManager.default
774
- let fileEntries = fileMetadataEntries(from: metadata)
775
-
776
- guard let enumerator = fm.enumerator(atPath: dirPath) else { return false }
777
- while let file = enumerator.nextObject() as? String {
778
- if file.contains("metadata.json") || file.contains(".DS_Store") { continue }
779
- let fullPath = (dirPath as NSString).appendingPathComponent(file)
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 !expected.secureCompare(actual) {
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
- self.sendEvent(type: "update/start")
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 = calculated.secureCompare(sha256)
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!.secureCompare(sha256)
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 !BundleUpdateStore.validateExtractedPathSafety(destination) {
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 calculated.secureCompare(sha256) else {
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 result = BundleUpdateStore.verifyGPGAndExtractSha256(testSignature)
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-bundle-update",
3
- "version": "3.0.38",
3
+ "version": "3.0.40",
4
4
  "description": "react-native-bundle-update",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -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>