@capgo/capacitor-updater 6.0.51-beta.1 → 6.0.52
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 +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdater.java +10 -50
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +5 -12
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipher.java +75 -119
- package/dist/docs.json +2 -2
- package/dist/esm/definitions.d.ts +2 -2
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Plugin/CapacitorUpdater.swift +9 -48
- package/ios/Plugin/CapacitorUpdaterPlugin.swift +11 -13
- package/ios/Plugin/CryptoCipher.swift +113 -83
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -197,7 +197,7 @@ CapacitorUpdater can be configured with these options:
|
|
|
197
197
|
| **`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> | |
|
|
198
198
|
| **`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/updates</code> | |
|
|
199
199
|
| **`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> | |
|
|
200
|
-
| **`
|
|
200
|
+
| **`privateKey`** | <code>string</code> | Configure the private key for end to end live update encryption. Only available for Android and iOS. | <code>undefined</code> | |
|
|
201
201
|
| **`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 |
|
|
202
202
|
| **`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 |
|
|
203
203
|
| **`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> | |
|
|
@@ -225,7 +225,7 @@ In `capacitor.config.json`:
|
|
|
225
225
|
"resetWhenUpdate": false,
|
|
226
226
|
"updateUrl": https://example.com/api/auto_update,
|
|
227
227
|
"statsUrl": https://example.com/api/stats,
|
|
228
|
-
"
|
|
228
|
+
"privateKey": undefined,
|
|
229
229
|
"version": undefined,
|
|
230
230
|
"directUpdate": undefined,
|
|
231
231
|
"periodCheckDelay": undefined,
|
|
@@ -259,7 +259,7 @@ const config: CapacitorConfig = {
|
|
|
259
259
|
resetWhenUpdate: false,
|
|
260
260
|
updateUrl: https://example.com/api/auto_update,
|
|
261
261
|
statsUrl: https://example.com/api/stats,
|
|
262
|
-
|
|
262
|
+
privateKey: undefined,
|
|
263
263
|
version: undefined,
|
|
264
264
|
directUpdate: undefined,
|
|
265
265
|
periodCheckDelay: undefined,
|
|
@@ -41,7 +41,7 @@ import java.io.UnsupportedEncodingException;
|
|
|
41
41
|
import java.net.URL;
|
|
42
42
|
import java.net.URLConnection;
|
|
43
43
|
import java.security.GeneralSecurityException;
|
|
44
|
-
import java.security.
|
|
44
|
+
import java.security.PrivateKey;
|
|
45
45
|
import java.security.SecureRandom;
|
|
46
46
|
import java.util.ArrayList;
|
|
47
47
|
import java.util.Date;
|
|
@@ -86,8 +86,7 @@ public class CapacitorUpdater {
|
|
|
86
86
|
public String channelUrl = "";
|
|
87
87
|
public String defaultChannel = "";
|
|
88
88
|
public String appId = "";
|
|
89
|
-
public String
|
|
90
|
-
public Boolean hasOldPrivateKeyPropertyInConfig = false;
|
|
89
|
+
public String privateKey = "";
|
|
91
90
|
public String deviceID = "";
|
|
92
91
|
public int timeout = 20000;
|
|
93
92
|
|
|
@@ -338,16 +337,7 @@ public class CapacitorUpdater {
|
|
|
338
337
|
) {
|
|
339
338
|
try {
|
|
340
339
|
final File downloaded = new File(this.documentsDir, dest);
|
|
341
|
-
if (this.hasOldPrivateKeyPropertyInConfig) {
|
|
342
|
-
Log.i(
|
|
343
|
-
CapacitorUpdater.TAG,
|
|
344
|
-
"There is still an privateKey property in the config"
|
|
345
|
-
);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
340
|
this.decryptFile(downloaded, sessionKey, version);
|
|
349
|
-
final String checksumDecrypted =
|
|
350
|
-
this.decryptChecksum(checksumRes, version);
|
|
351
341
|
final String checksum;
|
|
352
342
|
checksum = this.getChecksum(downloaded);
|
|
353
343
|
this.notifyDownload(id, 71);
|
|
@@ -367,13 +357,13 @@ public class CapacitorUpdater {
|
|
|
367
357
|
);
|
|
368
358
|
this.saveBundleInfo(id, next);
|
|
369
359
|
if (
|
|
370
|
-
|
|
371
|
-
!
|
|
372
|
-
!
|
|
360
|
+
checksumRes != null &&
|
|
361
|
+
!checksumRes.isEmpty() &&
|
|
362
|
+
!checksumRes.equals(checksum)
|
|
373
363
|
) {
|
|
374
364
|
Log.e(
|
|
375
365
|
CapacitorUpdater.TAG,
|
|
376
|
-
"Error checksum " +
|
|
366
|
+
"Error checksum " + checksumRes + " " + checksum
|
|
377
367
|
);
|
|
378
368
|
this.sendStats("checksum_fail");
|
|
379
369
|
final Boolean res = this.delete(id);
|
|
@@ -507,25 +497,6 @@ public class CapacitorUpdater {
|
|
|
507
497
|
return enc.toLowerCase();
|
|
508
498
|
}
|
|
509
499
|
|
|
510
|
-
private String decryptChecksum(String checksum, String version)
|
|
511
|
-
throws IOException {
|
|
512
|
-
if (this.publicKey == null || this.publicKey.isEmpty()) {
|
|
513
|
-
Log.i(TAG, "Cannot find public key");
|
|
514
|
-
return checksum;
|
|
515
|
-
}
|
|
516
|
-
try {
|
|
517
|
-
byte[] checksumBytes = Base64.decode(checksum, Base64.DEFAULT);
|
|
518
|
-
PublicKey pKey = CryptoCipher.stringToPublicKey(this.publicKey);
|
|
519
|
-
byte[] decryptedChecksum = CryptoCipher.decryptRSA(checksumBytes, pKey);
|
|
520
|
-
// Match Swift's base64 encoding of the decrypted result
|
|
521
|
-
return Base64.encodeToString(decryptedChecksum, Base64.NO_WRAP);
|
|
522
|
-
} catch (GeneralSecurityException e) {
|
|
523
|
-
Log.i(TAG, "decryptChecksum fail", e);
|
|
524
|
-
this.sendStats("decrypt_fail", version);
|
|
525
|
-
throw new IOException("GeneralSecurityException", e);
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
500
|
private void decryptFile(
|
|
530
501
|
final File file,
|
|
531
502
|
final String ivSessionKey,
|
|
@@ -533,24 +504,15 @@ public class CapacitorUpdater {
|
|
|
533
504
|
) throws IOException {
|
|
534
505
|
// (str != null && !str.isEmpty())
|
|
535
506
|
if (
|
|
536
|
-
this.
|
|
537
|
-
this.
|
|
507
|
+
this.privateKey == null ||
|
|
508
|
+
this.privateKey.isEmpty() ||
|
|
538
509
|
ivSessionKey == null ||
|
|
539
510
|
ivSessionKey.isEmpty() ||
|
|
540
511
|
ivSessionKey.split(":").length != 2
|
|
541
512
|
) {
|
|
542
|
-
Log.i(TAG, "Cannot
|
|
513
|
+
Log.i(TAG, "Cannot found privateKey or sessionKey");
|
|
543
514
|
return;
|
|
544
515
|
}
|
|
545
|
-
|
|
546
|
-
if (!this.publicKey.startsWith("-----BEGIN RSA PUBLIC KEY-----")) {
|
|
547
|
-
Log.e(
|
|
548
|
-
CapacitorUpdater.TAG,
|
|
549
|
-
"The public key is not a valid RSA Public key"
|
|
550
|
-
);
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
516
|
try {
|
|
555
517
|
String ivB64 = ivSessionKey.split(":")[0];
|
|
556
518
|
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
@@ -559,10 +521,8 @@ public class CapacitorUpdater {
|
|
|
559
521
|
sessionKeyB64.getBytes(),
|
|
560
522
|
Base64.DEFAULT
|
|
561
523
|
);
|
|
562
|
-
|
|
563
|
-
PublicKey pKey = CryptoCipher.stringToPublicKey(this.publicKey);
|
|
524
|
+
PrivateKey pKey = CryptoCipher.stringToPrivateKey(this.privateKey);
|
|
564
525
|
byte[] decryptedSessionKey = CryptoCipher.decryptRSA(sessionKey, pKey);
|
|
565
|
-
|
|
566
526
|
SecretKey sKey = CryptoCipher.byteToSessionKey(decryptedSessionKey);
|
|
567
527
|
byte[] content = new byte[(int) file.length()];
|
|
568
528
|
|
|
@@ -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 defaultPrivateKey =
|
|
54
|
+
"-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4pW9olT0FBXXivRCzd3xcImlWZrqkwcF2xTkX/FwXmj9eh9H\nkBLrsQmfsC+PJisRXIOGq6a0z3bsGq6jBpp3/Jr9jiaW5VuPGaKeMaZZBRvi/N5f\nIMG3hZXSOcy0IYg+E1Q7RkYO1xq5GLHseqG+PXvJsNe4R8R/Bmd/ngq0xh/cvcrH\nHpXwO0Aj9tfprlb+rHaVV79EkVRWYPidOLnK1n0EFHFJ1d/MyDIp10TEGm2xHpf/\nBrlb1an8wXEuzoC0DgYaczgTjovwR+ewSGhSHJliQdM0Qa3o1iN87DldWtydImMs\nPjJ3DUwpsjAMRe5X8Et4+udFW2ciYnQo9H0CkwIDAQABAoIBAQCtjlMV/4qBxAU4\nu0ZcWA9yywwraX0aJ3v1xrfzQYV322Wk4Ea5dbSxA5UcqCE29DA1M824t1Wxv/6z\npWbcTP9xLuresnJMtmgTE7umfiubvTONy2sENT20hgDkIwcq1CfwOEm61zjQzPhQ\nkSB5AmEsyR/BZEsUNc+ygR6AWOUFB7tj4yMc32LOTWSbE/znnF2BkmlmnQykomG1\n2oVqM3lUFP7+m8ux1O7scO6IMts+Z/eFXjWfxpbebUSvSIR83GXPQZ34S/c0ehOg\nyHdmCSOel1r3VvInMe+30j54Jr+Ml/7Ee6axiwyE2e/bd85MsK9sVdp0OtelXaqA\nOZZqWvN5AoGBAP2Hn3lSq+a8GsDH726mHJw60xM0LPbVJTYbXsmQkg1tl3NKJTMM\nQqz41+5uys+phEgLHI9gVJ0r+HaGHXnJ4zewlFjsudstb/0nfctUvTqnhEhfNo9I\ny4kufVKPRF3sMEeo7CDVJs4GNBLycEyIBy6Mbv0VcO7VaZqggRwu4no9AoGBAOTK\n6NWYs1BWlkua2wmxexGOzehNGedInp0wGr2l4FDayWjkZLqvB+nNXUQ63NdHlSs4\nWB2Z1kQXZxVaI2tPYexGUKXEo2uFob63uflbuE029ovDXIIPFTPtGNdNXwhHT5a+\nPhmy3sMc+s2BSNM5qaNmfxQxhdd6gRU6oikE+c0PAoGAMn3cKNFqIt27hkFLUgIL\nGKIuf1iYy9/PNWNmEUaVj88PpopRtkTu0nwMpROzmH/uNFriKTvKHjMvnItBO4wV\nkHW+VadvrFL0Rrqituf9d7z8/1zXBNo+juePVe3qc7oiM2NVA4Tv4YAixtM5wkQl\nCgQ15nlqsGYYTg9BJ1e/CxECgYEAjEYPzO2reuUrjr0p8F59ev1YJ0YmTJRMk0ks\nC/yIdGo/tGzbiU3JB0LfHPcN8Xu07GPGOpfYM7U5gXDbaG6qNgfCaHAQVdr/mQPi\nJQ1kCQtay8QCkscWk9iZM1//lP7LwDtxraXqSCwbZSYP9VlUNZeg8EuQqNU2EUL6\nqzWexmcCgYEA0prUGNBacraTYEknB1CsbP36UPWsqFWOvevlz+uEC5JPxPuW5ZHh\nSQN7xl6+PHyjPBM7ttwPKyhgLOVTb3K7ex/PXnudojMUK5fh7vYfChVTSlx2p6r0\nDi58PdD+node08cJH+ie0Yphp7m+D4+R9XD0v0nEvnu4BtAW6DrJasw=\n-----END RSA PRIVATE 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 = "6.0.
|
|
58
|
+
private final String PLUGIN_VERSION = "6.0.52";
|
|
59
59
|
private static final String DELAY_CONDITION_PREFERENCES = "";
|
|
60
60
|
|
|
61
61
|
private SharedPreferences.Editor editor;
|
|
@@ -172,15 +172,8 @@ public class CapacitorUpdaterPlugin extends Plugin {
|
|
|
172
172
|
);
|
|
173
173
|
}
|
|
174
174
|
Log.i(CapacitorUpdater.TAG, "appId: " + implementation.appId);
|
|
175
|
-
this.implementation.
|
|
176
|
-
.getString("
|
|
177
|
-
this.implementation.hasOldPrivateKeyPropertyInConfig = false;
|
|
178
|
-
if (
|
|
179
|
-
this.getConfig().getString("privateKey") != null &&
|
|
180
|
-
!this.getConfig().getString("privateKey").isEmpty()
|
|
181
|
-
) {
|
|
182
|
-
this.implementation.hasOldPrivateKeyPropertyInConfig = true;
|
|
183
|
-
}
|
|
175
|
+
this.implementation.privateKey = this.getConfig()
|
|
176
|
+
.getString("privateKey", defaultPrivateKey);
|
|
184
177
|
this.implementation.statsUrl = this.getConfig()
|
|
185
178
|
.getString("statsUrl", statsUrlDefault);
|
|
186
179
|
this.implementation.channelUrl = this.getConfig()
|
|
@@ -17,26 +17,33 @@ 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.PrivateKey;
|
|
21
21
|
import java.security.spec.InvalidKeySpecException;
|
|
22
|
-
import java.security.spec.
|
|
22
|
+
import java.security.spec.MGF1ParameterSpec;
|
|
23
|
+
import java.security.spec.PKCS8EncodedKeySpec;
|
|
23
24
|
import javax.crypto.BadPaddingException;
|
|
24
25
|
import javax.crypto.Cipher;
|
|
25
26
|
import javax.crypto.IllegalBlockSizeException;
|
|
26
27
|
import javax.crypto.NoSuchPaddingException;
|
|
27
28
|
import javax.crypto.SecretKey;
|
|
28
29
|
import javax.crypto.spec.IvParameterSpec;
|
|
30
|
+
import javax.crypto.spec.OAEPParameterSpec;
|
|
29
31
|
import javax.crypto.spec.PSource;
|
|
30
32
|
import javax.crypto.spec.SecretKeySpec;
|
|
31
33
|
|
|
32
34
|
public class CryptoCipher {
|
|
33
35
|
|
|
34
|
-
public static byte[] decryptRSA(byte[] source,
|
|
36
|
+
public static byte[] decryptRSA(byte[] source, PrivateKey privateKey)
|
|
35
37
|
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
|
36
|
-
Cipher cipher = Cipher.getInstance("RSA/ECB/
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
|
|
39
|
+
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
|
|
40
|
+
"SHA-256",
|
|
41
|
+
"MGF1",
|
|
42
|
+
new MGF1ParameterSpec("SHA-256"),
|
|
43
|
+
PSource.PSpecified.DEFAULT
|
|
44
|
+
);
|
|
45
|
+
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
|
|
46
|
+
return cipher.doFinal(source);
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
|
|
@@ -57,133 +64,82 @@ public class CryptoCipher {
|
|
|
57
64
|
return new SecretKeySpec(sessionKey, 0, sessionKey.length, "AES");
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
private static
|
|
67
|
+
private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes)
|
|
61
68
|
throws GeneralSecurityException {
|
|
62
69
|
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
63
|
-
|
|
70
|
+
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
|
|
64
71
|
try {
|
|
65
|
-
return keyFactory.
|
|
72
|
+
return keyFactory.generatePrivate(keySpec);
|
|
66
73
|
} catch (InvalidKeySpecException e) {
|
|
67
74
|
throw new IllegalArgumentException("Unexpected key format!", e);
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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)
|
|
117
|
-
);
|
|
118
|
-
byte[] keyInfoValue = joinPublic(
|
|
119
|
-
RSA_ALGORITHM_IDENTIFIER_SEQUENCE,
|
|
120
|
-
keyBitString
|
|
121
|
-
);
|
|
122
|
-
byte[] keyInfoSequence = createDEREncoding(SEQUENCE_TAG, keyInfoValue);
|
|
123
|
-
return readX509PublicKey(keyInfoSequence);
|
|
124
|
-
}
|
|
125
|
-
|
|
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;
|
|
140
|
-
}
|
|
141
|
-
|
|
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
|
-
}
|
|
148
|
-
|
|
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;
|
|
78
|
+
private static byte[] join(byte[] byteArray1, byte[] byteArray2) {
|
|
79
|
+
byte[] bytes = new byte[byteArray1.length + byteArray2.length];
|
|
80
|
+
System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
|
|
156
81
|
System.arraycopy(
|
|
157
|
-
|
|
82
|
+
byteArray2,
|
|
158
83
|
0,
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
84
|
+
bytes,
|
|
85
|
+
byteArray1.length,
|
|
86
|
+
byteArray2.length
|
|
162
87
|
);
|
|
163
|
-
|
|
164
|
-
|
|
88
|
+
return bytes;
|
|
89
|
+
}
|
|
165
90
|
|
|
166
|
-
|
|
91
|
+
private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes)
|
|
92
|
+
throws GeneralSecurityException {
|
|
93
|
+
// We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand
|
|
94
|
+
int pkcs1Length = pkcs1Bytes.length;
|
|
95
|
+
int totalLength = pkcs1Length + 22;
|
|
96
|
+
byte[] pkcs8Header = new byte[] {
|
|
97
|
+
0x30,
|
|
98
|
+
(byte) 0x82,
|
|
99
|
+
(byte) ((totalLength >> 8) & 0xff),
|
|
100
|
+
(byte) (totalLength & 0xff), // Sequence + total length
|
|
101
|
+
0x2,
|
|
102
|
+
0x1,
|
|
103
|
+
0x0, // Integer (0)
|
|
104
|
+
0x30,
|
|
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);
|
|
167
126
|
}
|
|
168
127
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
return new byte[] {
|
|
179
|
-
(byte) 0x82,
|
|
180
|
-
(byte) (size >> Byte.SIZE),
|
|
181
|
-
(byte) size,
|
|
182
|
-
};
|
|
183
|
-
}
|
|
128
|
+
public static PrivateKey stringToPrivateKey(String private_key)
|
|
129
|
+
throws GeneralSecurityException {
|
|
130
|
+
// Base64 decode the result
|
|
131
|
+
|
|
132
|
+
String pkcs1Pem = private_key;
|
|
133
|
+
pkcs1Pem = pkcs1Pem.replace("-----BEGIN RSA PRIVATE KEY-----", "");
|
|
134
|
+
pkcs1Pem = pkcs1Pem.replace("-----END RSA PRIVATE KEY-----", "");
|
|
135
|
+
pkcs1Pem = pkcs1Pem.replace("\\n", "");
|
|
136
|
+
pkcs1Pem = pkcs1Pem.replace(" ", "");
|
|
184
137
|
|
|
185
|
-
|
|
186
|
-
|
|
138
|
+
byte[] pkcs1EncodedBytes = Base64.decode(
|
|
139
|
+
pkcs1Pem.getBytes(),
|
|
140
|
+
Base64.DEFAULT
|
|
187
141
|
);
|
|
142
|
+
// extract the private key
|
|
143
|
+
return readPkcs1PrivateKey(pkcs1EncodedBytes);
|
|
188
144
|
}
|
|
189
145
|
}
|
package/dist/docs.json
CHANGED
|
@@ -1881,14 +1881,14 @@
|
|
|
1881
1881
|
"type": "string | undefined"
|
|
1882
1882
|
},
|
|
1883
1883
|
{
|
|
1884
|
-
"name": "
|
|
1884
|
+
"name": "privateKey",
|
|
1885
1885
|
"tags": [
|
|
1886
1886
|
{
|
|
1887
1887
|
"text": "undefined",
|
|
1888
1888
|
"name": "default"
|
|
1889
1889
|
}
|
|
1890
1890
|
],
|
|
1891
|
-
"docs": "Configure the
|
|
1891
|
+
"docs": "Configure the private key for end to end live update encryption.\n\nOnly available for Android and iOS.",
|
|
1892
1892
|
"complexTypes": [],
|
|
1893
1893
|
"type": "string | undefined"
|
|
1894
1894
|
},
|
|
@@ -78,13 +78,13 @@ declare module "@capacitor/cli" {
|
|
|
78
78
|
*/
|
|
79
79
|
statsUrl?: string;
|
|
80
80
|
/**
|
|
81
|
-
* Configure the
|
|
81
|
+
* Configure the private 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
|
+
privateKey?: 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.
|
|
@@ -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/updates\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}\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 delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n *\n * @example\n * // Delay 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 * // Delay 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 * // Delay 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>;\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>;\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>;\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>;\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>;\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>;\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>;\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>;\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>;\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 * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update\n * @since 4.0.0\n * @default undefined\n */\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/updates\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 delay the update.\n * After all conditions are met, the update process will run start again as usual, so update will be installed after a backgrounding or killing the app.\n *\n * @example\n * // Delay 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 * // Delay 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 * // Delay 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>;\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>;\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>;\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>;\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>;\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>;\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>;\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>;\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>;\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 * The version code/name of this bundle/version\n */\n version: string;\n /**\n * The session key for the update\n * @since 4.0.0\n * @default undefined\n */\n sessionKey?: string;\n /**\n * The checksum for the update\n * @since 4.0.0\n * @default undefined\n */\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"]}
|
|
@@ -169,7 +169,6 @@ enum CustomError: Error {
|
|
|
169
169
|
case cannotUnflat
|
|
170
170
|
case cannotCreateDirectory
|
|
171
171
|
case cannotDeleteDirectory
|
|
172
|
-
case cannotDecryptSessionKey
|
|
173
172
|
|
|
174
173
|
// Throw in all other cases
|
|
175
174
|
case unexpected(code: Int)
|
|
@@ -206,18 +205,13 @@ extension CustomError: LocalizedError {
|
|
|
206
205
|
case .cannotDecode:
|
|
207
206
|
return NSLocalizedString(
|
|
208
207
|
"Decoding the zip failed with this key",
|
|
209
|
-
comment: "Invalid
|
|
208
|
+
comment: "Invalid private key"
|
|
210
209
|
)
|
|
211
210
|
case .cannotWrite:
|
|
212
211
|
return NSLocalizedString(
|
|
213
212
|
"Cannot write to the destination",
|
|
214
213
|
comment: "Invalid destination"
|
|
215
214
|
)
|
|
216
|
-
case .cannotDecryptSessionKey:
|
|
217
|
-
return NSLocalizedString(
|
|
218
|
-
"Decrypting the session key failed",
|
|
219
|
-
comment: "Invalid session key"
|
|
220
|
-
)
|
|
221
215
|
}
|
|
222
216
|
}
|
|
223
217
|
}
|
|
@@ -245,8 +239,7 @@ extension CustomError: LocalizedError {
|
|
|
245
239
|
public var defaultChannel: String = ""
|
|
246
240
|
public var appId: String = ""
|
|
247
241
|
public var deviceID = UIDevice.current.identifierForVendor?.uuidString ?? ""
|
|
248
|
-
public var
|
|
249
|
-
public var hasOldPrivateKeyPropertyInConfig: Bool = false
|
|
242
|
+
public var privateKey: String = ""
|
|
250
243
|
|
|
251
244
|
public var notifyDownload: (String, Int) -> Void = { _, _ in }
|
|
252
245
|
|
|
@@ -371,37 +364,14 @@ extension CustomError: LocalizedError {
|
|
|
371
364
|
}
|
|
372
365
|
}
|
|
373
366
|
|
|
374
|
-
public func decryptChecksum(checksum: String, version: String) throws -> String {
|
|
375
|
-
if self.publicKey != nil && self.publicKey!.isEmpty {
|
|
376
|
-
print("\(self.TAG) Cannot find public key")
|
|
377
|
-
return checksum
|
|
378
|
-
}
|
|
379
|
-
do {
|
|
380
|
-
let checksumBytes: Data = Data(base64Encoded: checksum)!
|
|
381
|
-
guard let rsaPublicKey: RSAPublicKey = .load(rsaPublicKey: self.publicKey!) else {
|
|
382
|
-
print("cannot decode publicKey", self.publicKey!)
|
|
383
|
-
throw CustomError.cannotDecode
|
|
384
|
-
}
|
|
385
|
-
guard let decryptedChecksum = try? rsaPublicKey.decrypt(data: checksumBytes) else {
|
|
386
|
-
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
387
|
-
}
|
|
388
|
-
return decryptedChecksum.base64EncodedString()
|
|
389
|
-
} catch {
|
|
390
|
-
print("\(self.TAG) Cannot decrypt checksum: \(checksum)", error)
|
|
391
|
-
self.sendStats(action: "decrypt_fail", versionName: version)
|
|
392
|
-
throw CustomError.cannotDecode
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
367
|
private func decryptFile(filePath: URL, sessionKey: String, version: String) throws {
|
|
397
|
-
if self.
|
|
398
|
-
print("\(self.TAG) Cannot
|
|
368
|
+
if self.privateKey.isEmpty || sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
369
|
+
print("\(self.TAG) Cannot found privateKey or sessionKey")
|
|
399
370
|
return
|
|
400
371
|
}
|
|
401
|
-
|
|
402
372
|
do {
|
|
403
|
-
guard let
|
|
404
|
-
print("cannot decode
|
|
373
|
+
guard let rsaPrivateKey: RSAPrivateKey = .load(rsaPrivateKey: self.privateKey) else {
|
|
374
|
+
print("cannot decode privateKey", self.privateKey)
|
|
405
375
|
throw CustomError.cannotDecode
|
|
406
376
|
}
|
|
407
377
|
|
|
@@ -411,25 +381,21 @@ extension CustomError: LocalizedError {
|
|
|
411
381
|
throw CustomError.cannotDecode
|
|
412
382
|
}
|
|
413
383
|
|
|
414
|
-
// guard let base64EncodedData = sessionKeyArray[1].data(using: .utf8)! else {
|
|
415
|
-
// throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
416
|
-
// }
|
|
417
|
-
|
|
418
384
|
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
419
385
|
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
420
386
|
}
|
|
421
387
|
|
|
422
|
-
guard let sessionKeyDataDecrypted =
|
|
388
|
+
guard let sessionKeyDataDecrypted = rsaPrivateKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
423
389
|
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
424
390
|
}
|
|
425
391
|
|
|
426
|
-
let
|
|
392
|
+
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted)
|
|
427
393
|
|
|
428
394
|
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
429
395
|
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
430
396
|
}
|
|
431
397
|
|
|
432
|
-
guard let decryptedData =
|
|
398
|
+
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
433
399
|
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
434
400
|
}
|
|
435
401
|
|
|
@@ -614,12 +580,7 @@ extension CustomError: LocalizedError {
|
|
|
614
580
|
case .success:
|
|
615
581
|
self.notifyDownload(id, 71)
|
|
616
582
|
do {
|
|
617
|
-
if self.hasOldPrivateKeyPropertyInConfig {
|
|
618
|
-
print("\(self.TAG) There is still an privateKey property in the config")
|
|
619
|
-
}
|
|
620
|
-
|
|
621
583
|
try self.decryptFile(filePath: fileURL, sessionKey: sessionKey, version: version)
|
|
622
|
-
let checksumDecrypted = try self.decryptChecksum(checksum: checksum, version: version)
|
|
623
584
|
checksum = self.getChecksum(filePath: fileURL)
|
|
624
585
|
try self.saveDownloaded(sourceZip: fileURL, id: id, base: self.libraryDir.appendingPathComponent(self.bundleDirectory), notify: true)
|
|
625
586
|
try self.deleteFolder(source: fileURL)
|
|
@@ -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 = "6.0.
|
|
18
|
+
private let PLUGIN_VERSION: String = "6.0.52"
|
|
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 defaultPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpQIBAAKCAQEA4pW9olT0FBXXivRCzd3xcImlWZrqkwcF2xTkX/FwXmj9eh9H\nkBLrsQmfsC+PJisRXIOGq6a0z3bsGq6jBpp3/Jr9jiaW5VuPGaKeMaZZBRvi/N5f\nIMG3hZXSOcy0IYg+E1Q7RkYO1xq5GLHseqG+PXvJsNe4R8R/Bmd/ngq0xh/cvcrH\nHpXwO0Aj9tfprlb+rHaVV79EkVRWYPidOLnK1n0EFHFJ1d/MyDIp10TEGm2xHpf/\nBrlb1an8wXEuzoC0DgYaczgTjovwR+ewSGhSHJliQdM0Qa3o1iN87DldWtydImMs\nPjJ3DUwpsjAMRe5X8Et4+udFW2ciYnQo9H0CkwIDAQABAoIBAQCtjlMV/4qBxAU4\nu0ZcWA9yywwraX0aJ3v1xrfzQYV322Wk4Ea5dbSxA5UcqCE29DA1M824t1Wxv/6z\npWbcTP9xLuresnJMtmgTE7umfiubvTONy2sENT20hgDkIwcq1CfwOEm61zjQzPhQ\nkSB5AmEsyR/BZEsUNc+ygR6AWOUFB7tj4yMc32LOTWSbE/znnF2BkmlmnQykomG1\n2oVqM3lUFP7+m8ux1O7scO6IMts+Z/eFXjWfxpbebUSvSIR83GXPQZ34S/c0ehOg\nyHdmCSOel1r3VvInMe+30j54Jr+Ml/7Ee6axiwyE2e/bd85MsK9sVdp0OtelXaqA\nOZZqWvN5AoGBAP2Hn3lSq+a8GsDH726mHJw60xM0LPbVJTYbXsmQkg1tl3NKJTMM\nQqz41+5uys+phEgLHI9gVJ0r+HaGHXnJ4zewlFjsudstb/0nfctUvTqnhEhfNo9I\ny4kufVKPRF3sMEeo7CDVJs4GNBLycEyIBy6Mbv0VcO7VaZqggRwu4no9AoGBAOTK\n6NWYs1BWlkua2wmxexGOzehNGedInp0wGr2l4FDayWjkZLqvB+nNXUQ63NdHlSs4\nWB2Z1kQXZxVaI2tPYexGUKXEo2uFob63uflbuE029ovDXIIPFTPtGNdNXwhHT5a+\nPhmy3sMc+s2BSNM5qaNmfxQxhdd6gRU6oikE+c0PAoGAMn3cKNFqIt27hkFLUgIL\nGKIuf1iYy9/PNWNmEUaVj88PpopRtkTu0nwMpROzmH/uNFriKTvKHjMvnItBO4wV\nkHW+VadvrFL0Rrqituf9d7z8/1zXBNo+juePVe3qc7oiM2NVA4Tv4YAixtM5wkQl\nCgQ15nlqsGYYTg9BJ1e/CxECgYEAjEYPzO2reuUrjr0p8F59ev1YJ0YmTJRMk0ks\nC/yIdGo/tGzbiU3JB0LfHPcN8Xu07GPGOpfYM7U5gXDbaG6qNgfCaHAQVdr/mQPi\nJQ1kCQtay8QCkscWk9iZM1//lP7LwDtxraXqSCwbZSYP9VlUNZeg8EuQqNU2EUL6\nqzWexmcCgYEA0prUGNBacraTYEknB1CsbP36UPWsqFWOvevlz+uEC5JPxPuW5ZHh\nSQN7xl6+PHyjPBM7ttwPKyhgLOVTb3K7ex/PXnudojMUK5fh7vYfChVTSlx2p6r0\nDi58PdD+node08cJH+ie0Yphp7m+D4+R9XD0v0nEvnu4BtAW6DrJasw=\n-----END RSA PRIVATE 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
|
|
@@ -67,11 +67,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
67
67
|
periodCheckDelay = periodCheckDelayValue
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
implementation.
|
|
71
|
-
implementation.hasOldPrivateKeyPropertyInConfig = false
|
|
72
|
-
if getConfig().getString("privateKey") != nil && !getConfig().getString("privateKey")!.isEmpty {
|
|
73
|
-
implementation.hasOldPrivateKeyPropertyInConfig = true
|
|
74
|
-
}
|
|
70
|
+
implementation.privateKey = getConfig().getString("privateKey", self.defaultPrivateKey)!
|
|
75
71
|
implementation.notifyDownload = notifyDownload
|
|
76
72
|
implementation.PLUGIN_VERSION = self.PLUGIN_VERSION
|
|
77
73
|
let config = (self.bridge?.viewController as? CAPBridgeViewController)?.instanceDescriptor().legacyConfig
|
|
@@ -244,13 +240,12 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
244
240
|
return
|
|
245
241
|
}
|
|
246
242
|
let sessionKey = call.getString("sessionKey", "")
|
|
247
|
-
|
|
243
|
+
let checksum = call.getString("checksum", "")
|
|
248
244
|
let url = URL(string: urlString)
|
|
249
245
|
print("\(self.implementation.TAG) Downloading \(String(describing: url))")
|
|
250
246
|
DispatchQueue.global(qos: .background).async {
|
|
251
247
|
do {
|
|
252
248
|
let next = try self.implementation.download(url: url!, version: version, sessionKey: sessionKey)
|
|
253
|
-
checksum = try self.implementation.decryptChecksum(checksum: checksum, version: version)
|
|
254
249
|
if checksum != "" && next.getChecksum() != checksum {
|
|
255
250
|
print("\(self.implementation.TAG) Error checksum", next.getChecksum(), checksum)
|
|
256
251
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -529,13 +524,15 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
529
524
|
if !killed {
|
|
530
525
|
self._cancelDelay(source: "background check")
|
|
531
526
|
}
|
|
532
|
-
|
|
527
|
+
break
|
|
528
|
+
case "kill":
|
|
533
529
|
if killed {
|
|
534
530
|
self._cancelDelay(source: "kill check")
|
|
535
531
|
// instant install for kill action
|
|
536
532
|
self.installNext()
|
|
537
533
|
}
|
|
538
|
-
|
|
534
|
+
break
|
|
535
|
+
case "date":
|
|
539
536
|
if value != nil && value != "" {
|
|
540
537
|
let dateFormatter = ISO8601DateFormatter()
|
|
541
538
|
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
|
@@ -549,7 +546,8 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
549
546
|
} else {
|
|
550
547
|
self._cancelDelay(source: "delayVal absent")
|
|
551
548
|
}
|
|
552
|
-
|
|
549
|
+
break
|
|
550
|
+
case "nativeVersion":
|
|
553
551
|
if value != nil && value != "" {
|
|
554
552
|
do {
|
|
555
553
|
let versionLimit = try Version(value!)
|
|
@@ -562,6 +560,7 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
562
560
|
} else {
|
|
563
561
|
self._cancelDelay(source: "delayVal absent")
|
|
564
562
|
}
|
|
563
|
+
break
|
|
565
564
|
case .none:
|
|
566
565
|
print("\(self.implementation.TAG) _checkCancelDelay switch case none error")
|
|
567
566
|
case .some:
|
|
@@ -713,7 +712,6 @@ public class CapacitorUpdaterPlugin: CAPPlugin {
|
|
|
713
712
|
self.endBackGroundTaskWithNotif(msg: "Latest version is in error state. Aborting update.", latestVersionName: latestVersionName, current: current)
|
|
714
713
|
return
|
|
715
714
|
}
|
|
716
|
-
res.checksum = try self.implementation.decryptChecksum(checksum: res.checksum, version: latestVersionName)
|
|
717
715
|
if res.checksum != "" && next.getChecksum() != res.checksum {
|
|
718
716
|
print("\(self.implementation.TAG) Error checksum", next.getChecksum(), res.checksum)
|
|
719
717
|
self.implementation.sendStats(action: "checksum_fail", versionName: next.getVersionName())
|
|
@@ -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 = .rsaEncryptionOAEPSHA256
|
|
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 key to decrypt it. Will call `CCCrypt` in CommonCrypto
|
|
35
|
+
/// Takes the data and uses the private 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,36 +67,73 @@ public struct AES128Key {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
///
|
|
70
|
-
/// The RSA public key.
|
|
70
|
+
/// The RSA keypair. Includes both private and public key.
|
|
71
71
|
///
|
|
72
|
-
public struct
|
|
72
|
+
public struct RSAKeyPair {
|
|
73
|
+
private let privateKey: SecKey
|
|
73
74
|
private let publicKey: SecKey
|
|
74
75
|
|
|
75
76
|
#if DEBUG
|
|
77
|
+
public var __debug_privateKey: SecKey { self.privateKey }
|
|
76
78
|
public var __debug_publicKey: SecKey { self.publicKey }
|
|
77
79
|
#endif
|
|
78
80
|
|
|
79
|
-
fileprivate init(publicKey: SecKey) {
|
|
81
|
+
fileprivate init(privateKey: SecKey, publicKey: SecKey) {
|
|
82
|
+
self.privateKey = privateKey
|
|
80
83
|
self.publicKey = publicKey
|
|
81
84
|
}
|
|
82
85
|
|
|
86
|
+
public func extractPublicKey() -> RSAPublicKey {
|
|
87
|
+
RSAPublicKey(publicKey: publicKey)
|
|
88
|
+
}
|
|
89
|
+
|
|
83
90
|
///
|
|
84
|
-
/// Takes the data and uses the
|
|
91
|
+
/// Takes the data and uses the private key to decrypt it.
|
|
85
92
|
/// Returns the decrypted data.
|
|
86
93
|
///
|
|
87
94
|
public func decrypt(data: Data) -> Data? {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
95
|
+
var error: Unmanaged<CFError>?
|
|
96
|
+
if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
97
|
+
if error != nil {
|
|
98
|
+
return nil
|
|
99
|
+
} else {
|
|
100
|
+
return decryptedData as Data
|
|
91
101
|
}
|
|
92
|
-
|
|
93
|
-
return decryptedData
|
|
94
|
-
} catch {
|
|
95
|
-
print("Error decrypting data: \(error)")
|
|
102
|
+
} else {
|
|
96
103
|
return nil
|
|
97
104
|
}
|
|
98
105
|
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
///
|
|
109
|
+
/// The RSA public key.
|
|
110
|
+
///
|
|
111
|
+
public struct RSAPublicKey {
|
|
112
|
+
private let publicKey: SecKey
|
|
113
|
+
|
|
114
|
+
#if DEBUG
|
|
115
|
+
public var __debug_publicKey: SecKey { self.publicKey }
|
|
116
|
+
#endif
|
|
99
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 {
|
|
134
|
+
return nil
|
|
135
|
+
}
|
|
136
|
+
}
|
|
100
137
|
///
|
|
101
138
|
/// Allows you to export the RSA public key to a format (so you can send over the net).
|
|
102
139
|
///
|
|
@@ -108,87 +145,72 @@ public struct RSAPublicKey {
|
|
|
108
145
|
///
|
|
109
146
|
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
110
147
|
///
|
|
111
|
-
public static func load(
|
|
112
|
-
|
|
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
|
-
|
|
148
|
+
public static func load(rsaPublicKeyData: Data) -> RSAPublicKey? {
|
|
149
|
+
if let publicKey: SecKey = .loadPublicFromData(rsaPublicKeyData) {
|
|
126
150
|
return RSAPublicKey(publicKey: publicKey)
|
|
127
|
-
}
|
|
128
|
-
print("Error load RSA: \(error)")
|
|
151
|
+
} else {
|
|
129
152
|
return nil
|
|
130
153
|
}
|
|
131
154
|
}
|
|
155
|
+
}
|
|
156
|
+
///
|
|
157
|
+
/// The RSA public key.
|
|
158
|
+
///
|
|
159
|
+
public struct RSAPrivateKey {
|
|
160
|
+
private let privateKey: SecKey
|
|
132
161
|
|
|
133
|
-
|
|
134
|
-
public
|
|
135
|
-
|
|
136
|
-
let dataSize = encryptedData.count / MemoryLayout<UInt8>.size
|
|
137
|
-
|
|
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]
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
var decryptedDataBuffer = [UInt8](repeating: 0, count: blockSize)
|
|
154
|
-
var decryptedDataLength = blockSize
|
|
162
|
+
#if DEBUG
|
|
163
|
+
public var __debug_privateKey: SecKey { self.privateKey }
|
|
164
|
+
#endif
|
|
155
165
|
|
|
156
|
-
|
|
157
|
-
|
|
166
|
+
fileprivate init(privateKey: SecKey) {
|
|
167
|
+
self.privateKey = privateKey
|
|
168
|
+
}
|
|
169
|
+
///
|
|
170
|
+
/// Takes the data and uses the private key to decrypt it.
|
|
171
|
+
/// Returns the decrypted data.
|
|
172
|
+
///
|
|
173
|
+
public func decrypt(data: Data) -> Data? {
|
|
174
|
+
var error: Unmanaged<CFError>?
|
|
175
|
+
if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
176
|
+
if error != nil {
|
|
158
177
|
return nil
|
|
178
|
+
} else {
|
|
179
|
+
return decryptedData as Data
|
|
159
180
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
idx += blockSize
|
|
181
|
+
} else {
|
|
182
|
+
return nil
|
|
164
183
|
}
|
|
184
|
+
}
|
|
165
185
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
///
|
|
187
|
+
/// Allows you to export the RSA public key to a format (so you can send over the net).
|
|
188
|
+
///
|
|
189
|
+
public func export() -> Data? {
|
|
190
|
+
return privateKey.exportToData()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
///
|
|
194
|
+
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
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
|
|
181
205
|
}
|
|
206
|
+
guard let privateKey: SecKey = .loadPrivateFromData(rsaPrivateKeyData) else {
|
|
207
|
+
throw CustomError.cannotDecode
|
|
208
|
+
}
|
|
209
|
+
return RSAPrivateKey(privateKey: privateKey)
|
|
210
|
+
} catch {
|
|
211
|
+
print("Error load RSA: \(error)")
|
|
212
|
+
return nil
|
|
182
213
|
}
|
|
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
|
|
192
214
|
}
|
|
193
215
|
}
|
|
194
216
|
|
|
@@ -211,6 +233,14 @@ fileprivate extension SecKey {
|
|
|
211
233
|
kSecAttrKeyClass: kSecAttrKeyClassPublic,
|
|
212
234
|
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
213
235
|
]
|
|
214
|
-
return SecKeyCreateWithData(data as
|
|
236
|
+
return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
|
|
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)
|
|
215
245
|
}
|
|
216
246
|
}
|