Fixed an issue with blinding being switched on

Fixed an issue where a user that was part of an open group before blinding was enabled wouldn't be able to access the group once blinding was switched on
Updated the code to better support errors sent from SOGS as bencoded data
pull/732/head
Morgan Pretty 3 years ago
parent c934415355
commit f4d4e7d810

@ -227,6 +227,7 @@ public enum OpenGroupAPI {
public static func capabilities(
_ db: Database,
server: String,
forceBlinded: Bool = false,
using dependencies: SMKDependencies = SMKDependencies()
) -> Promise<(OnionRequestResponseInfoType, Capabilities)> {
return OpenGroupAPI
@ -236,6 +237,7 @@ public enum OpenGroupAPI {
server: server,
endpoint: .capabilities
),
forceBlinded: forceBlinded,
using: dependencies
)
.decoded(as: Capabilities.self, on: OpenGroupAPI.workQueue, using: dependencies)
@ -1260,6 +1262,7 @@ public enum OpenGroupAPI {
messageBytes: Bytes,
for serverName: String,
fallbackSigningType signingType: SessionId.Prefix,
forceBlinded: Bool = false,
using dependencies: SMKDependencies = SMKDependencies()
) -> (publicKey: String, signature: Bytes)? {
guard
@ -1279,7 +1282,7 @@ public enum OpenGroupAPI {
.defaulting(to: [])
// If we have no capabilities or if the server supports blinded keys then sign using the blinded key
if capabilities.isEmpty || capabilities.contains(.blind) {
if forceBlinded || capabilities.isEmpty || capabilities.contains(.blind) {
guard let blindedKeyPair: Box.KeyPair = dependencies.sodium.blindedKeyPair(serverPublicKey: serverPublicKey, edKeyPair: userEdKeyPair, genericHash: dependencies.genericHash) else {
return nil
}
@ -1326,6 +1329,7 @@ public enum OpenGroupAPI {
request: URLRequest,
for serverName: String,
with serverPublicKey: String,
forceBlinded: Bool = false,
using dependencies: SMKDependencies = SMKDependencies()
) -> URLRequest? {
guard let url: URL = request.url else { return nil }
@ -1366,7 +1370,7 @@ public enum OpenGroupAPI {
.appending(contentsOf: bodyHash ?? [])
/// Sign the above message
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: messageBytes, for: serverName, fallbackSigningType: .unblinded, using: dependencies) else {
guard let signResult: (publicKey: String, signature: Bytes) = sign(db, messageBytes: messageBytes, for: serverName, fallbackSigningType: .unblinded, forceBlinded: forceBlinded, using: dependencies) else {
return nil
}
@ -1386,6 +1390,7 @@ public enum OpenGroupAPI {
private static func send<T: Encodable>(
_ db: Database,
request: Request<T, Endpoint>,
forceBlinded: Bool = false,
using dependencies: SMKDependencies = SMKDependencies()
) -> Promise<(OnionRequestResponseInfoType, Data?)> {
let urlRequest: URLRequest
@ -1406,7 +1411,7 @@ public enum OpenGroupAPI {
guard let publicKey: String = maybePublicKey else { return Promise(error: OpenGroupAPIError.noPublicKey) }
// Attempt to sign the request with the new auth
guard let signedRequest: URLRequest = sign(db, request: urlRequest, for: request.server, with: publicKey, using: dependencies) else {
guard let signedRequest: URLRequest = sign(db, request: urlRequest, for: request.server, with: publicKey, forceBlinded: forceBlinded, using: dependencies) else {
return Promise(error: OpenGroupAPIError.signingFailed)
}

@ -16,6 +16,9 @@ public enum OnionRequestAPIError: LocalizedError {
switch self {
case .httpRequestFailedAtDestination(let statusCode, let data, let destination):
if statusCode == 429 { return "Rate limited." }
if let processedResponseBodyData: Data = OnionRequestAPI.process(bencodedData: data)?.body, let errorResponse: String = String(data: processedResponseBodyData, encoding: .utf8) {
return "HTTP request failed at destination (\(destination)) with status code: \(statusCode), error body: \(errorResponse)."
}
if let errorResponse: String = String(data: data, encoding: .utf8) {
return "HTTP request failed at destination (\(destination)) with status code: \(statusCode), error body: \(errorResponse)."
}

@ -700,72 +700,86 @@ public enum OnionRequestAPI: OnionRequestAPIType {
do {
let data: Data = try AESGCM.decrypt(responseData, with: destinationSymmetricKey)
// The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break the data into
// parts to properly process it
guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else {
return seal.reject(HTTP.Error.invalidResponse)
}
let stringParts: [String.SubSequence] = responseString.split(separator: ":")
guard stringParts.count > 1, let infoLength: Int = Int(stringParts[0].suffix(from: stringParts[0].index(stringParts[0].startIndex, offsetBy: 1))) else {
return seal.reject(HTTP.Error.invalidResponse)
}
let infoStringStartIndex: String.Index = responseString.index(responseString.startIndex, offsetBy: "l\(infoLength):".count)
let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength)
let infoString: String = String(responseString[infoStringStartIndex..<infoStringEndIndex])
guard let infoStringData: Data = infoString.data(using: .utf8), let responseInfo: ResponseInfo = try? JSONDecoder().decode(ResponseInfo.self, from: infoStringData) else {
// Process the bencoded response
guard let processedResponse: (info: ResponseInfo, body: Data?) = process(bencodedData: data) else {
return seal.reject(HTTP.Error.invalidResponse)
}
// Custom handle a clock out of sync error (v4 returns '425' but included the '406' just in case)
guard responseInfo.code != 406 && responseInfo.code != 425 else {
// Custom handle a clock out of sync error (v4 returns '425' but included the '406'
// just in case)
guard processedResponse.info.code != 406 && processedResponse.info.code != 425 else {
SNLog("The user's clock is out of sync with the service node network.")
return seal.reject(SnodeAPIError.clockOutOfSync)
}
guard responseInfo.code != 401 else { // Signature verification failed
guard processedResponse.info.code != 401 else { // Signature verification failed
SNLog("Failed to verify the signature.")
return seal.reject(SnodeAPIError.signatureVerificationFailed)
}
// Handle error status codes
guard 200...299 ~= responseInfo.code else {
guard 200...299 ~= processedResponse.info.code else {
return seal.reject(
OnionRequestAPIError.httpRequestFailedAtDestination(
statusCode: UInt(responseInfo.code),
statusCode: UInt(processedResponse.info.code),
data: data,
destination: destination
)
)
}
// If there is no data in the response then just return the ResponseInfo
guard responseString.count > "l\(infoLength)\(infoString)e".count else {
return seal.fulfill((responseInfo, nil))
}
// Extract the response data as well
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else {
return seal.reject(HTTP.Error.invalidResponse)
}
let dataBytes: Array<UInt8> = Array(data)
let dataEndIndex: Int = (dataBytes.count - suffixData.count)
let dataStartIndex: Int = (dataEndIndex - finalDataLength)
let finalDataBytes: ArraySlice<UInt8> = dataBytes[dataStartIndex..<dataEndIndex]
let finalData: Data = Data(finalDataBytes)
return seal.fulfill((responseInfo, finalData))
return seal.fulfill(processedResponse)
}
catch {
return seal.reject(error)
}
}
}
public static func process(bencodedData data: Data) -> (info: ResponseInfo, body: Data?)? {
// The data will be in the form of `l123:jsone` or `l123:json456:bodye` so we need to break the data
// into parts to properly process it
guard let responseString: String = String(data: data, encoding: .ascii), responseString.starts(with: "l") else {
return nil
}
let stringParts: [String.SubSequence] = responseString.split(separator: ":")
guard stringParts.count > 1, let infoLength: Int = Int(stringParts[0].suffix(from: stringParts[0].index(stringParts[0].startIndex, offsetBy: 1))) else {
return nil
}
let infoStringStartIndex: String.Index = responseString.index(responseString.startIndex, offsetBy: "l\(infoLength):".count)
let infoStringEndIndex: String.Index = responseString.index(infoStringStartIndex, offsetBy: infoLength)
let infoString: String = String(responseString[infoStringStartIndex..<infoStringEndIndex])
guard let infoStringData: Data = infoString.data(using: .utf8), let responseInfo: ResponseInfo = try? JSONDecoder().decode(ResponseInfo.self, from: infoStringData) else {
return nil
}
// Custom handle a clock out of sync error (v4 returns '425' but included the '406' just in case)
guard responseInfo.code != 406 && responseInfo.code != 425 else { return nil }
guard responseInfo.code != 401 else { return nil }
// If there is no data in the response then just return the ResponseInfo
guard responseString.count > "l\(infoLength)\(infoString)e".count else {
return (responseInfo, nil)
}
// Extract the response data as well
let dataString: String = String(responseString.suffix(from: infoStringEndIndex))
let dataStringParts: [String.SubSequence] = dataString.split(separator: ":")
guard dataStringParts.count > 1, let finalDataLength: Int = Int(dataStringParts[0]), let suffixData: Data = "e".data(using: .utf8) else {
return nil
}
let dataBytes: Array<UInt8> = Array(data)
let dataEndIndex: Int = (dataBytes.count - suffixData.count)
let dataStartIndex: Int = (dataEndIndex - finalDataLength)
let finalDataBytes: ArraySlice<UInt8> = dataBytes[dataStartIndex..<dataEndIndex]
let finalData: Data = Data(finalDataBytes)
return (responseInfo, finalData)
}
}

Loading…
Cancel
Save