@onekeyfe/react-native-bundle-crypto 3.0.157-alpha.251 → 3.0.157-alpha.254
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 +2 -0
- package/ReactNativeBundleCrypto.podspec +5 -0
- package/android/build.gradle +2 -0
- package/android/src/main/java/com/margelo/nitro/reactnativebundlecrypto/BundleCryptoCore.kt +7 -1
- package/android/src/test/java/com/margelo/nitro/nativelogger/OneKeyLog.kt +10 -0
- package/android/src/test/java/com/margelo/nitro/reactnativebundlecrypto/BundleCryptoCoreTest.kt +135 -0
- package/ios/BundleCryptoCore.swift +9 -1
- package/ios/tests/BundleCryptoCoreTests.swift +136 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Pod::Spec.new do |s|
|
|
|
18
18
|
"ios/**/*.{m,mm}",
|
|
19
19
|
"cpp/**/*.{hpp,cpp}",
|
|
20
20
|
]
|
|
21
|
+
s.exclude_files = "ios/tests/**/*"
|
|
21
22
|
|
|
22
23
|
# Vendored Gopenpgp framework: GPG cleartext/detached signature verification.
|
|
23
24
|
# This is the single in-tree copy used by bundle-crypto and by modules that
|
|
@@ -32,4 +33,8 @@ Pod::Spec.new do |s|
|
|
|
32
33
|
add_nitrogen_files(s)
|
|
33
34
|
|
|
34
35
|
install_modules_dependencies(s)
|
|
36
|
+
|
|
37
|
+
s.test_spec "Tests" do |test_spec|
|
|
38
|
+
test_spec.source_files = "ios/tests/**/*.swift"
|
|
39
|
+
end
|
|
35
40
|
end
|
package/android/build.gradle
CHANGED
|
@@ -133,4 +133,6 @@ dependencies {
|
|
|
133
133
|
// react-native-app-update (the modules this security logic was migrated from).
|
|
134
134
|
implementation "org.bouncycastle:bcpg-jdk15to18:1.78.1"
|
|
135
135
|
implementation "org.bouncycastle:bcprov-jdk15to18:1.78.1"
|
|
136
|
+
|
|
137
|
+
testImplementation "junit:junit:4.13.2"
|
|
136
138
|
}
|
|
@@ -391,8 +391,14 @@ object BundleCryptoCore {
|
|
|
391
391
|
return false
|
|
392
392
|
}
|
|
393
393
|
val jsBundleDir = dir.absolutePath + "/"
|
|
394
|
+
val resolvedDir = try { dir.canonicalFile.toPath() } catch (e: Exception) { return false }
|
|
394
395
|
val expected = HashMap<String, String>()
|
|
395
|
-
for (entry in entries)
|
|
396
|
+
for (entry in entries) {
|
|
397
|
+
if (entry.relativePath.isEmpty() || File(entry.relativePath).isAbsolute) return false
|
|
398
|
+
val resolvedFile = try { File(dir, entry.relativePath).canonicalFile.toPath() } catch (e: Exception) { return false }
|
|
399
|
+
if (resolvedFile == resolvedDir || !resolvedFile.startsWith(resolvedDir)) return false
|
|
400
|
+
expected[entry.relativePath] = entry.sha256
|
|
401
|
+
}
|
|
396
402
|
|
|
397
403
|
if (!validateFilesRecursive(dir, expected, jsBundleDir)) return false
|
|
398
404
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelogger
|
|
2
|
+
|
|
3
|
+
// Host-JVM test fixture: keep the production crypto core and BouncyCastle real
|
|
4
|
+
// while avoiding the production logger's Android/Nitro JNI initialization.
|
|
5
|
+
object OneKeyLog {
|
|
6
|
+
@JvmStatic
|
|
7
|
+
fun error(tag: String, message: String) = Unit
|
|
8
|
+
@JvmStatic
|
|
9
|
+
fun info(tag: String, message: String) = Unit
|
|
10
|
+
}
|
package/android/src/test/java/com/margelo/nitro/reactnativebundlecrypto/BundleCryptoCoreTest.kt
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
package com.margelo.nitro.reactnativebundlecrypto
|
|
2
|
+
|
|
3
|
+
import java.io.File
|
|
4
|
+
import java.nio.file.Files
|
|
5
|
+
import org.junit.Assert.assertEquals
|
|
6
|
+
import org.junit.Assert.assertFalse
|
|
7
|
+
import org.junit.Assert.assertNull
|
|
8
|
+
import org.junit.Assert.assertTrue
|
|
9
|
+
import org.junit.Rule
|
|
10
|
+
import org.junit.Test
|
|
11
|
+
import org.junit.rules.TemporaryFolder
|
|
12
|
+
|
|
13
|
+
class BundleCryptoCoreTest {
|
|
14
|
+
@get:Rule val temporaryFolder = TemporaryFolder()
|
|
15
|
+
|
|
16
|
+
private val expectedSignedHash = "2ada9c871104fc40649fa3de67a7d8e33faadc18e9abd587e8bb85be0a003eba"
|
|
17
|
+
|
|
18
|
+
// Existing release-key test vector, also used by BundleUpdate.testVerification.
|
|
19
|
+
private val signedMetadata = """-----BEGIN PGP SIGNED MESSAGE-----
|
|
20
|
+
Hash: SHA256
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
"fileName": "metadata.json",
|
|
24
|
+
"sha256": "2ada9c871104fc40649fa3de67a7d8e33faadc18e9abd587e8bb85be0a003eba",
|
|
25
|
+
"size": 158590,
|
|
26
|
+
"generatedAt": "2025-09-19T07:49:13.000Z"
|
|
27
|
+
}
|
|
28
|
+
-----BEGIN PGP SIGNATURE-----
|
|
29
|
+
|
|
30
|
+
iQJCBAEBCAAsFiEE62iuVE8f3YzSZGJPs2mmepC/OHsFAmjNJ1IOHGRldkBvbmVr
|
|
31
|
+
ZXkuc28ACgkQs2mmepC/OHs6Rw/9FKHl5aNsE7V0IsFf/l+h16BYKFwVsL69alMk
|
|
32
|
+
CFLna8oUn0+tyECF6wKBKw5pHo5YR27o2pJfYbAER6dygDF6WTZ1lZdf5QcBMjGA
|
|
33
|
+
LCeXC0hzUBzSSOH4bKBTa3fHp//HdSV1F2OnkymbXqYN7WXvuQPLZ0nV6aU88hCk
|
|
34
|
+
HgFifcvkXAnWKoosUtj0Bban/YBRyvmQ5C2akxUPEkr4Yck1QXwzJeNRd7wMXHjH
|
|
35
|
+
JFK6lJcuABiB8wpJDXJkFzKs29pvHIK2B2vdOjU2rQzKOUwaKHofDi5C4+JitT2b
|
|
36
|
+
2pSeYP3PAxXYw6XDOmKTOiC7fPnfLjtcPjNYNFCezVKZT6LKvZW9obnW8Q9LNJ4W
|
|
37
|
+
okMPgHObkabv3OqUaTA9QNVfI/X9nvggzlPnaKDUrDWTf7n3vlrdexugkLtV/tJA
|
|
38
|
+
uguPlI5hY7Ue5OW7ckWP46hfmq1+UaIdeUY7dEO+rPZDz6KcArpaRwBiLPBhneIr
|
|
39
|
+
/X3KuMzS272YbPbavgCZGN9xJR5kZsEQE5HhPCbr6Nf0qDnh+X8mg0tAB/U6F+ZE
|
|
40
|
+
o90sJL1ssIaYvST+VWVaGRr4V5nMDcgHzWSF9Q/wm22zxe4alDaBdvOlUseW0iaM
|
|
41
|
+
n2DMz6gqk326W6SFynYtvuiXo7wG4Cmn3SuIU8xfv9rJqunpZGYchMd7nZektmEJ
|
|
42
|
+
91Js0rQ=
|
|
43
|
+
=A/Ii
|
|
44
|
+
-----END PGP SIGNATURE-----"""
|
|
45
|
+
|
|
46
|
+
@Test
|
|
47
|
+
fun signedMetadataRejectsChangedBodyAndUnsignedInput() {
|
|
48
|
+
val tampered = signedMetadata.replace("158590", "158591")
|
|
49
|
+
val rejected = BundleCryptoCore.verifyGpgCleartext(tampered)
|
|
50
|
+
assertFalse(rejected.valid)
|
|
51
|
+
assertNull(rejected.sha256)
|
|
52
|
+
assertEquals("SIGNATURE_INVALID", rejected.reason)
|
|
53
|
+
|
|
54
|
+
val unsigned = BundleCryptoCore.verifyGpgCleartext("{\"sha256\":\"$expectedSignedHash\"}")
|
|
55
|
+
assertFalse(unsigned.valid)
|
|
56
|
+
assertNull(unsigned.sha256)
|
|
57
|
+
assertEquals("NOT_PGP_SIGNED_MESSAGE", unsigned.reason)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Test
|
|
61
|
+
fun validSignatureWithWrongAscBodyIsRejectedAfterVerification() {
|
|
62
|
+
val result = BundleCryptoCore.verifyDetachedAsc(signedMetadata)
|
|
63
|
+
assertFalse(result.valid)
|
|
64
|
+
assertNull(result.sha256)
|
|
65
|
+
assertEquals("SHA256_TOKEN_INVALID", result.reason)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@Test
|
|
69
|
+
fun directoryHashesRejectChangedMissingAndUnlistedFiles() {
|
|
70
|
+
val bundle = temporaryFolder.newFolder("bundle")
|
|
71
|
+
val nested = File(bundle, "nested/bundle").apply { mkdirs() }
|
|
72
|
+
val file = File(nested, "index.js").apply { writeText("abc") }
|
|
73
|
+
File(bundle, "metadata.json").writeText("ignored")
|
|
74
|
+
|
|
75
|
+
val hashes = BundleCryptoCore.hashDir(bundle.absolutePath)
|
|
76
|
+
assertEquals(1, hashes.size)
|
|
77
|
+
assertEquals("nested/bundle/index.js", hashes.single().relativePath)
|
|
78
|
+
assertEquals("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", hashes.single().sha256)
|
|
79
|
+
assertTrue(BundleCryptoCore.verifyDirAgainstHashes(bundle.absolutePath, hashes))
|
|
80
|
+
|
|
81
|
+
file.writeText("changed")
|
|
82
|
+
assertFalse(BundleCryptoCore.verifyDirAgainstHashes(bundle.absolutePath, hashes))
|
|
83
|
+
file.delete()
|
|
84
|
+
assertFalse(BundleCryptoCore.verifyDirAgainstHashes(bundle.absolutePath, hashes))
|
|
85
|
+
|
|
86
|
+
file.writeText("abc")
|
|
87
|
+
File(bundle, "evil-metadata.json").writeText("unlisted")
|
|
88
|
+
assertFalse(BundleCryptoCore.verifyDirAgainstHashes(bundle.absolutePath, hashes))
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
fun directoryHashesRejectExpectedPathsOutsideBundle() {
|
|
93
|
+
val root = temporaryFolder.newFolder("root")
|
|
94
|
+
val bundle = File(root, "bundle").apply { mkdirs() }
|
|
95
|
+
val outside = File(root, "outside.txt").apply { writeText("outside") }
|
|
96
|
+
val wrongHash = "0".repeat(64)
|
|
97
|
+
|
|
98
|
+
assertFalse(
|
|
99
|
+
BundleCryptoCore.verifyDirAgainstHashes(
|
|
100
|
+
bundle.absolutePath,
|
|
101
|
+
listOf(BundleCryptoCore.DirHash("../outside.txt", wrongHash))
|
|
102
|
+
)
|
|
103
|
+
)
|
|
104
|
+
assertFalse(
|
|
105
|
+
BundleCryptoCore.verifyDirAgainstHashes(
|
|
106
|
+
bundle.absolutePath,
|
|
107
|
+
listOf(BundleCryptoCore.DirHash(outside.absolutePath, wrongHash))
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
Files.createSymbolicLink(File(bundle, "link.txt").toPath(), outside.toPath())
|
|
111
|
+
assertFalse(
|
|
112
|
+
BundleCryptoCore.verifyDirAgainstHashes(
|
|
113
|
+
bundle.absolutePath,
|
|
114
|
+
listOf(BundleCryptoCore.DirHash("link.txt", wrongHash))
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
assertFalse(
|
|
118
|
+
BundleCryptoCore.verifyDirAgainstHashes(
|
|
119
|
+
bundle.absolutePath,
|
|
120
|
+
listOf(BundleCryptoCore.DirHash("", wrongHash))
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Test
|
|
126
|
+
fun extractedPathSafetyRejectsSymlinks() {
|
|
127
|
+
val bundle = temporaryFolder.newFolder("extracted")
|
|
128
|
+
val outside = temporaryFolder.newFile("outside.txt").apply { writeText("outside") }
|
|
129
|
+
File(bundle, "index.js").writeText("inside")
|
|
130
|
+
assertTrue(BundleCryptoCore.validateExtractedPathSafety(bundle.absolutePath))
|
|
131
|
+
|
|
132
|
+
Files.createSymbolicLink(File(bundle, "escape").toPath(), outside.toPath())
|
|
133
|
+
assertFalse(BundleCryptoCore.validateExtractedPathSafety(bundle.absolutePath))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -411,9 +411,17 @@ public enum BundleCryptoCore {
|
|
|
411
411
|
}
|
|
412
412
|
// Normalize the directory prefix so relative paths match.
|
|
413
413
|
let normalizedDir = (dirPath as NSString).hasSuffix("/") ? dirPath : dirPath + "/"
|
|
414
|
+
let resolvedDir = (dirPath as NSString).resolvingSymlinksInPath
|
|
415
|
+
let resolvedPrefix = resolvedDir == "/" ? "/" : resolvedDir + "/"
|
|
414
416
|
|
|
415
417
|
var expected: [String: String] = [:]
|
|
416
|
-
for entry in entries {
|
|
418
|
+
for entry in entries {
|
|
419
|
+
guard !entry.relativePath.isEmpty,
|
|
420
|
+
!(entry.relativePath as NSString).isAbsolutePath else { return false }
|
|
421
|
+
let resolvedFile = ((normalizedDir + entry.relativePath) as NSString).resolvingSymlinksInPath
|
|
422
|
+
guard resolvedFile.hasPrefix(resolvedPrefix) else { return false }
|
|
423
|
+
expected[entry.relativePath] = entry.sha256
|
|
424
|
+
}
|
|
417
425
|
|
|
418
426
|
guard let enumerator = fm.enumerator(atPath: dirPath) else { return false }
|
|
419
427
|
while let file = enumerator.nextObject() as? String {
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import ReactNativeBundleCrypto
|
|
3
|
+
import XCTest
|
|
4
|
+
|
|
5
|
+
final class BundleCryptoCoreTests: XCTestCase {
|
|
6
|
+
private let expectedSignedHash = "2ada9c871104fc40649fa3de67a7d8e33faadc18e9abd587e8bb85be0a003eba"
|
|
7
|
+
|
|
8
|
+
// Existing release-key test vector, also used by BundleUpdate.testVerification.
|
|
9
|
+
private let signedMetadata = """
|
|
10
|
+
-----BEGIN PGP SIGNED MESSAGE-----
|
|
11
|
+
Hash: SHA256
|
|
12
|
+
|
|
13
|
+
{
|
|
14
|
+
"fileName": "metadata.json",
|
|
15
|
+
"sha256": "2ada9c871104fc40649fa3de67a7d8e33faadc18e9abd587e8bb85be0a003eba",
|
|
16
|
+
"size": 158590,
|
|
17
|
+
"generatedAt": "2025-09-19T07:49:13.000Z"
|
|
18
|
+
}
|
|
19
|
+
-----BEGIN PGP SIGNATURE-----
|
|
20
|
+
|
|
21
|
+
iQJCBAEBCAAsFiEE62iuVE8f3YzSZGJPs2mmepC/OHsFAmjNJ1IOHGRldkBvbmVr
|
|
22
|
+
ZXkuc28ACgkQs2mmepC/OHs6Rw/9FKHl5aNsE7V0IsFf/l+h16BYKFwVsL69alMk
|
|
23
|
+
CFLna8oUn0+tyECF6wKBKw5pHo5YR27o2pJfYbAER6dygDF6WTZ1lZdf5QcBMjGA
|
|
24
|
+
LCeXC0hzUBzSSOH4bKBTa3fHp//HdSV1F2OnkymbXqYN7WXvuQPLZ0nV6aU88hCk
|
|
25
|
+
HgFifcvkXAnWKoosUtj0Bban/YBRyvmQ5C2akxUPEkr4Yck1QXwzJeNRd7wMXHjH
|
|
26
|
+
JFK6lJcuABiB8wpJDXJkFzKs29pvHIK2B2vdOjU2rQzKOUwaKHofDi5C4+JitT2b
|
|
27
|
+
2pSeYP3PAxXYw6XDOmKTOiC7fPnfLjtcPjNYNFCezVKZT6LKvZW9obnW8Q9LNJ4W
|
|
28
|
+
okMPgHObkabv3OqUaTA9QNVfI/X9nvggzlPnaKDUrDWTf7n3vlrdexugkLtV/tJA
|
|
29
|
+
uguPlI5hY7Ue5OW7ckWP46hfmq1+UaIdeUY7dEO+rPZDz6KcArpaRwBiLPBhneIr
|
|
30
|
+
/X3KuMzS272YbPbavgCZGN9xJR5kZsEQE5HhPCbr6Nf0qDnh+X8mg0tAB/U6F+ZE
|
|
31
|
+
o90sJL1ssIaYvST+VWVaGRr4V5nMDcgHzWSF9Q/wm22zxe4alDaBdvOlUseW0iaM
|
|
32
|
+
n2DMz6gqk326W6SFynYtvuiXo7wG4Cmn3SuIU8xfv9rJqunpZGYchMd7nZektmEJ
|
|
33
|
+
91Js0rQ=
|
|
34
|
+
=A/Ii
|
|
35
|
+
-----END PGP SIGNATURE-----
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
func testSignedMetadataAcceptsReleaseKeyAndRejectsTampering() {
|
|
39
|
+
let verified = BundleCryptoCore.verifyGpgCleartext(signedMetadata)
|
|
40
|
+
XCTAssertTrue(verified.valid)
|
|
41
|
+
XCTAssertEqual(verified.sha256, expectedSignedHash)
|
|
42
|
+
XCTAssertNil(verified.reason)
|
|
43
|
+
|
|
44
|
+
let tampered = signedMetadata.replacingOccurrences(of: "158590", with: "158591")
|
|
45
|
+
let rejected = BundleCryptoCore.verifyGpgCleartext(tampered)
|
|
46
|
+
XCTAssertFalse(rejected.valid)
|
|
47
|
+
XCTAssertNil(rejected.sha256)
|
|
48
|
+
XCTAssertNotNil(rejected.reason)
|
|
49
|
+
|
|
50
|
+
let unsigned = BundleCryptoCore.verifyGpgCleartext("{\"sha256\":\"\(expectedSignedHash)\"}")
|
|
51
|
+
XCTAssertFalse(unsigned.valid)
|
|
52
|
+
XCTAssertNil(unsigned.sha256)
|
|
53
|
+
XCTAssertEqual(unsigned.reason, "NOT_PGP_SIGNED_MESSAGE")
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func testDirectoryHashesRejectChangedMissingAndUnlistedFiles() throws {
|
|
57
|
+
try withTemporaryDirectory { root in
|
|
58
|
+
let bundle = root.appendingPathComponent("bundle", isDirectory: true)
|
|
59
|
+
let nested = bundle.appendingPathComponent("nested/bundle", isDirectory: true)
|
|
60
|
+
try FileManager.default.createDirectory(at: nested, withIntermediateDirectories: true)
|
|
61
|
+
let file = nested.appendingPathComponent("index.js")
|
|
62
|
+
try "abc".write(to: file, atomically: true, encoding: .utf8)
|
|
63
|
+
try "ignored".write(to: bundle.appendingPathComponent("metadata.json"), atomically: true, encoding: .utf8)
|
|
64
|
+
|
|
65
|
+
let hashes = try BundleCryptoCore.hashDir(bundle.path)
|
|
66
|
+
XCTAssertEqual(hashes.count, 1)
|
|
67
|
+
XCTAssertEqual(hashes.first?.relativePath, "nested/bundle/index.js")
|
|
68
|
+
XCTAssertEqual(hashes.first?.sha256, "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad")
|
|
69
|
+
XCTAssertTrue(BundleCryptoCore.verifyDirAgainstHashes(dirPath: bundle.path, entries: hashes))
|
|
70
|
+
|
|
71
|
+
try "changed".write(to: file, atomically: true, encoding: .utf8)
|
|
72
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(dirPath: bundle.path, entries: hashes))
|
|
73
|
+
try FileManager.default.removeItem(at: file)
|
|
74
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(dirPath: bundle.path, entries: hashes))
|
|
75
|
+
|
|
76
|
+
try "abc".write(to: file, atomically: true, encoding: .utf8)
|
|
77
|
+
try "unlisted".write(to: bundle.appendingPathComponent("evil-metadata.json"), atomically: true, encoding: .utf8)
|
|
78
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(dirPath: bundle.path, entries: hashes))
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
func testExtractedPathSafetyRejectsSymlinks() throws {
|
|
83
|
+
try withTemporaryDirectory { root in
|
|
84
|
+
let destination = root.appendingPathComponent("bundle", isDirectory: true)
|
|
85
|
+
let outside = root.appendingPathComponent("outside.txt")
|
|
86
|
+
try FileManager.default.createDirectory(at: destination, withIntermediateDirectories: true)
|
|
87
|
+
try "outside".write(to: outside, atomically: true, encoding: .utf8)
|
|
88
|
+
try "inside".write(to: destination.appendingPathComponent("index.js"), atomically: true, encoding: .utf8)
|
|
89
|
+
XCTAssertTrue(BundleCryptoCore.validateExtractedPathSafety(destination.path))
|
|
90
|
+
|
|
91
|
+
try FileManager.default.createSymbolicLink(
|
|
92
|
+
at: destination.appendingPathComponent("escape"),
|
|
93
|
+
withDestinationURL: outside
|
|
94
|
+
)
|
|
95
|
+
XCTAssertFalse(BundleCryptoCore.validateExtractedPathSafety(destination.path))
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func testDirectoryHashesRejectExpectedPathsOutsideBundle() throws {
|
|
100
|
+
try withTemporaryDirectory { root in
|
|
101
|
+
let bundle = root.appendingPathComponent("bundle", isDirectory: true)
|
|
102
|
+
let outside = root.appendingPathComponent("outside.txt")
|
|
103
|
+
try FileManager.default.createDirectory(at: bundle, withIntermediateDirectories: true)
|
|
104
|
+
try "outside".write(to: outside, atomically: true, encoding: .utf8)
|
|
105
|
+
let wrongHash = String(repeating: "0", count: 64)
|
|
106
|
+
|
|
107
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(
|
|
108
|
+
dirPath: bundle.path,
|
|
109
|
+
entries: [.init(relativePath: "../outside.txt", sha256: wrongHash)]
|
|
110
|
+
))
|
|
111
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(
|
|
112
|
+
dirPath: bundle.path,
|
|
113
|
+
entries: [.init(relativePath: outside.path, sha256: wrongHash)]
|
|
114
|
+
))
|
|
115
|
+
try FileManager.default.createSymbolicLink(
|
|
116
|
+
at: bundle.appendingPathComponent("link.txt"),
|
|
117
|
+
withDestinationURL: outside
|
|
118
|
+
)
|
|
119
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(
|
|
120
|
+
dirPath: bundle.path,
|
|
121
|
+
entries: [.init(relativePath: "link.txt", sha256: wrongHash)]
|
|
122
|
+
))
|
|
123
|
+
XCTAssertFalse(BundleCryptoCore.verifyDirAgainstHashes(
|
|
124
|
+
dirPath: bundle.path,
|
|
125
|
+
entries: [.init(relativePath: "", sha256: wrongHash)]
|
|
126
|
+
))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
private func withTemporaryDirectory(_ body: (URL) throws -> Void) throws {
|
|
131
|
+
let root = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
|
132
|
+
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
|
|
133
|
+
defer { try? FileManager.default.removeItem(at: root) }
|
|
134
|
+
try body(root)
|
|
135
|
+
}
|
|
136
|
+
}
|
package/package.json
CHANGED