199 lines
7.5 KiB
Swift
199 lines
7.5 KiB
Swift
//
|
|
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import XCTest
|
|
|
|
@testable import SignalServiceKit
|
|
|
|
class KeyBackupServiceTests: SSKBaseTestSwift {
|
|
lazy var decoder: JSONDecoder = {
|
|
let decoder = JSONDecoder()
|
|
decoder.keyDecodingStrategy = .convertFromSnakeCase
|
|
decoder.dataDecodingStrategy = .custom { decoder in
|
|
let container = try decoder.singleValueContainer()
|
|
let string = try container.decode(String.self)
|
|
guard let data = Data.data(fromHex: string) else { throw OWSAssertionError("Invalid data") }
|
|
return data
|
|
}
|
|
return decoder
|
|
}()
|
|
|
|
func test_vectors() throws {
|
|
struct Vector: Codable {
|
|
let pin: String
|
|
let backupId: Data
|
|
let argon2Hash: Data
|
|
let masterKey: Data
|
|
let kbsAccessKey: Data
|
|
let ivAndCipher: Data
|
|
let registrationLock: String
|
|
}
|
|
|
|
let vectorsUrl = Bundle(for: type(of: self)).url(forResource: "kbs_vectors", withExtension: "json")!
|
|
let jsonData = try Data(contentsOf: vectorsUrl)
|
|
let vectors = try decoder.decode([Vector].self, from: jsonData)
|
|
|
|
for vector in vectors {
|
|
let (encryptionKey, accessKey) = try KeyBackupService.deriveEncryptionKeyAndAccessKey(pin: vector.pin, backupId: vector.backupId)
|
|
|
|
XCTAssertEqual(vector.argon2Hash, encryptionKey + accessKey)
|
|
XCTAssertEqual(vector.kbsAccessKey, accessKey)
|
|
|
|
let ivAndCipher = try KeyBackupService.encryptMasterKey(vector.masterKey, encryptionKey: encryptionKey)
|
|
|
|
XCTAssertEqual(vector.ivAndCipher, ivAndCipher)
|
|
|
|
let decryptedMasterKey = try KeyBackupService.decryptMasterKey(ivAndCipher, encryptionKey: encryptionKey)
|
|
|
|
XCTAssertEqual(vector.masterKey, decryptedMasterKey)
|
|
|
|
databaseStorage.write { transaction in
|
|
KeyBackupService.store(
|
|
masterKey: vector.masterKey,
|
|
isMasterKeyBackedUp: true,
|
|
pinType: .init(forPin: vector.pin),
|
|
encodedVerificationString: "",
|
|
enclaveName: "",
|
|
transaction: transaction
|
|
)
|
|
}
|
|
|
|
let registrationLockToken = KeyBackupService.deriveRegistrationLockToken()
|
|
|
|
XCTAssertEqual(vector.registrationLock, registrationLockToken)
|
|
}
|
|
}
|
|
|
|
func test_pinNormalization() throws {
|
|
struct Vector: Codable {
|
|
let name: String
|
|
let pin: String
|
|
let bytes: Data
|
|
}
|
|
|
|
let vectorsUrl = Bundle(for: type(of: self)).url(forResource: "kbs_pin_sanitation_vectors", withExtension: "json")!
|
|
let jsonData = try Data(contentsOf: vectorsUrl)
|
|
let vectors = try decoder.decode([Vector].self, from: jsonData)
|
|
|
|
for vector in vectors {
|
|
let normalizedPin = KeyBackupService.normalizePin(vector.pin)
|
|
XCTAssertEqual(vector.bytes, normalizedPin.data(using: .utf8)!, vector.name)
|
|
}
|
|
}
|
|
|
|
func test_pinVerification() throws {
|
|
let pin = "apassword"
|
|
let salt = Data.data(
|
|
fromHex: "202122232425262728292A2B2C2D2E2F"
|
|
)!
|
|
let expectedEncodedVerificationString = "$argon2i$v=19$m=512,t=64,p=1$ICEiIyQlJicoKSorLC0uLw$NeZzhiNv4cRmRMct9scf7d838bzmHJvrZtU/0BH0v/U"
|
|
|
|
let encodedVerificationString = try KeyBackupService.deriveEncodedVerificationString(pin: pin, salt: salt)
|
|
|
|
XCTAssertEqual(expectedEncodedVerificationString, encodedVerificationString)
|
|
|
|
databaseStorage.write { transaction in
|
|
KeyBackupService.store(
|
|
masterKey: Data(repeating: 0x00, count: 32),
|
|
isMasterKeyBackedUp: true,
|
|
pinType: .init(forPin: pin),
|
|
encodedVerificationString: encodedVerificationString,
|
|
enclaveName: "",
|
|
transaction: transaction
|
|
)
|
|
}
|
|
|
|
// The correct PIN returns as valid.
|
|
AssertPin(pin, isValid: true)
|
|
|
|
// The incorrect PIN returns as invalid.
|
|
AssertPin("notmypassword", isValid: false)
|
|
}
|
|
|
|
func test_storageServiceEncryption() throws {
|
|
struct Vector: Codable {
|
|
enum VectorType: String, Codable {
|
|
case storageServiceManifest
|
|
case storageServiceRecord
|
|
}
|
|
let type: VectorType
|
|
|
|
enum VectorMode: String, Codable {
|
|
case local
|
|
case synced
|
|
}
|
|
let mode: VectorMode
|
|
|
|
let masterKeyData: Data?
|
|
let storageServiceKeyData: Data
|
|
let derivedKeyData: Data
|
|
let associatedValueData: Data
|
|
let rawData: Data
|
|
let encryptedData: Data
|
|
|
|
var derivedKey: KeyBackupService.DerivedKey {
|
|
switch type {
|
|
case .storageServiceRecord:
|
|
return .storageServiceRecord(identifier: StorageService.StorageIdentifier(data: associatedValueData, type: .contact))
|
|
case .storageServiceManifest:
|
|
return .storageServiceManifest(version: associatedValueData.withUnsafeBytes { $0.pointee })
|
|
}
|
|
}
|
|
|
|
func storeKey(transaction: SDSAnyWriteTransaction) {
|
|
KeyBackupService.clearKeys(transaction: transaction)
|
|
switch mode {
|
|
case .local:
|
|
KeyBackupService.store(
|
|
masterKey: masterKeyData!,
|
|
isMasterKeyBackedUp: true,
|
|
pinType: .numeric,
|
|
encodedVerificationString: "",
|
|
enclaveName: "",
|
|
transaction: transaction
|
|
)
|
|
case .synced:
|
|
KeyBackupService.storeSyncedKey(type: .storageService, data: storageServiceKeyData, transaction: transaction)
|
|
}
|
|
}
|
|
}
|
|
|
|
let vectorsUrl = Bundle(for: type(of: self)).url(forResource: "kbs_storage_service_encryption_vectors", withExtension: "json")!
|
|
let jsonData = try Data(contentsOf: vectorsUrl)
|
|
let vectors = try decoder.decode([Vector].self, from: jsonData)
|
|
|
|
for vector in vectors {
|
|
databaseStorage.write { vector.storeKey(transaction: $0) }
|
|
|
|
XCTAssertEqual(KeyBackupService.hasMasterKey, vector.masterKeyData != nil)
|
|
|
|
XCTAssertEqual(vector.derivedKeyData, vector.derivedKey.data)
|
|
XCTAssertEqual(vector.storageServiceKeyData, KeyBackupService.DerivedKey.storageService.data)
|
|
|
|
let encryptedData = try KeyBackupService.encrypt(keyType: vector.derivedKey, data: vector.rawData)
|
|
let decryptedData = try KeyBackupService.decrypt(keyType: vector.derivedKey, encryptedData: encryptedData)
|
|
XCTAssertEqual(vector.rawData, decryptedData)
|
|
|
|
let decryptedVectorData = try KeyBackupService.decrypt(keyType: vector.derivedKey, encryptedData: vector.encryptedData)
|
|
XCTAssertEqual(vector.rawData, decryptedVectorData)
|
|
}
|
|
}
|
|
|
|
func AssertPin(
|
|
_ pin: String,
|
|
isValid expectedResult: Bool,
|
|
_ message: String = "",
|
|
file: StaticString = #file,
|
|
line: UInt = #line
|
|
) {
|
|
let expectation = XCTestExpectation(description: "Verify Pin")
|
|
KeyBackupService.verifyPin(pin) { isValid in
|
|
XCTAssertEqual(isValid, expectedResult, message, file: file, line: line)
|
|
expectation.fulfill()
|
|
}
|
|
wait(for: [expectation], timeout: 5)
|
|
}
|
|
}
|