@capgo/capacitor-updater 6.1.16 → 6.1.18

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/README.md CHANGED
@@ -218,7 +218,6 @@ CapacitorUpdater can be configured with these options:
218
218
  | **`localSupaAnon`** | <code>string</code> | Configure the CLI to use a local server for testing. | <code>undefined</code> | 4.17.48 |
219
219
  | **`allowModifyUrl`** | <code>boolean</code> | Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side. | <code>false</code> | 5.4.0 |
220
220
  | **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. | <code>undefined</code> | 5.5.0 |
221
- | **`signKey`** | <code>string</code> | Public key used for bundle signing. | <code>undefined</code> | 6.1.0 |
222
221
 
223
222
  ### Examples
224
223
 
@@ -246,8 +245,7 @@ In `capacitor.config.json`:
246
245
  "localSupa": undefined,
247
246
  "localSupaAnon": undefined,
248
247
  "allowModifyUrl": undefined,
249
- "defaultChannel": undefined,
250
- "signKey": undefined
248
+ "defaultChannel": undefined
251
249
  }
252
250
  }
253
251
  }
@@ -282,7 +280,6 @@ const config: CapacitorConfig = {
282
280
  localSupaAnon: undefined,
283
281
  allowModifyUrl: undefined,
284
282
  defaultChannel: undefined,
285
- signKey: undefined,
286
283
  },
287
284
  },
288
285
  };
@@ -937,7 +934,6 @@ Listen for app ready event in the App, let you know when app is ready to use
937
934
  | **`version`** | <code>string</code> | The version code/name of this bundle/version | | |
938
935
  | **`sessionKey`** | <code>string</code> | The session key for the update | <code>undefined</code> | 4.0.0 |
939
936
  | **`checksum`** | <code>string</code> | The checksum for the update | <code>undefined</code> | 4.0.0 |
940
- | **`signature`** | <code>string</code> | The signature of the update. Can be generated using capgo CLI | <code>undefined</code> | 6.1.0 |
941
937
 
942
938
 
943
939
  #### BundleId
@@ -40,13 +40,11 @@ import java.io.InputStream;
40
40
  import java.io.UnsupportedEncodingException;
41
41
  import java.net.URL;
42
42
  import java.net.URLConnection;
43
- import java.nio.charset.StandardCharsets;
44
43
  import java.security.GeneralSecurityException;
45
44
  import java.security.MessageDigest;
46
45
  import java.security.PrivateKey;
47
46
  import java.security.PublicKey;
48
47
  import java.security.SecureRandom;
49
- import java.security.Signature;
50
48
  import java.util.ArrayList;
51
49
  import java.util.Date;
52
50
  import java.util.Iterator;
@@ -55,7 +53,6 @@ import java.util.Objects;
55
53
  import java.util.zip.CRC32;
56
54
  import java.util.zip.ZipEntry;
57
55
  import java.util.zip.ZipInputStream;
58
- import javax.crypto.Cipher;
59
56
  import javax.crypto.SecretKey;
60
57
  import org.json.JSONException;
61
58
  import org.json.JSONObject;
@@ -92,9 +89,10 @@ public class CapacitorUpdater {
92
89
  public String defaultChannel = "";
93
90
  public String appId = "";
94
91
  public String privateKey = "";
92
+ public String publicKey = "";
93
+ public boolean hasOldPrivateKeyPropertyInConfig = false;
95
94
  public String deviceID = "";
96
95
  public int timeout = 20000;
97
- public PublicKey signKey = null;
98
96
 
99
97
  private final FilenameFilter filter = (f, name) -> {
100
98
  // ignore directories generated by mac os x
@@ -285,7 +283,6 @@ public class CapacitorUpdater {
285
283
  String sessionKey = bundle.getString(DownloadService.SESSIONKEY);
286
284
  String checksum = bundle.getString(DownloadService.CHECKSUM);
287
285
  String error = bundle.getString(DownloadService.ERROR);
288
- String signature = bundle.getString(DownloadService.SIGNATURE);
289
286
  Log.i(
290
287
  CapacitorUpdater.TAG,
291
288
  "res " +
@@ -299,8 +296,6 @@ public class CapacitorUpdater {
299
296
  " " +
300
297
  checksum +
301
298
  " " +
302
- signature +
303
- " " +
304
299
  error
305
300
  );
306
301
  if (dest == null) {
@@ -323,7 +318,6 @@ public class CapacitorUpdater {
323
318
  version,
324
319
  sessionKey,
325
320
  checksum,
326
- signature,
327
321
  true
328
322
  );
329
323
  } else {
@@ -333,13 +327,32 @@ public class CapacitorUpdater {
333
327
  }
334
328
  };
335
329
 
330
+ private String decryptChecksum(String checksum, String version)
331
+ throws IOException {
332
+ if (this.publicKey.isEmpty()) {
333
+ Log.e(CapacitorUpdater.TAG, "The public key is empty");
334
+ return checksum;
335
+ }
336
+ try {
337
+ byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
338
+ PublicKey pKey = CryptoCipherV2.stringToPublicKey(this.publicKey);
339
+ byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
340
+ // return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
341
+ String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
342
+ return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
343
+ } catch (GeneralSecurityException e) {
344
+ Log.e(TAG, "decryptChecksum fail: " + e.getMessage());
345
+ this.sendStats("decrypt_fail", version);
346
+ throw new IOException("Decryption failed: " + e.getMessage());
347
+ }
348
+ }
349
+
336
350
  public Boolean finishDownload(
337
351
  String id,
338
352
  String dest,
339
353
  String version,
340
354
  String sessionKey,
341
355
  String checksumRes,
342
- String signature,
343
356
  Boolean setNext
344
357
  ) {
345
358
  File downloaded = null;
@@ -349,36 +362,27 @@ public class CapacitorUpdater {
349
362
  this.notifyDownload(id, 71);
350
363
  downloaded = new File(this.documentsDir, dest);
351
364
 
352
- boolean valid = verifyBundleSignature(version, downloaded, signature);
353
- if (!valid) {
354
- Log.e(
355
- CapacitorUpdater.TAG,
356
- "Invalid signature, cannot accept download"
357
- );
358
-
359
- this.sendStats("invalid_signature", version);
360
- throw new GeneralSecurityException(
361
- "Signature is not valid, cannot accept update"
362
- );
365
+ String checksumDecrypted = Objects.requireNonNullElse(checksumRes, "");
366
+ if (!this.hasOldPrivateKeyPropertyInConfig && !sessionKey.isEmpty()) {
367
+ this.decryptFileV2(downloaded, sessionKey, version);
368
+ checksumDecrypted = this.decryptChecksum(checksumRes, version);
369
+ checksum = this.calcChecksumV2(downloaded);
363
370
  } else {
364
- Log.i(CapacitorUpdater.TAG, "Valid signature");
371
+ this.decryptFile(downloaded, sessionKey, version);
372
+ checksum = this.calcChecksum(downloaded);
365
373
  }
366
-
367
- this.decryptFile(downloaded, sessionKey, version);
368
- checksum = this.calcChecksum(downloaded);
369
374
  if (
370
- checksumRes != null &&
371
- !checksumRes.isEmpty() &&
372
- !checksumRes.equals(checksum)
375
+ (!checksumDecrypted.isEmpty() || !this.publicKey.isEmpty()) &&
376
+ !checksumDecrypted.equals(checksum)
373
377
  ) {
374
378
  Log.e(
375
379
  CapacitorUpdater.TAG,
376
- "Error checksum " + checksumRes + " " + checksum
380
+ "Error checksum '" + checksumDecrypted + "' '" + checksum + "' '"
377
381
  );
378
382
  this.sendStats("checksum_fail");
379
383
  throw new IOException("Checksum failed: " + id);
380
384
  }
381
- } catch (IOException | GeneralSecurityException e) {
385
+ } catch (IOException e) {
382
386
  final Boolean res = this.delete(id);
383
387
  if (!res) {
384
388
  Log.i(CapacitorUpdater.TAG, "Double error, cannot cleanup: " + version);
@@ -443,8 +447,7 @@ public class CapacitorUpdater {
443
447
  final String version,
444
448
  final String sessionKey,
445
449
  final String checksum,
446
- final String dest,
447
- final String signature
450
+ final String dest
448
451
  ) {
449
452
  Intent intent = new Intent(this.activity, DownloadService.class);
450
453
  intent.putExtra(DownloadService.URL, url);
@@ -457,7 +460,6 @@ public class CapacitorUpdater {
457
460
  intent.putExtra(DownloadService.VERSION, version);
458
461
  intent.putExtra(DownloadService.SESSIONKEY, sessionKey);
459
462
  intent.putExtra(DownloadService.CHECKSUM, checksum);
460
- intent.putExtra(DownloadService.SIGNATURE, signature);
461
463
  this.activity.startService(intent);
462
464
  }
463
465
 
@@ -544,44 +546,100 @@ public class CapacitorUpdater {
544
546
  }
545
547
  }
546
548
 
547
- private boolean verifyBundleSignature(
548
- final String version,
549
- final File file,
550
- final String signatureStr
551
- ) throws GeneralSecurityException, IOException {
552
- if (this.signKey == null) {
553
- Log.i(TAG, "Signing not configured");
554
- return true;
549
+ private String calcChecksumV2(File file) {
550
+ final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
551
+ MessageDigest digest;
552
+ try {
553
+ digest = MessageDigest.getInstance("SHA-256");
554
+ } catch (java.security.NoSuchAlgorithmException e) {
555
+ System.err.println(TAG + " SHA-256 algorithm not available");
556
+ return "";
555
557
  }
556
558
 
557
- if (signatureStr.isEmpty()) {
558
- Log.i(TAG, "Signature required but none provided");
559
- this.sendStats("signature_not_provided", version);
560
- throw new GeneralSecurityException(
561
- "Signature was required but none was provided"
559
+ try (FileInputStream fis = new FileInputStream(file)) {
560
+ byte[] buffer = new byte[BUFFER_SIZE];
561
+ int length;
562
+ while ((length = fis.read(buffer)) != -1) {
563
+ digest.update(buffer, 0, length);
564
+ }
565
+ byte[] hash = digest.digest();
566
+ StringBuilder hexString = new StringBuilder();
567
+ for (byte b : hash) {
568
+ String hex = Integer.toHexString(0xff & b);
569
+ if (hex.length() == 1) hexString.append('0');
570
+ hexString.append(hex);
571
+ }
572
+ return hexString.toString();
573
+ } catch (IOException e) {
574
+ System.err.println(
575
+ TAG +
576
+ " Cannot calc checksum v2: " +
577
+ file.getPath() +
578
+ " " +
579
+ e.getMessage()
562
580
  );
581
+ return "";
563
582
  }
583
+ }
564
584
 
565
- byte[] providedSignatureBytes = Base64.decode(
566
- signatureStr.getBytes(StandardCharsets.UTF_8),
567
- Base64.DEFAULT
568
- );
569
-
570
- Signature signature = Signature.getInstance("SHA512withRSA");
571
- signature.initVerify(this.signKey);
572
-
573
- byte[] content = new byte[(int) file.length()];
574
-
575
- try (
576
- final FileInputStream fis = new FileInputStream(file);
577
- final BufferedInputStream bis = new BufferedInputStream(fis);
578
- final DataInputStream dis = new DataInputStream(bis)
585
+ private void decryptFileV2(
586
+ final File file,
587
+ final String ivSessionKey,
588
+ final String version
589
+ ) throws IOException {
590
+ // (str != null && !str.isEmpty())
591
+ if (
592
+ this.publicKey.isEmpty() ||
593
+ ivSessionKey == null ||
594
+ ivSessionKey.isEmpty() ||
595
+ ivSessionKey.split(":").length != 2
579
596
  ) {
580
- dis.readFully(content);
581
- dis.close();
597
+ Log.i(TAG, "Cannot found public key or sessionKey");
598
+ return;
599
+ }
600
+ if (!this.publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
601
+ Log.e(
602
+ CapacitorUpdater.TAG,
603
+ "The public key is not a valid RSA Public key"
604
+ );
605
+ return;
606
+ }
607
+ try {
608
+ String ivB64 = ivSessionKey.split(":")[0];
609
+ String sessionKeyB64 = ivSessionKey.split(":")[1];
610
+ byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
611
+ byte[] sessionKey = Base64.decode(
612
+ sessionKeyB64.getBytes(),
613
+ Base64.DEFAULT
614
+ );
615
+ PublicKey pKey = CryptoCipherV2.stringToPublicKey(this.publicKey);
616
+ byte[] decryptedSessionKey = CryptoCipherV2.decryptRSA(sessionKey, pKey);
582
617
 
583
- signature.update(content);
584
- return signature.verify(providedSignatureBytes);
618
+ SecretKey sKey = CryptoCipherV2.byteToSessionKey(decryptedSessionKey);
619
+ byte[] content = new byte[(int) file.length()];
620
+
621
+ try (
622
+ final FileInputStream fis = new FileInputStream(file);
623
+ final BufferedInputStream bis = new BufferedInputStream(fis);
624
+ final DataInputStream dis = new DataInputStream(bis)
625
+ ) {
626
+ dis.readFully(content);
627
+ dis.close();
628
+ byte[] decrypted = CryptoCipherV2.decryptAES(content, sKey, iv);
629
+ // write the decrypted string to the file
630
+ try (
631
+ final FileOutputStream fos = new FileOutputStream(
632
+ file.getAbsolutePath()
633
+ )
634
+ ) {
635
+ fos.write(decrypted);
636
+ }
637
+ }
638
+ } catch (GeneralSecurityException e) {
639
+ Log.i(TAG, "decryptFile fail");
640
+ this.sendStats("decrypt_fail", version);
641
+ e.printStackTrace();
642
+ throw new IOException("GeneralSecurityException");
585
643
  }
586
644
  }
587
645
 
@@ -591,14 +649,15 @@ public class CapacitorUpdater {
591
649
  final String version
592
650
  ) throws IOException {
593
651
  // (str != null && !str.isEmpty())
594
- if (
595
- this.privateKey == null ||
596
- this.privateKey.isEmpty() ||
652
+ if (this.privateKey == null || this.privateKey.isEmpty()) {
653
+ Log.i(TAG, "Cannot found privateKey");
654
+ return;
655
+ } else if (
597
656
  ivSessionKey == null ||
598
657
  ivSessionKey.isEmpty() ||
599
658
  ivSessionKey.split(":").length != 2
600
659
  ) {
601
- Log.i(TAG, "Cannot found privateKey or sessionKey");
660
+ Log.i(TAG, "Cannot found sessionKey");
602
661
  return;
603
662
  }
604
663
  try {
@@ -643,8 +702,7 @@ public class CapacitorUpdater {
643
702
  final String url,
644
703
  final String version,
645
704
  final String sessionKey,
646
- final String checksum,
647
- final String signature
705
+ final String checksum
648
706
  ) {
649
707
  final String id = this.randomString();
650
708
  this.saveBundleInfo(
@@ -665,8 +723,7 @@ public class CapacitorUpdater {
665
723
  version,
666
724
  sessionKey,
667
725
  checksum,
668
- this.randomString(),
669
- signature
726
+ this.randomString()
670
727
  );
671
728
  }
672
729
 
@@ -674,8 +731,7 @@ public class CapacitorUpdater {
674
731
  final String url,
675
732
  final String version,
676
733
  final String sessionKey,
677
- final String checksum,
678
- final String signature
734
+ final String checksum
679
735
  ) throws IOException {
680
736
  final String id = this.randomString();
681
737
  this.saveBundleInfo(
@@ -693,15 +749,7 @@ public class CapacitorUpdater {
693
749
  final String dest = this.randomString();
694
750
  this.downloadFile(id, url, dest);
695
751
  final Boolean finished =
696
- this.finishDownload(
697
- id,
698
- dest,
699
- version,
700
- sessionKey,
701
- checksum,
702
- signature,
703
- false
704
- );
752
+ this.finishDownload(id, dest, version, sessionKey, checksum, false);
705
753
  final BundleStatus status = finished
706
754
  ? BundleStatus.PENDING
707
755
  : BundleStatus.ERROR;
@@ -30,7 +30,6 @@ import java.io.IOException;
30
30
  import java.lang.reflect.Type;
31
31
  import java.net.MalformedURLException;
32
32
  import java.net.URL;
33
- import java.security.PublicKey;
34
33
  import java.text.SimpleDateFormat;
35
34
  import java.util.ArrayList;
36
35
  import java.util.Date;
@@ -51,12 +50,10 @@ public class CapacitorUpdaterPlugin extends Plugin {
51
50
  private static final String updateUrlDefault =
52
51
  "https://api.capgo.app/updates";
53
52
  private static final String statsUrlDefault = "https://api.capgo.app/stats";
54
- private static final String defaultPrivateKey =
55
- "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4pW9olT0FBXXivRCzd3xcImlWZrqkwcF2xTkX/FwXmj9eh9H\nkBLrsQmfsC+PJisRXIOGq6a0z3bsGq6jBpp3/Jr9jiaW5VuPGaKeMaZZBRvi/N5f\nIMG3hZXSOcy0IYg+E1Q7RkYO1xq5GLHseqG+PXvJsNe4R8R/Bmd/ngq0xh/cvcrH\nHpXwO0Aj9tfprlb+rHaVV79EkVRWYPidOLnK1n0EFHFJ1d/MyDIp10TEGm2xHpf/\nBrlb1an8wXEuzoC0DgYaczgTjovwR+ewSGhSHJliQdM0Qa3o1iN87DldWtydImMs\nPjJ3DUwpsjAMRe5X8Et4+udFW2ciYnQo9H0CkwIDAQABAoIBAQCtjlMV/4qBxAU4\nu0ZcWA9yywwraX0aJ3v1xrfzQYV322Wk4Ea5dbSxA5UcqCE29DA1M824t1Wxv/6z\npWbcTP9xLuresnJMtmgTE7umfiubvTONy2sENT20hgDkIwcq1CfwOEm61zjQzPhQ\nkSB5AmEsyR/BZEsUNc+ygR6AWOUFB7tj4yMc32LOTWSbE/znnF2BkmlmnQykomG1\n2oVqM3lUFP7+m8ux1O7scO6IMts+Z/eFXjWfxpbebUSvSIR83GXPQZ34S/c0ehOg\nyHdmCSOel1r3VvInMe+30j54Jr+Ml/7Ee6axiwyE2e/bd85MsK9sVdp0OtelXaqA\nOZZqWvN5AoGBAP2Hn3lSq+a8GsDH726mHJw60xM0LPbVJTYbXsmQkg1tl3NKJTMM\nQqz41+5uys+phEgLHI9gVJ0r+HaGHXnJ4zewlFjsudstb/0nfctUvTqnhEhfNo9I\ny4kufVKPRF3sMEeo7CDVJs4GNBLycEyIBy6Mbv0VcO7VaZqggRwu4no9AoGBAOTK\n6NWYs1BWlkua2wmxexGOzehNGedInp0wGr2l4FDayWjkZLqvB+nNXUQ63NdHlSs4\nWB2Z1kQXZxVaI2tPYexGUKXEo2uFob63uflbuE029ovDXIIPFTPtGNdNXwhHT5a+\nPhmy3sMc+s2BSNM5qaNmfxQxhdd6gRU6oikE+c0PAoGAMn3cKNFqIt27hkFLUgIL\nGKIuf1iYy9/PNWNmEUaVj88PpopRtkTu0nwMpROzmH/uNFriKTvKHjMvnItBO4wV\nkHW+VadvrFL0Rrqituf9d7z8/1zXBNo+juePVe3qc7oiM2NVA4Tv4YAixtM5wkQl\nCgQ15nlqsGYYTg9BJ1e/CxECgYEAjEYPzO2reuUrjr0p8F59ev1YJ0YmTJRMk0ks\nC/yIdGo/tGzbiU3JB0LfHPcN8Xu07GPGOpfYM7U5gXDbaG6qNgfCaHAQVdr/mQPi\nJQ1kCQtay8QCkscWk9iZM1//lP7LwDtxraXqSCwbZSYP9VlUNZeg8EuQqNU2EUL6\nqzWexmcCgYEA0prUGNBacraTYEknB1CsbP36UPWsqFWOvevlz+uEC5JPxPuW5ZHh\nSQN7xl6+PHyjPBM7ttwPKyhgLOVTb3K7ex/PXnudojMUK5fh7vYfChVTSlx2p6r0\nDi58PdD+node08cJH+ie0Yphp7m+D4+R9XD0v0nEvnu4BtAW6DrJasw=\n-----END RSA PRIVATE KEY-----\n";
56
53
  private static final String channelUrlDefault =
57
54
  "https://api.capgo.app/channel_self";
58
55
 
59
- private final String PLUGIN_VERSION = "6.1.16";
56
+ private final String PLUGIN_VERSION = "6.1.18";
60
57
  private static final String DELAY_CONDITION_PREFERENCES = "";
61
58
 
62
59
  private SharedPreferences.Editor editor;
@@ -138,12 +135,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
138
135
  this.implementation.requestQueue = Volley.newRequestQueue(
139
136
  this.getContext()
140
137
  );
141
- final String signKeyStr = this.getConfig().getString("signKey", "");
142
- if (signKeyStr.length() > 0) {
143
- this.implementation.signKey = CryptoCipher.stringToPublicKey(
144
- signKeyStr
145
- );
146
- }
147
138
 
148
139
  this.implementation.directUpdate = this.getConfig()
149
140
  .getBoolean("directUpdate", false);
@@ -181,8 +172,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
181
172
  );
182
173
  }
183
174
  Log.i(CapacitorUpdater.TAG, "appId: " + implementation.appId);
175
+ this.implementation.publicKey = this.getConfig().getString("publicKey", "");
184
176
  this.implementation.privateKey = this.getConfig()
185
- .getString("privateKey", defaultPrivateKey);
177
+ .getString("privateKey", "");
178
+ if (
179
+ this.implementation.privateKey != null &&
180
+ !this.implementation.privateKey.isEmpty()
181
+ ) {
182
+ this.implementation.hasOldPrivateKeyPropertyInConfig = true;
183
+ }
186
184
  this.implementation.statsUrl = this.getConfig()
187
185
  .getString("statsUrl", statsUrlDefault);
188
186
  this.implementation.channelUrl = this.getConfig()
@@ -583,7 +581,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
583
581
  final String version = call.getString("version");
584
582
  final String sessionKey = call.getString("sessionKey", "");
585
583
  final String checksum = call.getString("checksum", "");
586
- final String signature = call.getString("signature", "");
587
584
  if (url == null) {
588
585
  Log.e(CapacitorUpdater.TAG, "Download called without url");
589
586
  call.reject("Download called without url");
@@ -594,17 +591,6 @@ public class CapacitorUpdaterPlugin extends Plugin {
594
591
  call.reject("Download called without version");
595
592
  return;
596
593
  }
597
- if (
598
- this.implementation.signKey != null &&
599
- (signature == null || signature.isEmpty())
600
- ) {
601
- Log.e(
602
- CapacitorUpdater.TAG,
603
- "Signature required but none provided for download call"
604
- );
605
- call.reject("Signature required but none provided");
606
- return;
607
- }
608
594
  try {
609
595
  Log.i(CapacitorUpdater.TAG, "Downloading " + url);
610
596
  startNewThread(() -> {
@@ -614,8 +600,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
614
600
  url,
615
601
  version,
616
602
  sessionKey,
617
- checksum,
618
- signature
603
+ checksum
619
604
  );
620
605
  if (downloaded.isErrorStatus()) {
621
606
  throw new RuntimeException(
@@ -1283,15 +1268,11 @@ public class CapacitorUpdaterPlugin extends Plugin {
1283
1268
  final String checksum = res.has("checksum")
1284
1269
  ? res.getString("checksum")
1285
1270
  : "";
1286
- final String signature = res.has("signature")
1287
- ? res.getString("signature")
1288
- : "";
1289
1271
  CapacitorUpdaterPlugin.this.implementation.downloadBackground(
1290
1272
  url,
1291
1273
  latestVersionName,
1292
1274
  sessionKey,
1293
- checksum,
1294
- signature
1275
+ checksum
1295
1276
  );
1296
1277
  } catch (final Exception e) {
1297
1278
  Log.e(CapacitorUpdater.TAG, "error downloading file", e);
@@ -0,0 +1,184 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package ee.forgr.capacitor_updater;
8
+
9
+ /**
10
+ * Created by Awesometic
11
+ * It's encrypt returns Base64 encoded, and also decrypt for Base64 encoded cipher
12
+ * references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
13
+ */
14
+ import android.util.Base64;
15
+ import java.security.GeneralSecurityException;
16
+ import java.security.InvalidAlgorithmParameterException;
17
+ import java.security.InvalidKeyException;
18
+ import java.security.KeyFactory;
19
+ import java.security.NoSuchAlgorithmException;
20
+ import java.security.PublicKey;
21
+ import java.security.spec.InvalidKeySpecException;
22
+ import java.security.spec.X509EncodedKeySpec;
23
+ import javax.crypto.BadPaddingException;
24
+ import javax.crypto.Cipher;
25
+ import javax.crypto.IllegalBlockSizeException;
26
+ import javax.crypto.NoSuchPaddingException;
27
+ import javax.crypto.SecretKey;
28
+ import javax.crypto.spec.IvParameterSpec;
29
+ import javax.crypto.spec.PSource;
30
+ import javax.crypto.spec.SecretKeySpec;
31
+
32
+ public class CryptoCipherV2 {
33
+
34
+ public static byte[] decryptRSA(byte[] source, PublicKey publicKey)
35
+ throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
36
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
37
+ cipher.init(Cipher.DECRYPT_MODE, publicKey);
38
+ byte[] decryptedBytes = cipher.doFinal(source);
39
+ return decryptedBytes;
40
+ }
41
+
42
+ public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
43
+ try {
44
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
45
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
46
+ SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
47
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
48
+ return cipher.doFinal(cipherText);
49
+ } catch (Exception e) {
50
+ e.printStackTrace();
51
+ }
52
+ return null;
53
+ }
54
+
55
+ public static SecretKey byteToSessionKey(byte[] sessionKey) {
56
+ // rebuild key using SecretKeySpec
57
+ return new SecretKeySpec(sessionKey, 0, sessionKey.length, "AES");
58
+ }
59
+
60
+ private static PublicKey readX509PublicKey(byte[] x509Bytes)
61
+ throws GeneralSecurityException {
62
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
63
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(x509Bytes);
64
+ try {
65
+ return keyFactory.generatePublic(keySpec);
66
+ } catch (InvalidKeySpecException e) {
67
+ throw new IllegalArgumentException("Unexpected key format!", e);
68
+ }
69
+ }
70
+
71
+ public static PublicKey stringToPublicKey(String public_key)
72
+ throws GeneralSecurityException {
73
+ String pkcs1Pem = public_key
74
+ .replaceAll("\\s+", "")
75
+ .replace("-----BEGINRSAPUBLICKEY-----", "")
76
+ .replace("-----ENDRSAPUBLICKEY-----", "");
77
+
78
+ byte[] pkcs1EncodedBytes = Base64.decode(pkcs1Pem, Base64.DEFAULT);
79
+ return readPkcs1PublicKey(pkcs1EncodedBytes);
80
+ }
81
+
82
+ // since the public key is in pkcs1 format, we have to convert it to x509 format similar
83
+ // to what needs done with the private key converting to pkcs8 format
84
+ // so, the rest of the code below here is adapted from here https://stackoverflow.com/a/54246646
85
+ private static final int SEQUENCE_TAG = 0x30;
86
+ private static final int BIT_STRING_TAG = 0x03;
87
+ private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
88
+ private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE = {
89
+ (byte) 0x30,
90
+ (byte) 0x0d,
91
+ (byte) 0x06,
92
+ (byte) 0x09,
93
+ (byte) 0x2a,
94
+ (byte) 0x86,
95
+ (byte) 0x48,
96
+ (byte) 0x86,
97
+ (byte) 0xf7,
98
+ (byte) 0x0d,
99
+ (byte) 0x01,
100
+ (byte) 0x01,
101
+ (byte) 0x01,
102
+ (byte) 0x05,
103
+ (byte) 0x00,
104
+ };
105
+
106
+ private static PublicKey readPkcs1PublicKey(byte[] pkcs1Bytes)
107
+ throws NoSuchAlgorithmException, InvalidKeySpecException, GeneralSecurityException {
108
+ // convert the pkcs1 public key to an x509 favorable format
109
+ byte[] keyBitString = createDEREncoding(
110
+ BIT_STRING_TAG,
111
+ joinPublic(NO_UNUSED_BITS, pkcs1Bytes)
112
+ );
113
+ byte[] keyInfoValue = joinPublic(
114
+ RSA_ALGORITHM_IDENTIFIER_SEQUENCE,
115
+ keyBitString
116
+ );
117
+ byte[] keyInfoSequence = createDEREncoding(SEQUENCE_TAG, keyInfoValue);
118
+ return readX509PublicKey(keyInfoSequence);
119
+ }
120
+
121
+ private static byte[] joinPublic(byte[]... bas) {
122
+ int len = 0;
123
+ for (int i = 0; i < bas.length; i++) {
124
+ len += bas[i].length;
125
+ }
126
+
127
+ byte[] buf = new byte[len];
128
+ int off = 0;
129
+ for (int i = 0; i < bas.length; i++) {
130
+ System.arraycopy(bas[i], 0, buf, off, bas[i].length);
131
+ off += bas[i].length;
132
+ }
133
+
134
+ return buf;
135
+ }
136
+
137
+ private static byte[] createDEREncoding(int tag, byte[] value) {
138
+ if (tag < 0 || tag >= 0xFF) {
139
+ throw new IllegalArgumentException(
140
+ "Currently only single byte tags supported"
141
+ );
142
+ }
143
+
144
+ byte[] lengthEncoding = createDERLengthEncoding(value.length);
145
+
146
+ int size = 1 + lengthEncoding.length + value.length;
147
+ byte[] derEncodingBuf = new byte[size];
148
+
149
+ int off = 0;
150
+ derEncodingBuf[off++] = (byte) tag;
151
+ System.arraycopy(
152
+ lengthEncoding,
153
+ 0,
154
+ derEncodingBuf,
155
+ off,
156
+ lengthEncoding.length
157
+ );
158
+ off += lengthEncoding.length;
159
+ System.arraycopy(value, 0, derEncodingBuf, off, value.length);
160
+
161
+ return derEncodingBuf;
162
+ }
163
+
164
+ private static byte[] createDERLengthEncoding(int size) {
165
+ if (size <= 0x7F) {
166
+ // single byte length encoding
167
+ return new byte[] { (byte) size };
168
+ } else if (size <= 0xFF) {
169
+ // double byte length encoding
170
+ return new byte[] { (byte) 0x81, (byte) size };
171
+ } else if (size <= 0xFFFF) {
172
+ // triple byte length encoding
173
+ return new byte[] {
174
+ (byte) 0x82,
175
+ (byte) (size >> Byte.SIZE),
176
+ (byte) size,
177
+ };
178
+ }
179
+
180
+ throw new IllegalArgumentException(
181
+ "size too large, only up to 64KiB length encoding supported: " + size
182
+ );
183
+ }
184
+ }