@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
@@ -0,0 +1,312 @@
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
+ * Parsing and comparison logic ported from version-compare 1.5.0
7
+ * (https://github.com/G00fY2/version-compare), Copyright G00fY2,
8
+ * licensed under the Apache License, Version 2.0
9
+ * (https://www.apache.org/licenses/LICENSE-2.0).
10
+ */
11
+
12
+ package app.capgo.cordova.updater;
13
+
14
+ import androidx.annotation.NonNull;
15
+ import androidx.annotation.Nullable;
16
+ import java.util.ArrayList;
17
+ import java.util.List;
18
+ import java.util.Locale;
19
+
20
+ /**
21
+ * Lenient version parser and comparator.
22
+ *
23
+ * <p>Behavior-compatible port of {@code io.github.g00fy2:versioncompare:1.5.0} (Apache-2.0), kept in-repo so the
24
+ * plugin does not ship an extra third-party dependency. Parsing never throws for non-null input that starts with a
25
+ * digit; unparsable input (null or not starting with a digit) yields an empty version equal to {@code 0}.
26
+ *
27
+ * <p>Pre-release suffix ordering: {@code snapshot < pre-alpha < alpha < beta < rc < stable}, with an optional number
28
+ * right after the qualifier ({@code 1.0.0-rc2 > 1.0.0-rc1}).
29
+ */
30
+ public class Version implements Comparable<Version> {
31
+
32
+ enum ReleaseType {
33
+ SNAPSHOT,
34
+ PRE_ALPHA,
35
+ ALPHA,
36
+ BETA,
37
+ RC,
38
+ STABLE
39
+ }
40
+
41
+ private static final String SNAPSHOT_STRING = "snapshot";
42
+ private static final String PRE_STRING = "pre";
43
+ private static final String ALPHA_STRING = "alpha";
44
+ private static final String BETA_STRING = "beta";
45
+ private static final String RC_STRING = "rc";
46
+
47
+ @Nullable
48
+ private final String originalString;
49
+
50
+ private final List<Long> subversionNumbers = new ArrayList<>();
51
+ private final List<Long> trimmedSubversionNumbers = new ArrayList<>();
52
+ private final String suffix;
53
+ private final ReleaseType releaseType;
54
+ private final long preReleaseVersion;
55
+
56
+ public Version(@Nullable String versionString) {
57
+ originalString = versionString;
58
+ if (originalString != null && startsNumeric(originalString)) {
59
+ String normalized = originalString.replaceAll("\\s", "");
60
+ final int buildMetadata = normalized.indexOf('+');
61
+ if (buildMetadata >= 0) {
62
+ normalized = normalized.substring(0, buildMetadata);
63
+ }
64
+ String[] versionTokens = normalized.split("\\.");
65
+ boolean suffixFound = false;
66
+ StringBuilder suffixSb = null;
67
+
68
+ for (String versionToken : versionTokens) {
69
+ if (suffixFound) {
70
+ suffixSb.append(".");
71
+ suffixSb.append(versionToken);
72
+ } else if (isNumeric(versionToken)) {
73
+ subversionNumbers.add(safeParseLong(versionToken));
74
+ } else {
75
+ for (int i = 0; i < versionToken.length(); i++) {
76
+ if (!Character.isDigit(versionToken.charAt(i))) {
77
+ suffixSb = new StringBuilder();
78
+ if (i > 0) {
79
+ subversionNumbers.add(safeParseLong(versionToken.substring(0, i)));
80
+ suffixSb.append(versionToken.substring(i));
81
+ } else {
82
+ suffixSb.append(versionToken);
83
+ }
84
+ suffixFound = true;
85
+ break;
86
+ }
87
+ }
88
+ }
89
+ }
90
+ suffix = (suffixSb != null) ? suffixSb.toString() : "";
91
+ trimmedSubversionNumbers.addAll(subversionNumbers);
92
+ while (!trimmedSubversionNumbers.isEmpty() && trimmedSubversionNumbers.get(trimmedSubversionNumbers.size() - 1) == 0L) {
93
+ trimmedSubversionNumbers.remove(trimmedSubversionNumbers.size() - 1);
94
+ }
95
+ } else {
96
+ suffix = "";
97
+ }
98
+ releaseType = qualifierToReleaseType(suffix);
99
+ preReleaseVersion = preReleaseVersion(suffix, releaseType);
100
+ }
101
+
102
+ public long getMajor() {
103
+ return trimmedSubversionNumbers.size() > 0 ? trimmedSubversionNumbers.get(0) : 0L;
104
+ }
105
+
106
+ public long getMinor() {
107
+ return trimmedSubversionNumbers.size() > 1 ? trimmedSubversionNumbers.get(1) : 0L;
108
+ }
109
+
110
+ public long getPatch() {
111
+ return trimmedSubversionNumbers.size() > 2 ? trimmedSubversionNumbers.get(2) : 0L;
112
+ }
113
+
114
+ @NonNull
115
+ public List<Long> getSubversionNumbers() {
116
+ return subversionNumbers;
117
+ }
118
+
119
+ @NonNull
120
+ public String getSuffix() {
121
+ return suffix;
122
+ }
123
+
124
+ @Nullable
125
+ public String getOriginalString() {
126
+ return originalString;
127
+ }
128
+
129
+ public boolean isHigherThan(String otherVersion) {
130
+ return isHigherThan(new Version(otherVersion));
131
+ }
132
+
133
+ public boolean isHigherThan(Version otherVersion) {
134
+ return compareTo(otherVersion) > 0;
135
+ }
136
+
137
+ public boolean isLowerThan(String otherVersion) {
138
+ return isLowerThan(new Version(otherVersion));
139
+ }
140
+
141
+ public boolean isLowerThan(Version otherVersion) {
142
+ return compareTo(otherVersion) < 0;
143
+ }
144
+
145
+ public boolean isEqual(String otherVersion) {
146
+ return isEqual(new Version(otherVersion));
147
+ }
148
+
149
+ public boolean isEqual(Version otherVersion) {
150
+ return compareTo(otherVersion) == 0;
151
+ }
152
+
153
+ public boolean isAtLeast(String otherVersion) {
154
+ return isAtLeast(new Version(otherVersion));
155
+ }
156
+
157
+ public boolean isAtLeast(Version otherVersion) {
158
+ return compareTo(otherVersion) >= 0;
159
+ }
160
+
161
+ public boolean isAtLeast(String otherVersion, boolean ignoreSuffix) {
162
+ return isAtLeast(new Version(otherVersion), ignoreSuffix);
163
+ }
164
+
165
+ public boolean isAtLeast(Version otherVersion, boolean ignoreSuffix) {
166
+ return compareTo(otherVersion, ignoreSuffix) >= 0;
167
+ }
168
+
169
+ @Override
170
+ public final int compareTo(@NonNull Version version) {
171
+ return compareTo(version, false);
172
+ }
173
+
174
+ private int compareTo(@NonNull Version version, boolean ignoreSuffix) {
175
+ int versionNumberResult = compareSubversionNumbers(trimmedSubversionNumbers, version.trimmedSubversionNumbers);
176
+ if (versionNumberResult != 0 || ignoreSuffix) {
177
+ return versionNumberResult;
178
+ }
179
+ int releaseTypeResult = releaseType.compareTo(version.releaseType);
180
+ if (releaseTypeResult != 0) {
181
+ return releaseTypeResult;
182
+ }
183
+ return Long.compare(preReleaseVersion, version.preReleaseVersion);
184
+ }
185
+
186
+ @Override
187
+ public final boolean equals(Object o) {
188
+ return o instanceof Version && isEqual((Version) o);
189
+ }
190
+
191
+ @Override
192
+ public final int hashCode() {
193
+ int result = trimmedSubversionNumbers.hashCode();
194
+ result = 31 * result + releaseType.hashCode();
195
+ result = 31 * result + (int) (preReleaseVersion ^ (preReleaseVersion >>> 32));
196
+ return result;
197
+ }
198
+
199
+ @NonNull
200
+ @Override
201
+ public String toString() {
202
+ return String.valueOf(originalString);
203
+ }
204
+
205
+ // helpers
206
+
207
+ private static int compareSubversionNumbers(@NonNull final List<Long> versionNumbersA, @NonNull final List<Long> versionNumbersB) {
208
+ final int numbersSizeA = versionNumbersA.size();
209
+ final int numbersSizeB = versionNumbersB.size();
210
+ final int maxSize = Math.max(numbersSizeA, numbersSizeB);
211
+
212
+ for (int i = 0; i < maxSize; i++) {
213
+ final long a = i < numbersSizeA ? versionNumbersA.get(i) : 0L;
214
+ final long b = i < numbersSizeB ? versionNumbersB.get(i) : 0L;
215
+ if (a > b) {
216
+ return 1;
217
+ } else if (a < b) {
218
+ return -1;
219
+ }
220
+ }
221
+ return 0;
222
+ }
223
+
224
+ static ReleaseType qualifierToReleaseType(@NonNull String suffix) {
225
+ if (suffix.length() > 0) {
226
+ suffix = suffix.toLowerCase(Locale.ROOT);
227
+ if (suffix.contains(RC_STRING)) return ReleaseType.RC;
228
+ if (suffix.contains(BETA_STRING)) return ReleaseType.BETA;
229
+ if (suffix.contains(ALPHA_STRING)) {
230
+ if (suffix.substring(0, suffix.indexOf(ALPHA_STRING)).contains(PRE_STRING)) {
231
+ return ReleaseType.PRE_ALPHA;
232
+ }
233
+ return ReleaseType.ALPHA;
234
+ }
235
+ if (suffix.contains(SNAPSHOT_STRING)) return ReleaseType.SNAPSHOT;
236
+ }
237
+ return ReleaseType.STABLE;
238
+ }
239
+
240
+ private static long preReleaseVersion(@NonNull final String suffix, final ReleaseType releaseType) {
241
+ if (releaseType == ReleaseType.STABLE || releaseType == ReleaseType.SNAPSHOT) return 0;
242
+
243
+ final int startIndex = indexOfQualifier(suffix, releaseType);
244
+ if (startIndex < suffix.length()) {
245
+ final int maxStartIndex = Math.min(startIndex + 2, suffix.length());
246
+ if (containsNumeric(suffix.substring(startIndex, maxStartIndex))) {
247
+ final StringBuilder versionNumber = new StringBuilder();
248
+ for (int i = startIndex; i < suffix.length(); i++) {
249
+ final char c = suffix.charAt(i);
250
+ if (Character.isDigit(c)) {
251
+ versionNumber.append(c);
252
+ } else if (i != startIndex) {
253
+ break;
254
+ }
255
+ }
256
+ return safeParseLong(versionNumber.toString());
257
+ }
258
+ }
259
+ return 0;
260
+ }
261
+
262
+ private static int indexOfQualifier(@NonNull String suffix, final ReleaseType releaseType) {
263
+ suffix = suffix.toLowerCase(Locale.ROOT);
264
+ switch (releaseType) {
265
+ case RC:
266
+ return suffix.indexOf(RC_STRING) + RC_STRING.length();
267
+ case BETA:
268
+ return suffix.indexOf(BETA_STRING) + BETA_STRING.length();
269
+ case ALPHA:
270
+ case PRE_ALPHA:
271
+ return suffix.indexOf(ALPHA_STRING) + ALPHA_STRING.length();
272
+ default:
273
+ return 0;
274
+ }
275
+ }
276
+
277
+ private static boolean startsNumeric(@NonNull String str) {
278
+ str = str.trim();
279
+ return str.length() > 0 && Character.isDigit(str.charAt(0));
280
+ }
281
+
282
+ private static long safeParseLong(@NonNull String numbers) {
283
+ try {
284
+ return Long.parseLong(numbers);
285
+ } catch (NumberFormatException e) {
286
+ return Long.MAX_VALUE;
287
+ }
288
+ }
289
+
290
+ private static boolean isNumeric(@NonNull final CharSequence cs) {
291
+ final int sz = cs.length();
292
+ if (sz > 0) {
293
+ for (int i = 0; i < sz; i++) {
294
+ if (!Character.isDigit(cs.charAt(i))) {
295
+ return false;
296
+ }
297
+ }
298
+ return true;
299
+ }
300
+ return false;
301
+ }
302
+
303
+ private static boolean containsNumeric(@NonNull final CharSequence cs) {
304
+ final int sz = cs.length();
305
+ for (int i = 0; i < sz; i++) {
306
+ if (Character.isDigit(cs.charAt(i))) {
307
+ return true;
308
+ }
309
+ }
310
+ return false;
311
+ }
312
+ }
@@ -5,15 +5,13 @@ repositories {
5
5
  }
6
6
 
7
7
  dependencies {
8
- implementation "androidx.work:work-runtime:2.10.5"
9
- implementation "androidx.lifecycle:lifecycle-process:2.10.0"
8
+ implementation "androidx.work:work-runtime:2.12.0"
9
+ implementation "androidx.lifecycle:lifecycle-process:2.11.0"
10
10
  implementation "com.google.android.gms:play-services-tasks:18.4.1"
11
- implementation "com.google.guava:guava:33.6.0-android"
12
- implementation "io.github.g00fy2:versioncompare:1.5.0"
13
11
  implementation "com.google.android.play:app-update:2.1.0"
14
12
  implementation "com.google.android.play:app-update-ktx:2.1.0"
15
13
  implementation "org.brotli:dec:0.1.2"
16
14
  implementation "com.squareup.okhttp3:okhttp:4.12.0"
17
- implementation "androidx.webkit:webkit:1.11.0"
15
+ implementation "androidx.webkit:webkit:1.17.1"
18
16
  implementation "androidx.appcompat:appcompat:1.7.1"
19
17
  }
@@ -0,0 +1,29 @@
1
+ package app.capgo.cordova.updater;
2
+
3
+ import static org.junit.Assert.assertFalse;
4
+ import static org.junit.Assert.assertTrue;
5
+
6
+ import org.junit.Test;
7
+ import org.junit.runner.RunWith;
8
+ import org.robolectric.RobolectricTestRunner;
9
+ import org.robolectric.annotation.Config;
10
+
11
+ @RunWith(RobolectricTestRunner.class)
12
+ @Config(manifest = Config.NONE)
13
+ public class BackgroundDownloadSettlementTest {
14
+
15
+ @Test
16
+ public void acceptsMatchingSettlementToken() {
17
+ assertTrue(CordovaUpdaterPlugin.shouldAcceptBackgroundDownloadSettlement(3L, 3L));
18
+ }
19
+
20
+ @Test
21
+ public void rejectsStaleSettlementToken() {
22
+ assertFalse(CordovaUpdaterPlugin.shouldAcceptBackgroundDownloadSettlement(2L, 3L));
23
+ }
24
+
25
+ @Test
26
+ public void rejectsUnsetSettlementToken() {
27
+ assertFalse(CordovaUpdaterPlugin.shouldAcceptBackgroundDownloadSettlement(0L, 3L));
28
+ }
29
+ }
@@ -281,17 +281,6 @@ public class CapacitorUpdaterUnitTest {
281
281
  return this.existingLatestBundle;
282
282
  }
283
283
 
284
- @Override
285
- public void downloadBackground(
286
- final String url,
287
- final String version,
288
- final String sessionKey,
289
- final String checksum,
290
- final JSONArray manifest
291
- ) {
292
- this.downloadBackground(url, version, sessionKey, checksum, manifest, true);
293
- }
294
-
295
284
  @Override
296
285
  public void downloadBackground(
297
286
  final String url,
@@ -299,7 +288,8 @@ public class CapacitorUpdaterUnitTest {
299
288
  final String sessionKey,
300
289
  final String checksum,
301
290
  final JSONArray manifest,
302
- final boolean setNext
291
+ final boolean setNext,
292
+ final long settlementToken
303
293
  ) {
304
294
  this.downloadBackgroundCalled = true;
305
295
  this.downloadBackgroundSetNext = setNext;
@@ -7,7 +7,6 @@ import static org.mockito.ArgumentMatchers.eq;
7
7
  import static org.mockito.Mockito.*;
8
8
 
9
9
  import android.content.SharedPreferences;
10
- import io.github.g00fy2.versioncompare.Version;
11
10
  import java.util.ArrayList;
12
11
  import org.json.JSONArray;
13
12
  import org.json.JSONObject;
@@ -0,0 +1,137 @@
1
+ package app.capgo.cordova.updater;
2
+
3
+ import static org.junit.Assert.assertArrayEquals;
4
+ import static org.junit.Assert.assertEquals;
5
+ import static org.junit.Assert.assertFalse;
6
+ import static org.junit.Assert.assertTrue;
7
+ import static org.junit.Assert.fail;
8
+
9
+ import java.io.IOException;
10
+ import java.nio.charset.StandardCharsets;
11
+ import java.nio.file.Files;
12
+ import java.nio.file.Path;
13
+ import java.security.GeneralSecurityException;
14
+ import java.security.PublicKey;
15
+ import org.json.JSONArray;
16
+ import org.json.JSONObject;
17
+ import org.junit.BeforeClass;
18
+ import org.junit.Test;
19
+ import org.junit.runner.RunWith;
20
+ import org.robolectric.RobolectricTestRunner;
21
+ import org.robolectric.annotation.Config;
22
+
23
+ @RunWith(RobolectricTestRunner.class)
24
+ @Config(manifest = Config.NONE)
25
+ public class RsaContractTest {
26
+
27
+ private static JSONObject contract;
28
+
29
+ @BeforeClass
30
+ public static void setUpClass() throws Exception {
31
+ CryptoCipher.setLogger(new Logger("RsaContractTest", new Logger.Options(Logger.LogLevel.silent)));
32
+ contract = new JSONObject(new String(Files.readAllBytes(contractFile()), StandardCharsets.UTF_8));
33
+ }
34
+
35
+ private static Path contractFile() throws IOException {
36
+ Path current = Path.of(System.getProperty("user.dir")).toAbsolutePath();
37
+ while (current != null) {
38
+ Path candidate = current.resolve("native-contract-tests/crypto-rsa.json");
39
+ if (Files.exists(candidate)) {
40
+ return candidate;
41
+ }
42
+ current = current.getParent();
43
+ }
44
+ throw new IOException("native-contract-tests/crypto-rsa.json not found");
45
+ }
46
+
47
+ private static byte[] hexToBytes(String hex) {
48
+ final byte[] out = new byte[hex.length() / 2];
49
+ for (int i = 0; i < out.length; i++) {
50
+ out[i] = (byte) Integer.parseInt(hex.substring(i * 2, i * 2 + 2), 16);
51
+ }
52
+ return out;
53
+ }
54
+
55
+ @Test
56
+ public void rsaPublicDecryptMatchesNativeContract() throws Exception {
57
+ final PublicKey publicKey = CryptoCipher.stringToPublicKey(contract.getString("publicKeyPem"));
58
+ final JSONArray cases = contract.getJSONArray("rsaPublicDecrypt");
59
+ for (int index = 0; index < cases.length(); index++) {
60
+ final JSONObject testCase = cases.getJSONObject(index);
61
+ final String id = testCase.getString("id");
62
+ final JSONObject input = testCase.getJSONObject("input");
63
+ final JSONObject expect = testCase.getJSONObject("expect");
64
+ final byte[] ciphertext = hexToBytes(input.getString("ciphertextHex"));
65
+ final byte[] expected = hexToBytes(expect.getString("plaintextHex"));
66
+ final byte[] decrypted = CryptoCipher.decryptRSA(ciphertext, publicKey);
67
+ assertArrayEquals(id, expected, decrypted);
68
+ }
69
+ }
70
+
71
+ @Test
72
+ public void decryptChecksumMatchesNativeContract() throws Exception {
73
+ final String publicKey = contract.getString("publicKeyPem");
74
+ final JSONArray cases = contract.getJSONArray("decryptChecksum");
75
+ for (int index = 0; index < cases.length(); index++) {
76
+ final JSONObject testCase = cases.getJSONObject(index);
77
+ final String id = testCase.getString("id");
78
+ final JSONObject input = testCase.getJSONObject("input");
79
+ final JSONObject expect = testCase.getJSONObject("expect");
80
+ final String result = CryptoCipher.decryptChecksum(input.getString("checksumHex"), publicKey);
81
+ assertEquals(id, expect.getString("decryptedHex"), result);
82
+ }
83
+ }
84
+
85
+ @Test
86
+ public void decryptChecksumInvalidMatchesNativeContract() throws Exception {
87
+ final String publicKey = contract.getString("publicKeyPem");
88
+ final JSONArray cases = contract.getJSONArray("decryptChecksumInvalid");
89
+ for (int index = 0; index < cases.length(); index++) {
90
+ final JSONObject testCase = cases.getJSONObject(index);
91
+ final String id = testCase.getString("id");
92
+ final JSONObject input = testCase.getJSONObject("input");
93
+ final boolean shouldThrow = testCase.getJSONObject("expect").getBoolean("throws");
94
+ try {
95
+ CryptoCipher.decryptChecksum(input.getString("checksumHex"), publicKey);
96
+ if (shouldThrow) {
97
+ fail(id + " expected IOException");
98
+ }
99
+ } catch (IOException e) {
100
+ if (!shouldThrow) {
101
+ fail(id + " unexpected IOException: " + e.getMessage());
102
+ }
103
+ }
104
+ }
105
+ }
106
+
107
+ @Test
108
+ public void calcKeyIdMatchesNativeContract() throws Exception {
109
+ final JSONArray cases = contract.getJSONArray("calcKeyId");
110
+ for (int index = 0; index < cases.length(); index++) {
111
+ final JSONObject testCase = cases.getJSONObject(index);
112
+ final String id = testCase.getString("id");
113
+ final JSONObject input = testCase.getJSONObject("input");
114
+ final JSONObject expect = testCase.getJSONObject("expect");
115
+ assertEquals(id, expect.getString("keyId"), CryptoCipher.calcKeyId(input.getString("publicKeyPem")));
116
+ }
117
+ }
118
+
119
+ @Test
120
+ public void rsaPublicKeyLoadMatchesNativeContract() throws Exception {
121
+ final JSONArray cases = contract.getJSONArray("rsaPublicKeyLoad");
122
+ for (int index = 0; index < cases.length(); index++) {
123
+ final JSONObject testCase = cases.getJSONObject(index);
124
+ final String id = testCase.getString("id");
125
+ final String publicKeyPem = testCase.getJSONObject("input").getString("publicKeyPem");
126
+ final boolean shouldLoad = testCase.getJSONObject("expect").getBoolean("loads");
127
+ boolean loaded = false;
128
+ try {
129
+ CryptoCipher.stringToPublicKey(publicKeyPem);
130
+ loaded = true;
131
+ } catch (Exception ignored) {
132
+ loaded = false;
133
+ }
134
+ assertEquals(id, shouldLoad, loaded);
135
+ }
136
+ }
137
+ }