Updates KeychainAccess pod

This commit is contained in:
Riley Testut
2020-10-01 13:34:06 -07:00
parent 546db3fa23
commit 668ca66a04
7 changed files with 351 additions and 177 deletions

View File

@@ -28,7 +28,7 @@ target 'AltStoreCore' do
use_frameworks! use_frameworks!
# Pods for AltServer # Pods for AltServer
pod 'KeychainAccess', '~> 3.2.0' pod 'KeychainAccess', '~> 4.2.0'
end end

View File

@@ -7,14 +7,14 @@ PODS:
- AppCenter/Core (3.1.0) - AppCenter/Core (3.1.0)
- AppCenter/Crashes (3.1.0): - AppCenter/Crashes (3.1.0):
- AppCenter/Core - AppCenter/Core
- KeychainAccess (3.2.0) - KeychainAccess (4.2.1)
- Nuke (7.6.3) - Nuke (7.6.3)
- Sparkle (1.21.3) - Sparkle (1.21.3)
- STPrivilegedTask (1.0.7) - STPrivilegedTask (1.0.7)
DEPENDENCIES: DEPENDENCIES:
- AppCenter (~> 3.1.0) - AppCenter (~> 3.1.0)
- KeychainAccess (~> 3.2.0) - KeychainAccess (~> 4.2.0)
- Nuke (~> 7.0) - Nuke (~> 7.0)
- Sparkle - Sparkle
- STPrivilegedTask (from `https://github.com/rileytestut/STPrivilegedTask.git`) - STPrivilegedTask (from `https://github.com/rileytestut/STPrivilegedTask.git`)
@@ -37,11 +37,11 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS: SPEC CHECKSUMS:
AppCenter: a1c30c47b7882a04a615ffa5ab26c007326436d8 AppCenter: a1c30c47b7882a04a615ffa5ab26c007326436d8
KeychainAccess: 3b1bf8a77eb4c6ea1ce9404c292e48f948954c6b KeychainAccess: 9b07f665298d13c3a85881bd3171f6f49b8151c1
Nuke: 44130e95e09463f8773ae4b96b90de1eba6b3350 Nuke: 44130e95e09463f8773ae4b96b90de1eba6b3350
Sparkle: 3f75576db8b0265adef36c43249d747f22d0b708 Sparkle: 3f75576db8b0265adef36c43249d747f22d0b708
STPrivilegedTask: 56c3397238a1ec07720fb877a044898373cd2c68 STPrivilegedTask: 56c3397238a1ec07720fb877a044898373cd2c68
PODFILE CHECKSUM: b687e2aeec1ffc369787e92f2a2fefa460687731 PODFILE CHECKSUM: 6e3f9d2fc666262d43ff8079a3f9149b8f3376ee
COCOAPODS: 1.8.4 COCOAPODS: 1.8.4

View File

@@ -106,6 +106,7 @@ public enum Accessibility {
for anything except system use. Items with this attribute will migrate for anything except system use. Items with this attribute will migrate
to a new device when using encrypted backups. to a new device when using encrypted backups.
*/ */
@available(macCatalyst, unavailable)
case always case always
/** /**
@@ -148,79 +149,145 @@ public enum Accessibility {
attribute will never migrate to a new device, so after a backup is attribute will never migrate to a new device, so after a backup is
restored to a new device, these items will be missing. restored to a new device, these items will be missing.
*/ */
@available(macCatalyst, unavailable)
case alwaysThisDeviceOnly case alwaysThisDeviceOnly
} }
/**
Predefined item attribute constants used to get or set values
in a dictionary. The kSecUseAuthenticationUI constant is the key and its
value is one of the constants defined here.
If the key kSecUseAuthenticationUI not provided then kSecUseAuthenticationUIAllow
is used as default.
*/
public enum AuthenticationUI {
/**
Specifies that authenticate UI can appear.
*/
case allow
/**
Specifies that the error
errSecInteractionNotAllowed will be returned if an item needs
to authenticate with UI
*/
case fail
/**
Specifies that all items which need
to authenticate with UI will be silently skipped. This value can be used
only with SecItemCopyMatching.
*/
case skip
}
@available(iOS 9.0, OSX 10.11, *)
extension AuthenticationUI {
public var rawValue: String {
switch self {
case .allow:
return UseAuthenticationUIAllow
case .fail:
return UseAuthenticationUIFail
case .skip:
return UseAuthenticationUISkip
}
}
public var description: String {
switch self {
case .allow:
return "allow"
case .fail:
return "fail"
case .skip:
return "skip"
}
}
}
public struct AuthenticationPolicy: OptionSet { public struct AuthenticationPolicy: OptionSet {
/** /**
User presence policy using Touch ID or Passcode. Touch ID does not User presence policy using Touch ID or Passcode. Touch ID does not
have to be available or enrolled. Item is still accessible by Touch ID have to be available or enrolled. Item is still accessible by Touch ID
even if fingers are added or removed. even if fingers are added or removed.
*/ */
@available(iOS 8.0, OSX 10.10, *) @available(iOS 8.0, OSX 10.10, watchOS 2.0, tvOS 8.0, *)
@available(watchOS, unavailable)
public static let userPresence = AuthenticationPolicy(rawValue: 1 << 0) public static let userPresence = AuthenticationPolicy(rawValue: 1 << 0)
/** /**
Constraint: Touch ID (any finger). Touch ID must be available and Constraint: Touch ID (any finger) or Face ID. Touch ID or Face ID must be available. With Touch ID
at least one finger must be enrolled. Item is still accessible by at least one finger must be enrolled. With Face ID user has to be enrolled. Item is still accessible by Touch ID even
Touch ID even if fingers are added or removed. if fingers are added or removed. Item is still accessible by Face ID if user is re-enrolled.
*/ */
@available(iOS 9.0, *) @available(iOS 11.3, OSX 10.13.4, watchOS 4.3, tvOS 11.3, *)
@available(OSX, unavailable) public static let biometryAny = AuthenticationPolicy(rawValue: 1 << 1)
@available(watchOS, unavailable)
/**
Deprecated, please use biometryAny instead.
*/
@available(iOS, introduced: 9.0, deprecated: 11.3, renamed: "biometryAny")
@available(OSX, introduced: 10.12.1, deprecated: 10.13.4, renamed: "biometryAny")
@available(watchOS, introduced: 2.0, deprecated: 4.3, renamed: "biometryAny")
@available(tvOS, introduced: 9.0, deprecated: 11.3, renamed: "biometryAny")
public static let touchIDAny = AuthenticationPolicy(rawValue: 1 << 1) public static let touchIDAny = AuthenticationPolicy(rawValue: 1 << 1)
/** /**
Constraint: Touch ID from the set of currently enrolled fingers. Constraint: Touch ID from the set of currently enrolled fingers. Touch ID must be available and at least one finger must
Touch ID must be available and at least one finger must be enrolled. be enrolled. When fingers are added or removed, the item is invalidated. When Face ID is re-enrolled this item is invalidated.
When fingers are added or removed, the item is invalidated.
*/ */
@available(iOS 9.0, *) @available(iOS 11.3, OSX 10.13, watchOS 4.3, tvOS 11.3, *)
@available(OSX, unavailable) public static let biometryCurrentSet = AuthenticationPolicy(rawValue: 1 << 3)
@available(watchOS, unavailable)
/**
Deprecated, please use biometryCurrentSet instead.
*/
@available(iOS, introduced: 9.0, deprecated: 11.3, renamed: "biometryCurrentSet")
@available(OSX, introduced: 10.12.1, deprecated: 10.13.4, renamed: "biometryCurrentSet")
@available(watchOS, introduced: 2.0, deprecated: 4.3, renamed: "biometryCurrentSet")
@available(tvOS, introduced: 9.0, deprecated: 11.3, renamed: "biometryCurrentSet")
public static let touchIDCurrentSet = AuthenticationPolicy(rawValue: 1 << 3) public static let touchIDCurrentSet = AuthenticationPolicy(rawValue: 1 << 3)
/** /**
Constraint: Device passcode Constraint: Device passcode
*/ */
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
@available(watchOS, unavailable)
public static let devicePasscode = AuthenticationPolicy(rawValue: 1 << 4) public static let devicePasscode = AuthenticationPolicy(rawValue: 1 << 4)
/**
Constraint: Watch
*/
@available(iOS, unavailable)
@available(OSX 10.15, *)
@available(watchOS, unavailable)
@available(tvOS, unavailable)
public static let watch = AuthenticationPolicy(rawValue: 1 << 5)
/** /**
Constraint logic operation: when using more than one constraint, Constraint logic operation: when using more than one constraint,
at least one of them must be satisfied. at least one of them must be satisfied.
*/ */
@available(iOS 9.0, *) @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
@available(OSX, unavailable)
@available(watchOS, unavailable)
public static let or = AuthenticationPolicy(rawValue: 1 << 14) public static let or = AuthenticationPolicy(rawValue: 1 << 14)
/** /**
Constraint logic operation: when using more than one constraint, Constraint logic operation: when using more than one constraint,
all must be satisfied. all must be satisfied.
*/ */
@available(iOS 9.0, *) @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
@available(OSX, unavailable)
@available(watchOS, unavailable)
public static let and = AuthenticationPolicy(rawValue: 1 << 15) public static let and = AuthenticationPolicy(rawValue: 1 << 15)
/** /**
Create access control for private key operations (i.e. sign operation) Create access control for private key operations (i.e. sign operation)
*/ */
@available(iOS 9.0, *) @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
@available(OSX, unavailable)
@available(watchOS, unavailable)
public static let privateKeyUsage = AuthenticationPolicy(rawValue: 1 << 30) public static let privateKeyUsage = AuthenticationPolicy(rawValue: 1 << 30)
/** /**
Security: Application provided password for data encryption key generation. Security: Application provided password for data encryption key generation.
This is not a constraint but additional item encryption mechanism. This is not a constraint but additional item encryption mechanism.
*/ */
@available(iOS 9.0, *) @available(iOS 9.0, OSX 10.12.1, watchOS 2.0, tvOS 9.0, *)
@available(OSX, unavailable)
@available(watchOS, unavailable)
public static let applicationPassword = AuthenticationPolicy(rawValue: 1 << 31) public static let applicationPassword = AuthenticationPolicy(rawValue: 1 << 31)
#if swift(>=2.3) #if swift(>=2.3)
@@ -348,6 +415,8 @@ public final class Keychain {
return options.service return options.service
} }
// This attribute (kSecAttrAccessGroup) applies to macOS keychain items only if you also set a value of true for the
// kSecUseDataProtectionKeychain key, the kSecAttrSynchronizable key, or both.
public var accessGroup: String? { public var accessGroup: String? {
return options.accessGroup return options.accessGroup
} }
@@ -392,6 +461,11 @@ public final class Keychain {
return options.authenticationPrompt return options.authenticationPrompt
} }
@available(iOS 9.0, OSX 10.11, *)
public var authenticationUI: AuthenticationUI {
return options.authenticationUI ?? .allow
}
#if os(iOS) || os(OSX) #if os(iOS) || os(OSX)
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, *)
public var authenticationContext: LAContext? { public var authenticationContext: LAContext? {
@@ -433,15 +507,16 @@ public final class Keychain {
self.init(options) self.init(options)
} }
public convenience init(server: String, protocolType: ProtocolType, authenticationType: AuthenticationType = .default) { public convenience init(server: String, protocolType: ProtocolType, accessGroup: String? = nil, authenticationType: AuthenticationType = .default) {
self.init(server: URL(string: server)!, protocolType: protocolType, authenticationType: authenticationType) self.init(server: URL(string: server)!, protocolType: protocolType, accessGroup: accessGroup, authenticationType: authenticationType)
} }
public convenience init(server: URL, protocolType: ProtocolType, authenticationType: AuthenticationType = .default) { public convenience init(server: URL, protocolType: ProtocolType, accessGroup: String? = nil, authenticationType: AuthenticationType = .default) {
var options = Options() var options = Options()
options.itemClass = .internetPassword options.itemClass = .internetPassword
options.server = server options.server = server
options.protocolType = protocolType options.protocolType = protocolType
options.accessGroup = accessGroup
options.authenticationType = authenticationType options.authenticationType = authenticationType
self.init(options) self.init(options)
} }
@@ -499,6 +574,13 @@ public final class Keychain {
return Keychain(options) return Keychain(options)
} }
@available(iOS 9.0, OSX 10.11, *)
public func authenticationUI(_ authenticationUI: AuthenticationUI) -> Keychain {
var options = self.options
options.authenticationUI = authenticationUI
return Keychain(options)
}
#if os(iOS) || os(OSX) #if os(iOS) || os(OSX)
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, *)
public func authenticationContext(_ authenticationContext: LAContext) -> Keychain { public func authenticationContext(_ authenticationContext: LAContext) -> Keychain {
@@ -510,12 +592,12 @@ public final class Keychain {
// MARK: // MARK:
public func get(_ key: String) throws -> String? { public func get(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> String? {
return try getString(key) return try getString(key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
} }
public func getString(_ key: String) throws -> String? { public func getString(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> String? {
guard let data = try getData(key) else { guard let data = try getData(key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable) else {
return nil return nil
} }
guard let string = String(data: data, encoding: .utf8) else { guard let string = String(data: data, encoding: .utf8) else {
@@ -525,8 +607,8 @@ public final class Keychain {
return string return string
} }
public func getData(_ key: String) throws -> Data? { public func getData(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws -> Data? {
var query = options.query() var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
query[MatchLimit] = MatchLimitOne query[MatchLimit] = MatchLimitOne
query[ReturnData] = kCFBooleanTrue query[ReturnData] = kCFBooleanTrue
@@ -549,8 +631,8 @@ public final class Keychain {
} }
} }
public func get<T>(_ key: String, handler: (Attributes?) -> T) throws -> T { public func get<T>(_ key: String, ignoringAttributeSynchronizable: Bool = true, handler: (Attributes?) -> T) throws -> T {
var query = options.query() var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
query[MatchLimit] = MatchLimitOne query[MatchLimit] = MatchLimitOne
@@ -579,28 +661,40 @@ public final class Keychain {
// MARK: // MARK:
public func set(_ value: String, key: String) throws { public func set(_ value: String, key: String, ignoringAttributeSynchronizable: Bool = true) throws {
guard let data = value.data(using: .utf8, allowLossyConversion: false) else { guard let data = value.data(using: .utf8, allowLossyConversion: false) else {
print("failed to convert string to data") print("failed to convert string to data")
throw Status.conversionError throw Status.conversionError
} }
try set(data, key: key) try set(data, key: key, ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
} }
public func set(_ value: Data, key: String) throws { public func set(_ value: Data, key: String, ignoringAttributeSynchronizable: Bool = true) throws {
var query = options.query() var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
query[AttributeAccount] = key query[AttributeAccount] = key
#if os(iOS) #if os(iOS)
if #available(iOS 9.0, *) { if #available(iOS 9.0, *) {
if let authenticationUI = options.authenticationUI {
query[UseAuthenticationUI] = authenticationUI.rawValue
} else {
query[UseAuthenticationUI] = UseAuthenticationUIFail query[UseAuthenticationUI] = UseAuthenticationUIFail
}
} else { } else {
query[UseNoAuthenticationUI] = kCFBooleanTrue query[UseNoAuthenticationUI] = kCFBooleanTrue
} }
#elseif os(OSX) #elseif os(OSX)
query[ReturnData] = kCFBooleanTrue query[ReturnData] = kCFBooleanTrue
if #available(OSX 10.11, *) { if #available(OSX 10.11, *) {
if let authenticationUI = options.authenticationUI {
query[UseAuthenticationUI] = authenticationUI.rawValue
} else {
query[UseAuthenticationUI] = UseAuthenticationUIFail query[UseAuthenticationUI] = UseAuthenticationUIFail
} }
}
#else
if let authenticationUI = options.authenticationUI {
query[UseAuthenticationUI] = authenticationUI.rawValue
}
#endif #endif
var status = SecItemCopyMatching(query as CFDictionary, nil) var status = SecItemCopyMatching(query as CFDictionary, nil)
@@ -717,8 +811,8 @@ public final class Keychain {
// MARK: // MARK:
public func remove(_ key: String) throws { public func remove(_ key: String, ignoringAttributeSynchronizable: Bool = true) throws {
var query = options.query() var query = options.query(ignoringAttributeSynchronizable: ignoringAttributeSynchronizable)
query[AttributeAccount] = key query[AttributeAccount] = key
let status = SecItemDelete(query as CFDictionary) let status = SecItemDelete(query as CFDictionary)
@@ -741,14 +835,49 @@ public final class Keychain {
// MARK: // MARK:
public func contains(_ key: String) throws -> Bool { public func contains(_ key: String, withoutAuthenticationUI: Bool = false) throws -> Bool {
var query = options.query() var query = options.query()
query[AttributeAccount] = key query[AttributeAccount] = key
if withoutAuthenticationUI {
#if os(iOS) || os(watchOS) || os(tvOS)
if #available(iOS 9.0, *) {
if let authenticationUI = options.authenticationUI {
query[UseAuthenticationUI] = authenticationUI.rawValue
} else {
query[UseAuthenticationUI] = UseAuthenticationUIFail
}
} else {
query[UseNoAuthenticationUI] = kCFBooleanTrue
}
#else
if #available(OSX 10.11, *) {
if let authenticationUI = options.authenticationUI {
query[UseAuthenticationUI] = authenticationUI.rawValue
} else {
query[UseAuthenticationUI] = UseAuthenticationUIFail
}
} else if #available(OSX 10.10, *) {
query[UseNoAuthenticationUI] = kCFBooleanTrue
}
#endif
} else {
if #available(iOS 9.0, OSX 10.11, *) {
if let authenticationUI = options.authenticationUI {
query[UseAuthenticationUI] = authenticationUI.rawValue
}
}
}
let status = SecItemCopyMatching(query as CFDictionary, nil) let status = SecItemCopyMatching(query as CFDictionary, nil)
switch status { switch status {
case errSecSuccess: case errSecSuccess:
return true return true
case errSecInteractionNotAllowed:
if withoutAuthenticationUI {
return true
}
return false
case errSecItemNotFound: case errSecItemNotFound:
return false return false
default: default:
@@ -830,7 +959,7 @@ public final class Keychain {
return type(of: self).prettify(itemClass: itemClass, items: items()) return type(of: self).prettify(itemClass: itemClass, items: items())
} }
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public func getSharedPassword(_ completion: @escaping (_ account: String?, _ password: String?, _ error: Error?) -> () = { account, password, error -> () in }) { public func getSharedPassword(_ completion: @escaping (_ account: String?, _ password: String?, _ error: Error?) -> () = { account, password, error -> () in }) {
if let domain = server.host { if let domain = server.host {
@@ -850,7 +979,7 @@ public final class Keychain {
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public func getSharedPassword(_ account: String, completion: @escaping (_ password: String?, _ error: Error?) -> () = { password, error -> () in }) { public func getSharedPassword(_ account: String, completion: @escaping (_ password: String?, _ error: Error?) -> () = { password, error -> () in }) {
if let domain = server.host { if let domain = server.host {
@@ -872,14 +1001,14 @@ public final class Keychain {
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public func setSharedPassword(_ password: String, account: String, completion: @escaping (_ error: Error?) -> () = { e -> () in }) { public func setSharedPassword(_ password: String, account: String, completion: @escaping (_ error: Error?) -> () = { e -> () in }) {
setSharedPassword(password as String?, account: account, completion: completion) setSharedPassword(password as String?, account: account, completion: completion)
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
fileprivate func setSharedPassword(_ password: String?, account: String, completion: @escaping (_ error: Error?) -> () = { e -> () in }) { fileprivate func setSharedPassword(_ password: String?, account: String, completion: @escaping (_ error: Error?) -> () = { e -> () in }) {
if let domain = server.host { if let domain = server.host {
@@ -897,35 +1026,35 @@ public final class Keychain {
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public func removeSharedPassword(_ account: String, completion: @escaping (_ error: Error?) -> () = { e -> () in }) { public func removeSharedPassword(_ account: String, completion: @escaping (_ error: Error?) -> () = { e -> () in }) {
setSharedPassword(nil, account: account, completion: completion) setSharedPassword(nil, account: account, completion: completion)
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public class func requestSharedWebCredential(_ completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> () = { credentials, error -> () in }) { public class func requestSharedWebCredential(_ completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> () = { credentials, error -> () in }) {
requestSharedWebCredential(domain: nil, account: nil, completion: completion) requestSharedWebCredential(domain: nil, account: nil, completion: completion)
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public class func requestSharedWebCredential(domain: String, completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> () = { credentials, error -> () in }) { public class func requestSharedWebCredential(domain: String, completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> () = { credentials, error -> () in }) {
requestSharedWebCredential(domain: domain, account: nil, completion: completion) requestSharedWebCredential(domain: domain, account: nil, completion: completion)
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
public class func requestSharedWebCredential(domain: String, account: String, completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> () = { credentials, error -> () in }) { public class func requestSharedWebCredential(domain: String, account: String, completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> () = { credentials, error -> () in }) {
requestSharedWebCredential(domain: Optional(domain), account: Optional(account)!, completion: completion) requestSharedWebCredential(domain: Optional(domain), account: Optional(account)!, completion: completion)
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
@available(iOS 8.0, *) @available(iOS 8.0, *)
fileprivate class func requestSharedWebCredential(domain: String?, account: String?, completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> ()) { fileprivate class func requestSharedWebCredential(domain: String?, account: String?, completion: @escaping (_ credentials: [[String: String]], _ error: Error?) -> ()) {
SecRequestSharedWebCredential(domain as CFString?, account as CFString?) { (credentials, error) -> () in SecRequestSharedWebCredential(domain as CFString?, account as CFString?) { (credentials, error) -> () in
@@ -960,7 +1089,7 @@ public final class Keychain {
} }
#endif #endif
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
/** /**
@abstract Returns a randomly generated password. @abstract Returns a randomly generated password.
@return String password in the form xxx-xxx-xxx-xxx where x is taken from the sets "abcdefghkmnopqrstuvwxy", "ABCDEFGHJKLMNPQRSTUVWXYZ", "3456789" with at least one character from each set being present. @return String password in the form xxx-xxx-xxx-xxx where x is taken from the sets "abcdefghkmnopqrstuvwxy", "ABCDEFGHJKLMNPQRSTUVWXYZ", "3456789" with at least one character from each set being present.
@@ -1004,14 +1133,15 @@ public final class Keychain {
item["class"] = itemClass.description item["class"] = itemClass.description
if let accessGroup = attributes[AttributeAccessGroup] as? String {
item["accessGroup"] = accessGroup
}
switch itemClass { switch itemClass {
case .genericPassword: case .genericPassword:
if let service = attributes[AttributeService] as? String { if let service = attributes[AttributeService] as? String {
item["service"] = service item["service"] = service
} }
if let accessGroup = attributes[AttributeAccessGroup] as? String {
item["accessGroup"] = accessGroup
}
case .internetPassword: case .internetPassword:
if let server = attributes[AttributeServer] as? String { if let server = attributes[AttributeServer] as? String {
item["server"] = server item["server"] = server
@@ -1058,7 +1188,9 @@ public final class Keychain {
@discardableResult @discardableResult
fileprivate class func securityError(status: OSStatus) -> Error { fileprivate class func securityError(status: OSStatus) -> Error {
let error = Status(status: status) let error = Status(status: status)
if error != .userCanceled {
print("OSStatus error:[\(error.errorCode)] \(error.description)") print("OSStatus error:[\(error.errorCode)] \(error.description)")
}
return error return error
} }
@@ -1088,6 +1220,7 @@ struct Options {
var comment: String? var comment: String?
var authenticationPrompt: String? var authenticationPrompt: String?
var authenticationUI: AuthenticationUI?
var authenticationContext: AnyObject? var authenticationContext: AnyObject?
var attributes = [String: Any]() var attributes = [String: Any]()
@@ -1142,35 +1275,31 @@ private let ValueRef = String(kSecValueRef)
private let ValuePersistentRef = String(kSecValuePersistentRef) private let ValuePersistentRef = String(kSecValuePersistentRef)
/** Other Constants */ /** Other Constants */
@available(iOS 8.0, OSX 10.10, *) @available(iOS 8.0, OSX 10.10, tvOS 8.0, *)
private let UseOperationPrompt = String(kSecUseOperationPrompt) private let UseOperationPrompt = String(kSecUseOperationPrompt)
#if os(iOS)
@available(iOS, introduced: 8.0, deprecated: 9.0, message: "Use a UseAuthenticationUI instead.") @available(iOS, introduced: 8.0, deprecated: 9.0, message: "Use a UseAuthenticationUI instead.")
@available(OSX, introduced: 10.10, deprecated: 10.11, message: "Use UseAuthenticationUI instead.")
@available(watchOS, introduced: 2.0, deprecated: 2.0, message: "Use UseAuthenticationUI instead.")
@available(tvOS, introduced: 8.0, deprecated: 9.0, message: "Use UseAuthenticationUI instead.")
private let UseNoAuthenticationUI = String(kSecUseNoAuthenticationUI) private let UseNoAuthenticationUI = String(kSecUseNoAuthenticationUI)
#endif
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
@available(watchOS, unavailable)
private let UseAuthenticationUI = String(kSecUseAuthenticationUI) private let UseAuthenticationUI = String(kSecUseAuthenticationUI)
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
@available(watchOS, unavailable)
private let UseAuthenticationContext = String(kSecUseAuthenticationContext) private let UseAuthenticationContext = String(kSecUseAuthenticationContext)
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
@available(watchOS, unavailable)
private let UseAuthenticationUIAllow = String(kSecUseAuthenticationUIAllow) private let UseAuthenticationUIAllow = String(kSecUseAuthenticationUIAllow)
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
@available(watchOS, unavailable)
private let UseAuthenticationUIFail = String(kSecUseAuthenticationUIFail) private let UseAuthenticationUIFail = String(kSecUseAuthenticationUIFail)
@available(iOS 9.0, OSX 10.11, *) @available(iOS 9.0, OSX 10.11, watchOS 2.0, tvOS 9.0, *)
@available(watchOS, unavailable)
private let UseAuthenticationUISkip = String(kSecUseAuthenticationUISkip) private let UseAuthenticationUISkip = String(kSecUseAuthenticationUISkip)
#if os(iOS) #if os(iOS) && !targetEnvironment(macCatalyst)
/** Credential Key Constants */ /** Credential Key Constants */
private let SharedPassword = String(kSecSharedPassword) private let SharedPassword = String(kSecSharedPassword)
#endif #endif
@@ -1196,22 +1325,22 @@ extension Keychain: CustomStringConvertible, CustomDebugStringConvertible {
} }
extension Options { extension Options {
func query(ignoringAttributeSynchronizable: Bool = true) -> [String: Any] {
func query() -> [String: Any] {
var query = [String: Any]() var query = [String: Any]()
query[Class] = itemClass.rawValue query[Class] = itemClass.rawValue
if let accessGroup = self.accessGroup {
query[AttributeAccessGroup] = accessGroup
}
if ignoringAttributeSynchronizable {
query[AttributeSynchronizable] = SynchronizableAny query[AttributeSynchronizable] = SynchronizableAny
} else {
query[AttributeSynchronizable] = synchronizable ? kCFBooleanTrue : kCFBooleanFalse
}
switch itemClass { switch itemClass {
case .genericPassword: case .genericPassword:
query[AttributeService] = service query[AttributeService] = service
// Access group is not supported on any simulators.
#if (!arch(i386) && !arch(x86_64)) || (!os(iOS) && !os(watchOS) && !os(tvOS))
if let accessGroup = self.accessGroup {
query[AttributeAccessGroup] = accessGroup
}
#endif
case .internetPassword: case .internetPassword:
query[AttributeServer] = server.host query[AttributeServer] = server.host
query[AttributePort] = server.port query[AttributePort] = server.port
@@ -1292,7 +1421,6 @@ extension Attributes: CustomStringConvertible, CustomDebugStringConvertible {
} }
extension ItemClass: RawRepresentable, CustomStringConvertible { extension ItemClass: RawRepresentable, CustomStringConvertible {
public init?(rawValue: String) { public init?(rawValue: String) {
switch rawValue { switch rawValue {
case String(kSecClassGenericPassword): case String(kSecClassGenericPassword):
@@ -1324,7 +1452,6 @@ extension ItemClass: RawRepresentable, CustomStringConvertible {
} }
extension ProtocolType: RawRepresentable, CustomStringConvertible { extension ProtocolType: RawRepresentable, CustomStringConvertible {
public init?(rawValue: String) { public init?(rawValue: String) {
switch rawValue { switch rawValue {
case String(kSecAttrProtocolFTP): case String(kSecAttrProtocolFTP):
@@ -1530,7 +1657,6 @@ extension ProtocolType: RawRepresentable, CustomStringConvertible {
} }
extension AuthenticationType: RawRepresentable, CustomStringConvertible { extension AuthenticationType: RawRepresentable, CustomStringConvertible {
public init?(rawValue: String) { public init?(rawValue: String) {
switch rawValue { switch rawValue {
case String(kSecAttrAuthenticationTypeNTLM): case String(kSecAttrAuthenticationTypeNTLM):
@@ -1598,7 +1724,6 @@ extension AuthenticationType: RawRepresentable, CustomStringConvertible {
} }
extension Accessibility: RawRepresentable, CustomStringConvertible { extension Accessibility: RawRepresentable, CustomStringConvertible {
public init?(rawValue: String) { public init?(rawValue: String) {
if #available(OSX 10.10, *) { if #available(OSX 10.10, *) {
switch rawValue { switch rawValue {
@@ -1606,16 +1731,20 @@ extension Accessibility: RawRepresentable, CustomStringConvertible {
self = .whenUnlocked self = .whenUnlocked
case String(kSecAttrAccessibleAfterFirstUnlock): case String(kSecAttrAccessibleAfterFirstUnlock):
self = .afterFirstUnlock self = .afterFirstUnlock
#if !targetEnvironment(macCatalyst)
case String(kSecAttrAccessibleAlways): case String(kSecAttrAccessibleAlways):
self = .always self = .always
#endif
case String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly): case String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly):
self = .whenPasscodeSetThisDeviceOnly self = .whenPasscodeSetThisDeviceOnly
case String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly): case String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly):
self = .whenUnlockedThisDeviceOnly self = .whenUnlockedThisDeviceOnly
case String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly): case String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly):
self = .afterFirstUnlockThisDeviceOnly self = .afterFirstUnlockThisDeviceOnly
#if !targetEnvironment(macCatalyst)
case String(kSecAttrAccessibleAlwaysThisDeviceOnly): case String(kSecAttrAccessibleAlwaysThisDeviceOnly):
self = .alwaysThisDeviceOnly self = .alwaysThisDeviceOnly
#endif
default: default:
return nil return nil
} }
@@ -1625,14 +1754,18 @@ extension Accessibility: RawRepresentable, CustomStringConvertible {
self = .whenUnlocked self = .whenUnlocked
case String(kSecAttrAccessibleAfterFirstUnlock): case String(kSecAttrAccessibleAfterFirstUnlock):
self = .afterFirstUnlock self = .afterFirstUnlock
#if !targetEnvironment(macCatalyst)
case String(kSecAttrAccessibleAlways): case String(kSecAttrAccessibleAlways):
self = .always self = .always
#endif
case String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly): case String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly):
self = .whenUnlockedThisDeviceOnly self = .whenUnlockedThisDeviceOnly
case String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly): case String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly):
self = .afterFirstUnlockThisDeviceOnly self = .afterFirstUnlockThisDeviceOnly
#if !targetEnvironment(macCatalyst)
case String(kSecAttrAccessibleAlwaysThisDeviceOnly): case String(kSecAttrAccessibleAlwaysThisDeviceOnly):
self = .alwaysThisDeviceOnly self = .alwaysThisDeviceOnly
#endif
default: default:
return nil return nil
} }
@@ -1645,8 +1778,10 @@ extension Accessibility: RawRepresentable, CustomStringConvertible {
return String(kSecAttrAccessibleWhenUnlocked) return String(kSecAttrAccessibleWhenUnlocked)
case .afterFirstUnlock: case .afterFirstUnlock:
return String(kSecAttrAccessibleAfterFirstUnlock) return String(kSecAttrAccessibleAfterFirstUnlock)
#if !targetEnvironment(macCatalyst)
case .always: case .always:
return String(kSecAttrAccessibleAlways) return String(kSecAttrAccessibleAlways)
#endif
case .whenPasscodeSetThisDeviceOnly: case .whenPasscodeSetThisDeviceOnly:
if #available(OSX 10.10, *) { if #available(OSX 10.10, *) {
return String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly) return String(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)
@@ -1657,8 +1792,10 @@ extension Accessibility: RawRepresentable, CustomStringConvertible {
return String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly) return String(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)
case .afterFirstUnlockThisDeviceOnly: case .afterFirstUnlockThisDeviceOnly:
return String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly) return String(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
#if !targetEnvironment(macCatalyst)
case .alwaysThisDeviceOnly: case .alwaysThisDeviceOnly:
return String(kSecAttrAccessibleAlwaysThisDeviceOnly) return String(kSecAttrAccessibleAlwaysThisDeviceOnly)
#endif
} }
} }
@@ -1668,16 +1805,20 @@ extension Accessibility: RawRepresentable, CustomStringConvertible {
return "WhenUnlocked" return "WhenUnlocked"
case .afterFirstUnlock: case .afterFirstUnlock:
return "AfterFirstUnlock" return "AfterFirstUnlock"
#if !targetEnvironment(macCatalyst)
case .always: case .always:
return "Always" return "Always"
#endif
case .whenPasscodeSetThisDeviceOnly: case .whenPasscodeSetThisDeviceOnly:
return "WhenPasscodeSetThisDeviceOnly" return "WhenPasscodeSetThisDeviceOnly"
case .whenUnlockedThisDeviceOnly: case .whenUnlockedThisDeviceOnly:
return "WhenUnlockedThisDeviceOnly" return "WhenUnlockedThisDeviceOnly"
case .afterFirstUnlockThisDeviceOnly: case .afterFirstUnlockThisDeviceOnly:
return "AfterFirstUnlockThisDeviceOnly" return "AfterFirstUnlockThisDeviceOnly"
#if !targetEnvironment(macCatalyst)
case .alwaysThisDeviceOnly: case .alwaysThisDeviceOnly:
return "AlwaysThisDeviceOnly" return "AlwaysThisDeviceOnly"
#endif
} }
} }
} }

View File

@@ -1,13 +1,9 @@
# KeychainAccess # KeychainAccess
[![CI Status](http://img.shields.io/travis/kishikawakatsumi/KeychainAccess.svg)](https://travis-ci.org/kishikawakatsumi/KeychainAccess) [![CI Status](http://img.shields.io/travis/kishikawakatsumi/KeychainAccess.svg)](https://travis-ci.org/kishikawakatsumi/KeychainAccess)
[![codecov](https://codecov.io/gh/kishikawakatsumi/KeychainAccess/branch/master/graph/badge.svg)](https://codecov.io/gh/kishikawakatsumi/KeychainAccess)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![SPM supported](https://img.shields.io/badge/SPM-supported-DE5C43.svg?style=flat)](https://swift.org/package-manager)
[![Version](https://img.shields.io/cocoapods/v/KeychainAccess.svg)](http://cocoadocs.org/docsets/KeychainAccess) [![Version](https://img.shields.io/cocoapods/v/KeychainAccess.svg)](http://cocoadocs.org/docsets/KeychainAccess)
[![Platform](https://img.shields.io/cocoapods/p/KeychainAccess.svg)](http://cocoadocs.org/docsets/KeychainAccess) [![Platform](https://img.shields.io/cocoapods/p/KeychainAccess.svg)](http://cocoadocs.org/docsets/KeychainAccess)
[![Swift 3.x](https://img.shields.io/badge/Swift-3.x-orange.svg?style=flat)](https://swift.org/)
[![Swift 4.0](https://img.shields.io/badge/Swift-4.0-orange.svg?style=flat)](https://swift.org/)
[![Swift 4.1](https://img.shields.io/badge/Swift-4.1-orange.svg?style=flat)](https://swift.org/)
[![Swift 4.2](https://img.shields.io/badge/Swift-4.2-orange.svg?style=flat)](https://swift.org/)
KeychainAccess is a simple Swift wrapper for Keychain that works on iOS and OS X. Makes using Keychain APIs extremely easy and much more palatable to use in Swift. KeychainAccess is a simple Swift wrapper for Keychain that works on iOS and OS X. Makes using Keychain APIs extremely easy and much more palatable to use in Swift.
@@ -23,9 +19,10 @@ KeychainAccess is a simple Swift wrapper for Keychain that works on iOS and OS X
- [Support iCloud sharing](#icloud_sharing) - [Support iCloud sharing](#icloud_sharing)
- **[Support TouchID and Keychain integration (iOS 8+)](#touch_id_integration)** - **[Support TouchID and Keychain integration (iOS 8+)](#touch_id_integration)**
- **[Support Shared Web Credentials (iOS 8+)](#shared_web_credentials)** - **[Support Shared Web Credentials (iOS 8+)](#shared_web_credentials)**
- [Works on both iOS & OS X](#requirements) - [Works on both iOS & macOS](#requirements)
- [watchOS and tvOS are supported](#requirements) - [watchOS and tvOS are supported](#requirements)
- **[Swift 4 & Swift 3 compatible](#requirements)** - **[Mac Catalyst is supported](#requirements)**
- **[Swift 3, 4 and 5 compatible](#requirements)**
## :book: Usage ## :book: Usage
@@ -183,7 +180,7 @@ do {
```swift ```swift
let keychain = Keychain() let keychain = Keychain()
let persistentRef = keychain[attributes: "kishikawakatsumi"].persistentRef let persistentRef = keychain[attributes: "kishikawakatsumi"]?.persistentRef
... ...
``` ```
@@ -191,7 +188,7 @@ let persistentRef = keychain[attributes: "kishikawakatsumi"].persistentRef
```swift ```swift
let keychain = Keychain() let keychain = Keychain()
let creationDate = keychain[attributes: "kishikawakatsumi"].creationDate let creationDate = keychain[attributes: "kishikawakatsumi"]?.creationDate
... ...
``` ```
@@ -201,9 +198,9 @@ let creationDate = keychain[attributes: "kishikawakatsumi"].creationDate
let keychain = Keychain() let keychain = Keychain()
do { do {
let attributes = try keychain.get("kishikawakatsumi") { $0 } let attributes = try keychain.get("kishikawakatsumi") { $0 }
print(attributes.comment) print(attributes?.comment)
print(attributes.label) print(attributes?.label)
print(attributes.creator) print(attributes?.creator)
... ...
} catch let error { } catch let error {
print("error: \(error)") print("error: \(error)")
@@ -214,10 +211,11 @@ do {
```swift ```swift
let keychain = Keychain() let keychain = Keychain()
let attributes = keychain[attributes: "kishikawakatsumi"] if let attributes = keychain[attributes: "kishikawakatsumi"] {
print(attributes.comment) print(attributes.comment)
print(attributes.label) print(attributes.label)
print(attributes.creator) print(attributes.creator)
}
``` ```
### :key: Configuration (Accessibility, Sharing, iCloud Sync) ### :key: Configuration (Accessibility, Sharing, iCloud Sync)
@@ -320,12 +318,15 @@ do {
} }
``` ```
### <a name="touch_id_integration"> :fu: Touch ID integration ### <a name="touch_id_integration"> :cyclone: Touch ID (Face ID) integration
**Any Operation that require authentication must be run in the background thread.** **Any Operation that require authentication must be run in the background thread.**
**If you run in the main thread, UI thread will lock for the system to try to display the authentication dialog.** **If you run in the main thread, UI thread will lock for the system to try to display the authentication dialog.**
#### :closed_lock_with_key: Adding a Touch ID protected item
**To use Face ID, add `NSFaceIDUsageDescription` key to your `Info.plist`**
#### :closed_lock_with_key: Adding a Touch ID (Face ID) protected item
If you want to store the Touch ID protected Keychain item, specify `accessibility` and `authenticationPolicy` attributes. If you want to store the Touch ID protected Keychain item, specify `accessibility` and `authenticationPolicy` attributes.
@@ -344,7 +345,7 @@ DispatchQueue.global().async {
} }
``` ```
#### :closed_lock_with_key: Updating a Touch ID protected item #### :closed_lock_with_key: Updating a Touch ID (Face ID) protected item
The same way as when adding. The same way as when adding.
@@ -370,7 +371,7 @@ DispatchQueue.global().async {
} }
``` ```
#### :closed_lock_with_key: Obtaining a Touch ID protected item #### :closed_lock_with_key: Obtaining a Touch ID (Face ID) protected item
The same way as when you get a normal item. It will be displayed automatically Touch ID or passcode authentication If the item you try to get is protected. The same way as when you get a normal item. It will be displayed automatically Touch ID or passcode authentication If the item you try to get is protected.
If you want to show custom authentication prompt message, specify an `authenticationPrompt` attribute. If you want to show custom authentication prompt message, specify an `authenticationPrompt` attribute.
@@ -392,7 +393,7 @@ DispatchQueue.global().async {
} }
``` ```
#### :closed_lock_with_key: Removing a Touch ID protected item #### :closed_lock_with_key: Removing a Touch ID (Face ID) protected item
The same way as when you remove a normal item. The same way as when you remove a normal item.
There is no way to show Touch ID or passcode authentication when removing Keychain items. There is no way to show Touch ID or passcode authentication when removing Keychain items.
@@ -467,15 +468,15 @@ let password = Keychain.generatePassword() // => Nhu-GKm-s3n-pMx
#### How to set up Shared Web Credentials #### How to set up Shared Web Credentials
> 1. Add a com.apple.developer.associated-domains entitlement to your app. This entitlement must include all the domains with which you want to share credentials. > 1. Add a com.apple.developer.associated-domains entitlement to your app. This entitlement must include all the domains with which you want to share credentials.
>
> 2. Add an apple-app-site-association file to your website. This file must include application identifiers for all the apps with which the site wants to share credentials, and it must be properly signed. > 2. Add an apple-app-site-association file to your website. This file must include application identifiers for all the apps with which the site wants to share credentials, and it must be properly signed.
>
> 3. When the app is installed, the system downloads and verifies the site association file for each of its associated domains. If the verification is successful, the app is associated with the domain. > 3. When the app is installed, the system downloads and verifies the site association file for each of its associated domains. If the verification is successful, the app is associated with the domain.
**More details:** **More details:**
<https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/> <https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/>
### :key: Debugging ### :mag: Debugging
#### Display all stored items if print keychain object #### Display all stored items if print keychain object
@@ -529,19 +530,34 @@ item: [authenticationType: Default, key: hirohamada, server: github.com, class:
item: [authenticationType: Default, key: honeylemon, server: github.com, class: InternetPassword, protocol: https] item: [authenticationType: Default, key: honeylemon, server: github.com, class: InternetPassword, protocol: https]
``` ```
## Keychain sharing capability
If you encounter the error below, you need to add an `Keychain.entitlements`.
```
OSStatus error:[-34018] Internal error when a required entitlement isn't present, client has neither application-identifier nor keychain-access-groups entitlements.
```
<img alt="Screen Shot 2019-10-27 at 8 08 50" src="https://user-images.githubusercontent.com/40610/67627108-1a7f2f80-f891-11e9-97bc-7f7313cb63d1.png" width="500">
<img src="https://user-images.githubusercontent.com/40610/67627072-333b1580-f890-11e9-9feb-bf507abc2724.png" width="500" />
## Requirements ## Requirements
| | OS | Swift | | | OS | Swift |
|------------|----------------------------------------|---------------| |------------|------------------------------------------------------------|--------------------|
| **v1.1.x** | iOS 7+, OSX 10.9+ | 1.1 | | **v1.1.x** | iOS 7+, macOS 10.9+ | 1.1 |
| **v1.2.x** | iOS 7+, OSX 10.9+ | 1.2 | | **v1.2.x** | iOS 7+, macOS 10.9+ | 1.2 |
| **v2.0.x** | iOS 7+, OSX 10.9+, watchOS 2+ | 2.0 | | **v2.0.x** | iOS 7+, macOS 10.9+, watchOS 2+ | 2.0 |
| **v2.1.x** | iOS 7+, OSX 10.9+, watchOS 2+ | 2.0 | | **v2.1.x** | iOS 7+, macOS 10.9+, watchOS 2+ | 2.0 |
| **v2.2.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1 | | **v2.2.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1 |
| **v2.3.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1, 2.2 | | **v2.3.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1, 2.2 |
| **v2.4.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 2.2, 2.3 | | **v2.4.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.2, 2.3 |
| **v3.0.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 3.x | | **v3.0.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 3.x |
| **v3.1.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2 | | **v3.1.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2 |
| **v3.2.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2, 5.0 |
| **v4.0.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2, 5.1 |
| **v4.1.x** | iOS 8+, macOS 10.9+, watchOS 3+, tvOS 9+, Mac Catalyst 13+ | 4.0, 4.1, 4.2, 5.1 |
## Installation ## Installation
@@ -565,14 +581,31 @@ it, simply add the following line to your Cartfile:
### Swift Package Manager ### Swift Package Manager
KeychainAccess is also available through [Swift Package Manager](https://github.com/apple/swift-package-manager/). KeychainAccess is also available through [Swift Package Manager](https://github.com/apple/swift-package-manager/).
#### Xcode
Select `File > Swift Packages > Add Package Dependency...`,
<img src="https://user-images.githubusercontent.com/40610/67627000-2833b580-f88f-11e9-89ef-18819b1a6c67.png" width="800px" />
#### CLI
First, create `Package.swift` that its package declaration includes: First, create `Package.swift` that its package declaration includes:
```swift ```swift
// swift-tools-version:5.0
import PackageDescription import PackageDescription
let package = Package( let package = Package(
name: "MyLibrary",
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"]),
],
dependencies: [ dependencies: [
.Package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", majorVersion: 2) .package(url: "https://github.com/kishikawakatsumi/KeychainAccess.git", from: "3.0.0"),
],
targets: [
.target(name: "MyLibrary", dependencies: ["KeychainAccess"]),
] ]
) )
``` ```

8
Pods/Manifest.lock generated
View File

@@ -7,14 +7,14 @@ PODS:
- AppCenter/Core (3.1.0) - AppCenter/Core (3.1.0)
- AppCenter/Crashes (3.1.0): - AppCenter/Crashes (3.1.0):
- AppCenter/Core - AppCenter/Core
- KeychainAccess (3.2.0) - KeychainAccess (4.2.1)
- Nuke (7.6.3) - Nuke (7.6.3)
- Sparkle (1.21.3) - Sparkle (1.21.3)
- STPrivilegedTask (1.0.7) - STPrivilegedTask (1.0.7)
DEPENDENCIES: DEPENDENCIES:
- AppCenter (~> 3.1.0) - AppCenter (~> 3.1.0)
- KeychainAccess (~> 3.2.0) - KeychainAccess (~> 4.2.0)
- Nuke (~> 7.0) - Nuke (~> 7.0)
- Sparkle - Sparkle
- STPrivilegedTask (from `https://github.com/rileytestut/STPrivilegedTask.git`) - STPrivilegedTask (from `https://github.com/rileytestut/STPrivilegedTask.git`)
@@ -37,11 +37,11 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS: SPEC CHECKSUMS:
AppCenter: a1c30c47b7882a04a615ffa5ab26c007326436d8 AppCenter: a1c30c47b7882a04a615ffa5ab26c007326436d8
KeychainAccess: 3b1bf8a77eb4c6ea1ce9404c292e48f948954c6b KeychainAccess: 9b07f665298d13c3a85881bd3171f6f49b8151c1
Nuke: 44130e95e09463f8773ae4b96b90de1eba6b3350 Nuke: 44130e95e09463f8773ae4b96b90de1eba6b3350
Sparkle: 3f75576db8b0265adef36c43249d747f22d0b708 Sparkle: 3f75576db8b0265adef36c43249d747f22d0b708
STPrivilegedTask: 56c3397238a1ec07720fb877a044898373cd2c68 STPrivilegedTask: 56c3397238a1ec07720fb877a044898373cd2c68
PODFILE CHECKSUM: b687e2aeec1ffc369787e92f2a2fefa460687731 PODFILE CHECKSUM: 6e3f9d2fc666262d43ff8079a3f9149b8f3376ee
COCOAPODS: 1.8.4 COCOAPODS: 1.8.4

View File

@@ -986,7 +986,7 @@
}; };
name = Release; name = Release;
}; };
4D7B47C1C925E85EF583F90672CFDCED /* Release */ = { 45293695F003AF5DCBA69464D33BD875 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 64D9DBBA06AB12F798AE3F1F91A19FAF /* KeychainAccess.xcconfig */; baseConfigurationReference = 64D9DBBA06AB12F798AE3F1F91A19FAF /* KeychainAccess.xcconfig */;
buildSettings = { buildSettings = {
@@ -1014,13 +1014,12 @@
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 5.0; SWIFT_VERSION = 5.1;
TARGETED_DEVICE_FAMILY = "1,2"; TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic"; VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = ""; VERSION_INFO_PREFIX = "";
}; };
name = Release; name = Debug;
}; };
4F7F91411B6EC0AEB386B2E5FE5F20E8 /* Release */ = { 4F7F91411B6EC0AEB386B2E5FE5F20E8 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
@@ -1101,41 +1100,6 @@
}; };
name = Debug; name = Debug;
}; };
5D237BFE5BE554A49204FA025EAC7A41 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 64D9DBBA06AB12F798AE3F1F91A19FAF /* KeychainAccess.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/KeychainAccess/KeychainAccess-prefix.pch";
INFOPLIST_FILE = "Target Support Files/KeychainAccess/KeychainAccess-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MODULEMAP_FILE = "Target Support Files/KeychainAccess/KeychainAccess.modulemap";
PRODUCT_MODULE_NAME = KeychainAccess;
PRODUCT_NAME = KeychainAccess;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Debug;
};
5E307238EB942E7EC9F39671288715AE /* Release */ = { 5E307238EB942E7EC9F39671288715AE /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = CAC29D24D26CC8214B6B5A283B48A108 /* Pods-AltStoreCore.release.xcconfig */; baseConfigurationReference = CAC29D24D26CC8214B6B5A283B48A108 /* Pods-AltStoreCore.release.xcconfig */;
@@ -1578,6 +1542,42 @@
}; };
name = Release; name = Release;
}; };
FF3EC215E8144B4CBE1F2C8440716AA6 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 64D9DBBA06AB12F798AE3F1F91A19FAF /* KeychainAccess.xcconfig */;
buildSettings = {
CODE_SIGN_IDENTITY = "";
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/KeychainAccess/KeychainAccess-prefix.pch";
INFOPLIST_FILE = "Target Support Files/KeychainAccess/KeychainAccess-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.2;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MODULEMAP_FILE = "Target Support Files/KeychainAccess/KeychainAccess.modulemap";
PRODUCT_MODULE_NAME = KeychainAccess;
PRODUCT_NAME = KeychainAccess;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 5.1;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
/* End XCBuildConfiguration section */ /* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */ /* Begin XCConfigurationList section */
@@ -1611,8 +1611,8 @@
4BEE926243448802ACA6F07A75D9C025 /* Build configuration list for PBXNativeTarget "KeychainAccess" */ = { 4BEE926243448802ACA6F07A75D9C025 /* Build configuration list for PBXNativeTarget "KeychainAccess" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
5D237BFE5BE554A49204FA025EAC7A41 /* Debug */, 45293695F003AF5DCBA69464D33BD875 /* Debug */,
4D7B47C1C925E85EF583F90672CFDCED /* Release */, FF3EC215E8144B4CBE1F2C8440716AA6 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>3.2.0</string> <string>4.2.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>