Skip to content
This repository was archived by the owner on Feb 24, 2025. It is now read-only.

Commit 3dce946

Browse files
Change how we save the encyption key (#1290)
Task/Issue URL: https://app.asana.com/0/1177771139624306/1204466438263867/f Description: During migration to a new mac users lose their history because the encryption key (used to encrypt and decrypt history and autofill data) is not migrated into the new mac. This PR changes the way the encryption key is saved. It used to be saved in binary as an iCloud item with this change will be saved in bae64 and as a login item. The change to login item should allow the key to be migrated to the new mac. Saving the key in base 64 so that is visible from keychain could be potentially use to fix problems after migration afterwards. If the key is not been saved in the new way yet it will read the original one and save it in the new way
1 parent 17a5622 commit 3dce946

File tree

2 files changed

+99
-17
lines changed

2 files changed

+99
-17
lines changed

DuckDuckGo/Common/FileSystem/EncryptionKeys/EncryptionKeyStore.swift

+66-17
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ enum EncryptionKeyStoreError: Error, ErrorWithParameters {
2323
case storageFailed(OSStatus)
2424
case readFailed(OSStatus)
2525
case deletionFailed(OSStatus)
26+
case cannotTransformDataToString(OSStatus)
27+
case cannotTransfrotmStringToBase64Data(OSStatus)
2628

2729
var errorParameters: [String: String] {
2830
switch self {
@@ -32,6 +34,10 @@ enum EncryptionKeyStoreError: Error, ErrorWithParameters {
3234
return [Pixel.Parameters.keychainErrorCode: "\(status)"]
3335
case .deletionFailed(let status):
3436
return [Pixel.Parameters.keychainErrorCode: "\(status)"]
37+
case .cannotTransformDataToString(let status):
38+
return [Pixel.Parameters.keychainErrorCode: "\(status)"]
39+
case .cannotTransfrotmStringToBase64Data(let status):
40+
return [Pixel.Parameters.keychainErrorCode: "\(status)"]
3541
}
3642
}
3743
}
@@ -45,6 +51,8 @@ final class EncryptionKeyStore: EncryptionKeyStoring {
4551
static let encryptionKeyAccount = "com.duckduckgo.macos.browser"
4652
#endif
4753
static let encryptionKeyService = "DuckDuckGo Privacy Browser Data Encryption Key"
54+
55+
static let encryptionKeyServiceBase64 = "DuckDuckGo Privacy Browser Encryption Key v2"
4856
}
4957

5058
private let generator: EncryptionKeyGenerating
@@ -53,8 +61,7 @@ final class EncryptionKeyStore: EncryptionKeyStoring {
5361
private var defaultKeychainQueryAttributes: [String: Any] {
5462
return [
5563
kSecClass: kSecClassGenericPassword,
56-
kSecAttrAccount: account,
57-
kSecUseDataProtectionKeychain: true
64+
kSecAttrAccount: account
5865
] as [String: Any]
5966
}
6067

@@ -70,25 +77,43 @@ final class EncryptionKeyStore: EncryptionKeyStoring {
7077
// MARK: - Keychain
7178

7279
func store(key: SymmetricKey) throws {
73-
var query = defaultKeychainQueryAttributes
74-
query[kSecAttrService as String] = Constants.encryptionKeyService
75-
query[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
76-
query[kSecValueData as String] = key.dataRepresentation
80+
let attributes: [String: Any] = [
81+
kSecClass as String: kSecClassGenericPassword,
82+
kSecAttrAccount as String: account,
83+
kSecValueData as String: key.dataRepresentation.base64EncodedString(),
84+
kSecAttrService as String: Constants.encryptionKeyServiceBase64,
85+
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
86+
]
87+
88+
// Add the login item to the keychain
89+
let status = SecItemAdd(attributes as CFDictionary, nil)
90+
91+
guard status == errSecSuccess else {
92+
throw EncryptionKeyStoreError.storageFailed(status)
93+
}
94+
}
7795

78-
let status = SecItemAdd(query as CFDictionary, nil)
79-
80-
guard status == errSecSuccess else {
81-
throw EncryptionKeyStoreError.storageFailed(status)
96+
func readKey() throws -> SymmetricKey {
97+
/// Needed to change how we save the key
98+
/// Checks if the base64 non iCloud key already exist
99+
/// if so we return
100+
if let key = try? readKeyFromKeychain(account: account, format: .base64) {
101+
return key
102+
}
103+
/// If the base64 key does not exist we check if we have the legacy key
104+
/// if so we store it as base64 local item key
105+
if let key = try readKeyFromKeychain(account: account, format: .raw) {
106+
try store(key: key)
82107
}
83-
}
84108

85-
func readKey() throws -> SymmetricKey {
86-
if let key = try readKeyFromKeychain(account: account) {
109+
/// We try again to retrieve the base64 non iCloud key
110+
/// if so we return the key
111+
/// otherwise we generate a new one and store it
112+
if let key = try readKeyFromKeychain(account: account, format: .base64) {
87113
return key
88114
} else {
89115
let generatedKey = generator.randomKey()
90116
try store(key: generatedKey)
91-
92117
return generatedKey
93118
}
94119
}
@@ -105,11 +130,22 @@ final class EncryptionKeyStore: EncryptionKeyStoring {
105130

106131
// MARK: - Private
107132

108-
private func readKeyFromKeychain(account: String) throws -> SymmetricKey? {
133+
private enum KeyFormat {
134+
case raw
135+
case base64
136+
}
137+
138+
private func readKeyFromKeychain(account: String, format: KeyFormat) throws -> SymmetricKey? {
109139
var query = defaultKeychainQueryAttributes
110140
query[kSecReturnData as String] = true
111141

112142
var item: CFTypeRef?
143+
switch format {
144+
case .raw:
145+
query[kSecAttrService as String] = Constants.encryptionKeyService
146+
case .base64:
147+
query[kSecAttrService as String] = Constants.encryptionKeyServiceBase64
148+
}
113149
let status = SecItemCopyMatching(query as CFDictionary, &item)
114150

115151
switch status {
@@ -118,12 +154,25 @@ final class EncryptionKeyStore: EncryptionKeyStoring {
118154
throw EncryptionKeyStoreError.readFailed(status)
119155
}
120156

121-
return SymmetricKey(data: data)
157+
let finalData: Data
158+
switch format {
159+
case .raw:
160+
finalData = data
161+
case .base64:
162+
guard let base64String = String(data: data, encoding: .utf8) else {
163+
throw EncryptionKeyStoreError.cannotTransformDataToString(status)
164+
}
165+
guard let keyData = Data(base64Encoded: base64String) else {
166+
throw EncryptionKeyStoreError.cannotTransfrotmStringToBase64Data(status)
167+
}
168+
finalData = keyData
169+
}
170+
print(finalData.base64EncodedString())
171+
return SymmetricKey(data: finalData)
122172
case errSecItemNotFound:
123173
return nil
124174
default:
125175
throw EncryptionKeyStoreError.readFailed(status)
126176
}
127177
}
128-
129178
}

IntegrationTests/EncryptionKeyStoreTests.swift

+33
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ final class EncryptionKeyStoreTests: XCTestCase {
2727
override func setUp() {
2828
super.setUp()
2929
removeTestKeys()
30+
UserDefaultsWrapper<Any>.clearAll()
3031
}
3132

3233
override func tearDown() {
3334
super.tearDown()
3435
removeTestKeys()
36+
UserDefaultsWrapper<Any>.clearAll()
3537
}
3638

3739
func testStoringKeys() {
@@ -68,9 +70,40 @@ final class EncryptionKeyStoreTests: XCTestCase {
6870
XCTAssertEqual(firstReadKey, secondReadKey)
6971
}
7072

73+
func testThatIfWhenThereIsAKeySavedInRowFormatTheSameKeyIsReadInBase64() {
74+
let originalKey = generator.randomKey()
75+
let store = EncryptionKeyStore(generator: generator, account: account)
76+
77+
try? storeWithOldMechanism(key: originalKey)
78+
let readKey = try? store.readKey()
79+
80+
XCTAssertEqual(originalKey, readKey)
81+
}
82+
7183
private func removeTestKeys() {
7284
let store = EncryptionKeyStore(generator: generator, account: account)
7385
try? store.deleteKey()
7486
}
7587

88+
private func storeWithOldMechanism(key: SymmetricKey) throws {
89+
var query = oldDefaultKeychainQueryAttributes
90+
query[kSecAttrService as String] = EncryptionKeyStore.Constants.encryptionKeyService
91+
query[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
92+
query[kSecValueData as String] = key.dataRepresentation
93+
94+
let status = SecItemAdd(query as CFDictionary, nil)
95+
96+
guard status == errSecSuccess else {
97+
throw EncryptionKeyStoreError.storageFailed(status)
98+
}
99+
}
100+
101+
private var oldDefaultKeychainQueryAttributes: [String: Any] {
102+
return [
103+
kSecClass: kSecClassGenericPassword,
104+
kSecAttrAccount: account,
105+
kSecUseDataProtectionKeychain: true
106+
] as [String: Any]
107+
}
108+
76109
}

0 commit comments

Comments
 (0)