Skip to content

Commit

Permalink
Migrate to new Media/Message backup auth credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
pete-signal authored Nov 1, 2024
1 parent 03d9cc0 commit 3a63402
Show file tree
Hide file tree
Showing 30 changed files with 392 additions and 484 deletions.
3 changes: 2 additions & 1 deletion Signal/Provisioning/ProvisioningCoordinatorImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//

import Foundation
import LibSignalClient
public import SignalServiceKit

public class ProvisioningCoordinatorImpl: ProvisioningCoordinator {
Expand Down Expand Up @@ -246,7 +247,7 @@ public class ProvisioningCoordinatorImpl: ProvisioningCoordinator {

if
FeatureFlags.linkAndSyncSecondary,
let ephemeralBackupKey = EphemeralBackupKey(provisioningMessage: provisionMessage)
let ephemeralBackupKey = BackupKey(provisioningMessage: provisionMessage)
{
do {
try await self.linkAndSyncManager.waitForBackupAndRestore(
Expand Down
2 changes: 2 additions & 0 deletions Signal/Registration/RegistrationCoordinatorDependencies.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public struct RegistrationCoordinatorDependencies {
public let featureFlags: RegistrationCoordinatorImpl.Shims.FeatureFlags
public let keyValueStoreFactory: KeyValueStoreFactory
public let localUsernameManager: LocalUsernameManager
public let messageBackupKeyMaterial: MessageBackupKeyMaterial
public let messageBackupErrorPresenter: MessageBackupErrorPresenter
public let messageBackupManager: MessageBackupManager
public let messagePipelineSupervisor: RegistrationCoordinatorImpl.Shims.MessagePipelineSupervisor
Expand Down Expand Up @@ -51,6 +52,7 @@ public struct RegistrationCoordinatorDependencies {
featureFlags: RegistrationCoordinatorImpl.Wrappers.FeatureFlags(),
keyValueStoreFactory: DependenciesBridge.shared.keyValueStoreFactory,
localUsernameManager: DependenciesBridge.shared.localUsernameManager,
messageBackupKeyMaterial: DependenciesBridge.shared.messageBackupKeyMaterial,
messageBackupErrorPresenter: DependenciesBridge.shared.messageBackupErrorPresenter,
messageBackupManager: DependenciesBridge.shared.messageBackupManager,
messagePipelineSupervisor: RegistrationCoordinatorImpl.Wrappers.MessagePipelineSupervisor(SSKEnvironment.shared.messagePipelineSupervisorRef),
Expand Down
6 changes: 5 additions & 1 deletion Signal/Registration/RegistrationCoordinatorImpl.swift
Original file line number Diff line number Diff line change
Expand Up @@ -464,10 +464,14 @@ public class RegistrationCoordinatorImpl: RegistrationCoordinator {
auth: identity.chatServiceAuth
)
}
// Get Backup Key
let backupKey = try self.deps.db.read { tx in
return try self.deps.messageBackupKeyMaterial.backupKey(type: .messages, tx: tx)
}
try await self.deps.messageBackupManager.importEncryptedBackup(
fileUrl: fileUrl,
localIdentifiers: identity.localIdentifiers,
mode: .remote
backupKey: backupKey
)
self.inMemoryState.hasRestoredFromLocalMessageBackup = true
Logger.info("Finished restore")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ private extension InternalSettingsViewController {
}

func exportMessageBackupProto() {
let messageBackupKeyMaterial = DependenciesBridge.shared.messageBackupKeyMaterial
let messageBackupManager = DependenciesBridge.shared.messageBackupManager
let tsAccountManager = DependenciesBridge.shared.tsAccountManager

Expand All @@ -286,9 +287,12 @@ private extension InternalSettingsViewController {

Task {
do {
let backupKey = try SSKEnvironment.shared.databaseStorageRef.read { tx in
try messageBackupKeyMaterial.backupKey(type: .messages, tx: tx.asV2Read)
}
let metadata = try await messageBackupManager.exportEncryptedBackup(
localIdentifiers: localIdentifiers,
mode: .remote
backupKey: backupKey
)
await MainActor.run {
let actionSheet = ActionSheetController(title: "Choose backup destination:")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class LinkDeviceViewController: OWSViewController {
return mrbk
}

let ephemeralBackupKey: EphemeralBackupKey?
let ephemeralBackupKey: BackupKey?
if
isLinkAndSyncEnabled,
deviceProvisioningUrl.capabilities.contains(where: { $0 == .linknsync })
Expand Down
6 changes: 3 additions & 3 deletions Signal/test/Provisioning/ProvisioningCoordinatorTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,12 @@ private class MockLinkAndSyncManager: LinkAndSyncManager {

func setIsLinkAndSyncEnabledOnPrimary(_ isEnabled: Bool, tx: DBWriteTransaction) {}

func generateEphemeralBackupKey() -> EphemeralBackupKey {
func generateEphemeralBackupKey() -> BackupKey {
return .forTesting()
}

func waitForLinkingAndUploadBackup(
ephemeralBackupKey: EphemeralBackupKey,
ephemeralBackupKey: BackupKey,
tokenId: DeviceProvisioningTokenId
) async throws(PrimaryLinkNSyncError) {
return
Expand All @@ -205,7 +205,7 @@ private class MockLinkAndSyncManager: LinkAndSyncManager {
func waitForBackupAndRestore(
localIdentifiers: LocalIdentifiers,
auth: ChatServiceAuth,
ephemeralBackupKey: EphemeralBackupKey
ephemeralBackupKey: BackupKey
) async throws(SecondaryLinkNSyncError) {
return
}
Expand Down
1 change: 1 addition & 0 deletions Signal/test/Registration/RegistrationCoordinatorTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ public class RegistrationCoordinatorTest: XCTestCase {
featureFlags: featureFlags,
keyValueStoreFactory: InMemoryKeyValueStoreFactory(),
localUsernameManager: localUsernameManagerMock,
messageBackupKeyMaterial: MessageBackupKeyMaterialMock(),
messageBackupErrorPresenter: NoOpMessageBackupErrorPresenter(),
messageBackupManager: MessageBackupManagerMock(),
messagePipelineSupervisor: mockMessagePipelineSupervisor,
Expand Down
95 changes: 48 additions & 47 deletions SignalServiceKit/Cryptography/Cryptography.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum Cryptography {
public static func computeSHA256DigestOfFile(at url: URL) throws -> Data {
let file = try LocalFileHandle(url: url)
var sha256 = SHA256()
var buffer = Data(count: diskPageSize)
var buffer = Data(count: Constants.diskPageSize)
var bytesRead: Int
repeat {
bytesRead = try file.read(into: &buffer)
Expand Down Expand Up @@ -63,15 +63,16 @@ public protocol EncryptedFileHandle {
}

public extension Cryptography {

fileprivate static let hmac256KeyLength = 32
fileprivate static let hmac256OutputLength = 32
fileprivate static let aescbcIVLength = 16
fileprivate static let aesKeySize = 32
fileprivate static let aescbcBlockLength = 16
fileprivate static var concatenatedEncryptionKeyLength: Int { aesKeySize + hmac256KeyLength }
/// Optimize reads/writes by reading this many bytes at once; best balance of performance/memory use from testing in practice.
fileprivate static let diskPageSize = 8192
enum Constants {
static let hmac256KeyLength = 32
static let hmac256OutputLength = 32
static let aescbcIVLength = 16
static let aesKeySize = 32
static let aescbcBlockLength = 16
static var concatenatedEncryptionKeyLength: Int { aesKeySize + hmac256KeyLength }
/// Optimize reads/writes by reading this many bytes at once; best balance of performance/memory use from testing in practice.
static let diskPageSize = 8192
}

static func paddedSize(unpaddedSize: UInt) -> UInt {
// In order to obsfucate attachment size on the wire, we round up
Expand All @@ -84,7 +85,7 @@ public extension Cryptography {
static func randomAttachmentEncryptionKey() -> Data {
// The metadata "key" is actually a concatentation of the
// encryption key and the hmac key.
return Randomness.generateRandomBytes(UInt(concatenatedEncryptionKeyLength))
return Randomness.generateRandomBytes(UInt(Constants.concatenatedEncryptionKeyLength))
}

/// Encrypt an input file to a provided output file location.
Expand Down Expand Up @@ -135,7 +136,7 @@ public extension Cryptography {
encryptionKey inputKey: Data?,
applyExtraPadding: Bool
) throws -> EncryptionMetadata {
if let inputKey, inputKey.count != concatenatedEncryptionKeyLength {
if let inputKey, inputKey.count != Constants.concatenatedEncryptionKeyLength {
throw OWSAssertionError("Invalid encryption key length")
}

Expand All @@ -155,12 +156,12 @@ public extension Cryptography {
let outputFile = try FileHandle(forWritingTo: encryptedUrl)

let inputKey = inputKey ?? randomAttachmentEncryptionKey()
let encryptionKey = inputKey.prefix(aesKeySize)
let hmacKey = inputKey.suffix(hmac256KeyLength)
let encryptionKey = inputKey.prefix(Constants.aesKeySize)
let hmacKey = inputKey.suffix(Constants.hmac256KeyLength)

return try _encryptAttachment(
enumerateInputInBlocks: { closure in
var buffer = Data(count: diskPageSize)
var buffer = Data(count: Constants.diskPageSize)
var totalBytesRead: UInt = 0
var bytesRead: Int
repeat {
Expand Down Expand Up @@ -208,15 +209,15 @@ public extension Cryptography {
let outputFileHandle = try FileHandle(forWritingTo: outputFileURL)

let inputKey = inputKey ?? randomAttachmentEncryptionKey()
let encryptionKey = inputKey.prefix(aesKeySize)
let hmacKey = inputKey.suffix(hmac256KeyLength)
let encryptionKey = inputKey.prefix(Constants.aesKeySize)
let hmacKey = inputKey.suffix(Constants.hmac256KeyLength)

return try _encryptAttachment(
enumerateInputInBlocks: { closure in
var totalBytesRead: UInt = 0
var bytesRead: Int
repeat {
let data = try encryptedFileHandle.read(upToCount: UInt32(diskPageSize))
let data = try encryptedFileHandle.read(upToCount: UInt32(Constants.diskPageSize))
bytesRead = data.count
if bytesRead > 0 {
totalBytesRead += UInt(bytesRead)
Expand Down Expand Up @@ -250,13 +251,13 @@ public extension Cryptography {
iv: Data? = nil,
applyExtraPadding: Bool = false
) throws -> (Data, EncryptionMetadata) {
if let inputKey, inputKey.count != concatenatedEncryptionKeyLength {
if let inputKey, inputKey.count != Constants.concatenatedEncryptionKeyLength {
throw OWSAssertionError("Invalid encryption key length")
}

let inputKey = inputKey ?? randomAttachmentEncryptionKey()
let encryptionKey = inputKey.prefix(aesKeySize)
let hmacKey = inputKey.suffix(hmac256KeyLength)
let encryptionKey = inputKey.prefix(Constants.aesKeySize)
let hmacKey = inputKey.suffix(Constants.hmac256KeyLength)

var outputData = Data()
let encryptionMetadata = try _encryptAttachment(
Expand Down Expand Up @@ -305,12 +306,12 @@ public extension Cryptography {

let iv: Data
if let inputIV {
if inputIV.count != aescbcIVLength {
if inputIV.count != Constants.aescbcIVLength {
throw OWSAssertionError("Invalid IV length")
}
iv = inputIV
} else {
iv = Randomness.generateRandomBytes(UInt(aescbcIVLength))
iv = Randomness.generateRandomBytes(UInt(Constants.aescbcIVLength))
}

var hmac = HMAC<SHA256>(key: .init(data: hmacKey))
Expand Down Expand Up @@ -466,7 +467,7 @@ public extension Cryptography {
at: encryptedUrl,
metadata: metadata,
// Most efficient to write one page size at a time.
outputBlockSize: UInt32(diskPageSize)
outputBlockSize: UInt32(Constants.diskPageSize)
) { plaintextDataBlock in
outputFile.write(plaintextDataBlock)
}
Expand Down Expand Up @@ -504,7 +505,7 @@ public extension Cryptography {
metadata: metadata,
validateHmacAndDigest: false,
// Most efficient to write one page size at a time.
outputBlockSize: UInt32(diskPageSize)
outputBlockSize: UInt32(Constants.diskPageSize)
) { plaintextDataBlock in
outputFile.write(plaintextDataBlock)
}
Expand Down Expand Up @@ -594,7 +595,7 @@ public extension Cryptography {
if validateHmacAndDigest {
// The metadata "key" is actually a concatentation of the
// encryption key and the hmac key.
let hmacKey = metadata.key.suffix(hmac256KeyLength)
let hmacKey = metadata.key.suffix(Constants.hmac256KeyLength)

hmac = HMAC<SHA256>(key: .init(data: hmacKey))
if metadata.digest != nil {
Expand Down Expand Up @@ -641,7 +642,7 @@ public extension Cryptography {

if validateHmacAndDigest, var hmac {
// Add the last padding bytes to the hmac/digest.
var remainingPaddingLength = aescbcIVLength + inputFile.ciphertextLength - inputFile.file.offsetInFile
var remainingPaddingLength = Constants.aescbcIVLength + inputFile.ciphertextLength - inputFile.file.offsetInFile
while remainingPaddingLength > 0 {
let lengthToRead = min(remainingPaddingLength, 1024 * 16)
let paddingCiphertext = try inputFile.file.readData(ofLength: Int(lengthToRead))
Expand All @@ -657,7 +658,7 @@ public extension Cryptography {
// At this point we are done with the EncryptedFileHandle, so grab its internal
// FileHandle for reading directly.
// (This breaks EncryptedFileHandle's invariants and renders it unuseable).
let inputFileHmac = try inputFile.file.readData(ofLength: hmac256OutputLength)
let inputFileHmac = try inputFile.file.readData(ofLength: Constants.hmac256OutputLength)
guard hmacResult.ows_constantTimeIsEqual(to: inputFileHmac) else {
Logger.debug("Bad hmac. Their hmac: \(inputFileHmac.hexadecimalString), our hmac: \(hmacResult.hexadecimalString)")
throw OWSAssertionError("Bad hmac")
Expand Down Expand Up @@ -722,7 +723,7 @@ public extension Cryptography {
/// CCCryptor documentation says: "the output length is never larger than the input length plus the block size."
/// To ensure we always have enough room in the buffer, we allocate two block lengths.
/// `numBytesInPlaintextBuffer` indicates how many bytes (starting from 0) contain non-stale content.
private var plaintextBuffer = Data(repeating: 0, count: aescbcBlockLength * 2)
private var plaintextBuffer = Data(repeating: 0, count: Constants.aescbcBlockLength * 2)
private var numBytesInPlaintextBuffer = 0

init(
Expand All @@ -734,22 +735,22 @@ public extension Cryptography {
throw OWSAssertionError("Missing attachment file.")
}

guard encryptionKey.count == (aesKeySize + hmac256KeyLength) else {
guard encryptionKey.count == (Constants.aesKeySize + Constants.hmac256KeyLength) else {
throw OWSAssertionError("Encryption key shorter than combined key length")
}

self.file = try LocalFileHandle(url: encryptedUrl)

let cryptoOverheadLength = aescbcIVLength + hmac256OutputLength
let cryptoOverheadLength = Constants.aescbcIVLength + Constants.hmac256OutputLength
self.ciphertextLength = file.fileLength - cryptoOverheadLength

// The metadata "key" is actually a concatentation of the
// encryption key and the hmac key.
self.encryptionKey = encryptionKey.prefix(aesKeySize)
self.encryptionKey = encryptionKey.prefix(Constants.aesKeySize)

// This first N bytes of the encrypted file are the IV
self.iv = try file.readData(ofLength: aescbcIVLength)
guard iv.count == aescbcIVLength else {
self.iv = try file.readData(ofLength: Constants.aescbcIVLength)
guard iv.count == Constants.aescbcIVLength else {
throw OWSAssertionError("Failed to read IV")
}

Expand All @@ -765,17 +766,17 @@ public extension Cryptography {
// determine the pkcs7 padding length.
let prePaddingBlockOffset = file.fileLength
// Not the hmac
- hmac256OutputLength
- Constants.hmac256OutputLength
// Start of the previous block which has the pkcs7 padding
- aescbcBlockLength
- Constants.aescbcBlockLength
// Start of the block before that which has its iv
- aescbcBlockLength
- Constants.aescbcBlockLength
try file.seek(toFileOffset: prePaddingBlockOffset)

// Read the preceding block, use it as the IV.
let paddingBlockIV = try file.readData(ofLength: aescbcBlockLength)
let paddingBlockIV = try file.readData(ofLength: Constants.aescbcBlockLength)
// Read the block itself
let paddingBlockCiphertext = try file.readData(ofLength: aescbcBlockLength)
let paddingBlockCiphertext = try file.readData(ofLength: Constants.aescbcBlockLength)

// Decrypt, but use ecb instead of cbc mode; we _want_ the plaintext
// of the pkcs7 padding bytes; doing the block cipher XOR'ing ourselves
Expand All @@ -786,7 +787,7 @@ public extension Cryptography {
options: .ecbMode,
key: self.encryptionKey,
// Irrelevant in ecb mode.
iv: Data(repeating: 0, count: aescbcBlockLength)
iv: Data(repeating: 0, count: Constants.aescbcBlockLength)
)

var paddingBlockPlaintext = try paddingCipherContext.update(paddingBlockCiphertext)
Expand All @@ -803,11 +804,11 @@ public extension Cryptography {
self._plaintextLength = ciphertextLength - Int(paddingLength)

// Move the file handle to the start of the encrypted data (after IV)
try file.seek(toFileOffset: aescbcIVLength)
try file.seek(toFileOffset: Constants.aescbcIVLength)
}

// We should be just after the iv at this point.
owsAssertDebug(file.offsetInFile == aescbcIVLength)
owsAssertDebug(file.offsetInFile == Constants.aescbcIVLength)

self.cipherContext = try CipherContext(
operation: .decrypt,
Expand Down Expand Up @@ -835,14 +836,14 @@ public extension Cryptography {
// The offset in the encrypted file rounds down to the start of the block.
// Add 1 because the first block in the encrypted file is the iv which isn't
// represented in the virtual plaintext's address space.
var (desiredBlock, desiredOffsetInBlock) = toOffset.quotientAndRemainder(dividingBy: aescbcBlockLength)
var (desiredBlock, desiredOffsetInBlock) = toOffset.quotientAndRemainder(dividingBy: Constants.aescbcBlockLength)
desiredBlock += 1

// The preceding block serves as the iv for decryption.
let ivBlock = desiredBlock - 1
let ivOffset = ivBlock * aescbcBlockLength
let ivOffset = ivBlock * Constants.aescbcBlockLength
try file.seek(toFileOffset: ivOffset)
let iv = try file.readData(ofLength: aescbcBlockLength)
let iv = try file.readData(ofLength: Constants.aescbcBlockLength)

// Initialize a new context with the preceding block as the iv.
self.cipherContext = try CipherContext(
Expand Down Expand Up @@ -925,9 +926,9 @@ public extension Cryptography {
result += 16 - remainder
}
// Read at most the page size; no point in reading more.
result = min(result, diskPageSize)
result = min(result, Constants.diskPageSize)
// But never read past the end of the file.
result = min(result, aescbcIVLength + ciphertextLength - file.offsetInFile)
result = min(result, Constants.aescbcIVLength + ciphertextLength - file.offsetInFile)
return result
}
var numCiphertextBytesToRead = computeNumCiphertextBytesToRead()
Expand Down
Loading

0 comments on commit 3a63402

Please sign in to comment.