-
Notifications
You must be signed in to change notification settings - Fork 77
[PM-23289] Migrate PIN unlock keys on biometrics/MP unlock #2026
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
) | ||
await flightRecorder.log("[Auth] Migrated from legacy PIN to PIN-protected user key envelope") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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. | ||
|
@@ -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. | ||
|
There was a problem hiding this comment.
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?There was a problem hiding this comment.
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:
ios/BitwardenShared/Core/Platform/Services/StateService.swift
Lines 2092 to 2093 in 6a5266c