@capgo/capacitor-updater 8.0.0 → 8.0.1

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.
Files changed (40) hide show
  1. package/CapgoCapacitorUpdater.podspec +2 -2
  2. package/Package.swift +35 -0
  3. package/README.md +667 -206
  4. package/android/build.gradle +16 -11
  5. package/android/proguard-rules.pro +28 -0
  6. package/android/src/main/AndroidManifest.xml +0 -1
  7. package/android/src/main/java/ee/forgr/capacitor_updater/BundleInfo.java +134 -194
  8. package/android/src/main/java/ee/forgr/capacitor_updater/BundleStatus.java +23 -23
  9. package/android/src/main/java/ee/forgr/capacitor_updater/Callback.java +13 -0
  10. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +967 -1027
  11. package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +1283 -1180
  12. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV2.java +276 -0
  13. package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +28 -0
  14. package/android/src/main/java/ee/forgr/capacitor_updater/DelayCondition.java +45 -48
  15. package/android/src/main/java/ee/forgr/capacitor_updater/DelayUntilNext.java +4 -4
  16. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +440 -113
  17. package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +101 -0
  18. package/android/src/main/java/ee/forgr/capacitor_updater/InternalUtils.java +32 -0
  19. package/dist/docs.json +1316 -473
  20. package/dist/esm/definitions.d.ts +518 -248
  21. package/dist/esm/definitions.js.map +1 -1
  22. package/dist/esm/index.d.ts +2 -2
  23. package/dist/esm/index.js +4 -4
  24. package/dist/esm/index.js.map +1 -1
  25. package/dist/esm/web.d.ts +25 -41
  26. package/dist/esm/web.js +67 -35
  27. package/dist/esm/web.js.map +1 -1
  28. package/dist/plugin.cjs.js +67 -35
  29. package/dist/plugin.cjs.js.map +1 -1
  30. package/dist/plugin.js +67 -35
  31. package/dist/plugin.js.map +1 -1
  32. package/ios/Plugin/CapacitorUpdater.swift +736 -361
  33. package/ios/Plugin/CapacitorUpdaterPlugin.swift +436 -136
  34. package/ios/Plugin/CryptoCipherV2.swift +310 -0
  35. package/ios/Plugin/InternalUtils.swift +258 -0
  36. package/package.json +33 -29
  37. package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +0 -153
  38. package/ios/Plugin/CapacitorUpdaterPlugin.h +0 -10
  39. package/ios/Plugin/CapacitorUpdaterPlugin.m +0 -27
  40. package/ios/Plugin/CryptoCipher.swift +0 -240
@@ -0,0 +1,276 @@
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ package ee.forgr.capacitor_updater;
8
+
9
+ /**
10
+ * Created by Awesometic
11
+ * It's encrypt returns Base64 encoded, and also decrypt for Base64 encoded cipher
12
+ * references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
13
+ */
14
+ import android.util.Base64;
15
+ import android.util.Log;
16
+ import java.io.BufferedInputStream;
17
+ import java.io.DataInputStream;
18
+ import java.io.File;
19
+ import java.io.FileInputStream;
20
+ import java.io.FileOutputStream;
21
+ import java.io.IOException;
22
+ import java.security.GeneralSecurityException;
23
+ import java.security.InvalidAlgorithmParameterException;
24
+ import java.security.InvalidKeyException;
25
+ import java.security.KeyFactory;
26
+ import java.security.MessageDigest;
27
+ import java.security.NoSuchAlgorithmException;
28
+ import java.security.PublicKey;
29
+ import java.security.spec.InvalidKeySpecException;
30
+ import java.security.spec.X509EncodedKeySpec;
31
+ import javax.crypto.BadPaddingException;
32
+ import javax.crypto.Cipher;
33
+ import javax.crypto.IllegalBlockSizeException;
34
+ import javax.crypto.NoSuchPaddingException;
35
+ import javax.crypto.SecretKey;
36
+ import javax.crypto.spec.IvParameterSpec;
37
+ import javax.crypto.spec.SecretKeySpec;
38
+
39
+ public class CryptoCipherV2 {
40
+
41
+ public static byte[] decryptRSA(byte[] source, PublicKey publicKey)
42
+ throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
43
+ Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
44
+ cipher.init(Cipher.DECRYPT_MODE, publicKey);
45
+ byte[] decryptedBytes = cipher.doFinal(source);
46
+ return decryptedBytes;
47
+ }
48
+
49
+ public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
50
+ try {
51
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
52
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
53
+ SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
54
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
55
+ return cipher.doFinal(cipherText);
56
+ } catch (Exception e) {
57
+ e.printStackTrace();
58
+ }
59
+ return null;
60
+ }
61
+
62
+ public static SecretKey byteToSessionKey(byte[] sessionKey) {
63
+ // rebuild key using SecretKeySpec
64
+ return new SecretKeySpec(sessionKey, 0, sessionKey.length, "AES");
65
+ }
66
+
67
+ private static PublicKey readX509PublicKey(byte[] x509Bytes) throws GeneralSecurityException {
68
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
69
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(x509Bytes);
70
+ try {
71
+ return keyFactory.generatePublic(keySpec);
72
+ } catch (InvalidKeySpecException e) {
73
+ throw new IllegalArgumentException("Unexpected key format!", e);
74
+ }
75
+ }
76
+
77
+ public static PublicKey stringToPublicKey(String public_key) throws GeneralSecurityException {
78
+ String pkcs1Pem = public_key
79
+ .replaceAll("\\s+", "")
80
+ .replace("-----BEGINRSAPUBLICKEY-----", "")
81
+ .replace("-----ENDRSAPUBLICKEY-----", "");
82
+
83
+ byte[] pkcs1EncodedBytes = Base64.decode(pkcs1Pem, Base64.DEFAULT);
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(BIT_STRING_TAG, joinPublic(NO_UNUSED_BITS, pkcs1Bytes));
115
+ byte[] keyInfoValue = joinPublic(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, keyBitString);
116
+ byte[] keyInfoSequence = createDEREncoding(SEQUENCE_TAG, keyInfoValue);
117
+ return readX509PublicKey(keyInfoSequence);
118
+ }
119
+
120
+ private static byte[] joinPublic(byte[]... bas) {
121
+ int len = 0;
122
+ for (int i = 0; i < bas.length; i++) {
123
+ len += bas[i].length;
124
+ }
125
+
126
+ byte[] buf = new byte[len];
127
+ int off = 0;
128
+ for (int i = 0; i < bas.length; i++) {
129
+ System.arraycopy(bas[i], 0, buf, off, bas[i].length);
130
+ off += bas[i].length;
131
+ }
132
+
133
+ return buf;
134
+ }
135
+
136
+ public static void decryptFile(final File file, final String publicKey, final String ivSessionKey) throws IOException {
137
+ if (publicKey.isEmpty() || ivSessionKey == null || ivSessionKey.isEmpty() || ivSessionKey.split(":").length != 2) {
138
+ Log.i(CapacitorUpdater.TAG, "Cannot found public key or sessionKey");
139
+ return;
140
+ }
141
+ if (!publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
142
+ Log.e(CapacitorUpdater.TAG, "The public key is not a valid RSA Public key");
143
+ return;
144
+ }
145
+
146
+ try {
147
+ String ivB64 = ivSessionKey.split(":")[0];
148
+ String sessionKeyB64 = ivSessionKey.split(":")[1];
149
+ byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
150
+ byte[] sessionKey = Base64.decode(sessionKeyB64.getBytes(), Base64.DEFAULT);
151
+ PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
152
+ byte[] decryptedSessionKey = CryptoCipherV2.decryptRSA(sessionKey, pKey);
153
+
154
+ SecretKey sKey = CryptoCipherV2.byteToSessionKey(decryptedSessionKey);
155
+ byte[] content = new byte[(int) file.length()];
156
+
157
+ try (
158
+ final FileInputStream fis = new FileInputStream(file);
159
+ final BufferedInputStream bis = new BufferedInputStream(fis);
160
+ final DataInputStream dis = new DataInputStream(bis)
161
+ ) {
162
+ dis.readFully(content);
163
+ dis.close();
164
+ byte[] decrypted = CryptoCipherV2.decryptAES(content, sKey, iv);
165
+ // write the decrypted string to the file
166
+ try (final FileOutputStream fos = new FileOutputStream(file.getAbsolutePath())) {
167
+ fos.write(decrypted);
168
+ }
169
+ }
170
+ } catch (GeneralSecurityException e) {
171
+ Log.i(CapacitorUpdater.TAG, "decryptFile fail");
172
+ e.printStackTrace();
173
+ throw new IOException("GeneralSecurityException");
174
+ }
175
+ }
176
+
177
+ public static String decryptChecksum(String checksum, String publicKey) throws IOException {
178
+ if (publicKey.isEmpty()) {
179
+ Log.e(CapacitorUpdater.TAG, "The public key is empty");
180
+ return checksum;
181
+ }
182
+ try {
183
+ byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
184
+ PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
185
+ byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
186
+ // return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
187
+ String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
188
+ return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
189
+ } catch (GeneralSecurityException e) {
190
+ Log.e(CapacitorUpdater.TAG, "decryptChecksum fail: " + e.getMessage());
191
+ throw new IOException("Decryption failed: " + e.getMessage());
192
+ }
193
+ }
194
+
195
+ public static String decryptChecksum(String checksum, String publicKey, String version) throws IOException {
196
+ if (publicKey.isEmpty()) {
197
+ Log.e(CapacitorUpdater.TAG, "The public key is empty");
198
+ return checksum;
199
+ }
200
+ try {
201
+ byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
202
+ PublicKey pKey = CryptoCipherV2.stringToPublicKey(publicKey);
203
+ byte[] decryptedChecksum = CryptoCipherV2.decryptRSA(checksumBytes, pKey);
204
+ // return Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
205
+ String result = Base64.encodeToString(decryptedChecksum, Base64.DEFAULT);
206
+ return result.replaceAll("\\s", ""); // Remove all whitespace, including newlines
207
+ } catch (GeneralSecurityException e) {
208
+ Log.e(CapacitorUpdater.TAG, "decryptChecksum fail: " + e.getMessage());
209
+ throw new IOException("Decryption failed: " + e.getMessage());
210
+ }
211
+ }
212
+
213
+ public static String calcChecksum(File file) {
214
+ final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
215
+ MessageDigest digest;
216
+ try {
217
+ digest = MessageDigest.getInstance("SHA-256");
218
+ } catch (java.security.NoSuchAlgorithmException e) {
219
+ System.err.println(CapacitorUpdater.TAG + " SHA-256 algorithm not available");
220
+ return "";
221
+ }
222
+
223
+ try (FileInputStream fis = new FileInputStream(file)) {
224
+ byte[] buffer = new byte[BUFFER_SIZE];
225
+ int length;
226
+ while ((length = fis.read(buffer)) != -1) {
227
+ digest.update(buffer, 0, length);
228
+ }
229
+ byte[] hash = digest.digest();
230
+ StringBuilder hexString = new StringBuilder();
231
+ for (byte b : hash) {
232
+ String hex = Integer.toHexString(0xff & b);
233
+ if (hex.length() == 1) hexString.append('0');
234
+ hexString.append(hex);
235
+ }
236
+ return hexString.toString();
237
+ } catch (IOException e) {
238
+ System.err.println(CapacitorUpdater.TAG + " Cannot calc checksum v2: " + file.getPath() + " " + e.getMessage());
239
+ return "";
240
+ }
241
+ }
242
+
243
+ private static byte[] createDEREncoding(int tag, byte[] value) {
244
+ if (tag < 0 || tag >= 0xFF) {
245
+ throw new IllegalArgumentException("Currently only single byte tags supported");
246
+ }
247
+
248
+ byte[] lengthEncoding = createDERLengthEncoding(value.length);
249
+
250
+ int size = 1 + lengthEncoding.length + value.length;
251
+ byte[] derEncodingBuf = new byte[size];
252
+
253
+ int off = 0;
254
+ derEncodingBuf[off++] = (byte) tag;
255
+ System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
256
+ off += lengthEncoding.length;
257
+ System.arraycopy(value, 0, derEncodingBuf, off, value.length);
258
+
259
+ return derEncodingBuf;
260
+ }
261
+
262
+ private static byte[] createDERLengthEncoding(int size) {
263
+ if (size <= 0x7F) {
264
+ // single byte length encoding
265
+ return new byte[] { (byte) size };
266
+ } else if (size <= 0xFF) {
267
+ // double byte length encoding
268
+ return new byte[] { (byte) 0x81, (byte) size };
269
+ } else if (size <= 0xFFFF) {
270
+ // triple byte length encoding
271
+ return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
272
+ }
273
+
274
+ throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
275
+ }
276
+ }
@@ -0,0 +1,28 @@
1
+ package ee.forgr.capacitor_updater;
2
+
3
+ import org.json.JSONArray;
4
+
5
+ public class DataManager {
6
+
7
+ private static DataManager instance;
8
+ private JSONArray currentManifest;
9
+
10
+ private DataManager() {}
11
+
12
+ public static synchronized DataManager getInstance() {
13
+ if (instance == null) {
14
+ instance = new DataManager();
15
+ }
16
+ return instance;
17
+ }
18
+
19
+ public void setManifest(JSONArray manifest) {
20
+ this.currentManifest = manifest;
21
+ }
22
+
23
+ public JSONArray getAndClearManifest() {
24
+ JSONArray manifest = this.currentManifest;
25
+ this.currentManifest = null;
26
+ return manifest;
27
+ }
28
+ }
@@ -6,57 +6,54 @@
6
6
 
7
7
  package ee.forgr.capacitor_updater;
8
8
 
9
+ import androidx.annotation.NonNull;
9
10
  import com.google.gson.annotations.SerializedName;
10
11
  import java.util.Objects;
11
12
 
12
13
  public class DelayCondition {
13
14
 
14
- @SerializedName("kind")
15
- private DelayUntilNext kind;
16
-
17
- @SerializedName("value")
18
- private String value;
19
-
20
- public DelayCondition(DelayUntilNext kind, String value) {
21
- this.kind = kind;
22
- this.value = value;
23
- }
24
-
25
- public DelayUntilNext getKind() {
26
- return kind;
27
- }
28
-
29
- public void setKind(DelayUntilNext kind) {
30
- this.kind = kind;
31
- }
32
-
33
- public String getValue() {
34
- return value;
35
- }
36
-
37
- public void setValue(String value) {
38
- this.value = value;
39
- }
40
-
41
- @Override
42
- public boolean equals(Object o) {
43
- if (this == o) return true;
44
- if (!(o instanceof DelayCondition)) return false;
45
- DelayCondition that = (DelayCondition) o;
46
- return (
47
- getKind() == that.getKind() && Objects.equals(getValue(), that.getValue())
48
- );
49
- }
50
-
51
- @Override
52
- public int hashCode() {
53
- return Objects.hash(getKind(), getValue());
54
- }
55
-
56
- @Override
57
- public String toString() {
58
- return (
59
- "DelayCondition{" + "kind=" + kind + ", value='" + value + '\'' + '}'
60
- );
61
- }
15
+ @SerializedName("kind")
16
+ private DelayUntilNext kind;
17
+
18
+ @SerializedName("value")
19
+ private String value;
20
+
21
+ public DelayCondition(DelayUntilNext kind, String value) {
22
+ this.kind = kind;
23
+ this.value = value;
24
+ }
25
+
26
+ public DelayUntilNext getKind() {
27
+ return kind;
28
+ }
29
+
30
+ public void setKind(DelayUntilNext kind) {
31
+ this.kind = kind;
32
+ }
33
+
34
+ public String getValue() {
35
+ return value;
36
+ }
37
+
38
+ public void setValue(String value) {
39
+ this.value = value;
40
+ }
41
+
42
+ @Override
43
+ public boolean equals(Object o) {
44
+ if (this == o) return true;
45
+ if (!(o instanceof DelayCondition that)) return false;
46
+ return (getKind() == that.getKind() && Objects.equals(getValue(), that.getValue()));
47
+ }
48
+
49
+ @Override
50
+ public int hashCode() {
51
+ return Objects.hash(getKind(), getValue());
52
+ }
53
+
54
+ @NonNull
55
+ @Override
56
+ public String toString() {
57
+ return ("DelayCondition{" + "kind=" + kind + ", value='" + value + '\'' + '}');
58
+ }
62
59
  }
@@ -7,8 +7,8 @@
7
7
  package ee.forgr.capacitor_updater;
8
8
 
9
9
  public enum DelayUntilNext {
10
- background,
11
- kill,
12
- nativeVersion,
13
- date,
10
+ background,
11
+ kill,
12
+ nativeVersion,
13
+ date
14
14
  }