Return password reference at the time of setting

Simplifies app/extension IPC.
This commit is contained in:
Davide De Rosa 2021-11-23 00:24:01 +01:00
parent 8e6624e113
commit bb5cd1e1ab
6 changed files with 27 additions and 36 deletions

View File

@ -98,12 +98,11 @@ class ViewController: UIViewController, URLSessionDataDelegate {
let credentials = OpenVPN.Credentials(textUsername.text!, textPassword.text!) let credentials = OpenVPN.Credentials(textUsername.text!, textPassword.text!)
let cfg = Configuration.make(hostname: hostname, port: port, socketType: socketType) let cfg = Configuration.make(hostname: hostname, port: port, socketType: socketType)
try? keychain.set(password: credentials.password, for: credentials.username, context: tunnelIdentifier)
let proto = try! cfg.generatedTunnelProtocol( let proto = try! cfg.generatedTunnelProtocol(
withBundleIdentifier: tunnelIdentifier, withBundleIdentifier: tunnelIdentifier,
appGroup: appGroup, appGroup: appGroup,
context: tunnelIdentifier, context: tunnelIdentifier,
username: credentials.username credentials: credentials
) )
let neCfg = NetworkExtensionVPNConfiguration(title: "BasicTunnel", protocolConfiguration: proto, onDemandRules: []) let neCfg = NetworkExtensionVPNConfiguration(title: "BasicTunnel", protocolConfiguration: proto, onDemandRules: [])
vpn.reconnect(configuration: neCfg) { (error) in vpn.reconnect(configuration: neCfg) { (error) in
@ -147,15 +146,11 @@ class ViewController: UIViewController, URLSessionDataDelegate {
let username = "foo" let username = "foo"
let password = "bar" let password = "bar"
guard let _ = try? keychain.set(password: password, for: username, context: tunnelIdentifier) else { guard let ref = try? keychain.set(password: password, for: username, context: tunnelIdentifier) else {
print("Couldn't set password") print("Couldn't set password")
return return
} }
guard let passwordReference = try? keychain.passwordReference(for: username, context: tunnelIdentifier) else { guard let fetchedPassword = try? Keychain.password(forReference: ref) else {
print("Couldn't get password reference")
return
}
guard let fetchedPassword = try? keychain.password(for: username, reference: passwordReference, context: tunnelIdentifier) else {
print("Couldn't fetch password") print("Couldn't fetch password")
return return
} }

View File

@ -89,12 +89,11 @@ class ViewController: NSViewController {
let credentials = OpenVPN.Credentials(textUsername.stringValue, textPassword.stringValue) let credentials = OpenVPN.Credentials(textUsername.stringValue, textPassword.stringValue)
let cfg = Configuration.make(hostname: hostname, port: port, socketType: .udp) let cfg = Configuration.make(hostname: hostname, port: port, socketType: .udp)
try? keychain.set(password: credentials.password, for: credentials.username, context: tunnelIdentifier)
let proto = try! cfg.generatedTunnelProtocol( let proto = try! cfg.generatedTunnelProtocol(
withBundleIdentifier: tunnelIdentifier, withBundleIdentifier: tunnelIdentifier,
appGroup: appGroup, appGroup: appGroup,
context: tunnelIdentifier, context: tunnelIdentifier,
username: credentials.username credentials: credentials
) )
let neCfg = NetworkExtensionVPNConfiguration(title: "BasicTunnel", protocolConfiguration: proto, onDemandRules: []) let neCfg = NetworkExtensionVPNConfiguration(title: "BasicTunnel", protocolConfiguration: proto, onDemandRules: [])
vpn.reconnect(configuration: neCfg) { (error) in vpn.reconnect(configuration: neCfg) { (error) in
@ -132,15 +131,11 @@ class ViewController: NSViewController {
let username = "foo" let username = "foo"
let password = "bar" let password = "bar"
guard let _ = try? keychain.set(password: password, for: username, context: tunnelIdentifier) else { guard let ref = try? keychain.set(password: password, for: username, context: tunnelIdentifier) else {
print("Couldn't set password") print("Couldn't set password")
return return
} }
guard let passwordReference = try? keychain.passwordReference(for: username, context: tunnelIdentifier) else { guard let fetchedPassword = try? Keychain.password(forReference: ref) else {
print("Couldn't get password reference")
return
}
guard let fetchedPassword = try? keychain.password(for: username, reference: passwordReference, context: tunnelIdentifier) else {
print("Couldn't fetch password") print("Couldn't fetch password")
return return
} }

View File

@ -74,13 +74,15 @@ 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 context: An optional context. - Parameter context: An optional context.
- Returns: The reference to the password.
- 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, context: String? = nil) throws { @discardableResult
public func set(password: String, for username: String, context: String? = nil) throws -> Data {
do { do {
let currentPassword = try self.password(for: username, context: context) let currentPassword = try self.password(for: username, context: context)
guard password != currentPassword else { guard password != currentPassword else {
return return try passwordReference(for: username, context: context)
} }
removePassword(for: username, context: context) removePassword(for: username, context: context)
} catch let e as KeychainError { } catch let e as KeychainError {
@ -99,11 +101,14 @@ public class Keychain {
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)
query[kSecReturnPersistentRef as String] = true
let status = SecItemAdd(query as CFDictionary, nil) var ref: CFTypeRef?
guard status == errSecSuccess else { let status = SecItemAdd(query as CFDictionary, &ref)
guard status == errSecSuccess, let refData = ref as? Data else {
throw KeychainError.add throw KeychainError.add
} }
return refData
} }
/** /**
@ -195,18 +200,13 @@ public class Keychain {
/** /**
Gets a password associated with a password reference. Gets a password associated with a password reference.
- 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 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 func password(for username: String, reference: Data, context: String? = nil) throws -> String { public static func password(forReference reference: Data) throws -> String {
var query = [String: Any]() var query = [String: Any]()
setScope(query: &query, context: context) query[kSecValuePersistentRef as String] = reference
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
query[kSecMatchItemList as String] = [reference]
query[kSecReturnData as String] = true query[kSecReturnData as String] = true
var result: AnyObject? var result: AnyObject?

View File

@ -217,9 +217,8 @@ open class OpenVPNTunnelProvider: NEPacketTunnelProvider {
// optional credentials // optional credentials
let credentials: OpenVPN.Credentials? let credentials: OpenVPN.Credentials?
if let username = protocolConfiguration.username, let passwordReference = protocolConfiguration.passwordReference { if let username = protocolConfiguration.username, let passwordReference = protocolConfiguration.passwordReference {
let keychain = Keychain(group: appGroup) guard let password = try? Keychain.password(forReference: passwordReference) else {
guard let password = try? keychain.password(for: username, reference: passwordReference) else { completionHandler(OpenVPNProviderConfigurationError.credentials(details: "Keychain.password(forReference:)"))
completionHandler(OpenVPNProviderConfigurationError.credentials(details: "keychain.password(for:, reference:)"))
return return
} }
credentials = OpenVPN.Credentials(username, password) credentials = OpenVPN.Credentials(username, password)

View File

@ -276,7 +276,7 @@ extension OpenVPNProvider {
- Parameter bundleIdentifier: The provider bundle identifier required to locate the tunnel extension. - Parameter bundleIdentifier: The provider bundle identifier required to locate the tunnel extension.
- Parameter appGroup: The name of the app group in which the tunnel extension lives in. - Parameter appGroup: The name of the app group in which the tunnel extension lives in.
- Parameter context: The keychain context where to look for the password reference. - Parameter context: The keychain context where to look for the password reference.
- Parameter username: The username to authenticate with. - Parameter credentials: The credentials to authenticate with.
- Returns: The generated `NETunnelProviderProtocol` object. - Returns: The generated `NETunnelProviderProtocol` object.
- Throws: `OpenVPNProviderError.credentials` if unable to store `credentials.password` to the `appGroup` keychain. - Throws: `OpenVPNProviderError.credentials` if unable to store `credentials.password` to the `appGroup` keychain.
*/ */
@ -284,16 +284,18 @@ extension OpenVPNProvider {
withBundleIdentifier bundleIdentifier: String, withBundleIdentifier bundleIdentifier: String,
appGroup: String, appGroup: String,
context: String, context: String,
username: String?) throws -> NETunnelProviderProtocol { credentials: OpenVPN.Credentials?) throws -> NETunnelProviderProtocol {
let protocolConfiguration = NETunnelProviderProtocol() let protocolConfiguration = NETunnelProviderProtocol()
let keychain = Keychain(group: appGroup) let keychain = Keychain(group: appGroup)
protocolConfiguration.providerBundleIdentifier = bundleIdentifier protocolConfiguration.providerBundleIdentifier = bundleIdentifier
protocolConfiguration.serverAddress = sessionConfiguration.hostname ?? resolvedAddresses?.first protocolConfiguration.serverAddress = sessionConfiguration.hostname ?? resolvedAddresses?.first
if let username = username { if let username = credentials?.username {
protocolConfiguration.username = username protocolConfiguration.username = username
protocolConfiguration.passwordReference = try? keychain.passwordReference(for: username, context: context) if let password = credentials?.password {
protocolConfiguration.passwordReference = try? keychain.set(password: password, for: username, context: context)
}
} }
protocolConfiguration.providerConfiguration = generatedProviderConfiguration(appGroup: appGroup) protocolConfiguration.providerConfiguration = generatedProviderConfiguration(appGroup: appGroup)

View File

@ -81,7 +81,7 @@ class AppExtensionTests: XCTestCase {
withBundleIdentifier: identifier, withBundleIdentifier: identifier,
appGroup: appGroup, appGroup: appGroup,
context: context, context: context,
username: credentials.username credentials: credentials
) )
XCTAssertNotNil(proto) XCTAssertNotNil(proto)