Switched to generating ephemeral key-pair per proxy request.
pull/79/head
Mikunj 5 years ago
parent 54c2e793ed
commit 8f7ba1407f

@ -2,7 +2,6 @@ import PromiseKit
internal class LokiHttpClient { internal class LokiHttpClient {
enum HttpError: LocalizedError { enum HttpError: LocalizedError {
/// Wraps TSNetworkManager failure callback params in a single throwable error
case networkError(code: Int, response: Any?, underlyingError: Error?) case networkError(code: Int, response: Any?, underlyingError: Error?)
public var errorDescription: String? { public var errorDescription: String? {
@ -14,7 +13,7 @@ internal class LokiHttpClient {
func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> Promise<Any> { func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> Promise<Any> {
return TSNetworkManager.shared().perform(request, withCompletionQueue: queue).map { $0.responseObject }.recover { error -> Promise<Any> in return TSNetworkManager.shared().perform(request, withCompletionQueue: queue).map { $0.responseObject }.recover { error -> Promise<Any> in
throw LokiHttpClient.HttpError.from(error: error) ?? error throw HttpError.from(error: error) ?? error
} }
} }
} }
@ -24,12 +23,13 @@ extension LokiHttpClient.HttpError {
if let error = error as? NetworkManagerError { if let error = error as? NetworkManagerError {
if case NetworkManagerError.taskError(_, let underlyingError) = error, let nsError = underlyingError as? NSError { if case NetworkManagerError.taskError(_, let underlyingError) = error, let nsError = underlyingError as? NSError {
var response = nsError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] var response = nsError.userInfo[AFNetworkingOperationFailingURLResponseDataErrorKey]
// Deserialize response if needed
if let data = response as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON { if let data = response as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON {
response = json response = json
} }
return LokiHttpClient.HttpError.networkError(code: error.statusCode, response: response, underlyingError: underlyingError) return LokiHttpClient.HttpError.networkError(code: error.statusCode, response: response, underlyingError: underlyingError)
} }
return LokiHttpClient.HttpError.networkError(code: error.statusCode, response: nil, underlyingError: nil) return LokiHttpClient.HttpError.networkError(code: error.statusCode, response: nil, underlyingError: error)
} }
return nil return nil
} }

@ -2,6 +2,7 @@ import PromiseKit
internal class LokiSnodeProxy: LokiHttpClient { internal class LokiSnodeProxy: LokiHttpClient {
internal let target: LokiAPITarget internal let target: LokiAPITarget
private let keyPair: ECKeyPair
internal enum Error : LocalizedError { internal enum Error : LocalizedError {
case invalidPublicKeys case invalidPublicKeys
@ -30,34 +31,20 @@ internal class LokiSnodeProxy: LokiHttpClient {
return manager return manager
}() }()
// MARK: - Ephemeral key
private var _kp: ECKeyPair
private var _lastGenerated: TimeInterval
private let keyPairRefreshTime: TimeInterval = 3 * 60 * 1000 // 3 minutes
// MARK: - Class functions // MARK: - Class functions
init(target: LokiAPITarget) { init(target: LokiAPITarget) {
self.target = target self.target = target
_kp = Curve25519.generateKeyPair() keyPair = Curve25519.generateKeyPair()
_lastGenerated = Date().timeIntervalSince1970
super.init() super.init()
} }
private func getKeyPair() -> ECKeyPair {
if (Date().timeIntervalSince1970 > _lastGenerated + keyPairRefreshTime) {
_kp = Curve25519.generateKeyPair()
_lastGenerated = Date().timeIntervalSince1970
}
return _kp
}
override func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> Promise<Any> { override func perform(_ request: TSRequest, withCompletionQueue queue: DispatchQueue = DispatchQueue.main) -> Promise<Any> {
guard let targetHexEncodedPublicKeys = target.publicKeys else { guard let targetHexEncodedPublicKeys = target.publicKeys else {
return Promise(error: Error.invalidPublicKeys) return Promise(error: Error.invalidPublicKeys)
} }
let keyPair = getKeyPair()
guard let symmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: Data(hex: targetHexEncodedPublicKeys.encryption), privateKey: keyPair.privateKey) else { guard let symmetricKey = try? Curve25519.generateSharedSecret(fromPublicKey: Data(hex: targetHexEncodedPublicKeys.encryption), privateKey: keyPair.privateKey) else {
return Promise(error: Error.failedToEncryptRequest) return Promise(error: Error.failedToEncryptRequest)
} }
@ -65,13 +52,18 @@ internal class LokiSnodeProxy: LokiHttpClient {
return LokiAPI.getRandomSnode().then { snode -> Promise<Any> in return LokiAPI.getRandomSnode().then { snode -> Promise<Any> in
let url = "\(snode.address):\(snode.port)/proxy" let url = "\(snode.address):\(snode.port)/proxy"
print("[Loki][Snode proxy] Proxy request to \(self.target) via \(snode).") print("[Loki][Snode proxy] Proxy request to \(self.target) via \(snode).")
var peepee = request.parameters let requestParams = try JSONSerialization.data(withJSONObject: request.parameters, options: [])
let jsonBodyData = try JSONSerialization.data(withJSONObject: peepee, options: []) let params: [String : Any] = [
let jsonBodyString = String(bytes: jsonBodyData, encoding: .utf8) "method" : request.httpMethod,
let params: [String : Any] = [ "method" : request.httpMethod, "body" : jsonBodyString, "headers" : self.getHeaders(request: request) ] "body" : String(bytes: requestParams, encoding: .utf8),
let jsonParams = try JSONSerialization.data(withJSONObject: params, options: []) "headers" : self.getHeaders(request: request)
let ivAndCipherText = try DiffieHellman.encrypt(jsonParams, using: symmetricKey) ]
let headers = [ "X-Sender-Public-Key" : keyPair.publicKey.hexadecimalString, "X-Target-Snode-Key" : targetHexEncodedPublicKeys.identification] let proxyParams = try JSONSerialization.data(withJSONObject: params, options: [])
let ivAndCipherText = try DiffieHellman.encrypt(proxyParams, using: symmetricKey)
let headers = [
"X-Sender-Public-Key" : self.keyPair.publicKey.hexadecimalString,
"X-Target-Snode-Key" : targetHexEncodedPublicKeys.identification
]
return self.post(url: url, body: ivAndCipherText, headers: headers, timeoutInterval: request.timeoutInterval) return self.post(url: url, body: ivAndCipherText, headers: headers, timeoutInterval: request.timeoutInterval)
}.map { response in }.map { response in
guard response is Data, let cipherText = Data(base64Encoded: response as! Data) else { guard response is Data, let cipherText = Data(base64Encoded: response as! Data) else {
@ -89,10 +81,9 @@ internal class LokiSnodeProxy: LokiHttpClient {
let success = (200..<300).contains(code) let success = (200..<300).contains(code)
var body: Any? = nil var body: Any? = nil
if let string = json["body"] as? String { if let string = json["body"] as? String {
body = string
if let jsonBody = try? JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: .allowFragments) as? [String: Any] { if let jsonBody = try? JSONSerialization.jsonObject(with: string.data(using: .utf8)!, options: .allowFragments) as? [String: Any] {
body = jsonBody body = jsonBody
} else {
body = string
} }
} }
@ -107,6 +98,8 @@ internal class LokiSnodeProxy: LokiHttpClient {
} }
} }
// MARK:- Private functions
private func getHeaders(request: TSRequest) -> [String: Any] { private func getHeaders(request: TSRequest) -> [String: Any] {
guard let headers = request.allHTTPHeaderFields else { guard let headers = request.allHTTPHeaderFields else {
return [:] return [:]

@ -193,13 +193,7 @@ NSString *const kNSNotificationName_IsCensorshipCircumventionActiveDidChange =
sessionManager.securityPolicy = securityPolicy; sessionManager.securityPolicy = securityPolicy;
sessionManager.requestSerializer = [AFJSONRequestSerializer serializer]; sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];
sessionManager.requestSerializer.HTTPShouldHandleCookies = NO; sessionManager.requestSerializer.HTTPShouldHandleCookies = NO;
sessionManager.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
// We could get JSON or text responses
NSArray *serializers = @[
[AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments],
[AFHTTPResponseSerializer serializer]
];
sessionManager.responseSerializer = [AFCompoundResponseSerializer compoundSerializerWithResponseSerializers:serializers];
return sessionManager; return sessionManager;
} }

Loading…
Cancel
Save