Merge pull request #473 from oxen-io/authentication

Implement Authenticated Message Retrieval
This commit is contained in:
Niels Andriesse 2021-07-26 09:53:01 +10:00 committed by GitHub
commit c2a4d9fc33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 27 additions and 12 deletions

View File

@ -4,6 +4,8 @@ import Sodium
@objc(SNSnodeAPI)
public final class SnodeAPI : NSObject {
private static let sodium = Sodium()
private static var hasLoadedSnodePool = false
private static var loadedSwarms: Set<String> = []
private static var getSnodePoolPromise: Promise<Set<Snode>>?
@ -296,7 +298,6 @@ public final class SnodeAPI : NSObject {
}
public static func getSessionID(for onsName: String) -> Promise<String> {
let sodium = Sodium()
let validationCount = 3
let sessionIDByteCount = 33
// The name must be lowercased
@ -390,26 +391,18 @@ public final class SnodeAPI : NSObject {
public static func getRawMessages(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise {
let (promise, seal) = RawResponsePromise.pending()
let storage = SNSnodeKitConfiguration.shared.storage
Threading.workQueue.async {
storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey)
let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? ""
let parameters = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, "lastHash" : lastHash ]
invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) }
getMessagesInternal(from: snode, associatedWith: publicKey).done2 { seal.fulfill($0) }.catch2 { seal.reject($0) }
}
return promise
}
public static func getMessages(for publicKey: String) -> Promise<Set<MessageListPromise>> {
let (promise, seal) = Promise<Set<MessageListPromise>>.pending()
let storage = SNSnodeKitConfiguration.shared.storage
Threading.workQueue.async {
attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
getTargetSnodes(for: publicKey).mapValues2 { targetSnode in
storage.pruneLastMessageHashInfoIfExpired(for: targetSnode, associatedWith: publicKey)
let lastHash = storage.getLastMessageHash(for: targetSnode, associatedWith: publicKey) ?? ""
let parameters = [ "pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey, "lastHash" : lastHash ]
return invoke(.getMessages, on: targetSnode, associatedWith: publicKey, parameters: parameters).map2 { rawResponse in
return getMessagesInternal(from: targetSnode, associatedWith: publicKey).map2 { rawResponse in
parseRawMessagesResponse(rawResponse, from: targetSnode, associatedWith: publicKey)
}
}.map2 { Set($0) }
@ -417,6 +410,28 @@ public final class SnodeAPI : NSObject {
}
return promise
}
private static func getMessagesInternal(from snode: Snode, associatedWith publicKey: String) -> RawResponsePromise {
let storage = SNSnodeKitConfiguration.shared.storage
guard let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
// Get last message hash
storage.pruneLastMessageHashInfoIfExpired(for: snode, associatedWith: publicKey)
let lastHash = storage.getLastMessageHash(for: snode, associatedWith: publicKey) ?? ""
// Construct signature
let timestamp = UInt64(Int64(NSDate.millisecondTimestamp()) + SnodeAPI.clockOffset)
let ed25519PublicKey = userED25519KeyPair.publicKey.toHexString()
let verificationData = ("retrieve" + String(timestamp)).data(using: String.Encoding.utf8)!
let signature = sodium.sign.signature(message: Bytes(verificationData), secretKey: userED25519KeyPair.secretKey)!
// Make the request
let parameters: JSON = [
"pubKey" : Features.useTestnet ? publicKey.removing05PrefixIfNeeded() : publicKey,
"lastHash" : lastHash,
"timestamp" : timestamp,
"pubkey_ed25519" : ed25519PublicKey,
"signature" : signature.toBase64()!
]
return invoke(.getMessages, on: snode, associatedWith: publicKey, parameters: parameters)
}
public static func sendMessage(_ message: SnodeMessage) -> Promise<Set<RawResponsePromise>> {
let (promise, seal) = Promise<Set<RawResponsePromise>>.pending()
@ -439,7 +454,6 @@ public final class SnodeAPI : NSObject {
let storage = SNSnodeKitConfiguration.shared.storage
guard let userX25519PublicKey = storage.getUserPublicKey(),
let userED25519KeyPair = storage.getUserED25519KeyPair() else { return Promise(error: Error.noKeyPair) }
let sodium = Sodium()
return attempt(maxRetryCount: maxRetryCount, recoveringOn: Threading.workQueue) {
getSwarm(for: userX25519PublicKey).then2 { swarm -> Promise<[String:Bool]> in
let snode = swarm.randomElement()!
@ -532,6 +546,7 @@ public final class SnodeAPI : NSObject {
newReceivedMessages.insert(hash)
return !isDuplicate
}
// Avoid the sync write transaction if possible
if oldReceivedMessages != newReceivedMessages {
SNSnodeKitConfiguration.shared.storage.writeSync { transaction in
SNSnodeKitConfiguration.shared.storage.setReceivedMessages(to: newReceivedMessages, for: publicKey, using: transaction)