|
|
|
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import Sodium
|
|
|
|
import SessionUtilitiesKit
|
|
|
|
|
|
|
|
extension SnodeAPI {
|
|
|
|
public class ONSResolveResponse: SnodeResponse {
|
|
|
|
private struct Result: Codable {
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
|
|
case nonce
|
|
|
|
case encryptedValue = "encrypted_value"
|
|
|
|
}
|
|
|
|
|
|
|
|
fileprivate let nonce: String?
|
|
|
|
fileprivate let encryptedValue: String?
|
|
|
|
}
|
|
|
|
|
|
|
|
enum CodingKeys: String, CodingKey {
|
|
|
|
case result
|
|
|
|
}
|
|
|
|
|
|
|
|
private let result: Result
|
|
|
|
|
|
|
|
// MARK: - Initialization
|
|
|
|
|
|
|
|
required init(from decoder: Decoder) throws {
|
|
|
|
let container: KeyedDecodingContainer<CodingKeys> = try decoder.container(keyedBy: CodingKeys.self)
|
|
|
|
|
|
|
|
result = try container.decode(Result.self, forKey: .result)
|
|
|
|
|
|
|
|
try super.init(from: decoder)
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Convenience
|
|
|
|
|
|
|
|
func sessionId(sodium: Sodium, nameBytes: [UInt8], nameHashBytes: [UInt8]) throws -> String {
|
|
|
|
guard let encryptedValue = result.encryptedValue else { throw SnodeAPIError.onsNotFound }
|
|
|
|
|
|
|
|
let ciphertext: [UInt8] = Data(hex: encryptedValue).bytes
|
|
|
|
|
|
|
|
// Handle old Argon2-based encryption used before HF16
|
|
|
|
guard let hexEncodedNonce: String = result.nonce else {
|
|
|
|
let salt: [UInt8] = Data(repeating: 0, count: sodium.pwHash.SaltBytes).bytes
|
|
|
|
|
|
|
|
guard
|
|
|
|
let key: [UInt8] = sodium.pwHash.hash(
|
|
|
|
outputLength: sodium.secretBox.KeyBytes,
|
|
|
|
passwd: nameBytes,
|
|
|
|
salt: salt,
|
|
|
|
opsLimit: sodium.pwHash.OpsLimitModerate,
|
|
|
|
memLimit: sodium.pwHash.MemLimitModerate,
|
|
|
|
alg: .Argon2ID13
|
|
|
|
)
|
|
|
|
else { throw SnodeAPIError.onsHashingFailed }
|
|
|
|
|
|
|
|
let nonce: [UInt8] = Data(repeating: 0, count: sodium.secretBox.NonceBytes).bytes
|
|
|
|
|
|
|
|
guard let sessionIdAsData: [UInt8] = sodium.secretBox.open(authenticatedCipherText: ciphertext, secretKey: key, nonce: nonce) else {
|
|
|
|
throw SnodeAPIError.onsDecryptionFailed
|
|
|
|
}
|
|
|
|
|
|
|
|
return sessionIdAsData.toHexString()
|
|
|
|
}
|
|
|
|
|
|
|
|
let nonceBytes: [UInt8] = Data(hex: hexEncodedNonce).bytes
|
|
|
|
|
|
|
|
// xchacha-based encryption
|
|
|
|
// key = H(name, key=H(name))
|
|
|
|
guard let key: [UInt8] = sodium.genericHash.hash(message: nameBytes, key: nameHashBytes) else {
|
|
|
|
throw SnodeAPIError.onsHashingFailed
|
|
|
|
}
|
|
|
|
guard
|
|
|
|
// Should always be equal in practice
|
|
|
|
ciphertext.count >= (SessionId.byteCount + sodium.aead.xchacha20poly1305ietf.ABytes),
|
|
|
|
let sessionIdAsData = sodium.aead.xchacha20poly1305ietf.decrypt(
|
|
|
|
authenticatedCipherText: ciphertext,
|
|
|
|
secretKey: key,
|
|
|
|
nonce: nonceBytes
|
|
|
|
)
|
|
|
|
else { throw SnodeAPIError.onsDecryptionFailed }
|
|
|
|
|
|
|
|
return sessionIdAsData.toHexString()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|