Skip to content
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

Keychain group migration #1243

Merged
merged 13 commits into from
Dec 1, 2023
7 changes: 0 additions & 7 deletions Example/ExampleApp.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@
C56EE276293F56D7004840D1 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE26C293F56D6004840D1 /* UIViewController.swift */; };
C56EE279293F56D7004840D1 /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE268293F56D6004840D1 /* Color.swift */; };
C56EE27B293F56F8004840D1 /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = C56EE27A293F56F8004840D1 /* WalletConnectAuth */; };
C56EE27D293F56F8004840D1 /* WalletConnectChat in Frameworks */ = {isa = PBXBuildFile; productRef = C56EE27C293F56F8004840D1 /* WalletConnectChat */; };
C56EE288293F5757004840D1 /* ThirdPartyConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE286293F5757004840D1 /* ThirdPartyConfigurator.swift */; };
C56EE289293F5757004840D1 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE280293F5757004840D1 /* Application.swift */; };
C56EE28A293F5757004840D1 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C56EE27F293F5757004840D1 /* AppDelegate.swift */; };
Expand Down Expand Up @@ -781,7 +780,6 @@
files = (
A573C53D29EC366500E3CBFD /* HDWalletKit in Frameworks */,
A59D25EE2AB3672700D7EA3A /* AsyncButton in Frameworks */,
C56EE27D293F56F8004840D1 /* WalletConnectChat in Frameworks */,
C5133A78294125CC00A8314C /* Web3 in Frameworks */,
C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */,
8487A9462A836C3F0003D5AF /* Sentry in Frameworks */,
Expand Down Expand Up @@ -2129,7 +2127,6 @@
packageProductDependencies = (
C56EE254293F569A004840D1 /* Starscream */,
C56EE27A293F56F8004840D1 /* WalletConnectAuth */,
C56EE27C293F56F8004840D1 /* WalletConnectChat */,
C5133A77294125CC00A8314C /* Web3 */,
C55D349829630D440004314A /* Web3Wallet */,
C5B2F7042970573D000DBA0E /* SolanaSwift */,
Expand Down Expand Up @@ -3576,10 +3573,6 @@
isa = XCSwiftPackageProductDependency;
productName = WalletConnectAuth;
};
C56EE27C293F56F8004840D1 /* WalletConnectChat */ = {
isa = XCSwiftPackageProductDependency;
productName = WalletConnectChat;
};
C579FEB52AFA86CD008855EB /* Web3Modal */ = {
isa = XCSwiftPackageProductDependency;
package = A5F1526D2ACDC46B00D745A6 /* XCRemoteSwiftPackageReference "web3modal-swift" */;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,20 @@
ReferencedContainer = "container:..">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "NO"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ChatTests"
BuildableName = "ChatTests"
BlueprintName = "ChatTests"
ReferencedContainer = "container:..">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
Expand Down Expand Up @@ -188,16 +202,6 @@
ReferencedContainer = "container:..">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "ChatTests"
BuildableName = "ChatTests"
BlueprintName = "ChatTests"
ReferencedContainer = "container:..">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
Expand Down
3 changes: 2 additions & 1 deletion Example/WalletApp/ApplicationLayer/Application.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Foundation
import WalletConnectChat
import WalletConnectUtils
import WalletConnectSigner

final class Application {
var uri: WalletConnectURI?
Expand Down
2 changes: 1 addition & 1 deletion Sources/Auth/AuthClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public struct AuthClientFactory {
guard let keyValueStorage = UserDefaults(suiteName: groupIdentifier) else {
fatalError("Could not instantiate UserDefaults for a group identifier \(groupIdentifier)")
}
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)
let iatProvider = DefaultIATProvider()

return AuthClientFactory.create(
Expand Down
3 changes: 2 additions & 1 deletion Sources/Chat/ChatClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Foundation
public struct ChatClientFactory {

static func create(keyserverUrl: String, relayClient: RelayClient, networkingInteractor: NetworkingInteractor, syncClient: SyncClient, historyClient: HistoryClient) -> ChatClient {
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
fatalError("fix access group")
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: "")
let keyserverURL = URL(string: keyserverUrl)!
return ChatClientFactory.create(
keyserverURL: keyserverURL,
Expand Down
2 changes: 1 addition & 1 deletion Sources/WalletConnectHistory/HistoryClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import Foundation
class HistoryClientFactory {

static func create() -> HistoryClient {
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: "")
let keyValueStorage = UserDefaults.standard
let logger = ConsoleLogger()
return HistoryClientFactory.create(
Expand Down
74 changes: 53 additions & 21 deletions Sources/WalletConnectKMS/Keychain/KeychainStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ public protocol KeychainStorageProtocol {
public final class KeychainStorage: KeychainStorageProtocol {

private let service: String
private let accessGroup: String
private let synchronizationQueue = DispatchQueue(label: "com.yourapp.KeychainStorage")

private let secItem: KeychainServiceProtocol

public init(keychainService: KeychainServiceProtocol = KeychainServiceWrapper(), serviceIdentifier: String) {
public init(
keychainService: KeychainServiceProtocol = KeychainServiceWrapper(),
serviceIdentifier: String,
accessGroup: String
) {
self.secItem = keychainService
service = serviceIdentifier
self.accessGroup = accessGroup
self.service = serviceIdentifier
}

public func add<T>(_ item: T, forKey key: String) throws where T: GenericPasswordConvertible {
Expand Down Expand Up @@ -55,7 +62,16 @@ public final class KeychainStorage: KeychainStorageProtocol {
case errSecSuccess:
return item as? Data
case errSecItemNotFound:
return tryMigrateAttrAccessibleOnRead(key: key) // TODO: Replace with nil once migration period ends
return try synchronizationQueue.sync {
// Try to update the accessibility attribute first - migration V1
tryUpdateAccessibilityAttribute(key: key)
// Then attempt to migrate to the new access group and return if item exists - migration V2
if let updatedData = try tryToMigrateKeyToNewAccessGroupOnRead(key: key) {
return updatedData
} else {
return nil
}
}
default:
throw KeychainError(status)
}
Expand All @@ -70,17 +86,21 @@ public final class KeychainStorage: KeychainStorageProtocol {
let attributes = [kSecValueData: data]

let status = secItem.update(query as CFDictionary, attributes as CFDictionary)

switch status {
case errSecSuccess:
return
case errSecItemNotFound:
try tryMigrateAttrAccessibleOnUpdate(data: data, key: key) // TODO: Remove once migration period ends
// Try to update the accessibility attribute - migration V1
tryUpdateAccessibilityAttribute(key: key)
// Then attempt to migrate to the new access group - migration V2
try tryToMigrateKeyToNewAccessGroupOnUpdate(data: data, key: key)
default:
throw KeychainError(status)
}
}


public func delete(key: String) throws {
let query = buildBaseServiceQuery(for: key)

Expand Down Expand Up @@ -109,40 +129,41 @@ public final class KeychainStorage: KeychainStorageProtocol {
kSecAttrIsInvisible: true,
kSecUseDataProtectionKeychain: true,
kSecAttrService: service,
kSecAttrAccessGroup: accessGroup,
kSecAttrAccount: key
]
}

private func tryMigrateAttrAccessibleOnRead(key: String) -> Data? {

private func tryUpdateAccessibilityAttribute(key: String) {
var updateQuery = buildBaseServiceQuery(for: key)
updateQuery.removeValue(forKey: kSecAttrAccessGroup)
updateQuery[kSecAttrAccessible] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly

let attributes = [kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]
let status = secItem.update(updateQuery as CFDictionary, attributes as CFDictionary)
let _ = secItem.update(updateQuery as CFDictionary, attributes as CFDictionary)
}

guard status == errSecSuccess else {
return nil
}
private func tryToMigrateKeyToNewAccessGroupOnRead(key: String) throws -> Data? {
tryToMigrateToNewAccessGroup(key: key)

// Try to read the item again with updated accessibility
var readQuery = buildBaseServiceQuery(for: key)
readQuery[kSecReturnData] = true

var item: CFTypeRef?
_ = secItem.copyMatching(readQuery as CFDictionary, &item)
let readStatus = secItem.copyMatching(readQuery as CFDictionary, &item)

return item as? Data
if readStatus == errSecSuccess, let data = item as? Data {
return data
} else {
return nil
}
}

private func tryMigrateAttrAccessibleOnUpdate(data: Data, key: String) throws {
var updateAccessQuery = buildBaseServiceQuery(for: key)
updateAccessQuery[kSecAttrAccessible] = kSecAttrAccessibleWhenUnlockedThisDeviceOnly
private func tryToMigrateKeyToNewAccessGroupOnUpdate(data: Data, key: String) throws {

let accessAttributes = [kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly]
let accessStatus = secItem.update(updateAccessQuery as CFDictionary, accessAttributes as CFDictionary)

guard accessStatus == errSecSuccess else {
throw KeychainError.itemNotFound
}
tryToMigrateToNewAccessGroup(key: key)

let updateQuery = buildBaseServiceQuery(for: key)
let updateAttributes = [kSecValueData: data]
Expand All @@ -153,4 +174,15 @@ public final class KeychainStorage: KeychainStorageProtocol {
throw KeychainError.itemNotFound
}
}

private func tryToMigrateToNewAccessGroup(key: String) {
var query = buildBaseServiceQuery(for: key)
query.removeValue(forKey: kSecAttrAccessGroup)

let attributesToUpdate = [
kSecAttrAccessGroup: accessGroup
] as [CFString: Any]

let _ = secItem.update(query as CFDictionary, attributesToUpdate as CFDictionary)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ public struct NetworkingClientFactory {
groupIdentifier: String
) -> NetworkingInteractor {
let logger = ConsoleLogger(prefix: "🕸️", loggingLevel: .off)

guard let keyValueStorage = UserDefaults(suiteName: groupIdentifier) else {
fatalError("Could not instantiate UserDefaults for a group identifier \(groupIdentifier)")
}
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")

let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)
return NetworkingClientFactory.create(relayClient: relayClient, logger: logger, keychainStorage: keychainStorage, keyValueStorage: keyValueStorage)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public struct NotifyClientFactory {
public static func create(projectId: String, groupIdentifier: String, networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, crypto: CryptoProvider, notifyHost: String, explorerHost: String) -> NotifyClient {
let logger = ConsoleLogger(prefix: "🔔",loggingLevel: .debug)
let keyserverURL = URL(string: "https://keys.walletconnect.com")!
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)
let groupKeychainService = GroupKeychainStorage(serviceIdentifier: groupIdentifier)
let databasePath = databasePath(appGroup: groupIdentifier, database: "notify.db")
let sqlite = DiskSqlite(path: databasePath)
Expand Down
4 changes: 3 additions & 1 deletion Sources/WalletConnectPairing/PairingClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ public struct PairingClientFactory {
groupIdentifier: String
) -> PairingClient {
let logger = ConsoleLogger(loggingLevel: .off)

guard let keyValueStorage = UserDefaults(suiteName: groupIdentifier) else {
fatalError("Could not instantiate UserDefaults for a group identifier \(groupIdentifier)")
}
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)

return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient)
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/WalletConnectPush/PushClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ public struct PushClientFactory {
environment: APNSEnvironment
) -> PushClient {

let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")

guard let keyValueStorage = UserDefaults(suiteName: groupIdentifier) else {
fatalError("Could not instantiate UserDefaults for a group identifier \(groupIdentifier)")
}
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)

return PushClientFactory.create(
projectId: projectId,
Expand Down
4 changes: 2 additions & 2 deletions Sources/WalletConnectRelay/RelayClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ public struct RelayClientFactory {
socketConnectionType: SocketConnectionType
) -> RelayClient {


guard let keyValueStorage = UserDefaults(suiteName: groupIdentifier) else {
fatalError("Could not instantiate UserDefaults for a group identifier \(groupIdentifier)")
}

let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)

let logger = ConsoleLogger(prefix: "🚄" ,loggingLevel: .off)

Expand Down
4 changes: 3 additions & 1 deletion Sources/WalletConnectSign/Sign/SignClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ public struct SignClientFactory {
groupIdentifier: String
) -> SignClient {
let logger = ConsoleLogger(loggingLevel: .debug)

guard let keyValueStorage = UserDefaults(suiteName: groupIdentifier) else {
fatalError("Could not instantiate UserDefaults for a group identifier \(groupIdentifier)")
}
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier)

return SignClientFactory.create(metadata: metadata, logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, pairingClient: pairingClient, networkingClient: networkingClient)
}

Expand Down
3 changes: 2 additions & 1 deletion Sources/WalletConnectSync/SyncClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import Foundation
final class SyncClientFactory {

static func create(networkInteractor: NetworkInteracting, bip44: BIP44Provider) -> SyncClient {
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk")
fatalError("fix access group")
let keychain = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: "")
return create(networkInteractor: networkInteractor, bip44: bip44, keychain: keychain)
}

Expand Down
3 changes: 2 additions & 1 deletion Tests/WalletConnectKMSTests/KeychainStorageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ final class KeychainStorageTests: XCTestCase {
fakeKeychain = KeychainServiceFake()
sut = KeychainStorage(
keychainService: fakeKeychain,
serviceIdentifier: "")
serviceIdentifier: "",
accessGroup: "")
}

override func tearDown() {
Expand Down