Skip to content
Open
Show file tree
Hide file tree
Changes from 40 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b6b0513
Getvalue initial
KatherineInCode Mar 24, 2026
b359f17
setValue initial
KatherineInCode Mar 25, 2026
acb5d43
More set
KatherineInCode Mar 25, 2026
176d4c6
deleteValue initial
KatherineInCode Mar 25, 2026
393dabe
Handle shared things better
KatherineInCode Mar 26, 2026
9e7efde
Move to enum for namespacing
KatherineInCode Mar 26, 2026
e99d2db
Adjust tests
KatherineInCode Mar 26, 2026
960a9bc
Updates to tests
KatherineInCode Mar 26, 2026
a777a0a
Merge branch 'main' into pm-34009/keychain-service-facade
KatherineInCode Mar 30, 2026
af40156
Adjust prefix location
KatherineInCode Mar 30, 2026
338c433
Initial passthrough in sharedstorage
KatherineInCode Mar 30, 2026
14a23e8
Replace storage with facade
KatherineInCode Mar 30, 2026
1e500bd
Fix tests
KatherineInCode Mar 30, 2026
15a181a
Use shared facade in BWA repository
KatherineInCode Mar 30, 2026
ec529b1
Initial pass on keychain repository
KatherineInCode Mar 30, 2026
5b1e60d
Make KeychainService AutoMockable
KatherineInCode Mar 30, 2026
be174ff
Fix test
KatherineInCode Mar 30, 2026
2422d52
Delete storage
KatherineInCode Mar 30, 2026
1c1ac16
Bring back SharedKeychainItem
KatherineInCode Mar 30, 2026
15bfb65
Merge branch 'main' into pm-34009/keychain-service-facade
KatherineInCode Mar 30, 2026
519a8c3
Update comments
KatherineInCode Mar 31, 2026
ff0b9d0
Include optional setter for simplicity
KatherineInCode Mar 31, 2026
741162e
Fix up shared keychain repository
KatherineInCode Mar 31, 2026
4095106
Merge branch 'main' into pm-34009/keychain-service-facade
KatherineInCode Mar 31, 2026
946d7f7
Move mock to automockable
KatherineInCode Apr 1, 2026
3208689
Clear chaff
KatherineInCode Apr 1, 2026
bc2ba84
Update tests
KatherineInCode Apr 1, 2026
8c2461a
Update tests to Swift Testing
KatherineInCode Apr 2, 2026
fa2b3cf
Fix variable name
KatherineInCode Apr 2, 2026
9989fda
Merge branch 'main' into pm-34009/keychain-service-facade
KatherineInCode Apr 3, 2026
c7d7e73
Fix merge weirdness
KatherineInCode Apr 3, 2026
110fbeb
Convert KeychainRepository to Automockable
KatherineInCode Apr 6, 2026
5042524
Fix warnings
KatherineInCode Apr 6, 2026
793d71b
Spin Claude on tests
KatherineInCode Apr 6, 2026
57abacc
Naive convert to Swift Testing
KatherineInCode Apr 6, 2026
70add3b
Refactor
KatherineInCode Apr 6, 2026
f74b44b
Update
KatherineInCode Apr 6, 2026
a97d331
Continue fixing up tests
KatherineInCode Apr 6, 2026
5b509f7
Update tests
KatherineInCode Apr 7, 2026
56aa14d
Update tests
KatherineInCode Apr 7, 2026
b74fb5a
Use guard
KatherineInCode Apr 7, 2026
68fef64
Localize thing that doesn't need to be non-local
KatherineInCode Apr 7, 2026
3994525
Clean warnings
KatherineInCode Apr 7, 2026
98aca51
Fix warning
KatherineInCode Apr 7, 2026
dfca6a1
Merge branch 'main' into pm-34009/keychain-service-facade
KatherineInCode Apr 7, 2026
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

This file was deleted.

113 changes: 77 additions & 36 deletions AuthenticatorBridgeKit/SharedKeychain/SharedKeychainRepository.swift
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
import BitwardenKit
import Foundation

// MARK: - SharedKeychainItem

/// Enumeration of support Keychain Items that can be placed in the `SharedKeychainRepository`
///
public enum SharedKeychainItem: Equatable, KeychainItem {
/// A date at which a BWPM account automatically logs out.
case accountAutoLogout(userId: String)

/// The keychain item for the authenticator encryption key.
case authenticatorKey

/// The `SecAccessControlCreateFlags` level for this keychain item.
/// If `nil`, no extra protection is applied.
public var accessControlFlags: SecAccessControlCreateFlags? { nil }

/// The protection level for this keychain item.
public var protection: CFTypeRef { kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly }

/// The storage key for this keychain item.
public var unformattedKey: String {
switch self {
case let .accountAutoLogout(userId: userId):
"accountAutoLogout_\(userId)"
case .authenticatorKey:
"authenticatorKey"
}
}
}

// MARK: - SharedKeychainRepository

/// A repository for managing keychain items to be shared between Password Manager and Authenticator.
/// This should be the entry point in retrieving items from the shared keychain.
public protocol SharedKeychainRepository { // sourcery: AutoMockable
/// Deletes the authenticator key.
///
func deleteAuthenticatorKey() async throws

/// Gets the authenticator key.
///
/// - Returns: Data representing the authenticator key.
///
func getAuthenticatorKey() async throws -> Data

/// Stores the access token for a user in the keychain.
///
/// - Parameter value: The authenticator key to store.
///
func setAuthenticatorKey(_ value: Data) async throws
// MARK: AccountAutoLogoutTime

/// Gets when a user account should automatically log out.
///
Expand All @@ -42,48 +57,52 @@ public protocol SharedKeychainRepository { // sourcery: AutoMockable
_ value: Date?,
userId: String,
) async throws
}

public class DefaultSharedKeychainRepository: SharedKeychainRepository {
/// The shared keychain storage used by the repository.
let storage: SharedKeychainStorage
// MARK: AuthenticatorKey

/// Initialize a `DefaultSharedKeychainStorage`.
/// Deletes the authenticator key.
///
/// - Parameters:
/// - storage: The shared keychain storage used by the repository
public init(storage: SharedKeychainStorage) {
self.storage = storage
}

public func deleteAuthenticatorKey() async throws {
try await storage.deleteValue(for: .authenticatorKey)
}
func deleteAuthenticatorKey() async throws

/// Gets the authenticator key.
///
/// - Returns: Data representing the authenticator key.
///
public func getAuthenticatorKey() async throws -> Data {
try await storage.getValue(for: .authenticatorKey)
}
func getAuthenticatorKey() async throws -> Data

/// Stores the access token for a user in the keychain.
///
/// - Parameter value: The authenticator key to store.
///
public func setAuthenticatorKey(_ value: Data) async throws {
try await storage.setValue(value, for: .authenticatorKey)
func setAuthenticatorKey(_ value: Data) async throws
}

public class DefaultSharedKeychainRepository: SharedKeychainRepository {
/// The keychain service facade used by the repository.
let keychainServiceFacade: KeychainServiceFacade

/// Initialize a `DefaultSharedKeychainRepository`.
///
/// - Parameters:
/// - keychainServiceFacade: The keychain service facade used by the repository
public init(keychainServiceFacade: KeychainServiceFacade) {
self.keychainServiceFacade = keychainServiceFacade
}

// MARK: AccountAutoLogoutTime

/// Gets when a user account should automatically log out.
///
/// - Parameters:
/// - userId: The user ID of the account
/// - Returns: The time the user should be automatically logged out. If `nil`, then the user should not be.
///
public func getAccountAutoLogoutTime(userId: String) async throws -> Date? {
try await storage.getValue(for: .accountAutoLogout(userId: userId))
do {
return try await keychainServiceFacade.getValue(for: SharedKeychainItem.accountAutoLogout(userId: userId))
} catch KeychainServiceError.osStatusError(errSecItemNotFound), KeychainServiceError.keyNotFound {
return nil
}
}

/// Sets when a user account should automatically log out.
Expand All @@ -96,6 +115,28 @@ public class DefaultSharedKeychainRepository: SharedKeychainRepository {
_ value: Date?,
userId: String,
) async throws {
try await storage.setValue(value, for: .accountAutoLogout(userId: userId))
try await keychainServiceFacade.setValue(value, for: SharedKeychainItem.accountAutoLogout(userId: userId))
}

// MARK: AuthenticatorKey

public func deleteAuthenticatorKey() async throws {
try await keychainServiceFacade.deleteValue(for: SharedKeychainItem.authenticatorKey)
}

/// Gets the authenticator key.
///
/// - Returns: Data representing the authenticator key.
///
public func getAuthenticatorKey() async throws -> Data {
try await keychainServiceFacade.getValue(for: SharedKeychainItem.authenticatorKey)
}

/// Stores the access token for a user in the keychain.
///
/// - Parameter value: The authenticator key to store.
///
public func setAuthenticatorKey(_ value: Data) async throws {
try await keychainServiceFacade.setValue(value, for: SharedKeychainItem.authenticatorKey)
}
}
Loading
Loading