@capgo/capacitor-updater 5.7.17 → 6.0.0-alpha.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 +7 -4
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +33 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +16 -6
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +119 -75
- package/dist/docs.json +18 -2
- package/dist/esm/definitions.d.ts +11 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +32 -9
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +10 -4
- package/ios/Plugin/CryptoCipher.swift +83 -113
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -165,7 +165,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
165
165
|
| **`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> | |
|
|
166
166
|
| **`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/auto_update</code> | |
|
|
167
167
|
| **`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> | |
|
|
168
|
-
| **`
|
|
168
|
+
| **`publicKey`** | <code>string</code> | Configure the public key for end to end live update encryption. Only available for Android and iOS. | <code>undefined</code> | |
|
|
169
169
|
| **`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 |
|
|
170
170
|
| **`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 |
|
|
171
171
|
| **`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> | |
|
|
@@ -176,6 +176,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
176
176
|
| **`localSupaAnon`** | <code>string</code> | Configure the CLI to use a local server for testing. | <code>undefined</code> | 4.17.48 |
|
|
177
177
|
| **`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 |
|
|
178
178
|
| **`defaultChannel`** | <code>string</code> | Set the default channel for the app in the config. | <code>undefined</code> | 5.5.0 |
|
|
179
|
+
| **`forceEncryption`** | <code>boolean</code> | If set to true with encryption enabled, the plugin will only accept encrypted bundles. | <code>true</code> | 6.0.0 |
|
|
179
180
|
|
|
180
181
|
### Examples
|
|
181
182
|
|
|
@@ -193,7 +194,7 @@ In `capacitor.config.json`:
|
|
|
193
194
|
"resetWhenUpdate": false,
|
|
194
195
|
"updateUrl": https://example.com/api/auto_update,
|
|
195
196
|
"statsUrl": https://example.com/api/stats,
|
|
196
|
-
"
|
|
197
|
+
"publicKey": undefined,
|
|
197
198
|
"version": undefined,
|
|
198
199
|
"directUpdate": undefined,
|
|
199
200
|
"periodCheckDelay": undefined,
|
|
@@ -203,7 +204,8 @@ In `capacitor.config.json`:
|
|
|
203
204
|
"localSupa": undefined,
|
|
204
205
|
"localSupaAnon": undefined,
|
|
205
206
|
"allowModifyUrl": undefined,
|
|
206
|
-
"defaultChannel": undefined
|
|
207
|
+
"defaultChannel": undefined,
|
|
208
|
+
"forceEncryption": undefined
|
|
207
209
|
}
|
|
208
210
|
}
|
|
209
211
|
}
|
|
@@ -227,7 +229,7 @@ const config: CapacitorConfig = {
|
|
|
227
229
|
resetWhenUpdate: false,
|
|
228
230
|
updateUrl: https://example.com/api/auto_update,
|
|
229
231
|
statsUrl: https://example.com/api/stats,
|
|
230
|
-
|
|
232
|
+
publicKey: undefined,
|
|
231
233
|
version: undefined,
|
|
232
234
|
directUpdate: undefined,
|
|
233
235
|
periodCheckDelay: undefined,
|
|
@@ -238,6 +240,7 @@ const config: CapacitorConfig = {
|
|
|
238
240
|
localSupaAnon: undefined,
|
|
239
241
|
allowModifyUrl: undefined,
|
|
240
242
|
defaultChannel: undefined,
|
|
243
|
+
forceEncryption: undefined,
|
|
241
244
|
},
|
|
242
245
|
},
|
|
243
246
|
};
|
|
@@ -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,9 +87,11 @@ public class CapacitorUpdater {
|
|
|
86
87
|
public String channelUrl = "";
|
|
87
88
|
public String defaultChannel = "";
|
|
88
89
|
public String appId = "";
|
|
89
|
-
public String
|
|
90
|
+
public String publicKey = "";
|
|
91
|
+
public Boolean hasOldPrivateKeyPropertyInConfig = false;
|
|
90
92
|
public String deviceID = "";
|
|
91
93
|
public int timeout = 20000;
|
|
94
|
+
public Boolean forceEncryption = false;
|
|
92
95
|
|
|
93
96
|
private final FilenameFilter filter = (f, name) -> {
|
|
94
97
|
// ignore directories generated by mac os x
|
|
@@ -338,6 +341,13 @@ public class CapacitorUpdater {
|
|
|
338
341
|
) {
|
|
339
342
|
try {
|
|
340
343
|
final File downloaded = new File(this.documentsDir, dest);
|
|
344
|
+
if (this.hasOldPrivateKeyPropertyInConfig) {
|
|
345
|
+
Log.i(
|
|
346
|
+
CapacitorUpdater.TAG,
|
|
347
|
+
"There is still an privateKey property in the config"
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
|
|
341
351
|
this.decryptFile(downloaded, sessionKey, version);
|
|
342
352
|
final String checksum;
|
|
343
353
|
checksum = this.getChecksum(downloaded);
|
|
@@ -503,17 +513,32 @@ public class CapacitorUpdater {
|
|
|
503
513
|
final String ivSessionKey,
|
|
504
514
|
final String version
|
|
505
515
|
) throws IOException {
|
|
506
|
-
// (str != null && !str.isEmpty())
|
|
507
516
|
if (
|
|
508
|
-
this.privateKey == null ||
|
|
509
|
-
this.privateKey.isEmpty() ||
|
|
510
517
|
ivSessionKey == null ||
|
|
511
518
|
ivSessionKey.isEmpty() ||
|
|
512
519
|
ivSessionKey.split(":").length != 2
|
|
513
520
|
) {
|
|
514
|
-
|
|
521
|
+
if (
|
|
522
|
+
this.forceEncryption &&
|
|
523
|
+
this.publicKey != null &&
|
|
524
|
+
!this.publicKey.isEmpty()
|
|
525
|
+
) {
|
|
526
|
+
Log.i(TAG, "Cannot accept non-encrypted bundle");
|
|
527
|
+
this.sendStats("decrypt_fail", version);
|
|
528
|
+
throw new IOException("GeneralSecurityException");
|
|
529
|
+
}
|
|
530
|
+
Log.i(TAG, "Cannot find public key or sessionKey");
|
|
515
531
|
return;
|
|
516
532
|
}
|
|
533
|
+
|
|
534
|
+
if (!this.publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
535
|
+
Log.e(
|
|
536
|
+
CapacitorUpdater.TAG,
|
|
537
|
+
"The public key is not a valid RSA Public key"
|
|
538
|
+
);
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
517
542
|
try {
|
|
518
543
|
String ivB64 = ivSessionKey.split(":")[0];
|
|
519
544
|
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
@@ -522,8 +547,10 @@ public class CapacitorUpdater {
|
|
|
522
547
|
sessionKeyB64.getBytes(),
|
|
523
548
|
Base64.DEFAULT
|
|
524
549
|
);
|
|
525
|
-
|
|
550
|
+
|
|
551
|
+
PublicKey pKey = CryptoCipher.stringToPublicKey(this.publicKey);
|
|
526
552
|
byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
|
|
553
|
+
|
|
527
554
|
SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
|
|
528
555
|
byte[] content = new byte[(int) file.length()];
|
|
529
556
|
|
|
@@ -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
|
|
54
|
-
"-----BEGIN RSA
|
|
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 = "
|
|
58
|
+
private final String PLUGIN_VERSION = "6.0.0-alpha.0";
|
|
59
59
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
60
60
|
|
|
61
61
|
private SharedPreferences.Editor editor;
|
|
@@ -136,6 +136,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
136
136
|
this.implementation.activity = this.getActivity();
|
|
137
137
|
this.implementation.versionBuild =
|
|
138
138
|
this.getConfig().getString("version", pInfo.versionName);
|
|
139
|
+
this.implementation.forceEncryption =
|
|
140
|
+
this.getConfig().getBoolean("forceEncryption", true);
|
|
139
141
|
this.implementation.PLUGIN_VERSION = this.PLUGIN_VERSION;
|
|
140
142
|
this.implementation.versionCode = Integer.toString(pInfo.versionCode);
|
|
141
143
|
this.implementation.requestQueue =
|
|
@@ -174,8 +176,15 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
174
176
|
);
|
|
175
177
|
}
|
|
176
178
|
Log.i(CapacitorUpdater.TAG, "appId: " + implementation.appId);
|
|
177
|
-
this.implementation.
|
|
178
|
-
|
|
179
|
+
this.implementation.publicKey =
|
|
180
|
+
getConfig().getString("publicKey", defaultPublicKey);
|
|
181
|
+
this.implementation.hasOldPrivateKeyPropertyInConfig = false;
|
|
182
|
+
if (
|
|
183
|
+
this.getConfig().getString("privateKey") != null &&
|
|
184
|
+
!this.getConfig().getString("privateKey").isEmpty()
|
|
185
|
+
) {
|
|
186
|
+
this.implementation.hasOldPrivateKeyPropertyInConfig = true;
|
|
187
|
+
}
|
|
179
188
|
this.implementation.statsUrl =
|
|
180
189
|
this.getConfig().getString("statsUrl", statsUrlDefault);
|
|
181
190
|
this.implementation.channelUrl =
|
|
@@ -835,6 +844,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
835
844
|
@Override
|
|
836
845
|
public void run() {
|
|
837
846
|
try {
|
|
847
|
+
Log.i(CapacitorUpdater.TAG, "Period Check");
|
|
838
848
|
CapacitorUpdaterPlugin.this.implementation.getLatest(
|
|
839
849
|
CapacitorUpdaterPlugin.this.updateUrl,
|
|
840
850
|
res -> {
|
|
@@ -1115,7 +1125,7 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
1115
1125
|
if (res.has("message")) {
|
|
1116
1126
|
Log.i(
|
|
1117
1127
|
CapacitorUpdater.TAG,
|
|
1118
|
-
"API message
|
|
1128
|
+
"API message " + res.get("message")
|
|
1119
1129
|
);
|
|
1120
1130
|
if (
|
|
1121
1131
|
res.has("major") &&
|
|
@@ -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.
|
|
20
|
+
import java.security.PublicKey;
|
|
21
21
|
import java.security.spec.InvalidKeySpecException;
|
|
22
|
-
import java.security.spec.
|
|
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,
|
|
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/
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
|
60
|
+
private static PublicKey readX509PublicKey(byte[] x509Bytes)
|
|
68
61
|
throws GeneralSecurityException {
|
|
69
62
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
70
|
-
|
|
63
|
+
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(x509Bytes);
|
|
71
64
|
try {
|
|
72
|
-
return keyFactory.
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
(
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
139
|
-
|
|
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
|
@@ -1863,14 +1863,14 @@
|
|
|
1863
1863
|
"type": "string | undefined"
|
|
1864
1864
|
},
|
|
1865
1865
|
{
|
|
1866
|
-
"name": "
|
|
1866
|
+
"name": "publicKey",
|
|
1867
1867
|
"tags": [
|
|
1868
1868
|
{
|
|
1869
1869
|
"text": "undefined",
|
|
1870
1870
|
"name": "default"
|
|
1871
1871
|
}
|
|
1872
1872
|
],
|
|
1873
|
-
"docs": "Configure the
|
|
1873
|
+
"docs": "Configure the public key for end to end live update encryption.\n\nOnly available for Android and iOS.",
|
|
1874
1874
|
"complexTypes": [],
|
|
1875
1875
|
"type": "string | undefined"
|
|
1876
1876
|
},
|
|
@@ -2029,6 +2029,22 @@
|
|
|
2029
2029
|
"docs": "Set the default channel for the app in the config.",
|
|
2030
2030
|
"complexTypes": [],
|
|
2031
2031
|
"type": "string | undefined"
|
|
2032
|
+
},
|
|
2033
|
+
{
|
|
2034
|
+
"name": "forceEncryption",
|
|
2035
|
+
"tags": [
|
|
2036
|
+
{
|
|
2037
|
+
"text": "true",
|
|
2038
|
+
"name": "default"
|
|
2039
|
+
},
|
|
2040
|
+
{
|
|
2041
|
+
"text": "6.0.0",
|
|
2042
|
+
"name": "since"
|
|
2043
|
+
}
|
|
2044
|
+
],
|
|
2045
|
+
"docs": "If set to true with encryption enabled, the plugin will only accept encrypted bundles.",
|
|
2046
|
+
"complexTypes": [],
|
|
2047
|
+
"type": "boolean | undefined"
|
|
2032
2048
|
}
|
|
2033
2049
|
],
|
|
2034
2050
|
"docs": "CapacitorUpdater can be configured with these options:"
|
|
@@ -78,13 +78,13 @@ declare module "@capacitor/cli" {
|
|
|
78
78
|
*/
|
|
79
79
|
statsUrl?: string;
|
|
80
80
|
/**
|
|
81
|
-
* Configure the
|
|
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
|
-
|
|
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.
|
|
@@ -170,6 +170,15 @@ declare module "@capacitor/cli" {
|
|
|
170
170
|
* @since 5.5.0
|
|
171
171
|
*/
|
|
172
172
|
defaultChannel?: string;
|
|
173
|
+
/**
|
|
174
|
+
* If set to true with encryption enabled, the plugin will only accept encrypted bundles.
|
|
175
|
+
*
|
|
176
|
+
*
|
|
177
|
+
*
|
|
178
|
+
* @default true
|
|
179
|
+
* @since 6.0.0
|
|
180
|
+
*/
|
|
181
|
+
forceEncryption?: boolean;
|
|
173
182
|
};
|
|
174
183
|
}
|
|
175
184
|
}
|
|
@@ -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/auto_update\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 determine when to install updates.\n *\n * @example\n * // Install 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 * // Install 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 * // Install 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> & 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> & 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> & 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> & 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> & 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> & 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> & 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> & 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> & 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 /**\n * The version code/name of this bundle/version\n */\n version: string;\n sessionKey?: string;\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/auto_update\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 * If set to true with encryption enabled, the plugin will only accept encrypted bundles.\n *\n *\n *\n * @default true\n * @since 6.0.0\n */\n forceEncryption?: boolean;\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 determine when to install updates.\n *\n * @example\n * // Install 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 * // Install 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 * // Install 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> & 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> & 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> & 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> & 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> & 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> & 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> & 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> & 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> & 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 /**\n * The version code/name of this bundle/version\n */\n version: string;\n sessionKey?: string;\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"]}
|
|
@@ -168,6 +168,7 @@ enum CustomError: Error {
|
|
|
168
168
|
case cannotUnflat
|
|
169
169
|
case cannotCreateDirectory
|
|
170
170
|
case cannotDeleteDirectory
|
|
171
|
+
case cannotDecryptSessionKey
|
|
171
172
|
|
|
172
173
|
// Throw in all other cases
|
|
173
174
|
case unexpected(code: Int)
|
|
@@ -204,13 +205,18 @@ extension CustomError: LocalizedError {
|
|
|
204
205
|
case .cannotDecode:
|
|
205
206
|
return NSLocalizedString(
|
|
206
207
|
"Decoding the zip failed with this key",
|
|
207
|
-
comment: "Invalid
|
|
208
|
+
comment: "Invalid public key"
|
|
208
209
|
)
|
|
209
210
|
case .cannotWrite:
|
|
210
211
|
return NSLocalizedString(
|
|
211
212
|
"Cannot write to the destination",
|
|
212
213
|
comment: "Invalid destination"
|
|
213
214
|
)
|
|
215
|
+
case .cannotDecryptSessionKey:
|
|
216
|
+
return NSLocalizedString(
|
|
217
|
+
"Decrypting the session key failed",
|
|
218
|
+
comment: "Invalid session key"
|
|
219
|
+
)
|
|
214
220
|
}
|
|
215
221
|
}
|
|
216
222
|
}
|
|
@@ -240,7 +246,9 @@ extension CustomError: LocalizedError {
|
|
|
240
246
|
public var defaultChannel: String = ""
|
|
241
247
|
public var appId: String = ""
|
|
242
248
|
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
243
|
-
public var
|
|
249
|
+
public var publicKey: String? = ""
|
|
250
|
+
public var hasOldPrivateKeyPropertyInConfig: Bool = false
|
|
251
|
+
public var forceEncryption: Bool = false
|
|
244
252
|
|
|
245
253
|
public var notifyDownload: (String, Int) -> Void = { _, _ in }
|
|
246
254
|
|
|
@@ -366,13 +374,19 @@ extension CustomError: LocalizedError {
|
|
|
366
374
|
}
|
|
367
375
|
|
|
368
376
|
private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
|
|
369
|
-
if
|
|
370
|
-
|
|
377
|
+
if sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
378
|
+
if (self.forceEncryption && self.publicKey != nil && !self.publicKey!.isEmpty) {
|
|
379
|
+
print("\(self.TAG) Cannot accept non-encrypted bundle")
|
|
380
|
+
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
381
|
+
throw CustomError.cannotDecode
|
|
382
|
+
}
|
|
383
|
+
print("\(self.TAG) Cannot find public key or sessionKey")
|
|
371
384
|
return
|
|
372
385
|
}
|
|
386
|
+
|
|
373
387
|
do {
|
|
374
|
-
guard let
|
|
375
|
-
print("cannot decode
|
|
388
|
+
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey!) else {
|
|
389
|
+
print("cannot decode publicKey", self.publicKey!)
|
|
376
390
|
throw CustomError.cannotDecode
|
|
377
391
|
}
|
|
378
392
|
|
|
@@ -382,21 +396,25 @@ extension CustomError: LocalizedError {
|
|
|
382
396
|
throw CustomError.cannotDecode
|
|
383
397
|
}
|
|
384
398
|
|
|
399
|
+
// guard let base64EncodedData = sessionKeyArray[1].data(using: .utf8)! else {
|
|
400
|
+
// throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
401
|
+
// }
|
|
402
|
+
|
|
385
403
|
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
386
404
|
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
387
405
|
}
|
|
388
406
|
|
|
389
|
-
guard let sessionKeyDataDecrypted =
|
|
407
|
+
guard let sessionKeyDataDecrypted = try? rsaPublicKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
390
408
|
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
391
409
|
}
|
|
392
410
|
|
|
393
|
-
let
|
|
411
|
+
let aesPublicKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
394
412
|
|
|
395
413
|
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
396
414
|
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
397
415
|
}
|
|
398
416
|
|
|
399
|
-
guard let decryptedData =
|
|
417
|
+
guard let decryptedData = try? aesPublicKey.decrypt(data: encryptedData) else {
|
|
400
418
|
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
401
419
|
}
|
|
402
420
|
|
|
@@ -569,7 +587,12 @@ extension CustomError: LocalizedError {
|
|
|
569
587
|
case .success:
|
|
570
588
|
self.notifyDownload(id, 71)
|
|
571
589
|
do {
|
|
590
|
+
if self.hasOldPrivateKeyPropertyInConfig {
|
|
591
|
+
print("\(self.TAG) There is still an privateKey property in the config")
|
|
592
|
+
}
|
|
593
|
+
|
|
572
594
|
try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
|
|
595
|
+
|
|
573
596
|
checksum = self.getChecksum(filePath: fileURL)
|
|
574
597
|
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.documentsDir.appendingPathComponent(self.bundleDirectoryHot))
|
|
575
598
|
self.notifyDownload(id, 85)
|
|
@@ -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 = "
|
|
18
|
+
private let PLUGIN_VERSION: String = "6.0.0-alpha.0"
|
|
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
|
|
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
|
|
@@ -52,6 +52,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
52
52
|
}
|
|
53
53
|
print("\(self.implementation.TAG) version native \(self.currentVersionNative.description)")
|
|
54
54
|
implementation.versionBuild = getConfig().getString("version", Bundle.main.versionName)!
|
|
55
|
+
implementation.forceEncryption = getConfig().getBoolean("forceEncryption", true)
|
|
55
56
|
autoDeleteFailed = getConfig().getBoolean("autoDeleteFailed", true)
|
|
56
57
|
autoDeletePrevious = getConfig().getBoolean("autoDeletePrevious", true)
|
|
57
58
|
directUpdate = getConfig().getBoolean("directUpdate", false)
|
|
@@ -67,7 +68,11 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
67
68
|
periodCheckDelay = periodCheckDelayValue
|
|
68
69
|
}
|
|
69
70
|
|
|
70
|
-
implementation.
|
|
71
|
+
implementation.publicKey = getConfig().getString("publicKey", self.defaultPublicKey)!
|
|
72
|
+
implementation.hasOldPrivateKeyPropertyInConfig = false
|
|
73
|
+
if getConfig().getString("privateKey") != nil && !getConfig().getString("privateKey")!.isEmpty {
|
|
74
|
+
implementation.hasOldPrivateKeyPropertyInConfig = true
|
|
75
|
+
}
|
|
71
76
|
implementation.notifyDownload = notifyDownload
|
|
72
77
|
implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
|
|
73
78
|
let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
|
|
@@ -643,7 +648,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
643
648
|
let current = self.implementation.getCurrentBundle()
|
|
644
649
|
|
|
645
650
|
if (res.message) != nil {
|
|
646
|
-
print("\(self.implementation.TAG) API
|
|
651
|
+
print("\(self.implementation.TAG) API response: \(res.message ?? "")")
|
|
647
652
|
if res.major == true {
|
|
648
653
|
self.notifyListeners("majorAvailable", data: ["version": res.version])
|
|
649
654
|
}
|
|
@@ -792,6 +797,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
792
797
|
return
|
|
793
798
|
}
|
|
794
799
|
let timer = Timer.scheduledTimer(withTimeInterval: TimeInterval(periodCheckDelay), repeats: true) { _ in
|
|
800
|
+
print("\(self.implementation.TAG) Period Check")
|
|
795
801
|
DispatchQueue.global(qos: .background).async {
|
|
796
802
|
let res = self.implementation.getLatest(url: url)
|
|
797
803
|
let current = self.implementation.getCurrentBundle()
|
|
@@ -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 = .
|
|
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
|
|
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
|
|
70
|
+
/// The RSA public key.
|
|
71
71
|
///
|
|
72
|
-
public struct
|
|
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(
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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(
|
|
149
|
-
|
|
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
|
-
}
|
|
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
|
-
#
|
|
163
|
-
public
|
|
164
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
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
|
|
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
|
}
|