Locksmith
A powerful, protocol-oriented library for working with the keychain in Swift.
-
đ± iOS 8.0+ -
đ» Mac OS X 10.10+ -
âïž watchOS 2 -
đș tvOS
đ I make Rocket, an app that gives you Slack-style emoji everywhere on your Mac.
Details
How is Locksmith different to other keychain wrappers?
- Locksmithâs API is both super-simple and deeply powerful
- Provides access to all of the keychainâs metadata with strongly typed results
- Add functionality to your existing types for free
- Useful enums and Swift-native types
Want to read more about Locksmithâs design? I wrote a blog post on protocol oriented programming in Swift.
Installation
- Locksmith 4.0 and greater is Swift 4 compatible. See the
swift-3.1
branch for compatibility with older versions of Swift.
CocoaPods
Locksmith is available through CocoaPods.
pod 'Locksmith'
Carthage
Locksmith is available through Carthage.
github "matthewpalmer/Locksmith"
Quick start
Setup
- Choose your target project
- Select Capabilties
- Enable Keychain Sharing
Xcode then automatically creates a .entitlements file and you are ready to use Locksmith!
Save data
try Locksmith.saveData(["some key": "some value"], forUserAccount: "myUserAccount")
Load data
let dictionary = Locksmith.loadDataForUserAccount("myUserAccount")
Update data
- as well as replacing existing data, this writes data to the keychain if it does not exist already
try Locksmith.updateData(["some key": "another value"], forUserAccount: "myUserAccount")
Delete data
try Locksmith.deleteDataForUserAccount("myUserAccount")
Power to the protocols
Locksmith has been designed with Swift 2, protocols, and protocol extensions in mind.
Why do this? Because you can add existing functionality to your types with only the slightest changes!
Say we have a Twitter account
struct TwitterAccount {
let username: String
let password: String
}
and we want to save it to the keychain as a generic password. All we need to do is conform to the right protocols in Locksmith and we get that functionality for free.
struct TwitterAccount: CreateableSecureStorable, GenericPasswordSecureStorable {
let username: String
let password: String
// Required by GenericPasswordSecureStorable
let service = "Twitter"
var account: String { return username }
// Required by CreateableSecureStorable
var data: [String: AnyObject] {
return ["password": password]
}
}
Now we get the ability to save our account in the keychain.
let account = TwitterAccount(username: "_matthewpalmer", password: "my_password")
try account.createInSecureStore()
Creating, reading, and deleting each have their own protocols: CreateableSecureStorable
, ReadableSecureStorable
, and DeleteableSecureStorable
. And the best part?
You can conform to all three protocols on the same type!
struct TwitterAccount: ReadableSecureStorable,
CreateableSecureStorable,
DeleteableSecureStorable,
GenericPasswordSecureStorable {
let username: String
let password: String
let service = "Twitter"
var account: String { return username }
var data: [String: AnyObject] {
return ["password": password]
}
}
let account = TwitterAccount(username: "_matthewpalmer", password: "my_password")
// CreateableSecureStorable lets us create the account in the keychain
try account.createInSecureStore()
// ReadableSecureStorable lets us read the account from the keychain
let result = account.readFromSecureStore()
// DeleteableSecureStorable lets us delete the account from the keychain
try account.deleteFromSecureStore()
So. cool.
The details
By declaring that your type adopts these protocolsâwhich is what we did above with struct TwitterAccount: CreateableSecureStorable, ...
âyou get a bunch of functionality for free.
I like to think about protocols with extensions in terms of âwhat you get,â âwhat youâve gotta do,â and âwhatâs optional.â Most of the stuff under âoptionalâ should only be implemented if you want to change existing functionality.
CreateableSecureStorable
What you get
// Saves a type to the keychain
func createInSecureStore() throws
Required
// The data to save to the keychain
var data: [String: AnyObject] { get }
Optional
// Perform the request in this closure
var performCreateRequestClosure: PerformRequestClosureType { get }
ReadableSecureStorable
What you get
// Read from the keychain
func readFromSecureStore() -> SecureStorableResultType?
Required
Nothing!
Optional
// Perform the request in this closure
var performReadRequestClosure: PerformRequestClosureType { get }
DeleteableSecureStorable
What you get
// Read from the keychain
func deleteFromSecureStore() throws
Required
Nothing!
Optional
// Perform the request in this closure
var performDeleteRequestClosure: PerformRequestClosureType { get }
Powerful support for the Cocoa Keychain
Many wrappers around the keychain have only support certain parts of the API. This is because there are so many options and variations on the way you can query the keychain that itâs almost impossible to abstract effectively.
Locksmith tries to include as much of the keychain as possible, using protocols and protocol extensions to minimize the complexity. You can mix-and-match your generic passwords with your read requests while staying completely type-safe.
Please refer to the Keychain Services Reference for full information on what each of the attributes mean and what they can do.
Certificates, keys, and identities are possibleâitâs just a matter of translating the
kSec...
constants!
GenericPasswordSecureStorable
Generic passwords are probably the most common use-case of the keychain, and are great for storing usernames and passwords.
Properties listed under âRequiredâ have to be implemented by any types that conform; those listed under âOptionalâ can be implemented to add additional information to what is saved or read if desired.
One thing to note: if you implement an optional property, its type annotation must match the type specified in the protocol exactly. If you implement description: String?
it canât be declared as var description: String
.
Required
var account: String { get }
var service: String { get }
Optional
var comment: String? { get }
var creator: UInt? { get }
var description: String? { get }
var generic: NSData? { get }
var isInvisible: Bool? { get }
var isNegative: Bool? { get }
var label: String? { get }
var type: UInt? { get }
InternetPasswordSecureStorable
Types that conform to InternetPasswordSecureStorable
typically come from web services and have certain associated metadata.
Required
var account: String { get }
var authenticationType: LocksmithInternetAuthenticationType { get }
var internetProtocol: LocksmithInternetProtocol { get }
var port: String { get }
var server: String { get }
Optional
var comment: String? { get }
var creator: UInt? { get }
var description: String? { get }
var isInvisible: Bool? { get }
var isNegative: Bool? { get }
var path: String? { get }
var securityDomain: String? { get }
var type: UInt? { get }
Result types
By adopting a protocol-oriented design from the ground up, Locksmith can provide access to the result of your keychain queries with type annotations includedâstore an NSDate
, get an NSDate
back with no type-casting!
Letâs start with an example: the Twitter account from before, except itâs now an InternetPasswordSecureStorable
, which lets us store a bit more metadata.
struct TwitterAccount: InternetPasswordSecureStorable,
ReadableSecureStorable,
CreateableSecureStorable {
let username: String
let password: String
var account: String { return username }
var data: [String: AnyObject] {
return ["password": password]
}
let server = "com.twitter"
let port = 80
let internetProtocol = .HTTPS
let authenticationType = .HTTPBasic
let path: String? = "/api/2.0/"
}
let account = TwitterAccount(username: "_matthewpalmer", password: "my_password")
// Save all this to the keychain
account.createInSecureStore()
// Now letâs get it back
let result: InternetPasswordSecureStorableResultType = account.readFromSecureStore()
result?.port // Gives us an Int directly!
result?.internetProtocol // Gives us a LocksmithInternetProtocol enum case directly!
result?.data // Gives us a [String: AnyObject] of what was saved
// and so on...
This is awesome. No more typecasting.
GenericPasswordSecureStorableResultType
Everything listed here can be set on a type conforming to GenericPasswordSecureStorable
, and gotten back from the result returned from readFromSecureStore()
on that type.
var account: String { get }
var service: String { get }
var comment: String? { get }
var creator: UInt? { get }
var description: String? { get }
var data: [String: AnyObject]? { get }
var generic: NSData? { get }
var isInvisible: Bool? { get }
var isNegative: Bool? { get }
var label: String? { get }
var type: UInt? { get }
InternetPasswordSecureStorableResultType
Everything listed here can be set on a type conforming to InternetPasswordSecureStorable
, and gotten back from the result returned from readFromSecureStore()
on that type.
var account: String { get }
var authenticationType: LocksmithInternetAuthenticationType { get }
var internetProtocol: LocksmithInternetProtocol { get }
var port: Int { get }
var server: String { get }
var comment: String? { get }
var creator: UInt? { get }
var data: [String: AnyObject]? { get }
var description: String? { get }
var isInvisible: Bool? { get }
var isNegative: Bool? { get }
var path: String? { get }
var securityDomain: String? { get }
var type: UInt? { get }
Enumerations
Locksmith provides a bunch of handy enums for configuring your requests, so you can say kSecGoodByeStringConstants
.
LocksmithAccessibleOption
LocksmithAccessibleOption
configures when an item can be accessedâyou might require that stuff is available when the device is unlocked, after a passcode has been entered, etc.
public enum LocksmithAccessibleOption {
case AfterFirstUnlock
case AfterFirstUnlockThisDeviceOnly
case Always
case AlwaysThisDeviceOnly
case WhenPasscodeSetThisDeviceOnly
case WhenUnlocked
case WhenUnlockedThisDeviceOnly
}
LocksmithError
LocksmithError
provides Swift-friendly translations of common keychain error codes. These are thrown from methods throughout the library. Appleâs documentation provides more information on these errors.
public enum LocksmithError: ErrorType {
case Allocate
case AuthFailed
case Decode
case Duplicate
case InteractionNotAllowed
case NoError
case NotAvailable
case NotFound
case Param
case RequestNotSet
case TypeNotFound
case UnableToClear
case Undefined
case Unimplemented
}
LocksmithInternetAuthenticationType
LocksmithInternetAuthenticationType
lets you pick out the type of authentication you want to store alongside your .InternetPassword
sâanything from .MSN
to .HTTPDigest
. Appleâs documentation provides more information on these values.
public enum LocksmithInternetAuthenticationType {
case Default
case DPA
case HTMLForm
case HTTPBasic
case HTTPDigest
case MSN
case NTLM
case RPA
}
LocksmithInternetProtocol
LocksmithInternetProtocol
is used with .InternetPassword
to choose which protocol was used for the interaction with the web service, including .HTTP
, .SMB
, and a whole bunch more. Appleâs documentation provides more information on these values.
public enum {
case AFP
case AppleTalk
case DAAP
case EPPC
case FTP
case FTPAccount
case FTPProxy
case FTPS
case HTTP
case HTTPProxy
case HTTPS
case HTTPSProxy
case IMAP
case IMAPS
case IPP
case IRC
case IRCS
case LDAP
case NNTP
case NNTPS, LDAPS
case POP3
case POP3S
case RTSP
case RTSPProxy
case SMB
case SMTP
case SOCKS
case SSH
case Telnet
case TelnetS
}
Author
Matthew Palmer, [email protected]
License
Locksmith is available under the MIT license. See the LICENSE file for more info.