Add multi device friend request acceptance test

This commit is contained in:
nielsandriesse 2020-04-28 09:18:33 +10:00
parent 43866596ba
commit c758ad1b89
6 changed files with 81 additions and 18 deletions

View File

@ -4466,7 +4466,7 @@ typedef enum : NSUInteger {
- (void)acceptFriendRequest:(TSIncomingMessage *)friendRequest
{
[OWSPrimaryStorage.sharedManager.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[LKFriendRequestProtocol acceptFriendRequest:friendRequest in:self.thread using:transaction];
[LKFriendRequestProtocol acceptFriendRequestFrom:friendRequest.authorId in:self.thread using:transaction];
}];
}

View File

@ -0,0 +1,12 @@
public extension Data {
/// Returns `size` bytes of random data generated using the default secure random number generator. See
/// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information.
public static func getSecureRandomData(ofSize size: UInt) -> Data? {
var data = Data(count: Int(size))
let result = data.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, Int(size), $0.baseAddress!) }
guard result == errSecSuccess else { return nil }
return data
}
}

View File

@ -7,19 +7,10 @@ extension OnionRequestAPI {
internal typealias EncryptionResult = (ciphertext: Data, symmetricKey: Data, ephemeralPublicKey: Data)
/// Returns `size` bytes of random data generated using the default secure random number generator. See
/// [SecRandomCopyBytes](https://developer.apple.com/documentation/security/1399291-secrandomcopybytes) for more information.
private static func getSecureRandomData(ofSize size: UInt) throws -> Data {
var data = Data(count: Int(size))
let result = data.withUnsafeMutableBytes { SecRandomCopyBytes(kSecRandomDefault, Int(size), $0.baseAddress!) }
guard result == errSecSuccess else { throw Error.randomDataGenerationFailed }
return data
}
/// - Note: Sync. Don't call from the main thread.
private static func encrypt(_ plaintext: Data, usingAESGCMWithSymmetricKey symmetricKey: Data) throws -> Data {
guard !Thread.isMainThread else { preconditionFailure("It's illegal to call encrypt(_:usingAESGCMWithSymmetricKey:) from the main thread.") }
let iv = try getSecureRandomData(ofSize: ivSize)
guard let iv = Data.getSecureRandomData(ofSize: ivSize) else { throw Error.randomDataGenerationFailed }
let gcm = GCM(iv: iv.bytes, tagLength: Int(gcmTagSize), mode: .combined)
let aes = try AES(key: symmetricKey.bytes, blockMode: gcm, padding: .noPadding)
let ciphertext = try aes.encrypt(plaintext.bytes)

View File

@ -55,18 +55,17 @@ public final class FriendRequestProtocol : NSObject {
}
// MARK: - Sending
@objc(acceptFriendRequest:in:using:)
public static func acceptFriendRequest(_ friendRequest: TSIncomingMessage, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
@objc(acceptFriendRequestFrom:in:using:)
public static func acceptFriendRequest(from hexEncodedPublicKey: String, in thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) {
// Accept all outstanding friend requests associated with this user and try to establish sessions with the
// subset of their devices that haven't sent a friend request.
let senderID = friendRequest.authorId
let linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: senderID, in: transaction) // This doesn't create new threads if they don't exist yet
let linkedDeviceThreads = LokiDatabaseUtilities.getLinkedDeviceThreads(for: hexEncodedPublicKey, in: transaction) // This doesn't create new threads if they don't exist yet
for thread in linkedDeviceThreads {
if thread.hasPendingFriendRequest {
sendFriendRequestAcceptanceMessage(to: thread.contactIdentifier(), in: thread, using: transaction) // NOT senderID
sendFriendRequestAcceptanceMessage(to: thread.contactIdentifier(), in: thread, using: transaction) // NOT hexEncodedPublicKey
thread.saveFriendRequestStatus(.friends, with: transaction)
} else {
let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: senderID, in: transaction)
let autoGeneratedFRMessageSend = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessageSend(for: thread.contactIdentifier(), in: transaction) // NOT hexEncodedPublicKey
OWSDispatch.sendingQueue().async {
let messageSender = SSKEnvironment.shared.messageSender
messageSender.sendMessage(autoGeneratedFRMessageSend)

View File

@ -1,9 +1,68 @@
import CryptoSwift
import PromiseKit
@testable import SignalServiceKit
import XCTest
class FriendRequestProtocolTests : XCTestCase {
// TODO: Add tests
private var storage: OWSPrimaryStorage { OWSPrimaryStorage.shared() }
override func setUp() {
super.setUp()
// Activate the mock environment
ClearCurrentAppContextForTests()
SetCurrentAppContext(TestAppContext())
MockSSKEnvironment.activate()
// Register a mock user
let identityManager = OWSIdentityManager.shared()
let seed = Randomness.generateRandomBytes(16)!
let keyPair = Curve25519.generateKeyPair(fromSeed: seed + seed)
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
databaseConnection.setObject(keyPair, forKey: OWSPrimaryStorageIdentityKeyStoreIdentityKey, inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
TSAccountManager.sharedInstance().phoneNumberAwaitingVerification = keyPair.hexEncodedPublicKey
TSAccountManager.sharedInstance().didRegister()
}
func testMultiDeviceFriendRequestAcceptance() {
// When Alice accepts Bob's friend request, she should accept all outstanding friend requests with Bob's
// linked devices and try to establish sessions with the subset of Bob's devices that haven't sent a friend request.
func getDevice() -> DeviceLink.Device? {
guard let publicKey = Data.getSecureRandomData(ofSize: 64) else { return nil }
let hexEncodedPublicKey = "05" + publicKey.toHexString()
guard let signature = Data.getSecureRandomData(ofSize: 64) else { return nil }
return DeviceLink.Device(hexEncodedPublicKey: hexEncodedPublicKey, signature: signature)
}
func createThread(for hexEncodedPublicKey: String) -> TSContactThread {
var result: TSContactThread!
storage.dbReadWriteConnection.readWrite { transaction in
result = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction)
}
return result
}
// Get devices
guard let bobMasterDevice = getDevice() else { return XCTFail() }
guard let bobSlaveDevice = getDevice() else { return XCTFail() }
// Create device link
let bobDeviceLink = DeviceLink(between: bobMasterDevice, and: bobSlaveDevice)
storage.dbReadWriteConnection.readWrite { transaction in
self.storage.addDeviceLink(bobDeviceLink, in: transaction)
}
// Create threads
let bobMasterThread = createThread(for: bobMasterDevice.hexEncodedPublicKey)
let bobSlaveThread = createThread(for: bobSlaveDevice.hexEncodedPublicKey)
// Scenario 1: Alice has a pending friend request from Bob's master device, and nothing
// from his slave device. After accepting the pending friend request we'd expect the
// friend request status for Bob's master thread to be `friends`, and that of Bob's
// slave thread to be `requestSent`.
storage.dbReadWriteConnection.readWrite { transaction in
bobMasterThread.saveFriendRequestStatus(.requestReceived, with: transaction)
bobSlaveThread.saveFriendRequestStatus(.none, with: transaction)
}
storage.dbReadWriteConnection.readWrite { transaction in
FriendRequestProtocol.acceptFriendRequest(from: bobMasterDevice.hexEncodedPublicKey, in: bobMasterThread, using: transaction)
}
XCTAssert(bobMasterThread.friendRequestStatus == .friends)
XCTAssert(bobSlaveThread.friendRequestStatus == .requestSent)
// TODO: Add other scenarios
}
}

View File

@ -40,6 +40,7 @@ class OWSUDManagerTest: SSKBaseTestSwift {
let aliceRecipientId = "+13213214321"
override func setUp() {
/*
super.setUp()
tsAccountManager.registerForTests(withLocalNumber: aliceRecipientId)
@ -60,6 +61,7 @@ class OWSUDManagerTest: SSKBaseTestSwift {
signatureData: Randomness.generateRandomBytes(ECCSignatureLength))
udManager.setSenderCertificate(try! senderCertificate.serialized())
*/
}
override func tearDown() {