83 lines
2.8 KiB
Swift
83 lines
2.8 KiB
Swift
//
|
|
// Copyright 2026 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
import CryptoKit
|
|
import Foundation
|
|
import LibSignalClient
|
|
|
|
protocol SVR2PinHasher {
|
|
func hashPin(
|
|
normalizedPin: String,
|
|
username: String,
|
|
mrEnclave: MrEnclave,
|
|
) throws -> SVR2PinHash
|
|
}
|
|
|
|
struct LibSignalPinHasher: SVR2PinHasher {
|
|
func hashPin(normalizedPin: String, username: String, mrEnclave: MrEnclave) throws -> SVR2PinHash {
|
|
return try SVR2PinHash.derive(normalizedPin: normalizedPin, username: username, mrEnclave: mrEnclave)
|
|
}
|
|
}
|
|
|
|
#if TESTABLE_BUILD
|
|
|
|
/// A "mock" PIN hasher that works even for invalid MrEnclave values.
|
|
struct MockPinHasher: SVR2PinHasher {
|
|
func hashPin(normalizedPin: String, username: String, mrEnclave: MrEnclave) throws -> SVR2PinHash {
|
|
// A fake hashing function that considers the same inputs as the real one.
|
|
let result = try hkdf(outputLength: 64, inputKeyMaterial: Data(normalizedPin.utf8), salt: Data(username.utf8), info: mrEnclave.dataValue)
|
|
return SVR2PinHash(accessKey: result.prefix(32), encryptionKey: result.suffix(32))
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
struct SVR2PinHash {
|
|
// The thing we use as the "pin" in SVR2 backup/restore requests.
|
|
var accessKey: Data
|
|
|
|
var encryptionKey: Data
|
|
|
|
// The data we put into SVR2 backups, encrypted with the PIN.
|
|
func encryptMasterKey(_ masterKey: Data) throws -> Data {
|
|
let (iv, cipherText) = try Sha256HmacSiv.encrypt(data: masterKey, key: encryptionKey)
|
|
if iv.count != 16 || cipherText.count != 32 {
|
|
throw OWSGenericError("iv or ciphertext is wrong length")
|
|
}
|
|
let encryptedMasterKey = iv + cipherText
|
|
return encryptedMasterKey
|
|
}
|
|
|
|
func decryptMasterKey(_ encryptedMasterKey: Data) throws -> Data {
|
|
guard encryptedMasterKey.count == 48 else {
|
|
throw OWSGenericError("encrypted master key is wrong length")
|
|
}
|
|
|
|
let startIndex: Int = encryptedMasterKey.startIndex
|
|
let ivRange = startIndex...(startIndex + 15)
|
|
let cipherRange = (startIndex + 16)...(startIndex + 47)
|
|
let masterKey = try Sha256HmacSiv.decrypt(
|
|
iv: encryptedMasterKey[ivRange],
|
|
cipherText: encryptedMasterKey[cipherRange],
|
|
key: encryptionKey,
|
|
)
|
|
|
|
guard masterKey.count == MasterKey.Constants.byteLength else {
|
|
throw OWSGenericError("decrypted master key is wrong length")
|
|
}
|
|
|
|
return masterKey
|
|
}
|
|
|
|
static func derive(
|
|
normalizedPin: String,
|
|
username: String,
|
|
mrEnclave: MrEnclave,
|
|
) throws -> Self {
|
|
let pinHash = try LibSignalClient.PinHash(normalizedPin: Data(normalizedPin.utf8), username: username, mrenclave: mrEnclave.dataValue)
|
|
return Self(accessKey: pinHash.accessKey, encryptionKey: pinHash.encryptionKey)
|
|
}
|
|
}
|