Use keychain service as item context
Primary key = (context, username)
This commit is contained in:
parent
4a5bc92fcb
commit
304d0215b6
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Update API to access current Wi-Fi SSID.
|
- Update API to access current Wi-Fi SSID.
|
||||||
|
- Refactor access to keychain.
|
||||||
|
|
||||||
## 3.0.0 (2020-11-15)
|
## 3.0.0 (2020-11-15)
|
||||||
|
|
||||||
|
|
|
@ -54,39 +54,18 @@ public enum KeychainError: Error {
|
||||||
|
|
||||||
/// Wrapper for easy keychain access and modification.
|
/// Wrapper for easy keychain access and modification.
|
||||||
public class Keychain {
|
public class Keychain {
|
||||||
private let service: String?
|
|
||||||
|
|
||||||
private let accessGroup: String?
|
private let accessGroup: String?
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public init() {
|
|
||||||
service = Bundle.main.bundleIdentifier
|
|
||||||
accessGroup = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates a keychain in an App Group.
|
Creates a keychain.
|
||||||
|
|
||||||
- Parameter group: The App Group.
|
- Parameter group: An optional App Group.
|
||||||
- Precondition: Proper App Group entitlements.
|
- Precondition: Proper App Group entitlements (if group is non-nil).
|
||||||
**/
|
**/
|
||||||
public init(group: String) {
|
public init(group: String?) {
|
||||||
service = nil
|
|
||||||
accessGroup = group
|
accessGroup = group
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Creates a keychain in an App Group and a Team ID prefix.
|
|
||||||
|
|
||||||
- Parameter team: The Team ID prefix.
|
|
||||||
- Parameter group: The App Group.
|
|
||||||
- Precondition: Proper App Group entitlements.
|
|
||||||
**/
|
|
||||||
public init(team: String, group: String) {
|
|
||||||
service = nil
|
|
||||||
accessGroup = "\(team).\(group)"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Password
|
// MARK: Password
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,16 +73,16 @@ public class Keychain {
|
||||||
|
|
||||||
- Parameter password: The password to set.
|
- Parameter password: The password to set.
|
||||||
- Parameter username: The username to set the password for.
|
- Parameter username: The username to set the password for.
|
||||||
- Parameter label: An optional label.
|
- Parameter context: An optional context.
|
||||||
- Throws: `KeychainError.add` if unable to add the password to the keychain.
|
- Throws: `KeychainError.add` if unable to add the password to the keychain.
|
||||||
**/
|
**/
|
||||||
public func set(password: String, for username: String, label: String? = nil) throws {
|
public func set(password: String, for username: String, context: String? = nil) throws {
|
||||||
do {
|
do {
|
||||||
let currentPassword = try self.password(for: username)
|
let currentPassword = try self.password(for: username, context: context)
|
||||||
guard password != currentPassword else {
|
guard password != currentPassword else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
removePassword(for: username)
|
removePassword(for: username, context: context)
|
||||||
} catch let e as KeychainError {
|
} catch let e as KeychainError {
|
||||||
|
|
||||||
// rethrow cancelation
|
// rethrow cancelation
|
||||||
|
@ -115,17 +94,14 @@ public class Keychain {
|
||||||
}
|
}
|
||||||
|
|
||||||
var query = [String: Any]()
|
var query = [String: Any]()
|
||||||
setScope(query: &query)
|
setScope(query: &query, context: context)
|
||||||
query[kSecClass as String] = kSecClassGenericPassword
|
query[kSecClass as String] = kSecClassGenericPassword
|
||||||
if let label = label {
|
|
||||||
query[kSecAttrLabel as String] = label
|
|
||||||
}
|
|
||||||
query[kSecAttrAccount as String] = username
|
query[kSecAttrAccount as String] = username
|
||||||
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
|
||||||
query[kSecValueData as String] = password.data(using: .utf8)
|
query[kSecValueData as String] = password.data(using: .utf8)
|
||||||
|
|
||||||
let status = SecItemAdd(query as CFDictionary, nil)
|
let status = SecItemAdd(query as CFDictionary, nil)
|
||||||
guard (status == errSecSuccess) else {
|
guard status == errSecSuccess else {
|
||||||
throw KeychainError.add
|
throw KeychainError.add
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,28 +110,30 @@ public class Keychain {
|
||||||
Removes a password.
|
Removes a password.
|
||||||
|
|
||||||
- Parameter username: The username to remove the password for.
|
- Parameter username: The username to remove the password for.
|
||||||
|
- Parameter context: An optional context.
|
||||||
- Returns: `true` if the password was successfully removed.
|
- Returns: `true` if the password was successfully removed.
|
||||||
**/
|
**/
|
||||||
@discardableResult public func removePassword(for username: String) -> Bool {
|
@discardableResult public func removePassword(for username: String, context: String? = nil) -> Bool {
|
||||||
var query = [String: Any]()
|
var query = [String: Any]()
|
||||||
setScope(query: &query)
|
setScope(query: &query, context: context)
|
||||||
query[kSecClass as String] = kSecClassGenericPassword
|
query[kSecClass as String] = kSecClassGenericPassword
|
||||||
query[kSecAttrAccount as String] = username
|
query[kSecAttrAccount as String] = username
|
||||||
|
|
||||||
let status = SecItemDelete(query as CFDictionary)
|
let status = SecItemDelete(query as CFDictionary)
|
||||||
return (status == errSecSuccess)
|
return status == errSecSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Gets a password.
|
Gets a password.
|
||||||
|
|
||||||
- Parameter username: The username to get the password for.
|
- Parameter username: The username to get the password for.
|
||||||
|
- Parameter context: An optional context.
|
||||||
- Returns: The password for the input username.
|
- Returns: The password for the input username.
|
||||||
- Throws: `KeychainError.notFound` if unable to find the password in the keychain.
|
- Throws: `KeychainError.notFound` if unable to find the password in the keychain.
|
||||||
**/
|
**/
|
||||||
public func password(for username: String) throws -> String {
|
public func password(for username: String, context: String? = nil) throws -> String {
|
||||||
var query = [String: Any]()
|
var query = [String: Any]()
|
||||||
setScope(query: &query)
|
setScope(query: &query, context: context)
|
||||||
query[kSecClass as String] = kSecClassGenericPassword
|
query[kSecClass as String] = kSecClassGenericPassword
|
||||||
query[kSecAttrAccount as String] = username
|
query[kSecAttrAccount as String] = username
|
||||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||||
|
@ -185,12 +163,13 @@ public class Keychain {
|
||||||
Gets a password reference.
|
Gets a password reference.
|
||||||
|
|
||||||
- Parameter username: The username to get the password for.
|
- Parameter username: The username to get the password for.
|
||||||
|
- Parameter context: An optional context.
|
||||||
- Returns: The password reference for the input username.
|
- Returns: The password reference for the input username.
|
||||||
- Throws: `KeychainError.notFound` if unable to find the password in the keychain.
|
- Throws: `KeychainError.notFound` if unable to find the password in the keychain.
|
||||||
**/
|
**/
|
||||||
public func passwordReference(for username: String) throws -> Data {
|
public func passwordReference(for username: String, context: String? = nil) throws -> Data {
|
||||||
var query = [String: Any]()
|
var query = [String: Any]()
|
||||||
setScope(query: &query)
|
setScope(query: &query, context: context)
|
||||||
query[kSecClass as String] = kSecClassGenericPassword
|
query[kSecClass as String] = kSecClassGenericPassword
|
||||||
query[kSecAttrAccount as String] = username
|
query[kSecAttrAccount as String] = username
|
||||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||||
|
@ -218,12 +197,16 @@ public class Keychain {
|
||||||
|
|
||||||
- Parameter username: The username to get the password for.
|
- Parameter username: The username to get the password for.
|
||||||
- Parameter reference: The password reference.
|
- Parameter reference: The password reference.
|
||||||
|
- Parameter context: An optional context.
|
||||||
- Returns: The password for the input username and reference.
|
- Returns: The password for the input username and reference.
|
||||||
- Throws: `KeychainError.notFound` if unable to find the password in the keychain.
|
- Throws: `KeychainError.notFound` if unable to find the password in the keychain.
|
||||||
**/
|
**/
|
||||||
public static func password(for username: String, reference: Data) throws -> String {
|
public static func password(for username: String, reference: Data, context: String? = nil) throws -> String {
|
||||||
var query = [String: Any]()
|
var query = [String: Any]()
|
||||||
query[kSecClass as String] = kSecClassGenericPassword
|
query[kSecClass as String] = kSecClassGenericPassword
|
||||||
|
if let context = context {
|
||||||
|
query[kSecAttrService as String] = context
|
||||||
|
}
|
||||||
query[kSecAttrAccount as String] = username
|
query[kSecAttrAccount as String] = username
|
||||||
query[kSecMatchItemList as String] = [reference]
|
query[kSecMatchItemList as String] = [reference]
|
||||||
query[kSecReturnData as String] = true
|
query[kSecReturnData as String] = true
|
||||||
|
@ -331,18 +314,17 @@ public class Keychain {
|
||||||
query.removeValue(forKey: kSecAttrService as String)
|
query.removeValue(forKey: kSecAttrService as String)
|
||||||
|
|
||||||
let status = SecItemDelete(query as CFDictionary)
|
let status = SecItemDelete(query as CFDictionary)
|
||||||
return (status == errSecSuccess)
|
return status == errSecSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Helpers
|
// MARK: Helpers
|
||||||
|
|
||||||
private func setScope(query: inout [String: Any]) {
|
private func setScope(query: inout [String: Any], context: String?) {
|
||||||
if let service = service {
|
if let accessGroup = accessGroup {
|
||||||
query[kSecAttrService as String] = service
|
|
||||||
} else if let accessGroup = accessGroup {
|
|
||||||
query[kSecAttrAccessGroup as String] = accessGroup
|
query[kSecAttrAccessGroup as String] = accessGroup
|
||||||
} else {
|
}
|
||||||
fatalError("No service nor accessGroup set")
|
if let context = context {
|
||||||
|
query[kSecAttrService as String] = context
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -403,12 +403,12 @@ extension OpenVPNTunnelProvider {
|
||||||
if let username = credentials?.username, let password = credentials?.password {
|
if let username = credentials?.username, let password = credentials?.password {
|
||||||
let keychain = Keychain(group: appGroup)
|
let keychain = Keychain(group: appGroup)
|
||||||
do {
|
do {
|
||||||
try keychain.set(password: password, for: username, label: Bundle.main.bundleIdentifier)
|
try keychain.set(password: password, for: username, context: bundleIdentifier)
|
||||||
} catch _ {
|
} catch _ {
|
||||||
throw ProviderConfigurationError.credentials(details: "keychain.set()")
|
throw ProviderConfigurationError.credentials(details: "keychain.set()")
|
||||||
}
|
}
|
||||||
protocolConfiguration.username = username
|
protocolConfiguration.username = username
|
||||||
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username)
|
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username, context: bundleIdentifier)
|
||||||
}
|
}
|
||||||
protocolConfiguration.providerConfiguration = generatedProviderConfiguration(appGroup: appGroup)
|
protocolConfiguration.providerConfiguration = generatedProviderConfiguration(appGroup: appGroup)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue