@capgo/capacitor-updater 6.11.2 → 6.12.0
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 +9 -196
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +85 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +147 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +24 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +2 -0
- package/ios/Plugin/CapacitorUpdater.swift +114 -495
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +106 -96
- package/ios/Plugin/CryptoCipher.swift +77 -0
- package/ios/Plugin/CryptoCipherV2.swift +95 -1
- package/ios/Plugin/InternalUtils.swift +258 -0
- package/package.json +1 -1
|
@@ -365,6 +365,7 @@ public class CapacitorUpdater {
|
|
|
365
365
|
version,
|
|
366
366
|
sessionKey,
|
|
367
367
|
checksum,
|
|
368
|
+
this.publicKey,
|
|
368
369
|
manifest != null
|
|
369
370
|
);
|
|
370
371
|
|
|
@@ -373,26 +374,6 @@ public class CapacitorUpdater {
|
|
|
373
374
|
}
|
|
374
375
|
}
|
|
375
376
|
|
|
376
|
-
private String decryptChecksum(String checksum, String version)
|
|
377
|
-
throws IOException {
|
|
378
|
-
if (this.publicKey.isEmpty()) {
|
|
379
|
-
Log.e(CapacitorUpdater.TAG, "The public key is empty");
|
|
380
|
-
return checksum;
|
|
381
|
-
}
|
|
382
|
-
try {
|
|
383
|
-
byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
|
|
384
|
-
PublicKey pKey = CryptoCipherV2.stringToPublicKey(this.publicKey);
|
|
385
|
-
byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
|
|
386
|
-
// return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
387
|
-
String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
388
|
-
return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
|
|
389
|
-
} catch (GeneralSecurityException e) {
|
|
390
|
-
Log.e(TAG, "decryptChecksum fail: " + e.getMessage());
|
|
391
|
-
this.sendStats("decrypt_fail", version);
|
|
392
|
-
throw new IOException("Decryption failed: " + e.getMessage());
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
377
|
public Boolean finishDownload(
|
|
397
378
|
String id,
|
|
398
379
|
String dest,
|
|
@@ -412,12 +393,15 @@ public class CapacitorUpdater {
|
|
|
412
393
|
if (!isManifest) {
|
|
413
394
|
String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
|
|
414
395
|
if (!this.hasOldPrivateKeyPropertyInConfig && !sessionKey.isEmpty()) {
|
|
415
|
-
|
|
416
|
-
checksumDecrypted =
|
|
417
|
-
|
|
396
|
+
CryptoCipherV2.decryptFile(downloaded, publicKey, sessionKey);
|
|
397
|
+
checksumDecrypted = CryptoCipherV2.decryptChecksum(
|
|
398
|
+
checksumRes,
|
|
399
|
+
publicKey
|
|
400
|
+
);
|
|
401
|
+
checksum = CryptoCipherV2.calcChecksum(downloaded);
|
|
418
402
|
} else {
|
|
419
|
-
|
|
420
|
-
checksum =
|
|
403
|
+
CryptoCipher.decryptFile(downloaded, privateKey, sessionKey, version);
|
|
404
|
+
checksum = CryptoCipher.calcChecksum(downloaded);
|
|
421
405
|
}
|
|
422
406
|
if (
|
|
423
407
|
(!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) &&
|
|
@@ -517,177 +501,6 @@ public class CapacitorUpdater {
|
|
|
517
501
|
this.editor.commit();
|
|
518
502
|
}
|
|
519
503
|
|
|
520
|
-
private String calcChecksum(File file) {
|
|
521
|
-
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
522
|
-
CRC32 crc = new CRC32();
|
|
523
|
-
|
|
524
|
-
try (FileInputStream fis = new FileInputStream(file)) {
|
|
525
|
-
byte[] buffer = new byte[BUFFER_SIZE];
|
|
526
|
-
int length;
|
|
527
|
-
while ((length = fis.read(buffer)) != -1) {
|
|
528
|
-
crc.update(buffer, 0, length);
|
|
529
|
-
}
|
|
530
|
-
return String.format("%08x", crc.getValue());
|
|
531
|
-
} catch (IOException e) {
|
|
532
|
-
System.err.println(
|
|
533
|
-
TAG + " Cannot calc checksum: " + file.getPath() + " " + e.getMessage()
|
|
534
|
-
);
|
|
535
|
-
return "";
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
private String calcChecksumV2(File file) {
|
|
540
|
-
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
541
|
-
MessageDigest digest;
|
|
542
|
-
try {
|
|
543
|
-
digest = MessageDigest.getInstance("SHA-256");
|
|
544
|
-
} catch (java.security.NoSuchAlgorithmException e) {
|
|
545
|
-
System.err.println(TAG + " SHA-256 algorithm not available");
|
|
546
|
-
return "";
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
try (FileInputStream fis = new FileInputStream(file)) {
|
|
550
|
-
byte[] buffer = new byte[BUFFER_SIZE];
|
|
551
|
-
int length;
|
|
552
|
-
while ((length = fis.read(buffer)) != -1) {
|
|
553
|
-
digest.update(buffer, 0, length);
|
|
554
|
-
}
|
|
555
|
-
byte[] hash = digest.digest();
|
|
556
|
-
StringBuilder hexString = new StringBuilder();
|
|
557
|
-
for (byte b : hash) {
|
|
558
|
-
String hex = Integer.toHexString(0xff & b);
|
|
559
|
-
if (hex.length() == 1) hexString.append('0');
|
|
560
|
-
hexString.append(hex);
|
|
561
|
-
}
|
|
562
|
-
return hexString.toString();
|
|
563
|
-
} catch (IOException e) {
|
|
564
|
-
System.err.println(
|
|
565
|
-
TAG +
|
|
566
|
-
" Cannot calc checksum v2: " +
|
|
567
|
-
file.getPath() +
|
|
568
|
-
" " +
|
|
569
|
-
e.getMessage()
|
|
570
|
-
);
|
|
571
|
-
return "";
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
private void decryptFileV2(
|
|
576
|
-
final File file,
|
|
577
|
-
final String ivSessionKey,
|
|
578
|
-
final String version
|
|
579
|
-
) throws IOException {
|
|
580
|
-
// (str != null && !str.isEmpty())
|
|
581
|
-
if (
|
|
582
|
-
this.publicKey.isEmpty() ||
|
|
583
|
-
ivSessionKey == null ||
|
|
584
|
-
ivSessionKey.isEmpty() ||
|
|
585
|
-
ivSessionKey.split(":").length != 2
|
|
586
|
-
) {
|
|
587
|
-
Log.i(TAG, "Cannot found public key or sessionKey");
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
if (!this.publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
591
|
-
Log.e(
|
|
592
|
-
CapacitorUpdater.TAG,
|
|
593
|
-
"The public key is not a valid RSA Public key"
|
|
594
|
-
);
|
|
595
|
-
return;
|
|
596
|
-
}
|
|
597
|
-
try {
|
|
598
|
-
String ivB64 = ivSessionKey.split(":")[0];
|
|
599
|
-
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
600
|
-
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
601
|
-
byte[] sessionKey = Base64.decode(
|
|
602
|
-
sessionKeyB64.getBytes(),
|
|
603
|
-
Base64.DEFAULT
|
|
604
|
-
);
|
|
605
|
-
PublicKey pKey = CryptoCipherV2.stringToPublicKey(this.publicKey);
|
|
606
|
-
byte[] decryptedSessionKey = CryptoCipherV2.decryptRSA(sessionKey, pKey);
|
|
607
|
-
|
|
608
|
-
SecretKey sKey = CryptoCipherV2.byteToSessionKey(decryptedSessionKey);
|
|
609
|
-
byte[] content = new byte[(int) file.length()];
|
|
610
|
-
|
|
611
|
-
try (
|
|
612
|
-
final FileInputStream fis = new FileInputStream(file);
|
|
613
|
-
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
614
|
-
final DataInputStream dis = new DataInputStream(bis)
|
|
615
|
-
) {
|
|
616
|
-
dis.readFully(content);
|
|
617
|
-
dis.close();
|
|
618
|
-
byte[] decrypted = CryptoCipherV2.decryptAES(content, sKey, iv);
|
|
619
|
-
// write the decrypted string to the file
|
|
620
|
-
try (
|
|
621
|
-
final FileOutputStream fos = new FileOutputStream(
|
|
622
|
-
file.getAbsolutePath()
|
|
623
|
-
)
|
|
624
|
-
) {
|
|
625
|
-
fos.write(decrypted);
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
} catch (GeneralSecurityException e) {
|
|
629
|
-
Log.i(TAG, "decryptFile fail");
|
|
630
|
-
this.sendStats("decrypt_fail", version);
|
|
631
|
-
e.printStackTrace();
|
|
632
|
-
throw new IOException("GeneralSecurityException");
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
private void decryptFile(
|
|
637
|
-
final File file,
|
|
638
|
-
final String ivSessionKey,
|
|
639
|
-
final String version
|
|
640
|
-
) throws IOException {
|
|
641
|
-
// (str != null && !str.isEmpty())
|
|
642
|
-
if (this.privateKey == null || this.privateKey.isEmpty()) {
|
|
643
|
-
Log.i(TAG, "Cannot found privateKey");
|
|
644
|
-
return;
|
|
645
|
-
} else if (
|
|
646
|
-
ivSessionKey == null ||
|
|
647
|
-
ivSessionKey.isEmpty() ||
|
|
648
|
-
ivSessionKey.split(":").length != 2
|
|
649
|
-
) {
|
|
650
|
-
Log.i(TAG, "Cannot found sessionKey");
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
try {
|
|
654
|
-
String ivB64 = ivSessionKey.split(":")[0];
|
|
655
|
-
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
656
|
-
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
657
|
-
byte[] sessionKey = Base64.decode(
|
|
658
|
-
sessionKeyB64.getBytes(),
|
|
659
|
-
Base64.DEFAULT
|
|
660
|
-
);
|
|
661
|
-
PrivateKey pKey = CryptoCipher.stringToPrivateKey(this.privateKey);
|
|
662
|
-
byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
|
|
663
|
-
SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
|
|
664
|
-
byte[] content = new byte[(int) file.length()];
|
|
665
|
-
|
|
666
|
-
try (
|
|
667
|
-
final FileInputStream fis = new FileInputStream(file);
|
|
668
|
-
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
669
|
-
final DataInputStream dis = new DataInputStream(bis)
|
|
670
|
-
) {
|
|
671
|
-
dis.readFully(content);
|
|
672
|
-
dis.close();
|
|
673
|
-
byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
|
|
674
|
-
// write the decrypted string to the file
|
|
675
|
-
try (
|
|
676
|
-
final FileOutputStream fos = new FileOutputStream(
|
|
677
|
-
file.getAbsolutePath()
|
|
678
|
-
)
|
|
679
|
-
) {
|
|
680
|
-
fos.write(decrypted);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
} catch (GeneralSecurityException e) {
|
|
684
|
-
Log.i(TAG, "decryptFile fail");
|
|
685
|
-
this.sendStats("decrypt_fail", version);
|
|
686
|
-
e.printStackTrace();
|
|
687
|
-
throw new IOException("GeneralSecurityException");
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
504
|
public void downloadBackground(
|
|
692
505
|
final String url,
|
|
693
506
|
final String version,
|
|
@@ -60,7 +60,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
60
60
|
private static final String channelUrlDefault =
|
|
61
61
|
"https://plugin.capgo.app/channel_self";
|
|
62
62
|
|
|
63
|
-
private final String PLUGIN_VERSION = "6.
|
|
63
|
+
private final String PLUGIN_VERSION = "6.12.0";
|
|
64
64
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
65
65
|
|
|
66
66
|
private SharedPreferences.Editor editor;
|
|
@@ -13,6 +13,12 @@ package ee.forgr.capacitor_updater;
|
|
|
13
13
|
*/
|
|
14
14
|
import android.util.Base64;
|
|
15
15
|
import android.util.Log;
|
|
16
|
+
import java.io.BufferedInputStream;
|
|
17
|
+
import java.io.DataInputStream;
|
|
18
|
+
import java.io.File;
|
|
19
|
+
import java.io.FileInputStream;
|
|
20
|
+
import java.io.FileOutputStream;
|
|
21
|
+
import java.io.IOException;
|
|
16
22
|
import java.security.GeneralSecurityException;
|
|
17
23
|
import java.security.InvalidAlgorithmParameterException;
|
|
18
24
|
import java.security.InvalidKeyException;
|
|
@@ -24,6 +30,7 @@ import java.security.spec.InvalidKeySpecException;
|
|
|
24
30
|
import java.security.spec.MGF1ParameterSpec;
|
|
25
31
|
import java.security.spec.PKCS8EncodedKeySpec;
|
|
26
32
|
import java.security.spec.X509EncodedKeySpec;
|
|
33
|
+
import java.util.zip.CRC32;
|
|
27
34
|
import javax.crypto.BadPaddingException;
|
|
28
35
|
import javax.crypto.Cipher;
|
|
29
36
|
import javax.crypto.IllegalBlockSizeException;
|
|
@@ -146,6 +153,84 @@ public class CryptoCipher {
|
|
|
146
153
|
return readPkcs1PrivateKey(pkcs1EncodedBytes);
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
public static void decryptFile(
|
|
157
|
+
final File file,
|
|
158
|
+
final String privateKey,
|
|
159
|
+
final String ivSessionKey,
|
|
160
|
+
final String version
|
|
161
|
+
) throws IOException {
|
|
162
|
+
// (str != null && !str.isEmpty())
|
|
163
|
+
if (privateKey == null || privateKey.isEmpty()) {
|
|
164
|
+
Log.i(CapacitorUpdater.TAG, "Cannot found privateKey");
|
|
165
|
+
return;
|
|
166
|
+
} else if (
|
|
167
|
+
ivSessionKey == null ||
|
|
168
|
+
ivSessionKey.isEmpty() ||
|
|
169
|
+
ivSessionKey.split(":").length != 2
|
|
170
|
+
) {
|
|
171
|
+
Log.i(CapacitorUpdater.TAG, "Cannot found sessionKey");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
String ivB64 = ivSessionKey.split(":")[0];
|
|
176
|
+
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
177
|
+
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
178
|
+
byte[] sessionKey = Base64.decode(
|
|
179
|
+
sessionKeyB64.getBytes(),
|
|
180
|
+
Base64.DEFAULT
|
|
181
|
+
);
|
|
182
|
+
PrivateKey pKey = CryptoCipher.stringToPrivateKey(privateKey);
|
|
183
|
+
byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
|
|
184
|
+
SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
|
|
185
|
+
byte[] content = new byte[(int) file.length()];
|
|
186
|
+
|
|
187
|
+
try (
|
|
188
|
+
final FileInputStream fis = new FileInputStream(file);
|
|
189
|
+
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
190
|
+
final DataInputStream dis = new DataInputStream(bis)
|
|
191
|
+
) {
|
|
192
|
+
dis.readFully(content);
|
|
193
|
+
dis.close();
|
|
194
|
+
byte[] decrypted = CryptoCipher.decryptAES(content, sKey, iv);
|
|
195
|
+
// write the decrypted string to the file
|
|
196
|
+
try (
|
|
197
|
+
final FileOutputStream fos = new FileOutputStream(
|
|
198
|
+
file.getAbsolutePath()
|
|
199
|
+
)
|
|
200
|
+
) {
|
|
201
|
+
fos.write(decrypted);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
} catch (GeneralSecurityException e) {
|
|
205
|
+
Log.i(CapacitorUpdater.TAG, "decryptFile fail");
|
|
206
|
+
e.printStackTrace();
|
|
207
|
+
throw new IOException("GeneralSecurityException");
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public static String calcChecksum(File file) {
|
|
212
|
+
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
213
|
+
CRC32 crc = new CRC32();
|
|
214
|
+
|
|
215
|
+
try (FileInputStream fis = new FileInputStream(file)) {
|
|
216
|
+
byte[] buffer = new byte[BUFFER_SIZE];
|
|
217
|
+
int length;
|
|
218
|
+
while ((length = fis.read(buffer)) != -1) {
|
|
219
|
+
crc.update(buffer, 0, length);
|
|
220
|
+
}
|
|
221
|
+
return String.format("%08x", crc.getValue());
|
|
222
|
+
} catch (IOException e) {
|
|
223
|
+
System.err.println(
|
|
224
|
+
CapacitorUpdater.TAG +
|
|
225
|
+
" Cannot calc checksum: " +
|
|
226
|
+
file.getPath() +
|
|
227
|
+
" " +
|
|
228
|
+
e.getMessage()
|
|
229
|
+
);
|
|
230
|
+
return "";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
149
234
|
public static PublicKey stringToPublicKey(String publicKey) {
|
|
150
235
|
byte[] encoded = Base64.decode(publicKey, Base64.DEFAULT);
|
|
151
236
|
|
|
@@ -12,10 +12,18 @@ package ee.forgr.capacitor_updater;
|
|
|
12
12
|
* references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
|
|
13
13
|
*/
|
|
14
14
|
import android.util.Base64;
|
|
15
|
+
import android.util.Log;
|
|
16
|
+
import java.io.BufferedInputStream;
|
|
17
|
+
import java.io.DataInputStream;
|
|
18
|
+
import java.io.File;
|
|
19
|
+
import java.io.FileInputStream;
|
|
20
|
+
import java.io.FileOutputStream;
|
|
21
|
+
import java.io.IOException;
|
|
15
22
|
import java.security.GeneralSecurityException;
|
|
16
23
|
import java.security.InvalidAlgorithmParameterException;
|
|
17
24
|
import java.security.InvalidKeyException;
|
|
18
25
|
import java.security.KeyFactory;
|
|
26
|
+
import java.security.MessageDigest;
|
|
19
27
|
import java.security.NoSuchAlgorithmException;
|
|
20
28
|
import java.security.PublicKey;
|
|
21
29
|
import java.security.spec.InvalidKeySpecException;
|
|
@@ -26,7 +34,6 @@ import javax.crypto.IllegalBlockSizeException;
|
|
|
26
34
|
import javax.crypto.NoSuchPaddingException;
|
|
27
35
|
import javax.crypto.SecretKey;
|
|
28
36
|
import javax.crypto.spec.IvParameterSpec;
|
|
29
|
-
import javax.crypto.spec.PSource;
|
|
30
37
|
import javax.crypto.spec.SecretKeySpec;
|
|
31
38
|
|
|
32
39
|
public class CryptoCipherV2 {
|
|
@@ -134,6 +141,145 @@ public class CryptoCipherV2 {
|
|
|
134
141
|
return buf;
|
|
135
142
|
}
|
|
136
143
|
|
|
144
|
+
public static void decryptFile(
|
|
145
|
+
final File file,
|
|
146
|
+
final String publicKey,
|
|
147
|
+
final String ivSessionKey
|
|
148
|
+
) throws IOException {
|
|
149
|
+
if (
|
|
150
|
+
publicKey.isEmpty() ||
|
|
151
|
+
ivSessionKey == null ||
|
|
152
|
+
ivSessionKey.isEmpty() ||
|
|
153
|
+
ivSessionKey.split(":").length != 2
|
|
154
|
+
) {
|
|
155
|
+
Log.i(CapacitorUpdater.TAG, "Cannot found public key or sessionKey");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (!publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
159
|
+
Log.e(
|
|
160
|
+
CapacitorUpdater.TAG,
|
|
161
|
+
"The public key is not a valid RSA Public key"
|
|
162
|
+
);
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
String ivB64 = ivSessionKey.split(":")[0];
|
|
168
|
+
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
169
|
+
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
170
|
+
byte[] sessionKey = Base64.decode(
|
|
171
|
+
sessionKeyB64.getBytes(),
|
|
172
|
+
Base64.DEFAULT
|
|
173
|
+
);
|
|
174
|
+
PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
|
|
175
|
+
byte[] decryptedSessionKey = CryptoCipherV2.decryptRSA(sessionKey, pKey);
|
|
176
|
+
|
|
177
|
+
SecretKey sKey = CryptoCipherV2.byteToSessionKey(decryptedSessionKey);
|
|
178
|
+
byte[] content = new byte[(int) file.length()];
|
|
179
|
+
|
|
180
|
+
try (
|
|
181
|
+
final FileInputStream fis = new FileInputStream(file);
|
|
182
|
+
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
183
|
+
final DataInputStream dis = new DataInputStream(bis)
|
|
184
|
+
) {
|
|
185
|
+
dis.readFully(content);
|
|
186
|
+
dis.close();
|
|
187
|
+
byte[] decrypted = CryptoCipherV2.decryptAES(content, sKey, iv);
|
|
188
|
+
// write the decrypted string to the file
|
|
189
|
+
try (
|
|
190
|
+
final FileOutputStream fos = new FileOutputStream(
|
|
191
|
+
file.getAbsolutePath()
|
|
192
|
+
)
|
|
193
|
+
) {
|
|
194
|
+
fos.write(decrypted);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (GeneralSecurityException e) {
|
|
198
|
+
Log.i(CapacitorUpdater.TAG, "decryptFile fail");
|
|
199
|
+
e.printStackTrace();
|
|
200
|
+
throw new IOException("GeneralSecurityException");
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
public static String decryptChecksum(String checksum, String publicKey)
|
|
205
|
+
throws IOException {
|
|
206
|
+
if (publicKey.isEmpty()) {
|
|
207
|
+
Log.e(CapacitorUpdater.TAG, "The public key is empty");
|
|
208
|
+
return checksum;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
|
|
212
|
+
PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
|
|
213
|
+
byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
|
|
214
|
+
// return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
215
|
+
String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
216
|
+
return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
|
|
217
|
+
} catch (GeneralSecurityException e) {
|
|
218
|
+
Log.e(CapacitorUpdater.TAG, "decryptChecksum fail: " + e.getMessage());
|
|
219
|
+
throw new IOException("Decryption failed: " + e.getMessage());
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public static String decryptChecksum(
|
|
224
|
+
String checksum,
|
|
225
|
+
String publicKey,
|
|
226
|
+
String version
|
|
227
|
+
) throws IOException {
|
|
228
|
+
if (publicKey.isEmpty()) {
|
|
229
|
+
Log.e(CapacitorUpdater.TAG, "The public key is empty");
|
|
230
|
+
return checksum;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
|
|
234
|
+
PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
|
|
235
|
+
byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
|
|
236
|
+
// return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
237
|
+
String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
|
|
238
|
+
return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
|
|
239
|
+
} catch (GeneralSecurityException e) {
|
|
240
|
+
Log.e(CapacitorUpdater.TAG, "decryptChecksum fail: " + e.getMessage());
|
|
241
|
+
throw new IOException("Decryption failed: " + e.getMessage());
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
public static String calcChecksum(File file) {
|
|
246
|
+
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
247
|
+
MessageDigest digest;
|
|
248
|
+
try {
|
|
249
|
+
digest = MessageDigest.getInstance("SHA-256");
|
|
250
|
+
} catch (java.security.NoSuchAlgorithmException e) {
|
|
251
|
+
System.err.println(
|
|
252
|
+
CapacitorUpdater.TAG + " SHA-256 algorithm not available"
|
|
253
|
+
);
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
try (FileInputStream fis = new FileInputStream(file)) {
|
|
258
|
+
byte[] buffer = new byte[BUFFER_SIZE];
|
|
259
|
+
int length;
|
|
260
|
+
while ((length = fis.read(buffer)) != -1) {
|
|
261
|
+
digest.update(buffer, 0, length);
|
|
262
|
+
}
|
|
263
|
+
byte[] hash = digest.digest();
|
|
264
|
+
StringBuilder hexString = new StringBuilder();
|
|
265
|
+
for (byte b : hash) {
|
|
266
|
+
String hex = Integer.toHexString(0xff & b);
|
|
267
|
+
if (hex.length() == 1) hexString.append('0');
|
|
268
|
+
hexString.append(hex);
|
|
269
|
+
}
|
|
270
|
+
return hexString.toString();
|
|
271
|
+
} catch (IOException e) {
|
|
272
|
+
System.err.println(
|
|
273
|
+
CapacitorUpdater.TAG +
|
|
274
|
+
" Cannot calc checksum v2: " +
|
|
275
|
+
file.getPath() +
|
|
276
|
+
" " +
|
|
277
|
+
e.getMessage()
|
|
278
|
+
);
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
137
283
|
private static byte[] createDEREncoding(int tag, byte[] value) {
|
|
138
284
|
if (tag < 0 || tag >= 0xFF) {
|
|
139
285
|
throw new IllegalArgumentException(
|
|
@@ -48,6 +48,7 @@ public class DownloadService extends Worker {
|
|
|
48
48
|
public static final String VERSION = "version";
|
|
49
49
|
public static final String SESSIONKEY = "sessionkey";
|
|
50
50
|
public static final String CHECKSUM = "checksum";
|
|
51
|
+
public static final String PUBLIC_KEY = "publickey";
|
|
51
52
|
public static final String IS_MANIFEST = "is_manifest";
|
|
52
53
|
private static final String UPDATE_FILE = "update.dat";
|
|
53
54
|
|
|
@@ -100,6 +101,7 @@ public class DownloadService extends Worker {
|
|
|
100
101
|
String version = getInputData().getString(VERSION);
|
|
101
102
|
String sessionKey = getInputData().getString(SESSIONKEY);
|
|
102
103
|
String checksum = getInputData().getString(CHECKSUM);
|
|
104
|
+
String publicKey = getInputData().getString(PUBLIC_KEY);
|
|
103
105
|
boolean isManifest = getInputData().getBoolean(IS_MANIFEST, false);
|
|
104
106
|
|
|
105
107
|
Log.d(TAG, "doWork isManifest: " + isManifest);
|
|
@@ -113,6 +115,7 @@ public class DownloadService extends Worker {
|
|
|
113
115
|
dest,
|
|
114
116
|
version,
|
|
115
117
|
sessionKey,
|
|
118
|
+
publicKey,
|
|
116
119
|
manifest.toString()
|
|
117
120
|
);
|
|
118
121
|
return createSuccessResult(dest, version, sessionKey, checksum, true);
|
|
@@ -154,6 +157,7 @@ public class DownloadService extends Worker {
|
|
|
154
157
|
String dest,
|
|
155
158
|
String version,
|
|
156
159
|
String sessionKey,
|
|
160
|
+
String publicKey,
|
|
157
161
|
String manifestString
|
|
158
162
|
) {
|
|
159
163
|
try {
|
|
@@ -231,7 +235,8 @@ public class DownloadService extends Worker {
|
|
|
231
235
|
targetFile,
|
|
232
236
|
cacheFile,
|
|
233
237
|
fileHash,
|
|
234
|
-
|
|
238
|
+
sessionKey,
|
|
239
|
+
publicKey
|
|
235
240
|
);
|
|
236
241
|
}
|
|
237
242
|
|
|
@@ -426,7 +431,8 @@ public class DownloadService extends Worker {
|
|
|
426
431
|
File targetFile,
|
|
427
432
|
File cacheFile,
|
|
428
433
|
String expectedHash,
|
|
429
|
-
String
|
|
434
|
+
String sessionKey,
|
|
435
|
+
String publicKey
|
|
430
436
|
) throws Exception {
|
|
431
437
|
Log.d(TAG, "downloadAndVerify " + downloadUrl);
|
|
432
438
|
|
|
@@ -461,6 +467,20 @@ public class DownloadService extends Worker {
|
|
|
461
467
|
}
|
|
462
468
|
}
|
|
463
469
|
|
|
470
|
+
String decryptedExpectedHash = expectedHash;
|
|
471
|
+
|
|
472
|
+
if (!publicKey.isEmpty() && sessionKey != null && !sessionKey.isEmpty()) {
|
|
473
|
+
Log.d(
|
|
474
|
+
CapacitorUpdater.TAG + " DLSrv",
|
|
475
|
+
"Decrypting file " + targetFile.getName()
|
|
476
|
+
);
|
|
477
|
+
CryptoCipherV2.decryptFile(compressedFile, publicKey, sessionKey);
|
|
478
|
+
decryptedExpectedHash = CryptoCipherV2.decryptChecksum(
|
|
479
|
+
decryptedExpectedHash,
|
|
480
|
+
publicKey
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
|
|
464
484
|
// Decompress the file
|
|
465
485
|
try (
|
|
466
486
|
FileInputStream fis = new FileInputStream(compressedFile);
|
|
@@ -476,10 +496,10 @@ public class DownloadService extends Worker {
|
|
|
476
496
|
|
|
477
497
|
// Delete the compressed file
|
|
478
498
|
compressedFile.delete();
|
|
499
|
+
String calculatedHash = CryptoCipherV2.calcChecksum(targetFile);
|
|
479
500
|
|
|
480
501
|
// Verify checksum
|
|
481
|
-
|
|
482
|
-
if (actualHash.equals(expectedHash)) {
|
|
502
|
+
if (calculatedHash.equals(decryptedExpectedHash)) {
|
|
483
503
|
// Only cache if checksum is correct
|
|
484
504
|
copyFile(targetFile, cacheFile);
|
|
485
505
|
} else {
|
|
@@ -47,6 +47,7 @@ public class DownloadWorkerManager {
|
|
|
47
47
|
String version,
|
|
48
48
|
String sessionKey,
|
|
49
49
|
String checksum,
|
|
50
|
+
String publicKey,
|
|
50
51
|
boolean isManifest
|
|
51
52
|
) {
|
|
52
53
|
initializeIfNeeded(context.getApplicationContext());
|
|
@@ -68,6 +69,7 @@ public class DownloadWorkerManager {
|
|
|
68
69
|
.putString(DownloadService.SESSIONKEY, sessionKey)
|
|
69
70
|
.putString(DownloadService.CHECKSUM, checksum)
|
|
70
71
|
.putBoolean(DownloadService.IS_MANIFEST, isManifest)
|
|
72
|
+
.putString(DownloadService.PUBLIC_KEY, publicKey)
|
|
71
73
|
.build();
|
|
72
74
|
|
|
73
75
|
// Create network constraints
|