Skip to content

Commit de99ef8

Browse files
Merge branch 'main' into brant/BITAU-152-handle-vault-lock-unlock
2 parents 3346c96 + 77ec482 commit de99ef8

33 files changed

+276
-962
lines changed

BitwardenShared/Core/Auth/Repositories/AuthRepository.swift

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,6 @@ extension DefaultAuthRepository: AuthRepository {
948948
break
949949
}
950950

951-
try await configureBiometricUnlockIfRequired(method: method)
952951
try await configurePinUnlockIfNeeded(method: method)
953952

954953
_ = try await trustDeviceService.trustDeviceIfNeeded()
@@ -1017,31 +1016,6 @@ extension DefaultAuthRepository: AuthRepository {
10171016
return try await stateService.getActiveAccountId()
10181017
}
10191018

1020-
/// This method checks the biometric unlock status, and if biometric unlock is available but not
1021-
/// fully configured (i.e., it doesn't have a valid integrity), it sets up biometric integrity and configures
1022-
/// the biometric unlock key.
1023-
///
1024-
/// - Parameter method: The unlocking `InitUserCryptoMethod` method.
1025-
/// - Throws: An error if configuring biometric integrity or setting the biometric unlock key fails.
1026-
///
1027-
private func configureBiometricUnlockIfRequired(method: InitUserCryptoMethod) async throws {
1028-
switch method {
1029-
case .authRequest,
1030-
.decryptedKey:
1031-
break
1032-
case .deviceKey,
1033-
.keyConnector,
1034-
.password,
1035-
.pin:
1036-
if case .available(_, true, false) = try? await biometricsRepository.getBiometricUnlockStatus() {
1037-
try await biometricsRepository.configureBiometricIntegrity()
1038-
try await biometricsRepository.setBiometricUnlockKey(
1039-
authKey: clientService.crypto().getUserEncryptionKey()
1040-
)
1041-
}
1042-
}
1043-
}
1044-
10451019
/// Configures PIN unlock if the user requires master password or biometrics after an app restart.
10461020
///
10471021
/// - Parameter method: The unlocking `InitUserCryptoMethod` method.

BitwardenShared/Core/Auth/Repositories/AuthRepositoryTests.swift

Lines changed: 1 addition & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,7 +1035,6 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
10351035
try await subject.unlockVaultWithNeverlockKey()
10361036
}
10371037
XCTAssertFalse(vaultTimeoutService.unlockVaultHadUserInteraction)
1038-
XCTAssertFalse(biometricsRepository.didConfigureBiometricIntegrity)
10391038
}
10401039

10411040
/// `test_unlockVaultWithDeviceKey` attempts to unlock the vault using the device key from the keychain.
@@ -1064,38 +1063,6 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
10641063
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
10651064
}
10661065

1067-
/// `test_unlockVaultWithDeviceKey` attempts to unlock the vault using the device key from the keychain.
1068-
func test_unlockVaultWithDeviceKey_successWithBiometricsEnabled() async throws {
1069-
let active = Account.fixtureWithTDE()
1070-
stateService.activeAccount = active
1071-
keychainService.mockStorage = [
1072-
keychainService.formattedKey(
1073-
for: KeychainItem.deviceKey(
1074-
userId: active.profile.userId
1075-
)
1076-
):
1077-
"pasta",
1078-
]
1079-
1080-
biometricsRepository.biometricUnlockStatus = .success(
1081-
.available(.faceID, enabled: true, hasValidIntegrity: false)
1082-
)
1083-
1084-
stateService.accountEncryptionKeys = [
1085-
active.profile.userId: .init(
1086-
encryptedPrivateKey: "secret",
1087-
encryptedUserKey: "recipe"
1088-
),
1089-
]
1090-
clientService.mockCrypto.getUserEncryptionKeyResult = .success("sauce")
1091-
clientService.mockCrypto.initializeUserCryptoResult = .success(())
1092-
await assertAsyncDoesNotThrow {
1093-
try await subject.unlockVaultWithDeviceKey()
1094-
}
1095-
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
1096-
XCTAssertTrue(biometricsRepository.didConfigureBiometricIntegrity)
1097-
}
1098-
10991066
/// `test_unlockVaultWithDeviceKey` attempts to unlock the vault using the device key from the keychain.
11001067
func test_unlockVaultWithDeviceKey_error() async throws {
11011068
let active = Account.fixture()
@@ -1189,7 +1156,7 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
11891156
stateService.timeoutAction["1"] = .lock
11901157
stateService.userHasMasterPassword["1"] = false
11911158
biometricsRepository.biometricUnlockStatus = .success(
1192-
.available(.faceID, enabled: true, hasValidIntegrity: true)
1159+
.available(.faceID, enabled: true)
11931160
)
11941161

11951162
var timeoutAction = try await subject.sessionTimeoutAction()
@@ -1339,43 +1306,9 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
13391306
XCTAssertEqual(authService.hashPasswordPassword, "password")
13401307
XCTAssertEqual(stateService.accountVolatileData["1"]?.pinProtectedUserKey, "ENCRYPTED_USER_KEY")
13411308
XCTAssertEqual(stateService.masterPasswordHashes["1"], "hashed")
1342-
XCTAssertFalse(biometricsRepository.didConfigureBiometricIntegrity)
13431309
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
13441310
}
13451311

1346-
/// `unlockVaultWithPassword(password:)` configures biometric integrity refreshes.
1347-
func test_unlockVault_integrityRefresh() async throws {
1348-
stateService.activeAccount = .fixture()
1349-
stateService.accountEncryptionKeys = [
1350-
"1": AccountEncryptionKeys(
1351-
encryptedPrivateKey: "PRIVATE_KEY",
1352-
encryptedUserKey: "USER_KEY"
1353-
),
1354-
]
1355-
biometricsRepository.biometricUnlockStatus = .success(
1356-
.available(.faceID, enabled: true, hasValidIntegrity: false)
1357-
)
1358-
1359-
await assertAsyncDoesNotThrow {
1360-
try await subject.unlockVaultWithPassword(password: "password")
1361-
}
1362-
1363-
XCTAssertEqual(
1364-
clientService.mockCrypto.initializeUserCryptoRequest,
1365-
InitUserCryptoRequest(
1366-
kdfParams: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)),
1367-
1368-
privateKey: "PRIVATE_KEY",
1369-
method: .password(password: "password", userKey: "USER_KEY")
1370-
)
1371-
)
1372-
XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1"))
1373-
XCTAssertTrue(organizationService.initializeOrganizationCryptoCalled)
1374-
XCTAssertEqual(authService.hashPasswordPassword, "password")
1375-
XCTAssertEqual(stateService.masterPasswordHashes["1"], "hashed")
1376-
XCTAssertTrue(biometricsRepository.didConfigureBiometricIntegrity)
1377-
}
1378-
13791312
/// `unlockVaultWithBiometrics()` throws an error if the vault is unable to be unlocked.
13801313
func test_unlockVaultWithBiometrics_error_cryptoFail() async {
13811314
stateService.accountEncryptionKeys = [
@@ -1505,43 +1438,6 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
15051438
)
15061439
XCTAssertFalse(keyConnectorService.convertNewUserToKeyConnectorCalled)
15071440
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
1508-
XCTAssertFalse(biometricsRepository.didConfigureBiometricIntegrity)
1509-
}
1510-
1511-
/// `unlockVaultWithKeyConnectorKey()` unlocks the user's vault with their key connector key.
1512-
func test_unlockVaultWithKeyConnectorKeyWithBiometricsEnabled() async {
1513-
clientService.mockCrypto.initializeUserCryptoResult = .success(())
1514-
keyConnectorService.getMasterKeyFromKeyConnectorResult = .success("key")
1515-
stateService.accountEncryptionKeys = [
1516-
"1": AccountEncryptionKeys(
1517-
encryptedPrivateKey: "private",
1518-
encryptedUserKey: "user"
1519-
),
1520-
]
1521-
stateService.activeAccount = .fixture()
1522-
biometricsRepository.biometricUnlockStatus = .success(
1523-
.available(.faceID, enabled: true, hasValidIntegrity: false)
1524-
)
1525-
1526-
await assertAsyncDoesNotThrow {
1527-
try await subject.unlockVaultWithKeyConnectorKey(
1528-
keyConnectorURL: URL(string: "https://example.com")!,
1529-
orgIdentifier: "org-id"
1530-
)
1531-
}
1532-
1533-
XCTAssertEqual(
1534-
clientService.mockCrypto.initializeUserCryptoRequest,
1535-
InitUserCryptoRequest(
1536-
kdfParams: KdfConfig().sdkKdf,
1537-
1538-
privateKey: "private",
1539-
method: .keyConnector(masterKey: "key", userKey: "user")
1540-
)
1541-
)
1542-
XCTAssertFalse(keyConnectorService.convertNewUserToKeyConnectorCalled)
1543-
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
1544-
XCTAssertTrue(biometricsRepository.didConfigureBiometricIntegrity)
15451441
}
15461442

15471443
/// `unlockVaultWithKeyConnectorKey()` converts a new user to use key connector and unlocks the
@@ -1748,37 +1644,6 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
17481644
)
17491645
XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1"))
17501646
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
1751-
XCTAssertFalse(biometricsRepository.didConfigureBiometricIntegrity)
1752-
}
1753-
1754-
/// `unlockVaultWithPIN(_:)` unlocks the vault with the user's PIN and configures biometric
1755-
/// integrity if needed.
1756-
func test_unlockVaultWithPIN_configuresBiometrics() async throws {
1757-
let account = Account.fixture()
1758-
stateService.activeAccount = account
1759-
stateService.accountEncryptionKeys = [
1760-
"1": AccountEncryptionKeys(encryptedPrivateKey: "PRIVATE_KEY", encryptedUserKey: "USER_KEY"),
1761-
]
1762-
stateService.encryptedPinByUserId[account.profile.userId] = "123"
1763-
stateService.pinProtectedUserKeyValue[account.profile.userId] = "123"
1764-
biometricsRepository.biometricUnlockStatus = .success(
1765-
.available(.faceID, enabled: true, hasValidIntegrity: false)
1766-
)
1767-
1768-
try await subject.unlockVaultWithPIN(pin: "123")
1769-
1770-
XCTAssertEqual(
1771-
clientService.mockCrypto.initializeUserCryptoRequest,
1772-
InitUserCryptoRequest(
1773-
kdfParams: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)),
1774-
1775-
privateKey: "PRIVATE_KEY",
1776-
method: .pin(pin: "123", pinProtectedUserKey: "123")
1777-
)
1778-
)
1779-
XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1"))
1780-
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
1781-
XCTAssertTrue(biometricsRepository.didConfigureBiometricIntegrity)
17821647
}
17831648

17841649
/// `unlockVaultWithPIN(_:)` throws an error if there's no pin.

BitwardenShared/Core/Auth/Services/Biometrics/BiometricsRepository.swift

Lines changed: 6 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import LocalAuthentication
55

66
enum BiometricsUnlockStatus: Equatable {
77
/// Biometric Unlock is available.
8-
case available(BiometricAuthenticationType, enabled: Bool, hasValidIntegrity: Bool)
8+
case available(BiometricAuthenticationType, enabled: Bool)
99

1010
/// Biometric Unlock is not available.
1111
case notAvailable
@@ -14,7 +14,7 @@ enum BiometricsUnlockStatus: Equatable {
1414

1515
/// Whether biometric unlock is both available and enabled.
1616
var isEnabled: Bool {
17-
guard case let .available(_, enabled, _) = self else {
17+
guard case let .available(_, enabled) = self else {
1818
return false
1919
}
2020
return enabled
@@ -26,10 +26,6 @@ enum BiometricsUnlockStatus: Equatable {
2626
/// A protocol for returning the available authentication policies and access controls for the user's device.
2727
///
2828
protocol BiometricsRepository: AnyObject {
29-
/// Configures the device Biometric Integrity state.
30-
/// Should be called following a successful launch when biometric unlock is enabled.
31-
func configureBiometricIntegrity() async throws
32-
3329
/// Returns the device BiometricAuthenticationType.
3430
///
3531
/// - Returns: The `BiometricAuthenticationType`.
@@ -69,7 +65,7 @@ class DefaultBiometricsRepository: BiometricsRepository {
6965
/// A service used to store the UserAuthKey key/value pair.
7066
var keychainRepository: KeychainRepository
7167

72-
/// A service used to store the Biometric Integrity Source key/value pair.
68+
/// A service used to update user preferences.
7369
var stateService: StateService
7470

7571
// MARK: Initialization
@@ -91,13 +87,6 @@ class DefaultBiometricsRepository: BiometricsRepository {
9187
self.stateService = stateService
9288
}
9389

94-
func configureBiometricIntegrity() async throws {
95-
if let state = biometricsService.getBiometricIntegrityState() {
96-
let base64State = state.base64EncodedString()
97-
try await stateService.setBiometricIntegrityState(base64State)
98-
}
99-
}
100-
10190
func getBiometricAuthenticationType() -> BiometricAuthenticationType? {
10291
biometricsService.getBiometricAuthenticationType()
10392
}
@@ -106,13 +95,11 @@ class DefaultBiometricsRepository: BiometricsRepository {
10695
guard let authKey,
10796
try await biometricsService.evaluateBiometricPolicy() else {
10897
try await stateService.setBiometricAuthenticationEnabled(false)
109-
try await stateService.setBiometricIntegrityState(nil)
11098
try? await deleteUserAuthKey()
11199
return
112100
}
113101

114102
try await setUserBiometricAuthKey(value: authKey)
115-
try await configureBiometricIntegrity()
116103
try await stateService.setBiometricAuthenticationEnabled(true)
117104
}
118105

@@ -122,14 +109,9 @@ class DefaultBiometricsRepository: BiometricsRepository {
122109
throw BiometricsServiceError.biometryLocked
123110
}
124111
let hasEnabledBiometricUnlock = try await stateService.getBiometricAuthenticationEnabled()
125-
let hasValidIntegrityState = await isBiometricIntegrityValid()
126112
switch biometryStatus {
127113
case let .authorized(type):
128-
return .available(
129-
type,
130-
enabled: hasEnabledBiometricUnlock,
131-
hasValidIntegrity: hasValidIntegrityState
132-
)
114+
return .available(type, enabled: hasEnabledBiometricUnlock)
133115
case .denied,
134116
.lockedOut,
135117
.noBiometrics,
@@ -149,10 +131,6 @@ class DefaultBiometricsRepository: BiometricsRepository {
149131
guard !string.isEmpty else {
150132
throw BiometricsServiceError.getAuthKeyFailed
151133
}
152-
if let state = biometricsService.getBiometricIntegrityState() {
153-
let base64State = state.base64EncodedString()
154-
try await stateService.setBiometricIntegrityState(base64State)
155-
}
156134
return string
157135
} catch let error as KeychainServiceError {
158136
switch error {
@@ -169,6 +147,8 @@ class DefaultBiometricsRepository: BiometricsRepository {
169147
kLAErrorSystemCancel,
170148
kLAErrorUserCancel:
171149
throw BiometricsServiceError.biometryCancelled
150+
case errSecItemNotFound:
151+
throw BiometricsServiceError.getAuthKeyFailed
172152
case kLAErrorBiometryDisconnected,
173153
kLAErrorUserFallback:
174154
throw BiometricsServiceError.biometryFailed
@@ -195,20 +175,6 @@ extension DefaultBiometricsRepository {
195175
}
196176
}
197177

198-
/// Checks if the device evaluatedPolicyDomainState matches the data saved to user defaults.
199-
///
200-
/// - Returns: A `Bool` indicating if the stored Data matches the current data.
201-
/// If no data is stored to the device, `true` is returned by default.
202-
///
203-
private func isBiometricIntegrityValid() async -> Bool {
204-
guard let data = biometricsService.getBiometricIntegrityState() else {
205-
// Fallback for devices unable to retrieve integrity state.
206-
return true
207-
}
208-
let integrityString: String? = try? await stateService.getBiometricIntegrityState()
209-
return data.base64EncodedString() == integrityString
210-
}
211-
212178
/// Attempts to save an auth key to the keychain with biometrics.
213179
///
214180
/// - Parameter value: The key to be stored.

0 commit comments

Comments
 (0)