Signal-iOS/SignalServiceKit/SecureValueRecovery/SVR2PinHash.swift
2026-05-26 17:48:41 -05:00

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)
}
}