diff --git a/BitwardenShared/Core/Auth/Extensions/BitwardenSdk+Auth.swift b/BitwardenShared/Core/Auth/Extensions/BitwardenSdk+Auth.swift index 5b1a0795a7..3d40362ced 100644 --- a/BitwardenShared/Core/Auth/Extensions/BitwardenSdk+Auth.swift +++ b/BitwardenShared/Core/Auth/Extensions/BitwardenSdk+Auth.swift @@ -17,7 +17,7 @@ extension BitwardenSdk.InitUserCryptoMethod { case .masterPasswordUnlock: "Master Password Unlock" case .password: - "Password" + "Password (Legacy - Deprecated)" case .pin: "PIN" case .pinEnvelope: diff --git a/BitwardenShared/Core/Auth/Repositories/AuthRepository.swift b/BitwardenShared/Core/Auth/Repositories/AuthRepository.swift index 5561a052c9..3e410a0fcc 100644 --- a/BitwardenShared/Core/Auth/Repositories/AuthRepository.swift +++ b/BitwardenShared/Core/Auth/Repositories/AuthRepository.swift @@ -992,18 +992,15 @@ extension DefaultAuthRepository: AuthRepository { func unlockVaultWithPassword(password: String) async throws { let account = try await stateService.getActiveAccount() - let encryptionKeys = try await stateService.getAccountEncryptionKeys(userId: account.profile.userId) - guard let encUserKey = encryptionKeys.encryptedUserKey else { throw StateServiceError.noEncUserKey } - let masterPasswordUnlock = account.profile.userDecryptionOptions?.masterPasswordUnlock - let unlockMethod: InitUserCryptoMethod = if let masterPasswordUnlock { - .masterPasswordUnlock( - password: password, - masterPasswordUnlock: MasterPasswordUnlockData(responseModel: masterPasswordUnlock), - ) - } else { - .password(password: password, userKey: encUserKey) + guard let masterPasswordUnlock = account.profile.userDecryptionOptions?.masterPasswordUnlock else { + throw AuthError.missingMasterPasswordUnlockData } + + let unlockMethod: InitUserCryptoMethod = .masterPasswordUnlock( + password: password, + masterPasswordUnlock: MasterPasswordUnlockData(responseModel: masterPasswordUnlock), + ) try await unlockVault(method: unlockMethod) let hashedPassword = try await authService.hashPassword( @@ -1249,8 +1246,7 @@ extension DefaultAuthRepository: AuthRepository { ) await flightRecorder.log("[Auth] Migrated from legacy PIN to PIN-protected user key envelope") case .decryptedKey, - .masterPasswordUnlock, - .password: + .masterPasswordUnlock: guard let encryptedPin = try await stateService.getEncryptedPin(), try await stateService.pinProtectedUserKeyEnvelope() == nil else { @@ -1276,7 +1272,8 @@ extension DefaultAuthRepository: AuthRepository { try await stateService.setPinProtectedUserKeyToMemory(enrollPinResponse.pinProtectedUserKeyEnvelope) await flightRecorder.log("[Auth] Set PIN-protected user key in memory") } - case .pinEnvelope: + case .password, + .pinEnvelope: break } } diff --git a/BitwardenShared/Core/Auth/Repositories/AuthRepositoryTests.swift b/BitwardenShared/Core/Auth/Repositories/AuthRepositoryTests.swift index 0607e8c40b..194b4fa87e 100644 --- a/BitwardenShared/Core/Auth/Repositories/AuthRepositoryTests.swift +++ b/BitwardenShared/Core/Auth/Repositories/AuthRepositoryTests.swift @@ -1169,7 +1169,18 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo /// `setMasterPassword()` sets the user's master password, saves their encryption keys and /// unlocks the vault. func test_setMasterPassword() async throws { - let account = Account.fixture() + let account = Account.fixture(profile: .fixture( + userDecryptionOptions: UserDecryptionOptions( + hasMasterPassword: true, + masterPasswordUnlock: MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), + masterKeyEncryptedUserKey: "encryptedUserKey", + salt: "SALT", + ), + keyConnectorOption: nil, + trustedDeviceOption: nil, + ), + )) client.result = .httpSuccess(testData: .emptyResponse) // Account encryption keys don't exist until after a MP has been set for non-TDE users. stateService.accountEncryptionKeys["1"] = nil @@ -1216,7 +1227,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "private", signingKey: nil, securityState: nil, - method: .password(password: "NEW_PASSWORD", userKey: "encryptedUserKey"), + method: .masterPasswordUnlock( + password: "NEW_PASSWORD", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)), + masterKeyWrappedUserKey: "encryptedUserKey", + salt: "SALT", + ), + ), ), ) } @@ -1258,7 +1276,13 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo encryptedPrivateKey: "PRIVATE_KEY", encryptedUserKey: "KEY", ) - stateService.activeAccount = Account.fixtureWithTdeNoPassword() + var tdeAccount = Account.fixtureWithTdeNoPassword() + tdeAccount.profile.userDecryptionOptions?.masterPasswordUnlock = MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), + masterKeyEncryptedUserKey: "NEW_KEY", + salt: "SALT", + ) + stateService.activeAccount = tdeAccount try await subject.setMasterPassword( "NEW_PASSWORD", @@ -1301,7 +1325,12 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo /// `setMasterPassword()` sets the user's master password, saves their encryption keys and /// unlocks the vault. func test_setMasterPassword_TDE() async throws { - let account = Account.fixtureWithTDE() + var account = Account.fixtureWithTDE() + account.profile.userDecryptionOptions?.masterPasswordUnlock = MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), + masterKeyEncryptedUserKey: "NEW_KEY", + salt: "SALT", + ) client.result = .httpSuccess(testData: .emptyResponse) clientService.mockCrypto.makeUpdatePasswordResult = .success( UpdatePasswordResponse(passwordHash: "NEW_PASSWORD_HASH", newKey: "NEW_KEY"), @@ -1347,7 +1376,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "PRIVATE_KEY", signingKey: nil, securityState: nil, - method: .password(password: "NEW_PASSWORD", userKey: "NEW_KEY"), + method: .masterPasswordUnlock( + password: "NEW_PASSWORD", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)), + masterKeyWrappedUserKey: "NEW_KEY", + salt: "SALT", + ), + ), ), ) } @@ -1797,7 +1833,18 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo /// `unlockVaultWithPassword(password:)` unlocks the vault with the user's password. func test_unlockVault() async throws { - stateService.activeAccount = .fixture() + stateService.activeAccount = .fixture(profile: .fixture( + userDecryptionOptions: UserDecryptionOptions( + hasMasterPassword: true, + masterPasswordUnlock: MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), + masterKeyEncryptedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + keyConnectorOption: nil, + trustedDeviceOption: nil, + ), + )) stateService.accountEncryptionKeys = [ "1": AccountEncryptionKeys( accountKeys: .fixtureFilled(), @@ -1820,7 +1867,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "PRIVATE_KEY", signingKey: "WRAPPED_SIGNING_KEY", securityState: "SECURITY_STATE", - method: .password(password: "password", userKey: "USER_KEY"), + method: .masterPasswordUnlock( + password: "password", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)), + masterKeyWrappedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + ), ), ) XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1")) @@ -2086,7 +2140,19 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo // `unlockVaultWithPassword(_:)` unlocks the vault with the user's password and checks if the // user's KDF settings need to be updated. func test_unlockVaultWithPassword_checksForKdfUpdate() async throws { - let account = Account.fixture(profile: .fixture(kdfIterations: 100_000)) + let account = Account.fixture(profile: .fixture( + kdfIterations: 100_000, + userDecryptionOptions: UserDecryptionOptions( + hasMasterPassword: true, + masterPasswordUnlock: MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: 100_000), + masterKeyEncryptedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + keyConnectorOption: nil, + trustedDeviceOption: nil, + ), + )) configService.featureFlagsBool[.forceUpdateKdfSettings] = false changeKdfService.needsKdfUpdateToMinimumsResult = true stateService.activeAccount = account @@ -2109,7 +2175,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "PRIVATE_KEY", signingKey: "WRAPPED_SIGNING_KEY", securityState: "SECURITY_STATE", - method: .password(password: "password", userKey: "USER_KEY"), + method: .masterPasswordUnlock( + password: "password", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: 100_000), + masterKeyWrappedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + ), ), ) XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1")) @@ -2125,7 +2198,19 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo // user's KDF settings need to be updated. If updating the user's KDF fails, an error is logged // but vault unlock still succeeds. func test_unlockVaultWithPassword_checksForKdfUpdate_error() async throws { - let account = Account.fixture(profile: .fixture(kdfIterations: 100_000)) + let account = Account.fixture(profile: .fixture( + kdfIterations: 100_000, + userDecryptionOptions: UserDecryptionOptions( + hasMasterPassword: true, + masterPasswordUnlock: MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: 100_000), + masterKeyEncryptedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + keyConnectorOption: nil, + trustedDeviceOption: nil, + ), + )) configService.featureFlagsBool[.forceUpdateKdfSettings] = false changeKdfService.needsKdfUpdateToMinimumsResult = true changeKdfService.updateKdfToMinimumsResult = .failure(BitwardenTestError.example) @@ -2151,7 +2236,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "PRIVATE_KEY", signingKey: "WRAPPED_SIGNING_KEY", securityState: "SECURITY_STATE", - method: .password(password: "password", userKey: "USER_KEY"), + method: .masterPasswordUnlock( + password: "password", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: 100_000), + masterKeyWrappedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + ), ), ) XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1")) @@ -2170,7 +2262,7 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo userDecryptionOptions: UserDecryptionOptions( hasMasterPassword: true, masterPasswordUnlock: MasterPasswordUnlockResponseModel( - kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: 600_000), + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), masterKeyEncryptedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", salt: "SALT", ), @@ -2203,7 +2295,7 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo method: .masterPasswordUnlock( password: "password", masterPasswordUnlock: MasterPasswordUnlockData( - kdf: .pbkdf2(iterations: 600_000), + kdf: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)), masterKeyWrappedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", salt: "SALT", ), @@ -2405,7 +2497,18 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo // `unlockVaultWithPassword(_:)` unlocks the vault with the user's password and migrates the // legacy pin keys. func test_unlockVaultWithPassword_migratesPinProtectedUserKey() async throws { - let account = Account.fixture() + let account = Account.fixture(profile: .fixture( + userDecryptionOptions: UserDecryptionOptions( + hasMasterPassword: true, + masterPasswordUnlock: MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), + masterKeyEncryptedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + keyConnectorOption: nil, + trustedDeviceOption: nil, + ), + )) clientService.mockCrypto.enrollPinWithEncryptedPinResult = .success( EnrollPinResponse( pinProtectedUserKeyEnvelope: "pinProtectedUserKeyEnvelope", @@ -2434,7 +2537,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "PRIVATE_KEY", signingKey: "WRAPPED_SIGNING_KEY", securityState: "SECURITY_STATE", - method: .password(password: "password", userKey: "USER_KEY"), + method: .masterPasswordUnlock( + password: "password", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)), + masterKeyWrappedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + ), ), ) XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1")) @@ -2447,7 +2557,7 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo XCTAssertEqual(stateService.encryptedPinByUserId["1"], "userKeyEncryptedPin") XCTAssertEqual(stateService.pinProtectedUserKeyEnvelopeValue["1"], "pinProtectedUserKeyEnvelope") XCTAssertEqual(flightRecorder.logMessages, [ - "[Auth] Vault unlocked, method: Password", + "[Auth] Vault unlocked, method: Master Password Unlock", "[Auth] Migrated from legacy PIN to PIN-protected user key envelope", ]) } @@ -2455,7 +2565,18 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo // `unlockVaultWithPassword(_:)` unlocks the vault with the user's password and sets the // PIN-protected user key in memory. func test_unlockVaultWithPassword_setsPinProtectedUserKeyInMemory() async throws { - let account = Account.fixture() + let account = Account.fixture(profile: .fixture( + userDecryptionOptions: UserDecryptionOptions( + hasMasterPassword: true, + masterPasswordUnlock: MasterPasswordUnlockResponseModel( + kdf: KdfConfig(kdfType: .pbkdf2sha256, iterations: Constants.pbkdf2Iterations), + masterKeyEncryptedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + keyConnectorOption: nil, + trustedDeviceOption: nil, + ), + )) clientService.mockCrypto.enrollPinWithEncryptedPinResult = .success( EnrollPinResponse( pinProtectedUserKeyEnvelope: "pinProtectedUserKeyEnvelope", @@ -2483,7 +2604,14 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo privateKey: "PRIVATE_KEY", signingKey: "WRAPPED_SIGNING_KEY", securityState: "SECURITY_STATE", - method: .password(password: "password", userKey: "USER_KEY"), + method: .masterPasswordUnlock( + password: "password", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)), + masterKeyWrappedUserKey: "MASTER_KEY_ENCRYPTED_USER_KEY", + salt: "SALT", + ), + ), ), ) XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1")) @@ -2495,7 +2623,7 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo XCTAssertEqual(stateService.encryptedPinByUserId["1"], "encryptedPin") XCTAssertNil(stateService.pinProtectedUserKeyEnvelopeValue["1"]) XCTAssertEqual(flightRecorder.logMessages, [ - "[Auth] Vault unlocked, method: Password", + "[Auth] Vault unlocked, method: Master Password Unlock", "[Auth] Set PIN-protected user key in memory", ]) } diff --git a/BitwardenShared/Core/Auth/Services/AuthService.swift b/BitwardenShared/Core/Auth/Services/AuthService.swift index 1f90fd2c8f..ec141fa0d4 100644 --- a/BitwardenShared/Core/Auth/Services/AuthService.swift +++ b/BitwardenShared/Core/Auth/Services/AuthService.swift @@ -24,6 +24,9 @@ enum AuthError: Error { /// The key used for login with device was missing. case missingLoginWithDeviceKey + /// The data for the master password unlock method was missing. + case missingMasterPasswordUnlockData + /// The request that should have been cached for the two-factor authentication method was missing. case missingTwoFactorRequest diff --git a/BitwardenShared/Core/Platform/Services/CryptoClientProtocolExtensionsTests.swift b/BitwardenShared/Core/Platform/Services/CryptoClientProtocolExtensionsTests.swift index 6f13881235..c99e249c2a 100644 --- a/BitwardenShared/Core/Platform/Services/CryptoClientProtocolExtensionsTests.swift +++ b/BitwardenShared/Core/Platform/Services/CryptoClientProtocolExtensionsTests.swift @@ -1,3 +1,4 @@ +import BitwardenSdk import XCTest @testable import BitwardenShared @@ -33,14 +34,31 @@ class CryptoClientProtocolExtensionsTests: BitwardenTestCase { encryptedPrivateKey: "PRIVATE_KEY", encryptedUserKey: "encryptedUserKey", ), - method: .password(password: "password123", userKey: "userKey"), + method: .masterPasswordUnlock( + password: "password123", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: 600_000), + masterKeyWrappedUserKey: "MASTER_KEY_WRAPPED_USER_KEY", + salt: "SALT", + ), + ), ) let request = try XCTUnwrap(subject.initializeUserCryptoRequest) XCTAssertEqual(request.userId, "1") XCTAssertEqual(request.kdfParams, .pbkdf2(iterations: 600_000)) XCTAssertEqual(request.email, "user@bitwarden.com") - XCTAssertEqual(request.method, .password(password: "password123", userKey: "userKey")) + XCTAssertEqual( + request.method, + .masterPasswordUnlock( + password: "password123", + masterPasswordUnlock: MasterPasswordUnlockData( + kdf: .pbkdf2(iterations: 600_000), + masterKeyWrappedUserKey: "MASTER_KEY_WRAPPED_USER_KEY", + salt: "SALT", + ), + ), + ) XCTAssertEqual(request.privateKey, "PRIVATE_KEY") XCTAssertEqual(request.securityState, "SECURITY_STATE") XCTAssertEqual(request.signingKey, "WRAPPED_SIGNING_KEY")