@capgo/capacitor-updater 6.14.11 → 6.14.12
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/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +3 -9
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +79 -18
- package/ios/Plugin/CapacitorUpdater.swift +49 -12
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +1 -1
- package/ios/Plugin/CryptoCipher.swift +0 -50
- package/ios/Plugin/CryptoCipherV2.swift +33 -22
- package/package.json +1 -1
|
@@ -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 (
|
|
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.
|
|
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;
|
|
@@ -35,6 +35,7 @@ import okhttp3.ResponseBody;
|
|
|
35
35
|
import org.brotli.dec.BrotliInputStream;
|
|
36
36
|
import org.json.JSONArray;
|
|
37
37
|
import org.json.JSONObject;
|
|
38
|
+
import java.nio.file.Files;
|
|
38
39
|
|
|
39
40
|
public class DownloadService extends Worker {
|
|
40
41
|
|
|
@@ -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
|
}
|
|
@@ -219,7 +219,7 @@ public class DownloadService extends Worker {
|
|
|
219
219
|
throw new IOException("One or more files failed to download");
|
|
220
220
|
}
|
|
221
221
|
} catch (Exception e) {
|
|
222
|
-
|
|
222
|
+
throw new RuntimeException(e.getLocalizedMessage());
|
|
223
223
|
}
|
|
224
224
|
}
|
|
225
225
|
|
|
@@ -317,10 +317,8 @@ public class DownloadService extends Worker {
|
|
|
317
317
|
}
|
|
318
318
|
}
|
|
319
319
|
} catch (OutOfMemoryError e) {
|
|
320
|
-
e.printStackTrace();
|
|
321
320
|
throw new RuntimeException("low_mem_fail");
|
|
322
321
|
} catch (Exception e) {
|
|
323
|
-
e.printStackTrace();
|
|
324
322
|
throw new RuntimeException(e.getLocalizedMessage());
|
|
325
323
|
}
|
|
326
324
|
}
|
|
@@ -334,7 +332,8 @@ public class DownloadService extends Worker {
|
|
|
334
332
|
infoFile.createNewFile();
|
|
335
333
|
tempFile.createNewFile();
|
|
336
334
|
} catch (IOException e) {
|
|
337
|
-
e
|
|
335
|
+
Log.e(TAG, "Error in clearDownloadData", e);
|
|
336
|
+
// not a fatal error, so we don't throw an exception
|
|
338
337
|
}
|
|
339
338
|
}
|
|
340
339
|
|
|
@@ -394,18 +393,10 @@ public class DownloadService extends Worker {
|
|
|
394
393
|
decryptedExpectedHash = CryptoCipherV2.decryptChecksum(decryptedExpectedHash, publicKey);
|
|
395
394
|
}
|
|
396
395
|
|
|
397
|
-
//
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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
|
-
}
|
|
396
|
+
// Use new decompression method
|
|
397
|
+
byte[] compressedData = Files.readAllBytes(compressedFile.toPath());
|
|
398
|
+
byte[] decompressedData = decompressBrotli(compressedData, targetFile.getName());
|
|
399
|
+
Files.write(targetFile.toPath(), decompressedData);
|
|
409
400
|
|
|
410
401
|
// Delete the compressed file
|
|
411
402
|
compressedFile.delete();
|
|
@@ -417,8 +408,19 @@ public class DownloadService extends Worker {
|
|
|
417
408
|
copyFile(targetFile, cacheFile);
|
|
418
409
|
} else {
|
|
419
410
|
targetFile.delete();
|
|
420
|
-
throw new IOException(
|
|
411
|
+
throw new IOException(
|
|
412
|
+
"Checksum verification failed for: " +
|
|
413
|
+
downloadUrl +
|
|
414
|
+
" " +
|
|
415
|
+
targetFile.getName() +
|
|
416
|
+
" expected: " +
|
|
417
|
+
decryptedExpectedHash +
|
|
418
|
+
" calculated: " +
|
|
419
|
+
calculatedHash
|
|
420
|
+
);
|
|
421
421
|
}
|
|
422
|
+
} catch (Exception e) {
|
|
423
|
+
throw new IOException("Error in downloadAndVerify: " + e.getMessage());
|
|
422
424
|
}
|
|
423
425
|
}
|
|
424
426
|
|
|
@@ -450,4 +452,63 @@ public class DownloadService extends Worker {
|
|
|
450
452
|
}
|
|
451
453
|
return sb.toString();
|
|
452
454
|
}
|
|
455
|
+
|
|
456
|
+
private byte[] decompressBrotli(byte[] data, String fileName) throws IOException {
|
|
457
|
+
// Validate input
|
|
458
|
+
if (data == null) {
|
|
459
|
+
Log.e(TAG, "Error: Null data received for " + fileName);
|
|
460
|
+
throw new IOException("Null data received");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Handle empty files
|
|
464
|
+
if (data.length == 0) {
|
|
465
|
+
return new byte[0];
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Handle the special EMPTY_BROTLI_STREAM case
|
|
469
|
+
if (data.length == 3 && data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06) {
|
|
470
|
+
return new byte[0];
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// For small files, check if it's a minimal Brotli wrapper
|
|
474
|
+
if (data.length > 3) {
|
|
475
|
+
try {
|
|
476
|
+
// Handle our minimal wrapper pattern
|
|
477
|
+
if (data[0] == 0x1B && data[1] == 0x00 && data[2] == 0x06 && data[data.length - 1] == 0x03) {
|
|
478
|
+
return Arrays.copyOfRange(data, 3, data.length - 1);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Handle brotli.compress minimal wrapper (quality 0)
|
|
482
|
+
if (data[0] == 0x0b && data[1] == 0x02 && data[2] == (byte)0x80 && data[data.length - 1] == 0x03) {
|
|
483
|
+
return Arrays.copyOfRange(data, 3, data.length - 1);
|
|
484
|
+
}
|
|
485
|
+
} catch (ArrayIndexOutOfBoundsException e) {
|
|
486
|
+
Log.e(TAG, "Error: Malformed data for " + fileName);
|
|
487
|
+
throw new IOException("Malformed data structure");
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// For all other cases, try standard decompression
|
|
492
|
+
try (
|
|
493
|
+
ByteArrayInputStream bis = new ByteArrayInputStream(data);
|
|
494
|
+
BrotliInputStream brotliInputStream = new BrotliInputStream(bis);
|
|
495
|
+
ByteArrayOutputStream bos = new ByteArrayOutputStream()
|
|
496
|
+
) {
|
|
497
|
+
byte[] buffer = new byte[8192];
|
|
498
|
+
int len;
|
|
499
|
+
while ((len = brotliInputStream.read(buffer)) != -1) {
|
|
500
|
+
bos.write(buffer, 0, len);
|
|
501
|
+
}
|
|
502
|
+
return bos.toByteArray();
|
|
503
|
+
} catch (IOException e) {
|
|
504
|
+
Log.e(TAG, "Error: Brotli process failed for " + fileName + ". Status: " + e.getMessage());
|
|
505
|
+
// Add hex dump for debugging
|
|
506
|
+
StringBuilder hexDump = new StringBuilder();
|
|
507
|
+
for (int i = 0; i < Math.min(32, data.length); i++) {
|
|
508
|
+
hexDump.append(String.format("%02x ", data[i]));
|
|
509
|
+
}
|
|
510
|
+
Log.e(TAG, "Error: Raw data (" + fileName + "): " + hexDump.toString());
|
|
511
|
+
throw e;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
453
514
|
}
|
|
@@ -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
|
-
|
|
431
|
+
NSLog("\(CapacitorUpdater.TAG) downloadManifest \(id) \(fileName) error: \(error.localizedDescription)")
|
|
432
432
|
}
|
|
433
433
|
case .failure(let error):
|
|
434
|
-
|
|
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)
|
|
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:
|
|
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
|
|
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)
|
|
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
|
-
|
|
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.
|
|
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
|
|
21
|
+
/// The AES key. Contains both the initialization vector and secret key.
|
|
22
22
|
///
|
|
23
|
-
public struct
|
|
24
|
-
|
|
25
|
-
private let
|
|
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
|
|
29
|
-
public var
|
|
28
|
+
public var __debug_iv: Data { iv }
|
|
29
|
+
public var __debug_aes128Key: Data { aes128Key }
|
|
30
30
|
#endif
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
62
|
+
return nil
|
|
52
63
|
}
|
|
53
64
|
} else {
|
|
54
65
|
return nil
|