Signal-iOS/SignalServiceKit/tests/Messages/Interactions/TSOutgoingMessageTest.swift
Jordan Rose 16c1249b24 PNP: Clear phone number sharing flag after getting a delivery receipt
When someone sends a message to your PNI, your responses (from your
ACI) must include a PNI signature, and the sealed sender certificate
you use during this period should include your phone number. This
confirms to the other user that your ACI is associated with your PNI.

However, as soon as they have processed a PniSignature, there's no
need to keep wasting space in messages. Detect this using the
following conditions:
- You received a delivery receipt from them.
- The original message was sent via sealed sender (so the server
  can't synthesize a delivery receipt).
- All their other devices have already sent delivery receipts for this
  message.
- The original message actually had a PniSignature in it.
- The original message's PniSignature matches your current PNI
  (relevant if you change your number and then get an incoming message
  from the same person at your new PNI).

Additionally, reset phone number sharing for *everyone* when you
change your number; they've contacted you at your old PNI,
representing your old number, and even if you reply you can no longer
claim that PNI as yours.
2022-05-20 10:48:22 -07:00

288 lines
15 KiB
Swift

//
// Copyright (c) 2022 Open Whisper Systems. All rights reserved.
//
@testable import SignalServiceKit
import XCTest
class TSOutgoingMessageTest: SSKBaseTestSwift {
override func setUp() {
super.setUp()
tsAccountManager.registerForTests(withLocalNumber: "+17775550101", uuid: UUID(), pni: UUID())
_ = identityManager.generateNewIdentityKey(for: .aci)
_ = identityManager.generateNewIdentityKey(for: .pni)
}
func testShouldNotStartExpireTimerWithMessageThatDoesNotExpire() {
write { transaction in
let otherAddress = SignalServiceAddress(phoneNumber: "+12223334444")
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = 100
let message = messageBuilder.build()
XCTAssertFalse(message.shouldStartExpireTimer())
message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction)
XCTAssertFalse(message.shouldStartExpireTimer())
}
}
func testShouldStartExpireTimerWithSentMessage() {
write { transaction in
let otherAddress = SignalServiceAddress(phoneNumber: "+12223334444")
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = 100
messageBuilder.expiresInSeconds = 10
let message = messageBuilder.build()
XCTAssertFalse(message.shouldStartExpireTimer())
message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction)
XCTAssertTrue(message.shouldStartExpireTimer())
}
}
func testShouldNotStartExpireTimerWithAttemptingOutMessage() {
write { transaction in
let otherAddress = SignalServiceAddress(phoneNumber: "+12223334444")
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = 100
messageBuilder.expiresInSeconds = 10
let message = messageBuilder.build()
message.updateAllUnsentRecipientsAsSending(transaction: transaction)
XCTAssertFalse(message.shouldStartExpireTimer())
}
}
func testNoPniSignatureByDefault() {
write { transaction in
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = 100
let message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
let content = try! SSKProtoContent(serializedData: messageData)
XCTAssertNil(content.pniSignatureMessage)
}
}
func testPniSignatureWhenNeeded() {
write { transaction in
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = 100
let message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
let content = try! SSKProtoContent(serializedData: messageData)
let messagePni = content.pniSignatureMessage!.pni
XCTAssertEqual(messagePni, tsAccountManager.localPni!.data)
let aciKeyPair = identityManager.identityKeyPair(for: .aci, transaction: transaction)!.identityKeyPair
let pniKeyPair = identityManager.identityKeyPair(for: .pni, transaction: transaction)!.identityKeyPair
XCTAssert(try! pniKeyPair.identityKey.verifyAlternateIdentity(
aciKeyPair.identityKey,
signature: content.pniSignatureMessage!.signature!))
}
}
func testReceiptClearsSharePhoneNumber() {
write { transaction in
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = Date.ows_millisecondTimestamp()
let message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction)
let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64
MessageSendLog.recordPendingDelivery(payloadId: payloadId,
recipientUuid: otherAddress.uuid!,
recipientDeviceId: 1,
message: message,
transaction: transaction)
// Nothing changed yet...
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
message.update(withDeliveredRecipient: otherAddress,
recipientDeviceId: 1,
deliveryTimestamp: nil,
transaction: transaction)
XCTAssertFalse(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
}
}
func testReceiptClearsSharePhoneNumberOnlyOnLastDevice() {
write { transaction in
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = Date.ows_millisecondTimestamp()
let message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction)
let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64
MessageSendLog.recordPendingDelivery(payloadId: payloadId,
recipientUuid: otherAddress.uuid!,
recipientDeviceId: 1,
message: message,
transaction: transaction)
MessageSendLog.recordPendingDelivery(payloadId: payloadId,
recipientUuid: otherAddress.uuid!,
recipientDeviceId: 2,
message: message,
transaction: transaction)
// Nothing changed yet...
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
message.update(withDeliveredRecipient: otherAddress,
recipientDeviceId: 1,
deliveryTimestamp: nil,
transaction: transaction)
// Still waiting on device #2!
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
message.update(withDeliveredRecipient: otherAddress,
recipientDeviceId: 2,
deliveryTimestamp: nil,
transaction: transaction)
// There we go.
XCTAssertFalse(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
}
}
func testReceiptDoesNotClearSharePhoneNumberIfNotSealedSender() {
write { transaction in
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = Date.ows_millisecondTimestamp()
let message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
message.update(withSentRecipient: otherAddress, wasSentByUD: false, transaction: transaction)
let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64
MessageSendLog.recordPendingDelivery(payloadId: payloadId,
recipientUuid: otherAddress.uuid!,
recipientDeviceId: 1,
message: message,
transaction: transaction)
// Nothing changed yet...
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
message.update(withDeliveredRecipient: otherAddress,
recipientDeviceId: 1,
deliveryTimestamp: nil,
transaction: transaction)
// Still not changed!
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
}
}
func testReceiptDoesNotClearSharePhoneNumberIfNoPniSignature() {
write { transaction in
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = Date.ows_millisecondTimestamp()
let message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction)
let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64
MessageSendLog.recordPendingDelivery(payloadId: payloadId,
recipientUuid: otherAddress.uuid!,
recipientDeviceId: 1,
message: message,
transaction: transaction)
// If we set it now...
XCTAssertFalse(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
message.update(withDeliveredRecipient: otherAddress,
recipientDeviceId: 1,
deliveryTimestamp: nil,
transaction: transaction)
// ...it should stay active.
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
}
}
func testReceiptDoesNotClearSharePhoneNumberIfPniHasChanged() {
let otherAddress = SignalServiceAddress(uuid: UUID(), phoneNumber: "+12223334444")
var message: TSOutgoingMessage!
write { transaction in
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
let thread = TSContactThread.getOrCreateThread(withContactAddress: otherAddress, transaction: transaction)
let messageBuilder = TSOutgoingMessageBuilder.outgoingMessageBuilder(thread: thread, messageBody: nil)
messageBuilder.timestamp = Date.ows_millisecondTimestamp()
message = messageBuilder.build()
let messageData = message.buildPlainTextData(thread, transaction: transaction)!
message.update(withSentRecipient: otherAddress, wasSentByUD: true, transaction: transaction)
let payloadId = MessageSendLog.recordPayload(messageData, forMessageBeingSent: message, transaction: transaction) as! Int64
MessageSendLog.recordPendingDelivery(payloadId: payloadId,
recipientUuid: otherAddress.uuid!,
recipientDeviceId: 1,
message: message,
transaction: transaction)
}
// Change our PNI, using registerForTests(...) instead of updateLocalPhoneNumber(...) because the latter kicks
// off a request to check with the server.
tsAccountManager.registerForTests(withLocalNumber: "+17775550199",
uuid: tsAccountManager.localUuid!,
pni: UUID())
write { transaction in
// Changing your number resets this setting.
XCTAssertFalse(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
identityManager.setShouldSharePhoneNumber(with: otherAddress, transaction: transaction)
message.update(withDeliveredRecipient: otherAddress,
recipientDeviceId: 1,
deliveryTimestamp: nil,
transaction: transaction)
// Still on, because our PNI changed!
XCTAssert(identityManager.shouldSharePhoneNumber(with: otherAddress, transaction: transaction))
}
}
}