@capgo/capacitor-updater 6.30.0 → 6.34.0
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 +98 -12
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +75 -37
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +49 -112
- package/android/src/main/java/ee/forgr/capacitor_updater/{CryptoCipherV2.java → CryptoCipher.java} +80 -8
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +8 -4
- package/dist/docs.json +81 -8
- package/dist/esm/definitions.d.ts +87 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +29 -9
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +38 -65
- package/ios/Sources/CapacitorUpdaterPlugin/{CryptoCipherV2.swift → CryptoCipher.swift} +49 -3
- package/package.json +1 -1
- package/android/src/main/java/ee/forgr/capacitor_updater/CryptoCipherV1.java +0 -222
- package/ios/Sources/CapacitorUpdaterPlugin/CryptoCipherV1.swift +0 -245
|
@@ -8,8 +8,7 @@ import Foundation
|
|
|
8
8
|
import CryptoKit
|
|
9
9
|
import BigInt
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
public struct CryptoCipherV2 {
|
|
11
|
+
public struct CryptoCipher {
|
|
13
12
|
private static var logger: Logger!
|
|
14
13
|
|
|
15
14
|
public static func setLogger(_ logger: Logger) {
|
|
@@ -42,6 +41,7 @@ public struct CryptoCipherV2 {
|
|
|
42
41
|
// Determine if input is hex or base64 encoded
|
|
43
42
|
// Hex strings only contain 0-9 and a-f, while base64 contains other characters
|
|
44
43
|
let checksumBytes: Data
|
|
44
|
+
let detectedFormat: String
|
|
45
45
|
if isHexString(checksum) {
|
|
46
46
|
// Hex encoded (new format from CLI for plugin versions >= 5.30.0, 6.30.0, 7.30.0)
|
|
47
47
|
guard let hexData = hexStringToData(checksum) else {
|
|
@@ -49,6 +49,7 @@ public struct CryptoCipherV2 {
|
|
|
49
49
|
throw CustomError.cannotDecode
|
|
50
50
|
}
|
|
51
51
|
checksumBytes = hexData
|
|
52
|
+
detectedFormat = "hex"
|
|
52
53
|
} else {
|
|
53
54
|
// TODO: remove backwards compatibility
|
|
54
55
|
// Base64 encoded (old format for backwards compatibility)
|
|
@@ -57,7 +58,9 @@ public struct CryptoCipherV2 {
|
|
|
57
58
|
throw CustomError.cannotDecode
|
|
58
59
|
}
|
|
59
60
|
checksumBytes = base64Data
|
|
61
|
+
detectedFormat = "base64"
|
|
60
62
|
}
|
|
63
|
+
logger.debug("Received encrypted checksum format: \(detectedFormat) (length: \(checksum.count) chars, \(checksumBytes.count) bytes)")
|
|
61
64
|
|
|
62
65
|
if checksumBytes.isEmpty {
|
|
63
66
|
logger.error("Decoded checksum is empty")
|
|
@@ -75,12 +78,55 @@ public struct CryptoCipherV2 {
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
// Return as hex string to match calcChecksum output format
|
|
78
|
-
|
|
81
|
+
let result = decryptedChecksum.map { String(format: "%02x", $0) }.joined()
|
|
82
|
+
|
|
83
|
+
// Detect checksum algorithm based on length
|
|
84
|
+
let detectedAlgorithm: String
|
|
85
|
+
if decryptedChecksum.count == 32 {
|
|
86
|
+
detectedAlgorithm = "SHA-256"
|
|
87
|
+
} else if decryptedChecksum.count == 4 {
|
|
88
|
+
detectedAlgorithm = "CRC32 (deprecated)"
|
|
89
|
+
logger.error("CRC32 checksum detected. This algorithm is deprecated and no longer supported. Please update your CLI to use SHA-256 checksums.")
|
|
90
|
+
} else {
|
|
91
|
+
detectedAlgorithm = "unknown (\(decryptedChecksum.count) bytes)"
|
|
92
|
+
logger.error("Unknown checksum algorithm detected with \(decryptedChecksum.count) bytes. Expected SHA-256 (32 bytes).")
|
|
93
|
+
}
|
|
94
|
+
logger.debug("Decrypted checksum: \(detectedAlgorithm) hex format (length: \(result.count) chars, \(decryptedChecksum.count) bytes)")
|
|
95
|
+
return result
|
|
79
96
|
} catch {
|
|
80
97
|
logger.error("decryptChecksum fail: \(error.localizedDescription)")
|
|
81
98
|
throw CustomError.cannotDecode
|
|
82
99
|
}
|
|
83
100
|
}
|
|
101
|
+
|
|
102
|
+
/// Detect checksum algorithm based on hex string length.
|
|
103
|
+
/// SHA-256 = 64 hex chars (32 bytes)
|
|
104
|
+
/// CRC32 = 8 hex chars (4 bytes)
|
|
105
|
+
public static func detectChecksumAlgorithm(_ hexChecksum: String) -> String {
|
|
106
|
+
if hexChecksum.isEmpty {
|
|
107
|
+
return "empty"
|
|
108
|
+
}
|
|
109
|
+
let len = hexChecksum.count
|
|
110
|
+
if len == 64 {
|
|
111
|
+
return "SHA-256"
|
|
112
|
+
} else if len == 8 {
|
|
113
|
+
return "CRC32 (deprecated)"
|
|
114
|
+
} else {
|
|
115
|
+
return "unknown (\(len) hex chars)"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Log checksum info and warn if deprecated algorithm detected.
|
|
120
|
+
public static func logChecksumInfo(label: String, hexChecksum: String) {
|
|
121
|
+
let algorithm = detectChecksumAlgorithm(hexChecksum)
|
|
122
|
+
logger.debug("\(label): \(algorithm) hex format (length: \(hexChecksum.count) chars)")
|
|
123
|
+
if algorithm.contains("CRC32") {
|
|
124
|
+
logger.error("CRC32 checksum detected. This algorithm is deprecated and no longer supported. Please update your CLI to use SHA-256 checksums.")
|
|
125
|
+
} else if algorithm.contains("unknown") {
|
|
126
|
+
logger.error("Unknown checksum algorithm detected. Expected SHA-256 (64 hex chars) but got \(hexChecksum.count) chars.")
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
84
130
|
public static func calcChecksum(filePath: URL) -> String {
|
|
85
131
|
let bufferSize = 1024 * 1024 * 5 // 5 MB
|
|
86
132
|
var sha256 = SHA256()
|
package/package.json
CHANGED
|
@@ -1,222 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
package ee.forgr.capacitor_updater;
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Created by Awesometic
|
|
11
|
-
* It's encrypt returns Base64 encoded, and also decrypt for Base64 encoded cipher
|
|
12
|
-
* references: http://stackoverflow.com/questions/12471999/rsa-encryption-decryption-in-android
|
|
13
|
-
*
|
|
14
|
-
* V1 Encryption - uses privateKey (deprecated but kept for backwards compatibility)
|
|
15
|
-
*/
|
|
16
|
-
import android.util.Base64;
|
|
17
|
-
import android.util.Log;
|
|
18
|
-
import java.io.BufferedInputStream;
|
|
19
|
-
import java.io.DataInputStream;
|
|
20
|
-
import java.io.File;
|
|
21
|
-
import java.io.FileInputStream;
|
|
22
|
-
import java.io.FileOutputStream;
|
|
23
|
-
import java.io.IOException;
|
|
24
|
-
import java.security.GeneralSecurityException;
|
|
25
|
-
import java.security.InvalidAlgorithmParameterException;
|
|
26
|
-
import java.security.InvalidKeyException;
|
|
27
|
-
import java.security.KeyFactory;
|
|
28
|
-
import java.security.NoSuchAlgorithmException;
|
|
29
|
-
import java.security.PrivateKey;
|
|
30
|
-
import java.security.PublicKey;
|
|
31
|
-
import java.security.spec.InvalidKeySpecException;
|
|
32
|
-
import java.security.spec.MGF1ParameterSpec;
|
|
33
|
-
import java.security.spec.PKCS8EncodedKeySpec;
|
|
34
|
-
import java.security.spec.X509EncodedKeySpec;
|
|
35
|
-
import java.util.zip.CRC32;
|
|
36
|
-
import javax.crypto.BadPaddingException;
|
|
37
|
-
import javax.crypto.Cipher;
|
|
38
|
-
import javax.crypto.IllegalBlockSizeException;
|
|
39
|
-
import javax.crypto.NoSuchPaddingException;
|
|
40
|
-
import javax.crypto.SecretKey;
|
|
41
|
-
import javax.crypto.spec.IvParameterSpec;
|
|
42
|
-
import javax.crypto.spec.OAEPParameterSpec;
|
|
43
|
-
import javax.crypto.spec.PSource;
|
|
44
|
-
import javax.crypto.spec.SecretKeySpec;
|
|
45
|
-
|
|
46
|
-
public class CryptoCipherV1 {
|
|
47
|
-
|
|
48
|
-
private static Logger logger;
|
|
49
|
-
|
|
50
|
-
public static void setLogger(Logger loggerInstance) {
|
|
51
|
-
logger = loggerInstance;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
public static byte[] decryptRSA(byte[] source, PrivateKey privateKey)
|
|
55
|
-
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
|
56
|
-
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
|
|
57
|
-
OAEPParameterSpec oaepParams = new OAEPParameterSpec(
|
|
58
|
-
"SHA-256",
|
|
59
|
-
"MGF1",
|
|
60
|
-
new MGF1ParameterSpec("SHA-256"),
|
|
61
|
-
PSource.PSpecified.DEFAULT
|
|
62
|
-
);
|
|
63
|
-
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
|
|
64
|
-
return cipher.doFinal(source);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
public static byte[] decryptAES(byte[] cipherText, SecretKey key, byte[] iv) {
|
|
68
|
-
try {
|
|
69
|
-
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
|
|
70
|
-
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
71
|
-
SecretKeySpec keySpec = new SecretKeySpec(key.getEncoded(), "AES");
|
|
72
|
-
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivParameterSpec);
|
|
73
|
-
return cipher.doFinal(cipherText);
|
|
74
|
-
} catch (Exception e) {
|
|
75
|
-
e.printStackTrace();
|
|
76
|
-
}
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
public static SecretKey byteToSessionKey(byte[] sessionKey) {
|
|
81
|
-
// rebuild key using SecretKeySpec
|
|
82
|
-
return new SecretKeySpec(sessionKey, 0, sessionKey.length, "AES");
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
private static PrivateKey readPkcs8PrivateKey(byte[] pkcs8Bytes) throws GeneralSecurityException {
|
|
86
|
-
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
|
87
|
-
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);
|
|
88
|
-
try {
|
|
89
|
-
return keyFactory.generatePrivate(keySpec);
|
|
90
|
-
} catch (InvalidKeySpecException e) {
|
|
91
|
-
throw new IllegalArgumentException("Unexpected key format!", e);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
private static byte[] join(byte[] byteArray1, byte[] byteArray2) {
|
|
96
|
-
byte[] bytes = new byte[byteArray1.length + byteArray2.length];
|
|
97
|
-
System.arraycopy(byteArray1, 0, bytes, 0, byteArray1.length);
|
|
98
|
-
System.arraycopy(byteArray2, 0, bytes, byteArray1.length, byteArray2.length);
|
|
99
|
-
return bytes;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private static PrivateKey readPkcs1PrivateKey(byte[] pkcs1Bytes) throws GeneralSecurityException {
|
|
103
|
-
// We can't use Java internal APIs to parse ASN.1 structures, so we build a PKCS#8 key Java can understand
|
|
104
|
-
int pkcs1Length = pkcs1Bytes.length;
|
|
105
|
-
int totalLength = pkcs1Length + 22;
|
|
106
|
-
byte[] pkcs8Header = new byte[] {
|
|
107
|
-
0x30,
|
|
108
|
-
(byte) 0x82,
|
|
109
|
-
(byte) ((totalLength >> 8) & 0xff),
|
|
110
|
-
(byte) (totalLength & 0xff), // Sequence + total length
|
|
111
|
-
0x2,
|
|
112
|
-
0x1,
|
|
113
|
-
0x0, // Integer (0)
|
|
114
|
-
0x30,
|
|
115
|
-
0xD,
|
|
116
|
-
0x6,
|
|
117
|
-
0x9,
|
|
118
|
-
0x2A,
|
|
119
|
-
(byte) 0x86,
|
|
120
|
-
0x48,
|
|
121
|
-
(byte) 0x86,
|
|
122
|
-
(byte) 0xF7,
|
|
123
|
-
0xD,
|
|
124
|
-
0x1,
|
|
125
|
-
0x1,
|
|
126
|
-
0x1,
|
|
127
|
-
0x5,
|
|
128
|
-
0x0, // Sequence: 1.2.840.113549.1.1.1, NULL
|
|
129
|
-
0x4,
|
|
130
|
-
(byte) 0x82,
|
|
131
|
-
(byte) ((pkcs1Length >> 8) & 0xff),
|
|
132
|
-
(byte) (pkcs1Length & 0xff) // Octet string + length
|
|
133
|
-
};
|
|
134
|
-
byte[] pkcs8bytes = join(pkcs8Header, pkcs1Bytes);
|
|
135
|
-
return readPkcs8PrivateKey(pkcs8bytes);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public static PrivateKey stringToPrivateKey(String private_key) throws GeneralSecurityException {
|
|
139
|
-
// Base64 decode the result
|
|
140
|
-
|
|
141
|
-
String pkcs1Pem = private_key;
|
|
142
|
-
pkcs1Pem = pkcs1Pem.replace("-----BEGIN RSA PRIVATE KEY-----", "");
|
|
143
|
-
pkcs1Pem = pkcs1Pem.replace("-----END RSA PRIVATE KEY-----", "");
|
|
144
|
-
pkcs1Pem = pkcs1Pem.replace("\\n", "");
|
|
145
|
-
pkcs1Pem = pkcs1Pem.replace(" ", "");
|
|
146
|
-
|
|
147
|
-
byte[] pkcs1EncodedBytes = Base64.decode(pkcs1Pem.getBytes(), Base64.DEFAULT);
|
|
148
|
-
// extract the private key
|
|
149
|
-
return readPkcs1PrivateKey(pkcs1EncodedBytes);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
public static void decryptFile(final File file, final String privateKey, final String ivSessionKey, final String version)
|
|
153
|
-
throws IOException {
|
|
154
|
-
// (str != null && !str.isEmpty())
|
|
155
|
-
if (privateKey == null || privateKey.isEmpty()) {
|
|
156
|
-
Log.i("[Capacitor-updater]", "Cannot found privateKey");
|
|
157
|
-
return;
|
|
158
|
-
} else if (ivSessionKey == null || ivSessionKey.isEmpty() || ivSessionKey.split(":").length != 2) {
|
|
159
|
-
Log.i("[Capacitor-updater]", "Cannot found sessionKey");
|
|
160
|
-
return;
|
|
161
|
-
}
|
|
162
|
-
try {
|
|
163
|
-
String ivB64 = ivSessionKey.split(":")[0];
|
|
164
|
-
String sessionKeyB64 = ivSessionKey.split(":")[1];
|
|
165
|
-
byte[] iv = Base64.decode(ivB64.getBytes(), Base64.DEFAULT);
|
|
166
|
-
byte[] sessionKey = Base64.decode(sessionKeyB64.getBytes(), Base64.DEFAULT);
|
|
167
|
-
PrivateKey pKey = CryptoCipherV1.stringToPrivateKey(privateKey);
|
|
168
|
-
byte[] decryptedSessionKey = CryptoCipherV1.decryptRSA(sessionKey, pKey);
|
|
169
|
-
SecretKey sKey = CryptoCipherV1.byteToSessionKey(decryptedSessionKey);
|
|
170
|
-
byte[] content = new byte[(int) file.length()];
|
|
171
|
-
|
|
172
|
-
try (
|
|
173
|
-
final FileInputStream fis = new FileInputStream(file);
|
|
174
|
-
final BufferedInputStream bis = new BufferedInputStream(fis);
|
|
175
|
-
final DataInputStream dis = new DataInputStream(bis)
|
|
176
|
-
) {
|
|
177
|
-
dis.readFully(content);
|
|
178
|
-
dis.close();
|
|
179
|
-
byte[] decrypted = CryptoCipherV1.decryptAES(content, sKey, iv);
|
|
180
|
-
// write the decrypted string to the file
|
|
181
|
-
try (final FileOutputStream fos = new FileOutputStream(file.getAbsolutePath())) {
|
|
182
|
-
fos.write(decrypted);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
} catch (GeneralSecurityException e) {
|
|
186
|
-
Log.i("[Capacitor-updater]", "decryptFile fail");
|
|
187
|
-
e.printStackTrace();
|
|
188
|
-
throw new IOException("GeneralSecurityException");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
public static String calcChecksum(File file) {
|
|
193
|
-
final int BUFFER_SIZE = 1024 * 1024 * 5; // 5 MB buffer size
|
|
194
|
-
CRC32 crc = new CRC32();
|
|
195
|
-
|
|
196
|
-
try (FileInputStream fis = new FileInputStream(file)) {
|
|
197
|
-
byte[] buffer = new byte[BUFFER_SIZE];
|
|
198
|
-
int length;
|
|
199
|
-
while ((length = fis.read(buffer)) != -1) {
|
|
200
|
-
crc.update(buffer, 0, length);
|
|
201
|
-
}
|
|
202
|
-
return String.format("%08x", crc.getValue());
|
|
203
|
-
} catch (IOException e) {
|
|
204
|
-
System.err.println("[Capacitor-updater]" + " Cannot calc checksum: " + file.getPath() + " " + e.getMessage());
|
|
205
|
-
return "";
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
public static PublicKey stringToPublicKey(String publicKey) {
|
|
210
|
-
byte[] encoded = Base64.decode(publicKey, Base64.DEFAULT);
|
|
211
|
-
|
|
212
|
-
KeyFactory keyFactory = null;
|
|
213
|
-
try {
|
|
214
|
-
keyFactory = KeyFactory.getInstance("RSA");
|
|
215
|
-
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
|
|
216
|
-
return keyFactory.generatePublic(keySpec);
|
|
217
|
-
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
|
|
218
|
-
Log.i("Capacitor-updater", "stringToPublicKey fail\nError:\n" + e.toString());
|
|
219
|
-
return null;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
}
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
3
|
-
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
-
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import Foundation
|
|
8
|
-
import CommonCrypto
|
|
9
|
-
import zlib
|
|
10
|
-
|
|
11
|
-
///
|
|
12
|
-
/// Constants
|
|
13
|
-
///
|
|
14
|
-
private enum CryptoCipherConstants {
|
|
15
|
-
static let rsaKeySizeInBits: NSNumber = 2048
|
|
16
|
-
static let aesAlgorithm: CCAlgorithm = CCAlgorithm(kCCAlgorithmAES)
|
|
17
|
-
static let aesOptions: CCOptions = CCOptions(kCCOptionPKCS7Padding)
|
|
18
|
-
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
19
|
-
}
|
|
20
|
-
///
|
|
21
|
-
/// The RSA keypair. Includes both private and public key.
|
|
22
|
-
///
|
|
23
|
-
public struct RSAKeyPair {
|
|
24
|
-
private let privateKey: SecKey
|
|
25
|
-
private let publicKey: SecKey
|
|
26
|
-
|
|
27
|
-
#if DEBUG
|
|
28
|
-
public var __debug_privateKey: SecKey { self.privateKey }
|
|
29
|
-
public var __debug_publicKey: SecKey { self.publicKey }
|
|
30
|
-
#endif
|
|
31
|
-
|
|
32
|
-
fileprivate init(privateKey: SecKey, publicKey: SecKey) {
|
|
33
|
-
self.privateKey = privateKey
|
|
34
|
-
self.publicKey = publicKey
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
///
|
|
38
|
-
/// Takes the data and uses the private key to decrypt it.
|
|
39
|
-
/// Returns the decrypted data.
|
|
40
|
-
///
|
|
41
|
-
public func decrypt(data: Data) -> Data? {
|
|
42
|
-
var error: Unmanaged<CFError>?
|
|
43
|
-
if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
44
|
-
if error != nil {
|
|
45
|
-
return nil
|
|
46
|
-
} else {
|
|
47
|
-
return decryptedData as Data
|
|
48
|
-
}
|
|
49
|
-
} else {
|
|
50
|
-
return nil
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
///
|
|
55
|
-
/// Takes the data and uses the public key to encrypt it.
|
|
56
|
-
/// Returns the encrypted data.
|
|
57
|
-
///
|
|
58
|
-
public func encrypt(data: Data) -> Data? {
|
|
59
|
-
var error: Unmanaged<CFError>?
|
|
60
|
-
if let encryptedData: CFData = SecKeyCreateEncryptedData(self.publicKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
61
|
-
if error != nil {
|
|
62
|
-
return nil
|
|
63
|
-
} else {
|
|
64
|
-
return encryptedData as Data
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
return nil
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
///
|
|
73
|
-
/// The RSA public key.
|
|
74
|
-
///
|
|
75
|
-
public struct RSAPrivateKey {
|
|
76
|
-
private let privateKey: SecKey
|
|
77
|
-
|
|
78
|
-
#if DEBUG
|
|
79
|
-
public var __debug_privateKey: SecKey { self.privateKey }
|
|
80
|
-
#endif
|
|
81
|
-
|
|
82
|
-
fileprivate init(privateKey: SecKey) {
|
|
83
|
-
self.privateKey = privateKey
|
|
84
|
-
}
|
|
85
|
-
///
|
|
86
|
-
/// Takes the data and uses the private key to decrypt it.
|
|
87
|
-
/// Returns the decrypted data.
|
|
88
|
-
///
|
|
89
|
-
public func decrypt(data: Data) -> Data? {
|
|
90
|
-
var error: Unmanaged<CFError>?
|
|
91
|
-
if let decryptedData: CFData = SecKeyCreateDecryptedData(self.privateKey, CryptoCipherConstants.rsaAlgorithm, data as CFData, &error) {
|
|
92
|
-
if error != nil {
|
|
93
|
-
return nil
|
|
94
|
-
} else {
|
|
95
|
-
return decryptedData as Data
|
|
96
|
-
}
|
|
97
|
-
} else {
|
|
98
|
-
return nil
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
///
|
|
103
|
-
/// Allows you to export the RSA public key to a format (so you can send over the net).
|
|
104
|
-
///
|
|
105
|
-
public func export() -> Data? {
|
|
106
|
-
return privateKey.exportToData()
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
///
|
|
110
|
-
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
111
|
-
///
|
|
112
|
-
public static func load(rsaPrivateKey: String) -> RSAPrivateKey? {
|
|
113
|
-
var privKey: String = rsaPrivateKey
|
|
114
|
-
privKey = privKey.replacingOccurrences(of: "-----BEGIN RSA PRIVATE KEY-----", with: "")
|
|
115
|
-
privKey = privKey.replacingOccurrences(of: "-----END RSA PRIVATE KEY-----", with: "")
|
|
116
|
-
privKey = privKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
117
|
-
privKey = privKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
118
|
-
do {
|
|
119
|
-
guard let rsaPrivateKeyData: Data = Data(base64Encoded: privKey) else {
|
|
120
|
-
throw CustomError.cannotDecode
|
|
121
|
-
}
|
|
122
|
-
guard let privateKey: SecKey = .loadPrivateFromData(rsaPrivateKeyData) else {
|
|
123
|
-
throw CustomError.cannotDecode
|
|
124
|
-
}
|
|
125
|
-
return RSAPrivateKey(privateKey: privateKey)
|
|
126
|
-
} catch {
|
|
127
|
-
print("Error load RSA: \(error)")
|
|
128
|
-
return nil
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
fileprivate extension SecKey {
|
|
134
|
-
func exportToData() -> Data? {
|
|
135
|
-
var error: Unmanaged<CFError>?
|
|
136
|
-
if let cfData: CFData = SecKeyCopyExternalRepresentation(self, &error) {
|
|
137
|
-
if error != nil {
|
|
138
|
-
return nil
|
|
139
|
-
} else {
|
|
140
|
-
return cfData as Data
|
|
141
|
-
}
|
|
142
|
-
} else {
|
|
143
|
-
return nil
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
static func loadPublicFromData(_ data: Data) -> SecKey? {
|
|
147
|
-
let keyDict: [NSObject: NSObject] = [
|
|
148
|
-
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
149
|
-
kSecAttrKeyClass: kSecAttrKeyClassPublic,
|
|
150
|
-
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
151
|
-
]
|
|
152
|
-
return SecKeyCreateWithData(data as NSData, keyDict as CFDictionary, nil)
|
|
153
|
-
}
|
|
154
|
-
static func loadPrivateFromData(_ data: Data) -> SecKey? {
|
|
155
|
-
let keyDict: [NSObject: NSObject] = [
|
|
156
|
-
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
157
|
-
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
|
158
|
-
kSecAttrKeySizeInBits: CryptoCipherConstants.rsaKeySizeInBits
|
|
159
|
-
]
|
|
160
|
-
return SecKeyCreateWithData(data as CFData, keyDict as CFDictionary, nil)
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// V1 Encryption - uses privateKey (deprecated but kept for backwards compatibility)
|
|
165
|
-
public struct CryptoCipherV1 {
|
|
166
|
-
private static var logger: Logger?
|
|
167
|
-
|
|
168
|
-
public static func setLogger(_ newLogger: Logger) {
|
|
169
|
-
logger = newLogger
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
public static func calcChecksum(filePath: URL) -> String {
|
|
173
|
-
let bufferSize = 1024 * 1024 * 5 // 5 MB
|
|
174
|
-
var checksum = uLong(0)
|
|
175
|
-
|
|
176
|
-
do {
|
|
177
|
-
let fileHandle = try FileHandle(forReadingFrom: filePath)
|
|
178
|
-
defer {
|
|
179
|
-
fileHandle.closeFile()
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
while autoreleasepool(invoking: {
|
|
183
|
-
let fileData = fileHandle.readData(ofLength: bufferSize)
|
|
184
|
-
if fileData.count > 0 {
|
|
185
|
-
checksum = fileData.withUnsafeBytes {
|
|
186
|
-
crc32(checksum, $0.bindMemory(to: Bytef.self).baseAddress, uInt(fileData.count))
|
|
187
|
-
}
|
|
188
|
-
return true // Continue
|
|
189
|
-
} else {
|
|
190
|
-
return false // End of file
|
|
191
|
-
}
|
|
192
|
-
}) {}
|
|
193
|
-
|
|
194
|
-
return String(format: "%08X", checksum).lowercased()
|
|
195
|
-
} catch {
|
|
196
|
-
print("\("[Capacitor-updater]") Cannot get checksum: \(filePath.path)", error)
|
|
197
|
-
return ""
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
public static func decryptFile(filePath: URL, privateKey: String, sessionKey: String, version: String) throws {
|
|
201
|
-
if privateKey.isEmpty {
|
|
202
|
-
print("\("[Capacitor-updater]") Cannot found privateKey")
|
|
203
|
-
return
|
|
204
|
-
} else if sessionKey.isEmpty || sessionKey.components(separatedBy: ":").count != 2 {
|
|
205
|
-
print("\("[Capacitor-updater]") Cannot found sessionKey")
|
|
206
|
-
return
|
|
207
|
-
}
|
|
208
|
-
do {
|
|
209
|
-
guard let rsaPrivateKey: RSAPrivateKey = .load(rsaPrivateKey: privateKey) else {
|
|
210
|
-
print("cannot decode privateKey", privateKey)
|
|
211
|
-
throw CustomError.cannotDecode
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
let sessionKeyArray: [String] = sessionKey.components(separatedBy: ":")
|
|
215
|
-
guard let ivData: Data = Data(base64Encoded: sessionKeyArray[0]) else {
|
|
216
|
-
print("cannot decode sessionKey", sessionKey)
|
|
217
|
-
throw CustomError.cannotDecode
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
guard let sessionKeyDataEncrypted = Data(base64Encoded: sessionKeyArray[1]) else {
|
|
221
|
-
throw NSError(domain: "Invalid session key data", code: 1, userInfo: nil)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
guard let sessionKeyDataDecrypted = rsaPrivateKey.decrypt(data: sessionKeyDataEncrypted) else {
|
|
225
|
-
throw NSError(domain: "Failed to decrypt session key data", code: 2, userInfo: nil)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
let aesPrivateKey = AES128Key(iv: ivData, aes128Key: sessionKeyDataDecrypted, logger: logger ?? Logger(withTag: "[Capacitor-updater]"))
|
|
229
|
-
|
|
230
|
-
guard let encryptedData = try? Data(contentsOf: filePath) else {
|
|
231
|
-
throw NSError(domain: "Failed to read encrypted data", code: 3, userInfo: nil)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
guard let decryptedData = aesPrivateKey.decrypt(data: encryptedData) else {
|
|
235
|
-
throw NSError(domain: "Failed to decrypt data", code: 4, userInfo: nil)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
try decryptedData.write(to: filePath)
|
|
239
|
-
|
|
240
|
-
} catch {
|
|
241
|
-
print("\("[Capacitor-updater]") Cannot decode: \(filePath.path)", error)
|
|
242
|
-
throw CustomError.cannotDecode
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|