@capgo/capacitor-updater 8.51.14 → 8.51.16
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/CapgoCapacitorUpdater.podspec +0 -2
- package/Package.swift +4 -5
- package/README.md +1 -1
- package/android/build.gradle +3 -3
- package/android/src/main/java/ee/forgr/capacitor_updater/CapacitorUpdaterPlugin.java +55 -22
- package/android/src/main/java/ee/forgr/capacitor_updater/CapgoUpdater.java +59 -29
- package/android/src/main/java/ee/forgr/capacitor_updater/DataManager.java +23 -7
- package/android/src/main/java/ee/forgr/capacitor_updater/DelayUpdateUtils.java +11 -0
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadService.java +57 -31
- package/android/src/main/java/ee/forgr/capacitor_updater/DownloadWorkerManager.java +103 -3
- package/dist/docs.json +1 -1
- package/dist/esm/definitions.d.ts +4 -1
- package/dist/esm/definitions.js.map +1 -1
- package/ios/Sources/CapacitorUpdaterPlugin/BundleInfo.swift +4 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapacitorUpdaterPlugin.swift +114 -30
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoRawRsa.swift +36 -0
- package/ios/Sources/CapacitorUpdaterPlugin/CapgoUpdater.swift +56 -20
- package/ios/Sources/CapacitorUpdaterPlugin/RSA.swift +73 -251
- package/package.json +2 -2
- package/ios/Sources/CapacitorUpdaterPlugin/BigInt.swift +0 -39
|
@@ -1,274 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
///
|
|
7
|
-
/// Constants
|
|
8
|
-
///
|
|
9
|
-
private enum RSAConstants {
|
|
10
|
-
static let rsaKeySizeInBits: NSNumber = 2048
|
|
11
|
-
static let rsaAlgorithm: SecKeyAlgorithm = .rsaEncryptionOAEPSHA256
|
|
12
|
-
}
|
|
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
|
+
*/
|
|
13
6
|
|
|
14
|
-
|
|
15
|
-
|
|
7
|
+
import Foundation
|
|
8
|
+
import Security
|
|
9
|
+
import os.log
|
|
16
10
|
|
|
17
|
-
///
|
|
18
|
-
///
|
|
19
|
-
///
|
|
11
|
+
/// Capgo v2 uses Node's privateEncrypt with PKCS#1 v1.5 type-1 padding for
|
|
12
|
+
/// checksums and session keys. Recover the signed payload using Apple's RSA
|
|
13
|
+
/// implementation instead of custom modular exponentiation.
|
|
20
14
|
public struct RSAPublicKey {
|
|
21
|
-
private let
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
///
|
|
28
|
-
/// Takes the data and uses the public key to decrypt it.
|
|
29
|
-
/// Returns the decrypted data.
|
|
30
|
-
///
|
|
31
|
-
public func decrypt(data: Data) -> Data? {
|
|
32
|
-
return manualKey.decrypt(data)
|
|
33
|
-
}
|
|
15
|
+
private let key: SecKey
|
|
16
|
+
private static let keyLock = NSLock()
|
|
17
|
+
private static let log = OSLog(subsystem: "ee.forgr.capacitor_updater", category: "RSA")
|
|
18
|
+
// One public key only: bounded storage, invalid/rotated keys never fall back
|
|
19
|
+
// to a cached key with different material. SecKey is immutable after import.
|
|
20
|
+
private static var cachedKey: (der: Data, key: SecKey)?
|
|
34
21
|
|
|
35
|
-
///
|
|
36
|
-
/// Allows you to load an RSA public key (i.e. one downloaded from the net).
|
|
37
|
-
///
|
|
38
22
|
public static func load(rsaPublicKey: String) -> RSAPublicKey? {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
|
|
42
|
-
pubKey = pubKey.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
|
|
43
|
-
pubKey = pubKey.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
|
|
44
|
-
pubKey = pubKey.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
45
|
-
pubKey = pubKey.replacingOccurrences(of: "\\n+", with: "", options: .regularExpression)
|
|
46
|
-
pubKey = pubKey.replacingOccurrences(of: "\n", with: "")
|
|
47
|
-
pubKey = pubKey.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
48
|
-
|
|
49
|
-
do {
|
|
50
|
-
guard let rsaPublicKeyData: Data = Data(base64Encoded: String(pubKey)) else {
|
|
51
|
-
throw CustomError.cannotDecode
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Try parsing as PKCS#1
|
|
55
|
-
if let manualKey = ManualRSAPublicKey.fromPKCS1(rsaPublicKeyData) {
|
|
56
|
-
return RSAPublicKey(manualKey: manualKey)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Most common public exponent is 65537 (0x010001)
|
|
60
|
-
let commonExponent = Data([0x01, 0x00, 0x01]) // 65537 in big-endian
|
|
61
|
-
|
|
62
|
-
// Assume the entire key data is the modulus
|
|
63
|
-
let lastResortKey = ManualRSAPublicKey(modulus: rsaPublicKeyData, exponent: commonExponent)
|
|
64
|
-
return RSAPublicKey(manualKey: lastResortKey)
|
|
65
|
-
} catch {
|
|
66
|
-
return nil
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Manual RSA Public Key Implementation using the BigInt library
|
|
72
|
-
struct ManualRSAPublicKey {
|
|
73
|
-
let modulus: BigInt
|
|
74
|
-
let exponent: BigInt
|
|
75
|
-
|
|
76
|
-
init(modulus: Data, exponent: Data) {
|
|
77
|
-
// Create positive BigInts from Data
|
|
78
|
-
let modulusBytes = [UInt8](modulus)
|
|
79
|
-
var modulusValue = BigUInt(0)
|
|
80
|
-
for byte in modulusBytes {
|
|
81
|
-
modulusValue = (modulusValue << 8) | BigUInt(byte)
|
|
82
|
-
}
|
|
83
|
-
self.modulus = BigInt(modulusValue)
|
|
84
|
-
let exponentBytes = [UInt8](exponent)
|
|
85
|
-
var exponentValue = BigUInt(0)
|
|
86
|
-
for byte in exponentBytes {
|
|
87
|
-
exponentValue = (exponentValue << 8) | BigUInt(byte)
|
|
88
|
-
}
|
|
89
|
-
self.exponent = BigInt(exponentValue)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Parse PKCS#1 format public key
|
|
93
|
-
static func fromPKCS1(_ publicKeyData: Data) -> ManualRSAPublicKey? {
|
|
94
|
-
// Parse ASN.1 DER encoded RSA public key
|
|
95
|
-
// Format: RSAPublicKey ::= SEQUENCE { modulus INTEGER, publicExponent INTEGER }
|
|
96
|
-
|
|
97
|
-
guard publicKeyData.count > 0 else {
|
|
98
|
-
return nil
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
let bytes = [UInt8](publicKeyData)
|
|
102
|
-
|
|
103
|
-
// Check for sequence tag (0x30)
|
|
104
|
-
guard bytes[0] == 0x30 else {
|
|
105
|
-
// Try direct modulus/exponent approach as fallback
|
|
106
|
-
if publicKeyData.count >= 3 {
|
|
107
|
-
// Assume this is a raw RSA public key with modulus + exponent
|
|
108
|
-
// Most common: modulus is 256 bytes (2048 bits), exponent is 3 bytes (0x010001 = 65537)
|
|
109
|
-
let modulusSize = publicKeyData.count - 3
|
|
110
|
-
if modulusSize > 0 {
|
|
111
|
-
let modulusData = publicKeyData.prefix(modulusSize)
|
|
112
|
-
let exponentData = publicKeyData.suffix(3)
|
|
113
|
-
return ManualRSAPublicKey(modulus: modulusData, exponent: exponentData)
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return nil
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
var index = 1
|
|
121
|
-
|
|
122
|
-
// Skip length
|
|
123
|
-
if bytes[index] & 0x80 != 0 {
|
|
124
|
-
let lenBytes = Int(bytes[index] & 0x7F)
|
|
125
|
-
if (index + 1 + lenBytes) >= bytes.count {
|
|
126
|
-
return nil
|
|
127
|
-
}
|
|
128
|
-
index += 1 + lenBytes
|
|
129
|
-
} else {
|
|
130
|
-
index += 1
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check for INTEGER tag for modulus (0x02)
|
|
134
|
-
if index >= bytes.count {
|
|
135
|
-
return nil
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
guard bytes[index] == 0x02 else {
|
|
139
|
-
return nil
|
|
140
|
-
}
|
|
141
|
-
index += 1
|
|
142
|
-
|
|
143
|
-
// Get modulus length
|
|
144
|
-
if index >= bytes.count {
|
|
145
|
-
return nil
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
var modulusLength = 0
|
|
149
|
-
if bytes[index] & 0x80 != 0 {
|
|
150
|
-
let lenBytes = Int(bytes[index] & 0x7F)
|
|
151
|
-
if (index + 1 + lenBytes) >= bytes.count {
|
|
152
|
-
return nil
|
|
153
|
-
}
|
|
154
|
-
index += 1
|
|
155
|
-
for i in 0..<lenBytes {
|
|
156
|
-
modulusLength = (modulusLength << 8) | Int(bytes[index + i])
|
|
157
|
-
}
|
|
158
|
-
index += lenBytes
|
|
159
|
-
} else {
|
|
160
|
-
modulusLength = Int(bytes[index])
|
|
161
|
-
index += 1
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Skip any leading zero in modulus (for unsigned integer)
|
|
165
|
-
if index < bytes.count && bytes[index] == 0x00 {
|
|
166
|
-
index += 1
|
|
167
|
-
modulusLength -= 1
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Extract modulus
|
|
171
|
-
if (index + modulusLength) > bytes.count {
|
|
172
|
-
return nil
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let modulusData = Data(bytes[index..<(index + modulusLength)])
|
|
176
|
-
index += modulusLength
|
|
177
|
-
|
|
178
|
-
// Check for INTEGER tag for exponent
|
|
179
|
-
if index >= bytes.count {
|
|
23
|
+
guard let der = pemToDer(rsaPublicKey) else {
|
|
24
|
+
os_log("RSA key import failed: invalid PEM/base64", log: Self.log, type: .error)
|
|
180
25
|
return nil
|
|
181
26
|
}
|
|
182
27
|
|
|
183
|
-
|
|
28
|
+
keyLock.lock()
|
|
29
|
+
defer { keyLock.unlock() }
|
|
30
|
+
if let cached = cachedKey, cached.der == der {
|
|
31
|
+
return RSAPublicKey(key: cached.key)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let attributes: [CFString: Any] = [
|
|
35
|
+
kSecAttrKeyType: kSecAttrKeyTypeRSA,
|
|
36
|
+
kSecAttrKeyClass: kSecAttrKeyClassPublic
|
|
37
|
+
]
|
|
38
|
+
var error: Unmanaged<CFError>?
|
|
39
|
+
guard let key = SecKeyCreateWithData(der as CFData, attributes as CFDictionary, &error) else {
|
|
40
|
+
os_log(
|
|
41
|
+
"RSA key import failed: %{public}@",
|
|
42
|
+
log: Self.log,
|
|
43
|
+
type: .error,
|
|
44
|
+
String(describing: error?.takeRetainedValue())
|
|
45
|
+
)
|
|
184
46
|
return nil
|
|
185
47
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
48
|
+
guard CapgoRawRsa.supportsCapgoRecovery(key: key) else {
|
|
49
|
+
os_log(
|
|
50
|
+
"RSA key import failed: unsupported key (Capgo requires RSA-2048 with raw encryption)",
|
|
51
|
+
log: Self.log,
|
|
52
|
+
type: .error
|
|
53
|
+
)
|
|
190
54
|
return nil
|
|
191
55
|
}
|
|
56
|
+
cachedKey = (der, key)
|
|
57
|
+
return RSAPublicKey(key: key)
|
|
58
|
+
}
|
|
192
59
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
for i in 0..<lenBytes {
|
|
201
|
-
exponentLength = (exponentLength << 8) | Int(bytes[index + i])
|
|
202
|
-
}
|
|
203
|
-
index += lenBytes
|
|
204
|
-
} else {
|
|
205
|
-
exponentLength = Int(bytes[index])
|
|
206
|
-
index += 1
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Extract exponent
|
|
210
|
-
if (index + exponentLength) > bytes.count {
|
|
60
|
+
public func decrypt(data: Data) -> Data? {
|
|
61
|
+
let blockSize = SecKeyGetBlockSize(key)
|
|
62
|
+
guard data.count == blockSize else { return nil }
|
|
63
|
+
// Raw public-key encryption is the same c^e mod n operation as Node's
|
|
64
|
+
// publicDecrypt. Do NOT use encryption PKCS#1 padding here: the recovered
|
|
65
|
+
// block already contains signature (type-1) padding, checked below.
|
|
66
|
+
guard let recovered = CapgoRawRsa.recoverSignatureBlock(key: key, data: data) else {
|
|
211
67
|
return nil
|
|
212
68
|
}
|
|
213
|
-
|
|
214
|
-
let exponentData = Data(bytes[index..<(index + exponentLength)])
|
|
215
|
-
return ManualRSAPublicKey(modulus: modulusData, exponent: exponentData)
|
|
69
|
+
return Self.unpadSignature(recovered, blockSize: blockSize)
|
|
216
70
|
}
|
|
217
71
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
var
|
|
223
|
-
|
|
224
|
-
|
|
72
|
+
static func unpadSignature(_ block: Data, blockSize: Int = 256) -> Data? {
|
|
73
|
+
let bytes = [UInt8](block)
|
|
74
|
+
// RFC 8017: 00 01, at least eight FF bytes, 00, then the payload.
|
|
75
|
+
guard bytes.count == blockSize, bytes[0] == 0, bytes[1] == 1 else { return nil }
|
|
76
|
+
var separator = 2
|
|
77
|
+
while separator < bytes.count && bytes[separator] == 0xff {
|
|
78
|
+
separator += 1
|
|
225
79
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
// In Node.js:
|
|
229
|
-
// privateEncrypt uses the private key (d) to encrypt
|
|
230
|
-
// publicDecrypt uses the public key (e) to decrypt
|
|
231
|
-
// The operation we want is: ciphertext^e mod n
|
|
232
|
-
|
|
233
|
-
// RSA operation: c^e mod n
|
|
234
|
-
let decrypted = encrypted.manualPower(exponent, modulus: modulus)
|
|
235
|
-
|
|
236
|
-
// Convert to bytes with proper padding
|
|
237
|
-
guard let bigUIntValue = decrypted.magnitude as? BigUInt else {
|
|
80
|
+
guard separator >= 10, separator < bytes.count - 1, bytes[separator] == 0 else {
|
|
238
81
|
return nil
|
|
239
82
|
}
|
|
83
|
+
return Data(bytes[(separator + 1)...])
|
|
84
|
+
}
|
|
240
85
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
// Ensure we have at least 256 bytes (2048 bits) with leading zeros
|
|
251
|
-
let paddedBytes = [UInt8](repeating: 0, count: max(0, 256 - resultBytes.count)) + resultBytes
|
|
252
|
-
|
|
253
|
-
// For PKCS1 padding from Node.js privateEncrypt, the format is:
|
|
254
|
-
// 0x00 || 0x01 || PS || 0x00 || actual data
|
|
255
|
-
// where PS is a string of 0xFF bytes
|
|
256
|
-
|
|
257
|
-
// Check for privateEncrypt padding format (0x00 || 0x01 || PS || 0x00)
|
|
258
|
-
var startIndex = 0
|
|
259
|
-
if paddedBytes.count > 2 && paddedBytes[0] == 0x00 && paddedBytes[1] == 0x01 {
|
|
260
|
-
for i in 2..<paddedBytes.count {
|
|
261
|
-
if paddedBytes[i] == 0x00 {
|
|
262
|
-
startIndex = i + 1
|
|
263
|
-
break
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
if startIndex < paddedBytes.count {
|
|
268
|
-
let result = Data(paddedBytes[startIndex...])
|
|
269
|
-
return result
|
|
270
|
-
} else {
|
|
271
|
-
return Data(paddedBytes)
|
|
272
|
-
}
|
|
86
|
+
private static func pemToDer(_ pem: String) -> Data? {
|
|
87
|
+
let encoded = pem
|
|
88
|
+
.replacingOccurrences(of: "-----BEGIN RSA PUBLIC KEY-----", with: "")
|
|
89
|
+
.replacingOccurrences(of: "-----END RSA PUBLIC KEY-----", with: "")
|
|
90
|
+
.replacingOccurrences(of: "-----BEGIN PUBLIC KEY-----", with: "")
|
|
91
|
+
.replacingOccurrences(of: "-----END PUBLIC KEY-----", with: "")
|
|
92
|
+
.replacingOccurrences(of: "\\n", with: "")
|
|
93
|
+
.components(separatedBy: .whitespacesAndNewlines).joined()
|
|
94
|
+
return Data(base64Encoded: encoded)
|
|
273
95
|
}
|
|
274
96
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-updater",
|
|
3
|
-
"version": "8.51.
|
|
3
|
+
"version": "8.51.16",
|
|
4
4
|
"license": "MPL-2.0",
|
|
5
5
|
"description": "Live update for capacitor apps",
|
|
6
6
|
"main": "dist/plugin.cjs.js",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"@ionic/eslint-config": "^0.4.0",
|
|
90
90
|
"@ionic/prettier-config": "^4.0.0",
|
|
91
91
|
"@ionic/swiftlint-config": "^2.0.0",
|
|
92
|
-
"@types/node": "^
|
|
92
|
+
"@types/node": "^26.0.0",
|
|
93
93
|
"eslint": "^8.57.0",
|
|
94
94
|
"eslint-plugin-import": "^2.32.0",
|
|
95
95
|
"husky": "^9.1.7",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import BigInt
|
|
2
|
-
|
|
3
|
-
// Add this custom power function to ensure safer handling of power operations
|
|
4
|
-
|
|
5
|
-
// Manual exponentiation using the square-and-multiply algorithm
|
|
6
|
-
// which is more efficient and avoids using the built-in functions that might handle BigInt differently
|
|
7
|
-
extension BigInt {
|
|
8
|
-
func manualPower(_ exponent: BigInt, modulus: BigInt) -> BigInt {
|
|
9
|
-
// Quick checks
|
|
10
|
-
if modulus == 0 {
|
|
11
|
-
return 0
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if exponent == 0 {
|
|
15
|
-
return 1
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
guard let base = self.magnitude as? BigUInt,
|
|
19
|
-
let exp = exponent.magnitude as? BigUInt,
|
|
20
|
-
let mod = modulus.magnitude as? BigUInt else {
|
|
21
|
-
return 0
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
// Square and multiply algorithm for modular exponentiation
|
|
25
|
-
var result = BigUInt(1)
|
|
26
|
-
var currentBase = base % mod
|
|
27
|
-
var currentExp = exp
|
|
28
|
-
|
|
29
|
-
while currentExp > 0 {
|
|
30
|
-
if currentExp & 1 == 1 {
|
|
31
|
-
result = (result * currentBase) % mod
|
|
32
|
-
}
|
|
33
|
-
currentBase = (currentBase * currentBase) % mod
|
|
34
|
-
currentExp >>= 1
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return BigInt(result)
|
|
38
|
-
}
|
|
39
|
-
}
|