@capgo/capacitor-updater 6.14.11 → 6.14.13

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.
@@ -10,7 +10,6 @@ import android.app.Activity;
10
10
  import android.content.Context;
11
11
  import android.content.SharedPreferences;
12
12
  import android.os.Build;
13
- import android.util.Base64;
14
13
  import android.util.Log;
15
14
  import androidx.annotation.NonNull;
16
15
  import androidx.lifecycle.LifecycleOwner;
@@ -22,27 +21,20 @@ import com.getcapacitor.plugin.WebView;
22
21
  import com.google.common.util.concurrent.Futures;
23
22
  import com.google.common.util.concurrent.ListenableFuture;
24
23
  import java.io.BufferedInputStream;
25
- import java.io.DataInputStream;
26
24
  import java.io.File;
27
25
  import java.io.FileInputStream;
28
26
  import java.io.FileNotFoundException;
29
27
  import java.io.FileOutputStream;
30
28
  import java.io.FilenameFilter;
31
29
  import java.io.IOException;
32
- import java.security.GeneralSecurityException;
33
- import java.security.MessageDigest;
34
- import java.security.PrivateKey;
35
- import java.security.PublicKey;
36
30
  import java.security.SecureRandom;
37
31
  import java.util.ArrayList;
38
32
  import java.util.Date;
39
33
  import java.util.Iterator;
40
34
  import java.util.List;
41
35
  import java.util.Objects;
42
- import java.util.zip.CRC32;
43
36
  import java.util.zip.ZipEntry;
44
37
  import java.util.zip.ZipInputStream;
45
- import javax.crypto.SecretKey;
46
38
  import okhttp3.*;
47
39
  import org.json.JSONArray;
48
40
  import org.json.JSONException;
@@ -245,6 +237,7 @@ public class CapacitorUpdater {
245
237
 
246
238
  boolean success = finishDownload(id, dest, version, sessionKey, checksum, true, isManifest);
247
239
  if (!success) {
240
+ Log.e(TAG, "Finish download failed: " + version);
248
241
  saveBundleInfo(
249
242
  id,
250
243
  new BundleInfo(id, version, BundleStatus.ERROR, new Date(System.currentTimeMillis()), "")
@@ -259,6 +252,7 @@ public class CapacitorUpdater {
259
252
  case FAILED:
260
253
  Data failedData = workInfo.getOutputData();
261
254
  String error = failedData.getString(DownloadService.ERROR);
255
+ Log.e(TAG, "Download failed: " + error + " " + workInfo.getState());
262
256
  String failedVersion = failedData.getString(DownloadService.VERSION);
263
257
  saveBundleInfo(
264
258
  id,
@@ -344,7 +338,7 @@ public class CapacitorUpdater {
344
338
  }
345
339
  }
346
340
  // Remove the decryption for manifest downloads
347
- } catch (IOException e) {
341
+ } catch (Exception e) {
348
342
  final Boolean res = this.delete(id);
349
343
  if (!res) {
350
344
  Log.i(CapacitorUpdater.TAG, "Double error, cannot cleanup: " + version);
@@ -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 = "6.14.11";
60
+ private final String PLUGIN_VERSION = "6.14.12";
61
61
  private static final String DELAY_CONDITION_PREFERENCES = "";
62
62
 
63
63
  private SharedPreferences.Editor editor;
@@ -16,6 +16,7 @@ import java.io.FileInputStream;
16
16
  import java.net.HttpURLConnection;
17
17
  import java.net.URL;
18
18
  import java.nio.channels.FileChannel;
19
+ import java.nio.file.Files;
19
20
  import java.security.MessageDigest;
20
21
  import java.util.ArrayList;
21
22
  import java.util.Arrays;
@@ -109,7 +110,6 @@ public class DownloadService extends Worker {
109
110
  return createSuccessResult(dest, version, sessionKey, checksum, false);
110
111
  }
111
112
  } catch (Exception e) {
112
- Log.e(TAG, "Error in doWork", e);
113
113
  return createFailureResult(e.getMessage());
114
114
  }
115
115
  }
@@ -163,25 +163,38 @@ public class DownloadService extends Worker {
163
163
  String fileHash = entry.getString("file_hash");
164
164
  String downloadUrl = entry.getString("download_url");
165
165
 
166
+ if (!publicKey.isEmpty() && sessionKey != null && !sessionKey.isEmpty()) {
167
+ try {
168
+ fileHash = CryptoCipherV2.decryptChecksum(fileHash, publicKey);
169
+ } catch (Exception e) {
170
+ Log.e(TAG, "Error decrypting checksum for " + fileName, e);
171
+ hasError.set(true);
172
+ continue;
173
+ }
174
+ }
175
+
176
+ final String finalFileHash = fileHash;
166
177
  File targetFile = new File(destFolder, fileName);
167
- File cacheFile = new File(cacheFolder, fileHash + "_" + new File(fileName).getName());
178
+ File cacheFile = new File(cacheFolder, finalFileHash + "_" + new File(fileName).getName());
168
179
  File builtinFile = new File(builtinFolder, fileName);
169
180
 
170
181
  // Ensure parent directories of the target file exist
171
182
  if (!Objects.requireNonNull(targetFile.getParentFile()).exists() && !targetFile.getParentFile().mkdirs()) {
172
- throw new IOException("Failed to create parent directory for: " + targetFile.getAbsolutePath());
183
+ Log.e(TAG, "Failed to create parent directory for: " + targetFile.getAbsolutePath());
184
+ hasError.set(true);
185
+ continue;
173
186
  }
174
187
 
175
188
  Future<?> future = executor.submit(() -> {
176
189
  try {
177
- if (builtinFile.exists() && verifyChecksum(builtinFile, fileHash)) {
190
+ if (builtinFile.exists() && verifyChecksum(builtinFile, finalFileHash)) {
178
191
  copyFile(builtinFile, targetFile);
179
192
  Log.d(TAG, "using builtin file " + fileName);
180
- } else if (cacheFile.exists() && verifyChecksum(cacheFile, fileHash)) {
193
+ } else if (cacheFile.exists() && verifyChecksum(cacheFile, finalFileHash)) {
181
194
  copyFile(cacheFile, targetFile);
182
195
  Log.d(TAG, "already cached " + fileName);
183
196
  } else {
184
- downloadAndVerify(downloadUrl, targetFile, cacheFile, fileHash, sessionKey, publicKey);
197
+ downloadAndVerify(downloadUrl, targetFile, cacheFile, finalFileHash, sessionKey, publicKey);
185
198
  }
186
199
 
187
200
  long completed = completedFiles.incrementAndGet();
@@ -219,7 +232,7 @@ public class DownloadService extends Worker {
219
232
  throw new IOException("One or more files failed to download");
220
233
  }
221
234
  } catch (Exception e) {
222
- Log.e(TAG, "Error in handleManifestDownload", e);
235
+ throw new RuntimeException(e.getLocalizedMessage());
223
236
  }
224
237
  }
225
238
 
@@ -317,10 +330,8 @@ public class DownloadService extends Worker {
317
330
  }
318
331
  }
319
332
  } catch (OutOfMemoryError e) {
320
- e.printStackTrace();
321
333
  throw new RuntimeException("low_mem_fail");
322
334
  } catch (Exception e) {
323
- e.printStackTrace();
324
335
  throw new RuntimeException(e.getLocalizedMessage());
325
336
  }
326
337
  }
@@ -334,7 +345,8 @@ public class DownloadService extends Worker {
334
345
  infoFile.createNewFile();
335
346
  tempFile.createNewFile();
336
347
  } catch (IOException e) {
337
- e.printStackTrace();
348
+ Log.e(TAG, "Error in clearDownloadData", e);
349
+ // not a fatal error, so we don't throw an exception
338
350
  }
339
351
  }
340
352
 
@@ -394,18 +406,10 @@ public class DownloadService extends Worker {
394
406
  decryptedExpectedHash = CryptoCipherV2.decryptChecksum(decryptedExpectedHash, publicKey);
395
407
  }
396
408
 
397
- // Decompress the file
398
- try (
399
- FileInputStream fis = new FileInputStream(compressedFile);
400
- BrotliInputStream brotliInputStream = new BrotliInputStream(fis);
401
- FileOutputStream fos = new FileOutputStream(targetFile)
402
- ) {
403
- byte[] buffer = new byte[8192];
404
- int len;
405
- while ((len = brotliInputStream.read(buffer)) != -1) {
406
- fos.write(buffer, 0, len);
407
- }
408
- }
409
+ // Use new decompression method
410
+ byte[] compressedData = Files.readAllBytes(compressedFile.toPath());
411
+ byte[] decompressedData = decompressBrotli(compressedData, targetFile.getName());
412
+ Files.write(targetFile.toPath(), decompressedData);
409
413
 
410
414
  // Delete the compressed file
411
415
  compressedFile.delete();
@@ -417,8 +421,19 @@ public class DownloadService extends Worker {
417
421
  copyFile(targetFile, cacheFile);
418
422
  } else {
419
423
  targetFile.delete();
420
- throw new IOException("Checksum verification failed for " + targetFile.getName());
424
+ throw new IOException(
425
+ "Checksum verification failed for: " +
426
+ downloadUrl +
427
+ " " +
428
+ targetFile.getName() +
429
+ " expected: " +
430
+ decryptedExpectedHash +
431
+ " calculated: " +
432
+ calculatedHash
433
+ );
421
434
  }
435
+ } catch (Exception e) {
436
+ throw new IOException("Error in downloadAndVerify: " + e.getMessage());
422
437
  }
423
438
  }
424
439
 
@@ -450,4 +465,63 @@ public class DownloadService extends Worker {
450
465
  }
451
466
  return sb.toString();
452
467
  }
468
+
469
+ private byte[] decompressBrotli(byte[] data, String fileName) throws IOException {
470
+ // Validate input
471
+ if (data == null) {
472
+ Log.e(TAG, "Error: Null data received for " + fileName);
473
+ throw new IOException("Null data received");
474
+ }
475
+
476
+ // Handle empty files
477
+ if (data.length == 0) {
478
+ return new byte[0];
479
+ }
480
+
481
+ // Handle the special EMPTY_BROTLI_STREAM case
482
+ if (data.length == 3 && data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06) {
483
+ return new byte[0];
484
+ }
485
+
486
+ // For small files, check if it's a minimal Brotli wrapper
487
+ if (data.length > 3) {
488
+ try {
489
+ // Handle our minimal wrapper pattern
490
+ if (data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06 && data[data.length - 1] == 0x03) {
491
+ return Arrays.copyOfRange(data, 3, data.length - 1);
492
+ }
493
+
494
+ // Handle brotli.compress minimal wrapper (quality 0)
495
+ if (data[0] == 0x0b && data[1] == 0x02 && data[2] == (byte) 0x80 && data[data.length - 1] == 0x03) {
496
+ return Arrays.copyOfRange(data, 3, data.length - 1);
497
+ }
498
+ } catch (ArrayIndexOutOfBoundsException e) {
499
+ Log.e(TAG, "Error: Malformed data for " + fileName);
500
+ throw new IOException("Malformed data structure");
501
+ }
502
+ }
503
+
504
+ // For all other cases, try standard decompression
505
+ try (
506
+ ByteArrayInputStream bis = new ByteArrayInputStream(data);
507
+ BrotliInputStream brotliInputStream = new BrotliInputStream(bis);
508
+ ByteArrayOutputStream bos = new ByteArrayOutputStream()
509
+ ) {
510
+ byte[] buffer = new byte[8192];
511
+ int len;
512
+ while ((len = brotliInputStream.read(buffer)) != -1) {
513
+ bos.write(buffer, 0, len);
514
+ }
515
+ return bos.toByteArray();
516
+ } catch (IOException e) {
517
+ Log.e(TAG, "Error: Brotli process failed for " + fileName + ". Status: " + e.getMessage());
518
+ // Add hex dump for debugging
519
+ StringBuilder hexDump = new StringBuilder();
520
+ for (int i = 0; i < Math.min(32, data.length); i++) {
521
+ hexDump.append(String.format("%02x ", data[i]));
522
+ }
523
+ Log.e(TAG, "Error: Raw data (" + fileName + "): " + hexDump.toString());
524
+ throw e;
525
+ }
526
+ }
453
527
  }
@@ -383,9 +383,9 @@ import UIKit
383
383
  let statusCode = response.response?.statusCode ?? 200
384
384
  if statusCode < 200 || statusCode >= 300 {
385
385
  if let stringData = String(data: data, encoding: .utf8) {
386
- throw NSError(domain: "StatusCodeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch. Status code (\(statusCode)) invalid. Data: \(stringData)"])
386
+ throw NSError(domain: "StatusCodeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch. Status code (\(statusCode)) invalid. Data: \(stringData) for file \(fileName) at url \(downloadUrl)"])
387
387
  } else {
388
- throw NSError(domain: "StatusCodeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch. Status code (\(statusCode)) invalid"])
388
+ throw NSError(domain: "StatusCodeError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to fetch. Status code (\(statusCode)) invalid for file \(fileName) at url \(downloadUrl)"])
389
389
  }
390
390
  }
391
391
 
@@ -407,7 +407,7 @@ import UIKit
407
407
 
408
408
  // Decompress the Brotli data
409
409
  guard let decompressedData = self.decompressBrotli(data: finalData, fileName: fileName) else {
410
- throw NSError(domain: "BrotliDecompressionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress Brotli data"])
410
+ throw NSError(domain: "BrotliDecompressionError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to decompress Brotli data for file \(fileName) at url \(downloadUrl)"])
411
411
  }
412
412
  finalData = decompressedData
413
413
 
@@ -416,7 +416,7 @@ import UIKit
416
416
  // assume that calcChecksum != null
417
417
  let calculatedChecksum = CryptoCipherV2.calcChecksum(filePath: destFilePath)
418
418
  if calculatedChecksum != fileHash {
419
- throw NSError(domain: "ChecksumError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Computed checksum is not equal to required checksum (\(calculatedChecksum) != \(fileHash))"])
419
+ throw NSError(domain: "ChecksumError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Computed checksum is not equal to required checksum (\(calculatedChecksum) != \(fileHash)) for file \(fileName) at url \(downloadUrl)"])
420
420
  }
421
421
  }
422
422
 
@@ -428,10 +428,10 @@ import UIKit
428
428
  print("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) downloaded, decompressed\(!self.publicKey.isEmpty && !sessionKey.isEmpty ? ", decrypted" : ""), and cached")
429
429
  } catch {
430
430
  downloadError = error
431
- print("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) error: \(error)")
431
+ NSLog("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) error: \(error.localizedDescription)")
432
432
  }
433
433
  case .failure(let error):
434
- print("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) download error: \(error). Debug response: \(response.debugDescription).")
434
+ NSLog("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) download error: \(error.localizedDescription). Debug response: \(response.debugDescription).")
435
435
  }
436
436
  }
437
437
  }
@@ -455,14 +455,43 @@ import UIKit
455
455
  }
456
456
 
457
457
  private func decompressBrotli(data: Data, fileName: String) -> Data? {
458
+ // Handle empty files
459
+ if data.count == 0 {
460
+ return data
461
+ }
462
+
463
+ // Handle the special EMPTY_BROTLI_STREAM case
464
+ if data.count == 3 && data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06 {
465
+ return Data()
466
+ }
467
+
468
+ // For small files, check if it's a minimal Brotli wrapper
469
+ if data.count > 3 {
470
+ let maxBytes = min(32, data.count)
471
+ let hexDump = data.prefix(maxBytes).map { String(format: "%02x", $0) }.joined(separator: " ")
472
+ // Handle our minimal wrapper pattern
473
+ if data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06 && data.last == 0x03 {
474
+ let range = data.index(data.startIndex, offsetBy: 3)..<data.index(data.endIndex, offsetBy: -1)
475
+ return data[range]
476
+ }
477
+
478
+ // Handle brotli.compress minimal wrapper (quality 0)
479
+ if data[0] == 0x0b && data[1] == 0x02 && data[2] == 0x80 && data.last == 0x03 {
480
+ let range = data.index(data.startIndex, offsetBy: 3)..<data.index(data.endIndex, offsetBy: -1)
481
+ return data[range]
482
+ }
483
+ }
484
+
485
+ // For all other cases, try standard decompression
458
486
  let outputBufferSize = 65536
459
487
  var outputBuffer = [UInt8](repeating: 0, count: outputBufferSize)
460
488
  var decompressedData = Data()
461
489
 
462
490
  let streamPointer = UnsafeMutablePointer<compression_stream>.allocate(capacity: 1)
463
491
  var status = compression_stream_init(streamPointer, COMPRESSION_STREAM_DECODE, COMPRESSION_BROTLI)
492
+
464
493
  guard status != COMPRESSION_STATUS_ERROR else {
465
- print("\(CapacitorUpdater.TAG) Unable to initialize the decompression stream. \(fileName)")
494
+ print("\(CapacitorUpdater.TAG) Error: Failed to initialize Brotli stream for \(fileName). Status: \(status)")
466
495
  return nil
467
496
  }
468
497
 
@@ -484,7 +513,7 @@ import UIKit
484
513
  if let baseAddress = rawBufferPointer.baseAddress {
485
514
  streamPointer.pointee.src_ptr = baseAddress.assumingMemoryBound(to: UInt8.self)
486
515
  } else {
487
- print("\(CapacitorUpdater.TAG) Error: Unable to get base address of input data. \(fileName)")
516
+ print("\(CapacitorUpdater.TAG) Error: Failed to get base address for \(fileName)")
488
517
  status = COMPRESSION_STATUS_ERROR
489
518
  return
490
519
  }
@@ -492,6 +521,9 @@ import UIKit
492
521
  }
493
522
 
494
523
  if status == COMPRESSION_STATUS_ERROR {
524
+ let maxBytes = min(32, data.count)
525
+ let hexDump = data.prefix(maxBytes).map { String(format: "%02x", $0) }.joined(separator: " ")
526
+ print("\(CapacitorUpdater.TAG) Error: Brotli decompression failed for \(fileName). First \(maxBytes) bytes: \(hexDump)")
495
527
  break
496
528
  }
497
529
 
@@ -505,15 +537,19 @@ import UIKit
505
537
  if status == COMPRESSION_STATUS_END {
506
538
  break
507
539
  } else if status == COMPRESSION_STATUS_ERROR {
508
- print("\(CapacitorUpdater.TAG) Error during Brotli decompression. \(fileName)")
509
- // Try to decode as text if mostly ASCII
540
+ print("\(CapacitorUpdater.TAG) Error: Brotli process failed for \(fileName). Status: \(status)")
510
541
  if let text = String(data: data, encoding: .utf8) {
511
542
  let asciiCount = text.unicodeScalars.filter { $0.isASCII }.count
512
543
  let totalCount = text.unicodeScalars.count
513
544
  if totalCount > 0 && Double(asciiCount) / Double(totalCount) >= 0.8 {
514
- print("\(CapacitorUpdater.TAG) Compressed data as text: \(text)")
545
+ print("\(CapacitorUpdater.TAG) Error: Input appears to be plain text: \(text)")
515
546
  }
516
547
  }
548
+
549
+ let maxBytes = min(32, data.count)
550
+ let hexDump = data.prefix(maxBytes).map { String(format: "%02x", $0) }.joined(separator: " ")
551
+ print("\(CapacitorUpdater.TAG) Error: Raw data (\(fileName)): \(hexDump)")
552
+
517
553
  return nil
518
554
  }
519
555
 
@@ -523,6 +559,7 @@ import UIKit
523
559
  }
524
560
 
525
561
  if input.count == 0 {
562
+ print("\(CapacitorUpdater.TAG) Error: Zero input size for \(fileName)")
526
563
  break
527
564
  }
528
565
  }
@@ -557,7 +594,7 @@ import UIKit
557
594
  }
558
595
  let session = Session(eventMonitors: [monitor])
559
596
 
560
- var request = session.streamRequest(url, headers: requestHeaders).validate().onHTTPResponse(perform: { response in
597
+ let request = session.streamRequest(url, headers: requestHeaders).validate().onHTTPResponse(perform: { response in
561
598
  if let contentLength = response.headers.value(for: "Content-Length") {
562
599
  targetSize = (Int(contentLength) ?? -1) + Int(totalReceivedBytes)
563
600
  }
@@ -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 = "6.14.11"
48
+ private let PLUGIN_VERSION: String = "6.14.12"
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"
@@ -17,56 +17,6 @@ private enum CryptoCipherConstants {
17
17
  static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
18
18
  static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
19
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)
57
-
58
- if Int32(status) == Int32(kCCSuccess) {
59
- result.length = Int(decryptedLength)
60
- return result as Data
61
- } else {
62
- return nil
63
- }
64
- } else {
65
- return nil
66
- }
67
- }
68
- }
69
-
70
20
  ///
71
21
  /// The RSA keypair. Includes both private and public key.
72
22
  ///
@@ -18,37 +18,48 @@ private enum CryptoCipherConstants {
18
18
  static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
19
19
  }
20
20
  ///
21
- /// The RSA keypair. Includes both private and public key.
21
+ /// The AES key. Contains both the initialization vector and secret key.
22
22
  ///
23
- public struct RSAKeyPairV2 {
24
- private let privateKey: SecKey
25
- private let publicKey: SecKey
26
-
23
+ public struct AES128Key {
24
+ /// Initialization vector
25
+ private let iv: Data
26
+ private let aes128Key: Data
27
27
  #if DEBUG
28
- public var __debug_privateKey: SecKey { self.privateKey }
29
- public var __debug_publicKey: SecKey { self.publicKey }
28
+ public var __debug_iv: Data { iv }
29
+ public var __debug_aes128Key: Data { aes128Key }
30
30
  #endif
31
-
32
- fileprivate init(privateKey: SecKey, publicKey: SecKey) {
33
- self.privateKey = privateKey
34
- self.publicKey = publicKey
31
+ init(iv: Data, aes128Key: Data) {
32
+ self.iv = iv
33
+ self.aes128Key = aes128Key
35
34
  }
36
-
37
- public func extractPublicKey() -> RSAPublicKey {
38
- RSAPublicKey(publicKey: publicKey)
39
- }
40
-
41
35
  ///
42
- /// Takes the data and uses the private key to decrypt it.
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
+ ///
43
40
  /// Returns the decrypted data.
44
41
  ///
45
42
  public func decrypt(data: Data) -> Data? {
46
- var error: Unmanaged<CFError>?
47
- if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
48
- if error != nil {
49
- return nil
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)
57
+
58
+ if Int32(status) == Int32(kCCSuccess) {
59
+ result.length = Int(decryptedLength)
60
+ return result as Data
50
61
  } else {
51
- return decryptedData as Data
62
+ return nil
52
63
  }
53
64
  } else {
54
65
  return nil
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "6.14.11",
3
+ "version": "6.14.13",
4
4
  "license": "MPL-2.0",
5
5
  "description": "Live update for capacitor apps",
6
6
  "main": "dist/plugin.cjs.js",