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!
# Pods for AltServer
pod 'KeychainAccess', '~> 3.2.0'
pod 'KeychainAccess', '~> 4.2.0'
end

View File

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

View File

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

View File

@@ -1,13 +1,9 @@
# 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)
[![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)
[![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.
@@ -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 TouchID and Keychain integration (iOS 8+)](#touch_id_integration)**
- **[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)
- **[Swift 4 & Swift 3 compatible](#requirements)**
- **[Mac Catalyst is supported](#requirements)**
- **[Swift 3, 4 and 5 compatible](#requirements)**
## :book: Usage
@@ -183,7 +180,7 @@ do {
```swift
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
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()
do {
let attributes = try keychain.get("kishikawakatsumi") { $0 }
print(attributes.comment)
print(attributes.label)
print(attributes.creator)
print(attributes?.comment)
print(attributes?.label)
print(attributes?.creator)
...
} catch let error {
print("error: \(error)")
@@ -214,10 +211,11 @@ do {
```swift
let keychain = Keychain()
let attributes = keychain[attributes: "kishikawakatsumi"]
if let attributes = keychain[attributes: "kishikawakatsumi"] {
print(attributes.comment)
print(attributes.label)
print(attributes.creator)
}
```
### :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.**
**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.
@@ -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.
@@ -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.
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.
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
> 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.
>
> 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:**
<https://developer.apple.com/library/ios/documentation/Security/Reference/SharedWebCredentialsRef/>
### :key: Debugging
### :mag: Debugging
#### 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]
```
## 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
| | OS | Swift |
|------------|----------------------------------------|---------------|
| **v1.1.x** | iOS 7+, OSX 10.9+ | 1.1 |
| **v1.2.x** | iOS 7+, OSX 10.9+ | 1.2 |
| **v2.0.x** | iOS 7+, OSX 10.9+, watchOS 2+ | 2.0 |
| **v2.1.x** | iOS 7+, OSX 10.9+, watchOS 2+ | 2.0 |
| **v2.2.x** | iOS 8+, OSX 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.4.x** | iOS 8+, OSX 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.1.x** | iOS 8+, OSX 10.9+, watchOS 2+, tvOS 9+ | 4.0, 4.1, 4.2 |
|------------|------------------------------------------------------------|--------------------|
| **v1.1.x** | iOS 7+, macOS 10.9+ | 1.1 |
| **v1.2.x** | iOS 7+, macOS 10.9+ | 1.2 |
| **v2.0.x** | iOS 7+, macOS 10.9+, watchOS 2+ | 2.0 |
| **v2.1.x** | iOS 7+, macOS 10.9+, watchOS 2+ | 2.0 |
| **v2.2.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1 |
| **v2.3.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.0, 2.1, 2.2 |
| **v2.4.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 2.2, 2.3 |
| **v3.0.x** | iOS 8+, macOS 10.9+, watchOS 2+, tvOS 9+ | 3.x |
| **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
@@ -565,14 +581,31 @@ it, simply add the following line to your Cartfile:
### 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:
```swift
// swift-tools-version:5.0
import PackageDescription
let package = Package(
name: "MyLibrary",
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"]),
],
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/Crashes (3.1.0):
- AppCenter/Core
- KeychainAccess (3.2.0)
- KeychainAccess (4.2.1)
- Nuke (7.6.3)
- Sparkle (1.21.3)
- STPrivilegedTask (1.0.7)
DEPENDENCIES:
- AppCenter (~> 3.1.0)
- KeychainAccess (~> 3.2.0)
- KeychainAccess (~> 4.2.0)
- Nuke (~> 7.0)
- Sparkle
- STPrivilegedTask (from `https://github.com/rileytestut/STPrivilegedTask.git`)
@@ -37,11 +37,11 @@ CHECKOUT OPTIONS:
SPEC CHECKSUMS:
AppCenter: a1c30c47b7882a04a615ffa5ab26c007326436d8
KeychainAccess: 3b1bf8a77eb4c6ea1ce9404c292e48f948954c6b
KeychainAccess: 9b07f665298d13c3a85881bd3171f6f49b8151c1
Nuke: 44130e95e09463f8773ae4b96b90de1eba6b3350
Sparkle: 3f75576db8b0265adef36c43249d747f22d0b708
STPrivilegedTask: 56c3397238a1ec07720fb877a044898373cd2c68
PODFILE CHECKSUM: b687e2aeec1ffc369787e92f2a2fefa460687731
PODFILE CHECKSUM: 6e3f9d2fc666262d43ff8079a3f9149b8f3376ee
COCOAPODS: 1.8.4

View File

@@ -986,7 +986,7 @@
};
name = Release;
};
4D7B47C1C925E85EF583F90672CFDCED /* Release */ = {
45293695F003AF5DCBA69464D33BD875 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 64D9DBBA06AB12F798AE3F1F91A19FAF /* KeychainAccess.xcconfig */;
buildSettings = {
@@ -1014,13 +1014,12 @@
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 5.0;
SWIFT_VERSION = 5.1;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
name = Debug;
};
4F7F91411B6EC0AEB386B2E5FE5F20E8 /* Release */ = {
isa = XCBuildConfiguration;
@@ -1101,41 +1100,6 @@
};
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 */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = CAC29D24D26CC8214B6B5A283B48A108 /* Pods-AltStoreCore.release.xcconfig */;
@@ -1578,6 +1542,42 @@
};
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 */
/* Begin XCConfigurationList section */
@@ -1611,8 +1611,8 @@
4BEE926243448802ACA6F07A75D9C025 /* Build configuration list for PBXNativeTarget "KeychainAccess" */ = {
isa = XCConfigurationList;
buildConfigurations = (
5D237BFE5BE554A49204FA025EAC7A41 /* Debug */,
4D7B47C1C925E85EF583F90672CFDCED /* Release */,
45293695F003AF5DCBA69464D33BD875 /* Debug */,
FF3EC215E8144B4CBE1F2C8440716AA6 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;

View File

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