@capgo/capacitor-updater 6.0.50 → 6.0.51-beta.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/README.md CHANGED
@@ -197,7 +197,7 @@ CapacitorUpdater can be configured with these options:
197
197
  | **`resetWhenUpdate`** | <code>boolean</code> | Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device. Only available for Android and iOS. | <code>true</code> | |
198
198
  | **`updateUrl`** | <code>string</code> | Configure the URL / endpoint to which update checks are sent. Only available for Android and iOS. | <code>https://api.capgo.app/updates</code> | |
199
199
  | **`statsUrl`** | <code>string</code> | Configure the URL / endpoint to which update statistics are sent. Only available for Android and iOS. Set to "" to disable stats reporting. | <code>https://api.capgo.app/stats</code> | |
200
- | **`privateKey`** | <code>string</code> | Configure the private key for end to end live update encryption. Only available for Android and iOS. | <code>undefined</code> | |
200
+ | **`publicKey`** | <code>string</code> | Configure the public key for end to end live update encryption. Only available for Android and iOS. | <code>undefined</code> | |
201
201
  | **`version`** | <code>string</code> | Configure the current version of the app. This will be used for the first update request. If not set, the plugin will get the version from the native code. Only available for Android and iOS. | <code>undefined</code> | 4.17.48 |
202
202
  | **`directUpdate`** | <code>boolean</code> | Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode. Only available for Android and iOS. | <code>undefined</code> | 5.1.0 |
203
203
  | **`periodCheckDelay`** | <code>number</code> | Configure the delay period for period update check. the unit is in seconds. Only available for Android and iOS. Cannot be less than 600 seconds (10 minutes). | <code>600 // (10 minutes)</code> | |
@@ -225,7 +225,7 @@ In `capacitor.config.json`:
225
225
  "resetWhenUpdate": false,
226
226
  "updateUrl": https://example.com/api/auto_update,
227
227
  "statsUrl": https://example.com/api/stats,
228
- "privateKey": undefined,
228
+ "publicKey": undefined,
229
229
  "version": undefined,
230
230
  "directUpdate": undefined,
231
231
  "periodCheckDelay": undefined,
@@ -259,7 +259,7 @@ const config: CapacitorConfig = {
259
259
  resetWhenUpdate: false,
260
260
  updateUrl: https://example.com/api/auto_update,
261
261
  statsUrl: https://example.com/api/stats,
262
- privateKey: undefined,
262
+ publicKey: undefined,
263
263
  version: undefined,
264
264
  directUpdate: undefined,
265
265
  periodCheckDelay: undefined,
@@ -11,7 +11,7 @@ buildscript {
11
11
  mavenCentral()
12
12
  }
13
13
  dependencies {
14
- classpath 'com.android.tools.build:gradle:8.5.0'
14
+ classpath 'com.android.tools.build:gradle:8.5.1'
15
15
  }
16
16
  }
17
17
 
@@ -42,6 +42,7 @@ import java.net.URL;
42
42
  import java.net.URLConnection;
43
43
  import java.security.GeneralSecurityException;
44
44
  import java.security.PrivateKey;
45
+ import java.security.PublicKey;
45
46
  import java.security.SecureRandom;
46
47
  import java.util.ArrayList;
47
48
  import java.util.Date;
@@ -86,7 +87,8 @@ public class CapacitorUpdater {
86
87
  public String channelUrl = "";
87
88
  public String defaultChannel = "";
88
89
  public String appId = "";
89
- public String privateKey = "";
90
+ public String publicKey = "";
91
+ public Boolean hasOldPrivateKeyPropertyInConfig = false;
90
92
  public String deviceID = "";
91
93
  public int timeout = 20000;
92
94
 
@@ -337,7 +339,16 @@ public class CapacitorUpdater {
337
339
  ) {
338
340
  try {
339
341
  final File downloaded = new File(this.documentsDir, dest);
342
+ if (this.hasOldPrivateKeyPropertyInConfig) {
343
+ Log.i(
344
+ CapacitorUpdater.TAG,
345
+ "There is still an privateKey property in the config"
346
+ );
347
+ }
348
+
340
349
  this.decryptFile(downloaded, sessionKey, version);
350
+ final String checksumDecrypted =
351
+ this.decryptChecksum(checksumRes, version);
341
352
  final String checksum;
342
353
  checksum = this.getChecksum(downloaded);
343
354
  this.notifyDownload(id, 71);
@@ -357,13 +368,13 @@ public class CapacitorUpdater {
357
368
  );
358
369
  this.saveBundleInfo(id, next);
359
370
  if (
360
- checksumRes != null &&
361
- !checksumRes.isEmpty() &&
362
- !checksumRes.equals(checksum)
371
+ checksumDecrypted != null &&
372
+ !checksumDecrypted.isEmpty() &&
373
+ !checksumDecrypted.equals(checksum)
363
374
  ) {
364
375
  Log.e(
365
376
  CapacitorUpdater.TAG,
366
- "Error checksum " + checksumRes + " " + checksum
377
+ "Error checksum " + checksumDecrypted + " " + checksum
367
378
  );
368
379
  this.sendStats("checksum_fail");
369
380
  final Boolean res = this.delete(id);
@@ -497,6 +508,25 @@ public class CapacitorUpdater {
497
508
  return enc.toLowerCase();
498
509
  }
499
510
 
511
+ private String decryptChecksum(String checksum, String version)
512
+ throws IOException {
513
+ if (this.publicKey == null || this.publicKey.isEmpty()) {
514
+ Log.i(TAG, "Cannot find public key");
515
+ return checksum;
516
+ }
517
+ try {
518
+ byte[] checksumBytes = Base64.decode(checksum.getBytes(), Base64.DEFAULT);
519
+ PublicKey pKey = CryptoCipher.stringToPublicKey(this.publicKey);
520
+ byte[] decryptedChecksum = CryptoCipher.decryptRSA(checksumBytes, pKey);
521
+ return new String(decryptedChecksum);
522
+ } catch (GeneralSecurityException e) {
523
+ Log.i(TAG, "decryptChecksum fail");
524
+ this.sendStats("decrypt_fail", version);
525
+ e.printStackTrace();
526
+ throw new IOException("GeneralSecurityException");
527
+ }
528
+ }
529
+
500
530
  private void decryptFile(
501
531
  final File file,
502
532
  final String ivSessionKey,
@@ -504,15 +534,24 @@ public class CapacitorUpdater {
504
534
  ) throws IOException {
505
535
  // (str != null && !str.isEmpty())
506
536
  if (
507
- this.privateKey == null ||
508
- this.privateKey.isEmpty() ||
537
+ this.publicKey == null ||
538
+ this.publicKey.isEmpty() ||
509
539
  ivSessionKey == null ||
510
540
  ivSessionKey.isEmpty() ||
511
541
  ivSessionKey.split(":").length != 2
512
542
  ) {
513
- Log.i(TAG, "Cannot found privateKey or sessionKey");
543
+ Log.i(TAG, "Cannot find public key or sessionKey");
544
+ return;
545
+ }
546
+
547
+ if (!this.publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
548
+ Log.e(
549
+ CapacitorUpdater.TAG,
550
+ "The public key is not a valid RSA Public key"
551
+ );
514
552
  return;
515
553
  }
554
+
516
555
  try {
517
556
  String ivB64 = ivSessionKey.split(":")[0];
518
557
  String sessionKeyB64 = ivSessionKey.split(":")[1];
@@ -521,8 +560,10 @@ public class CapacitorUpdater {
521
560
  sessionKeyB64.getBytes(),
522
561
  Base64.DEFAULT
523
562
  );
524
- PrivateKey pKey = CryptoCipher.stringToPrivateKey(this.privateKey);
563
+
564
+ PublicKey pKey = CryptoCipher.stringToPublicKey(this.publicKey);
525
565
  byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
566
+
526
567
  SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
527
568
  byte[] content = new byte[(int) file.length()];
528
569
 
@@ -50,12 +50,12 @@ public class CapacitorUpdaterPlugin extends Plugin {
50
50
  private static final String updateUrlDefault =
51
51
  "https://api.capgo.app/updates";
52
52
  private static final String statsUrlDefault = "https://api.capgo.app/stats";
53
- private static final String defaultPrivateKey =
54
- "-----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";
53
+ private static final String defaultPublicKey =
54
+ "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA4pW9olT0FBXXivRCzd3xcImlWZrqkwcF2xTkX/FwXmj9eh9HkBLr\nsQmfsC+PJisRXIOGq6a0z3bsGq6jBpp3/Jr9jiaW5VuPGaKeMaZZBRvi/N5fIMG3\nhZXSOcy0IYg+E1Q7RkYO1xq5GLHseqG+PXvJsNe4R8R/Bmd/ngq0xh/cvcrHHpXw\nO0Aj9tfprlb+rHaVV79EkVRWYPidOLnK1n0EFHFJ1d/MyDIp10TEGm2xHpf/Brlb\n1an8wXEuzoC0DgYaczgTjovwR+ewSGhSHJliQdM0Qa3o1iN87DldWtydImMsPjJ3\nDUwpsjAMRe5X8Et4+udFW2ciYnQo9H0CkwIDAQAB\n-----END RSA PUBLIC KEY-----\n";
55
55
  private static final String channelUrlDefault =
56
56
  "https://api.capgo.app/channel_self";
57
57
 
58
- private final String PLUGIN_VERSION = "6.0.50";
58
+ private final String PLUGIN_VERSION = "6.0.51";
59
59
  private static final String DELAY_CONDITION_PREFERENCES = "";
60
60
 
61
61
  private SharedPreferences.Editor editor;
@@ -172,8 +172,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
172
172
  );
173
173
  }
174
174
  Log.i(CapacitorUpdater.TAG, "appId: " + implementation.appId);
175
- this.implementation.privateKey = this.getConfig()
176
- .getString("privateKey", defaultPrivateKey);
175
+ this.implementation.publicKey = getConfig()
176
+ .getString("publicKey", defaultPublicKey);
177
+ this.implementation.hasOldPrivateKeyPropertyInConfig = false;
178
+ if (
179
+ this.getConfig().getString("privateKey") != null &&
180
+ !this.getConfig().getString("privateKey").isEmpty()
181
+ ) {
182
+ this.implementation.hasOldPrivateKeyPropertyInConfig = true;
183
+ }
177
184
  this.implementation.statsUrl = this.getConfig()
178
185
  .getString("statsUrl", statsUrlDefault);
179
186
  this.implementation.channelUrl = this.getConfig()
@@ -17,33 +17,26 @@ import java.security.InvalidAlgorithmParameterException;
17
17
  import java.security.InvalidKeyException;
18
18
  import java.security.KeyFactory;
19
19
  import java.security.NoSuchAlgorithmException;
20
- import java.security.PrivateKey;
20
+ import java.security.PublicKey;
21
21
  import java.security.spec.InvalidKeySpecException;
22
- import java.security.spec.MGF1ParameterSpec;
23
- import java.security.spec.PKCS8EncodedKeySpec;
22
+ import java.security.spec.X509EncodedKeySpec;
24
23
  import javax.crypto.BadPaddingException;
25
24
  import javax.crypto.Cipher;
26
25
  import javax.crypto.IllegalBlockSizeException;
27
26
  import javax.crypto.NoSuchPaddingException;
28
27
  import javax.crypto.SecretKey;
29
28
  import javax.crypto.spec.IvParameterSpec;
30
- import javax.crypto.spec.OAEPParameterSpec;
31
29
  import javax.crypto.spec.PSource;
32
30
  import javax.crypto.spec.SecretKeySpec;
33
31
 
34
32
  public class CryptoCipher {
35
33
 
36
- public static byte[] decryptRSA(byte[] source, PrivateKey privateKey)
34
+ public static byte[] decryptRSA(byte[] source, PublicKey publicKey)
37
35
  throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
38
- Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
39
- OAEPParameterSpec oaepParams = new OAEPParameterSpec(
40
- "SHA-256",
41
- "MGF1",
42
- new MGF1ParameterSpec("SHA-256"),
43
- PSource.PSpecified.DEFAULT
44
- );
45
- cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
46
- return cipher.doFinal(source);
36
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
37
+ cipher.init(Cipher.DECRYPT_MODE, publicKey);
38
+ byte[] decryptedBytes = cipher.doFinal(source);
39
+ return decryptedBytes;
47
40
  }
48
41
 
49
42
  public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
@@ -64,82 +57,133 @@ public class CryptoCipher {
64
57
  return new SecretKeySpec(sessionKey, 0, sessionKey.length, "AES");
65
58
  }
66
59
 
67
- private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes)
60
+ private static PublicKey readX509PublicKey(byte[] x509Bytes)
68
61
  throws GeneralSecurityException {
69
62
  KeyFactory keyFactory = KeyFactory.getInstance("RSA");
70
- PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
63
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(x509Bytes);
71
64
  try {
72
- return keyFactory.generatePrivate(keySpec);
65
+ return keyFactory.generatePublic(keySpec);
73
66
  } catch (InvalidKeySpecException e) {
74
67
  throw new IllegalArgumentException("Unexpected key format!", e);
75
68
  }
76
69
  }
77
70
 
78
- private static byte[] join(byte[] byteArray1, byte[] byteArray2) {
79
- byte[] bytes = new byte[byteArray1.length + byteArray2.length];
80
- System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
81
- System.arraycopy(
82
- byteArray2,
83
- 0,
84
- bytes,
85
- byteArray1.length,
86
- byteArray2.length
71
+ public static PublicKey stringToPublicKey(String public_key)
72
+ throws GeneralSecurityException {
73
+ // Base64 decode the result
74
+
75
+ String pkcs1Pem = public_key.toString();
76
+ pkcs1Pem = pkcs1Pem.replace("-----BEGIN RSA PUBLIC KEY-----", "");
77
+ pkcs1Pem = pkcs1Pem.replace("-----END RSA PUBLIC KEY-----", "");
78
+ pkcs1Pem = pkcs1Pem.replace("\\n", "");
79
+ pkcs1Pem = pkcs1Pem.replace(" ", "");
80
+
81
+ byte[] pkcs1EncodedBytes = Base64.decode(pkcs1Pem, Base64.DEFAULT);
82
+
83
+ // extract the public key
84
+ return readPkcs1PublicKey(pkcs1EncodedBytes);
85
+ }
86
+
87
+ // since the public key is in pkcs1 format, we have to convert it to x509 format similar
88
+ // to what needs done with the private key converting to pkcs8 format
89
+ // so, the rest of the code below here is adapted from here https://stackoverflow.com/a/54246646
90
+ private static final int SEQUENCE_TAG = 0x30;
91
+ private static final int BIT_STRING_TAG = 0x03;
92
+ private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
93
+ private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE = {
94
+ (byte) 0x30,
95
+ (byte) 0x0d,
96
+ (byte) 0x06,
97
+ (byte) 0x09,
98
+ (byte) 0x2a,
99
+ (byte) 0x86,
100
+ (byte) 0x48,
101
+ (byte) 0x86,
102
+ (byte) 0xf7,
103
+ (byte) 0x0d,
104
+ (byte) 0x01,
105
+ (byte) 0x01,
106
+ (byte) 0x01,
107
+ (byte) 0x05,
108
+ (byte) 0x00,
109
+ };
110
+
111
+ private static PublicKey readPkcs1PublicKey(byte[] pkcs1Bytes)
112
+ throws NoSuchAlgorithmException, InvalidKeySpecException, GeneralSecurityException {
113
+ // convert the pkcs1 public key to an x509 favorable format
114
+ byte[] keyBitString = createDEREncoding(
115
+ BIT_STRING_TAG,
116
+ joinPublic(NO_UNUSED_BITS, pkcs1Bytes)
87
117
  );
88
- return bytes;
118
+ byte[] keyInfoValue = joinPublic(
119
+ RSA_ALGORITHM_IDENTIFIER_SEQUENCE,
120
+ keyBitString
121
+ );
122
+ byte[] keyInfoSequence = createDEREncoding(SEQUENCE_TAG, keyInfoValue);
123
+ return readX509PublicKey(keyInfoSequence);
89
124
  }
90
125
 
91
- private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes)
92
- throws GeneralSecurityException {
93
- // We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand
94
- int pkcs1Length = pkcs1Bytes.length;
95
- int totalLength = pkcs1Length + 22;
96
- byte[] pkcs8Header = new byte[] {
97
- 0x30,
98
- (byte) 0x82,
99
- (byte) ((totalLength >> 8) & 0xff),
100
- (byte) (totalLength & 0xff), // Sequence + total length
101
- 0x2,
102
- 0x1,
103
- 0x0, // Integer (0)
104
- 0x30,
105
- 0xD,
106
- 0x6,
107
- 0x9,
108
- 0x2A,
109
- (byte) 0x86,
110
- 0x48,
111
- (byte) 0x86,
112
- (byte) 0xF7,
113
- 0xD,
114
- 0x1,
115
- 0x1,
116
- 0x1,
117
- 0x5,
118
- 0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
119
- 0x4,
120
- (byte) 0x82,
121
- (byte) ((pkcs1Length >> 8) & 0xff),
122
- (byte) (pkcs1Length & 0xff), // Octet string + length
123
- };
124
- byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes);
125
- return readPkcs8PrivateKey(pkcs8bytes);
126
+ private static byte[] joinPublic(byte[]... bas) {
127
+ int len = 0;
128
+ for (int i = 0; i < bas.length; i++) {
129
+ len += bas[i].length;
130
+ }
131
+
132
+ byte[] buf = new byte[len];
133
+ int off = 0;
134
+ for (int i = 0; i < bas.length; i++) {
135
+ System.arraycopy(bas[i], 0, buf, off, bas[i].length);
136
+ off += bas[i].length;
137
+ }
138
+
139
+ return buf;
126
140
  }
127
141
 
128
- public static PrivateKey stringToPrivateKey(String private_key)
129
- throws GeneralSecurityException {
130
- // Base64 decode the result
142
+ private static byte[] createDEREncoding(int tag, byte[] value) {
143
+ if (tag < 0 || tag >= 0xFF) {
144
+ throw new IllegalArgumentException(
145
+ "Currently only single byte tags supported"
146
+ );
147
+ }
131
148
 
132
- String pkcs1Pem = private_key;
133
- pkcs1Pem = pkcs1Pem.replace("-----BEGIN RSA PRIVATE KEY-----", "");
134
- pkcs1Pem = pkcs1Pem.replace("-----END RSA PRIVATE KEY-----", "");
135
- pkcs1Pem = pkcs1Pem.replace("\\n", "");
136
- pkcs1Pem = pkcs1Pem.replace(" ", "");
149
+ byte[] lengthEncoding = createDERLengthEncoding(value.length);
150
+
151
+ int size = 1 + lengthEncoding.length + value.length;
152
+ byte[] derEncodingBuf = new byte[size];
153
+
154
+ int off = 0;
155
+ derEncodingBuf[off++] = (byte) tag;
156
+ System.arraycopy(
157
+ lengthEncoding,
158
+ 0,
159
+ derEncodingBuf,
160
+ off,
161
+ lengthEncoding.length
162
+ );
163
+ off += lengthEncoding.length;
164
+ System.arraycopy(value, 0, derEncodingBuf, off, value.length);
165
+
166
+ return derEncodingBuf;
167
+ }
168
+
169
+ private static byte[] createDERLengthEncoding(int size) {
170
+ if (size <= 0x7F) {
171
+ // single byte length encoding
172
+ return new byte[] { (byte) size };
173
+ } else if (size <= 0xFF) {
174
+ // double byte length encoding
175
+ return new byte[] { (byte) 0x81, (byte) size };
176
+ } else if (size <= 0xFFFF) {
177
+ // triple byte length encoding
178
+ return new byte[] {
179
+ (byte) 0x82,
180
+ (byte) (size >> Byte.SIZE),
181
+ (byte) size,
182
+ };
183
+ }
137
184
 
138
- byte[] pkcs1EncodedBytes = Base64.decode(
139
- pkcs1Pem.getBytes(),
140
- Base64.DEFAULT
185
+ throw new IllegalArgumentException(
186
+ "size too large, only up to 64KiB length encoding supported: " + size
141
187
  );
142
- // extract the private key
143
- return readPkcs1PrivateKey(pkcs1EncodedBytes);
144
188
  }
145
189
  }
package/dist/docs.json CHANGED
@@ -1881,14 +1881,14 @@
1881
1881
  "type": "string | undefined"
1882
1882
  },
1883
1883
  {
1884
- "name": "privateKey",
1884
+ "name": "publicKey",
1885
1885
  "tags": [
1886
1886
  {
1887
1887
  "text": "undefined",
1888
1888
  "name": "default"
1889
1889
  }
1890
1890
  ],
1891
- "docs": "Configure the private key for end to end live update encryption.\n\nOnly available for Android and iOS.",
1891
+ "docs": "Configure the public key for end to end live update encryption.\n\nOnly available for Android and iOS.",
1892
1892
  "complexTypes": [],
1893
1893
  "type": "string | undefined"
1894
1894
  },
@@ -78,13 +78,13 @@ declare module "@capacitor/cli" {
78
78
  */
79
79
  statsUrl?: string;
80
80
  /**
81
- * Configure the private key for end to end live update encryption.
81
+ * Configure the public key for end to end live update encryption.
82
82
  *
83
83
  * Only available for Android and iOS.
84
84
  *
85
85
  * @default undefined
86
86
  */
87
- privateKey?: string;
87
+ publicKey?: string;
88
88
  /**
89
89
  * Configure the current version of the app. This will be used for the first update request.
90
90
  * If not set, the plugin will get the version from the native code.
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from \"@capacitor/core\";\n\ndeclare module \"@capacitor/cli\" {\n export interface PluginsConfig {\n /**\n * CapacitorUpdater can be configured with these options:\n */\n CapacitorUpdater?: {\n /**\n * Configure the number of milliseconds the native plugin should wait before considering an update 'failed'.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @example 1000 // (1 second)\n */\n appReadyTimeout?: number;\n /**\n * Configure the number of milliseconds the native plugin should wait before considering API timeout.\n *\n * Only available for Android and iOS.\n *\n * @default 20 // (20 second)\n * @example 10 // (10 second)\n */\n responseTimeout?: number;\n /**\n * Configure whether the plugin should use automatically delete failed bundles.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeleteFailed?: boolean;\n\n /**\n * Configure whether the plugin should use automatically delete previous bundles after a successful update.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeletePrevious?: boolean;\n\n /**\n * Configure whether the plugin should use Auto Update via an update server.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoUpdate?: boolean;\n\n /**\n * Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n resetWhenUpdate?: boolean;\n\n /**\n * Configure the URL / endpoint to which update checks are sent.\n *\n * Only available for Android and iOS.\n *\n * @default https://api.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint to which update statistics are sent.\n *\n * Only available for Android and iOS. Set to \"\" to disable stats reporting.\n *\n * @default https://api.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the private key for end to end live update encryption.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n */\n privateKey?: string;\n\n /**\n * Configure the current version of the app. This will be used for the first update request.\n * If not set, the plugin will get the version from the native code.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 4.17.48\n */\n version?: string;\n /**\n * Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 5.1.0\n */\n directUpdate?: boolean;\n\n /**\n * Configure the delay period for period update check. the unit is in seconds.\n *\n * Only available for Android and iOS.\n * Cannot be less than 600 seconds (10 minutes).\n *\n * @default 600 // (10 minutes)\n */\n periodCheckDelay?: number;\n\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localS3?: boolean;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localWebHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupa?: string;\n /**\n * Configure the CLI to use a local server for testing.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupaAnon?: string;\n\n /**\n * Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 5.4.0\n */\n allowModifyUrl?: boolean;\n\n /**\n * Set the default channel for the app in the config.\n *\n *\n *\n * @default undefined\n * @since 5.5.0\n */\n defaultChannel?: string;\n };\n }\n}\n\nexport interface CapacitorUpdaterPlugin {\n /**\n * Notify Capacitor Updater that the current bundle is working (a rollback will occur if this method is not called on every app launch)\n * By default this method should be called in the first 10 sec after app launch, otherwise a rollback will occur.\n * Change this behaviour with {@link appReadyTimeout}\n *\n * @returns {Promise<AppReadyResult>} an Promise resolved directly\n * @throws {Error}\n */\n notifyAppReady(): Promise<AppReadyResult>;\n\n /**\n * Set the updateUrl for the app, this will be used to check for updates.\n *\n * @param options contains the URL to use for checking for updates.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setUpdateUrl(options: UpdateUrl): Promise<void>;\n\n /**\n * Set the statsUrl for the app, this will be used to send statistics. Passing an empty string will disable statistics gathering.\n *\n * @param options contains the URL to use for sending statistics.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setStatsUrl(options: StatsUrl): Promise<void>;\n\n /**\n * Set the channelUrl for the app, this will be used to set the channel.\n *\n * @param options contains the URL to use for setting the channel.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setChannelUrl(options: ChannelUrl): Promise<void>;\n\n /**\n * Download a new bundle from the provided URL, it should be a zip file, with files inside or with a unique id inside with all your files\n *\n * @example const bundle = await CapacitorUpdater.download({ url: `https://example.com/versions/${version}/dist.zip`, version });\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle.\n * @param options The {@link DownloadOptions} for downloading a new bundle zip.\n */\n download(options: DownloadOptions): Promise<BundleInfo>;\n\n /**\n * Set the next bundle to be used when the app is reloaded.\n *\n * @param options Contains the ID of the next Bundle to set on next app launch. {@link BundleInfo.id}\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle id.\n * @throws {Error} When there is no index.html file inside the bundle folder.\n */\n next(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Set the current bundle and immediately reloads the app.\n *\n * @param options A {@link BundleId} object containing the new bundle id to set as current.\n * @returns {Promise<void>}\n * @throws {Error} When there are is no index.html file inside the bundle folder.\n */\n set(options: BundleId): Promise<void>;\n\n /**\n * Deletes the specified bundle from the native app storage. Use with {@link list} to get the stored Bundle IDs.\n *\n * @param options A {@link BundleId} object containing the ID of a bundle to delete (note, this is the bundle id, NOT the version name)\n * @returns {Promise<void>} When the bundle is deleted\n * @throws {Error}\n */\n delete(options: BundleId): Promise<void>;\n\n /**\n * Get all locally downloaded bundles in your app\n *\n * @returns {Promise<BundleListResult>} A Promise containing the {@link BundleListResult.bundles}\n * @throws {Error}\n */\n list(): Promise<BundleListResult>;\n\n /**\n * Reset the app to the `builtin` bundle (the one sent to Apple App Store / Google Play Store ) or the last successfully loaded bundle.\n *\n * @param options Containing {@link ResetOptions.toLastSuccessful}, `true` resets to the builtin bundle and `false` will reset to the last successfully loaded bundle.\n * @returns {Promise<void>}\n * @throws {Error}\n */\n reset(options?: ResetOptions): Promise<void>;\n\n /**\n * Get the current bundle, if none are set it returns `builtin`. currentNative is the original bundle installed on the device\n *\n * @returns {Promise<CurrentBundleResult>} A Promise evaluating to the {@link CurrentBundleResult}\n * @throws {Error}\n */\n current(): Promise<CurrentBundleResult>;\n\n /**\n * Reload the view\n *\n * @returns {Promise<void>} A Promise which is resolved when the view is reloaded\n * @throws {Error}\n */\n reload(): Promise<void>;\n\n /**\n * Sets a {@link DelayCondition} array containing conditions that the Plugin will use to delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n *\n * @example\n * // Delay the update after the user kills the app or after a background of 300000 ms (5 minutes)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'kill' }, { kind: 'background', value: '300000' }] })\n * @example\n * // Delay the update after the specific iso8601 date is expired\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'date', value: '2022-09-14T06:14:11.920Z' }] })\n * @example\n * // Delay the update after the first background (default behaviour without setting delay)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'background' }] })\n * @param options Containing the {@link MultiDelayConditions} array of conditions to set\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.3.0\n */\n setMultiDelay(options: MultiDelayConditions): Promise<void>;\n\n /**\n * Cancels a {@link DelayCondition} to process an update immediately.\n *\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.0.0\n */\n cancelDelay(): Promise<void>;\n\n /**\n * Get Latest bundle available from update Url\n *\n * @returns {Promise<LatestVersion>} A Promise resolved when url is loaded\n * @throws {Error}\n * @since 4.0.0\n */\n getLatest(): Promise<LatestVersion>;\n\n /**\n * Sets the channel for this device. The channel has to allow for self assignment for this to work.\n * Do not use this method to set the channel at boot when `autoUpdate` is enabled in the {@link PluginsConfig}.\n * This method is to set the channel after the app is ready.\n *\n * @param options Is the {@link SetChannelOptions} channel to set\n * @returns {Promise<ChannelRes>} A Promise which is resolved when the new channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n setChannel(options: SetChannelOptions): Promise<ChannelRes>;\n\n /**\n * Unset the channel for this device. The device will then return to the default channel\n *\n * @returns {Promise<ChannelRes>} A Promise resolved when channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n unsetChannel(options: UnsetChannelOptions): Promise<void>;\n\n /**\n * Get the channel for this device\n *\n * @returns {Promise<ChannelRes>} A Promise that resolves with the channel info\n * @throws {Error}\n * @since 4.8.0\n */\n getChannel(): Promise<GetChannelRes>;\n\n /**\n * Set a custom ID for this device\n *\n * @param options is the {@link SetCustomIdOptions} customId to set\n * @returns {Promise<void>} an Promise resolved instantly\n * @throws {Error}\n * @since 4.9.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n\n /**\n * Get the native app version or the builtin version if set in config\n *\n * @returns {Promise<BuiltinVersion>} A Promise with version for this device\n * @since 5.2.0\n */\n getBuiltinVersion(): Promise<BuiltinVersion>;\n\n /**\n * Get unique ID used to identify device (sent to auto update server)\n *\n * @returns {Promise<DeviceId>} A Promise with id for this device\n * @throws {Error}\n */\n getDeviceId(): Promise<DeviceId>;\n\n /**\n * Get the native Capacitor Updater plugin version (sent to auto update server)\n *\n * @returns {Promise<PluginVersion>} A Promise with Plugin version\n * @throws {Error}\n */\n getPluginVersion(): Promise<PluginVersion>;\n\n /**\n * Get the state of auto update config.\n *\n * @returns {Promise<AutoUpdateEnabled>} The status for auto update. Evaluates to `false` in manual mode.\n * @throws {Error}\n */\n isAutoUpdateEnabled(): Promise<AutoUpdateEnabled>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Listen for bundle download event in the App. Fires once a download has started, during downloading and when finished.\n *\n * @since 2.0.11\n */\n addListener(\n eventName: \"download\",\n listenerFunc: (state: DownloadEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for no need to update event, useful when you want force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"noNeedUpdate\",\n listenerFunc: (state: NoNeedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for available update event, useful when you want to force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"updateAvailable\",\n listenerFunc: (state: UpdateAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for downloadComplete events.\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadComplete\",\n listenerFunc: (state: DownloadCompleteEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for Major update event in the App, let you know when major update is blocked by setting disableAutoUpdateBreaking\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"majorAvailable\",\n listenerFunc: (state: MajorAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for update fail event in the App, let you know when update has fail to install at next app start\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"updateFailed\",\n listenerFunc: (state: UpdateFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for download fail event in the App, let you know when a bundle download has failed\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadFailed\",\n listenerFunc: (state: DownloadFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for reload event in the App, let you know when reload has happened\n *\n * @since 4.3.0\n */\n addListener(\n eventName: \"appReloaded\",\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for app ready event in the App, let you know when app is ready to use\n *\n * @since 5.1.0\n */\n addListener(\n eventName: \"appReady\",\n listenerFunc: (state: AppReadyEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n\nexport type BundleStatus = \"success\" | \"error\" | \"pending\" | \"downloading\";\n\nexport type DelayUntilNext = \"background\" | \"kill\" | \"nativeVersion\" | \"date\";\n\nexport interface NoNeedEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateAvailableEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface ChannelRes {\n /**\n * Current status of set channel\n *\n * @since 4.7.0\n */\n status: string;\n error?: any;\n message?: any;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: any;\n message?: any;\n status?: string;\n allowSet?: boolean;\n}\n\nexport interface DownloadEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n percent: number;\n bundle: BundleInfo;\n}\n\nexport interface MajorAvailableEvent {\n /**\n * Emit when a new major bundle is available.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadFailedEvent {\n /**\n * Emit when a download fail.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadCompleteEvent {\n /**\n * Emit when a new update is available.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateFailedEvent {\n /**\n * Emit when a update failed to install.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface AppReadyEvent {\n /**\n * Emitted when the app is ready to use.\n *\n * @since 5.2.0\n */\n bundle: BundleInfo;\n status: string;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n}\n\nexport interface BundleInfo {\n id: string;\n version: string;\n downloaded: string;\n checksum: string;\n status: BundleStatus;\n}\n\nexport interface SetChannelOptions {\n channel: string;\n triggerAutoUpdate?: boolean;\n}\n\nexport interface UnsetChannelOptions {\n triggerAutoUpdate?: boolean;\n}\n\nexport interface SetCustomIdOptions {\n customId: string;\n}\n\nexport interface DelayCondition {\n /**\n * Set up delay conditions in setMultiDelay\n * @param value is useless for @param kind \"kill\", optional for \"background\" (default value: \"0\") and required for \"nativeVersion\" and \"date\"\n */\n kind: DelayUntilNext;\n value?: string;\n}\n\nexport interface AppReadyResult {\n bundle: BundleInfo;\n}\n\nexport interface UpdateUrl {\n url: string;\n}\n\nexport interface StatsUrl {\n url: string;\n}\n\nexport interface ChannelUrl {\n url: string;\n}\n\nexport interface DownloadOptions {\n /**\n * The URL of the bundle zip file (e.g: dist.zip) to be downloaded. (This can be any URL. E.g: Amazon S3, a GitHub tag, any other place you've hosted your bundle.)\n */\n url: string;\n /**\n * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update\n * @since 4.0.0\n * @default undefined\n */\n checksum?: string;\n}\n\nexport interface BundleId {\n id: string;\n}\n\nexport interface BundleListResult {\n bundles: BundleInfo[];\n}\n\nexport interface ResetOptions {\n toLastSuccessful: boolean;\n}\n\nexport interface CurrentBundleResult {\n bundle: BundleInfo;\n native: string;\n}\n\nexport interface MultiDelayConditions {\n delayConditions: DelayCondition[];\n}\n\nexport interface BuiltinVersion {\n version: string;\n}\n\nexport interface DeviceId {\n deviceId: string;\n}\n\nexport interface PluginVersion {\n version: string;\n}\n\nexport interface AutoUpdateEnabled {\n enabled: boolean;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"AAAA;;;;GAIG","sourcesContent":["/*\n * This Source Code Form is subject to the terms of the Mozilla Public\n * License, v. 2.0. If a copy of the MPL was not distributed with this\n * file, You can obtain one at https://mozilla.org/MPL/2.0/.\n */\n\n/// <reference types=\"@capacitor/cli\" />\n\nimport type { PluginListenerHandle } from \"@capacitor/core\";\n\ndeclare module \"@capacitor/cli\" {\n export interface PluginsConfig {\n /**\n * CapacitorUpdater can be configured with these options:\n */\n CapacitorUpdater?: {\n /**\n * Configure the number of milliseconds the native plugin should wait before considering an update 'failed'.\n *\n * Only available for Android and iOS.\n *\n * @default 10000 // (10 seconds)\n * @example 1000 // (1 second)\n */\n appReadyTimeout?: number;\n /**\n * Configure the number of milliseconds the native plugin should wait before considering API timeout.\n *\n * Only available for Android and iOS.\n *\n * @default 20 // (20 second)\n * @example 10 // (10 second)\n */\n responseTimeout?: number;\n /**\n * Configure whether the plugin should use automatically delete failed bundles.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeleteFailed?: boolean;\n\n /**\n * Configure whether the plugin should use automatically delete previous bundles after a successful update.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoDeletePrevious?: boolean;\n\n /**\n * Configure whether the plugin should use Auto Update via an update server.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n autoUpdate?: boolean;\n\n /**\n * Automatically delete previous downloaded bundles when a newer native app bundle is installed to the device.\n *\n * Only available for Android and iOS.\n *\n * @default true\n * @example false\n */\n resetWhenUpdate?: boolean;\n\n /**\n * Configure the URL / endpoint to which update checks are sent.\n *\n * Only available for Android and iOS.\n *\n * @default https://api.capgo.app/updates\n * @example https://example.com/api/auto_update\n */\n updateUrl?: string;\n\n /**\n * Configure the URL / endpoint to which update statistics are sent.\n *\n * Only available for Android and iOS. Set to \"\" to disable stats reporting.\n *\n * @default https://api.capgo.app/stats\n * @example https://example.com/api/stats\n */\n statsUrl?: string;\n /**\n * Configure the public key for end to end live update encryption.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n */\n publicKey?: string;\n\n /**\n * Configure the current version of the app. This will be used for the first update request.\n * If not set, the plugin will get the version from the native code.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 4.17.48\n */\n version?: string;\n /**\n * Make the plugin direct install the update when the app what just updated/installed. Only for autoUpdate mode.\n *\n * Only available for Android and iOS.\n *\n * @default undefined\n * @since 5.1.0\n */\n directUpdate?: boolean;\n\n /**\n * Configure the delay period for period update check. the unit is in seconds.\n *\n * Only available for Android and iOS.\n * Cannot be less than 600 seconds (10 minutes).\n *\n * @default 600 // (10 minutes)\n */\n periodCheckDelay?: number;\n\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localS3?: boolean;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localWebHost?: string;\n /**\n * Configure the CLI to use a local server for testing or self-hosted update server.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupa?: string;\n /**\n * Configure the CLI to use a local server for testing.\n *\n *\n * @default undefined\n * @since 4.17.48\n */\n localSupaAnon?: string;\n\n /**\n * Allow the plugin to modify the updateUrl, statsUrl and channelUrl dynamically from the JavaScript side.\n *\n *\n * @default false\n * @since 5.4.0\n */\n allowModifyUrl?: boolean;\n\n /**\n * Set the default channel for the app in the config.\n *\n *\n *\n * @default undefined\n * @since 5.5.0\n */\n defaultChannel?: string;\n };\n }\n}\n\nexport interface CapacitorUpdaterPlugin {\n /**\n * Notify Capacitor Updater that the current bundle is working (a rollback will occur if this method is not called on every app launch)\n * By default this method should be called in the first 10 sec after app launch, otherwise a rollback will occur.\n * Change this behaviour with {@link appReadyTimeout}\n *\n * @returns {Promise<AppReadyResult>} an Promise resolved directly\n * @throws {Error}\n */\n notifyAppReady(): Promise<AppReadyResult>;\n\n /**\n * Set the updateUrl for the app, this will be used to check for updates.\n *\n * @param options contains the URL to use for checking for updates.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setUpdateUrl(options: UpdateUrl): Promise<void>;\n\n /**\n * Set the statsUrl for the app, this will be used to send statistics. Passing an empty string will disable statistics gathering.\n *\n * @param options contains the URL to use for sending statistics.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setStatsUrl(options: StatsUrl): Promise<void>;\n\n /**\n * Set the channelUrl for the app, this will be used to set the channel.\n *\n * @param options contains the URL to use for setting the channel.\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 5.4.0\n */\n setChannelUrl(options: ChannelUrl): Promise<void>;\n\n /**\n * Download a new bundle from the provided URL, it should be a zip file, with files inside or with a unique id inside with all your files\n *\n * @example const bundle = await CapacitorUpdater.download({ url: `https://example.com/versions/${version}/dist.zip`, version });\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle.\n * @param options The {@link DownloadOptions} for downloading a new bundle zip.\n */\n download(options: DownloadOptions): Promise<BundleInfo>;\n\n /**\n * Set the next bundle to be used when the app is reloaded.\n *\n * @param options Contains the ID of the next Bundle to set on next app launch. {@link BundleInfo.id}\n * @returns {Promise<BundleInfo>} The {@link BundleInfo} for the specified bundle id.\n * @throws {Error} When there is no index.html file inside the bundle folder.\n */\n next(options: BundleId): Promise<BundleInfo>;\n\n /**\n * Set the current bundle and immediately reloads the app.\n *\n * @param options A {@link BundleId} object containing the new bundle id to set as current.\n * @returns {Promise<void>}\n * @throws {Error} When there are is no index.html file inside the bundle folder.\n */\n set(options: BundleId): Promise<void>;\n\n /**\n * Deletes the specified bundle from the native app storage. Use with {@link list} to get the stored Bundle IDs.\n *\n * @param options A {@link BundleId} object containing the ID of a bundle to delete (note, this is the bundle id, NOT the version name)\n * @returns {Promise<void>} When the bundle is deleted\n * @throws {Error}\n */\n delete(options: BundleId): Promise<void>;\n\n /**\n * Get all locally downloaded bundles in your app\n *\n * @returns {Promise<BundleListResult>} A Promise containing the {@link BundleListResult.bundles}\n * @throws {Error}\n */\n list(): Promise<BundleListResult>;\n\n /**\n * Reset the app to the `builtin` bundle (the one sent to Apple App Store / Google Play Store ) or the last successfully loaded bundle.\n *\n * @param options Containing {@link ResetOptions.toLastSuccessful}, `true` resets to the builtin bundle and `false` will reset to the last successfully loaded bundle.\n * @returns {Promise<void>}\n * @throws {Error}\n */\n reset(options?: ResetOptions): Promise<void>;\n\n /**\n * Get the current bundle, if none are set it returns `builtin`. currentNative is the original bundle installed on the device\n *\n * @returns {Promise<CurrentBundleResult>} A Promise evaluating to the {@link CurrentBundleResult}\n * @throws {Error}\n */\n current(): Promise<CurrentBundleResult>;\n\n /**\n * Reload the view\n *\n * @returns {Promise<void>} A Promise which is resolved when the view is reloaded\n * @throws {Error}\n */\n reload(): Promise<void>;\n\n /**\n * Sets a {@link DelayCondition} array containing conditions that the Plugin will use to delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n *\n * @example\n * // Delay the update after the user kills the app or after a background of 300000 ms (5 minutes)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'kill' }, { kind: 'background', value: '300000' }] })\n * @example\n * // Delay the update after the specific iso8601 date is expired\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'date', value: '2022-09-14T06:14:11.920Z' }] })\n * @example\n * // Delay the update after the first background (default behaviour without setting delay)\n * await CapacitorUpdater.setMultiDelay({ delayConditions: [{ kind: 'background' }] })\n * @param options Containing the {@link MultiDelayConditions} array of conditions to set\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.3.0\n */\n setMultiDelay(options: MultiDelayConditions): Promise<void>;\n\n /**\n * Cancels a {@link DelayCondition} to process an update immediately.\n *\n * @returns {Promise<void>}\n * @throws {Error}\n * @since 4.0.0\n */\n cancelDelay(): Promise<void>;\n\n /**\n * Get Latest bundle available from update Url\n *\n * @returns {Promise<LatestVersion>} A Promise resolved when url is loaded\n * @throws {Error}\n * @since 4.0.0\n */\n getLatest(): Promise<LatestVersion>;\n\n /**\n * Sets the channel for this device. The channel has to allow for self assignment for this to work.\n * Do not use this method to set the channel at boot when `autoUpdate` is enabled in the {@link PluginsConfig}.\n * This method is to set the channel after the app is ready.\n *\n * @param options Is the {@link SetChannelOptions} channel to set\n * @returns {Promise<ChannelRes>} A Promise which is resolved when the new channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n setChannel(options: SetChannelOptions): Promise<ChannelRes>;\n\n /**\n * Unset the channel for this device. The device will then return to the default channel\n *\n * @returns {Promise<ChannelRes>} A Promise resolved when channel is set\n * @throws {Error}\n * @since 4.7.0\n */\n unsetChannel(options: UnsetChannelOptions): Promise<void>;\n\n /**\n * Get the channel for this device\n *\n * @returns {Promise<ChannelRes>} A Promise that resolves with the channel info\n * @throws {Error}\n * @since 4.8.0\n */\n getChannel(): Promise<GetChannelRes>;\n\n /**\n * Set a custom ID for this device\n *\n * @param options is the {@link SetCustomIdOptions} customId to set\n * @returns {Promise<void>} an Promise resolved instantly\n * @throws {Error}\n * @since 4.9.0\n */\n setCustomId(options: SetCustomIdOptions): Promise<void>;\n\n /**\n * Get the native app version or the builtin version if set in config\n *\n * @returns {Promise<BuiltinVersion>} A Promise with version for this device\n * @since 5.2.0\n */\n getBuiltinVersion(): Promise<BuiltinVersion>;\n\n /**\n * Get unique ID used to identify device (sent to auto update server)\n *\n * @returns {Promise<DeviceId>} A Promise with id for this device\n * @throws {Error}\n */\n getDeviceId(): Promise<DeviceId>;\n\n /**\n * Get the native Capacitor Updater plugin version (sent to auto update server)\n *\n * @returns {Promise<PluginVersion>} A Promise with Plugin version\n * @throws {Error}\n */\n getPluginVersion(): Promise<PluginVersion>;\n\n /**\n * Get the state of auto update config.\n *\n * @returns {Promise<AutoUpdateEnabled>} The status for auto update. Evaluates to `false` in manual mode.\n * @throws {Error}\n */\n isAutoUpdateEnabled(): Promise<AutoUpdateEnabled>;\n\n /**\n * Remove all listeners for this plugin.\n *\n * @since 1.0.0\n */\n removeAllListeners(): Promise<void>;\n\n /**\n * Listen for bundle download event in the App. Fires once a download has started, during downloading and when finished.\n *\n * @since 2.0.11\n */\n addListener(\n eventName: \"download\",\n listenerFunc: (state: DownloadEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for no need to update event, useful when you want force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"noNeedUpdate\",\n listenerFunc: (state: NoNeedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for available update event, useful when you want to force check every time the app is launched\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"updateAvailable\",\n listenerFunc: (state: UpdateAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for downloadComplete events.\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadComplete\",\n listenerFunc: (state: DownloadCompleteEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for Major update event in the App, let you know when major update is blocked by setting disableAutoUpdateBreaking\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"majorAvailable\",\n listenerFunc: (state: MajorAvailableEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for update fail event in the App, let you know when update has fail to install at next app start\n *\n * @since 2.3.0\n */\n addListener(\n eventName: \"updateFailed\",\n listenerFunc: (state: UpdateFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for download fail event in the App, let you know when a bundle download has failed\n *\n * @since 4.0.0\n */\n addListener(\n eventName: \"downloadFailed\",\n listenerFunc: (state: DownloadFailedEvent) => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for reload event in the App, let you know when reload has happened\n *\n * @since 4.3.0\n */\n addListener(\n eventName: \"appReloaded\",\n listenerFunc: () => void,\n ): Promise<PluginListenerHandle>;\n\n /**\n * Listen for app ready event in the App, let you know when app is ready to use\n *\n * @since 5.1.0\n */\n addListener(\n eventName: \"appReady\",\n listenerFunc: (state: AppReadyEvent) => void,\n ): Promise<PluginListenerHandle>;\n}\n\nexport type BundleStatus = \"success\" | \"error\" | \"pending\" | \"downloading\";\n\nexport type DelayUntilNext = \"background\" | \"kill\" | \"nativeVersion\" | \"date\";\n\nexport interface NoNeedEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateAvailableEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface ChannelRes {\n /**\n * Current status of set channel\n *\n * @since 4.7.0\n */\n status: string;\n error?: any;\n message?: any;\n}\n\nexport interface GetChannelRes {\n /**\n * Current status of get channel\n *\n * @since 4.8.0\n */\n channel?: string;\n error?: any;\n message?: any;\n status?: string;\n allowSet?: boolean;\n}\n\nexport interface DownloadEvent {\n /**\n * Current status of download, between 0 and 100.\n *\n * @since 4.0.0\n */\n percent: number;\n bundle: BundleInfo;\n}\n\nexport interface MajorAvailableEvent {\n /**\n * Emit when a new major bundle is available.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadFailedEvent {\n /**\n * Emit when a download fail.\n *\n * @since 4.0.0\n */\n version: string;\n}\n\nexport interface DownloadCompleteEvent {\n /**\n * Emit when a new update is available.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface UpdateFailedEvent {\n /**\n * Emit when a update failed to install.\n *\n * @since 4.0.0\n */\n bundle: BundleInfo;\n}\n\nexport interface AppReadyEvent {\n /**\n * Emitted when the app is ready to use.\n *\n * @since 5.2.0\n */\n bundle: BundleInfo;\n status: string;\n}\n\nexport interface LatestVersion {\n /**\n * Result of getLatest method\n *\n * @since 4.0.0\n */\n version: string;\n major?: boolean;\n message?: string;\n sessionKey?: string;\n error?: string;\n old?: string;\n url?: string;\n}\n\nexport interface BundleInfo {\n id: string;\n version: string;\n downloaded: string;\n checksum: string;\n status: BundleStatus;\n}\n\nexport interface SetChannelOptions {\n channel: string;\n triggerAutoUpdate?: boolean;\n}\n\nexport interface UnsetChannelOptions {\n triggerAutoUpdate?: boolean;\n}\n\nexport interface SetCustomIdOptions {\n customId: string;\n}\n\nexport interface DelayCondition {\n /**\n * Set up delay conditions in setMultiDelay\n * @param value is useless for @param kind \"kill\", optional for \"background\" (default value: \"0\") and required for \"nativeVersion\" and \"date\"\n */\n kind: DelayUntilNext;\n value?: string;\n}\n\nexport interface AppReadyResult {\n bundle: BundleInfo;\n}\n\nexport interface UpdateUrl {\n url: string;\n}\n\nexport interface StatsUrl {\n url: string;\n}\n\nexport interface ChannelUrl {\n url: string;\n}\n\nexport interface DownloadOptions {\n /**\n * The URL of the bundle zip file (e.g: dist.zip) to be downloaded. (This can be any URL. E.g: Amazon S3, a GitHub tag, any other place you've hosted your bundle.)\n */\n url: string;\n /**\n * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update\n * @since 4.0.0\n * @default undefined\n */\n checksum?: string;\n}\n\nexport interface BundleId {\n id: string;\n}\n\nexport interface BundleListResult {\n bundles: BundleInfo[];\n}\n\nexport interface ResetOptions {\n toLastSuccessful: boolean;\n}\n\nexport interface CurrentBundleResult {\n bundle: BundleInfo;\n native: string;\n}\n\nexport interface MultiDelayConditions {\n delayConditions: DelayCondition[];\n}\n\nexport interface BuiltinVersion {\n version: string;\n}\n\nexport interface DeviceId {\n deviceId: string;\n}\n\nexport interface PluginVersion {\n version: string;\n}\n\nexport interface AutoUpdateEnabled {\n enabled: boolean;\n}\n"]}
@@ -169,6 +169,7 @@ enum CustomError: Error {
169
169
  case cannotUnflat
170
170
  case cannotCreateDirectory
171
171
  case cannotDeleteDirectory
172
+ case cannotDecryptSessionKey
172
173
 
173
174
  // Throw in all other cases
174
175
  case unexpected(code: Int)
@@ -205,13 +206,18 @@ extension CustomError: LocalizedError {
205
206
  case .cannotDecode:
206
207
  return NSLocalizedString(
207
208
  "Decoding the zip failed with this key",
208
- comment: "Invalid private key"
209
+ comment: "Invalid public key"
209
210
  )
210
211
  case .cannotWrite:
211
212
  return NSLocalizedString(
212
213
  "Cannot write to the destination",
213
214
  comment: "Invalid destination"
214
215
  )
216
+ case .cannotDecryptSessionKey:
217
+ return NSLocalizedString(
218
+ "Decrypting the session key failed",
219
+ comment: "Invalid session key"
220
+ )
215
221
  }
216
222
  }
217
223
  }
@@ -239,7 +245,8 @@ extension CustomError: LocalizedError {
239
245
  public var defaultChannel: String = ""
240
246
  public var appId: String = ""
241
247
  public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
242
- public var privateKey: String = ""
248
+ public var publicKey: String? = ""
249
+ public var hasOldPrivateKeyPropertyInConfig: Bool = false
243
250
 
244
251
  public var notifyDownload: (String, Int) -> Void = { _, _ in }
245
252
 
@@ -364,14 +371,37 @@ extension CustomError: LocalizedError {
364
371
  }
365
372
  }
366
373
 
374
+ public func decryptChecksum(checksum: String, version: String) throws -> String {
375
+ if self.publicKey != nil && self.publicKey!.isEmpty {
376
+ print("\(self.TAG) Cannot find public key")
377
+ return checksum
378
+ }
379
+ do {
380
+ let checksumBytes: Data = Data(base64Encoded: checksum)!
381
+ guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey!) else {
382
+ print("cannot decode publicKey", self.publicKey!)
383
+ throw CustomError.cannotDecode
384
+ }
385
+ guard let decryptedChecksum = try? rsaPublicKey.decrypt(data: checksumBytes) else {
386
+ throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
387
+ }
388
+ return decryptedChecksum.base64EncodedString()
389
+ } catch {
390
+ print("\(self.TAG) Cannot decrypt checksum: \(checksum)", error)
391
+ self.sendStats(action: "decrypt_fail", versionName: version)
392
+ throw CustomError.cannotDecode
393
+ }
394
+ }
395
+
367
396
  private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
368
- if self.privateKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
369
- print("\(self.TAG) Cannot found privateKey or sessionKey")
397
+ if self.publicKey != nil && self.publicKey!.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
398
+ print("\(self.TAG) Cannot find public key or sessionKey")
370
399
  return
371
400
  }
401
+
372
402
  do {
373
- guard let rsaPrivateKey: RSAPrivateKey = .load(rsaPrivateKey: self.privateKey) else {
374
- print("cannot decode privateKey", self.privateKey)
403
+ guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey!) else {
404
+ print("cannot decode publicKey", self.publicKey!)
375
405
  throw CustomError.cannotDecode
376
406
  }
377
407
 
@@ -381,21 +411,25 @@ extension CustomError: LocalizedError {
381
411
  throw CustomError.cannotDecode
382
412
  }
383
413
 
414
+ // guard let base64EncodedData = sessionKeyArray[1].data(using: .utf8)! else {
415
+ // throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
416
+ // }
417
+
384
418
  guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
385
419
  throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
386
420
  }
387
421
 
388
- guard let sessionKeyDataDecrypted = rsaPrivateKey.decrypt(data: sessionKeyDataEncrypted) else {
422
+ guard let sessionKeyDataDecrypted = try? rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
389
423
  throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
390
424
  }
391
425
 
392
- let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
426
+ let aesPublicKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
393
427
 
394
428
  guard let encryptedData = try? Data(contentsOf: filePath) else {
395
429
  throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
396
430
  }
397
431
 
398
- guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
432
+ guard let decryptedData = try? aesPublicKey.decrypt(data: encryptedData) else {
399
433
  throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
400
434
  }
401
435
 
@@ -580,7 +614,12 @@ extension CustomError: LocalizedError {
580
614
  case .success:
581
615
  self.notifyDownload(id, 71)
582
616
  do {
617
+ if self.hasOldPrivateKeyPropertyInConfig {
618
+ print("\(self.TAG) There is still an privateKey property in the config")
619
+ }
620
+
583
621
  try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
622
+ let checksumDecrypted = try self.decryptChecksum(checksum: checksum, version: version)
584
623
  checksum = self.getChecksum(filePath: fileURL)
585
624
  try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
586
625
  try self.deleteFolder(source: fileURL)
@@ -15,14 +15,14 @@ import Version
15
15
  @objc(CapacitorUpdaterPlugin)
16
16
  public class CapacitorUpdaterPlugin: CAPPlugin {
17
17
  public var implementation = CapacitorUpdater()
18
- private let PLUGIN_VERSION: String = "6.0.50"
18
+ private let PLUGIN_VERSION: String = "6.0.51"
19
19
  static let updateUrlDefault = "https://api.capgo.app/updates"
20
20
  static let statsUrlDefault = "https://api.capgo.app/stats"
21
21
  static let channelUrlDefault = "https://api.capgo.app/channel_self"
22
22
  let DELAY_CONDITION_PREFERENCES = ""
23
23
  private var updateUrl = ""
24
24
  private var statsUrl = ""
25
- private var defaultPrivateKey = "-----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"
25
+ private var defaultPublicKey = "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEA4pW9olT0FBXXivRCzd3xcImlWZrqkwcF2xTkX/FwXmj9eh9HkBLr\nsQmfsC+PJisRXIOGq6a0z3bsGq6jBpp3/Jr9jiaW5VuPGaKeMaZZBRvi/N5fIMG3\nhZXSOcy0IYg+E1Q7RkYO1xq5GLHseqG+PXvJsNe4R8R/Bmd/ngq0xh/cvcrHHpXw\nO0Aj9tfprlb+rHaVV79EkVRWYPidOLnK1n0EFHFJ1d/MyDIp10TEGm2xHpf/Brlb\n1an8wXEuzoC0DgYaczgTjovwR+ewSGhSHJliQdM0Qa3o1iN87DldWtydImMsPjJ3\nDUwpsjAMRe5X8Et4+udFW2ciYnQo9H0CkwIDAQAB\n-----END RSA PUBLIC KEY-----\n"
26
26
  private var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
27
27
  private var currentVersionNative: Version = "0.0.0"
28
28
  private var autoUpdate = false
@@ -67,7 +67,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
67
67
  periodCheckDelay = periodCheckDelayValue
68
68
  }
69
69
 
70
- implementation.privateKey = getConfig().getString("privateKey", self.defaultPrivateKey)!
70
+ implementation.publicKey = getConfig().getString("publicKey", self.defaultPublicKey)!
71
+ implementation.hasOldPrivateKeyPropertyInConfig = false
72
+ if getConfig().getString("privateKey") != nil && !getConfig().getString("privateKey")!.isEmpty {
73
+ implementation.hasOldPrivateKeyPropertyInConfig = true
74
+ }
71
75
  implementation.notifyDownload = notifyDownload
72
76
  implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
73
77
  let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
@@ -240,12 +244,13 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
240
244
  return
241
245
  }
242
246
  let sessionKey = call.getString("sessionKey", "")
243
- let checksum = call.getString("checksum", "")
247
+ var checksum = call.getString("checksum", "")
244
248
  let url = URL(string: urlString)
245
249
  print("\(self.implementation.TAG) Downloading \(String(describing: url))")
246
250
  DispatchQueue.global(qos: .background).async {
247
251
  do {
248
252
  let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
253
+ checksum = try self.implementation.decryptChecksum(checksum: checksum, version: version)
249
254
  if checksum != "" && next.getChecksum() != checksum {
250
255
  print("\(self.implementation.TAG) Error checksum", next.getChecksum(), checksum)
251
256
  self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
@@ -712,6 +717,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
712
717
  self.endBackGroundTaskWithNotif(msg: "Latest version is in error state. Aborting update.", latestVersionName: latestVersionName, current: current)
713
718
  return
714
719
  }
720
+ res.checksum = try self.implementation.decryptChecksum(checksum: res.checksum, version: latestVersionName)
715
721
  if res.checksum != "" && next.getChecksum() != res.checksum {
716
722
  print("\(self.implementation.TAG) Error checksum", next.getChecksum(), res.checksum)
717
723
  self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
@@ -14,7 +14,7 @@ private enum CryptoCipherConstants {
14
14
  static let rsaKeySizeInBits: NSNumber = 2048
15
15
  static let aesAlgorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
16
16
  static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
17
- static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
17
+ static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionPKCS1
18
18
  }
19
19
  ///
20
20
  /// The AES key. Contains both the initialization vector and secret key.
@@ -32,7 +32,7 @@ public struct AES128Key {
32
32
  self.aes128Key = aes128Key
33
33
  }
34
34
  ///
35
- /// Takes the data and uses the private key to decrypt it. Will call `CCCrypt` in CommonCrypto
35
+ /// Takes the data and uses the key to decrypt it. Will call `CCCrypt` in CommonCrypto
36
36
  /// and provide it `ivData` for the initialization vector. Will use cipher block chaining (CBC) as
37
37
  /// the mode of operation.
38
38
  ///
@@ -67,73 +67,36 @@ public struct AES128Key {
67
67
  }
68
68
 
69
69
  ///
70
- /// The RSA keypair. Includes both private and public key.
70
+ /// The RSA public key.
71
71
  ///
72
- public struct RSAKeyPair {
73
- private let privateKey: SecKey
72
+ public struct RSAPublicKey {
74
73
  private let publicKey: SecKey
75
74
 
76
75
  #if DEBUG
77
- public var __debug_privateKey: SecKey { self.privateKey }
78
76
  public var __debug_publicKey: SecKey { self.publicKey }
79
77
  #endif
80
78
 
81
- fileprivate init(privateKey: SecKey, publicKey: SecKey) {
82
- self.privateKey = privateKey
79
+ fileprivate init(publicKey: SecKey) {
83
80
  self.publicKey = publicKey
84
81
  }
85
82
 
86
- public func extractPublicKey() -> RSAPublicKey {
87
- RSAPublicKey(publicKey: publicKey)
88
- }
89
-
90
83
  ///
91
- /// Takes the data and uses the private key to decrypt it.
84
+ /// Takes the data and uses the public key to decrypt it.
92
85
  /// Returns the decrypted data.
93
86
  ///
94
87
  public func decrypt(data: Data) -> Data? {
95
- var error: Unmanaged<CFError>?
96
- if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
97
- if error != nil {
98
- return nil
99
- } else {
100
- return decryptedData as Data
88
+ do {
89
+ guard let decryptedData = RSAPublicKey.decryptWithRSAKey(data, rsaKeyRef: self.publicKey, padding: SecPadding()) else {
90
+ throw CustomError.cannotDecryptSessionKey
101
91
  }
102
- } else {
103
- return nil
104
- }
105
- }
106
- }
107
-
108
- ///
109
- /// The RSA public key.
110
- ///
111
- public struct RSAPublicKey {
112
- private let publicKey: SecKey
113
92
 
114
- #if DEBUG
115
- public var __debug_publicKey: SecKey { self.publicKey }
116
- #endif
117
-
118
- fileprivate init(publicKey: SecKey) {
119
- self.publicKey = publicKey
120
- }
121
- ///
122
- /// Takes the data and uses the public key to encrypt it.
123
- /// Returns the encrypted data.
124
- ///
125
- public func encrypt(data: Data) -> Data? {
126
- var error: Unmanaged<CFError>?
127
- if let encryptedData: CFData = SecKeyCreateEncryptedData(self.publicKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
128
- if error != nil {
129
- return nil
130
- } else {
131
- return encryptedData as Data
132
- }
133
- } else {
93
+ return decryptedData
94
+ } catch {
95
+ print("Error decrypting data: \(error)")
134
96
  return nil
135
97
  }
136
98
  }
99
+
137
100
  ///
138
101
  /// Allows you to export the RSA public key to a format (so you can send over the net).
139
102
  ///
@@ -145,72 +108,87 @@ public struct RSAPublicKey {
145
108
  ///
146
109
  /// Allows you to load an RSA public key (i.e. one downloaded from the net).
147
110
  ///
148
- public static func load(rsaPublicKeyData: Data) -> RSAPublicKey? {
149
- if let publicKey: SecKey = .loadPublicFromData(rsaPublicKeyData) {
111
+ public static func load(rsaPublicKey: String) -> RSAPublicKey? {
112
+ var pubKey: String = rsaPublicKey
113
+ pubKey = pubKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
114
+ pubKey = pubKey.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
115
+ pubKey = pubKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
116
+ pubKey = pubKey.trimmingCharacters(in: .whitespacesAndNewlines)
117
+ do {
118
+ guard let rsaPublicKeyData: Data = Data(base64Encoded: String(pubKey)) else {
119
+ throw CustomError.cannotDecode
120
+ }
121
+
122
+ guard let publicKey: SecKey = .loadPublicFromData(rsaPublicKeyData) else {
123
+ throw CustomError.cannotDecode
124
+ }
125
+
150
126
  return RSAPublicKey(publicKey: publicKey)
151
- } else {
127
+ } catch {
128
+ print("Error load RSA: \(error)")
152
129
  return nil
153
130
  }
154
131
  }
155
- }
156
- ///
157
- /// The RSA public key.
158
- ///
159
- public struct RSAPrivateKey {
160
- private let privateKey: SecKey
161
132
 
162
- #if DEBUG
163
- public var __debug_privateKey: SecKey { self.privateKey }
164
- #endif
133
+ // code is copied from here: https://github.com/btnguyen2k/swiftutils/blob/88494f4c635b6c6d42ef0fb30a7d666acd38c4fa/SwiftUtils/RSAUtils.swift#L393
134
+ public static func decryptWithRSAKey(_ encryptedData: Data, rsaKeyRef: SecKey, padding: SecPadding) -> Data? {
135
+ let blockSize = SecKeyGetBlockSize(rsaKeyRef)
136
+ let dataSize = encryptedData.count / MemoryLayout<UInt8>.size
165
137
 
166
- fileprivate init(privateKey: SecKey) {
167
- self.privateKey = privateKey
168
- }
169
- ///
170
- /// Takes the data and uses the private key to decrypt it.
171
- /// Returns the decrypted data.
172
- ///
173
- public func decrypt(data: Data) -> Data? {
174
- var error: Unmanaged<CFError>?
175
- if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
176
- if error != nil {
177
- return nil
178
- } else {
179
- return decryptedData as Data
138
+ var encryptedDataAsArray = [UInt8](repeating: 0, count: dataSize)
139
+ (encryptedData as NSData).getBytes(&encryptedDataAsArray, length: dataSize)
140
+
141
+ var decryptedData = [UInt8](repeating: 0, count: 0)
142
+ var idx = 0
143
+ while idx < encryptedDataAsArray.count {
144
+ var idxEnd = idx + blockSize
145
+ if idxEnd > encryptedDataAsArray.count {
146
+ idxEnd = encryptedDataAsArray.count
147
+ }
148
+ var chunkData = [UInt8](repeating: 0, count: blockSize)
149
+ for i in idx..<idxEnd {
150
+ chunkData[i-idx] = encryptedDataAsArray[i]
180
151
  }
181
- } else {
182
- return nil
183
- }
184
- }
185
152
 
186
- ///
187
- /// Allows you to export the RSA public key to a format (so you can send over the net).
188
- ///
189
- public func export() -> Data? {
190
- return privateKey.exportToData()
191
- }
153
+ var decryptedDataBuffer = [UInt8](repeating: 0, count: blockSize)
154
+ var decryptedDataLength = blockSize
192
155
 
193
- ///
194
- /// Allows you to load an RSA public key (i.e. one downloaded from the net).
195
- ///
196
- public static func load(rsaPrivateKey: String) -> RSAPrivateKey? {
197
- var privKey: String = rsaPrivateKey
198
- privKey = privKey.replacingOccurrences(of: "-----BEGIN RSA PRIVATE KEY-----", with: "")
199
- privKey = privKey.replacingOccurrences(of: "-----END RSA PRIVATE KEY-----", with: "")
200
- privKey = privKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
201
- privKey = privKey.trimmingCharacters(in: .whitespacesAndNewlines)
202
- do {
203
- guard let rsaPrivateKeyData: Data = Data(base64Encoded: privKey) else {
204
- throw CustomError.cannotDecode
156
+ let status = SecKeyDecrypt(rsaKeyRef, padding, chunkData, idxEnd-idx, &decryptedDataBuffer, &decryptedDataLength)
157
+ if status != noErr {
158
+ return nil
205
159
  }
206
- guard let privateKey: SecKey = .loadPrivateFromData(rsaPrivateKeyData) else {
207
- throw CustomError.cannotDecode
160
+ let finalData = removePadding(decryptedDataBuffer)
161
+ decryptedData += finalData
162
+
163
+ idx += blockSize
164
+ }
165
+
166
+ return Data(decryptedData)
167
+ }
168
+
169
+ // code is copied from here: https://github.com/btnguyen2k/swiftutils/blob/88494f4c635b6c6d42ef0fb30a7d666acd38c4fa/SwiftUtils/RSAUtils.swift#L429
170
+ private static func removePadding(_ data: [UInt8]) -> [UInt8] {
171
+ var idxFirstZero = -1
172
+ var idxNextZero = data.count
173
+ for i in 0..<data.count {
174
+ if data[i] == 0 {
175
+ if idxFirstZero < 0 {
176
+ idxFirstZero = i
177
+ } else {
178
+ idxNextZero = i
179
+ break
180
+ }
208
181
  }
209
- return RSAPrivateKey(privateKey: privateKey)
210
- } catch {
211
- print("Error load RSA: \(error)")
212
- return nil
213
182
  }
183
+ if idxNextZero-idxFirstZero-1 == 0 {
184
+ idxNextZero = idxFirstZero
185
+ idxFirstZero = -1
186
+ }
187
+ var newData = [UInt8](repeating: 0, count: idxNextZero-idxFirstZero-1)
188
+ for i in idxFirstZero+1..<idxNextZero {
189
+ newData[i-idxFirstZero-1] = data[i]
190
+ }
191
+ return newData
214
192
  }
215
193
  }
216
194
 
@@ -233,14 +211,6 @@ fileprivate extension SecKey {
233
211
  kSecAttrKeyClass: kSecAttrKeyClassPublic,
234
212
  kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
235
213
  ]
236
- return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
237
- }
238
- static func loadPrivateFromData(_ data: Data) -> SecKey? {
239
- let keyDict: [NSObject: NSObject] = [
240
- kSecAttrKeyType: kSecAttrKeyTypeRSA,
241
- kSecAttrKeyClass: kSecAttrKeyClassPrivate,
242
- kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
243
- ]
244
- return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
214
+ return SecKeyCreateWithData(data as NSData, keyDict as CFDictionary, nil)
245
215
  }
246
216
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-updater",
3
- "version": "6.0.50",
3
+ "version": "6.0.51-beta.0",
4
4
  "packageManager": "pnpm@8.15.9",
5
5
  "license": "MPL-2.0",
6
6
  "description": "Live update for capacitor apps",