pull/148/head
gmbnt 4 years ago
parent 49839900bc
commit ec660e6017

@ -5,6 +5,10 @@ extension OnionRequestAPI {
internal typealias EncryptionResult = (ciphertext: Data, symmetricKey: Data, ephemeralPublicKey: Data)
private static func getQueue() -> DispatchQueue {
return DispatchQueue(label: UUID().uuidString, qos: .userInitiated)
}
/// Returns `size` bytes of random data generated using the default random number generator. See
/// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information.
private static func getRandomData(ofSize size: UInt) throws -> Data {
@ -43,9 +47,10 @@ extension OnionRequestAPI {
/// Encrypts `payload` for `snode` and returns the result. Use this to build the core of an onion request.
internal static func encrypt(_ payload: Data, forTargetSnode snode: LokiAPITarget) -> Promise<EncryptionResult> {
let (promise, seal) = Promise<EncryptionResult>.pending()
workQueue.async {
let parameters: JSON = [ "body" : payload ]
getQueue().async {
let parameters: JSON = [ "body" : payload.base64EncodedString() ]
do {
guard JSONSerialization.isValidJSONObject(parameters) else { return seal.reject(Error.invalidJSON) }
let plaintext = try JSONSerialization.data(withJSONObject: parameters, options: [])
let result = try encrypt(plaintext, forSnode: snode)
seal.fulfill(result)
@ -59,13 +64,14 @@ extension OnionRequestAPI {
/// Encrypts the previous encryption result (i.e. that of the hop after this one) for the given hop. Use this to build the layers of an onion request.
internal static func encryptHop(from snode1: LokiAPITarget, to snode2: LokiAPITarget, using previousEncryptionResult: EncryptionResult) -> Promise<EncryptionResult> {
let (promise, seal) = Promise<EncryptionResult>.pending()
workQueue.async {
getQueue().async {
let parameters: JSON = [
"ciphertext" : previousEncryptionResult.ciphertext.base64EncodedString(),
"ephemeral_key" : previousEncryptionResult.ephemeralPublicKey.toHexString(),
"destination" : snode2.publicKeySet!.ed25519Key
]
do {
guard JSONSerialization.isValidJSONObject(parameters) else { return seal.reject(Error.invalidJSON) }
let plaintext = try JSONSerialization.data(withJSONObject: parameters, options: [])
let result = try encrypt(plaintext, forSnode: snode1)
seal.fulfill(result)

@ -10,7 +10,7 @@ internal enum OnionRequestAPI {
internal static var guardSnodes: Set<LokiAPITarget> = []
internal static var paths: Set<Path> = []
/// - Note: Exposed for testing purposes.
internal static let workQueue = DispatchQueue.global() // TODO: We should probably move away from using the global queue for this
internal static let workQueue = DispatchQueue(label: "OnionRequestAPI.workQueue", qos: .userInitiated)
// MARK: Settings
private static let guardSnodeCount: UInt = 3
@ -32,6 +32,7 @@ internal enum OnionRequestAPI {
internal enum Error : LocalizedError {
case generic
case insufficientSnodes
case invalidJSON
case randomDataGenerationFailed
case snodePublicKeySetMissing
@ -39,6 +40,7 @@ internal enum OnionRequestAPI {
switch self {
case .generic: return "An error occurred."
case .insufficientSnodes: return "Couldn't find enough snodes to build a path."
case .invalidJSON: return "Invalid JSON."
case .randomDataGenerationFailed: return "Couldn't generate random data."
case .snodePublicKeySetMissing: return "Missing snode public key set."
}
@ -51,11 +53,21 @@ internal enum OnionRequestAPI {
// MARK: Private API
/// Tests the given snode. The returned promise errors out if the snode is faulty; the promise is fulfilled otherwise.
private static func testSnode(_ snode: LokiAPITarget) -> Promise<Void> {
print("[Loki] [Onion Request API] Testing snode: \(snode).")
let hexEncodedPublicKey = getUserHexEncodedPublicKey()
let parameters: JSON = [ "pubKey" : hexEncodedPublicKey ]
let timeout: TimeInterval = 10 // Use a shorter timeout for testing
return LokiAPI.invoke(.getSwarm, on: snode, associatedWith: hexEncodedPublicKey, parameters: parameters, timeout: timeout).map(on: workQueue) { _ in }
let (promise, seal) = Promise<Void>.pending()
let queue = DispatchQueue(label: UUID().uuidString, qos: .userInitiated) // No need to block the work queue for this
queue.async {
print("[Loki] [Onion Request API] Testing snode: \(snode).")
let hexEncodedPublicKey = getUserHexEncodedPublicKey()
let parameters: JSON = [ "pubKey" : hexEncodedPublicKey ]
let timeout: TimeInterval = 6 // Use a shorter timeout for testing
// TODO: Move LokiAPI away from using TSNetworkManager so that we can be smarter about threading
LokiAPI.invoke(.getSwarm, on: snode, associatedWith: hexEncodedPublicKey, parameters: parameters, timeout: timeout).done(on: queue) { _ in
seal.fulfill(())
}.catch { error in
seal.reject(error)
}
}
return promise
}
/// Finds `guardSnodeCount` guard snodes to use for path building. The returned promise errors out with `Error.insufficientSnodes`
@ -108,9 +120,10 @@ internal enum OnionRequestAPI {
let result: Set<Path> = Set(guardSnodes.map { guardSnode in
// Force unwrapping is safe because of the minSnodeCount check above
// randomElement() uses the system's default random generator, which is cryptographically secure
return [ guardSnode ] + (0..<(pathSize - 1)).map { _ in unusedSnodes.randomElement()! }
let result = [ guardSnode ] + (0..<(pathSize - 1)).map { _ in unusedSnodes.randomElement()! }
print("[Loki] [Onion Request API] Built new onion request path: \(result.prettifiedDescription).")
return result
})
print("[Loki] [Onion Request API] Built new onion request paths: \(result.map { "\($0.description)" }.joined(separator: ", "))")
return result
}
}
@ -176,6 +189,7 @@ internal enum OnionRequestAPI {
request.httpBody = onion
request.timeoutInterval = timeout
let (promise, seal) = Promise<Any>.pending()
print("[Loki] [Onion Request API] Sending onion request.")
let task = urlSession.dataTask(with: request) { response, result, error in
if let error = error {
seal.reject(error)

@ -0,0 +1,7 @@
public extension Array where Element : CustomStringConvertible {
public var prettifiedDescription: String {
return "[ " + map { $0.description }.joined(separator: ", ") + " ]"
}
}
Loading…
Cancel
Save