@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.
- package/dist/esm/definitions.d.ts +116 -86
- package/dist/esm/definitions.js +2 -2
- package/dist/esm/definitions.js.map +1 -1
- package/dist/esm/exec.js +11 -1
- package/dist/esm/exec.js.map +1 -1
- package/dist/esm/history.js +7 -7
- package/dist/esm/history.js.map +1 -1
- package/dist/plugin.cjs.js +13 -3
- package/dist/plugin.cjs.js.map +1 -1
- package/dist/plugin.js +13 -3
- package/dist/plugin.js.map +1 -1
- package/package.json +5 -3
- package/plugin.xml +9 -9
- package/src/android/CordovaUpdaterPlugin.java +173 -5
- package/src/android/app/capgo/cordova/updater/CapgoUpdater.java +313 -55
- package/src/android/app/capgo/cordova/updater/CryptoCipher.java +51 -18
- package/src/android/app/capgo/cordova/updater/DelayUpdateUtils.java +0 -1
- package/src/android/app/capgo/cordova/updater/DownloadService.java +402 -89
- package/src/android/app/capgo/cordova/updater/Version.java +312 -0
- package/src/android/capgo-cordova-updater.gradle +3 -5
- package/src/android/test/app/capgo/cordova/updater/BackgroundDownloadSettlementTest.java +29 -0
- package/src/android/test/app/capgo/cordova/updater/CapacitorUpdaterUnitTest.java.skip +2 -12
- package/src/android/test/app/capgo/cordova/updater/DelayUpdateUtilsTest.java +0 -1
- package/src/android/test/app/capgo/cordova/updater/RsaContractTest.java +137 -0
- package/src/android/test/app/capgo/cordova/updater/SecurityHardeningTest.java +197 -0
- package/src/android/test/app/capgo/cordova/updater/SessionKeyRequiredTest.java +344 -0
- package/src/ios/CapgoSemanticVersion.swift +181 -0
- package/src/ios/CapgoUpdater.swift +427 -114
- package/src/ios/CordovaUpdaterPlugin.swift +171 -40
- package/src/ios/CryptoCipher.swift +41 -38
- package/src/ios/DelayUpdateUtils.swift +19 -45
- package/src/ios/Logger.swift +81 -5
- package/src/ios/RedirectPolicyDelegate.swift +53 -0
- package/src/ios/WebViewStatsReporter.swift +5 -0
- package/src/ios/ZipArchiveReader.swift +263 -0
- 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.
|
|
9
|
-
implementation "androidx.lifecycle:lifecycle-process:2.
|
|
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.
|
|
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
|
+
}
|