-
Notifications
You must be signed in to change notification settings - Fork 221
/
OhttpManager.swift
107 lines (87 loc) · 4.21 KB
/
OhttpManager.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Foundation
class OhttpManager {
// The OhttpManager communicates with the relay and key server using
// URLSession.shared.data unless an alternative networking method is
// provided with this signature.
typealias NetworkFunction = (_: URLRequest) async throws -> (Data, URLResponse)
// Global cache to caching Gateway encryption keys. Stale entries are
// ignored and on Gateway errors the key used should be purged and retrieved
// again next at next network attempt.
static var keyCache = [URL: ([UInt8], Date)]()
private var configUrl: String
private var relayUrl: String
private var network: NetworkFunction
init(configUrl: String,
relayUrl: String,
network: @escaping NetworkFunction = URLSession.shared.data)
{
self.configUrl = configUrl
self.relayUrl = relayUrl
self.network = network
}
private func fetchKey(url: URL) async throws -> [UInt8] {
let request = URLRequest(url: url)
if let (data, response) = try? await network(request) {
if (response as! HTTPURLResponse).statusCode == 200 {
return [UInt8](data)
}
}
throw OhttpError.KeyFetchFailed(message: "Failed to fetch encryption key")
}
private func keyForGateway(gatewayConfigUrl: URL, ttl: TimeInterval) async throws -> [UInt8] {
if let (data, timestamp) = Self.keyCache[gatewayConfigUrl] {
if Date.now < timestamp + ttl {
// Cache Hit!
return data
}
Self.keyCache.removeValue(forKey: gatewayConfigUrl)
}
let data = try await fetchKey(url: gatewayConfigUrl)
Self.keyCache[gatewayConfigUrl] = (data, Date.now)
return data
}
private func invalidateKey() {
Self.keyCache.removeValue(forKey: URL(string: configUrl)!)
}
func data(for request: URLRequest) async throws -> (Data, HTTPURLResponse) {
// Get the encryption keys for Gateway
let config = try await keyForGateway(gatewayConfigUrl: URL(string: configUrl)!,
ttl: TimeInterval(3600))
// Create an encryption session for a request-response round-trip
let session = try OhttpSession(config: config)
// Encapsulate the URLRequest for the Target
let encoded = try session.encapsulate(method: request.httpMethod ?? "GET",
scheme: request.url!.scheme!,
server: request.url!.host!,
endpoint: request.url!.path,
headers: request.allHTTPHeaderFields ?? [:],
payload: [UInt8](request.httpBody ?? Data()))
// Request from Client to Relay
var request = URLRequest(url: URL(string: relayUrl)!)
request.httpMethod = "POST"
request.setValue("message/ohttp-req", forHTTPHeaderField: "Content-Type")
request.httpBody = Data(encoded)
let (data, response) = try await network(request)
let httpResponse = response as! HTTPURLResponse
// Decapsulation failures have these codes, so invalidate any cached
// keys in case the gateway has changed them.
if httpResponse.statusCode == 400 ||
httpResponse.statusCode == 401
{
invalidateKey()
}
guard httpResponse.statusCode == 200 else {
throw OhttpError.RelayFailed(message: "Network errors communicating with Relay / Gateway")
}
// Decapsulate the Target response into a HTTPURLResponse
let message = try session.decapsulate(encoded: [UInt8](data))
return (Data(message.payload),
HTTPURLResponse(url: request.url!,
statusCode: Int(message.statusCode),
httpVersion: "HTTP/1.1",
headerFields: message.headers)!)
}
}