Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions BitwardenShared/Core/Auth/Repositories/AuthRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1247,14 +1247,31 @@ extension DefaultAuthRepository: AuthRepository {
await flightRecorder.log("[Auth] Migrated from legacy PIN to PIN-protected user key envelope")
case .decryptedKey,
.password:
// If the user has a pin, but requires master password after restart, set the pin
// protected user key in memory for future unlocks prior to app restart.
guard let encryptedPin = try await stateService.getEncryptedPin() else { break }
guard let encryptedPin = try await stateService.getEncryptedPin(),
try await stateService.pinProtectedUserKeyEnvelope() == nil
else {
break
}

let enrollPinResponse = try await clientService.crypto().enrollPinWithEncryptedPin(
encryptedPin: encryptedPin,
)
try await stateService.setPinProtectedUserKeyToMemory(enrollPinResponse.pinProtectedUserKeyEnvelope)
await flightRecorder.log("[Auth] Set PIN-protected user key in memory")
let pinProtectedUserKey = try await stateService.pinProtectedUserKey()
let pinUnlockRequiresPasswordAfterRestart = try await stateService.pinUnlockRequiresPasswordAfterRestart()

if pinProtectedUserKey != nil, !pinUnlockRequiresPasswordAfterRestart {
// The stored PIN-protected user key needs to be migrated to a PIN-protected user key envelope.
try await stateService.setPinKeys(
enrollPinResponse: enrollPinResponse,
requirePasswordAfterRestart: pinUnlockRequiresPasswordAfterRestart,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿค” Do we also need to delete the pinProtectedUserKey out of state service as part of this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, yeah. This is handled in StateService:

// Remove any legacy pin protected user keys.
appSettingsStore.setPinProtectedUserKey(key: nil, userId: userId)

await flightRecorder.log("[Auth] Migrated from legacy PIN to PIN-protected user key envelope")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๐Ÿ’ก It seems to me we might want to expand the Flight Recorder to work something like OSLog, where you can have several different subsystem logs, and so we could flight record just some subsystems, etc. and it would let us prepend the [Auth] ourselves. Something to consider long-term, I suppose

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ohh that's a good idea, I can drop a ticket into the backlog.

} else {
// If the user has a PIN, but requires master password after restart, set the
// PIN-protected user key in memory for future unlocks prior to app restart.
try await stateService.setPinProtectedUserKeyToMemory(enrollPinResponse.pinProtectedUserKeyEnvelope)
await flightRecorder.log("[Auth] Set PIN-protected user key in memory")
}
case .pinEnvelope:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2337,8 +2337,62 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
XCTAssertEqual(stateService.manuallyLockedAccounts["1"], false)

// Existing pin is migrated to pin protected key envelope.
XCTAssertEqual(clientService.mockCrypto.enrollPinWithEncryptedPinEncryptedPin, "encryptedPin")
XCTAssertNil(stateService.accountVolatileData["1"]?.pinProtectedUserKey)
XCTAssertEqual(stateService.encryptedPinByUserId["1"], "userKeyEncryptedPin")
XCTAssertEqual(stateService.pinProtectedUserKeyEnvelopeValue["1"], "pinProtectedUserKeyEnvelope")
XCTAssertEqual(flightRecorder.logMessages, [
"[Auth] Vault unlocked, method: Password",
"[Auth] Migrated from legacy PIN to PIN-protected user key envelope",
])
}

// `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()
clientService.mockCrypto.enrollPinWithEncryptedPinResult = .success(
EnrollPinResponse(
pinProtectedUserKeyEnvelope: "pinProtectedUserKeyEnvelope",
userKeyEncryptedPin: "userKeyEncryptedPin",
),
)
stateService.activeAccount = account
stateService.accountEncryptionKeys = [
"1": AccountEncryptionKeys(
accountKeys: .fixtureFilled(),
encryptedPrivateKey: "PRIVATE_KEY",
encryptedUserKey: "USER_KEY",
),
]
stateService.encryptedPinByUserId[account.profile.userId] = "encryptedPin"

try await subject.unlockVaultWithPassword(password: "password")

XCTAssertEqual(
clientService.mockCrypto.initializeUserCryptoRequest,
InitUserCryptoRequest(
userId: "1",
kdfParams: .pbkdf2(iterations: UInt32(Constants.pbkdf2Iterations)),
email: "[email protected]",
privateKey: "PRIVATE_KEY",
signingKey: "WRAPPED_SIGNING_KEY",
securityState: "SECURITY_STATE",
method: .password(password: "password", userKey: "USER_KEY"),
),
)
XCTAssertFalse(vaultTimeoutService.isLocked(userId: "1"))
XCTAssertTrue(vaultTimeoutService.unlockVaultHadUserInteraction)
XCTAssertEqual(stateService.manuallyLockedAccounts["1"], false)

XCTAssertEqual(clientService.mockCrypto.enrollPinWithEncryptedPinEncryptedPin, "encryptedPin")
XCTAssertEqual(stateService.accountVolatileData["1"]?.pinProtectedUserKey, "pinProtectedUserKeyEnvelope")
XCTAssertEqual(stateService.encryptedPinByUserId["1"], "encryptedPin")
XCTAssertNil(stateService.pinProtectedUserKeyEnvelopeValue["1"])
XCTAssertEqual(flightRecorder.logMessages, [
"[Auth] Vault unlocked, method: Password",
"[Auth] Set PIN-protected user key in memory",
])
}

/// `unlockVaultWithPIN(_:)` unlocks the vault with the user's PIN and migrates the legacy pin keys.
Expand Down Expand Up @@ -2383,6 +2437,10 @@ class AuthRepositoryTests: BitwardenTestCase { // swiftlint:disable:this type_bo
XCTAssertEqual(clientService.mockCrypto.enrollPinWithEncryptedPinEncryptedPin, "encryptedPin")
XCTAssertEqual(stateService.pinProtectedUserKeyEnvelopeValue["1"], "pinProtectedUserKeyEnvelope")
XCTAssertEqual(stateService.encryptedPinByUserId["1"], "userKeyEncryptedPin")
XCTAssertEqual(flightRecorder.logMessages, [
"[Auth] Vault unlocked, method: PIN",
"[Auth] Migrated from legacy PIN to PIN-protected user key envelope",
])
}

/// `unlockVaultWithPIN(_:)` unlocks the vault with the user's PIN using the pin protected user key envelope.
Expand Down