@capgo/capacitor-updater 7.0.36 → 7.0.38
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/CapgoCapacitorUpdater.podspec +1 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +0 -18
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +22 -8
- package/ios/Plugin/AES.swift +66 -0
- package/ios/Plugin/BigInt.swift +55 -0
- package/ios/Plugin/CapacitorUpdater.swift +13 -6
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +3 -3
- package/ios/Plugin/CryptoCipherV2.swift +97 -225
- package/ios/Plugin/RSA.swift +273 -0
- package/package.json +1 -1
|
@@ -57,7 +57,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
57
57
|
private static final String statsUrlDefault = "https://plugin.capgo.app/stats";
|
|
58
58
|
private static final String channelUrlDefault = "https://plugin.capgo.app/channel_self";
|
|
59
59
|
|
|
60
|
-
private final String PLUGIN_VERSION = "7.0.
|
|
60
|
+
private final String PLUGIN_VERSION = "7.0.38";
|
|
61
61
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
62
62
|
|
|
63
63
|
private SharedPreferences.Editor editor;
|
|
@@ -192,24 +192,6 @@ public class CryptoCipherV2 {
|
|
|
192
192
|
}
|
|
193
193
|
}
|
|
194
194
|
|
|
195
|
-
public static String decryptChecksum(String checksum, String publicKey, String version) throws IOException {
|
|
196
|
-
if (publicKey.isEmpty()) {
|
|
197
|
-
Log.e(CapacitorUpdater.TAG, "The public key is empty");
|
|
198
|
-
return checksum;
|
|
199
|
-
}
|
|
200
|
-
try {
|
|
201
|
-
byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
|
|
202
|
-
PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
|
|
203
|
-
byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
|
|
204
|
-
// return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
205
|
-
String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
206
|
-
return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
|
|
207
|
-
} catch (GeneralSecurityException e) {
|
|
208
|
-
Log.e(CapacitorUpdater.TAG, "decryptChecksum fail: " + e.getMessage());
|
|
209
|
-
throw new IOException("Decryption failed: " + e.getMessage());
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
195
|
public static String calcChecksum(File file) {
|
|
214
196
|
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
215
197
|
MessageDigest digest;
|
|
@@ -377,8 +377,16 @@ public class DownloadService extends Worker {
|
|
|
377
377
|
|
|
378
378
|
Request request = new Request.Builder().url(downloadUrl).build();
|
|
379
379
|
|
|
380
|
+
// Check if file is a Brotli file
|
|
381
|
+
boolean isBrotli = targetFile.getName().endsWith(".br");
|
|
382
|
+
|
|
383
|
+
// Create final target file with .br extension removed if it's a Brotli file
|
|
384
|
+
File finalTargetFile = isBrotli
|
|
385
|
+
? new File(targetFile.getParentFile(), targetFile.getName().substring(0, targetFile.getName().length() - 3))
|
|
386
|
+
: targetFile;
|
|
387
|
+
|
|
380
388
|
// Create a temporary file for the compressed data
|
|
381
|
-
File compressedFile = new File(getApplicationContext().getCacheDir(), "temp_" + targetFile.getName() + ".
|
|
389
|
+
File compressedFile = new File(getApplicationContext().getCacheDir(), "temp_" + targetFile.getName() + ".tmp");
|
|
382
390
|
|
|
383
391
|
try (Response response = client.newCall(request).execute()) {
|
|
384
392
|
if (!response.isSuccessful()) {
|
|
@@ -405,21 +413,27 @@ public class DownloadService extends Worker {
|
|
|
405
413
|
CryptoCipherV2.decryptFile(compressedFile, publicKey, sessionKey);
|
|
406
414
|
}
|
|
407
415
|
|
|
408
|
-
//
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
416
|
+
// Only decompress if file has .br extension
|
|
417
|
+
if (isBrotli) {
|
|
418
|
+
// Use new decompression method
|
|
419
|
+
byte[] compressedData = Files.readAllBytes(compressedFile.toPath());
|
|
420
|
+
byte[] decompressedData = decompressBrotli(compressedData, targetFile.getName());
|
|
421
|
+
Files.write(finalTargetFile.toPath(), decompressedData);
|
|
422
|
+
} else {
|
|
423
|
+
// Just copy the file without decompression
|
|
424
|
+
Files.copy(compressedFile.toPath(), finalTargetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING);
|
|
425
|
+
}
|
|
412
426
|
|
|
413
427
|
// Delete the compressed file
|
|
414
428
|
compressedFile.delete();
|
|
415
|
-
String calculatedHash = CryptoCipherV2.calcChecksum(
|
|
429
|
+
String calculatedHash = CryptoCipherV2.calcChecksum(finalTargetFile);
|
|
416
430
|
|
|
417
431
|
// Verify checksum
|
|
418
432
|
if (calculatedHash.equals(expectedHash)) {
|
|
419
433
|
// Only cache if checksum is correct
|
|
420
|
-
copyFile(
|
|
434
|
+
copyFile(finalTargetFile, cacheFile);
|
|
421
435
|
} else {
|
|
422
|
-
|
|
436
|
+
finalTargetFile.delete();
|
|
423
437
|
throw new IOException(
|
|
424
438
|
"Checksum verification failed for: " +
|
|
425
439
|
downloadUrl +
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import BigInt
|
|
2
|
+
import CommonCrypto
|
|
3
|
+
import CryptoKit
|
|
4
|
+
|
|
5
|
+
///
|
|
6
|
+
/// Constants
|
|
7
|
+
///
|
|
8
|
+
private enum AESConstants {
|
|
9
|
+
static let aesAlgorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
|
10
|
+
static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// We do all this stuff because ios is shit and open source libraries allow to do decryption with public key
|
|
14
|
+
// So we have to do it manually, while in nodejs or Java it's ok and done at language level.
|
|
15
|
+
|
|
16
|
+
///
|
|
17
|
+
/// The AES key. Contains both the initialization vector and secret key.
|
|
18
|
+
///
|
|
19
|
+
public struct AES128Key {
|
|
20
|
+
/// Initialization vector
|
|
21
|
+
private let iv: Data
|
|
22
|
+
private let aes128Key: Data
|
|
23
|
+
#if DEBUG
|
|
24
|
+
public var __debug_iv: Data { iv }
|
|
25
|
+
public var __debug_aes128Key: Data { aes128Key }
|
|
26
|
+
#endif
|
|
27
|
+
init(iv: Data, aes128Key: Data) {
|
|
28
|
+
self.iv = iv
|
|
29
|
+
self.aes128Key = aes128Key
|
|
30
|
+
}
|
|
31
|
+
///
|
|
32
|
+
/// Takes the data and uses the private key to decrypt it. Will call `CCCrypt` in CommonCrypto
|
|
33
|
+
/// and provide it `ivData` for the initialization vector. Will use cipher block chaining (CBC) as
|
|
34
|
+
/// the mode of operation.
|
|
35
|
+
///
|
|
36
|
+
/// Returns the decrypted data.
|
|
37
|
+
///
|
|
38
|
+
public func decrypt(data: Data) -> Data? {
|
|
39
|
+
let encryptedData: UnsafePointer<UInt8> = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
|
40
|
+
let encryptedDataLength: Int = data.count
|
|
41
|
+
|
|
42
|
+
if let result: NSMutableData = NSMutableData(length: encryptedDataLength) {
|
|
43
|
+
let keyData: UnsafePointer<UInt8> = (self.aes128Key as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.aes128Key.count)
|
|
44
|
+
let keyLength: size_t = size_t(self.aes128Key.count)
|
|
45
|
+
let ivData: UnsafePointer<UInt8> = (iv as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.iv.count)
|
|
46
|
+
|
|
47
|
+
let decryptedData: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(result.mutableBytes.assumingMemoryBound(to: UInt8.self))
|
|
48
|
+
let decryptedDataLength: size_t = size_t(result.length)
|
|
49
|
+
|
|
50
|
+
var decryptedLength: size_t = 0
|
|
51
|
+
|
|
52
|
+
let status: CCCryptorStatus = CCCrypt(CCOperation(kCCDecrypt), AESConstants.aesAlgorithm, AESConstants.aesOptions, keyData, keyLength, ivData, encryptedData, encryptedDataLength, decryptedData, decryptedDataLength, &decryptedLength)
|
|
53
|
+
|
|
54
|
+
if Int32(status) == Int32(kCCSuccess) {
|
|
55
|
+
result.length = Int(decryptedLength)
|
|
56
|
+
return result as Data
|
|
57
|
+
} else {
|
|
58
|
+
print("\(CapacitorUpdater.TAG) AES decryption failed with status: \(status)")
|
|
59
|
+
return nil
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
print("\(CapacitorUpdater.TAG) Failed to allocate memory for AES decryption")
|
|
63
|
+
return nil
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import BigInt
|
|
2
|
+
|
|
3
|
+
// Extension to serialize BigInt to bytes array
|
|
4
|
+
extension BigInt {
|
|
5
|
+
func serializeToBytes() -> [UInt8] {
|
|
6
|
+
let byteCount = (self.bitWidth + 7) / 8
|
|
7
|
+
var bytes = [UInt8](repeating: 0, count: byteCount)
|
|
8
|
+
|
|
9
|
+
var value = self
|
|
10
|
+
for i in 0..<byteCount {
|
|
11
|
+
bytes[byteCount - i - 1] = UInt8(truncatingIfNeeded: value & 0xFF)
|
|
12
|
+
value >>= 8
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return bytes
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Add this custom power function to ensure safer handling of power operations
|
|
20
|
+
|
|
21
|
+
// Manual exponentiation using the square-and-multiply algorithm
|
|
22
|
+
// which is more efficient and avoids using the built-in functions that might handle BigInt differently
|
|
23
|
+
extension BigInt {
|
|
24
|
+
func manualPower(_ exponent: BigInt, modulus: BigInt) -> BigInt {
|
|
25
|
+
// Quick checks
|
|
26
|
+
if modulus == 0 {
|
|
27
|
+
return 0
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if exponent == 0 {
|
|
31
|
+
return 1
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
guard let base = self.magnitude as? BigUInt,
|
|
35
|
+
let exp = exponent.magnitude as? BigUInt,
|
|
36
|
+
let mod = modulus.magnitude as? BigUInt else {
|
|
37
|
+
return 0
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Square and multiply algorithm for modular exponentiation
|
|
41
|
+
var result = BigUInt(1)
|
|
42
|
+
var x = base % mod
|
|
43
|
+
var e = exp
|
|
44
|
+
|
|
45
|
+
while e > 0 {
|
|
46
|
+
if e & 1 == 1 {
|
|
47
|
+
result = (result * x) % mod
|
|
48
|
+
}
|
|
49
|
+
x = (x * x) % mod
|
|
50
|
+
e >>= 1
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return BigInt(result)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -340,7 +340,7 @@ import UIKit
|
|
|
340
340
|
|
|
341
341
|
if !self.publicKey.isEmpty && !sessionKey.isEmpty {
|
|
342
342
|
do {
|
|
343
|
-
fileHash = try CryptoCipherV2.decryptChecksum(checksum: fileHash, publicKey: self.publicKey
|
|
343
|
+
fileHash = try CryptoCipherV2.decryptChecksum(checksum: fileHash, publicKey: self.publicKey)
|
|
344
344
|
} catch {
|
|
345
345
|
downloadError = error
|
|
346
346
|
print("\(CapacitorUpdater.TAG) CryptoCipherV2.decryptChecksum error \(id) \(fileName) error: \(error)")
|
|
@@ -403,11 +403,18 @@ import UIKit
|
|
|
403
403
|
try FileManager.default.removeItem(at: tempFile)
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
//
|
|
407
|
-
|
|
408
|
-
|
|
406
|
+
// Check if file has .br extension for Brotli decompression
|
|
407
|
+
let isBrotli = fileName.hasSuffix(".br")
|
|
408
|
+
let finalFileName = isBrotli ? String(fileName.dropLast(3)) : fileName
|
|
409
|
+
let destFilePath = destFolder.appendingPathComponent(finalFileName)
|
|
410
|
+
|
|
411
|
+
if isBrotli {
|
|
412
|
+
// Decompress the Brotli data
|
|
413
|
+
guard let decompressedData = self.decompressBrotli(data: finalData, fileName: fileName) else {
|
|
414
|
+
throw NSError(domain: "BrotliDecompressionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress Brotli data for file \(fileName) at url \(downloadUrl)"])
|
|
415
|
+
}
|
|
416
|
+
finalData = decompressedData
|
|
409
417
|
}
|
|
410
|
-
finalData = decompressedData
|
|
411
418
|
|
|
412
419
|
try finalData.write(to: destFilePath)
|
|
413
420
|
if !self.publicKey.isEmpty && !sessionKey.isEmpty {
|
|
@@ -423,7 +430,7 @@ import UIKit
|
|
|
423
430
|
|
|
424
431
|
completedFiles += 1
|
|
425
432
|
self.notifyDownload(id: id, percent: self.calcTotalPercent(percent: Int((Double(completedFiles) / Double(totalFiles)) * 100), min: 10, max: 70))
|
|
426
|
-
print("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) downloaded, decompressed\(!self.publicKey.isEmpty && !sessionKey.isEmpty ? ", decrypted" : ""), and cached")
|
|
433
|
+
print("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) downloaded\(isBrotli ? ", decompressed" : "")\(!self.publicKey.isEmpty && !sessionKey.isEmpty ? ", decrypted" : ""), and cached")
|
|
427
434
|
} catch {
|
|
428
435
|
downloadError = error
|
|
429
436
|
NSLog("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) error: \(error.localizedDescription)")
|
|
@@ -45,7 +45,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
45
45
|
CAPPluginMethod(name: "getNextBundle", returnType: CAPPluginReturnPromise)
|
|
46
46
|
]
|
|
47
47
|
public var implementation = CapacitorUpdater()
|
|
48
|
-
private let PLUGIN_VERSION: String = "7.0.
|
|
48
|
+
private let PLUGIN_VERSION: String = "7.0.38"
|
|
49
49
|
static let updateUrlDefault = "https://plugin.capgo.app/updates"
|
|
50
50
|
static let statsUrlDefault = "https://plugin.capgo.app/stats"
|
|
51
51
|
static let channelUrlDefault = "https://plugin.capgo.app/channel_self"
|
|
@@ -291,7 +291,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
291
291
|
DispatchQueue.global(qos: .background).async {
|
|
292
292
|
do {
|
|
293
293
|
let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
|
|
294
|
-
checksum = try CryptoCipherV2.decryptChecksum(checksum: checksum, publicKey: self.implementation.publicKey
|
|
294
|
+
checksum = try CryptoCipherV2.decryptChecksum(checksum: checksum, publicKey: self.implementation.publicKey)
|
|
295
295
|
if (checksum != "" || self.implementation.publicKey != "") && next.getChecksum() != checksum {
|
|
296
296
|
print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), checksum)
|
|
297
297
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -805,7 +805,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
|
805
805
|
self.endBackGroundTaskWithNotif(msg: "Latest version is in error state. Aborting update.", latestVersionName: latestVersionName, current: current)
|
|
806
806
|
return
|
|
807
807
|
}
|
|
808
|
-
res.checksum = try CryptoCipherV2.decryptChecksum(checksum: res.checksum, publicKey: self.implementation.publicKey
|
|
808
|
+
res.checksum = try CryptoCipherV2.decryptChecksum(checksum: res.checksum, publicKey: self.implementation.publicKey)
|
|
809
809
|
if res.checksum != "" && next.getChecksum() != res.checksum && res.manifest == nil {
|
|
810
810
|
print("\(CapacitorUpdater.TAG) Error checksum", next.getChecksum(), res.checksum)
|
|
811
811
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -5,235 +5,40 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import Foundation
|
|
8
|
-
import CommonCrypto
|
|
9
8
|
import CryptoKit
|
|
9
|
+
import BigInt
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
/// Constants
|
|
13
|
-
///
|
|
14
|
-
private enum CryptoCipherConstants {
|
|
15
|
-
static let rsaKeySizeInBits: NSNumber = 2048
|
|
16
|
-
static let aesAlgorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
|
17
|
-
static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
|
|
18
|
-
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
19
|
-
}
|
|
20
|
-
///
|
|
21
|
-
/// The AES key. Contains both the initialization vector and secret key.
|
|
22
|
-
///
|
|
23
|
-
public struct AES128Key {
|
|
24
|
-
/// Initialization vector
|
|
25
|
-
private let iv: Data
|
|
26
|
-
private let aes128Key: Data
|
|
27
|
-
#if DEBUG
|
|
28
|
-
public var __debug_iv: Data { iv }
|
|
29
|
-
public var __debug_aes128Key: Data { aes128Key }
|
|
30
|
-
#endif
|
|
31
|
-
init(iv: Data, aes128Key: Data) {
|
|
32
|
-
self.iv = iv
|
|
33
|
-
self.aes128Key = aes128Key
|
|
34
|
-
}
|
|
35
|
-
///
|
|
36
|
-
/// Takes the data and uses the private key to decrypt it. Will call `CCCrypt` in CommonCrypto
|
|
37
|
-
/// and provide it `ivData` for the initialization vector. Will use cipher block chaining (CBC) as
|
|
38
|
-
/// the mode of operation.
|
|
39
|
-
///
|
|
40
|
-
/// Returns the decrypted data.
|
|
41
|
-
///
|
|
42
|
-
public func decrypt(data: Data) -> Data? {
|
|
43
|
-
let encryptedData: UnsafePointer<UInt8> = (data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count)
|
|
44
|
-
let encryptedDataLength: Int = data.count
|
|
45
|
-
|
|
46
|
-
if let result: NSMutableData = NSMutableData(length: encryptedDataLength) {
|
|
47
|
-
let keyData: UnsafePointer<UInt8> = (self.aes128Key as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.aes128Key.count)
|
|
48
|
-
let keyLength: size_t = size_t(self.aes128Key.count)
|
|
49
|
-
let ivData: UnsafePointer<UInt8> = (iv as NSData).bytes.bindMemory(to: UInt8.self, capacity: self.iv.count)
|
|
50
|
-
|
|
51
|
-
let decryptedData: UnsafeMutablePointer<UInt8> = UnsafeMutablePointer<UInt8>(result.mutableBytes.assumingMemoryBound(to: UInt8.self))
|
|
52
|
-
let decryptedDataLength: size_t = size_t(result.length)
|
|
53
|
-
|
|
54
|
-
var decryptedLength: size_t = 0
|
|
55
|
-
|
|
56
|
-
let status: CCCryptorStatus = CCCrypt(CCOperation(kCCDecrypt), CryptoCipherConstants.aesAlgorithm, CryptoCipherConstants.aesOptions, keyData, keyLength, ivData, encryptedData, encryptedDataLength, decryptedData, decryptedDataLength, &decryptedLength)
|
|
11
|
+
public struct CryptoCipherV2 {
|
|
57
12
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return nil
|
|
63
|
-
}
|
|
64
|
-
} else {
|
|
65
|
-
return nil
|
|
13
|
+
public static func decryptChecksum(checksum: String, publicKey: String) throws -> String {
|
|
14
|
+
if publicKey.isEmpty {
|
|
15
|
+
print("\(CapacitorUpdater.TAG) The public key is empty")
|
|
16
|
+
return checksum
|
|
66
17
|
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
///
|
|
71
|
-
/// The RSA public key.
|
|
72
|
-
///
|
|
73
|
-
public struct RSAPublicKey {
|
|
74
|
-
private let publicKey: SecKey
|
|
75
|
-
|
|
76
|
-
#if DEBUG
|
|
77
|
-
public var __debug_publicKey: SecKey { self.publicKey }
|
|
78
|
-
#endif
|
|
79
|
-
|
|
80
|
-
fileprivate init(publicKey: SecKey) {
|
|
81
|
-
self.publicKey = publicKey
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
///
|
|
85
|
-
/// Takes the data and uses the public key to decrypt it.
|
|
86
|
-
/// Returns the decrypted data.
|
|
87
|
-
///
|
|
88
|
-
public func decrypt(data: Data) -> Data? {
|
|
89
18
|
do {
|
|
90
|
-
guard let
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
return decryptedData
|
|
95
|
-
} catch {
|
|
96
|
-
print("Error decrypting data: \(error)")
|
|
97
|
-
return nil
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
///
|
|
102
|
-
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
103
|
-
///
|
|
104
|
-
public static func load(rsaPublicKey: String) -> RSAPublicKey? {
|
|
105
|
-
var pubKey: String = rsaPublicKey
|
|
106
|
-
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
|
|
107
|
-
pubKey = pubKey.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
|
|
108
|
-
pubKey = pubKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
109
|
-
pubKey = pubKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
110
|
-
do {
|
|
111
|
-
guard let rsaPublicKeyData: Data = Data(base64Encoded: String(pubKey)) else {
|
|
19
|
+
guard let checksumBytes = Data(base64Encoded: checksum) else {
|
|
20
|
+
print("\(CapacitorUpdater.TAG) Cannot decode checksum as base64: \(checksum)")
|
|
112
21
|
throw CustomError.cannotDecode
|
|
113
22
|
}
|
|
114
23
|
|
|
115
|
-
|
|
24
|
+
if checksumBytes.isEmpty {
|
|
25
|
+
print("\(CapacitorUpdater.TAG) Decoded checksum is empty")
|
|
116
26
|
throw CustomError.cannotDecode
|
|
117
27
|
}
|
|
118
28
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
print("Error load RSA: \(error)")
|
|
122
|
-
return nil
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// code is copied from here: https://github.com/btnguyen2k/swiftutils/blob/88494f4c635b6c6d42ef0fb30a7d666acd38c4fa/SwiftUtils/RSAUtils.swift#L393
|
|
127
|
-
public static func decryptWithRSAKey(_ encryptedData: Data, rsaKeyRef: SecKey, padding: SecPadding) -> Data? {
|
|
128
|
-
let blockSize = SecKeyGetBlockSize(rsaKeyRef)
|
|
129
|
-
let dataSize = encryptedData.count / MemoryLayout<UInt8>.size
|
|
130
|
-
|
|
131
|
-
var encryptedDataAsArray = [UInt8](repeating: 0, count: dataSize)
|
|
132
|
-
(encryptedData as NSData).getBytes(&encryptedDataAsArray, length: dataSize)
|
|
133
|
-
|
|
134
|
-
var decryptedData = [UInt8](repeating: 0, count: 0)
|
|
135
|
-
var idx = 0
|
|
136
|
-
while idx < encryptedDataAsArray.count {
|
|
137
|
-
var idxEnd = idx + blockSize
|
|
138
|
-
if idxEnd > encryptedDataAsArray.count {
|
|
139
|
-
idxEnd = encryptedDataAsArray.count
|
|
140
|
-
}
|
|
141
|
-
var chunkData = [UInt8](repeating: 0, count: blockSize)
|
|
142
|
-
for i in idx..<idxEnd {
|
|
143
|
-
chunkData[i-idx] = encryptedDataAsArray[i]
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
var decryptedDataBuffer = [UInt8](repeating: 0, count: blockSize)
|
|
147
|
-
var decryptedDataLength = blockSize
|
|
148
|
-
|
|
149
|
-
let status = SecKeyDecrypt(rsaKeyRef, padding, chunkData, idxEnd-idx, &decryptedDataBuffer, &decryptedDataLength)
|
|
150
|
-
if status != noErr {
|
|
151
|
-
return nil
|
|
152
|
-
}
|
|
153
|
-
let finalData = removePadding(decryptedDataBuffer)
|
|
154
|
-
decryptedData += finalData
|
|
155
|
-
|
|
156
|
-
idx += blockSize
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return Data(decryptedData)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// code is copied from here: https://github.com/btnguyen2k/swiftutils/blob/88494f4c635b6c6d42ef0fb30a7d666acd38c4fa/SwiftUtils/RSAUtils.swift#L429
|
|
163
|
-
private static func removePadding(_ data: [UInt8]) -> [UInt8] {
|
|
164
|
-
var idxFirstZero = -1
|
|
165
|
-
var idxNextZero = data.count
|
|
166
|
-
for i in 0..<data.count {
|
|
167
|
-
if data[i] == 0 {
|
|
168
|
-
if idxFirstZero < 0 {
|
|
169
|
-
idxFirstZero = i
|
|
170
|
-
} else {
|
|
171
|
-
idxNextZero = i
|
|
172
|
-
break
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
if idxNextZero-idxFirstZero-1 == 0 {
|
|
177
|
-
idxNextZero = idxFirstZero
|
|
178
|
-
idxFirstZero = -1
|
|
179
|
-
}
|
|
180
|
-
var newData = [UInt8](repeating: 0, count: idxNextZero-idxFirstZero-1)
|
|
181
|
-
for i in idxFirstZero+1..<idxNextZero {
|
|
182
|
-
newData[i-idxFirstZero-1] = data[i]
|
|
183
|
-
}
|
|
184
|
-
return newData
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
fileprivate extension SecKey {
|
|
189
|
-
func exportToData() -> Data? {
|
|
190
|
-
var error: Unmanaged<CFError>?
|
|
191
|
-
if let cfData: CFData = SecKeyCopyExternalRepresentation(self, &error) {
|
|
192
|
-
if error != nil {
|
|
193
|
-
return nil
|
|
194
|
-
} else {
|
|
195
|
-
return cfData as Data
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
return nil
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
static func loadPublicFromData(_ data: Data) -> SecKey? {
|
|
202
|
-
let keyDict: [NSObject: NSObject] = [
|
|
203
|
-
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
204
|
-
kSecAttrKeyClass: kSecAttrKeyClassPublic,
|
|
205
|
-
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
206
|
-
]
|
|
207
|
-
return SecKeyCreateWithData(data as NSData, keyDict as CFDictionary, nil)
|
|
208
|
-
}
|
|
209
|
-
static func loadPrivateFromData(_ data: Data) -> SecKey? {
|
|
210
|
-
let keyDict: [NSObject: NSObject] = [
|
|
211
|
-
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
212
|
-
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
|
213
|
-
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
214
|
-
]
|
|
215
|
-
return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
public struct CryptoCipherV2 {
|
|
220
|
-
|
|
221
|
-
public static func decryptChecksum(checksum: String, publicKey: String, version: String) throws -> String {
|
|
222
|
-
if publicKey.isEmpty {
|
|
223
|
-
return checksum
|
|
224
|
-
}
|
|
225
|
-
do {
|
|
226
|
-
let checksumBytes: Data = Data(base64Encoded: checksum)!
|
|
227
|
-
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: publicKey) else {
|
|
228
|
-
print("cannot decode publicKey", publicKey)
|
|
29
|
+
guard let rsaPublicKey = RSAPublicKey.load(rsaPublicKey: publicKey) else {
|
|
30
|
+
print("\(CapacitorUpdater.TAG) The public key is not a valid RSA Public key")
|
|
229
31
|
throw CustomError.cannotDecode
|
|
230
32
|
}
|
|
33
|
+
|
|
231
34
|
guard let decryptedChecksum = rsaPublicKey.decrypt(data: checksumBytes) else {
|
|
35
|
+
print("\(CapacitorUpdater.TAG) decryptChecksum fail")
|
|
232
36
|
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
233
37
|
}
|
|
38
|
+
|
|
234
39
|
return decryptedChecksum.base64EncodedString()
|
|
235
40
|
} catch {
|
|
236
|
-
print("\(CapacitorUpdater.TAG)
|
|
41
|
+
print("\(CapacitorUpdater.TAG) decryptChecksum fail: \(error.localizedDescription)")
|
|
237
42
|
throw CustomError.cannotDecode
|
|
238
43
|
}
|
|
239
44
|
}
|
|
@@ -242,13 +47,35 @@ public struct CryptoCipherV2 {
|
|
|
242
47
|
var sha256 = SHA256()
|
|
243
48
|
|
|
244
49
|
do {
|
|
245
|
-
let fileHandle
|
|
50
|
+
let fileHandle: FileHandle
|
|
51
|
+
do {
|
|
52
|
+
fileHandle = try FileHandle(forReadingFrom: filePath)
|
|
53
|
+
} catch {
|
|
54
|
+
print("\(CapacitorUpdater.TAG) Cannot open file for checksum: \(filePath.path)", error)
|
|
55
|
+
return ""
|
|
56
|
+
}
|
|
57
|
+
|
|
246
58
|
defer {
|
|
247
|
-
|
|
59
|
+
do {
|
|
60
|
+
try fileHandle.close()
|
|
61
|
+
} catch {
|
|
62
|
+
print("\(CapacitorUpdater.TAG) Error closing file: \(error)")
|
|
63
|
+
}
|
|
248
64
|
}
|
|
249
65
|
|
|
250
66
|
while autoreleasepool(invoking: {
|
|
251
|
-
let fileData
|
|
67
|
+
let fileData: Data
|
|
68
|
+
do {
|
|
69
|
+
if #available(iOS 13.4, *) {
|
|
70
|
+
fileData = try fileHandle.read(upToCount: bufferSize) ?? Data()
|
|
71
|
+
} else {
|
|
72
|
+
fileData = fileHandle.readData(ofLength: bufferSize)
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
print("\(CapacitorUpdater.TAG) Error reading file: \(error)")
|
|
76
|
+
return false
|
|
77
|
+
}
|
|
78
|
+
|
|
252
79
|
if fileData.count > 0 {
|
|
253
80
|
sha256.update(data: fileData)
|
|
254
81
|
return true // Continue
|
|
@@ -266,44 +93,89 @@ public struct CryptoCipherV2 {
|
|
|
266
93
|
}
|
|
267
94
|
|
|
268
95
|
public static func decryptFile(filePath: URL, publicKey: String, sessionKey: String, version: String) throws {
|
|
269
|
-
if publicKey.isEmpty || sessionKey.isEmpty
|
|
270
|
-
print("\(CapacitorUpdater.TAG) Cannot
|
|
96
|
+
if publicKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
97
|
+
print("\(CapacitorUpdater.TAG) Cannot found public key or sessionKey")
|
|
271
98
|
return
|
|
272
99
|
}
|
|
100
|
+
|
|
101
|
+
if !publicKey.hasPrefix("-----BEGIN RSA PUBLIC KEY-----") {
|
|
102
|
+
print("\(CapacitorUpdater.TAG) The public key is not a valid RSA Public key")
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
273
106
|
do {
|
|
274
|
-
guard let rsaPublicKey
|
|
275
|
-
print("
|
|
107
|
+
guard let rsaPublicKey = RSAPublicKey.load(rsaPublicKey: publicKey) else {
|
|
108
|
+
print("\(CapacitorUpdater.TAG) The public key is not a valid RSA Public key")
|
|
276
109
|
throw CustomError.cannotDecode
|
|
277
110
|
}
|
|
278
111
|
|
|
279
|
-
let
|
|
280
|
-
|
|
281
|
-
|
|
112
|
+
let sessionKeyComponents = sessionKey.components(separatedBy: ":")
|
|
113
|
+
let ivBase64 = sessionKeyComponents[0]
|
|
114
|
+
let encryptedKeyBase64 = sessionKeyComponents[1]
|
|
115
|
+
|
|
116
|
+
guard let ivData = Data(base64Encoded: ivBase64) else {
|
|
117
|
+
print("\(CapacitorUpdater.TAG) Cannot decode sessionKey IV", ivBase64)
|
|
282
118
|
throw CustomError.cannotDecode
|
|
283
119
|
}
|
|
284
120
|
|
|
285
|
-
|
|
121
|
+
if ivData.count != 16 {
|
|
122
|
+
print("\(CapacitorUpdater.TAG) IV data has invalid length: \(ivData.count), expected 16")
|
|
123
|
+
throw CustomError.cannotDecode
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
guard let sessionKeyDataEncrypted = Data(base64Encoded: encryptedKeyBase64) else {
|
|
127
|
+
print("\(CapacitorUpdater.TAG) Cannot decode sessionKey data", encryptedKeyBase64)
|
|
286
128
|
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
287
129
|
}
|
|
288
130
|
|
|
289
131
|
guard let sessionKeyDataDecrypted = rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
132
|
+
print("\(CapacitorUpdater.TAG) Failed to decrypt session key data")
|
|
290
133
|
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
291
134
|
}
|
|
292
135
|
|
|
136
|
+
if sessionKeyDataDecrypted.count != 16 {
|
|
137
|
+
print("\(CapacitorUpdater.TAG) Decrypted session key has invalid length: \(sessionKeyDataDecrypted.count), expected 16")
|
|
138
|
+
throw NSError(domain: "Invalid decrypted session key", code: 5, userInfo: nil)
|
|
139
|
+
}
|
|
140
|
+
|
|
293
141
|
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
294
142
|
|
|
295
|
-
|
|
143
|
+
let encryptedData: Data
|
|
144
|
+
do {
|
|
145
|
+
encryptedData = try Data(contentsOf: filePath)
|
|
146
|
+
} catch {
|
|
147
|
+
print("\(CapacitorUpdater.TAG) Failed to read encrypted data: \(error)")
|
|
296
148
|
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
297
149
|
}
|
|
298
150
|
|
|
151
|
+
if encryptedData.isEmpty {
|
|
152
|
+
print("\(CapacitorUpdater.TAG) Encrypted file data is empty")
|
|
153
|
+
throw NSError(domain: "Empty encrypted data", code: 6, userInfo: nil)
|
|
154
|
+
}
|
|
155
|
+
|
|
299
156
|
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
157
|
+
print("\(CapacitorUpdater.TAG) Failed to decrypt data")
|
|
300
158
|
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
301
159
|
}
|
|
302
160
|
|
|
303
|
-
|
|
161
|
+
if decryptedData.isEmpty {
|
|
162
|
+
print("\(CapacitorUpdater.TAG) Decrypted data is empty")
|
|
163
|
+
throw NSError(domain: "Empty decrypted data", code: 7, userInfo: nil)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
do {
|
|
167
|
+
try decryptedData.write(to: filePath, options: .atomic)
|
|
168
|
+
if !FileManager.default.fileExists(atPath: filePath.path) {
|
|
169
|
+
print("\(CapacitorUpdater.TAG) File was not created after write")
|
|
170
|
+
throw NSError(domain: "File write failed", code: 8, userInfo: nil)
|
|
171
|
+
}
|
|
172
|
+
} catch {
|
|
173
|
+
print("\(CapacitorUpdater.TAG) Error writing decrypted file: \(error)")
|
|
174
|
+
throw error
|
|
175
|
+
}
|
|
304
176
|
|
|
305
177
|
} catch {
|
|
306
|
-
print("\(CapacitorUpdater.TAG)
|
|
178
|
+
print("\(CapacitorUpdater.TAG) decryptFile fail")
|
|
307
179
|
throw CustomError.cannotDecode
|
|
308
180
|
}
|
|
309
181
|
}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import BigInt
|
|
2
|
+
import CommonCrypto
|
|
3
|
+
import CryptoKit
|
|
4
|
+
|
|
5
|
+
///
|
|
6
|
+
/// Constants
|
|
7
|
+
///
|
|
8
|
+
private enum RSAConstants {
|
|
9
|
+
static let rsaKeySizeInBits: NSNumber = 2048
|
|
10
|
+
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// We do all this stuff because ios is shit and open source libraries allow to do decryption with public key
|
|
14
|
+
// So we have to do it manually, while in nodejs or Java it's ok and done at language level.
|
|
15
|
+
|
|
16
|
+
///
|
|
17
|
+
/// The RSA public key.
|
|
18
|
+
///
|
|
19
|
+
public struct RSAPublicKey {
|
|
20
|
+
private let manualKey: ManualRSAPublicKey
|
|
21
|
+
|
|
22
|
+
fileprivate init(manualKey: ManualRSAPublicKey) {
|
|
23
|
+
self.manualKey = manualKey
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
///
|
|
27
|
+
/// Takes the data and uses the public key to decrypt it.
|
|
28
|
+
/// Returns the decrypted data.
|
|
29
|
+
///
|
|
30
|
+
public func decrypt(data: Data) -> Data? {
|
|
31
|
+
return manualKey.decrypt(data)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
///
|
|
35
|
+
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
36
|
+
///
|
|
37
|
+
public static func load(rsaPublicKey: String) -> RSAPublicKey? {
|
|
38
|
+
// Clean up the key string
|
|
39
|
+
var pubKey: String = rsaPublicKey
|
|
40
|
+
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
|
|
41
|
+
pubKey = pubKey.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
|
|
42
|
+
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
|
|
43
|
+
pubKey = pubKey.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
44
|
+
pubKey = pubKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
45
|
+
pubKey = pubKey.replacingOccurrences(of: "\n", with: "")
|
|
46
|
+
pubKey = pubKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
47
|
+
|
|
48
|
+
do {
|
|
49
|
+
guard let rsaPublicKeyData: Data = Data(base64Encoded: String(pubKey)) else {
|
|
50
|
+
throw CustomError.cannotDecode
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Try parsing as PKCS#1
|
|
54
|
+
if let manualKey = ManualRSAPublicKey.fromPKCS1(rsaPublicKeyData) {
|
|
55
|
+
return RSAPublicKey(manualKey: manualKey)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Most common public exponent is 65537 (0x010001)
|
|
59
|
+
let commonExponent = Data([0x01, 0x00, 0x01]) // 65537 in big-endian
|
|
60
|
+
|
|
61
|
+
// Assume the entire key data is the modulus
|
|
62
|
+
let lastResortKey = ManualRSAPublicKey(modulus: rsaPublicKeyData, exponent: commonExponent)
|
|
63
|
+
return RSAPublicKey(manualKey: lastResortKey)
|
|
64
|
+
} catch {
|
|
65
|
+
return nil
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Manual RSA Public Key Implementation using the BigInt library
|
|
71
|
+
struct ManualRSAPublicKey {
|
|
72
|
+
let modulus: BigInt
|
|
73
|
+
let exponent: BigInt
|
|
74
|
+
|
|
75
|
+
init(modulus: Data, exponent: Data) {
|
|
76
|
+
// Create positive BigInts from Data
|
|
77
|
+
let modulusBytes = [UInt8](modulus)
|
|
78
|
+
var modulusValue = BigUInt(0)
|
|
79
|
+
for byte in modulusBytes {
|
|
80
|
+
modulusValue = (modulusValue << 8) | BigUInt(byte)
|
|
81
|
+
}
|
|
82
|
+
self.modulus = BigInt(modulusValue)
|
|
83
|
+
let exponentBytes = [UInt8](exponent)
|
|
84
|
+
var exponentValue = BigUInt(0)
|
|
85
|
+
for byte in exponentBytes {
|
|
86
|
+
exponentValue = (exponentValue << 8) | BigUInt(byte)
|
|
87
|
+
}
|
|
88
|
+
self.exponent = BigInt(exponentValue)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Parse PKCS#1 format public key
|
|
92
|
+
static func fromPKCS1(_ publicKeyData: Data) -> ManualRSAPublicKey? {
|
|
93
|
+
// Parse ASN.1 DER encoded RSA public key
|
|
94
|
+
// Format: RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
|
|
95
|
+
|
|
96
|
+
guard publicKeyData.count > 0 else {
|
|
97
|
+
return nil
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let bytes = [UInt8](publicKeyData)
|
|
101
|
+
|
|
102
|
+
// Check for sequence tag (0x30)
|
|
103
|
+
guard bytes[0] == 0x30 else {
|
|
104
|
+
// Try direct modulus/exponent approach as fallback
|
|
105
|
+
if publicKeyData.count >= 3 {
|
|
106
|
+
// Assume this is a raw RSA public key with modulus + exponent
|
|
107
|
+
// Most common: modulus is 256 bytes (2048 bits), exponent is 3 bytes (0x010001 = 65537)
|
|
108
|
+
let modulusSize = publicKeyData.count - 3
|
|
109
|
+
if modulusSize > 0 {
|
|
110
|
+
let modulusData = publicKeyData.prefix(modulusSize)
|
|
111
|
+
let exponentData = publicKeyData.suffix(3)
|
|
112
|
+
return ManualRSAPublicKey(modulus: modulusData, exponent: exponentData)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return nil
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
var index = 1
|
|
120
|
+
|
|
121
|
+
// Skip length
|
|
122
|
+
if bytes[index] & 0x80 != 0 {
|
|
123
|
+
let lenBytes = Int(bytes[index] & 0x7F)
|
|
124
|
+
if (index + 1 + lenBytes) >= bytes.count {
|
|
125
|
+
return nil
|
|
126
|
+
}
|
|
127
|
+
index += 1 + lenBytes
|
|
128
|
+
} else {
|
|
129
|
+
index += 1
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check for INTEGER tag for modulus (0x02)
|
|
133
|
+
if index >= bytes.count {
|
|
134
|
+
return nil
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
guard bytes[index] == 0x02 else {
|
|
138
|
+
return nil
|
|
139
|
+
}
|
|
140
|
+
index += 1
|
|
141
|
+
|
|
142
|
+
// Get modulus length
|
|
143
|
+
if index >= bytes.count {
|
|
144
|
+
return nil
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
var modulusLength = 0
|
|
148
|
+
if bytes[index] & 0x80 != 0 {
|
|
149
|
+
let lenBytes = Int(bytes[index] & 0x7F)
|
|
150
|
+
if (index + 1 + lenBytes) >= bytes.count {
|
|
151
|
+
return nil
|
|
152
|
+
}
|
|
153
|
+
index += 1
|
|
154
|
+
for i in 0..<lenBytes {
|
|
155
|
+
modulusLength = (modulusLength << 8) | Int(bytes[index + i])
|
|
156
|
+
}
|
|
157
|
+
index += lenBytes
|
|
158
|
+
} else {
|
|
159
|
+
modulusLength = Int(bytes[index])
|
|
160
|
+
index += 1
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Skip any leading zero in modulus (for unsigned integer)
|
|
164
|
+
if index < bytes.count && bytes[index] == 0x00 {
|
|
165
|
+
index += 1
|
|
166
|
+
modulusLength -= 1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Extract modulus
|
|
170
|
+
if (index + modulusLength) > bytes.count {
|
|
171
|
+
return nil
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
let modulusData = Data(bytes[index..<(index + modulusLength)])
|
|
175
|
+
index += modulusLength
|
|
176
|
+
|
|
177
|
+
// Check for INTEGER tag for exponent
|
|
178
|
+
if index >= bytes.count {
|
|
179
|
+
return nil
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
guard bytes[index] == 0x02 else {
|
|
183
|
+
return nil
|
|
184
|
+
}
|
|
185
|
+
index += 1
|
|
186
|
+
|
|
187
|
+
// Get exponent length
|
|
188
|
+
if index >= bytes.count {
|
|
189
|
+
return nil
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
var exponentLength = 0
|
|
193
|
+
if bytes[index] & 0x80 != 0 {
|
|
194
|
+
let lenBytes = Int(bytes[index] & 0x7F)
|
|
195
|
+
if (index + 1 + lenBytes) >= bytes.count {
|
|
196
|
+
return nil
|
|
197
|
+
}
|
|
198
|
+
index += 1
|
|
199
|
+
for i in 0..<lenBytes {
|
|
200
|
+
exponentLength = (exponentLength << 8) | Int(bytes[index + i])
|
|
201
|
+
}
|
|
202
|
+
index += lenBytes
|
|
203
|
+
} else {
|
|
204
|
+
exponentLength = Int(bytes[index])
|
|
205
|
+
index += 1
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Extract exponent
|
|
209
|
+
if (index + exponentLength) > bytes.count {
|
|
210
|
+
return nil
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
let exponentData = Data(bytes[index..<(index + exponentLength)])
|
|
214
|
+
return ManualRSAPublicKey(modulus: modulusData, exponent: exponentData)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Decrypt data using raw RSA operation (c^d mod n)
|
|
218
|
+
func decrypt(_ encryptedData: Data) -> Data? {
|
|
219
|
+
// Create positive BigInt from encrypted data
|
|
220
|
+
let encryptedBytes = [UInt8](encryptedData)
|
|
221
|
+
var encryptedValue = BigUInt(0)
|
|
222
|
+
for byte in encryptedBytes {
|
|
223
|
+
encryptedValue = (encryptedValue << 8) | BigUInt(byte)
|
|
224
|
+
}
|
|
225
|
+
let encrypted = BigInt(encryptedValue)
|
|
226
|
+
|
|
227
|
+
// In Node.js:
|
|
228
|
+
// privateEncrypt uses the private key (d) to encrypt
|
|
229
|
+
// publicDecrypt uses the public key (e) to decrypt
|
|
230
|
+
// The operation we want is: ciphertext^e mod n
|
|
231
|
+
|
|
232
|
+
// RSA operation: c^e mod n
|
|
233
|
+
let decrypted = encrypted.manualPower(exponent, modulus: modulus)
|
|
234
|
+
|
|
235
|
+
// Convert to bytes with proper padding
|
|
236
|
+
guard let bigUIntValue = decrypted.magnitude as? BigUInt else {
|
|
237
|
+
return nil
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Convert BigUInt to bytes with padding
|
|
241
|
+
var resultBytes = [UInt8]()
|
|
242
|
+
var tempValue = bigUIntValue
|
|
243
|
+
while tempValue > 0 {
|
|
244
|
+
let byte = UInt8(tempValue & 0xFF)
|
|
245
|
+
resultBytes.insert(byte, at: 0) // Prepend to get big-endian
|
|
246
|
+
tempValue >>= 8
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Ensure we have at least 256 bytes (2048 bits) with leading zeros
|
|
250
|
+
let paddedBytes = [UInt8](repeating: 0, count: max(0, 256 - resultBytes.count)) + resultBytes
|
|
251
|
+
|
|
252
|
+
// For PKCS1 padding from Node.js privateEncrypt, the format is:
|
|
253
|
+
// 0x00 || 0x01 || PS || 0x00 || actual data
|
|
254
|
+
// where PS is a string of 0xFF bytes
|
|
255
|
+
|
|
256
|
+
// Check for privateEncrypt padding format (0x00 || 0x01 || PS || 0x00)
|
|
257
|
+
var startIndex = 0
|
|
258
|
+
if paddedBytes.count > 2 && paddedBytes[0] == 0x00 && paddedBytes[1] == 0x01 {
|
|
259
|
+
for i in 2..<paddedBytes.count {
|
|
260
|
+
if paddedBytes[i] == 0x00 {
|
|
261
|
+
startIndex = i + 1
|
|
262
|
+
break
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if startIndex < paddedBytes.count {
|
|
267
|
+
let result = Data(paddedBytes[startIndex...])
|
|
268
|
+
return result
|
|
269
|
+
} else {
|
|
270
|
+
return Data(paddedBytes)
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|