@genation/bridge-peer 0.1.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/GenationBridgePeer.podspec +17 -0
- package/LICENSE +190 -0
- package/README.md +66 -0
- package/package.json +59 -0
- package/react-native/android/build.gradle +24 -0
- package/react-native/android/src/main/java/com/genation/bridge/GenationBridgePeerModule.kt +182 -0
- package/react-native/android/src/main/java/com/genation/bridge/GenationBridgePeerPackage.kt +15 -0
- package/react-native/android/src/main/jniLibs/arm64-v8a/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/android/src/main/jniLibs/armeabi-v7a/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/android/src/main/jniLibs/x86/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/android/src/main/jniLibs/x86_64/libgenation_bridge_crypto_uniffi.so +0 -0
- package/react-native/generated/kotlin/uniffi/genation_bridge_crypto_uniffi/genation_bridge_crypto_uniffi.kt +2006 -0
- package/react-native/generated/swift/GenationBridgeCryptoMobile.swift +1072 -0
- package/react-native/generated/swift/GenationBridgeCryptoMobileFFI.h +703 -0
- package/react-native/generated/swift/GenationBridgeCryptoMobileFFI.modulemap +7 -0
- package/react-native/ios/GenationBridgePeer.m +14 -0
- package/react-native/ios/GenationBridgePeer.swift +234 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/Info.plist +48 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64/Headers/GenationBridgeCryptoMobileFFI.h +703 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64/Headers/module.modulemap +7 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64/libgenation_bridge_crypto_uniffi.a +0 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64_x86_64-simulator/Headers/GenationBridgeCryptoMobileFFI.h +703 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64_x86_64-simulator/Headers/module.modulemap +7 -0
- package/react-native/native/GenationBridgeCryptoMobile.xcframework/ios-arm64_x86_64-simulator/libgenation_bridge_crypto_uniffi.a +0 -0
- package/react-native.config.js +8 -0
- package/src/core/codec.d.ts +5 -0
- package/src/core/codec.js +46 -0
- package/src/core/errors.d.ts +11 -0
- package/src/core/errors.js +27 -0
- package/src/core/identity.d.ts +16 -0
- package/src/core/identity.js +1 -0
- package/src/core/lifecycle.d.ts +4 -0
- package/src/core/lifecycle.js +1 -0
- package/src/core/mod.d.ts +6 -0
- package/src/core/mod.js +3 -0
- package/src/core/peer.d.ts +67 -0
- package/src/core/peer.js +373 -0
- package/src/core/runtime.d.ts +78 -0
- package/src/core/runtime.js +0 -0
- package/src/node/crypto-core/javascript/mod.d.ts +8 -0
- package/src/node/crypto-core/javascript/mod.js +24 -0
- package/src/node/crypto-core/javascript/provider.d.ts +2 -0
- package/src/node/crypto-core/javascript/provider.js +43 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm.d.ts +122 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm.js +433 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm_bg.wasm +0 -0
- package/src/node/crypto-core/pkg/wasm/genation_bridge_crypto_wasm_bg.wasm.d.ts +25 -0
- package/src/node/encoding.js +1 -0
- package/src/node/file-key-store.d.ts +6 -0
- package/src/node/file-key-store.js +111 -0
- package/src/node/identity-provider.d.ts +8 -0
- package/src/node/identity-provider.js +57 -0
- package/src/node/identity.js +45 -0
- package/src/node/mod.d.ts +16 -0
- package/src/node/mod.js +17 -0
- package/src/node/relay-runtime.d.ts +11 -0
- package/src/node/relay-runtime.js +11 -0
- package/src/react-native/identity-provider.d.ts +4 -0
- package/src/react-native/identity-provider.js +125 -0
- package/src/react-native/lifecycle.d.ts +8 -0
- package/src/react-native/lifecycle.js +9 -0
- package/src/react-native/mod.d.ts +20 -0
- package/src/react-native/mod.js +36 -0
- package/src/react-native/native-module.d.ts +17 -0
- package/src/react-native/native-module.js +9 -0
- package/src/relay/encoding.js +32 -0
- package/src/relay/frame-channel.d.ts +1 -0
- package/src/relay/frame-channel.js +110 -0
- package/src/relay/identity.d.ts +27 -0
- package/src/relay/identity.js +0 -0
- package/src/relay/runtime.d.ts +8 -0
- package/src/relay/runtime.js +900 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(GenationBridgePeer, NSObject)
|
|
4
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(loadOrCreateIdentity:(NSString *)projectId peerId:(NSString *)peerId)
|
|
5
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(signConnectionProof:(NSString *)handle inputJson:(NSString *)inputJson)
|
|
6
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(createInitiator:(NSString *)handle prologue:(NSString *)prologue responderPublicKey:(NSString *)publicKey)
|
|
7
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(createResponder:(NSString *)handle prologue:(NSString *)prologue initiatorPublicKey:(NSString *)publicKey)
|
|
8
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(beginHandshake:(NSString *)handle)
|
|
9
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(finishHandshake:(NSString *)handle message:(NSString *)message)
|
|
10
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(acceptHandshake:(NSString *)handle message:(NSString *)message)
|
|
11
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(encrypt:(NSString *)handle aad:(NSString *)aad plaintext:(NSString *)plaintext)
|
|
12
|
+
RCT_EXTERN__BLOCKING_SYNCHRONOUS_METHOD(decrypt:(NSString *)handle aad:(NSString *)aad ciphertext:(NSString *)ciphertext)
|
|
13
|
+
RCT_EXTERN_METHOD(closeSession:(NSString *)handle)
|
|
14
|
+
@end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import CryptoKit
|
|
2
|
+
import Foundation
|
|
3
|
+
import React
|
|
4
|
+
import Security
|
|
5
|
+
|
|
6
|
+
private struct StoredIdentity: Codable {
|
|
7
|
+
let connectionPrivateKey: Data
|
|
8
|
+
let connectionPublicKey: Data
|
|
9
|
+
let noiseStaticPrivateKey: Data
|
|
10
|
+
let noiseStaticPublicKey: Data
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private enum NativeSession {
|
|
14
|
+
case initiator(MobileInitiator)
|
|
15
|
+
case responder(MobileResponder)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@objc(GenationBridgePeer)
|
|
19
|
+
final class GenationBridgePeer: NSObject {
|
|
20
|
+
private let lock = NSLock()
|
|
21
|
+
private var identities: [String: StoredIdentity] = [:]
|
|
22
|
+
private var sessions: [String: NativeSession] = [:]
|
|
23
|
+
private let service = "com.genation.bridge.peer.identity.v1"
|
|
24
|
+
|
|
25
|
+
@objc static func requiresMainQueueSetup() -> Bool { false }
|
|
26
|
+
|
|
27
|
+
private func base64url(_ data: Data) -> String {
|
|
28
|
+
data.base64EncodedString().replacingOccurrences(of: "+", with: "-")
|
|
29
|
+
.replacingOccurrences(of: "/", with: "_")
|
|
30
|
+
.replacingOccurrences(of: "=", with: "")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
private func decode64(_ value: String) throws -> Data {
|
|
34
|
+
var encoded = value.replacingOccurrences(of: "-", with: "+")
|
|
35
|
+
.replacingOccurrences(of: "_", with: "/")
|
|
36
|
+
encoded += String(repeating: "=", count: (4 - encoded.count % 4) % 4)
|
|
37
|
+
guard let data = Data(base64Encoded: encoded) else { throw NativeError.invalid }
|
|
38
|
+
return data
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private func identityKey(_ projectId: String, _ peerId: String) -> String {
|
|
42
|
+
let digest = SHA256.hash(data: Data("\(projectId)\u{0}\(peerId)".utf8))
|
|
43
|
+
return digest.map { String(format: "%02x", $0) }.joined()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private func readIdentity(_ key: String) throws -> StoredIdentity? {
|
|
47
|
+
let query: [CFString: Any] = [
|
|
48
|
+
kSecClass: kSecClassGenericPassword,
|
|
49
|
+
kSecAttrService: service,
|
|
50
|
+
kSecAttrAccount: key,
|
|
51
|
+
kSecReturnData: true,
|
|
52
|
+
kSecMatchLimit: kSecMatchLimitOne,
|
|
53
|
+
]
|
|
54
|
+
var result: CFTypeRef?
|
|
55
|
+
let status = SecItemCopyMatching(query as CFDictionary, &result)
|
|
56
|
+
if status == errSecItemNotFound { return nil }
|
|
57
|
+
guard status == errSecSuccess, let data = result as? Data else {
|
|
58
|
+
throw NativeError.storage
|
|
59
|
+
}
|
|
60
|
+
return try JSONDecoder().decode(StoredIdentity.self, from: data)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private func createIdentity(_ key: String) throws -> StoredIdentity {
|
|
64
|
+
let generated = try generateMobileIdentity()
|
|
65
|
+
let identity = StoredIdentity(
|
|
66
|
+
connectionPrivateKey: generated.connectionPrivateKey,
|
|
67
|
+
connectionPublicKey: generated.connectionPublicKey,
|
|
68
|
+
noiseStaticPrivateKey: generated.noiseStaticPrivateKey,
|
|
69
|
+
noiseStaticPublicKey: generated.noiseStaticPublicKey
|
|
70
|
+
)
|
|
71
|
+
let encoded = try JSONEncoder().encode(identity)
|
|
72
|
+
let query: [CFString: Any] = [
|
|
73
|
+
kSecClass: kSecClassGenericPassword,
|
|
74
|
+
kSecAttrService: service,
|
|
75
|
+
kSecAttrAccount: key,
|
|
76
|
+
kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
|
77
|
+
kSecValueData: encoded,
|
|
78
|
+
]
|
|
79
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
80
|
+
if status == errSecDuplicateItem, let existing = try readIdentity(key) { return existing }
|
|
81
|
+
guard status == errSecSuccess else { throw NativeError.storage }
|
|
82
|
+
return identity
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private func identity(_ handle: String) throws -> StoredIdentity {
|
|
86
|
+
guard let value = identities[handle] else { throw NativeError.invalid }
|
|
87
|
+
return value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
@objc func loadOrCreateIdentity(
|
|
91
|
+
_ projectId: String,
|
|
92
|
+
peerId: String
|
|
93
|
+
) -> NSDictionary {
|
|
94
|
+
lock.lock(); defer { lock.unlock() }
|
|
95
|
+
do {
|
|
96
|
+
let handle = "identity:\(identityKey(projectId, peerId))"
|
|
97
|
+
let key = String(handle.dropFirst("identity:".count))
|
|
98
|
+
let value = try readIdentity(key) ?? createIdentity(key)
|
|
99
|
+
identities[handle] = value
|
|
100
|
+
return [
|
|
101
|
+
"identityHandle": handle,
|
|
102
|
+
"connectionPublicKey": base64url(value.connectionPublicKey),
|
|
103
|
+
"noiseStaticPublicKey": base64url(value.noiseStaticPublicKey),
|
|
104
|
+
]
|
|
105
|
+
} catch { return [:] }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@objc func signConnectionProof(_ handle: String, inputJson: String) -> String {
|
|
109
|
+
lock.lock(); defer { lock.unlock() }
|
|
110
|
+
do {
|
|
111
|
+
let value = try identity(handle)
|
|
112
|
+
guard
|
|
113
|
+
let input = try JSONSerialization.jsonObject(with: Data(inputJson.utf8)) as? [String: Any],
|
|
114
|
+
let grant = input["grant"] as? String,
|
|
115
|
+
let expiresAt = input["challengeExpiresAt"] as? Double
|
|
116
|
+
else { throw NativeError.invalid }
|
|
117
|
+
let now = Int(Date().timeIntervalSince1970)
|
|
118
|
+
let payload: [String: Any] = [
|
|
119
|
+
"version": "proof.v1",
|
|
120
|
+
"projectId": input["projectId"]!,
|
|
121
|
+
"peerId": input["peerId"]!,
|
|
122
|
+
"connectionId": input["connectionId"]!,
|
|
123
|
+
"challenge": input["challenge"]!,
|
|
124
|
+
"grantSha256": base64url(Data(SHA256.hash(data: Data(grant.utf8)))),
|
|
125
|
+
"issuedAt": now,
|
|
126
|
+
"expiresAt": min(now + 15, Int(expiresAt / 1000)),
|
|
127
|
+
]
|
|
128
|
+
let header = base64url(Data("{\"alg\":\"EdDSA\"}".utf8))
|
|
129
|
+
let body = base64url(try JSONSerialization.data(withJSONObject: payload))
|
|
130
|
+
let signingInput = Data("\(header).\(body)".utf8)
|
|
131
|
+
let signature = try signMobileEd25519(
|
|
132
|
+
privateKey: value.connectionPrivateKey,
|
|
133
|
+
message: signingInput
|
|
134
|
+
)
|
|
135
|
+
return "\(header).\(body).\(base64url(signature))"
|
|
136
|
+
} catch { return "" }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@objc func createInitiator(
|
|
140
|
+
_ identityHandle: String,
|
|
141
|
+
prologue: String,
|
|
142
|
+
responderPublicKey: String
|
|
143
|
+
) -> String {
|
|
144
|
+
lock.lock(); defer { lock.unlock() }
|
|
145
|
+
do {
|
|
146
|
+
let value = try identity(identityHandle)
|
|
147
|
+
let handle = "session:\(UUID().uuidString)"
|
|
148
|
+
sessions[handle] = .initiator(try MobileInitiator(
|
|
149
|
+
prologue: decode64(prologue),
|
|
150
|
+
localPrivateKey: value.noiseStaticPrivateKey,
|
|
151
|
+
responderPublicKey: decode64(responderPublicKey)
|
|
152
|
+
))
|
|
153
|
+
return handle
|
|
154
|
+
} catch { return "" }
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
@objc func createResponder(
|
|
158
|
+
_ identityHandle: String,
|
|
159
|
+
prologue: String,
|
|
160
|
+
initiatorPublicKey: String
|
|
161
|
+
) -> String {
|
|
162
|
+
lock.lock(); defer { lock.unlock() }
|
|
163
|
+
do {
|
|
164
|
+
let value = try identity(identityHandle)
|
|
165
|
+
let handle = "session:\(UUID().uuidString)"
|
|
166
|
+
sessions[handle] = .responder(try MobileResponder(
|
|
167
|
+
prologue: decode64(prologue),
|
|
168
|
+
localPrivateKey: value.noiseStaticPrivateKey,
|
|
169
|
+
initiatorPublicKey: decode64(initiatorPublicKey)
|
|
170
|
+
))
|
|
171
|
+
return handle
|
|
172
|
+
} catch { return "" }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@objc func beginHandshake(_ handle: String) -> String {
|
|
176
|
+
lock.lock(); defer { lock.unlock() }
|
|
177
|
+
guard case .initiator(let session) = sessions[handle] else { return "" }
|
|
178
|
+
return (try? base64url(session.beginHandshake())) ?? ""
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@objc func finishHandshake(_ handle: String, message: String) -> Bool {
|
|
182
|
+
lock.lock(); defer { lock.unlock() }
|
|
183
|
+
guard case .initiator(let session) = sessions[handle] else { return false }
|
|
184
|
+
do {
|
|
185
|
+
try session.finishHandshake(message: decode64(message))
|
|
186
|
+
return true
|
|
187
|
+
} catch {
|
|
188
|
+
sessions.removeValue(forKey: handle)
|
|
189
|
+
try? session.closeSession()
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@objc func acceptHandshake(_ handle: String, message: String) -> String {
|
|
195
|
+
lock.lock(); defer { lock.unlock() }
|
|
196
|
+
guard case .responder(let session) = sessions[handle] else { return "" }
|
|
197
|
+
return (try? base64url(session.acceptHandshake(message: decode64(message)))) ?? ""
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@objc func encrypt(_ handle: String, aad: String, plaintext: String) -> String {
|
|
201
|
+
lock.lock(); defer { lock.unlock() }
|
|
202
|
+
do {
|
|
203
|
+
let aadData = try decode64(aad); let plainData = try decode64(plaintext)
|
|
204
|
+
switch sessions[handle] {
|
|
205
|
+
case .initiator(let value): return base64url(try value.encrypt(aad: aadData, plaintext: plainData))
|
|
206
|
+
case .responder(let value): return base64url(try value.encrypt(aad: aadData, plaintext: plainData))
|
|
207
|
+
case .none: return ""
|
|
208
|
+
}
|
|
209
|
+
} catch { return "" }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@objc func decrypt(_ handle: String, aad: String, ciphertext: String) -> String {
|
|
213
|
+
lock.lock(); defer { lock.unlock() }
|
|
214
|
+
do {
|
|
215
|
+
let aadData = try decode64(aad); let cipherData = try decode64(ciphertext)
|
|
216
|
+
switch sessions[handle] {
|
|
217
|
+
case .initiator(let value): return base64url(try value.decrypt(aad: aadData, ciphertext: cipherData))
|
|
218
|
+
case .responder(let value): return base64url(try value.decrypt(aad: aadData, ciphertext: cipherData))
|
|
219
|
+
case .none: return ""
|
|
220
|
+
}
|
|
221
|
+
} catch { return "" }
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
@objc func closeSession(_ handle: String) {
|
|
225
|
+
lock.lock(); defer { lock.unlock() }
|
|
226
|
+
switch sessions.removeValue(forKey: handle) {
|
|
227
|
+
case .initiator(let value): try? value.closeSession()
|
|
228
|
+
case .responder(let value): try? value.closeSession()
|
|
229
|
+
case .none: break
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private enum NativeError: Error { case invalid, storage }
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>AvailableLibraries</key>
|
|
6
|
+
<array>
|
|
7
|
+
<dict>
|
|
8
|
+
<key>BinaryPath</key>
|
|
9
|
+
<string>libgenation_bridge_crypto_uniffi.a</string>
|
|
10
|
+
<key>HeadersPath</key>
|
|
11
|
+
<string>Headers</string>
|
|
12
|
+
<key>LibraryIdentifier</key>
|
|
13
|
+
<string>ios-arm64_x86_64-simulator</string>
|
|
14
|
+
<key>LibraryPath</key>
|
|
15
|
+
<string>libgenation_bridge_crypto_uniffi.a</string>
|
|
16
|
+
<key>SupportedArchitectures</key>
|
|
17
|
+
<array>
|
|
18
|
+
<string>arm64</string>
|
|
19
|
+
<string>x86_64</string>
|
|
20
|
+
</array>
|
|
21
|
+
<key>SupportedPlatform</key>
|
|
22
|
+
<string>ios</string>
|
|
23
|
+
<key>SupportedPlatformVariant</key>
|
|
24
|
+
<string>simulator</string>
|
|
25
|
+
</dict>
|
|
26
|
+
<dict>
|
|
27
|
+
<key>BinaryPath</key>
|
|
28
|
+
<string>libgenation_bridge_crypto_uniffi.a</string>
|
|
29
|
+
<key>HeadersPath</key>
|
|
30
|
+
<string>Headers</string>
|
|
31
|
+
<key>LibraryIdentifier</key>
|
|
32
|
+
<string>ios-arm64</string>
|
|
33
|
+
<key>LibraryPath</key>
|
|
34
|
+
<string>libgenation_bridge_crypto_uniffi.a</string>
|
|
35
|
+
<key>SupportedArchitectures</key>
|
|
36
|
+
<array>
|
|
37
|
+
<string>arm64</string>
|
|
38
|
+
</array>
|
|
39
|
+
<key>SupportedPlatform</key>
|
|
40
|
+
<string>ios</string>
|
|
41
|
+
</dict>
|
|
42
|
+
</array>
|
|
43
|
+
<key>CFBundlePackageType</key>
|
|
44
|
+
<string>XFWK</string>
|
|
45
|
+
<key>XCFrameworkFormatVersion</key>
|
|
46
|
+
<string>1.0</string>
|
|
47
|
+
</dict>
|
|
48
|
+
</plist>
|