Valet lets you securely store data in the iOS, tvOS, or macOS Keychain without knowing a thing about how the Keychain works. It’s easy. We promise.

Overview

Valet

CI Status Carthage Compatibility Version License Platform

Valet lets you securely store data in the iOS, tvOS, watchOS, or macOS Keychain without knowing a thing about how the Keychain works. It’s easy. We promise.

Getting Started

CocoaPods

Install with CocoaPods by adding the following to your Podfile:

on iOS:

platform :ios, '9.0'
use_frameworks!
pod 'Valet'

on tvOS:

platform :tvos, '9.0'
use_frameworks!
pod 'Valet'

on watchOS:

platform :watchos, '2.0'
use_frameworks!
pod 'Valet'

on macOS:

platform :osx, '10.11'
use_frameworks!
pod 'Valet'

Carthage

Install with Carthage by adding the following to your Cartfile:

github "Square/Valet"

Run carthage to build the framework and drag the built Valet.framework into your Xcode project.

Swift Package Manager

Install with Swift Package Manager by adding the following to your Package.swift:

dependencies: [
    .package(url: "https://github.com/Square/Valet", from: "4.0.0"),
],

Submodules

Or manually checkout the submodule with git submodule add [email protected]:Square/Valet.git, drag Valet.xcodeproj to your project, and add Valet as a build dependency.

Usage

Basic Initialization

let myValet = Valet.valet(with: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
VALValet *const myValet = [VALValet valetWithIdentifier:@"Druidia" accessibility:VALAccessibilityWhenUnlocked];

To begin storing data securely using Valet, you need to create a Valet instance with:

  • An identifier – a non-empty string that is used to identify this Valet. The Swift API uses an Identifier wrapper class to enforce the non-empty constraint.
  • An accessibility value – an enum (Accessibility) that defines when you will be able to persist and retrieve data.

This myValet instance can be used to store and retrieve data securely on this device, but only when the device is unlocked.

Choosing the Best Identifier

The identifier you choose for your Valet is used to create a sandbox for the data your Valet writes to the keychain. Two Valets of the same type created via the same initializer, accessibility value, and identifier will be able to read and write the same key:value pairs; Valets with different identifiers each have their own sandbox. Choose an identifier that describes the kind of data your Valet will protect. You do not need to include your application name or bundle identifier in your Valet’s identifier.

Choosing a User-friendly Identifier on macOS

let myValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
VALValet *const myValet = [VALValet valetWithExplicitlySetIdentifier:@"Druidia" accessibility:VALAccessibilityWhenUnlocked];

Mac apps signed with a developer ID may see their Valet’s identifier shown to their users. ⚠️ ⚠️ While it is possible to explicitly set a user-friendly identifier, note that doing so bypasses this project’s guarantee that one Valet type will not have access to one another type’s key:value pairs ⚠️ ⚠️ . To maintain this guarantee, ensure that each Valet’s identifier is globally unique.

Choosing the Best Accessibility Value

The Accessibility enum is used to determine when your secrets can be accessed. It’s a good idea to use the strictest accessibility possible that will allow your app to function. For example, if your app does not run in the background you will want to ensure the secrets can only be read when the phone is unlocked by using .whenUnlocked or .whenUnlockedThisDeviceOnly.

Changing an Accessibility Value After Persisting Data

let myOldValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
let myNewValet = Valet.valet(withExplicitlySet: Identifier(nonEmpty: "Druidia")!, accessibility: .afterFirstUnlock)
try? myNewValet.migrateObjects(from: myOldValet, removeOnCompletion: true)
VALValet *const myOldValet = [VALValet valetWithExplicitlySetIdentifier:@"Druidia" accessibility:VALAccessibilityWhenUnlocked];
VALValet *const myNewValet = [VALValet valetWithExplicitlySetIdentifier:@"Druidia" accessibility:VALAccessibilityAfterFirstUnlock];
[myNewValet migrateObjectsFrom:myOldValet removeOnCompletion:true error:nil];

The Valet type, identifier, accessibility value, and initializer chosen to create a Valet are combined to create a sandbox within the keychain. This behavior ensures that different Valets can not read or write one another's key:value pairs. If you change a Valet's accessibility after persisting key:value pairs, you must migrate the key:value pairs from the Valet with the no-longer-desired accessibility to the Valet with the desired accessibility to avoid data loss.

Reading and Writing

let username = "Skroob"
try? myValet.setString("12345", forKey: username)
let myLuggageCombination = myValet.string(forKey: username)
NSString *const username = @"Skroob";
[myValet setString:@"12345" forKey:username error:nil];
NSString *const myLuggageCombination = [myValet stringForKey:username error:nil];

In addition to allowing the storage of strings, Valet allows the storage of Data objects via setObject(_ object: Data, forKey key: Key) and object(forKey key: String). Valets created with a different class type, via a different initializer, or with a different accessibility attribute will not be able to read or modify values in myValet.

Sharing Secrets Among Multiple Applications Using a Keychain Sharing Entitlement

let mySharedValet = Valet.sharedGroupValet(with: SharedGroupIdentifier(appIDPrefix: "AppID12345", nonEmptyGroup: "Druidia")!, accessibility: .whenUnlocked)
VALValet *const mySharedValet = [VALValet sharedGroupValetWithAppIDPrefix:@"AppID12345" sharedGroupIdentifier:@"Druidia" accessibility:VALAccessibilityWhenUnlocked];

This instance can be used to store and retrieve data securely across any app written by the same developer that has AppID12345.Druidia (or $(AppIdentifierPrefix)Druidia) set as a value for the keychain-access-groups key in the app’s Entitlements, where AppID12345 is the application’s App ID prefix. This Valet is accessible when the device is unlocked. Note that myValet and mySharedValet can not read or modify one another’s values because the two Valets were created with different initializers. All Valet types can share secrets across applications written by the same developer by using the sharedGroupValet initializer.

Sharing Secrets Among Multiple Applications Using an App Groups Entitlement

let mySharedValet = Valet.sharedGroupValet(with: SharedGroupIdentifier(groupPrefix: "group", nonEmptyGroup: "Druidia")!, accessibility: .whenUnlocked)
VALValet *const mySharedValet = [VALValet sharedGroupValetWithGroupPrefix:@"group" sharedGroupIdentifier:@"Druidia" accessibility:VALAccessibilityWhenUnlocked];

This instance can be used to store and retrieve data securely across any app written by the same developer that has group.Druidia set as a value for the com.apple.security.application-groups key in the app’s Entitlements. This Valet is accessible when the device is unlocked. Note that myValet and mySharedValet cannot read or modify one another’s values because the two Valets were created with different initializers. All Valet types can share secrets across applications written by the same developer by using the sharedGroupValet initializer. Note that on macOS, the groupPrefix must be the App ID prefix.

Sharing Secrets Across Devices with iCloud

let myCloudValet = Valet.iCloudValet(with: Identifier(nonEmpty: "Druidia")!, accessibility: .whenUnlocked)
VALValet *const myCloudValet = [VALValet iCloudValetWithIdentifier:@"Druidia" accessibility:VALAccessibilityWhenUnlocked];

This instance can be used to store and retrieve data that can be retrieved by this app on other devices logged into the same iCloud account with iCloud Keychain enabled. If iCloud Keychain is not enabled on this device, secrets can still be read and written, but will not sync to other devices. Note that myCloudValet can not read or modify values in either myValet or mySharedValet because myCloudValet was created a different initializer.

Protecting Secrets with Face ID, Touch ID, or device Passcode

let mySecureEnclaveValet = SecureEnclaveValet.valet(with: Identifier(nonEmpty: "Druidia")!, accessControl: .userPresence)
VALSecureEnclaveValet *const mySecureEnclaveValet = [VALSecureEnclaveValet valetWithIdentifier:@"Druidia" accessControl:VALAccessControlUserPresence];

This instance can be used to store and retrieve data in the Secure Enclave. Each time data is retrieved from this Valet, the user will be prompted to confirm their presence via Face ID, Touch ID, or by entering their device passcode. If no passcode is set on the device, this instance will be unable to access or store data. Data is removed from the Secure Enclave when the user removes a passcode from the device. Storing data using SecureEnclaveValet is the most secure way to store data on iOS, tvOS, watchOS, and macOS.

let mySecureEnclaveValet = SinglePromptSecureEnclaveValet.valet(with: Identifier(nonEmpty: "Druidia")!, accessControl: .userPresence)
VALSinglePromptSecureEnclaveValet *const mySecureEnclaveValet = [VALSinglePromptSecureEnclaveValet valetWithIdentifier:@"Druidia" accessControl:VALAccessControlUserPresence];

This instance also stores and retrieves data in the Secure Enclave, but does not require the user to confirm their presence each time data is retrieved. Instead, the user will be prompted to confirm their presence only on the first data retrieval. A SinglePromptSecureEnclaveValet instance can be forced to prompt the user on the next data retrieval by calling the instance method requirePromptOnNextAccess().

In order for your customers not to receive a prompt that your app does not yet support Face ID, you must set a value for the Privacy - Face ID Usage Description (NSFaceIDUsageDescription) key in your app’s Info.plist.

Thread Safety

Valet is built to be thread safe: it is possible to use a Valet instance on any queue or thread. Valet instances ensure that code that talks to the Keychain is atomic – it is impossible to corrupt data in Valet by reading and writing on multiple queues simultaneously.

However, because the Keychain is effectively disk storage, there is no guarantee that reading and writing items is fast - accessing a Valet instance from the main queue can result in choppy animations or blocked UI. As a result, we recommend utilizing your Valet instance on a background queue; treat Valet like you treat other code that reads from and writes to disk.

Migrating Existing Keychain Values into Valet

Already using the Keychain and no longer want to maintain your own Keychain code? We feel you. That’s why we wrote migrateObjects(matching query: [String : AnyHashable], removeOnCompletion: Bool). This method allows you to migrate all your existing Keychain entries to a Valet instance in one line. Just pass in a Dictionary with the kSecClass, kSecAttrService, and any other kSecAttr* attributes you use – we’ll migrate the data for you. If you need more control over how your data is migrated, use migrateObjects(matching query: [String : AnyHashable], compactMap: (MigratableKeyValuePair<AnyHashable>) throws -> MigratableKeyValuePair<String>?) to filter or remap key:value pairs as part of your migration.

Integrating Valet into a macOS application

Your macOS application must have the Keychain Sharing entitlement in order to use Valet, even if your application does not intend to share keychain data between applications. For instructions on how to add a Keychain Sharing entitlement to your application, read Apple's documentation on the subject. For more information on why this requirement exists, see issue #213.

If your macOS application supports macOS 10.14 or prior, you must run myValet.migrateObjectsFromPreCatalina() before reading values from a Valet. macOS Catalina introduced a breaking change to the macOS keychain, requiring that macOS keychain items that utilize kSecAttrAccessible or kSecAttrAccessGroup set kSecUseDataProtectionKeychain to true when writing or accessing these items. Valet’s migrateObjectsFromPreCatalina() upgrades items entered into the keychain on older macOS devices or other operating systems to include the key:value pair kSecUseDataProtectionKeychain:true. Note that Valets that share keychain items between devices with iCloud are exempt from this requirement. Similarly, SecureEnclaveValet and SinglePromptSecureEnclaveValet are exempt from this requirement.

Debugging

Valet guarantees that reading and writing operations will succeed as long as written data is valid and canAccessKeychain() returns true. There are only a few cases that can lead to the keychain being inaccessible:

  1. Using the wrong Accessibility for your use case. Examples of improper use include using .whenPasscodeSetThisDeviceOnly when there is no passcode set on the device, or using .whenUnlocked when running in the background.
  2. Initializing a Valet with shared access group Valet when the shared access group identifier is not in your entitlements file.
  3. Using SecureEnclaveValet on an iOS device that doesn’t have a Secure Enclave. The Secure Enclave was introduced with the A7 chip, which first appeared in the iPhone 5S, iPad Air, and iPad Mini 2.
  4. Running your app in DEBUG from Xcode. Xcode sometimes does not properly sign your app, which causes a failure to access keychain due to entitlements. If you run into this issue, just hit Run in Xcode again. This signing issue will not occur in properly signed (not DEBUG) builds.
  5. Running your app on device or in the simulator with a debugger attached may also cause an entitlements error to be returned when reading from or writing to the keychain. To work around this issue on device, run the app without the debugger attached. After running once without the debugger attached the keychain will usually behave properly for a few runs with the debugger attached before the process needs to be repeated.
  6. Running your app or unit tests without the application-identifier entitlement. Xcode 8 introduced a requirement that all schemes must be signed with the application-identifier entitlement to access the keychain. To satisfy this requirement when running unit tests, your unit tests must be run inside of a host application.
  7. Attempting to write data larger than 4kb. The Keychain is built to securely store small secrets – writing large blobs is not supported by Apple's Security daemon.

Requirements

  • Xcode 11.0 or later. Xcode 10 and Xcode 9 require Valet version 3.2.8. Earlier versions of Xcode require Valet version 2.4.2.
  • iOS 9 or later.
  • tvOS 9 or later.
  • watchOS 2 or later.
  • macOS 10.11 or later.

Migrating from prior Valet versions

The good news: most Valet configurations do not have to migrate keychain data when upgrading from an older version of Valet. All Valet objects are backwards compatible with their counterparts from prior versions. We have exhaustive unit tests to prove it (search for test_backwardsCompatibility). Valets that have had their configurations deprecated by Apple will need to migrate stored data.

The bad news: there are multiple source-breaking API changes from prior versions.

Both guides below explain the changes required to upgrade to Valet 4.

Migrating from Valet 2

  1. Initializers have changed in both Swift and Objective-C - both languages use class methods now, which felt more semantically honest (a lot of the time you’re not instantiating a new Valet, you’re re-accessing one you’ve already created). See example usage above.
  2. VALSynchronizableValet (which allowed keychains to be synced to iCloud) has been replaced by a Valet.iCloudValet(with:accessibility:) (or +[VALValet iCloudValetWithIdentifier:accessibility:] in Objective-C). See examples above.
  3. VALAccessControl has been renamed to SecureEnclaveAccessControl (VALSecureEnclaveAccessControl in Objective-C). This enum no longer references TouchID; instead it refers to unlocking with biometric due to the introduction of Face ID.
  4. Valet, SecureEnclaveValet, and SinglePromptSecureEnclaveValet are no longer in the same inheritance tree. All three now inherit directly from NSObject and use composition to share code. If you were relying on the subclass hierarchy before, 1) that might be a code smell 2) consider declaring a protocol for the shared behavior you were expecting to make your migration to Valet 3 easier.

You'll also need to continue reading through the migration from Valet 3 section below.

Migrating from Valet 3

  1. The accessibility values always and alwaysThisDeviceOnly have been removed from Valet, because Apple has deprecated their counterparts (see the documentation for kSecAttrAccessibleAlways and kSecAttrAccessibleAlwaysThisDeviceOnly). To migrate values stored with always accessibility, use the method migrateObjectsFromAlwaysAccessibleValet(removeOnCompletion:) on a Valet with your new preferred accessibility. To migrate values stored with alwaysThisDeviceOnly accessibility, use the method migrateObjectsFromAlwaysAccessibleThisDeviceOnlyValet(removeOnCompletion:) on a Valet with your new preferred accessibility.
  2. Most APIs that returned optionals or Bool values have been migrated to returning a nonoptional and throwing if an error is encountered. Ignoring the error that can be thrown by each API will keep your code flow behaving the same as it did before. Walking through one example: in Swift, let secret: String? = myValet.string(forKey: myKey) becomes let secret: String? = try? myValet.string(forKey: myKey). In Objective-C, NSString *const secret = [myValet stringForKey:myKey]; becomes NSString *const secret = [myValet stringForKey:myKey error:nil];. If you're interested in the reason data wasn't returned, use a do-catch statement in Swift, or pass in an NSError to each API call and inspect the output in Objective-C. Each method clearly documents the Error type it can throw. See examples above.
  3. The class method used to create a Valet that can share secrets between applications using keychain shared access groups has changed. In order to prevent the incorrect detection of the App ID prefix in rare circumstances, the App ID prefix must now be explicitly passed into these methods. To create a shared access groups Valet, you'll need to create a SharedGroupIdentifier(appIDPrefix:nonEmptyGroup:). See examples above.

Contributing

We’re glad you’re interested in Valet, and we’d love to see where you take it. Please read our contributing guidelines prior to submitting a Pull Request.

Thanks, and please do take it for a joyride!

Comments
  • Differentiate between user cancel and deleted keychain item in VALSecureEnclaveValet.

    Differentiate between user cancel and deleted keychain item in VALSecureEnclaveValet.

    I am using VALSecureEnclaveValet for storing some sensitive data.

    My app needs to behave differently for use-case when user cancels the popup and when user disables PIN and then re-enables it.

    Since VALSecureEnclaveValet's objectForKey:userPrompt: returns nil for both the cases, how do I differentiate?

    bug 
    opened by nijindal 31
  • Missing Entitlements in debugger?

    Missing Entitlements in debugger?

    A 'Missing Entitlements' error occurred. This is likely due to an Apple Keychain bug. As a workaround try running on a device that is not attached to a debugger.

    More information: https://forums.developer.apple.com/thread/4743

    Guy, I got this issue in log when saving a string token to keychain on iOS 9

    valet = VALValet(identifier:"com.abc.xyz", accessibility: .WhenUnlockedThisDeviceOnly)
    valet!.setString(token, forKey: "token")
    

    Any help?

    duplicate question 
    opened by gloryluu 19
  • Support for new SecAccessControlCreateFlags in VALSecureEnclaveValet

    Support for new SecAccessControlCreateFlags in VALSecureEnclaveValet

    I want VALSecureEnclaveValet to support taking arbitrary SecAccessControlCreateFlags in the initializer. Currently only kSecAccessControlUserPresence is supported.

    enhancement 
    opened by dfed 19
  •  iOS 15 crash on `specialized static SecItem.copy<A>(matching:)`

    iOS 15 crash on `specialized static SecItem.copy(matching:)`

    Getting intermittent crash reports from devices running on iOS 15. Not sure if this is associated with Security framework or Valet.

    Valet version- 4.1.2

    I'm using a valet with basic initialisation: let keychain = Valet.valet(with: Identifier.init(nonEmpty: "identifier")!, accessibility: .afterFirstUnlock)

    The crash reports highlights line 25 specialized static SecItem.copy<A>(matching:) and Xcode organiser points to a line in code where it's accessing a string from keychain: keychain.string(forKey: "key")

    The stack trace from Crashlytics:

    Crashed: com.apple.root.user-initiated-qos
    0  libsystem_kernel.dylib         0x79c4 __pthread_kill + 8
    1  libsystem_pthread.dylib        0x7434 pthread_kill + 268
    2  libsystem_c.dylib              0x1ff64 abort + 164
    3  libsystem_malloc.dylib         0x1bac8 _malloc_put + 550
    4  libsystem_malloc.dylib         0x1bd64 malloc_zone_error + 104
    5  libsystem_malloc.dylib         0x162c8 nanov2_allocate_from_block + 568
    6  libsystem_malloc.dylib         0x1536c nanov2_allocate + 128
    7  libsystem_malloc.dylib         0x15288 nanov2_malloc + 64
    8  libsystem_malloc.dylib         0x5594 _malloc_zone_malloc + 156
    9  CoreFoundation                 0xed28 __CFBasicHashRehash + 376
    10 CoreFoundation                 0x206e4 __CFBasicHashAddValue + 104
    11 CoreFoundation                 0x14200 CFBasicHashAddValue + 2108
    12 CoreFoundation                 0x5d340 CFDictionaryAddValue + 348
    13 Security                       0x538c der_decode_dictionary + 248
    14 Security                       0x13d6c der_decode_plist + 1172
    15 Security                       0x115c4 SecXPCDictionaryCopyPList + 120
    16 Security                       0x16c8c SecXPCDictionaryCopyPListOptional + 72
    17 Security                       0x12aa0 securityd_send_sync_and_do + 136
    18 Security                       0xb55e0 cftype_to_bool_cftype_error_request + 160
    19 Security                       0x464c __SecItemCopyMatching_block_invoke_2 + 224
    20 Security                       0x5030 __SecItemAuthDoQuery_block_invoke + 540
    21 Security                       0x3790 SecItemAuthDoQuery + 1292
    22 Security                       0x4b98 __SecItemCopyMatching_block_invoke + 144
    23 Security                       0xaf58 SecOSStatusWith + 56
    24 Security                       0x48b8 SecItemCopyMatching + 400
    25 **                             0x1c9c770 specialized static SecItem.copy<A>(matching:) + 4362553200
    26 **                             0x1c9eb88 specialized static Keychain.object(forKey:options:) + 4362562440
    27 **                             0x1cb0988 Valet.string(forKey:) + 4362635656
    

    PS- lines 25, 26 & 27 shows the app name which I've replaced with **.

    opened by jithin-epifi 18
  • Add Multiple Swift Versions Support

    Add Multiple Swift Versions Support

    With Cocoapods 1.7.0 we are now able to specify support for multiple swift versions at once. This PR bumps the minimum Cocoapods version to 1.7.0 and adds explicit support for Swift versions 4.0, 4.1, 4.2, and 5.0. Valet's version has also been bumped to 3.2.4.

    opened by jakeholland 16
  • Allow kSecAttrService to be a customer-friendly string on Mac

    Allow kSecAttrService to be a customer-friendly string on Mac

    I'd been working on a project with uses valet and I was curious to know if I could change the name of the location to something more simple. For example, I would like it to display something like "Storage Space" instead of "VAL_VALValet_initWi...."

    40080620-5d5bb8f2-5840-11e8-9c6a-3b92af8a1e82

    enhancement 
    opened by abey 16
  • Add framework targets for iOS and OS X

    Add framework targets for iOS and OS X

    Closes #35

    This will allow the project to be used by Carthage and other dynamic framework tooling.

    Based loosely off of #37 from @EricMuller22, with a couple of changes. This adds two new targets for iOS and OS X that are both dynamic frameworks, compiling the source on their own, rather than including the static library into the dynamic framework.

    This also scopes out the headers into their correct visibility, so we shouldn't ever get to see anything we shouldn't, like mutableBaseQueryWithIdentifier:initializer:accessibility: or any of it's friends.

    Lastly, I renamed the framework files to be Valet.framework to better support universal frameworks.

    enhancement 
    opened by eliperkins 16
  • Intermittent Read Failures

    Intermittent Read Failures

    So I have a VALValet as such:

    static let sharedValet = VALValet(identifier: Key.identifier.rawValue, accessibility: .afterFirstUnlockThisDeviceOnly)!
    

    Most of the time it works fine. However, I'm running into rare, non-repeatable read failures for values which were stored in it (which I'm sure succeed). Are there any read failure scenarios I should be aware of? There isn't really a lot of insight into why a read failed as Valet just assumes it was because the value was never there to begin with. So what are my options here?

    opened by jshier 13
  • watchOS Support

    watchOS Support

    If it does not currently, it would be nice to use this library on watchOS 2 (and update the Podspec file to declare this support). I’m happy to help do this, just putting this here to see if anyone else is already working on this.

    enhancement 
    opened by SlaunchaMan 13
  • <unknown>:0: error: 'init' is inaccessible due to 'private' protection level

    :0: error: 'init' is inaccessible due to 'private' protection level

    When I upgrade Valet to 3.2.2 (happens with 3.0.1 as well), getting this:

    screen shot 2018-10-31 at 11 15 12

    Any ideas? I checked the initializers of the listed classes but they're are all public and I also don't understand why this is affected by upgrading Valet.

    opened by ivanschuetz 12
  • Build Error: Include of non-modular header inside framework module 'Valet.Valet'

    Build Error: Include of non-modular header inside framework module 'Valet.Valet'

    I got this error when I tried to build including the Valet framework, installed via Cocoapod.

    screen shot 2017-02-14 at 1 59 15 pm
    • Valet v2.3.0
    • Cocoapod v1.2.0
    • Xcode 8.0
    • Swift3-Compatible Project
    opened by mownier 12
  • Enable customization of biometrics prompt with application-specific fallback

    Enable customization of biometrics prompt with application-specific fallback

    In order have more control when retrieving something secured by biometrics in the secure enclave, an option is to first create an LAContext and call evaluatePolicy with it, and then, on success, pass that authenticated context when looking up the item. However, this is not currently possible with the public interface to SecureEnclaveValet.

    See more context here under "Restricting keychain item access to Touch ID and Face ID"

    Proposition to fix is to add new public methods to SecureEnclaveValet to allow a context to be provided:

    @objc
    public func string(forKey key: String, withPrompt userPrompt: String, context: LAContext) throws -> String {
        try execute(in: lock) {
            var keychainQuery = baseKeychainQuery
            keychainQuery[kSecUseAuthenticationContext as String] = context
            return try SecureEnclave.string(forKey: key, withPrompt: userPrompt, options: keychainQuery)
        }
    }
    

    and

    @objc
    public func object(forKey key: String, withPrompt userPrompt: String, context: LAContext) throws -> Data {
        try execute(in: lock) {
            var keychainQuery = baseKeychainQuery
            keychainQuery[kSecUseAuthenticationContext as String] = context
            return try SecureEnclave.string(forKey: key, withPrompt: userPrompt, options: keychainQuery)
        }
    }
    
    opened by allisonmoyer 25
  • Expose touchIDAuthenticationAllowableReuseDuration setting for SecureEnclaveValet

    Expose touchIDAuthenticationAllowableReuseDuration setting for SecureEnclaveValet

    Like apple doc says.

    This bypasses a scenario where the user unlocks the device and then is almost immediately prompted for another fingerprint.

    Some times that scenario is really annoying.

    enhancement 
    opened by the-pear 11
  • Run Watch tests in CI

    Run Watch tests in CI

    Since watchOS does not have an XCTest framework, we created a mock of the XCTest framework and built a watch app to host & run the tests.

    This watch app allows us to manually verify that Valet works as expected on the watch, but it doesn't allow us to catch regressions in CI. It'd be great for us to improve this situation by running our watch app in CI.

    Since the watch app exits with code 0 after successfully running tests, we're most of the way there. The crux of the remaining challenge is determining how to launch the watch app in the simulator from the command line.

    I've done some digging, and it looks like we may need to rely one some of the following commands:

    • xcrun simctl list # this command will allow us to determine which sims are on the CI machine, and allow us to find the UUIDs of phone+watch pairs.
    • xcrun simctl boot <uuid> # this command will boot the sims with the uuids we've found above. We'll likely need to launch both the phone and watch sims.
    • xcrun simctl install <uuid> <path-to-phone-app AND/OR path-to-watch-app> # this command should install the app on the simulator
    • xcrun simctl launch <uuid> <path-to-phone-app AND/OR path-to-watch-app> # this command should allow us to launch the app(s) we installed above

    I'm likely missing a command above, as I haven't managed to get a script working with the above. Pro-tip: you can monitor the logs emanating from your simulators by running the command xcrun simctl spawn <uuid> log stream. Note that the phone host app is ValetTouchIDTest, and the watch app is Valet watchOS Test Host App.

    Help would be greatly appreciated, since I'm brand new to command-line simulator manipulation.

    help wanted 
    opened by dfed 6
Releases(4.1.3)
  • 4.1.3(Aug 18, 2022)

    • Remove SinglePromptSecureEnclaveValet from the tvOS target in #284. This type never actually worked on tvOS, and its presence in the tvOS target was causing compiler errors in Xcode 14. Thank you @diogot for the contribution!
    Source code(tar.gz)
    Source code(zip)
  • 4.1.2(Feb 16, 2021)

  • 4.1.1(Jun 26, 2020)

  • 4.1.0(Jun 25, 2020)

  • 4.0.0(Jun 25, 2020)

    Valet 4.0 comes with lots of new functionality:

    • Introduced a throw-driven API, enabling more idiomatic handling of errors: #198
    • Enabled the customization of kSecAttrService on Mac: #140
    • Brought SinglePromptSecureEnclaveValet to tvOS: #191
    • Support for keychain sharing via App Groups: #230

    This release included breaking source changes from version 3. Data persisted by Valet 2 or Valet 3 can continue to be read and updated on Valet 4. Some functionality has been removed from Valet in this release:

    • Xcode 9 and 10 support has been removed: #190
    • Swift 4 support has been removed: #191
    • Accessibility's always and alwaysThisDeviceOnly were removed, as the underlying values were deprecated by Apple: #197

    See our README for instructions on migrating from prior Valet versions.

    Source code(tar.gz)
    Source code(zip)
  • 3.2.8(Jan 14, 2020)

    • Added explicit macOS Catalina support. Run migrateObjectsFromPreCatalina() on your Valet instances on macOS 10.15 or later prior to accessing values in a Valet. Your macOS application needs the Keychain Sharing entitlement to use Valet, even if you do not plan on sharing keychain data between applications. See #213 for more information.
    • This release does not affect iOS, tvOS, or watchOS.
    Source code(tar.gz)
    Source code(zip)
  • 3.2.7(Jan 14, 2020)

  • 3.2.6(Aug 16, 2019)

  • 3.2.5(May 30, 2019)

    • Removed deprecation warnings for projects that target an SDK of iOS 11.3, macOS 10.13.4, tvOS 11.3, watchOS 4.3 or higher. Thanks to @LinusU for the contribution!
    Source code(tar.gz)
    Source code(zip)
  • 3.2.4(May 28, 2019)

  • 3.2.3(Jan 9, 2019)

    • Fixes Valet triggering a spurious 'init' is inaccessible due to 'private' protection level build error when a private Valet initializer is used by a consuming application.
    Source code(tar.gz)
    Source code(zip)
  • 3.2.2(Oct 3, 2018)

  • 3.2.1(Oct 2, 2018)

  • 3.2.0(Oct 2, 2018)

  • 3.1.6(Aug 28, 2018)

  • 3.1.5(Jul 26, 2018)

  • 3.1.4(Jun 23, 2018)

  • 3.1.3(Mar 30, 2018)

  • 3.1.2(Mar 30, 2018)

    • Fixes an issue introduced in 3.0.0 where Valets created with Valet.sharedAccessGroupValet and Valet.iCloudSharedAccessGroupValet incorrectly returned a Valet.sharedAccessGroupValet and Valet.iCloudSharedAccessGroupValet, respectively.

    Note for apps that released an app with Valet v3.0-3.1.1 If you use Valet.sharedAccessGroupValet, run the following before accessing data in your Valet.sharedAccessGroupValet:

    let desiredValet = Valet.sharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.valet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    

    If you use Valet.iCloudSharedAccessGroupValet, run the following before accessing data in your Valet.iCloudSharedAccessGroupValet:

    let desiredValet = Valet.iCloudSharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.iCloudValet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    
    Source code(tar.gz)
    Source code(zip)
    Valet.framework.zip(2.53 MB)
  • 3.1.1(Mar 29, 2018)

    • Remove SinglePromptSecureEnclaveValet from tvOS target to get tvOS compiling on device

    This version is deprecated. Upgrade to 3.1.2+

    Known issue: attempting to create a Shared Access Group Valet via Valet.sharedAccessGroupValet or Valet.iCloudSharedAccessGroupValet would create a standard / non-shared Valet.

    Workaround

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.sharedAccessGroupValet, run the following before accessing data in your Valet.sharedAccessGroupValet:

    let desiredValet = Valet.sharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.valet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.iCloudSharedAccessGroupValet, run the following before accessing data in your Valet.iCloudSharedAccessGroupValet:

    let desiredValet = Valet.iCloudSharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.iCloudValet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    
    Source code(tar.gz)
    Source code(zip)
    Valet.framework.zip(2.50 MB)
  • 3.1.0(Mar 28, 2018)

    • Added support for tvOS

    This version is deprecated. Upgrade to 3.1.2+

    Known issue: This version does not build on tvOS devices due to the inclusion of LocalAuthentication in the tvOS target code.

    Workaround Update to 3.1.2+

    Known issue: attempting to create a Shared Access Group Valet via Valet.sharedAccessGroupValet or Valet.iCloudSharedAccessGroupValet would create a standard / non-shared Valet.

    Workaround

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.sharedAccessGroupValet, run the following before accessing data in your Valet.sharedAccessGroupValet:

    let desiredValet = Valet.sharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.valet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.iCloudSharedAccessGroupValet, run the following before accessing data in your Valet.iCloudSharedAccessGroupValet:

    let desiredValet = Valet.iCloudSharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.iCloudValet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    
    Source code(tar.gz)
    Source code(zip)
  • 3.0.1(Mar 28, 2018)

    • Assert when initializing a shared access group Valet with the Bundle Seed ID in the identifier. Addressed #130.

    This version is deprecated. Upgrade to 3.1.2+

    Known issue: attempting to create a Shared Access Group Valet via Valet.sharedAccessGroupValet or Valet.iCloudSharedAccessGroupValet would create a standard / non-shared Valet.

    Workaround

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.sharedAccessGroupValet, run the following before accessing data in your Valet.sharedAccessGroupValet:

    let desiredValet = Valet.sharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.valet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.iCloudSharedAccessGroupValet, run the following before accessing data in your Valet.iCloudSharedAccessGroupValet:

    let desiredValet = Valet.iCloudSharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.iCloudValet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    
    Source code(tar.gz)
    Source code(zip)
    Valet.framework.zip(1.46 MB)
  • 3.0.0(Mar 3, 2018)

    • Valet is rewritten in Swift!
    • A guide for upgrading from Valet 2.* can be found in our README. Note that you do not need to migrate your keychain data. Only the API surface has changed.

    This version is deprecated. Upgrade to 3.1.2+

    Known issue: attempting to create a Shared Access Group Valet via Valet.sharedAccessGroupValet or Valet.iCloudSharedAccessGroupValet would create a standard / non-shared Valet.

    Workaround

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.sharedAccessGroupValet, run the following before accessing data in your Valet.sharedAccessGroupValet:

    let desiredValet = Valet.sharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.valet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    

    For those who released an app with Valet v3.0-3.1.1 and used a Valet.iCloudSharedAccessGroupValet, run the following before accessing data in your Valet.iCloudSharedAccessGroupValet:

    let desiredValet = Valet.iCloudSharedAccessGroupValet(with: yourIdentifier, accessibility: yourAccessibility)
    let mistakenlyCreatedValet = Valet.iCloudValet(with: yourIdentifier, accessibility: yourAccessibility)
    desiredValet.migrateObjects(from: mistakenlyCreatedValet, removeOnCompletion: true)
    
    Source code(tar.gz)
    Source code(zip)
    Valet.framework.zip(1.46 MB)
  • 2.4.2(Sep 27, 2017)

  • 2.4.1(Aug 14, 2017)

  • 2.4.0(Feb 14, 2017)

  • 2.3.0(Jan 17, 2017)

  • 2.2.3(Sep 29, 2016)

  • 2.2.2(Jul 19, 2016)

  • 2.2.1(Feb 17, 2016)

    • Classify the case where user locks phone while Touch ID or Passcode Lock UI is up during VALSecureEnclaveValet authentication as userCancelled
    Source code(tar.gz)
    Source code(zip)
Owner
Square
Square
Simple Swift wrapper for Keychain that works on iOS, watchOS, tvOS and macOS.

KeychainAccess KeychainAccess is a simple Swift wrapper for Keychain that works on iOS and OS X. Makes using Keychain APIs extremely easy and much mor

Kishikawa Katsumi 7.2k Jan 5, 2023
Objective-C utility class for storing data securely in the key chain.

Lockbox Lockbox is an Objective-C utility class for storing data securely in the keychain. Use it to store small, sensitive bits of data securely. Loo

Mark Granoff 858 Sep 8, 2022
Simple Objective-C wrapper for the keychain that works on Mac and iOS

SAMKeychain SAMKeychain is a simple wrapper for accessing accounts, getting passwords, setting passwords, and deleting passwords using the system Keyc

Sam Soffes 5.4k Jan 8, 2023
Keychain - Keychain wrapper with swift

Keychain wrapper. Store a value as a generic password: let account = "an-arbitra

Alejandro Ramirez 0 Mar 14, 2022
A Layer-2 framework built over Keychain API which helps in using Keychain in all your Apple devices with easiness and flexibility.

Keychain Manager Keychain Manager is a Layer-2 framework built over Keychain API which helps in using Keychain in all your Apple devices with easiness

Gokul Nair 14 Jan 1, 2023
A simple wrapper for the iOS Keychain to allow you to use it in a similar fashion to User Defaults. Written in Swift.

SwiftKeychainWrapper A simple wrapper for the iOS / tvOS Keychain to allow you to use it in a similar fashion to User Defaults. Written in Swift. Prov

Jason 1.5k Jan 8, 2023
A keychain wrapper that is so easy to use that your cat could use it.

A keychain wrapper that is so easy to use that your cat could use it.

HyperRedink 71 Oct 15, 2022
Example of using TOTP with iCloud Keychain in iOS 15

Installation This example needs Ngrok and Ruby 3.0.3+. Setup project with Makefi

Makeeyaf 0 Dec 31, 2021
A tool to check which keychain items are available to an attacker once an iOS device has been jailbroken

Keychain Dumper Usage All that should be needed to use keychain_dumper is the binary that is checked in to the Keychain-Dumper Git repository. This bi

Patrick Toomey 1.2k Dec 28, 2022
A powerful, protocol-oriented library for working with the keychain in Swift.

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

Matthew Palmer 2.9k Dec 21, 2022
Modern Swift wrapper for Keychain Services API with the benefits of static typing

SwiftyKeychainKit SwiftyKeychainKit is a simple Swift wrapper for Keychain Services API with the benefits of static typing. Define your keys in one pl

Andriy Slyusar 18 Jan 1, 2023
Recover lost keychain passwords with swift

brutalkeychain Recover lost keychain passwords using a simple swift script (so c

Charles Edge 9 Sep 12, 2022
Recover lost keychain passwords with swift

brutalkeychain Recover lost keychain passwords using a simple swift script (so c

Charles Edge 4 Dec 24, 2021
Accessibility Abstraction Layer (axabl) for macOS window management

axabl accessibility abstraction layer axabl is an abstraction layer over the accessibility API (and more) provided by Apple to make window management

null 2 Jul 31, 2022
Valet lets you securely store data in the iOS, tvOS, or macOS Keychain without knowing a thing about how the Keychain works.

Valet Valet lets you securely store data in the iOS, tvOS, watchOS, or macOS Keychain without knowing a thing about how the Keychain works. It’s easy.

Square 3.8k Jan 4, 2023
A wrapper to make it really easy to deal with iOS, macOS, watchOS and Linux Keychain and store your user's credentials securely.

A wrapper (written only in Swift) to make it really easy to deal with iOS, macOS, watchOS and Linux Keychain and store your user's credentials securely.

Ezequiel Aceto 2 Mar 29, 2022
Helper functions for saving text in Keychain securely for iOS, OS X, tvOS and watchOS.

Helper functions for storing text in Keychain for iOS, macOS, tvOS and WatchOS This is a collection of helper functions for saving text and data in th

Evgenii Neumerzhitckii 2.3k Dec 28, 2022
UICKeyChainStore is a simple wrapper for Keychain on iOS, watchOS, tvOS and macOS. Makes using Keychain APIs as easy as NSUserDefaults.

UICKeyChainStore UICKeyChainStore is a simple wrapper for Keychain that works on iOS and OS X. Makes using Keychain APIs as easy as NSUserDefaults. Lo

Kishikawa Katsumi 3.1k Dec 28, 2022
(Animate CSS) animations for iOS. An easy to use library of iOS animations. As easy to use as an easy thing.

wobbly See Wobbly in action (examples) Add a drop of honey ?? to your project wobbly has a bunch of cool, fun, and easy to use iOS animations for you

Sagaya Abdulhafeez 150 Dec 23, 2021
(Animate CSS) animations for iOS. An easy to use library of iOS animations. As easy to use as an easy thing.

wobbly See Wobbly in action (examples) Add a drop of honey ?? to your project wobbly has a bunch of cool, fun, and easy to use iOS animations for you

Sagaya Abdulhafeez 150 Dec 23, 2021