generated from bitwarden/template
-
Notifications
You must be signed in to change notification settings - Fork 27
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BITAU-122] [BITAU-133] Add encryption to/from shared the CoreData st…
…ore (#938)
- Loading branch information
1 parent
b1a16a0
commit 6a820f4
Showing
10 changed files
with
415 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
AuthenticatorBridgeKit/AuthenticatorBridgeItemDataView.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import Foundation | ||
|
||
/// A struct for storing **unencrypted** information about items that are shared between the Bitwarden | ||
/// and Authenticator apps. | ||
/// | ||
public struct AuthenticatorBridgeItemDataView: Codable, Equatable { | ||
// MARK: Properties | ||
|
||
/// Bool indicating if this item is a favorite. | ||
public let favorite: Bool | ||
|
||
/// The unique id of the item. | ||
public let id: String | ||
|
||
/// The name of the item. | ||
public let name: String | ||
|
||
/// The TOTP key used to generate codes. | ||
public let totpKey: String? | ||
|
||
/// The username of the Bitwarden account that owns this iteam. | ||
public let username: String? | ||
|
||
/// Initialize an `AuthenticatorBridgeItemDataView` with the values provided. | ||
/// | ||
/// - Parameters: | ||
/// - favorite: Bool indicating if this item is a favorite. | ||
/// - id: The unique id of the item. | ||
/// - name: The name of the item. | ||
/// - totpKey: The TOTP key used to generate codes. | ||
/// - username: The username of the Bitwarden account that owns this item. | ||
/// | ||
public init(favorite: Bool, id: String, name: String, totpKey: String?, username: String?) { | ||
self.favorite = favorite | ||
self.id = id | ||
self.name = name | ||
self.totpKey = totpKey | ||
self.username = username | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import CryptoKit | ||
import Foundation | ||
|
||
// MARK: - SharedCryptographyService | ||
|
||
/// A service for handling encrypting/decrypting items to be shared between the main | ||
/// Bitwarden app and the Authenticator app. | ||
/// | ||
public protocol SharedCryptographyService: AnyObject { | ||
/// Takes an array of `AuthenticatorBridgeItemDataModel` with encrypted data and | ||
/// returns the list with each member decrypted. | ||
/// | ||
/// - Parameter items: The encrypted array of items to be decrypted | ||
/// - Returns: the array of items with their data decrypted | ||
/// - Throws: AuthenticatorKeychainServiceError.keyNotFound if the Authenticator | ||
/// key is not in the shared repository. | ||
/// | ||
func decryptAuthenticatorItems( | ||
_ items: [AuthenticatorBridgeItemDataModel] | ||
) async throws -> [AuthenticatorBridgeItemDataView] | ||
|
||
/// Takes an array of `AuthenticatorBridgeItemDataModel` with decrypted data and | ||
/// returns the list with each member encrypted. | ||
/// | ||
/// - Parameter items: The decrypted array of items to be encrypted | ||
/// - Returns: the array of items with their data encrypted | ||
/// - Throws: AuthenticatorKeychainServiceError.keyNotFound if the Authenticator | ||
/// key is not in the shared repository. | ||
/// | ||
func encryptAuthenticatorItems( | ||
_ items: [AuthenticatorBridgeItemDataView] | ||
) async throws -> [AuthenticatorBridgeItemDataModel] | ||
} | ||
|
||
/// A concrete implementation of the `SharedCryptographyService` protocol. | ||
/// | ||
public class DefaultAuthenticatorCryptographyService: SharedCryptographyService { | ||
// MARK: Properties | ||
|
||
/// the `SharedKeyRepository` to obtain the shared Authenticator | ||
/// key to use in encrypting/decrypting | ||
private let sharedKeychainRepository: SharedKeychainRepository | ||
|
||
// MARK: Initialization | ||
|
||
/// Initialize a `DefaultAuthenticatorCryptographyService` | ||
/// | ||
/// - Parameter sharedKeychainRepository: the `SharedKeyRepository` to obtain the shared Authenticator | ||
/// key to use in encrypting/decrypting | ||
/// | ||
public init(sharedKeychainRepository: SharedKeychainRepository) { | ||
self.sharedKeychainRepository = sharedKeychainRepository | ||
} | ||
|
||
// MARK: Methods | ||
|
||
public func decryptAuthenticatorItems( | ||
_ items: [AuthenticatorBridgeItemDataModel] | ||
) async throws -> [AuthenticatorBridgeItemDataView] { | ||
let key = try await sharedKeychainRepository.getAuthenticatorKey() | ||
let symmetricKey = SymmetricKey(data: key) | ||
|
||
return items.map { item in | ||
AuthenticatorBridgeItemDataView( | ||
favorite: item.favorite, | ||
id: item.id, | ||
name: (try? decrypt(item.name, withKey: symmetricKey)) ?? "", | ||
totpKey: try? decrypt(item.totpKey, withKey: symmetricKey), | ||
username: try? decrypt(item.username, withKey: symmetricKey) | ||
) | ||
} | ||
} | ||
|
||
public func encryptAuthenticatorItems( | ||
_ items: [AuthenticatorBridgeItemDataView] | ||
) async throws -> [AuthenticatorBridgeItemDataModel] { | ||
let key = try await sharedKeychainRepository.getAuthenticatorKey() | ||
let symmetricKey = SymmetricKey(data: key) | ||
|
||
return items.map { item in | ||
AuthenticatorBridgeItemDataModel( | ||
favorite: item.favorite, | ||
id: item.id, | ||
name: encrypt(item.name, withKey: symmetricKey) ?? "", | ||
totpKey: encrypt(item.totpKey, withKey: symmetricKey), | ||
username: encrypt(item.username, withKey: symmetricKey) | ||
) | ||
} | ||
} | ||
|
||
/// Decrypts a string given a key. | ||
/// | ||
/// - Parameters: | ||
/// - string: The string to decrypt. | ||
/// - key: The key to decrypt with. | ||
/// - Returns: A decrypted string, or `nil` if the passed-in string was not encoded in Base64. | ||
/// | ||
private func decrypt(_ string: String?, withKey key: SymmetricKey) throws -> String? { | ||
guard let string, !string.isEmpty, let data = Data(base64Encoded: string) else { | ||
return nil | ||
} | ||
let encryptedSealedBox = try AES.GCM.SealedBox( | ||
combined: data | ||
) | ||
let decryptedBox = try AES.GCM.open( | ||
encryptedSealedBox, | ||
using: key | ||
) | ||
return String(data: decryptedBox, encoding: .utf8) | ||
} | ||
|
||
/// Encrypt a string with the given key. | ||
/// | ||
/// - Parameters: | ||
/// - plainText: The string to encrypt | ||
/// - key: The key to use to encrypt the string | ||
/// - Returns: An encrypted string or `nil` if the string was nil | ||
/// | ||
private func encrypt(_ plainText: String?, withKey key: SymmetricKey) -> String? { | ||
guard let plainText else { | ||
return nil | ||
} | ||
|
||
let nonce = randomData(lengthInBytes: 12) | ||
|
||
let plainData = plainText.data(using: .utf8) | ||
let sealedData = try? AES.GCM.seal(plainData!, using: key, nonce: AES.GCM.Nonce(data: nonce)) | ||
return sealedData?.combined?.base64EncodedString() | ||
} | ||
|
||
/// Generate random data of the length specified | ||
/// | ||
/// - Parameter lengthInBytes: the length of the random data to generate | ||
/// - Returns: random `Data` of the length in bytes requested. | ||
/// | ||
private func randomData(lengthInBytes: Int) -> Data { | ||
var data = Data(count: lengthInBytes) | ||
_ = data.withUnsafeMutableBytes { bytes in | ||
SecRandomCopyBytes(kSecRandomDefault, lengthInBytes, bytes.baseAddress!) | ||
} | ||
return data | ||
} | ||
} |
Oops, something went wrong.