@capgo/cordova-updater 8.1.4 → 8.2.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 (36) hide show
  1. package/dist/esm/definitions.d.ts +116 -86
  2. package/dist/esm/definitions.js +2 -2
  3. package/dist/esm/definitions.js.map +1 -1
  4. package/dist/esm/exec.js +11 -1
  5. package/dist/esm/exec.js.map +1 -1
  6. package/dist/esm/history.js +7 -7
  7. package/dist/esm/history.js.map +1 -1
  8. package/dist/plugin.cjs.js +13 -3
  9. package/dist/plugin.cjs.js.map +1 -1
  10. package/dist/plugin.js +13 -3
  11. package/dist/plugin.js.map +1 -1
  12. package/package.json +5 -3
  13. package/plugin.xml +9 -9
  14. package/src/android/CordovaUpdaterPlugin.java +173 -5
  15. package/src/android/app/capgo/cordova/updater/CapgoUpdater.java +313 -55
  16. package/src/android/app/capgo/cordova/updater/CryptoCipher.java +51 -18
  17. package/src/android/app/capgo/cordova/updater/DelayUpdateUtils.java +0 -1
  18. package/src/android/app/capgo/cordova/updater/DownloadService.java +402 -89
  19. package/src/android/app/capgo/cordova/updater/Version.java +312 -0
  20. package/src/android/capgo-cordova-updater.gradle +3 -5
  21. package/src/android/test/app/capgo/cordova/updater/BackgroundDownloadSettlementTest.java +29 -0
  22. package/src/android/test/app/capgo/cordova/updater/CapacitorUpdaterUnitTest.java.skip +2 -12
  23. package/src/android/test/app/capgo/cordova/updater/DelayUpdateUtilsTest.java +0 -1
  24. package/src/android/test/app/capgo/cordova/updater/RsaContractTest.java +137 -0
  25. package/src/android/test/app/capgo/cordova/updater/SecurityHardeningTest.java +197 -0
  26. package/src/android/test/app/capgo/cordova/updater/SessionKeyRequiredTest.java +344 -0
  27. package/src/ios/CapgoSemanticVersion.swift +181 -0
  28. package/src/ios/CapgoUpdater.swift +427 -114
  29. package/src/ios/CordovaUpdaterPlugin.swift +171 -40
  30. package/src/ios/CryptoCipher.swift +41 -38
  31. package/src/ios/DelayUpdateUtils.swift +19 -45
  32. package/src/ios/Logger.swift +81 -5
  33. package/src/ios/RedirectPolicyDelegate.swift +53 -0
  34. package/src/ios/WebViewStatsReporter.swift +5 -0
  35. package/src/ios/ZipArchiveReader.swift +263 -0
  36. package/src/ios/ZipCentralDirectory.swift +283 -0
@@ -50,17 +50,15 @@ public class CryptoCipher {
50
50
  return decryptedBytes;
51
51
  }
52
52
 
53
- public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
54
- try {
55
- IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
56
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
57
- SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
58
- cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
59
- return cipher.doFinal(cipherText);
60
- } catch (Exception e) {
61
- e.printStackTrace();
53
+ public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) throws GeneralSecurityException {
54
+ if (key == null || iv == null) {
55
+ throw new IllegalArgumentException("AES key and IV must not be null");
62
56
  }
63
- return null;
57
+ IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
58
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
59
+ SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
60
+ cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
61
+ return cipher.doFinal(cipherText);
64
62
  }
65
63
 
66
64
  public static SecretKey byteToSessionKey(byte[] sessionKey) {
@@ -137,13 +135,25 @@ public class CryptoCipher {
137
135
  return buf;
138
136
  }
139
137
 
138
+ public static boolean isValidSessionKey(final String sessionKey) {
139
+ if (sessionKey == null || sessionKey.isEmpty()) {
140
+ return false;
141
+ }
142
+ String[] sessionKeyParts = sessionKey.split(":", -1);
143
+ return sessionKeyParts.length == 2 && !sessionKeyParts[0].isEmpty() && !sessionKeyParts[1].isEmpty();
144
+ }
145
+
140
146
  public static void decryptFile(final File file, final String publicKey, final String ivSessionKey) throws IOException {
141
- if (publicKey.isEmpty() || ivSessionKey == null || ivSessionKey.isEmpty() || ivSessionKey.split(":").length != 2) {
142
- logger.info("Encryption not set, no public key or session, ignored");
147
+ if (publicKey.isEmpty() || !isValidSessionKey(ivSessionKey)) {
148
+ if (logger != null) {
149
+ logger.info("Encryption not set, no public key or session, ignored");
150
+ }
143
151
  return;
144
152
  }
145
153
  if (!publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
146
- logger.error("The public key is not a valid RSA Public key");
154
+ if (logger != null) {
155
+ logger.error("The public key is not a valid RSA Public key");
156
+ }
147
157
  return;
148
158
  }
149
159
 
@@ -152,29 +162,50 @@ public class CryptoCipher {
152
162
  String sessionKeyB64 = ivSessionKey.split(":")[1];
153
163
  byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
154
164
  byte[] sessionKey = Base64.decode(sessionKeyB64.getBytes(), Base64.DEFAULT);
165
+ if (iv.length != 16) {
166
+ throw new IOException("AES file decryption failed: IV must be 16 bytes");
167
+ }
155
168
  PublicKey pKey = CryptoCipher.stringToPublicKey(publicKey);
156
169
  byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
170
+ if (decryptedSessionKey == null || decryptedSessionKey.length != 16) {
171
+ throw new IOException("AES file decryption failed: decrypted session key must be 16 bytes");
172
+ }
157
173
 
158
174
  SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
159
175
  decryptAesFile(file, sKey, iv);
160
176
  } catch (GeneralSecurityException e) {
161
- logger.info("decryptFile fail");
162
- throw new IOException("GeneralSecurityException", e);
177
+ if (logger != null) {
178
+ logger.info("decryptFile fail");
179
+ }
180
+ throw new IOException("AES file decryption failed: " + e.getMessage(), e);
163
181
  }
164
182
  }
165
183
 
166
- static void decryptAesFile(File file, SecretKey key, byte[] iv) throws IOException, GeneralSecurityException {
184
+ static void decryptAesFile(File file, SecretKey key, byte[] iv) throws IOException {
185
+ if (key == null) {
186
+ throw new IOException("AES file decryption failed: missing session key");
187
+ }
188
+ if (iv == null) {
189
+ throw new IOException("AES file decryption failed: missing IV");
190
+ }
191
+ if (iv.length != 16) {
192
+ throw new IOException("AES file decryption failed: IV must be 16 bytes");
193
+ }
194
+ byte[] keyBytes = key.getEncoded();
195
+ if (keyBytes == null || keyBytes.length != 16) {
196
+ throw new IOException("AES file decryption failed: session key must be 16 bytes");
197
+ }
167
198
  if (file.length() == 0) {
168
199
  throw new IOException("Empty encrypted data");
169
200
  }
170
- Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
171
- cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getEncoded(), "AES"), new IvParameterSpec(iv));
172
201
  File parent = file.getAbsoluteFile().getParentFile();
173
202
  if (parent == null) {
174
203
  throw new IOException("Cannot create temp file for " + file.getAbsolutePath());
175
204
  }
176
205
  File tempFile = File.createTempFile("capgo-aes-", ".tmp", parent);
177
206
  try {
207
+ Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
208
+ cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(iv));
178
209
  byte[] inBuf = new byte[ioBufferBytes()];
179
210
  // Reuse one output buffer. cipher.update(in) allocates a new byte[] per chunk.
180
211
  byte[] outBuf = new byte[inBuf.length + 16];
@@ -196,6 +227,8 @@ public class CryptoCipher {
196
227
  }
197
228
  replaceFile(tempFile, file);
198
229
  tempFile = null;
230
+ } catch (GeneralSecurityException | IllegalArgumentException e) {
231
+ throw new IOException("AES file decryption failed: " + e.getMessage(), e);
199
232
  } finally {
200
233
  if (tempFile != null && tempFile.exists()) {
201
234
  tempFile.delete();
@@ -1,7 +1,6 @@
1
1
  package app.capgo.cordova.updater;
2
2
 
3
3
  import android.content.SharedPreferences;
4
- import io.github.g00fy2.versioncompare.Version;
5
4
  import java.text.ParsePosition;
6
5
  import java.text.SimpleDateFormat;
7
6
  import java.util.ArrayList;