From ccf73cbf43745b795fe1cca5fa79e55a965076d2 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Thu, 10 Oct 2024 23:54:30 -0700 Subject: [PATCH 01/16] Adopt Swift 6 and Xcode 16 --- .github/workflows/ci.yml | 125 ++---------- Gemfile.lock | 36 ++-- Package.swift | 22 ++- README.md | 20 +- Scripts/build.swift | 182 +++++------------- Scripts/github/prepare-simulators.sh | 20 -- Sources/Valet/Internal/Keychain.swift | 2 +- Sources/Valet/Internal/SecItem.swift | 8 +- Sources/Valet/Internal/WeakStorage.swift | 36 ++++ Sources/Valet/SecureEnclaveValet.swift | 16 +- .../SinglePromptSecureEnclaveValet.swift | 16 +- Sources/Valet/Valet.swift | 26 +-- .../Base.lproj/Main.storyboard | 13 +- Valet.podspec | 12 +- ...t watchOS Test Host App Watch App.xcscheme | 92 +++++++++ 15 files changed, 275 insertions(+), 351 deletions(-) delete mode 100755 Scripts/github/prepare-simulators.sh create mode 100644 Sources/Valet/Internal/WeakStorage.swift create mode 100644 Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS Test Host App Watch App.xcscheme diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcef53bc..2452bdc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,38 +11,15 @@ concurrency: cancel-in-progress: true jobs: - xcode-build-13: - name: Xcode 13 Build - runs-on: macOS-12 - strategy: - matrix: - platforms: [ - 'iOS_15', - 'tvOS_15', - ] - fail-fast: false - timeout-minutes: 30 - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Bundle Install - run: bundle install - - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_13.4.1.app/Contents/Developer - - name: Build and Test Framework - run: Scripts/build.swift ${{ matrix.platforms }} xcode - - name: Upload Coverage Reports - if: success() - run: Scripts/upload-coverage-reports.sh ${{ matrix.platforms }} - xcode-build-14: - name: Xcode 14 Build + xcode-build-16: + name: Xcode 16 Build runs-on: macOS-14 strategy: matrix: platforms: [ - 'iOS_16', - 'tvOS_16', - 'watchOS_9', + 'iOS_18', + 'tvOS_18', + 'watchOS_11', ] fail-fast: false timeout-minutes: 30 @@ -52,31 +29,7 @@ jobs: - name: Bundle Install run: bundle install - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer - - name: Build and Test Framework - run: Scripts/build.swift ${{ matrix.platforms }} xcode - - name: Upload Coverage Reports - if: success() - run: Scripts/upload-coverage-reports.sh ${{ matrix.platforms }} - xcode-build-15: - name: Xcode 15 Build - runs-on: macOS-14 - strategy: - matrix: - platforms: [ - 'iOS_17', - 'tvOS_17', - 'watchOS_10', - ] - fail-fast: false - timeout-minutes: 30 - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Bundle Install - run: bundle install - - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer + run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer - name: Build and Test Framework run: Scripts/build.swift ${{ matrix.platforms }} xcode - name: Upload Coverage Reports @@ -92,9 +45,9 @@ jobs: - name: Bundle Install run: bundle install - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer + run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer - name: Lint Podspec - run: bundle exec pod lib lint --verbose --fail-fast --swift-version=5.4 + run: bundle exec pod lib lint --verbose --fail-fast --swift-version=6 carthage: name: Carthage runs-on: macOS-14 @@ -105,69 +58,21 @@ jobs: - name: Bundle Install run: bundle install - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer + run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer - name: Install Carthage run: brew outdated carthage || brew upgrade carthage - name: Build Framework run: carthage build --verbose --no-skip-current --use-xcframeworks - spm-12: - name: SPM Build macOS 12 - runs-on: macOS-12 - strategy: - matrix: - platforms: [ - 'iOS_15', - 'tvOS_15', - 'watchOS_8', - 'macOS_12', - ] - fail-fast: false - timeout-minutes: 30 - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Bundle Install - run: bundle install - - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_13.4.1.app/Contents/Developer - - name: Prepare Simulator Runtimes - run: Scripts/github/prepare-simulators.sh ${{ matrix.platforms }} - - name: Build Framework - run: Scripts/build.swift ${{ matrix.platforms }} spm - spm-13: - name: SPM Build macOS 13 - runs-on: macOS-13 - strategy: - matrix: - platforms: [ - 'iOS_16', - 'tvOS_16', - 'watchOS_9', - 'macOS_13', - ] - fail-fast: false - timeout-minutes: 30 - steps: - - name: Checkout Repo - uses: actions/checkout@v4 - - name: Bundle Install - run: bundle install - - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_14.3.1.app/Contents/Developer - - name: Prepare Simulator Runtimes - run: Scripts/github/prepare-simulators.sh ${{ matrix.platforms }} - - name: Build Framework - run: Scripts/build.swift ${{ matrix.platforms }} spm spm-14: name: SPM Build macOS 14 runs-on: macOS-14 strategy: matrix: platforms: [ - 'iOS_17', - 'tvOS_17', - 'watchOS_10', - 'macOS_14', + 'iOS_18', + 'tvOS_18', + 'watchOS_11', + 'macOS_15', ] fail-fast: false timeout-minutes: 30 @@ -177,8 +82,6 @@ jobs: - name: Bundle Install run: bundle install - name: Select Xcode Version - run: sudo xcode-select --switch /Applications/Xcode_15.3.app/Contents/Developer - - name: Prepare Simulator Runtimes - run: Scripts/github/prepare-simulators.sh ${{ matrix.platforms }} + run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer - name: Build Framework run: Scripts/build.swift ${{ matrix.platforms }} spm diff --git a/Gemfile.lock b/Gemfile.lock index 55e5820a..d719bd6a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,24 +5,25 @@ GEM base64 nkf rexml - activesupport (7.1.3.2) + activesupport (7.2.1) base64 bigdecimal - concurrent-ruby (~> 1.0, >= 1.0.2) + concurrent-ruby (~> 1.0, >= 1.3.1) connection_pool (>= 2.2.5) drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) - mutex_m - tzinfo (~> 2.0) - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) json (>= 1.5.1) atomos (0.1.3) base64 (0.2.0) - bigdecimal (3.1.7) + bigdecimal (3.1.8) claide (1.1.0) cocoapods (1.15.2) addressable (~> 2.8) @@ -62,43 +63,42 @@ GEM netrc (~> 0.11) cocoapods-try (1.2.0) colored2 (3.1.2) - concurrent-ruby (1.2.3) + concurrent-ruby (1.3.4) connection_pool (2.4.1) drb (2.2.1) escape (0.0.4) ethon (0.16.0) ffi (>= 1.15.0) - ffi (1.16.3) + ffi (1.17.0) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) httpclient (2.8.3) - i18n (1.14.4) + i18n (1.14.6) concurrent-ruby (~> 1.0) json (2.7.2) - minitest (5.22.3) + logger (1.6.1) + minitest (5.25.1) molinillo (0.8.0) - mutex_m (0.2.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) nkf (0.2.0) public_suffix (4.0.7) - rexml (3.3.6) - strscan + rexml (3.3.8) ruby-macho (2.5.1) - strscan (3.1.0) + securerandom (0.3.1) typhoeus (1.4.1) ethon (>= 0.9.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - xcodeproj (1.25.0) + xcodeproj (1.25.1) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) colored2 (~> 3.1) nanaimo (~> 0.3.0) - rexml (>= 3.3.2, < 4.0) + rexml (>= 3.3.6, < 4.0) PLATFORMS ruby @@ -107,4 +107,4 @@ DEPENDENCIES cocoapods (~> 1.15.0) BUNDLED WITH - 2.3.7 + 2.5.16 diff --git a/Package.swift b/Package.swift index 10340a95..a8e25acf 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.4 +// swift-tools-version:6.0 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -6,20 +6,24 @@ import PackageDescription let package = Package( name: "Valet", platforms: [ - .iOS(.v9), - .tvOS(.v9), - .watchOS(.v2), - .macOS(.v10_11), + .iOS(.v12), + .tvOS(.v12), + .watchOS(.v4), + .macOS(.v10_13), ], products: [ .library( name: "Valet", - targets: ["Valet"]), + targets: ["Valet"] + ), ], targets: [ .target( name: "Valet", - dependencies: []), - ], - swiftLanguageVersions: [.v5] + dependencies: [], + swiftSettings: [ + .swiftLanguageMode(.v6), + ] + ), + ] ) diff --git a/README.md b/README.md index c0c51bd4..514b1e8a 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Install with [CocoaPods](http://cocoapods.org) by adding the following to your ` on iOS: ``` -platform :ios, '9.0' +platform :ios, '12.0' use_frameworks! pod 'Valet' ``` @@ -27,7 +27,7 @@ pod 'Valet' on tvOS: ``` -platform :tvos, '9.0' +platform :tvos, '12.0' use_frameworks! pod 'Valet' ``` @@ -35,7 +35,7 @@ pod 'Valet' on watchOS: ``` -platform :watchos, '2.0' +platform :watchos, '4.0' use_frameworks! pod 'Valet' ``` @@ -43,7 +43,7 @@ pod 'Valet' on macOS: ``` -platform :osx, '10.11' +platform :osx, '10.13' use_frameworks! pod 'Valet' ``` @@ -64,7 +64,7 @@ Install with [Swift Package Manager](https://github.com/apple/swift-package-mana ```swift dependencies: [ - .package(url: "https://github.com/Square/Valet", from: "4.0.0"), + .package(url: "https://github.com/Square/Valet", from: "5.0.0"), ], ``` @@ -239,11 +239,11 @@ Valet guarantees that reading and writing operations will succeed as long as wri ## Requirements -* Xcode 13.0 or later. -* iOS 9 or later. -* tvOS 9 or later. -* watchOS 2 or later. -* macOS 10.11 or later. +* Xcode 16.0 or later. +* iOS 12 or later. +* tvOS 12 or later. +* watchOS 4 or later. +* macOS 10.13 or later. ## Migrating from prior Valet versions diff --git a/Scripts/build.swift b/Scripts/build.swift index eade7424..0a04b18c 100755 --- a/Scripts/build.swift +++ b/Scripts/build.swift @@ -21,83 +21,40 @@ enum TaskError: Error { } enum Platform: String, CustomStringConvertible { - case iOS_15 - case iOS_16 - case iOS_17 - case tvOS_15 - case tvOS_16 - case tvOS_17 - case macOS_12 - case macOS_13 - case macOS_14 - case watchOS_8 - case watchOS_9 - case watchOS_10 + case iOS_18 + case tvOS_18 + case macOS_15 + case watchOS_11 var destination: String { switch self { - case .iOS_15: - return "platform=iOS Simulator,OS=15.5,name=iPad Pro (12.9-inch) (5th generation)" - case .iOS_16: - return "platform=iOS Simulator,OS=16.4,name=iPad Pro (12.9-inch) (6th generation)" - case .iOS_17: - return "platform=iOS Simulator,OS=17.4,name=iPad Pro (12.9-inch) (6th generation)" - - case .tvOS_15: - return "platform=tvOS Simulator,OS=15.4,name=Apple TV" - case .tvOS_16: - return "platform=tvOS Simulator,OS=16.4,name=Apple TV" - case .tvOS_17: - return "platform=tvOS Simulator,OS=17.4,name=Apple TV" - - case .macOS_12, - .macOS_13, - .macOS_14: - return "platform=OS X" - - case .watchOS_8: - return "OS=8.5,name=Apple Watch Series 6 - 44mm" - case .watchOS_9: - return "OS=9.4,name=Apple Watch Series 6 (44mm)" - case .watchOS_10: - return "OS=10.4,name=Apple Watch Series 9 (45mm)" + case .iOS_18: + "platform=iOS Simulator,OS=18.0,name=iPad Pro (12.9-inch) (6th generation)" + + case .tvOS_18: + "platform=tvOS Simulator,OS=18.0,name=Apple TV" + + case .macOS_15: + "platform=OS X" + + case .watchOS_11: + "OS=11.0,name=Apple Watch Series 9 (45mm)" } } var sdk: String { switch self { - case .iOS_15, - .iOS_16, - .iOS_17: - return "iphonesimulator" - - case .tvOS_15, - .tvOS_16, - .tvOS_17: - return "appletvsimulator" - - case .macOS_12: - return "macosx12.3" - case .macOS_13: - return "macosx13.3" - case .macOS_14: - return "macosx14.4" - - case .watchOS_8, - .watchOS_9, - .watchOS_10: - return "watchsimulator" - } - } + case .iOS_18: + "iphonesimulator" - /// Whether the platform's Xcode version requires modern SPM integration in xcodebuild, given the removal of generate-xcodeproj. - var requiresModernSPMIntegration: Bool { - switch self { - case .iOS_16, .tvOS_16, .watchOS_9, .macOS_13, - .iOS_17, .tvOS_17, .watchOS_10, .macOS_14: - return true - default: - return false + case .tvOS_18: + "appletvsimulator" + + case .macOS_15: + "macosx15.0" + + case .watchOS_11: + "watchsimulator" } } @@ -107,25 +64,17 @@ enum Platform: String, CustomStringConvertible { var scheme: String { switch self { - case .iOS_15, - .iOS_16, - .iOS_17: - return "Valet iOS" - - case .tvOS_15, - .tvOS_16, - .tvOS_17: - return "Valet tvOS" - - case .macOS_12, - .macOS_13, - .macOS_14: - return "Valet Mac" - - case .watchOS_8, - .watchOS_9, - .watchOS_10: - return "Valet watchOS" + case .iOS_18: + "Valet iOS" + + case .tvOS_18: + "Valet tvOS" + + case .macOS_15: + "Valet Mac" + + case .watchOS_11: + "Valet watchOS" } } @@ -142,63 +91,33 @@ enum Task: String, CustomStringConvertible { rawValue } - func project(for platform: Platform) -> String? { - if platform.requiresModernSPMIntegration { - return nil - } else { - switch self { - case .spm: - return "generated/Valet.xcodeproj" - case .xcode: - return "Valet.xcodeproj" - } - } - } - - func shouldGenerateXcodeProject(for platform: Platform) -> Bool { - if platform.requiresModernSPMIntegration { - return false - } else { - switch self { - case .spm: - return true - case .xcode: - return false - } - } - } - var shouldUseLegacyBuildSystem: Bool { switch self { case .spm: - return false + false case .xcode: // The new build system choked on our XCTest framework. // Once this project compiles with the new build system, // we can change this to false. - return true + true } } var configuration: String { switch self { case .spm: - return "Release" + "Release" case .xcode: - return "Debug" + "Debug" } } func scheme(for platform: Platform) -> String { switch self { case .spm: - if platform.requiresModernSPMIntegration { - return "Valet" - } else { - return "Valet-Package" - } + "Valet" case .xcode: - return platform.scheme + platform.scheme } } @@ -206,9 +125,9 @@ enum Task: String, CustomStringConvertible { switch self { case .spm: // Our Package isn't set up with unit test targets, because SPM can't run unit tests in a codesigned environment. - return false + false case .xcode: - return true + true } } } @@ -234,18 +153,12 @@ let platforms = try rawPlatforms.map { rawPlatform -> Platform in return platform } -// Only generate xcodeproj for SPM on platforms that require it. -let shouldGenerateXcodeproj = task == .spm && platforms.map { task.shouldGenerateXcodeProject(for: $0) }.contains(true) -if shouldGenerateXcodeproj { - try execute(commandPath: "/usr/bin/xcrun", arguments: ["/usr/bin/swift", "package", "generate-xcodeproj", "--output=generated/"]) -} - for platform in platforms { var deletedXcodeproj = false var xcodeBuildArguments: [String] = [] // If necessary, delete Valet.xcodeproj, otherwise xcodebuild won't generate the SPM scheme. // If deleted, the xcodeproj will be restored by git at the end of the loop. - if task == .spm && platform.requiresModernSPMIntegration { + if task == .spm { do { print("Deleting Valet.xcodeproj, any uncommitted changes will be lost.") try execute(commandPath: "/bin/rm", arguments: ["-r", "Valet.xcodeproj"]) @@ -256,11 +169,6 @@ for platform in platforms { } } - if let project = task.project(for: platform) { - xcodeBuildArguments.append("-project") - xcodeBuildArguments.append(project) - } - xcodeBuildArguments.append(contentsOf: [ "-scheme", task.scheme(for: platform), "-sdk", platform.sdk, diff --git a/Scripts/github/prepare-simulators.sh b/Scripts/github/prepare-simulators.sh deleted file mode 100755 index 13a31b38..00000000 --- a/Scripts/github/prepare-simulators.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash -l -set -ex - -IFS=','; PLATFORMS=$(echo $1); unset IFS - -sudo mkdir -p /Library/Developer/CoreSimulator/Profiles/Runtimes - -if [[ ${PLATFORMS[*]} =~ 'iOS_13' ]]; then - sudo ln -s /Applications/Xcode_11.7.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime /Library/Developer/CoreSimulator/Profiles/Runtimes/iOS\ 13.7.simruntime -fi - -if [[ ${PLATFORMS[*]} =~ 'tvOS_13' ]]; then - sudo ln -s /Applications/Xcode_11.7.app/Contents/Developer/Platforms/AppleTVOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS.simruntime /Library/Developer/CoreSimulator/Profiles/Runtimes/tvOS\ 13.4.simruntime -fi - -if [[ ${PLATFORMS[*]} =~ 'watchOS_6' ]]; then - sudo ln -s /Applications/Xcode_11.7.app/Contents/Developer/Platforms/WatchOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS.simruntime /Library/Developer/CoreSimulator/Profiles/Runtimes/watchOS\ 6.2.simruntime -fi - -xcrun simctl list runtimes diff --git a/Sources/Valet/Internal/Keychain.swift b/Sources/Valet/Internal/Keychain.swift index c7b0ae09..c117ad2b 100644 --- a/Sources/Valet/Internal/Keychain.swift +++ b/Sources/Valet/Internal/Keychain.swift @@ -81,7 +81,7 @@ internal final class Keychain { try setObject(data, forKey: key, options: options) } - internal static func setObject(_ object: Data, forKey key: String, options: [String: AnyHashable]) throws { + internal static func setObject(_ object: Data, forKey key: String, options: [String: AnyHashable]) throws(KeychainError) { guard !key.isEmpty else { throw KeychainError.emptyKey } diff --git a/Sources/Valet/Internal/SecItem.swift b/Sources/Valet/Internal/SecItem.swift index 92011364..3156bd3e 100644 --- a/Sources/Valet/Internal/SecItem.swift +++ b/Sources/Valet/Internal/SecItem.swift @@ -30,7 +30,7 @@ internal final class SecItem { // MARK: Internal Class Methods - internal static func copy(matching query: [String : AnyHashable]) throws -> DesiredType { + internal static func copy(matching query: [String : AnyHashable]) throws(KeychainError) -> DesiredType { if query.isEmpty { assertionFailure("Must provide a query with at least one item") } @@ -70,7 +70,7 @@ internal final class SecItem { return status } - internal static func add(attributes: [String : AnyHashable]) throws { + internal static func add(attributes: [String : AnyHashable]) throws(KeychainError) { if attributes.isEmpty { assertionFailure("Must provide attributes with at least one item") } @@ -90,7 +90,7 @@ internal final class SecItem { } } - internal static func update(attributes: [String : AnyHashable], forItemsMatching query: [String : AnyHashable]) throws { + internal static func update(attributes: [String : AnyHashable], forItemsMatching query: [String : AnyHashable]) throws(KeychainError) { if attributes.isEmpty { assertionFailure("Must provide attributes with at least one item") } @@ -113,7 +113,7 @@ internal final class SecItem { } } - internal static func deleteItems(matching query: [String : AnyHashable]) throws { + internal static func deleteItems(matching query: [String : AnyHashable]) throws(KeychainError) { if query.isEmpty { assertionFailure("Must provide a query with at least one item") } diff --git a/Sources/Valet/Internal/WeakStorage.swift b/Sources/Valet/Internal/WeakStorage.swift new file mode 100644 index 00000000..a6bf3d0a --- /dev/null +++ b/Sources/Valet/Internal/WeakStorage.swift @@ -0,0 +1,36 @@ +// Created by Dan Federman on 10/10/24. +// Copyright © 2024 Square, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +//    http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation + + +internal final class WeakStorage: @unchecked Sendable { + internal subscript(_ key: String) -> T? { + get { + lock.withLock { + identifierToValetMap.object(forKey: key as NSString) + } + } + set { + lock.withLock { + identifierToValetMap.setObject(newValue, forKey: key as NSString) + } + } + } + + private let lock = NSLock() + private let identifierToValetMap = NSMapTable.strongToWeakObjects() +} diff --git a/Sources/Valet/SecureEnclaveValet.swift b/Sources/Valet/SecureEnclaveValet.swift index e29624eb..efaf7a41 100644 --- a/Sources/Valet/SecureEnclaveValet.swift +++ b/Sources/Valet/SecureEnclaveValet.swift @@ -28,13 +28,13 @@ public final class SecureEnclaveValet: NSObject, Sendable { /// - accessControl: The desired access control for the SecureEnclaveValet. /// - Returns: A SecureEnclaveValet that reads/writes keychain elements with the desired flavor. public class func valet(with identifier: Identifier, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet { - let key = Service.standard(identifier, .secureEnclave(accessControl)).description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = Service.standard(identifier, .secureEnclave(accessControl)).description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = SecureEnclaveValet(identifier: identifier, accessControl: accessControl) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } @@ -44,13 +44,13 @@ public final class SecureEnclaveValet: NSObject, Sendable { /// - accessControl: The desired access control for the SecureEnclaveValet. /// - Returns: A SecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team. public class func sharedGroupValet(with groupIdentifier: SharedGroupIdentifier, identifier: Identifier? = nil, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet { - let key = Service.sharedGroup(groupIdentifier, identifier, .secureEnclave(accessControl)).description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = Service.sharedGroup(groupIdentifier, identifier, .secureEnclave(accessControl)).description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = SecureEnclaveValet(sharedAccess: groupIdentifier, identifier: identifier, accessControl: accessControl) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } @@ -64,8 +64,8 @@ public final class SecureEnclaveValet: NSObject, Sendable { // MARK: Private Class Properties - private static let identifierToValetMap = NSMapTable.strongToWeakObjects() - + private static let identifierToValetMap = WeakStorage() + // MARK: Initialization @available(*, unavailable) diff --git a/Sources/Valet/SinglePromptSecureEnclaveValet.swift b/Sources/Valet/SinglePromptSecureEnclaveValet.swift index 19eddd55..67dd0b64 100644 --- a/Sources/Valet/SinglePromptSecureEnclaveValet.swift +++ b/Sources/Valet/SinglePromptSecureEnclaveValet.swift @@ -34,13 +34,13 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable /// - accessControl: The desired access control for the SinglePromptSecureEnclaveValet. /// - Returns: A SinglePromptSecureEnclaveValet that reads/writes keychain elements with the desired flavor. public class func valet(with identifier: Identifier, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet { - let key = Service.standard(identifier, .singlePromptSecureEnclave(accessControl)).description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = Service.standard(identifier, .singlePromptSecureEnclave(accessControl)).description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = SinglePromptSecureEnclaveValet(identifier: identifier, accessControl: accessControl) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } @@ -50,13 +50,13 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable /// - accessControl: The desired access control for the SinglePromptSecureEnclaveValet. /// - Returns: A SinglePromptSecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team. public class func sharedGroupValet(with groupIdentifier: SharedGroupIdentifier, identifier: Identifier? = nil, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet { - let key = Service.sharedGroup(groupIdentifier, identifier, .singlePromptSecureEnclave(accessControl)).description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = Service.sharedGroup(groupIdentifier, identifier, .singlePromptSecureEnclave(accessControl)).description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = SinglePromptSecureEnclaveValet(sharedAccess: groupIdentifier, identifier: identifier, accessControl: accessControl) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } @@ -70,8 +70,8 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable // MARK: Private Class Properties - private static let identifierToValetMap = NSMapTable.strongToWeakObjects() - + private static let identifierToValetMap = WeakStorage() + // MARK: Initialization @available(*, unavailable) diff --git a/Sources/Valet/Valet.swift b/Sources/Valet/Valet.swift index 8063a600..060220f3 100644 --- a/Sources/Valet/Valet.swift +++ b/Sources/Valet/Valet.swift @@ -112,32 +112,32 @@ public final class Valet: NSObject, Sendable { // MARK: Private Class Properties - private static let identifierToValetMap = NSMapTable.strongToWeakObjects() + private static let identifierToValetMap = WeakStorage() // MARK: Private Class Functions private class func findOrCreate(_ identifier: Identifier, configuration: Configuration) -> Valet { let service: Service = .standard(identifier, configuration) - let key = service.description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = service.description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = Valet(identifier: identifier, configuration: configuration) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } private class func findOrCreate(_ groupIdentifier: SharedGroupIdentifier, identifier: Identifier?, configuration: Configuration) -> Valet { let service: Service = .sharedGroup(groupIdentifier, identifier, configuration) - let key = service.description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = service.description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = Valet(sharedAccess: groupIdentifier, identifier: identifier, configuration: configuration) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } @@ -146,26 +146,26 @@ public final class Valet: NSObject, Sendable { #if os(macOS) private class func findOrCreate(explicitlySet identifier: Identifier, configuration: Configuration) -> Valet { let service: Service = .standardOverride(service: identifier, configuration) - let key = service.description + configuration.description + configuration.accessibility.description + identifier.description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = service.description + configuration.description + configuration.accessibility.description + identifier.description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = Valet(overrideIdentifier: identifier, configuration: configuration) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } private class func findOrCreate(explicitlySet identifier: SharedGroupIdentifier, configuration: Configuration) -> Valet { let service: Service = .sharedGroupOverride(service: identifier, configuration) - let key = service.description + configuration.description + configuration.accessibility.description + identifier.description as NSString - if let existingValet = identifierToValetMap.object(forKey: key) { + let key = service.description + configuration.description + configuration.accessibility.description + identifier.description + if let existingValet = identifierToValetMap[key] { return existingValet } else { let valet = Valet(overrideSharedAccess: identifier, configuration: configuration) - identifierToValetMap.setObject(valet, forKey: key) + identifierToValetMap[key] = valet return valet } } diff --git a/Valet macOS Test Host App/Base.lproj/Main.storyboard b/Valet macOS Test Host App/Base.lproj/Main.storyboard index a5dc3484..b3ad4786 100644 --- a/Valet macOS Test Host App/Base.lproj/Main.storyboard +++ b/Valet macOS Test Host App/Base.lproj/Main.storyboard @@ -1,7 +1,8 @@ - - + + - + + @@ -618,7 +619,7 @@ - + @@ -683,12 +684,12 @@ - + - + diff --git a/Valet.podspec b/Valet.podspec index af35256d..f3cf3e18 100644 --- a/Valet.podspec +++ b/Valet.podspec @@ -1,19 +1,19 @@ Pod::Spec.new do |s| s.name = 'Valet' - s.version = '4.3.0' + s.version = '5.0.0' s.license = 'Apache License, Version 2.0' s.summary = 'Securely store data on iOS, tvOS, watchOS, or macOS without knowing a thing about how the Keychain works. It\'s easy. We promise.' s.homepage = 'https://github.com/square/Valet' s.authors = 'Square' s.source = { :git => 'https://github.com/square/Valet.git', :tag => s.version } - s.swift_version = '5.4' + s.swift_version = '6.0' s.source_files = 'Sources/Valet/**/*.{swift,h}' s.public_header_files = 'Sources/Valet/*.h' s.frameworks = 'Security' - s.ios.deployment_target = '9.0' - s.tvos.deployment_target = '9.0' - s.watchos.deployment_target = '2.0' - s.macos.deployment_target = '10.11' + s.ios.deployment_target = '12.0' + s.tvos.deployment_target = '12.0' + s.watchos.deployment_target = '4.0' + s.macos.deployment_target = '10.13' s.tvos.exclude_files = 'Sources/Valet/SinglePromptSecureEnclaveValet.swift' s.watchos.exclude_files = 'Sources/Valet/SinglePromptSecureEnclaveValet.swift' diff --git a/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS Test Host App Watch App.xcscheme b/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS Test Host App Watch App.xcscheme new file mode 100644 index 00000000..0b976a31 --- /dev/null +++ b/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS Test Host App Watch App.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 99196868c638e689055d4667ae421f64ff1bd99f Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Fri, 11 Oct 2024 00:00:16 -0700 Subject: [PATCH 02/16] Project updates --- .../Valet/SecureEnclaveAccessControl.swift | 40 +-- Valet.xcodeproj/project.pbxproj | 298 ++++++++++++++---- .../xcshareddata/xcschemes/Valet Mac.xcscheme | 28 +- .../Valet iOS Test Host App.xcscheme | 10 +- .../xcshareddata/xcschemes/Valet iOS.xcscheme | 2 +- .../Valet macOS Test Host App.xcscheme | 10 +- .../Valet tvOS Test Host App.xcscheme | 10 +- .../xcschemes/Valet tvOS.xcscheme | 24 +- ...t watchOS Test Host App Extension.xcscheme | 2 +- ...t watchOS Test Host App Watch App.xcscheme | 2 +- .../Valet watchOS Test Host App.xcscheme | 10 +- .../xcschemes/Valet watchOS.xcscheme | 2 +- .../xcschemes/ValetTouchIDTest.xcscheme | 10 +- 13 files changed, 291 insertions(+), 157 deletions(-) diff --git a/Sources/Valet/SecureEnclaveAccessControl.swift b/Sources/Valet/SecureEnclaveAccessControl.swift index 2b47d3b3..b1e80933 100644 --- a/Sources/Valet/SecureEnclaveAccessControl.swift +++ b/Sources/Valet/SecureEnclaveAccessControl.swift @@ -67,46 +67,30 @@ public enum SecureEnclaveAccessControl: Int, CustomStringConvertible, Equatable, internal var secAccessControl: SecAccessControlCreateFlags { switch self { case .userPresence: - return .userPresence + .userPresence case .biometricAny: - if #available(iOS 11.3, tvOS 11.3, watchOS 4.3, macOS 10.13.4, *) { - return .biometryAny - } else if #available(macOS 10.12.1, *) { - return .touchIDAny + if #available(watchOS 4.3, macOS 10.13.4, *) { + .biometryAny } else { - assertionFailure(".biometricAny requires macOS 10.12.1.") - return .userPresence + .touchIDAny } case .biometricCurrentSet: - if #available(iOS 11.3, tvOS 11.3, watchOS 4.3, macOS 10.13.4, *) { - return .biometryCurrentSet - } else if #available(macOS 10.12.1, *) { - return .touchIDCurrentSet + if #available(watchOS 4.3, macOS 10.13.4, *) { + .biometryCurrentSet } else { - assertionFailure(".biometricCurrentSet requires macOS 10.12.1.") - return .userPresence + .touchIDCurrentSet } case .devicePasscode: - if #available(macOS 10.11, *) { - return .devicePasscode - } else { - assertionFailure(".devicePasscode requires macOS 10.11.") - return .userPresence - } + .devicePasscode } } internal static func allValues() -> [SecureEnclaveAccessControl] { - var values: [SecureEnclaveAccessControl] = [ + [ .userPresence, - .devicePasscode + .devicePasscode, + .biometricAny, + .biometricCurrentSet, ] - if #available(macOS 10.12.1, *) { - values += [ - .biometricAny, - .biometricCurrentSet, - ] - } - return values } } diff --git a/Valet.xcodeproj/project.pbxproj b/Valet.xcodeproj/project.pbxproj index 264268ff..79acee92 100644 --- a/Valet.xcodeproj/project.pbxproj +++ b/Valet.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -198,6 +198,10 @@ 32E7115C2336B03800018E15 /* SinglePromptSecureEnclaveValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD7922A9CAAB00FC1142 /* SinglePromptSecureEnclaveValet.swift */; }; 32E7115E2336B90800018E15 /* SinglePromptSecureEnclaveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0A22A9C95500FC1142 /* SinglePromptSecureEnclaveTests.swift */; }; 32E7115F2336B98800018E15 /* VALLegacySinglePromptSecureEnclaveValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD623072C6E00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m */; }; + 32F51A662CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; + 32F51A672CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; + 32F51A682CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; + 32F51A692CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; 371150A81E2962D8004A45D4 /* ValetIOSTestHostAppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371150A71E2962D8004A45D4 /* ValetIOSTestHostAppDelegate.swift */; }; 371150AA1E2962D8004A45D4 /* ValetIOSTestHostViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371150A91E2962D8004A45D4 /* ValetIOSTestHostViewController.swift */; }; 371150AD1E2962D8004A45D4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 371150AB1E2962D8004A45D4 /* Main.storyboard */; }; @@ -540,6 +544,7 @@ 32C1ED1D224C85890063E91D /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/ValetSecureElementTestMain.storyboard; sourceTree = ""; }; 32DC88842475111D005A9BFA /* MigratableKeyValuePair.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigratableKeyValuePair.swift; sourceTree = ""; }; 32DC88912475BA5F005A9BFA /* KeychainIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainIntegrationTests.swift; sourceTree = ""; }; + 32F51A652CB9038D0034C42C /* WeakStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeakStorage.swift; sourceTree = ""; }; 371150A51E2962D8004A45D4 /* Valet iOS Test Host App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Valet iOS Test Host App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 371150A71E2962D8004A45D4 /* ValetIOSTestHostAppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetIOSTestHostAppDelegate.swift; sourceTree = ""; }; 371150A91E2962D8004A45D4 /* ValetIOSTestHostViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValetIOSTestHostViewController.swift; sourceTree = ""; }; @@ -748,6 +753,7 @@ 1612FD7222A9CAAB00FC1142 /* Service.swift */, 1612FD7322A9CAAB00FC1142 /* Keychain.swift */, 1612FD7422A9CAAB00FC1142 /* Configuration.swift */, + 32F51A652CB9038D0034C42C /* WeakStorage.swift */, ); path = Internal; sourceTree = ""; @@ -1357,8 +1363,9 @@ EA1E1F7B1A8C46080067C991 /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1530; - LastUpgradeCheck = 1020; + LastUpgradeCheck = 1600; ORGANIZATIONNAME = "Square, Inc."; TargetAttributes = { 1612FDD222A9CB2200FC1142 = { @@ -1468,10 +1475,9 @@ }; buildConfigurationList = EA1E1F7E1A8C46080067C991 /* Build configuration list for PBXProject "Valet" */; compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( - English, en, Base, ); @@ -1690,6 +1696,7 @@ files = ( 1693E29723B2D24600F8D97A /* KeychainError.swift in Sources */, 1612FDBB22A9CAAB00FC1142 /* SecureEnclaveValet.swift in Sources */, + 32F51A692CB9038D0034C42C /* WeakStorage.swift in Sources */, 1612FD8F22A9CAAB00FC1142 /* Valet.swift in Sources */, 1612FD9322A9CAAB00FC1142 /* SecItem.swift in Sources */, 32E7115B2336B03800018E15 /* SinglePromptSecureEnclaveValet.swift in Sources */, @@ -1737,6 +1744,7 @@ files = ( 1693E29823B2D24600F8D97A /* KeychainError.swift in Sources */, 1612FDBC22A9CAAB00FC1142 /* SecureEnclaveValet.swift in Sources */, + 32F51A672CB9038D0034C42C /* WeakStorage.swift in Sources */, 1612FD9022A9CAAB00FC1142 /* Valet.swift in Sources */, 1612FD9422A9CAAB00FC1142 /* SecItem.swift in Sources */, 32E7115C2336B03800018E15 /* SinglePromptSecureEnclaveValet.swift in Sources */, @@ -1760,6 +1768,7 @@ files = ( 16967AE82405CAF800DC2B2D /* SharedGroupIdentifier.swift in Sources */, 1693E29523B2D24600F8D97A /* KeychainError.swift in Sources */, + 32F51A682CB9038D0034C42C /* WeakStorage.swift in Sources */, 1612FDB922A9CAAB00FC1142 /* SecureEnclaveValet.swift in Sources */, 1612FD8D22A9CAAB00FC1142 /* Valet.swift in Sources */, 1612FD9122A9CAAB00FC1142 /* SecItem.swift in Sources */, @@ -1783,6 +1792,7 @@ files = ( 1693E29623B2D24600F8D97A /* KeychainError.swift in Sources */, 1612FDBA22A9CAAB00FC1142 /* SecureEnclaveValet.swift in Sources */, + 32F51A662CB9038D0034C42C /* WeakStorage.swift in Sources */, 1612FD8E22A9CAAB00FC1142 /* Valet.swift in Sources */, 1612FD9222A9CAAB00FC1142 /* SecItem.swift in Sources */, 1612FDBE22A9CAAB00FC1142 /* Identifier.swift in Sources */, @@ -2073,6 +2083,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; @@ -2081,10 +2092,17 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.LegacyValet; @@ -2105,6 +2123,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; @@ -2114,10 +2133,17 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.LegacyValet; PRODUCT_NAME = LegacyValet; @@ -2138,21 +2164,28 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.LegacyValet; @@ -2173,22 +2206,29 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_IDENTITY = "Mac Developer"; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_VERSION = A; GCC_C_LANGUAGE_STANDARD = gnu11; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.LegacyValet; PRODUCT_NAME = LegacyValet; @@ -2215,7 +2255,11 @@ DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Valet tvOS Test Host App/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-tvOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2223,7 +2267,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Debug; }; @@ -2244,13 +2288,17 @@ DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Valet tvOS Test Host App/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-tvOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; TARGETED_DEVICE_FAMILY = 3; - TVOS_DEPLOYMENT_TARGET = 9.0; + TVOS_DEPLOYMENT_TARGET = 12.0; }; name = Release; }; @@ -2266,12 +2314,17 @@ CODE_SIGN_IDENTITY = "Mac Developer"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Valet macOS Test Host App/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-macOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2294,12 +2347,17 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = ""; + ENABLE_HARDENED_RUNTIME = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "Valet macOS Test Host App/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.11; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-macOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2325,9 +2383,16 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; SKIP_INSTALL = YES; @@ -2358,9 +2423,16 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; SKIP_INSTALL = YES; @@ -2373,7 +2445,6 @@ 16C3B0A0204B1E4C00B4D0B4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -2385,7 +2456,12 @@ DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-tvOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2401,7 +2477,6 @@ 16C3B0A1204B1E4C00B4D0B4 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; @@ -2414,7 +2489,12 @@ DEVELOPMENT_TEAM = ""; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-tvOS-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2443,10 +2523,17 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.13; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = watchos; SKIP_INSTALL = YES; @@ -2455,6 +2542,7 @@ TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Debug; }; @@ -2477,16 +2565,24 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 10.13; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = watchos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = 4; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; + WATCHOS_DEPLOYMENT_TARGET = 4.0; }; name = Release; }; @@ -2495,7 +2591,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_IDENTITY = ""; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; @@ -2503,9 +2599,16 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_NO_COMMON_BLOCKS = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -2520,7 +2623,7 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + CODE_SIGN_IDENTITY = ""; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -2529,9 +2632,16 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; GCC_NO_COMMON_BLOCKS = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; SDKROOT = iphoneos; SKIP_INSTALL = YES; TARGETED_DEVICE_FAMILY = "1,2"; @@ -2549,16 +2659,24 @@ CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_VERSION = A; GCC_NO_COMMON_BLOCKS = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SKIP_INSTALL = YES; @@ -2578,16 +2696,24 @@ COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; FRAMEWORK_VERSION = A; GCC_NO_COMMON_BLOCKS = YES; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; SKIP_INSTALL = YES; @@ -2619,8 +2745,12 @@ INFOPLIST_KEY_CFBundleDisplayName = "Valet watchOS Test Host App"; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKWatchOnly = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -2661,8 +2791,12 @@ INFOPLIST_KEY_CFBundleDisplayName = "Valet watchOS Test Host App"; INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown"; INFOPLIST_KEY_WKWatchOnly = YES; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-watchOS-Tests.Valet-watchOS-Test-Host-App.watchkitapp"; @@ -2679,7 +2813,6 @@ 321C3FC72BD796D600AB3952 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2694,6 +2827,7 @@ GCC_C_LANGUAGE_STANDARD = gnu17; INFOPLIST_KEY_CFBundleDisplayName = "Valet watchOS Test Host App"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -2709,7 +2843,6 @@ 321C3FC82BD796D600AB3952 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -2725,6 +2858,7 @@ GCC_C_LANGUAGE_STANDARD = gnu17; INFOPLIST_KEY_CFBundleDisplayName = "Valet watchOS Test Host App"; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-watchOS-Tests.Valet-watchOS-Test-Host-App"; @@ -2752,6 +2886,7 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.4; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -2791,6 +2926,7 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 17.4; LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 11.0; MARKETING_VERSION = 1.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-watchOS-Tests.Valet-watchOS-Tests"; @@ -2822,7 +2958,11 @@ DEVELOPMENT_TEAM = ""; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Valet iOS Test Host App/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-iOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -2848,12 +2988,17 @@ DEVELOPMENT_TEAM = ""; GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Valet iOS Test Host App/Info.plist"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-iOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -2882,12 +3027,14 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)"; @@ -2907,14 +3054,14 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Sources/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.Valet; PRODUCT_NAME = Valet; - SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 9.0; + SWIFT_VERSION = 6.0; + TVOS_DEPLOYMENT_TARGET = 12.0; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; @@ -2943,12 +3090,14 @@ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = YES; + DEAD_CODE_STRIPPING = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; FRAMEWORK_SEARCH_PATHS = "$(SRCROOT)"; @@ -2961,14 +3110,15 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; INFOPLIST_FILE = Sources/Info.plist; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MACOSX_DEPLOYMENT_TARGET = 10.11; MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.Valet; PRODUCT_NAME = Valet; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 5.0; - TVOS_DEPLOYMENT_TARGET = 9.0; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 6.0; + TVOS_DEPLOYMENT_TARGET = 12.0; VALIDATE_PRODUCT = YES; WATCHOS_DEPLOYMENT_TARGET = 2.0; }; @@ -2977,7 +3127,6 @@ EA1E1F9B1A8C46090067C991 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; @@ -2987,7 +3136,12 @@ ); GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -3000,13 +3154,17 @@ EA1E1F9C1A8C46090067C991 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEVELOPMENT_TEAM = ""; GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -3018,9 +3176,9 @@ EAEAA8951B167A8700F7AA98 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = dwarf; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", @@ -3033,7 +3191,12 @@ ); GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -3045,10 +3208,10 @@ EAEAA8961B167A8700F7AA98 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CLANG_ENABLE_MODULES = YES; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; FRAMEWORK_SEARCH_PATHS = ( "$(DEVELOPER_FRAMEWORKS_DIR)", @@ -3057,7 +3220,12 @@ GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; @@ -3068,7 +3236,6 @@ EAF894951B053E0700EDAD6C /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -3081,7 +3248,11 @@ ); GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = ValetTouchIDTest/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.ValetTouchIDTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -3093,7 +3264,6 @@ EAF894961B053E0700EDAD6C /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -3103,7 +3273,11 @@ GCC_NO_COMMON_BLOCKS = YES; GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = ValetTouchIDTest/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = com.squareup.ValetTouchIDTestApp; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/Valet.xcodeproj/xcshareddata/xcschemes/Valet Mac.xcscheme b/Valet.xcodeproj/xcshareddata/xcschemes/Valet Mac.xcscheme index 83630da8..d866c2e6 100644 --- a/Valet.xcodeproj/xcshareddata/xcschemes/Valet Mac.xcscheme +++ b/Valet.xcodeproj/xcshareddata/xcschemes/Valet Mac.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + + + + @@ -40,17 +49,6 @@ - - - - - - - - - - - - + + - - - - - - + + - - - - - - + + - - + + + + @@ -53,17 +62,6 @@ - - - - - - - - @@ -44,7 +44,7 @@ @@ -67,7 +67,7 @@ runnableDebuggingMode = "0"> @@ -84,7 +84,7 @@ runnableDebuggingMode = "0"> diff --git a/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS.xcscheme b/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS.xcscheme index 8f5edfac..9c54f64a 100644 --- a/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS.xcscheme +++ b/Valet.xcodeproj/xcshareddata/xcschemes/Valet watchOS.xcscheme @@ -1,6 +1,6 @@ - - - - + + - - Date: Fri, 11 Oct 2024 00:38:24 -0700 Subject: [PATCH 03/16] More --- .github/workflows/ci.yml | 2 +- .../Public/VALLegacySecureEnclaveValet.h | 2 +- .../VALLegacySinglePromptSecureEnclaveValet.h | 2 +- .../Public/VALSynchronizableValet.h | 2 +- Sources/Valet/Internal/Keychain.swift | 12 +-- Sources/Valet/SecureEnclave.swift | 33 +++++-- Sources/Valet/SecureEnclaveValet.swift | 90 ++++++++++++++----- .../SinglePromptSecureEnclaveValet.swift | 8 +- Sources/Valet/Valet.swift | 80 ++++++++++------- ...reEnclaveBackwardsCompatibilityTests.swift | 2 +- ...reEnclaveBackwardsCompatibilityTests.swift | 2 +- .../SecureEnclaveIntegrationTests.swift | 29 ++++++ ...ePromptSecureEnclaveIntegrationTests.swift | 3 +- .../ValetIntegrationTests.swift | 3 +- .../SinglePromptSecureEnclaveTests.swift | 3 +- .../ValetIOSTestHostAppDelegate.swift | 2 +- Valet tvOS Test Host App/AppDelegate.swift | 2 +- .../ValetTouchIDTestAppDelegate.swift | 2 +- 18 files changed, 191 insertions(+), 88 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2452bdc5..7b6093df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,7 +47,7 @@ jobs: - name: Select Xcode Version run: sudo xcode-select --switch /Applications/Xcode_16.app/Contents/Developer - name: Lint Podspec - run: bundle exec pod lib lint --verbose --fail-fast --swift-version=6 + run: bundle exec pod lib lint --verbose --fail-fast --swift-version=6.0 carthage: name: Carthage runs-on: macOS-14 diff --git a/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h b/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h index 6116639e..38ee1403 100644 --- a/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h +++ b/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h @@ -14,7 +14,7 @@ // limitations under the License. // -#import "VALLegacyValet.h" +#import /// Compiler flag for building against an SDK where Secure Enclave is available. diff --git a/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h b/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h index 744dc07b..cf411963 100644 --- a/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h +++ b/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h @@ -14,7 +14,7 @@ // limitations under the License. // -#import "VALLegacySecureEnclaveValet.h" +#import /// Reads and writes keychain elements that are stored on the Secure Enclave (available on iOS 8.0 and later and macOS 10.11 and later) using accessibility attribute VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly. The first access of these keychain elements will require the user to confirm their presence via Touch ID or passcode entry. diff --git a/Sources/LegacyValet/Public/VALSynchronizableValet.h b/Sources/LegacyValet/Public/VALSynchronizableValet.h index 25fd28f2..6c7666a4 100644 --- a/Sources/LegacyValet/Public/VALSynchronizableValet.h +++ b/Sources/LegacyValet/Public/VALSynchronizableValet.h @@ -14,7 +14,7 @@ // limitations under the License. // -#import "VALLegacyValet.h" +#import /// Reads and writes keychain elements that are synchronized with iCloud (supported on devices on iOS 7.0.3 and later). Accessibility must not be scoped to this device. diff --git a/Sources/Valet/Internal/Keychain.swift b/Sources/Valet/Internal/Keychain.swift index c117ad2b..f35b24d2 100644 --- a/Sources/Valet/Internal/Keychain.swift +++ b/Sources/Valet/Internal/Keychain.swift @@ -52,7 +52,7 @@ internal final class Keychain { // MARK: Getters - internal static func string(forKey key: String, options: [String : AnyHashable]) throws -> String { + internal static func string(forKey key: String, options: [String : AnyHashable]) throws(KeychainError) -> String { let data = try object(forKey: key, options: options) if let string = String(data: data, encoding: .utf8) { return string @@ -61,7 +61,7 @@ internal final class Keychain { } } - internal static func object(forKey key: String, options: [String : AnyHashable]) throws -> Data { + internal static func object(forKey key: String, options: [String : AnyHashable]) throws(KeychainError) -> Data { guard !key.isEmpty else { throw KeychainError.emptyKey } @@ -76,7 +76,7 @@ internal final class Keychain { // MARK: Setters - internal static func setString(_ string: String, forKey key: String, options: [String: AnyHashable]) throws { + internal static func setString(_ string: String, forKey key: String, options: [String: AnyHashable]) throws(KeychainError) { let data = Data(string.utf8) try setObject(data, forKey: key, options: options) } @@ -110,7 +110,7 @@ internal final class Keychain { // MARK: Removal - internal static func removeObject(forKey key: String, options: [String : AnyHashable]) throws { + internal static func removeObject(forKey key: String, options: [String : AnyHashable]) throws(KeychainError) { guard !key.isEmpty else { throw KeychainError.emptyKey } @@ -121,7 +121,7 @@ internal final class Keychain { try SecItem.deleteItems(matching: secItemQuery) } - internal static func removeAllObjects(matching options: [String : AnyHashable]) throws { + internal static func removeAllObjects(matching options: [String : AnyHashable]) throws(KeychainError) { try SecItem.deleteItems(matching: options) } @@ -140,7 +140,7 @@ internal final class Keychain { // MARK: AllObjects - internal static func allKeys(options: [String: AnyHashable]) throws -> Set { + internal static func allKeys(options: [String: AnyHashable]) throws(KeychainError) -> Set { var secItemQuery = options secItemQuery[kSecMatchLimit as String] = kSecMatchLimitAll secItemQuery[kSecReturnAttributes as String] = true diff --git a/Sources/Valet/SecureEnclave.swift b/Sources/Valet/SecureEnclave.swift index 9e3b4e37..03a2e8b7 100644 --- a/Sources/Valet/SecureEnclave.swift +++ b/Sources/Valet/SecureEnclave.swift @@ -14,6 +14,9 @@ // limitations under the License. // +#if !os(tvOS) && canImport(LocalAuthentication) +import LocalAuthentication +#endif import Foundation @@ -50,7 +53,7 @@ public final class SecureEnclave: Sendable { /// - key: A key that can be used to retrieve the `object` from the keychain. /// - options: A base query used to scope the calls in the keychain. /// - Throws: An error of type `KeychainError`. - internal static func setObject(_ object: Data, forKey key: String, options: [String : AnyHashable]) throws { + internal static func setObject(_ object: Data, forKey key: String, options: [String : AnyHashable]) throws(KeychainError) { // Remove the key before trying to set it. This will prevent us from calling SecItemUpdate on an item stored on the Secure Enclave, which would cause iOS to prompt the user for authentication. try Keychain.removeObject(forKey: key, options: options) @@ -63,23 +66,30 @@ public final class SecureEnclave: Sendable { /// - options: A base query used to scope the calls in the keychain. /// - Returns: The data currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. - internal static func object(forKey key: String, withPrompt userPrompt: String, options: [String : AnyHashable]) throws -> Data { + internal static func object(forKey key: String, withPrompt userPrompt: String, options: [String : AnyHashable]) throws(KeychainError) -> Data { var secItemQuery = options +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) if !userPrompt.isEmpty { - secItemQuery[kSecUseOperationPrompt as String] = userPrompt + let context = LAContext() + context.localizedReason = userPrompt + secItemQuery[kSecUseAuthenticationContext as String] = context } - +#endif + return try Keychain.object(forKey: key, options: secItemQuery) } +#if !os(tvOS) && canImport(LocalAuthentication) /// - Parameters: /// - key: The key to look up in the keychain. /// - options: A base query used to scope the calls in the keychain. /// - Returns: `true` if a value has been set for the given key, `false` otherwise. /// - Throws: An error of type `KeychainError`. - internal static func containsObject(forKey key: String, options: [String : AnyHashable]) throws -> Bool { + internal static func containsObject(forKey key: String, options: [String : AnyHashable]) throws(KeychainError) -> Bool { var secItemQuery = options - secItemQuery[kSecUseAuthenticationUI as String] = kSecUseAuthenticationUIFail + let context = LAContext() + context.interactionNotAllowed = true + secItemQuery[kSecUseAuthenticationContext as String] = context let status = Keychain.performCopy(forKey: key, options: secItemQuery) switch status { @@ -93,13 +103,14 @@ public final class SecureEnclave: Sendable { throw KeychainError(status: status) } } +#endif /// - Parameters: /// - string: A String value to be inserted into the keychain. /// - key: A key that can be used to retrieve the `string` from the keychain. /// - options: A base query used to scope the calls in the keychain. /// - Throws: An error of type `KeychainError`. - internal static func setString(_ string: String, forKey key: String, options: [String : AnyHashable]) throws { + internal static func setString(_ string: String, forKey key: String, options: [String : AnyHashable]) throws(KeychainError) { // Remove the key before trying to set it. This will prevent us from calling SecItemUpdate on an item stored on the Secure Enclave, which would cause iOS to prompt the user for authentication. try Keychain.removeObject(forKey: key, options: options) @@ -112,11 +123,15 @@ public final class SecureEnclave: Sendable { /// - options: A base query used to scope the calls in the keychain. /// - Returns: The string currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. - internal static func string(forKey key: String, withPrompt userPrompt: String, options: [String : AnyHashable]) throws -> String { + internal static func string(forKey key: String, withPrompt userPrompt: String, options: [String : AnyHashable]) throws(KeychainError) -> String { var secItemQuery = options +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) if !userPrompt.isEmpty { - secItemQuery[kSecUseOperationPrompt as String] = userPrompt + let context = LAContext() + context.localizedReason = userPrompt + secItemQuery[kSecUseAuthenticationContext as String] = context } +#endif return try Keychain.string(forKey: key, options: secItemQuery) } diff --git a/Sources/Valet/SecureEnclaveValet.swift b/Sources/Valet/SecureEnclaveValet.swift index efaf7a41..49b86070 100644 --- a/Sources/Valet/SecureEnclaveValet.swift +++ b/Sources/Valet/SecureEnclaveValet.swift @@ -121,33 +121,55 @@ public final class SecureEnclaveValet: NSObject, Sendable { /// - Throws: An error of type `KeychainError`. /// - Important: Inserted data should be no larger than 4kb. @objc - public func setObject(_ object: Data, forKey key: String) throws { - try execute(in: lock) { - try SecureEnclave.setObject(object, forKey: key, options: baseKeychainQuery) + public func setObject(_ object: Data, forKey key: String) throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try SecureEnclave.setObject(object, forKey: key, options: baseKeychainQuery) } +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) /// - Parameters: /// - key: A key used to retrieve the desired object from the keychain. /// - userPrompt: The prompt displayed to the user in Apple's Face ID, Touch ID, or passcode entry UI. /// - Returns: The data currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. @objc - public func object(forKey key: String, withPrompt userPrompt: String) throws -> Data { - try execute(in: lock) { - try SecureEnclave.object(forKey: key, withPrompt: userPrompt, options: baseKeychainQuery) + public func object(forKey key: String, withPrompt userPrompt: String) throws(KeychainError) -> Data { + lock.lock() + defer { + lock.unlock() + } + return try SecureEnclave.object(forKey: key, withPrompt: userPrompt, options: baseKeychainQuery) + } +#else + /// - Parameter key: A key used to retrieve the desired object from the keychain. + /// - Returns: The data currently stored in the keychain for the provided key. + /// - Throws: An error of type `KeychainError`. + @objc + public func object(forKey key: String) throws(KeychainError) -> Data { + lock.lock() + defer { + lock.unlock() } + return try SecureEnclave.object(forKey: key, withPrompt: "", options: baseKeychainQuery) } +#endif +#if !os(tvOS) && canImport(LocalAuthentication) /// - Parameter key: The key to look up in the keychain. /// - Returns: `true` if a value has been set for the given key, `false` otherwise. /// - Throws: An error of type `KeychainError`. /// - Note: Will never prompt the user for Face ID, Touch ID, or password. - public func containsObject(forKey key: String) throws -> Bool { - try execute(in: lock) { - try SecureEnclave.containsObject(forKey: key, options: baseKeychainQuery) + public func containsObject(forKey key: String) throws(KeychainError) -> Bool { + lock.lock() + defer { + lock.unlock() } + return try SecureEnclave.containsObject(forKey: key, options: baseKeychainQuery) } +#endif /// - Parameters: /// - string: A String value to be inserted into the keychain. @@ -155,41 +177,63 @@ public final class SecureEnclaveValet: NSObject, Sendable { /// - Throws: An error of type `KeychainError`. /// - Important: Inserted data should be no larger than 4kb. @objc - public func setString(_ string: String, forKey key: String) throws { - try execute(in: lock) { - try SecureEnclave.setString(string, forKey: key, options: baseKeychainQuery) + public func setString(_ string: String, forKey key: String) throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try SecureEnclave.setString(string, forKey: key, options: baseKeychainQuery) } +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) /// - Parameters: /// - key: A key used to retrieve the desired object from the keychain. /// - userPrompt: The prompt displayed to the user in Apple's Face ID, Touch ID, or passcode entry UI. /// - Returns: The string currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. @objc - public func string(forKey key: String, withPrompt userPrompt: String) throws -> String { - try execute(in: lock) { - try SecureEnclave.string(forKey: key, withPrompt: userPrompt, options: baseKeychainQuery) + public func string(forKey key: String, withPrompt userPrompt: String) throws(KeychainError) -> String { + lock.lock() + defer { + lock.unlock() } + return try SecureEnclave.string(forKey: key, withPrompt: userPrompt, options: baseKeychainQuery) } - +#else + /// - Parameter key: A key used to retrieve the desired object from the keychain. + /// - Returns: The string currently stored in the keychain for the provided key. + /// - Throws: An error of type `KeychainError`. + @objc + public func string(forKey key: String) throws(KeychainError) -> String { + lock.lock() + defer { + lock.unlock() + } + return try SecureEnclave.string(forKey: key, withPrompt: "", options: baseKeychainQuery) + } +#endif + /// Removes a key/object pair from the keychain. /// - Parameter key: A key used to remove the desired object from the keychain. /// - Throws: An error of type `KeychainError`. @objc - public func removeObject(forKey key: String) throws { - try execute(in: lock) { - try Keychain.removeObject(forKey: key, options: baseKeychainQuery) + public func removeObject(forKey key: String) throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try Keychain.removeObject(forKey: key, options: baseKeychainQuery) } /// Removes all key/object pairs accessible by this Valet instance from the keychain. /// - Throws: An error of type `KeychainError`. @objc - public func removeAllObjects() throws { - try execute(in: lock) { - try Keychain.removeAllObjects(matching: baseKeychainQuery) + public func removeAllObjects() throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try Keychain.removeAllObjects(matching: baseKeychainQuery) } /// Migrates objects matching the input query into the receiving SecureEnclaveValet instance. @@ -284,6 +328,7 @@ extension SecureEnclaveValet { return sharedGroupValet(with: identifier, accessControl: accessControl) } +#if !os(tvOS) && canImport(LocalAuthentication) /// - Parameter key: The key to look up in the keychain. /// - Returns: `true` if a value has been set for the given key, `false` otherwise. Will return `false` if the keychain is not accessible. /// - Note: Will never prompt the user for Face ID, Touch ID, or password. @@ -295,5 +340,6 @@ extension SecureEnclaveValet { } return containsObject } +#endif } diff --git a/Sources/Valet/SinglePromptSecureEnclaveValet.swift b/Sources/Valet/SinglePromptSecureEnclaveValet.swift index 67dd0b64..baa9ad3d 100644 --- a/Sources/Valet/SinglePromptSecureEnclaveValet.swift +++ b/Sources/Valet/SinglePromptSecureEnclaveValet.swift @@ -14,9 +14,7 @@ // limitations under the License. // -// Xcode 13 and prior incorrectly say that LocalAuthentication is available on tvOS, so we have to check both as long as Xcode 13 and prior are supported. -// Xcode 14 moved the LAContext availability to watchOS 3, so only that version is explicitly annotated. -#if !os(tvOS) && canImport(LocalAuthentication) +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) import LocalAuthentication import Foundation @@ -196,7 +194,9 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable try execute(in: lock) { var secItemQuery = try continuedAuthenticationKeychainQuery() if !userPrompt.isEmpty { - secItemQuery[kSecUseOperationPrompt as String] = userPrompt + let context = LAContext() + context.localizedReason = userPrompt + secItemQuery[kSecUseAuthenticationContext as String] = context } return try Keychain.allKeys(options: secItemQuery) diff --git a/Sources/Valet/Valet.swift b/Sources/Valet/Valet.swift index 060220f3..a5802202 100644 --- a/Sources/Valet/Valet.swift +++ b/Sources/Valet/Valet.swift @@ -251,36 +251,42 @@ public final class Valet: NSObject, Sendable { /// - Throws: An error of type `KeychainError`. /// - Important: Inserted data should be no larger than 4kb. @objc - public func setObject(_ object: Data, forKey key: String) throws { - try execute(in: lock) { - try Keychain.setObject(object, forKey: key, options: baseKeychainQuery) + public func setObject(_ object: Data, forKey key: String) throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try Keychain.setObject(object, forKey: key, options: baseKeychainQuery) } /// - Parameter key: A key used to retrieve the desired object from the keychain. /// - Returns: The data currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. @objc - public func object(forKey key: String) throws -> Data { - try execute(in: lock) { - try Keychain.object(forKey: key, options: baseKeychainQuery) + public func object(forKey key: String) throws(KeychainError) -> Data { + lock.lock() + defer { + lock.unlock() } + return try Keychain.object(forKey: key, options: baseKeychainQuery) } /// - Parameter key: The key to look up in the keychain. /// - Returns: `true` if a value has been set for the given key, `false` otherwise. /// - Throws: An error of type `KeychainError`. - public func containsObject(forKey key: String) throws -> Bool { - try execute(in: lock) { - let status = Keychain.performCopy(forKey: key, options: baseKeychainQuery) - switch status { - case errSecSuccess: - return true - case errSecItemNotFound: - return false - default: - throw KeychainError(status: status) - } + public func containsObject(forKey key: String) throws(KeychainError) -> Bool { + lock.lock() + defer { + lock.unlock() + } + let status = Keychain.performCopy(forKey: key, options: baseKeychainQuery) + switch status { + case errSecSuccess: + return true + case errSecItemNotFound: + return false + default: + throw KeychainError(status: status) } } @@ -290,29 +296,35 @@ public final class Valet: NSObject, Sendable { /// - Throws: An error of type `KeychainError`. /// - Important: Inserted data should be no larger than 4kb. @objc - public func setString(_ string: String, forKey key: String) throws { - try execute(in: lock) { - try Keychain.setString(string, forKey: key, options: baseKeychainQuery) + public func setString(_ string: String, forKey key: String) throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try Keychain.setString(string, forKey: key, options: baseKeychainQuery) } /// - Parameter key: A key used to retrieve the desired object from the keychain. /// - Returns: The string currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. @objc - public func string(forKey key: String) throws -> String { - try execute(in: lock) { - try Keychain.string(forKey: key, options: baseKeychainQuery) + public func string(forKey key: String) throws(KeychainError) -> String { + lock.lock() + defer { + lock.unlock() } + return try Keychain.string(forKey: key, options: baseKeychainQuery) } /// - Returns: The set of all (String) keys currently stored in this Valet instance. If no items are found, will return an empty set. /// - Throws: An error of type `KeychainError`. @objc - public func allKeys() throws -> Set { - try execute(in: lock) { - try Keychain.allKeys(options: baseKeychainQuery) + public func allKeys() throws(KeychainError) -> Set { + lock.lock() + defer { + lock.unlock() } + return try Keychain.allKeys(options: baseKeychainQuery) } /// Removes a key/object pair from the keychain. @@ -320,19 +332,23 @@ public final class Valet: NSObject, Sendable { /// - Throws: An error of type `KeychainError`. /// - Note: No error is thrown if the `key` is not found in the keychain. @objc - public func removeObject(forKey key: String) throws { - try execute(in: lock) { - try Keychain.removeObject(forKey: key, options: baseKeychainQuery) + public func removeObject(forKey key: String) throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try Keychain.removeObject(forKey: key, options: baseKeychainQuery) } /// Removes all key/object pairs accessible by this Valet instance from the keychain. /// - Throws: An error of type `KeychainError`. @objc - public func removeAllObjects() throws { - try execute(in: lock) { - try Keychain.removeAllObjects(matching: baseKeychainQuery) + public func removeAllObjects() throws(KeychainError) { + lock.lock() + defer { + lock.unlock() } + return try Keychain.removeAllObjects(matching: baseKeychainQuery) } /// Migrates objects matching the input query into the receiving Valet instance. diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift index 5f428b36..c3b653ad 100644 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift +++ b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift @@ -22,7 +22,7 @@ import XCTest extension SecureEnclaveIntegrationTests { - @available (*, deprecated) + @available(*, deprecated) func test_backwardsCompatibility_withLegacyValet() throws { guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() && testEnvironmentSupportsWhenPasscodeSet() else { diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift index 8d394e69..9126daac 100644 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift +++ b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift @@ -23,7 +23,7 @@ import XCTest @available(tvOS 11.0, *) extension SinglePromptSecureEnclaveIntegrationTests { - @available (*, deprecated) + @available(*, deprecated) func test_backwardsCompatibility_withLegacyValet() throws { guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() && testEnvironmentSupportsWhenPasscodeSet() else { diff --git a/Tests/ValetIntegrationTests/SecureEnclaveIntegrationTests.swift b/Tests/ValetIntegrationTests/SecureEnclaveIntegrationTests.swift index 38bb4a2b..5cf4cefc 100644 --- a/Tests/ValetIntegrationTests/SecureEnclaveIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/SecureEnclaveIntegrationTests.swift @@ -52,7 +52,11 @@ class SecureEnclaveIntegrationTests: XCTestCase try valet.setString(passcode, forKey: key) let equivalentValet = SecureEnclaveValet.valet(with: valet.identifier, accessControl: valet.accessControl) XCTAssertEqual(valet, equivalentValet) +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) XCTAssertEqual(passcode, try equivalentValet.string(forKey: key, withPrompt: "")) +#else + XCTAssertEqual(passcode, try equivalentValet.string(forKey: key)) +#endif } func test_secureEnclaveValetsWithDifferingAccessControl_canNotAccessSameData() throws @@ -64,10 +68,17 @@ class SecureEnclaveIntegrationTests: XCTestCase try valet.setString(passcode, forKey: key) let similarValet = SecureEnclaveValet.valet(with: valet.identifier, accessControl: .devicePasscode) XCTAssertNotEqual(valet, similarValet) +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) XCTAssertEqual(passcode, try valet.string(forKey: key, withPrompt: "")) XCTAssertThrowsError(try similarValet.string(forKey: key, withPrompt: "")) { error in XCTAssertEqual(error as? KeychainError, .itemNotFound) } +#else + XCTAssertEqual(passcode, try valet.string(forKey: key)) + XCTAssertThrowsError(try similarValet.string(forKey: key)) { error in + XCTAssertEqual(error as? KeychainError, .itemNotFound) + } +#endif } func test_secureEnclaveSharedGroupValetsWithDifferingIdentifiers_canNotAccessSameData() throws @@ -82,10 +93,17 @@ class SecureEnclaveIntegrationTests: XCTestCase try valet1.setString(passcode, forKey: key) XCTAssertNotEqual(valet1, valet2) +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) XCTAssertEqual(passcode, try valet1.string(forKey: key, withPrompt: "")) XCTAssertThrowsError(try valet2.string(forKey: key, withPrompt: "")) { error in XCTAssertEqual(error as? KeychainError, .itemNotFound) } +#else + XCTAssertEqual(passcode, try valet1.string(forKey: key)) + XCTAssertThrowsError(try valet2.string(forKey: key)) { error in + XCTAssertEqual(error as? KeychainError, .itemNotFound) + } +#endif } // MARK: canAccessKeychain @@ -180,10 +198,17 @@ class SecureEnclaveIntegrationTests: XCTestCase try valet.migrateObjects(from: plainOldValet, removeOnCompletion: true) for (key, value) in keyValuePairs { +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) XCTAssertEqual(value, try valet.string(forKey: key, withPrompt: "")) XCTAssertThrowsError(try plainOldValet.string(forKey: key)) { error in XCTAssertEqual(error as? KeychainError, .itemNotFound) } +#else + XCTAssertEqual(value, try valet.string(forKey: key)) + XCTAssertThrowsError(try plainOldValet.string(forKey: key)) { error in + XCTAssertEqual(error as? KeychainError, .itemNotFound) + } +#endif } // Clean up items for the next test run (allKeys and removeAllObjects are unsupported in VALSecureEnclaveValet). @@ -213,7 +238,11 @@ class SecureEnclaveIntegrationTests: XCTestCase try valet.migrateObjects(from: otherValet, removeOnCompletion: false) for (key, value) in keyStringPairToMigrateMap { +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) XCTAssertEqual(try valet.string(forKey: key, withPrompt: ""), value) +#else + XCTAssertEqual(try valet.string(forKey: key), value) +#endif XCTAssertEqual(try otherValet.string(forKey: key), value) } } diff --git a/Tests/ValetIntegrationTests/SinglePromptSecureEnclaveIntegrationTests.swift b/Tests/ValetIntegrationTests/SinglePromptSecureEnclaveIntegrationTests.swift index 4f803049..c9b6d3be 100644 --- a/Tests/ValetIntegrationTests/SinglePromptSecureEnclaveIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/SinglePromptSecureEnclaveIntegrationTests.swift @@ -18,8 +18,7 @@ import Foundation @testable import Valet import XCTest -// Xcode 13 and prior incorrectly say that LocalAuthentication is available on tvOS, so we have to check both as long as Xcode 13 and prior are supported. -#if !os(tvOS) && canImport(LocalAuthentication) +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) class SinglePromptSecureEnclaveIntegrationTests: XCTestCase { diff --git a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift index 2d5e81f7..432c7a60 100644 --- a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift @@ -629,7 +629,7 @@ class ValetIntegrationTests: XCTestCase func test_stringForKey_failsForDataNotBackedByString() throws { try allPermutations.forEach { valet in let dictionary = [ "that's no" : "moon" ] - let nonStringData = NSKeyedArchiver.archivedData(withRootObject: dictionary) + let nonStringData = try NSKeyedArchiver.archivedData(withRootObject: dictionary, requiringSecureCoding: false) try valet.setObject(nonStringData, forKey: key) XCTAssertThrowsError(try valet.string(forKey: key)) { error in XCTAssertEqual(error as? KeychainError, .itemNotFound) @@ -724,7 +724,6 @@ class ValetIntegrationTests: XCTestCase try backgroundValet.setString(self.passcode, forKey: self.key) } catch { XCTFail("Threw \(error) trying to write value") - expectation.fulfill() } stringForKeyQueue.async { do { diff --git a/Tests/ValetTests/SinglePromptSecureEnclaveTests.swift b/Tests/ValetTests/SinglePromptSecureEnclaveTests.swift index 10fcf39d..dd3c3d85 100644 --- a/Tests/ValetTests/SinglePromptSecureEnclaveTests.swift +++ b/Tests/ValetTests/SinglePromptSecureEnclaveTests.swift @@ -14,8 +14,7 @@ // limitations under the License. // -// Xcode 13 and prior incorrectly say that LocalAuthentication is available on tvOS, so we have to check both as long as Xcode 13 and prior are supported. -#if !os(tvOS) && canImport(LocalAuthentication) +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) import Foundation @testable import Valet diff --git a/Valet iOS Test Host App/ValetIOSTestHostAppDelegate.swift b/Valet iOS Test Host App/ValetIOSTestHostAppDelegate.swift index 2b28cbf5..4fee8df0 100644 --- a/Valet iOS Test Host App/ValetIOSTestHostAppDelegate.swift +++ b/Valet iOS Test Host App/ValetIOSTestHostAppDelegate.swift @@ -17,7 +17,7 @@ import UIKit -@UIApplicationMain +@main final class ValetIOSTestHostAppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? } diff --git a/Valet tvOS Test Host App/AppDelegate.swift b/Valet tvOS Test Host App/AppDelegate.swift index 32d79a07..1c3e54d9 100644 --- a/Valet tvOS Test Host App/AppDelegate.swift +++ b/Valet tvOS Test Host App/AppDelegate.swift @@ -16,7 +16,7 @@ import UIKit -@UIApplicationMain +@main final class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? } diff --git a/ValetTouchIDTest/ValetTouchIDTestAppDelegate.swift b/ValetTouchIDTest/ValetTouchIDTestAppDelegate.swift index f312940f..389edc06 100644 --- a/ValetTouchIDTest/ValetTouchIDTestAppDelegate.swift +++ b/ValetTouchIDTest/ValetTouchIDTestAppDelegate.swift @@ -17,7 +17,7 @@ import UIKit -@UIApplicationMain +@main final class ValetTouchIDTestAppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? } From 3bc8b654f43c285131c0dbb2a02e3e3ce71d64b1 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Fri, 11 Oct 2024 00:59:15 -0700 Subject: [PATCH 04/16] Share context --- Sources/Valet/SecureEnclave.swift | 55 +++++++++++++++---- Sources/Valet/SecureEnclaveValet.swift | 8 +-- .../SinglePromptSecureEnclaveValet.swift | 4 +- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/Sources/Valet/SecureEnclave.swift b/Sources/Valet/SecureEnclave.swift index 03a2e8b7..e0f36e02 100644 --- a/Sources/Valet/SecureEnclave.swift +++ b/Sources/Valet/SecureEnclave.swift @@ -60,24 +60,41 @@ public final class SecureEnclave: Sendable { try Keychain.setObject(object, forKey: key, options: options) } +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) /// - Parameters: /// - key: A key used to retrieve the desired object from the keychain. /// - userPrompt: The prompt displayed to the user in Apple's Face ID, Touch ID, or passcode entry UI. + /// - context: The context to use for the query. /// - options: A base query used to scope the calls in the keychain. /// - Returns: The data currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. - internal static func object(forKey key: String, withPrompt userPrompt: String, options: [String : AnyHashable]) throws(KeychainError) -> Data { + internal static func object( + forKey key: String, + withPrompt userPrompt: String, + context: LAContext?, + options: [String : AnyHashable] + ) throws(KeychainError) -> Data { var secItemQuery = options -#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) if !userPrompt.isEmpty { - let context = LAContext() + let context = context ?? LAContext() context.localizedReason = userPrompt secItemQuery[kSecUseAuthenticationContext as String] = context } -#endif - return try Keychain.object(forKey: key, options: secItemQuery) } +#else + /// - Parameters: + /// - key: A key used to retrieve the desired object from the keychain. + /// - options: A base query used to scope the calls in the keychain. + /// - Returns: The data currently stored in the keychain for the provided key. + /// - Throws: An error of type `KeychainError`. + internal static func object( + forKey key: String, + options: [String : AnyHashable] + ) throws(KeychainError) -> Data { + try Keychain.object(forKey: key, options: options) + } +#endif #if !os(tvOS) && canImport(LocalAuthentication) /// - Parameters: @@ -117,22 +134,40 @@ public final class SecureEnclave: Sendable { try Keychain.setString(string, forKey: key, options: options) } +#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) /// - Parameters: /// - key: A key used to retrieve the desired object from the keychain. /// - userPrompt: The prompt displayed to the user in Apple's Face ID, Touch ID, or passcode entry UI. + /// - context: The context to use for the query. /// - options: A base query used to scope the calls in the keychain. /// - Returns: The string currently stored in the keychain for the provided key. /// - Throws: An error of type `KeychainError`. - internal static func string(forKey key: String, withPrompt userPrompt: String, options: [String : AnyHashable]) throws(KeychainError) -> String { + internal static func string( + forKey key: String, + withPrompt userPrompt: String, + context: LAContext?, + options: [String : AnyHashable] + ) throws(KeychainError) -> String { var secItemQuery = options -#if !os(tvOS) && !os(watchOS) && canImport(LocalAuthentication) if !userPrompt.isEmpty { - let context = LAContext() + let context = context ?? LAContext() context.localizedReason = userPrompt secItemQuery[kSecUseAuthenticationContext as String] = context } -#endif - return try Keychain.string(forKey: key, options: secItemQuery) } +#else + /// - Parameters: + /// - key: A key used to retrieve the desired object from the keychain. + /// - options: A base query used to scope the calls in the keychain. + /// - Returns: The string currently stored in the keychain for the provided key. + /// - Throws: An error of type `KeychainError`. + internal static func string( + forKey key: String, + options: [String : AnyHashable] + ) throws(KeychainError) -> String { + try Keychain.string(forKey: key, options: options) + } + +#endif } diff --git a/Sources/Valet/SecureEnclaveValet.swift b/Sources/Valet/SecureEnclaveValet.swift index 49b86070..840e07bd 100644 --- a/Sources/Valet/SecureEnclaveValet.swift +++ b/Sources/Valet/SecureEnclaveValet.swift @@ -141,7 +141,7 @@ public final class SecureEnclaveValet: NSObject, Sendable { defer { lock.unlock() } - return try SecureEnclave.object(forKey: key, withPrompt: userPrompt, options: baseKeychainQuery) + return try SecureEnclave.object(forKey: key, withPrompt: userPrompt, context: nil, options: baseKeychainQuery) } #else /// - Parameter key: A key used to retrieve the desired object from the keychain. @@ -153,7 +153,7 @@ public final class SecureEnclaveValet: NSObject, Sendable { defer { lock.unlock() } - return try SecureEnclave.object(forKey: key, withPrompt: "", options: baseKeychainQuery) + return try SecureEnclave.object(forKey: key, options: baseKeychainQuery) } #endif @@ -197,7 +197,7 @@ public final class SecureEnclaveValet: NSObject, Sendable { defer { lock.unlock() } - return try SecureEnclave.string(forKey: key, withPrompt: userPrompt, options: baseKeychainQuery) + return try SecureEnclave.string(forKey: key, withPrompt: userPrompt, context: nil, options: baseKeychainQuery) } #else /// - Parameter key: A key used to retrieve the desired object from the keychain. @@ -209,7 +209,7 @@ public final class SecureEnclaveValet: NSObject, Sendable { defer { lock.unlock() } - return try SecureEnclave.string(forKey: key, withPrompt: "", options: baseKeychainQuery) + return try SecureEnclave.string(forKey: key, options: baseKeychainQuery) } #endif diff --git a/Sources/Valet/SinglePromptSecureEnclaveValet.swift b/Sources/Valet/SinglePromptSecureEnclaveValet.swift index baa9ad3d..6aa6b26b 100644 --- a/Sources/Valet/SinglePromptSecureEnclaveValet.swift +++ b/Sources/Valet/SinglePromptSecureEnclaveValet.swift @@ -139,7 +139,7 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable @objc public func object(forKey key: String, withPrompt userPrompt: String) throws -> Data { try execute(in: lock) { - try SecureEnclave.object(forKey: key, withPrompt: userPrompt, options: try continuedAuthenticationKeychainQuery()) + try SecureEnclave.object(forKey: key, withPrompt: userPrompt, context: localAuthenticationContext, options: try continuedAuthenticationKeychainQuery()) } } @@ -173,7 +173,7 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable @objc public func string(forKey key: String, withPrompt userPrompt: String) throws -> String { try execute(in: lock) { - try SecureEnclave.string(forKey: key, withPrompt: userPrompt, options: try continuedAuthenticationKeychainQuery()) + try SecureEnclave.string(forKey: key, withPrompt: userPrompt, context: localAuthenticationContext, options: try continuedAuthenticationKeychainQuery()) } } From 398a17370193786dddabf4097a2541b120af75a7 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Fri, 11 Oct 2024 01:15:16 -0700 Subject: [PATCH 05/16] Everything to Swift 6 --- Valet.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Valet.xcodeproj/project.pbxproj b/Valet.xcodeproj/project.pbxproj index 79acee92..ea5f1744 100644 --- a/Valet.xcodeproj/project.pbxproj +++ b/Valet.xcodeproj/project.pbxproj @@ -2468,7 +2468,7 @@ SDKROOT = appletvos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 3; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet tvOS Test Host App.app/Valet tvOS Test Host App"; }; @@ -2499,7 +2499,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = appletvos; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 3; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet tvOS Test Host App.app/Valet tvOS Test Host App"; }; @@ -2761,7 +2761,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; @@ -2804,7 +2804,7 @@ SDKROOT = watchos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 4; WATCHOS_DEPLOYMENT_TARGET = 7.0; }; @@ -2836,7 +2836,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -2864,7 +2864,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.Valet-watchOS-Tests.Valet-watchOS-Test-Host-App"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Release; }; @@ -2900,7 +2900,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 4; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet watchOS Test Host App Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Valet watchOS Test Host App Watch App"; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -2937,7 +2937,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 4; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet watchOS Test Host App Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Valet watchOS Test Host App Watch App"; WATCHOS_DEPLOYMENT_TARGET = 4.0; @@ -3146,7 +3146,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet iOS Test Host App.app/Valet iOS Test Host App"; }; name = Debug; @@ -3168,7 +3168,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet iOS Test Host App.app/Valet iOS Test Host App"; }; name = Release; @@ -3201,7 +3201,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -3229,7 +3229,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "com.squareup.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; - SWIFT_VERSION = 5.0; + SWIFT_VERSION = 6.0; }; name = Release; }; From 845f2e3c4bc196dff1e62a8f065a227304c9ff6e Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Fri, 11 Oct 2024 01:25:38 -0700 Subject: [PATCH 06/16] Get tests on Swift 6 --- .../ValetIntegrationTests.swift | 59 +++++++++++-------- Valet.xcodeproj/project.pbxproj | 8 ++- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift index 432c7a60..eb739cf0 100644 --- a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift @@ -70,7 +70,7 @@ internal extension Valet { // MARK: Shared Access Group - static var sharedAccessGroupIdentifier: SharedGroupIdentifier = { + static let sharedAccessGroupIdentifier: SharedGroupIdentifier = { #if os(iOS) return SharedGroupIdentifier(appIDPrefix: "9XUJ7M53NG", nonEmptyGroup: "com.squareup.Valet-iOS-Test-Host-App")! #elseif os(macOS) @@ -84,7 +84,7 @@ internal extension Valet { #endif }() - static var sharedAccessGroupIdentifier2: SharedGroupIdentifier = { + static let sharedAccessGroupIdentifier2: SharedGroupIdentifier = { #if os(iOS) return SharedGroupIdentifier(appIDPrefix: "9XUJ7M53NG", nonEmptyGroup: "com.squareup.Valet-iOS-Test-Host-App2")! #elseif os(macOS) @@ -100,7 +100,7 @@ internal extension Valet { // MARK: Shared App Group - static var sharedAppGroupIdentifier: SharedGroupIdentifier = { + static let sharedAppGroupIdentifier: SharedGroupIdentifier = { #if os(iOS) return SharedGroupIdentifier(groupPrefix: "group", nonEmptyGroup: "valet.test")! #elseif os(macOS) @@ -145,9 +145,9 @@ class ValetIntegrationTests: XCTestCase // MARK: XCTestCase - override func setUp() + override func setUp() async throws { - super.setUp() + try await super.setUp() let permutations: [Valet] if testEnvironmentIsSignedOrDoesNotRequireEntitlement() { @@ -646,22 +646,22 @@ class ValetIntegrationTests: XCTestCase // MARK: Concurrency - func test_concurrentSetAndRemoveOperations() + func test_concurrentSetAndRemoveOperations() async { let setQueue = DispatchQueue(label: "Set String Queue", attributes: .concurrent) let removeQueue = DispatchQueue(label: "Remove Object Queue", attributes: .concurrent) for _ in 1...50 { - setQueue.async { + setQueue.async { [vanillaValet, passcode, key] in do { - try self.vanillaValet.setString(self.passcode, forKey: self.key) + try vanillaValet.setString(passcode, forKey: key) } catch { XCTFail("Threw \(error) trying to write value") } } - removeQueue.async { + removeQueue.async { [vanillaValet, key] in do { - try self.vanillaValet.removeObject(forKey: self.key) + try vanillaValet.removeObject(forKey: key) } catch { XCTFail("Threw \(error) trying to remove value") } @@ -677,28 +677,31 @@ class ValetIntegrationTests: XCTestCase removeQueue.async(flags: .barrier) { removeQueueExpectation.fulfill() } - - waitForExpectations(timeout: 10.0, handler: nil) + + await fulfillment(of: [ + setQueueExpectation, + removeQueueExpectation, + ]) } - func test_stringForKey_canReadDataWrittenOnAnotherThread() + func test_stringForKey_canReadDataWrittenOnAnotherThread() async { let setStringQueue = DispatchQueue(label: "Set String Queue", attributes: .concurrent) let stringForKeyQueue = DispatchQueue(label: "String For Key Queue", attributes: .concurrent) let expectation = self.expectation(description: #function) - setStringQueue.async { + setStringQueue.async { [vanillaValet, passcode, key] in do { - try self.vanillaValet.setString(self.passcode, forKey: self.key) + try vanillaValet.setString(passcode, forKey: key) } catch { XCTFail("Threw \(error) trying to set value") } - stringForKeyQueue.async { + stringForKeyQueue.async { [vanillaValet, passcode, key] in do { - let stringForKey = try self.vanillaValet.string(forKey: self.key) - XCTAssertEqual(stringForKey, self.passcode) + let stringForKey = try vanillaValet.string(forKey: key) + XCTAssertEqual(stringForKey, passcode) } catch { XCTFail("Threw \(error) trying to read value") } @@ -707,10 +710,12 @@ class ValetIntegrationTests: XCTestCase } } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [ + expectation, + ]) } - func test_stringForKey_canReadDataWrittenToValetAllocatedOnDifferentThread() + func test_stringForKey_canReadDataWrittenToValetAllocatedOnDifferentThread() async { let setStringQueue = DispatchQueue(label: "Set String Queue", attributes: .concurrent) let stringForKeyQueue = DispatchQueue(label: "String For Key Queue", attributes: .concurrent) @@ -718,17 +723,17 @@ class ValetIntegrationTests: XCTestCase let backgroundIdentifier = Identifier(nonEmpty: "valet_background_testing")! let expectation = self.expectation(description: #function) - setStringQueue.async { + setStringQueue.async { [key, passcode] in let backgroundValet = Valet.valet(with: backgroundIdentifier, accessibility: .whenUnlocked) do { - try backgroundValet.setString(self.passcode, forKey: self.key) + try backgroundValet.setString(passcode, forKey: key) } catch { XCTFail("Threw \(error) trying to write value") } - stringForKeyQueue.async { + stringForKeyQueue.async { [key, passcode] in do { - let stringForKey = try backgroundValet.string(forKey: self.key) - XCTAssertEqual(stringForKey, self.passcode) + let stringForKey = try backgroundValet.string(forKey: key) + XCTAssertEqual(stringForKey, passcode) expectation.fulfill() } catch { XCTFail("Threw \(error) trying to read value") @@ -737,7 +742,9 @@ class ValetIntegrationTests: XCTestCase } } - waitForExpectations(timeout: 5.0, handler: nil) + await fulfillment(of: [ + expectation, + ]) } // MARK: Removal diff --git a/Valet.xcodeproj/project.pbxproj b/Valet.xcodeproj/project.pbxproj index ea5f1744..ae3643f1 100644 --- a/Valet.xcodeproj/project.pbxproj +++ b/Valet.xcodeproj/project.pbxproj @@ -2471,6 +2471,7 @@ SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 3; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet tvOS Test Host App.app/Valet tvOS Test Host App"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Debug; }; @@ -2502,6 +2503,7 @@ SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 3; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet tvOS Test Host App.app/Valet tvOS Test Host App"; + TVOS_DEPLOYMENT_TARGET = 13.0; }; name = Release; }; @@ -2903,7 +2905,7 @@ SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 4; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet watchOS Test Host App Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Valet watchOS Test Host App Watch App"; - WATCHOS_DEPLOYMENT_TARGET = 4.0; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Debug; }; @@ -2940,7 +2942,7 @@ SWIFT_VERSION = 6.0; TARGETED_DEVICE_FAMILY = 4; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Valet watchOS Test Host App Watch App.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Valet watchOS Test Host App Watch App"; - WATCHOS_DEPLOYMENT_TARGET = 4.0; + WATCHOS_DEPLOYMENT_TARGET = 6.0; }; name = Release; }; @@ -3136,6 +3138,7 @@ ); GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -3159,6 +3162,7 @@ DEVELOPMENT_TEAM = ""; GCC_TREAT_WARNINGS_AS_ERRORS = YES; INFOPLIST_FILE = Tests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", From 8b87ccf564aa9a68b7e116225ab05b4f390685b7 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Fri, 11 Oct 2024 01:27:24 -0700 Subject: [PATCH 07/16] =?UTF-8?q?Lower=20macOS=20deployment=20to=20macOS?= =?UTF-8?q?=2010.13=20=E2=80=93=20match=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Valet.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Valet.xcodeproj/project.pbxproj b/Valet.xcodeproj/project.pbxproj index ae3643f1..834bd070 100644 --- a/Valet.xcodeproj/project.pbxproj +++ b/Valet.xcodeproj/project.pbxproj @@ -2677,7 +2677,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; @@ -2714,7 +2714,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 11.0; + MACOSX_DEPLOYMENT_TARGET = 10.13; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu99 gnu++11"; PROVISIONING_PROFILE_SPECIFIER = ""; SDKROOT = macosx; From 86d82f4f5a7bad4d142233ab2d047bbabeff378d Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:00:49 -0700 Subject: [PATCH 08/16] Remove @available checks that no longer apply --- Sources/Valet/SecureEnclaveAccessControl.swift | 16 ++-------------- .../Valet/SinglePromptSecureEnclaveValet.swift | 2 -- ...ecureEnclaveBackwardsCompatibilityTests.swift | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Sources/Valet/SecureEnclaveAccessControl.swift b/Sources/Valet/SecureEnclaveAccessControl.swift index b1e80933..7c0883ad 100644 --- a/Sources/Valet/SecureEnclaveAccessControl.swift +++ b/Sources/Valet/SecureEnclaveAccessControl.swift @@ -23,11 +23,9 @@ public enum SecureEnclaveAccessControl: Int, CustomStringConvertible, Equatable, case userPresence = 1 /// Access to keychain elements requires user presence verification via Face ID, or any finger enrolled in Touch ID. Keychain elements remain accessible via Face ID or Touch ID after faces or fingers are added or removed. Face ID must be enabled with at least one face enrolled, or Touch ID must be available and at least one finger must be enrolled. - @available(macOS 10.12.1, *) case biometricAny /// Access to keychain elements requires user presence verification via the face currently enrolled in Face ID, or fingers currently enrolled in Touch ID. Previously written keychain elements become inaccessible when faces or fingers are added or removed. Face ID must be enabled with at least one face enrolled, or Touch ID must be available and at least one finger must be enrolled. - @available(macOS 10.12.1, *) case biometricCurrentSet /// Access to keychain elements requires user presence verification via device Passcode. @@ -44,19 +42,9 @@ public enum SecureEnclaveAccessControl: Int, CustomStringConvertible, Equatable, */ return "" case .biometricAny: - if #available(macOS 10.12.1, *) { - return "_AccessControlTouchIDAnyFingerprint" - } else { - assertionFailure(".biometricAny requires macOS 10.12.1.") - return "" - } + return "_AccessControlTouchIDAnyFingerprint" case .biometricCurrentSet: - if #available(macOS 10.12.1, *) { - return "_AccessControlTouchIDCurrentFingerprintSet" - } else { - assertionFailure(".biometricCurrentSet requires macOS 10.12.1.") - return "" - } + return "_AccessControlTouchIDCurrentFingerprintSet" case .devicePasscode: return "_AccessControlDevicePasscode" } diff --git a/Sources/Valet/SinglePromptSecureEnclaveValet.swift b/Sources/Valet/SinglePromptSecureEnclaveValet.swift index 6aa6b26b..d59f026b 100644 --- a/Sources/Valet/SinglePromptSecureEnclaveValet.swift +++ b/Sources/Valet/SinglePromptSecureEnclaveValet.swift @@ -22,7 +22,6 @@ import Foundation /// Reads and writes keychain elements that are stored on the Secure Enclave using Accessibility attribute `.whenPasscodeSetThisDeviceOnly`. The first access of these keychain elements will require the user to confirm their presence via Touch ID, Face ID, or passcode entry. If no passcode is set on the device, accessing the keychain via a `SinglePromptSecureEnclaveValet` will fail. Data is removed from the Secure Enclave when the user removes a passcode from the device. @objc(VALSinglePromptSecureEnclaveValet) -@available(watchOS 3, *) public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable { // MARK: Public Class Methods @@ -279,7 +278,6 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable // MARK: - Objective-C Compatibility -@available(watchOS 3, *) extension SinglePromptSecureEnclaveValet { // MARK: Public Class Methods diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift index 9126daac..a5775b1d 100644 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift +++ b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift @@ -20,7 +20,6 @@ import LegacyValet import XCTest -@available(tvOS 11.0, *) extension SinglePromptSecureEnclaveIntegrationTests { @available(*, deprecated) From d3ce184e75cb25a09f5797768eacf89ea705d617 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:01:23 -0700 Subject: [PATCH 09/16] Remove renamed method hints --- Sources/Valet/SecureEnclaveValet.swift | 8 -------- Sources/Valet/SinglePromptSecureEnclaveValet.swift | 8 -------- Sources/Valet/Valet.swift | 8 -------- 3 files changed, 24 deletions(-) diff --git a/Sources/Valet/SecureEnclaveValet.swift b/Sources/Valet/SecureEnclaveValet.swift index 840e07bd..5c965063 100644 --- a/Sources/Valet/SecureEnclaveValet.swift +++ b/Sources/Valet/SecureEnclaveValet.swift @@ -260,14 +260,6 @@ public final class SecureEnclaveValet: NSObject, Sendable { try migrateObjects(matching: valet.baseKeychainQuery, removeOnCompletion: removeOnCompletion) } - // MARK: Renamed Methods - - @available(*, unavailable, renamed: "setObject(_:forKey:)") - public func set(object: Data, forKey key: String) -> Bool { fatalError() } - - @available(*, unavailable, renamed: "setString(_:forKey:)") - public func set(string: String, forKey key: String) -> Bool { fatalError() } - // MARK: Internal Properties internal let service: Service diff --git a/Sources/Valet/SinglePromptSecureEnclaveValet.swift b/Sources/Valet/SinglePromptSecureEnclaveValet.swift index d59f026b..589fd005 100644 --- a/Sources/Valet/SinglePromptSecureEnclaveValet.swift +++ b/Sources/Valet/SinglePromptSecureEnclaveValet.swift @@ -245,14 +245,6 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable try migrateObjects(matching: valet.baseKeychainQuery, removeOnCompletion: removeOnCompletion) } - // MARK: Renamed Methods - - @available(*, unavailable, renamed: "setObject(_:forKey:)") - public func set(object: Data, forKey key: String) -> Bool { fatalError() } - - @available(*, unavailable, renamed: "setString(_:forKey:)") - public func set(string: String, forKey key: String) -> Bool { fatalError() } - // MARK: Internal Properties internal let service: Service diff --git a/Sources/Valet/Valet.swift b/Sources/Valet/Valet.swift index a5802202..9f5649da 100644 --- a/Sources/Valet/Valet.swift +++ b/Sources/Valet/Valet.swift @@ -484,14 +484,6 @@ public final class Valet: NSObject, Sendable { } #endif - // MARK: Renamed Methods - - @available(*, unavailable, renamed: "setObject(_:forKey:)") - public func set(object: Data, forKey key: String) -> Bool { fatalError() } - - @available(*, unavailable, renamed: "setString(_:forKey:)") - public func set(string: String, forKey key: String) -> Bool { fatalError() } - // MARK: Internal Properties internal let configuration: Configuration From 21a0fb36736145be4ca369e278738b84916b26e1 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:03:57 -0700 Subject: [PATCH 10/16] Add timeout back to async tests --- Tests/ValetIntegrationTests/ValetIntegrationTests.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift index eb739cf0..b658030d 100644 --- a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift @@ -681,7 +681,7 @@ class ValetIntegrationTests: XCTestCase await fulfillment(of: [ setQueueExpectation, removeQueueExpectation, - ]) + ], timeout: 10.0) } func test_stringForKey_canReadDataWrittenOnAnotherThread() async @@ -712,7 +712,7 @@ class ValetIntegrationTests: XCTestCase await fulfillment(of: [ expectation, - ]) + ], timeout: 10.0) } func test_stringForKey_canReadDataWrittenToValetAllocatedOnDifferentThread() async @@ -744,7 +744,7 @@ class ValetIntegrationTests: XCTestCase await fulfillment(of: [ expectation, - ]) + ], timeout: 10.0) } // MARK: Removal From 3f1a4b80a29ea450fb0f01719a999988488a873d Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:07:51 -0700 Subject: [PATCH 11/16] No need for async throws --- Tests/ValetIntegrationTests/ValetIntegrationTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift index b658030d..991644a9 100644 --- a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift @@ -145,9 +145,9 @@ class ValetIntegrationTests: XCTestCase // MARK: XCTestCase - override func setUp() async throws + override func setUp() { - try await super.setUp() + super.setUp() let permutations: [Valet] if testEnvironmentIsSignedOrDoesNotRequireEntitlement() { From 9c6f25457f11030d401b6c3c6edaf1bf2acc351a Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:10:02 -0700 Subject: [PATCH 12/16] Remove v2 (objc) compatibility tests --- Sources/LegacyValet/Public/LegacyValet.h | 20 - .../Public/VALLegacySecureEnclaveValet.h | 98 --- .../VALLegacySinglePromptSecureEnclaveValet.h | 28 - Sources/LegacyValet/Public/VALLegacyValet.h | 123 ---- .../Public/VALSynchronizableValet.h | 26 - .../LegacyValet/VALLegacySecureEnclaveValet.m | 508 ------------- .../VALLegacySecureEnclaveValet_Protected.h | 26 - .../VALLegacySinglePromptSecureEnclaveValet.m | 125 ---- Sources/LegacyValet/VALLegacyValet.m | 671 ------------------ .../LegacyValet/VALLegacyValet_Protected.h | 48 -- Sources/LegacyValet/VALSynchronizableValet.m | 89 --- Sources/LegacyValet/ValetDefines.h | 39 - ...reEnclaveBackwardsCompatibilityTests.swift | 37 - ...reEnclaveBackwardsCompatibilityTests.swift | 37 - ...ronizableBackwardsCompatibilityTests.swift | 54 -- .../ValetBackwardsCompatibilityTests.swift | 191 ----- Valet.xcodeproj/project.pbxproj | 120 ---- 17 files changed, 2240 deletions(-) delete mode 100644 Sources/LegacyValet/Public/LegacyValet.h delete mode 100644 Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h delete mode 100644 Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h delete mode 100644 Sources/LegacyValet/Public/VALLegacyValet.h delete mode 100644 Sources/LegacyValet/Public/VALSynchronizableValet.h delete mode 100644 Sources/LegacyValet/VALLegacySecureEnclaveValet.m delete mode 100644 Sources/LegacyValet/VALLegacySecureEnclaveValet_Protected.h delete mode 100644 Sources/LegacyValet/VALLegacySinglePromptSecureEnclaveValet.m delete mode 100644 Sources/LegacyValet/VALLegacyValet.m delete mode 100644 Sources/LegacyValet/VALLegacyValet_Protected.h delete mode 100644 Sources/LegacyValet/VALSynchronizableValet.m delete mode 100644 Sources/LegacyValet/ValetDefines.h delete mode 100644 Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift delete mode 100644 Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift delete mode 100644 Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SynchronizableBackwardsCompatibilityTests.swift delete mode 100644 Tests/ValetIntegrationTests/BackwardsCompatibilityTests/ValetBackwardsCompatibilityTests.swift diff --git a/Sources/LegacyValet/Public/LegacyValet.h b/Sources/LegacyValet/Public/LegacyValet.h deleted file mode 100644 index 6a13aa22..00000000 --- a/Sources/LegacyValet/Public/LegacyValet.h +++ /dev/null @@ -1,20 +0,0 @@ -// Created by Dan Federman on 6/6/19. -// Copyright 2019 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import -#import -#import -#import diff --git a/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h b/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h deleted file mode 100644 index 38ee1403..00000000 --- a/Sources/LegacyValet/Public/VALLegacySecureEnclaveValet.h +++ /dev/null @@ -1,98 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import - - -/// Compiler flag for building against an SDK where Secure Enclave is available. -#define VAL_SECURE_ENCLAVE_SDK_AVAILABLE ((TARGET_OS_IPHONE && __IPHONE_8_0) || (TARGET_OS_MAC && __MAC_10_11)) - - -typedef NS_ENUM(NSUInteger, VALAccessControl) { - /// Access to keychain elements requires user presence verification via Touch ID or device Passcode. Keychain elements are still accessible by Touch ID even if fingers are added or removed. Touch ID does not have to be available or enrolled. - /// @version Available on iOS 8 or later, and macOS 10.11 or later. - VALAccessControlUserPresence = 1, - - /// Access to keychain elements requires user presence verification via any finger enrolled in Touch ID. Keychain elements are still accessible by Touch ID even if fingers are added or removed. Touch ID must be available and at least one finger must be enrolled. - /// @version Available on iOS 9 or later, and macOS 10.12 or later. - VALAccessControlTouchIDAnyFingerprint = 2, - - /// Access to keychain elements requires user presence verification via fingers currently enrolled in Touch ID. Previously written keychain elements become inaccessible when fingers are added or removed. Touch ID must be available and at least one finger must be enrolled. - /// @version Available on iOS 9 or later, and macOS 10.12 or later. - VALAccessControlTouchIDCurrentFingerprintSet = 3, - - /// Access to keychain elements requires user presence verification via device Passcode. - /// @version Available on iOS 9 or later, and macOS 10.11 or later. - VALAccessControlDevicePasscode = 4, -}; - - -/// Reads and writes keychain elements that are stored on the Secure Enclave (available on iOS 8.0 and later and macOS 10.11 and later) using accessibility attribute VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly. Accessing these keychain elements will require the user to confirm their presence via Touch ID or passcode entry. If no passcode is set on the device, the below methods will fail. Data is removed from the Secure Enclave when the user removes a passcode from the device. Use the userPrompt methods to display custom text to the user in Apple's Touch ID and passcode entry UI. -/// @version Available on iOS 8 or later, and macOS 10.11 or later. -@interface VALLegacySecureEnclaveValet : VALLegacyValet - -/// @return YES if Secure Enclave storage is supported on the current iOS or macOS version (iOS 8.0 and macOS 10.11 and later). -+ (BOOL)supportsSecureEnclaveKeychainItems; - -/// Creates a Valet that reads/writes Secure Enclave keychain elements and the specified access control. -/// @see VALAccessControl -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessControl:(VALAccessControl)accessControl; - -/// Creates a Valet that reads/writes Secure Enclave keychain elements that can be shared across applications written by the same development team. -/// @param sharedAccessGroupIdentifier This must correspond with the value for keychain-access-groups in your Entitlements file. -/// @see VALAccessControl -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessControl:(VALAccessControl)accessControl; - -@property (readonly) VALAccessControl accessControl; - -/// Convenience method for retrieving data from the keychain with a user prompt. -/// @see -[VALLegacySecureEnclave objectForKey:userPrompt:userCancelled:] -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; - -/// Convenience method for retrieving data from the keychain with a user prompt. -/// @param userPrompt The prompt displayed to the user in Apple's Touch ID and passcode entry UI. -/// @param userCancelled A pointer to a BOOL which will be set to YES if the user cancels out of Touch ID or entering the device Passcode. -/// @return The object currently stored in the keychain for the provided key. Returns nil if no object exists in the keychain for the specified key, if the keychain is inaccessible, or if the user cancels out of the authentication UI. -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; - -/// Convenience method for retrieving a string from the keychain with a user prompt. -/// @see -[VALLegacySecureEnclave stringForKey:userPrompt:userCancelled:] -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; - -/// Convenience method for retrieving a string from the keychain with a user prompt. -/// @param userPrompt The prompt displayed to the user in Apple's Touch ID and passcode entry UI. -/// @param userCancelled A pointer to a BOOL which will be set to YES if the user cancels out of Touch ID or entering the device Passcode. -/// @return The string currently stored in the keychain for the provided key. Returns nil if no string exists in the keychain for the specified key, if the keychain is inaccessible, or if the user cancels out of the authentication UI. -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; - -/// This method is not supported on VALLegacySecureEnclaveValet. -- (nonnull NSSet *)allKeys NS_UNAVAILABLE; - -/// This method is not supported on VALLegacySecureEnclaveValet. -- (BOOL)removeAllObjects NS_UNAVAILABLE; - -@end - - -@interface VALLegacySecureEnclaveValet (Deprecated) - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier __attribute__((deprecated("Use backwards-compatible initWithIdentifier:accessControl: with VALAccessControlUserPresence instead"))); -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility __attribute__((deprecated("Use backwards-compatible initWithIdentifier:accessControl: with VALAccessControlUserPresence instead"))); - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier __attribute__((deprecated("Use backwards-compatible initWithIdentifier:accessControl: with VALAccessControlUserPresence instead"))); -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility __attribute__((deprecated("Use backwards-compatible initWithIdentifier:accessControl: with VALAccessControlUserPresence instead"))); - -@end diff --git a/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h b/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h deleted file mode 100644 index cf411963..00000000 --- a/Sources/LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h +++ /dev/null @@ -1,28 +0,0 @@ -// Created by Dan Federman on 1/23/17. -// Copyright © 2017 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import - - -/// Reads and writes keychain elements that are stored on the Secure Enclave (available on iOS 8.0 and later and macOS 10.11 and later) using accessibility attribute VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly. The first access of these keychain elements will require the user to confirm their presence via Touch ID or passcode entry. -/// @see VALLegacySecureEnclaveValet -/// @version Available on iOS 8 or later, and macOS 10.11 or later. -@interface VALLegacySinglePromptSecureEnclaveValet : VALLegacySecureEnclaveValet - -/// Forces a prompt for Touch ID or passcode entry on the next data retrieval from the Secure Enclave. -- (void)requirePromptOnNextAccess; - -@end diff --git a/Sources/LegacyValet/Public/VALLegacyValet.h b/Sources/LegacyValet/Public/VALLegacyValet.h deleted file mode 100644 index eceef7de..00000000 --- a/Sources/LegacyValet/Public/VALLegacyValet.h +++ /dev/null @@ -1,123 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import - - -typedef NS_ENUM(NSUInteger, VALLegacyAccessibility) { - /// Valet data can only be accessed while the device is unlocked. This attribute is recommended for data that only needs to be accessible while the application is in the foreground. Valet data with this attribute will migrate to a new device when using encrypted backups. - VALLegacyAccessibilityWhenUnlocked = 1, - /// Valet data can only be accessed once the device has been unlocked after a restart. This attribute is recommended for data that needs to be accessible by background applications. Valet data with this attribute will migrate to a new device when using encrypted backups. - VALLegacyAccessibilityAfterFirstUnlock, - /// Valet data can always be accessed regardless of the lock state of the device. This attribute is not recommended. Valet data with this attribute will migrate to a new device when using encrypted backups. - VALLegacyAccessibilityAlways, - - /// Valet data can only be accessed while the device is unlocked. This class is only available if a passcode is set on the device. This is recommended for items that only need to be accessible while the application is in the foreground. Valet data with this attribute will never migrate to a new device, so these items will be missing after a backup is restored to a new device. No items can be stored in this class on devices without a passcode. Disabling the device passcode will cause all items in this class to be deleted. - VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly NS_ENUM_AVAILABLE(10_10, 8_0), - /// Valet data can only be accessed while the device is unlocked. This is recommended for data that only needs to be accessible while the application is in the foreground. Valet data with this attribute will never migrate to a new device, so these items will be missing after a backup is restored to a new device. - VALLegacyAccessibilityWhenUnlockedThisDeviceOnly, - /// Valet data can only be accessed once the device has been unlocked after a restart. This is recommended for items that need to be accessible by background applications. Valet data with this attribute will never migrate to a new device, so these items will be missing after a backup is restored to a new device. - VALLegacyAccessibilityAfterFirstUnlockThisDeviceOnly, - /// Valet data can always be accessed regardless of the lock state of the device. This option is not recommended. Valet data with this attribute will never migrate to a new device, so these items will be missing after a backup is restored to a new device. - VALLegacyAccessibilityAlwaysThisDeviceOnly, -}; - -extern NSString * __nonnull const VALMigrationErrorDomain; - -typedef NS_ENUM(NSUInteger, VALMigrationError) { - /// Migration failed because the keychain query was not valid. - VALMigrationErrorInvalidQuery = 1, - /// Migration failed because no items to migrate were found. - VALMigrationErrorNoItemsToMigrateFound, - /// Migration failed because the keychain could not be read. - VALMigrationErrorCouldNotReadKeychain, - /// Migration failed because a key in the query result could not be read. - VALMigrationErrorKeyInQueryResultInvalid, - /// Migration failed because some data in the query result could not be read. - VALMigrationErrorDataInQueryResultInvalid, - /// Migration failed because two keys with the same value were found in the keychain. - VALMigrationErrorDuplicateKeyInQueryResult, - /// Migration failed because a key in the keychain duplicates a key already managed by Valet. - VALMigrationErrorKeyInQueryResultAlreadyExistsInValet, - /// Migration failed because writing to the keychain failed. - VALMigrationErrorCouldNotWriteToKeychain, - /// Migration failed because removing the migrated data from the keychain failed. - VALMigrationErrorRemovalFailed, -}; - - -/// Reads and writes keychain elements. -@interface VALLegacyValet : NSObject - -/// Creates a Valet that reads/writes keychain elements with the desired accessibility. -/// @see VALLegacyAccessibility -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility NS_DESIGNATED_INITIALIZER; - -/// Creates a Valet that reads/writes keychain elements that can be shared across applications written by the same development team. -/// @param sharedAccessGroupIdentifier This must correspond with the value for keychain-access-groups in your Entitlements file. -/// @see VALLegacyAccessibility -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility NS_DESIGNATED_INITIALIZER; - -- (nonnull instancetype)init NS_UNAVAILABLE; -+ (nonnull instancetype)new NS_UNAVAILABLE; - -@property (nonnull, copy, readonly) NSString *identifier; -@property (readonly, getter=isSharedAcrossApplications) BOOL sharedAcrossApplications; -@property (readonly) VALLegacyAccessibility accessibility; - -/// @return YES if otherValet reads from and writes to the same sandbox within the keychain as the receiver. -- (BOOL)isEqualToValet:(nonnull VALLegacyValet *)otherValet; - -/// @return YES if the keychain is accessible for reading and writing, NO otherwise. -/// @note Determined by writing a value to the keychain and then reading it back out. -- (BOOL)canAccessKeychain; - -/// @param value An NSData value to be inserted into the keychain. -/// @return NO if the keychain is not accessible. -- (BOOL)setObject:(nonnull NSData *)value forKey:(nonnull NSString *)key; -/// @return The data currently stored in the keychain for the provided key. Returns nil if no object exists in the keychain for the specified key, or if the keychain is inaccessible. -- (nullable NSData *)objectForKey:(nonnull NSString *)key; - -/// @param string An NSString value to store in the keychain for the provided key. -/// @return NO if the keychain is not accessible. -- (BOOL)setString:(nonnull NSString *)string forKey:(nonnull NSString *)key; -/// @return The string currently stored in the keychain for the provided key. Returns nil if no string exists in the keychain for the specified key, or if the keychain is inaccessible. -- (nullable NSString *)stringForKey:(nonnull NSString *)key; - -/// @param key The key to look up in the keychain. -/// @return YES if a value has been set for the given key, NO otherwise. -- (BOOL)containsObjectForKey:(nonnull NSString *)key; -/// @return The set of all (NSString) keys currently stored in this Valet instance. -- (nonnull NSSet *)allKeys; - -/// Removes a key/object pair from the keychain. -/// @return NO if the keychain is not accessible. -- (BOOL)removeObjectForKey:(nonnull NSString *)key; -/// Removes all key/object pairs accessible by this Valet instance from the keychain. -/// @return NO if the keychain is not accessible. -- (BOOL)removeAllObjects; - -/// Migrates objects matching the secItemQuery into the receiving Valet instance. -/// @return An error if the operation failed. Error domain will be VALMigrationErrorDomain, and codes will be of type VALMigrationError -/// @see VALMigrationError -/// @note The keychain is not modified if a failure occurs. -- (nullable NSError *)migrateObjectsMatchingQuery:(nonnull NSDictionary *)secItemQuery removeOnCompletion:(BOOL)remove; -/// Migrates objects from the passed-in Valet into the receiving Valet instance. -/// @return An error if the operation failed. Error domain will be VALMigrationErrorDomain, and codes will be of type VALMigrationError -/// @see VALMigrationError -- (nullable NSError *)migrateObjectsFromValet:(nonnull VALLegacyValet *)valet removeOnCompletion:(BOOL)remove; - -@end diff --git a/Sources/LegacyValet/Public/VALSynchronizableValet.h b/Sources/LegacyValet/Public/VALSynchronizableValet.h deleted file mode 100644 index 6c7666a4..00000000 --- a/Sources/LegacyValet/Public/VALSynchronizableValet.h +++ /dev/null @@ -1,26 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import - - -/// Reads and writes keychain elements that are synchronized with iCloud (supported on devices on iOS 7.0.3 and later). Accessibility must not be scoped to this device. -@interface VALSynchronizableValet : VALLegacyValet - -/// @return YES if iCloud syncronizable keychain is supported on the current iOS version (7.0.3 and later). -+ (BOOL)supportsSynchronizableKeychainItems; - -@end diff --git a/Sources/LegacyValet/VALLegacySecureEnclaveValet.m b/Sources/LegacyValet/VALLegacySecureEnclaveValet.m deleted file mode 100644 index 5360f025..00000000 --- a/Sources/LegacyValet/VALLegacySecureEnclaveValet.m +++ /dev/null @@ -1,508 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "VALLegacySecureEnclaveValet.h" -#import "VALLegacySecureEnclaveValet_Protected.h" -#import "VALLegacyValet_Protected.h" - -#import "ValetDefines.h" - - -/// Compiler flag for building against an SDK where VALAccessControlTouchIDAnyFingerprint and VALAccessControlTouchIDCurrentFingerprintSet are available. -#define VAL_ACCESS_CONTROL_TOUCH_ID_SDK_AVAILABLE ((TARGET_OS_IPHONE && __IPHONE_9_0) || (TARGET_OS_MAC && __MAC_10_12)) - -/// Compiler flag for building against an SDK where VALAccessControlDevicePasscode is available. -#define VAL_ACCESS_CONTROL_DEVICE_PASSCODE_SDK_AVAILABLE ((TARGET_OS_IPHONE && __IPHONE_9_0) || (TARGET_OS_MAC && __MAC_10_11)) - - -NSString *__nonnull VALStringForAccessControl(VALAccessControl accessControl) -{ - switch (accessControl) { - case VALAccessControlUserPresence: - return @"AccessControlUserPresence"; - - case VALAccessControlTouchIDAnyFingerprint: - return @"AccessControlTouchIDAnyFingerprint"; - - case VALAccessControlTouchIDCurrentFingerprintSet: - return @"AccessControlTouchIDCurrentFingerprintSet"; - - case VALAccessControlDevicePasscode: - return @"AccessControlDevicePasscode"; - } - - return @"AccessControlInvalid"; -} - -#if VAL_SECURE_ENCLAVE_SDK_AVAILABLE - -@implementation VALLegacySecureEnclaveValet - -@synthesize baseQuery = _baseQuery; - -#pragma mark - Class Methods - -+ (BOOL)supportsSecureEnclaveKeychainItems; -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - return (&kSecAttrAccessControl != NULL && &kSecUseOperationPrompt != NULL); -#pragma clang diagnostic pop -} - -#pragma mark - Private Class Methods - -+ (BOOL)_macOSElCapitanOrLater; -{ -#if TARGET_OS_MAC && __MAC_10_11 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - return (&kSecUseAuthenticationUI != NULL); -#pragma clang diagnostic pop -#else - return NO; -#endif -} - -+ (BOOL)_macOSSierraOrLater; -{ -#if TARGET_OS_MAC && __MAC_10_12 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - return (&kSecAttrTokenIDSecureEnclave != NULL); -#pragma clang diagnostic pop -#else - return NO; -#endif -} - -+ (BOOL)_iOS8OrLater; -{ -#if TARGET_OS_IPHONE -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - return (&kSecUseOperationPrompt != NULL); -#pragma clang diagnostic pop -#else - return NO; -#endif -} - -+ (BOOL)_iOS9OrLater; -{ -#if TARGET_OS_IPHONE && __IPHONE_9_0 -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - return (&kSecUseAuthenticationUI != NULL); -#pragma clang diagnostic pop -#else - return NO; -#endif -} - -+ (BOOL)_currentOSSupportedForAccessControl:(VALAccessControl)accessControl; -{ - switch (accessControl) { - case VALAccessControlUserPresence: - return ([self _iOS8OrLater] || [self _macOSElCapitanOrLater]); - - case VALAccessControlTouchIDAnyFingerprint: - case VALAccessControlTouchIDCurrentFingerprintSet: - return [self _iOS9OrLater] || [self _macOSSierraOrLater]; - - case VALAccessControlDevicePasscode: - return ([self _iOS9OrLater] || [self _macOSElCapitanOrLater]); - } - - return NO; -} - -+ (void)_augmentBaseQuery:(nonnull NSMutableDictionary *)mutableBaseQuery accessControl:(VALAccessControl)accessControl; -{ - // Add the access control, which opts us in to Secure Element storage. - [mutableBaseQuery addEntriesFromDictionary:@{ (__bridge id)kSecAttrAccessControl : (__bridge_transfer id)SecAccessControlCreateWithFlags(NULL, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, ({ - SecAccessControlCreateFlags accessControlFlag = 0; - - switch (accessControl) { - case VALAccessControlUserPresence: - accessControlFlag = kSecAccessControlUserPresence; - break; - -#if VAL_ACCESS_CONTROL_TOUCH_ID_SDK_AVAILABLE - case VALAccessControlTouchIDAnyFingerprint: - accessControlFlag = kSecAccessControlTouchIDAny; - break; - case VALAccessControlTouchIDCurrentFingerprintSet: - accessControlFlag = kSecAccessControlTouchIDCurrentSet; - break; -#else - case VALAccessControlTouchIDAnyFingerprint: - case VALAccessControlTouchIDCurrentFingerprintSet: - // This SDK does not support these access controls. But on this SDK we'll never reach this line, so just fake it. - break; -#endif - -#if VAL_ACCESS_CONTROL_DEVICE_PASSCODE_SDK_AVAILABLE - case VALAccessControlDevicePasscode: - accessControlFlag = kSecAccessControlDevicePasscode; - break; -#else - case VALAccessControlDevicePasscode: - // This SDK does not support these access controls. But on this SDK we'll never reach this line, so just fake it. - break; -#endif - } - - accessControlFlag; - }), NULL) }]; - - // kSecAttrAccessControl and kSecAttrAccessible are mutually exclusive, so remove kSecAttrAccessible from our query. - [mutableBaseQuery removeObjectForKey:(__bridge id)kSecAttrAccessible]; - - NSString *const service = mutableBaseQuery[(__bridge id)kSecAttrService]; - NSString *const accessControlServiceSuffix = ({ - NSString *accessControlServiceSuffix = @""; - - switch (accessControl) { - case VALAccessControlUserPresence: - /* - VALLegacySecureEnclaveValet v1.0-v2.0.7 used UserPresence without a suffix – the concept of a customizable AccessControl was added in v2.1. - For backwards compatibility, do not append an access control suffix for UserPresence. - */ - break; - - case VALAccessControlTouchIDAnyFingerprint: - case VALAccessControlTouchIDCurrentFingerprintSet: - case VALAccessControlDevicePasscode: - accessControlServiceSuffix = [@"_" stringByAppendingString:VALStringForAccessControl(accessControl)]; - break; - } - - accessControlServiceSuffix; - }); - - if (service.length > 0 && accessControlServiceSuffix.length > 0) { - // Ensure that our service identifier includes our access control suffix. - mutableBaseQuery[(__bridge id)kSecAttrService] = [service stringByAppendingString:accessControlServiceSuffix]; - } -} - -#pragma mark - Initialization - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessControl:(VALAccessControl)accessControl; -{ - VALCheckCondition([[self class] supportsSecureEnclaveKeychainItems], nil, @"This device does not support storing data on the secure enclave."); - VALCheckCondition([[self class] _currentOSSupportedForAccessControl:accessControl], nil, @"This device does not support %@", VALStringForAccessControl(accessControl)); - - VALLegacyAccessibility const accessibility = VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly; - self = [super initWithIdentifier:identifier accessibility:accessibility]; - - SEL const backwardsCompatibleInitializer = @selector(initWithIdentifier:accessibility:); - NSMutableDictionary *const baseQuery = [[self class] mutableBaseQueryWithIdentifier:identifier - accessibility:accessibility - initializer:backwardsCompatibleInitializer]; - [[self class] _augmentBaseQuery:baseQuery - accessControl:accessControl]; - _baseQuery = baseQuery; - _accessControl = accessControl; - - return [[self class] sharedValetForValet:self]; -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessControl:(VALAccessControl)accessControl -{ - VALCheckCondition([[self class] supportsSecureEnclaveKeychainItems], nil, @"This device does not support storing data on the secure enclave."); - VALCheckCondition([[self class] _currentOSSupportedForAccessControl:accessControl], nil, @"This device does not support %@", VALStringForAccessControl(accessControl)); - - VALLegacyAccessibility const accessibility = VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly; - self = [super initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessibility:accessibility]; - - SEL const backwardsCompatibleInitializer = @selector(initWithSharedAccessGroupIdentifier:accessibility:); - NSMutableDictionary *const baseQuery = [[self class] mutableBaseQueryWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier - accessibility:accessibility - initializer:backwardsCompatibleInitializer]; - [[self class] _augmentBaseQuery:baseQuery - accessControl:accessControl]; - _baseQuery = baseQuery; - _accessControl = accessControl; - - return [[self class] sharedValetForValet:self]; -} - -#pragma mark - VALValet - -- (BOOL)canAccessKeychain; -{ - // To avoid prompting the user for Touch ID or passcode, create a VALValet with our identifier and accessibility and ask it if it can access the keychain. - VALLegacyValet *noPromptValet = nil; - if ([self isSharedAcrossApplications]) { - noPromptValet = [[VALLegacyValet alloc] initWithSharedAccessGroupIdentifier:self.identifier accessibility:self.accessibility]; - } else { - noPromptValet = [[VALLegacyValet alloc] initWithIdentifier:self.identifier accessibility:self.accessibility]; - } - - return [noPromptValet canAccessKeychain]; -} - -- (BOOL)containsObjectForKey:(nonnull NSString *)key; -{ - OSStatus const status = [self containsObjectForKey:key options:nil]; - BOOL const keyAlreadyInKeychain = (status == errSecInteractionNotAllowed || status == errSecSuccess); - return keyAlreadyInKeychain; -} - -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wreturn-type" -- (nonnull NSSet *)allKeys; -{ - VALCheckCondition(NO, [NSSet new], @"%s is not supported on %@", __PRETTY_FUNCTION__, NSStringFromClass([self class])); -} - -- (BOOL)removeAllObjects; -{ - VALCheckCondition(NO, NO, @"%s is not supported on %@", __PRETTY_FUNCTION__, NSStringFromClass([self class])); -} -#pragma clang diagnostic pop - -- (nullable NSError *)migrateObjectsMatchingQuery:(nonnull NSDictionary *)secItemQuery removeOnCompletion:(BOOL)remove; -{ - if ([[self class] supportsSecureEnclaveKeychainItems]) { - VALCheckCondition(secItemQuery[(__bridge id)kSecUseOperationPrompt] == nil, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"kSecUseOperationPrompt is not supported in a migration query. Keychain items can not be migrated en masse from the Secure Enclave."); - } - - return [super migrateObjectsMatchingQuery:secItemQuery removeOnCompletion:remove]; -} - -#pragma mark - Public Methods - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; -{ - return [self objectForKey:key userPrompt:userPrompt userCancelled:NULL]; -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; -{ - return [self objectForKey:key userPrompt:userPrompt userCancelled:userCancelled options:nil]; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; -{ - return [self stringForKey:key userPrompt:userPrompt userCancelled:NULL]; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; -{ - return [self stringForKey:key userPrompt:userPrompt userCancelled:userCancelled options:nil]; -} - -#pragma mark - VALValet Protected Methods - -- (BOOL)setObject:(nonnull NSData *)value forKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -{ - // Remove the key before trying to set it. This will prevent us from calling SecItemUpdate on an item stored on the Secure Enclave, which would cause iOS to prompt the user for authentication. - [self removeObjectForKey:key]; - - return [super setObject:value forKey:key options:options]; -} - -- (OSStatus)containsObjectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -{ - NSDictionary *baseOptions = nil; - - // iOS 9 and macOS 10.11 use kSecUseAuthenticationUI, not kSecUseNoAuthenticationUI. -#if ((TARGET_OS_IPHONE && __IPHONE_9_0) || (TARGET_OS_MAC && __MAC_10_11)) - if ([[self class] _iOS9OrLater] || [[self class] _macOSElCapitanOrLater]) { - baseOptions = @{ (__bridge id)kSecUseAuthenticationUI : (__bridge id)kSecUseAuthenticationUIFail }; - } else { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - // kSecUseNoAuthenticationUI is deprecated in the iOS 9 SDK, but we still need it on iOS 8. -#if (TARGET_OS_IPHONE && __IPHONE_9_0) - options = @{ (__bridge id)kSecUseNoAuthenticationUI : @YES }; -#endif -#pragma GCC diagnostic pop - } -#else - options = @{ (__bridge id)kSecUseNoAuthenticationUI : @YES }; -#endif - - NSMutableDictionary *const allOptions = [baseOptions mutableCopy]; - [allOptions addEntriesFromDictionary:options]; - return [super containsObjectForKey:key options:allOptions]; -} - -#pragma mark - VALLegacySecureEnclaveValet Protected Methods - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options; -{ - OSStatus status = errSecSuccess; - - NSMutableDictionary *const allOptions = [[self _optionsDictionaryForUserPrompt:userPrompt] mutableCopy]; - if (options.count > 0) { - [allOptions addEntriesFromDictionary:options]; - } - - NSData *const objectForKey = [self objectForKey:key options:allOptions status:&status]; - if (userCancelled != NULL) { - *userCancelled = (status == errSecUserCanceled || status == errSecAuthFailed); - } - - return objectForKey; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options; -{ - OSStatus status = errSecSuccess; - - NSMutableDictionary *const allOptions = [[self _optionsDictionaryForUserPrompt:userPrompt] mutableCopy]; - if (options.count > 0) { - [allOptions addEntriesFromDictionary:options]; - } - - NSString *const stringForKey = [self stringForKey:key options:allOptions status:&status]; - if (userCancelled != NULL) { - *userCancelled = (status == errSecUserCanceled || status == errSecAuthFailed); - } - - return stringForKey; -} - -#pragma mark - Private Methods - -- (nullable NSDictionary *)_optionsDictionaryForUserPrompt:(nullable NSString *)userPrompt; -{ - if (userPrompt.length == 0) { - return nil; - - } else { - return @{ (__bridge id)kSecUseOperationPrompt : userPrompt }; - } -} - -@end - - -#pragma mark - Deprecated Category - - -@implementation VALLegacySecureEnclaveValet (Deprecated) - -#pragma mark - Deprecated Initializers - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier; -{ - return [self initWithIdentifier:identifier accessibility:VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly]; -} - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(accessibility == VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly, nil, @"Accessibility on SecureEnclaveValet must be VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly"); - - return [self initWithIdentifier:identifier accessControl:VALAccessControlUserPresence]; -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier; -{ - return [self initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessibility:VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly]; -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(accessibility == VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly, nil, @"Accessibility on SecureEnclaveValet must be VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly"); - - return [self initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessControl:VALAccessControlUserPresence]; -} - -@end - -#else // Below this line we're in !VAL_SECURE_ENCLAVE_SDK_AVAILABLE, meaning none of our API is actually usable. Return NO or nil everywhere. - -@implementation VALLegacySecureEnclaveValet - -+ (BOOL)supportsSecureEnclaveKeychainItems; -{ - VALCheckCondition(NO, NO, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessControl:(VALAccessControl)accessControl; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessControl:(VALAccessControl)accessControl; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -@end - - -@implementation VALLegacySecureEnclaveValet (Deprecated) - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(NO, nil, @"VALLegacySecureEnclaveValet unsupported on this SDK"); -} - -@end - - -#endif // VAL_SECURE_ENCLAVE_SDK_AVAILABLE diff --git a/Sources/LegacyValet/VALLegacySecureEnclaveValet_Protected.h b/Sources/LegacyValet/VALLegacySecureEnclaveValet_Protected.h deleted file mode 100644 index a11d7bcd..00000000 --- a/Sources/LegacyValet/VALLegacySecureEnclaveValet_Protected.h +++ /dev/null @@ -1,26 +0,0 @@ -// Created by Dan Federman on 1/23/17. -// Copyright © 2017 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "VALLegacySecureEnclaveValet.h" - - -@interface VALLegacySecureEnclaveValet () - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options; - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled options:(nullable NSDictionary *)options; - -@end diff --git a/Sources/LegacyValet/VALLegacySinglePromptSecureEnclaveValet.m b/Sources/LegacyValet/VALLegacySinglePromptSecureEnclaveValet.m deleted file mode 100644 index 7b3b9c0e..00000000 --- a/Sources/LegacyValet/VALLegacySinglePromptSecureEnclaveValet.m +++ /dev/null @@ -1,125 +0,0 @@ -// Created by Dan Federman on 1/23/17. -// Copyright © 2017 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "VALLegacySinglePromptSecureEnclaveValet.h" -#import "VALLegacySecureEnclaveValet_Protected.h" -#import "VALLegacyValet_Protected.h" - -#import "ValetDefines.h" - - -#if VAL_SECURE_ENCLAVE_SDK_AVAILABLE -#import - - -@interface VALLegacySinglePromptSecureEnclaveValet () - -@property (nonnull, strong, readwrite) LAContext *context; - -@end - - -@implementation VALLegacySinglePromptSecureEnclaveValet - -#pragma mark - Initialization - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessControl:(VALAccessControl)accessControl; -{ - self = [super initWithIdentifier:identifier accessControl:accessControl]; - - if (self != nil) { - _context = [LAContext new]; - } - - return self; -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessControl:(VALAccessControl)accessControl; -{ - self = [super initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessControl:accessControl]; - - if (self != nil) { - _context = [LAContext new]; - } - - return self; -} - -#pragma mark - VALValet - -- (nullable NSData *)objectForKey:(nonnull NSString *)key; -{ - return [self objectForKey:key options:[self _contextOptions] status:nil]; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key; -{ - return [self stringForKey:key options:[self _contextOptions] status:nil]; -} - -#pragma mark - VALLegacySecureEnclaveValet - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; -{ - return [self objectForKey:key userPrompt:userPrompt userCancelled:nil options:[self _contextOptions]]; -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; -{ - return [self objectForKey:key userPrompt:userPrompt userCancelled:userCancelled options:[self _contextOptions]]; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt; -{ - return [self stringForKey:key userPrompt:userPrompt userCancelled:nil options:[self _contextOptions]]; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key userPrompt:(nullable NSString *)userPrompt userCancelled:(nullable inout BOOL *)userCancelled; -{ - return [self stringForKey:key userPrompt:userPrompt userCancelled:userCancelled options:[self _contextOptions]]; -} - -#pragma mark - Public Methods - -- (void)requirePromptOnNextAccess; -{ - VALAtomicSecItemLock(^{ - [self.context invalidate]; - self.context = [LAContext new]; - }); -} - -#pragma mark - Private Methods - -- (nonnull NSDictionary *)_contextOptions; -{ - return @{ (__bridge id)kSecUseAuthenticationContext : self.context }; -} - -@end - -#else // Below this line we're in !VAL_SECURE_ENCLAVE_SDK_AVAILABLE, meaning none of our API is actually usable. Return NO or nil everywhere. - -@implementation VALLegacySinglePromptSecureEnclaveValet - -- (void)requirePromptOnNextAccess; -{ - VALCheckCondition(NO, , @"VALLegacySinglePromptSecureEnclaveValet unsupported on this SDK"); -} - -@end - -#endif // VAL_SECURE_ENCLAVE_SDK_AVAILABLE diff --git a/Sources/LegacyValet/VALLegacyValet.m b/Sources/LegacyValet/VALLegacyValet.m deleted file mode 100644 index 0bf2b6d1..00000000 --- a/Sources/LegacyValet/VALLegacyValet.m +++ /dev/null @@ -1,671 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "VALLegacyValet_Protected.h" - -#import "ValetDefines.h" - - -NSString * const VALMigrationErrorDomain = @"VALMigrationErrorDomain"; -NSString * const VALCanAccessKeychainCanaryKey = @"VAL_KeychainCanaryUsername"; - - -NSString *VALStringForAccessibility(VALLegacyAccessibility accessibility) -{ - switch (accessibility) { - case VALLegacyAccessibilityWhenUnlocked: - return @"AccessibleWhenUnlocked"; - case VALLegacyAccessibilityAfterFirstUnlock: - return @"AccessibleAfterFirstUnlock"; - case VALLegacyAccessibilityAlways: - return @"AccessibleAlways"; -#if (TARGET_OS_IPHONE && __IPHONE_8_0) || __MAC_10_10 - case VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly: - return @"AccessibleWhenPasscodeSetThisDeviceOnly"; -#endif - case VALLegacyAccessibilityWhenUnlockedThisDeviceOnly: - return @"AccessibleWhenUnlockedThisDeviceOnly"; - case VALLegacyAccessibilityAfterFirstUnlockThisDeviceOnly: - return @"AccessibleAfterFirstUnlockThisDeviceOnly"; - case VALLegacyAccessibilityAlwaysThisDeviceOnly: - return @"AccessibleAlwaysThisDeviceOnly"; - } -} - -void VALExecuteBlockInLock(__nonnull dispatch_block_t block, NSLock *__nonnull lock) -{ - VALCheckCondition(block != NULL, , @"Must pass in a block"); - VALCheckCondition(lock != nil, , @"Must pass in a lock"); - - [lock lock]; - @try { - block(); - } - @finally { - [lock unlock]; - } -} - -/// We can't be sure that SecItem calls are atomic, so ensure atomicity ourselves. -void VALAtomicSecItemLock(__nonnull dispatch_block_t block) -{ - VALCheckCondition(block != NULL, , @"Must pass in a block"); - - static NSLock *sSecItemLock = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sSecItemLock = [NSLock new]; - }); - - VALExecuteBlockInLock(block, sSecItemLock); -} - -OSStatus VALAtomicSecItemCopyMatching(__nonnull CFDictionaryRef query, CFTypeRef *__nullable result) -{ - VALCheckCondition(CFDictionaryGetCount(query) > 0, errSecParam, @"Must provide a query with at least one item"); - - __block OSStatus status = errSecNotAvailable; - VALAtomicSecItemLock(^{ - status = SecItemCopyMatching(query, result); - }); - - return status; -} - -OSStatus VALAtomicSecItemAdd(__nonnull CFDictionaryRef attributes, CFTypeRef *__nullable result) -{ - VALCheckCondition(CFDictionaryGetCount(attributes) > 0, errSecParam, @"Must provide attributes with at least one item"); - - __block OSStatus status = errSecNotAvailable; - VALAtomicSecItemLock(^{ - status = SecItemAdd(attributes, result); - }); - - return status; -} - -OSStatus VALAtomicSecItemUpdate(__nonnull CFDictionaryRef query, __nonnull CFDictionaryRef attributesToUpdate) -{ - VALCheckCondition(CFDictionaryGetCount(query) > 0, errSecParam, @"Must provide a query with at least one item"); - VALCheckCondition(CFDictionaryGetCount(attributesToUpdate) > 0, errSecParam, @"Must provide a attributesToUpdate with at least one item"); - - __block OSStatus status = errSecNotAvailable; - VALAtomicSecItemLock(^{ - status = SecItemUpdate(query, attributesToUpdate); - }); - - return status; -} - -OSStatus VALAtomicSecItemDelete(__nonnull CFDictionaryRef query) -{ - VALCheckCondition(CFDictionaryGetCount(query) > 0, errSecParam, @"Must provide a query with at least one item"); - - __block OSStatus status = errSecNotAvailable; - VALAtomicSecItemLock(^{ - status = SecItemDelete(query); - }); - - return status; -} - - -@interface VALLegacyValet () - -/// The service identifier within the baseQuery (kSecAttrService). -@property (nonnull, copy, readonly) NSString *secServiceIdentifier; - -/// Set and Remove must be atomic operations relative to one another to ensure that SecItemUpdate is never called on an item that has been removed from the keychain. -@property (nonnull, copy, readonly) NSLock *lockForSetAndRemoveOperations; - -@end - - -@implementation VALLegacyValet - -#pragma mark - Protected Class Methods - -+ (nonnull id)sharedValetForValet:(nonnull VALLegacyValet *)valet; -{ - @synchronized(self) { - static NSMapTable *sServiceIdentifierToWeakValet = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - sServiceIdentifierToWeakValet = [NSMapTable strongToWeakObjectsMapTable]; - }); - - VALLegacyValet *existingValet = [sServiceIdentifierToWeakValet objectForKey:valet.secServiceIdentifier]; - if (existingValet != nil) { - return existingValet; - } - - [sServiceIdentifierToWeakValet setObject:valet forKey:valet.secServiceIdentifier]; - return valet; - } -} - -+ (nullable NSMutableDictionary *)mutableBaseQueryWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility initializer:(nonnull SEL)initializer; -{ - VALCheckCondition(identifier.length > 0, nil, @"Must provide a valid identifier"); - - NSString *const legacyClassNameForBackwardCompatibility = [NSStringFromClass(self) stringByReplacingOccurrencesOfString:@"Legacy" withString:@""]; - return [@{ - // Valet only handles generic passwords. - (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword, - // Use the identifier, Valet type and accessibility settings to create the keychain service name. - (__bridge id)kSecAttrService : [NSString stringWithFormat:@"VAL_%@_%@_%@_%@", legacyClassNameForBackwardCompatibility, NSStringFromSelector(initializer), identifier, VALStringForAccessibility(accessibility)], - // Set our accessibility. - (__bridge id)kSecAttrAccessible : [self _secAccessibilityAttributeForAccessibility:accessibility], - } mutableCopy]; -} - -+ (nullable NSMutableDictionary *)mutableBaseQueryWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility initializer:(nonnull SEL)initializer; -{ - NSMutableDictionary *const mutableBaseQuery = [self mutableBaseQueryWithIdentifier:sharedAccessGroupIdentifier accessibility:accessibility initializer:initializer]; - - mutableBaseQuery[(__bridge id)kSecAttrAccessGroup] = [NSString stringWithFormat:@"%@.%@", [self _sharedAccessGroupPrefix], sharedAccessGroupIdentifier]; - - return mutableBaseQuery; -} - -#pragma mark - Private Class Methods - -+ (nonnull id)_secAccessibilityAttributeForAccessibility:(VALLegacyAccessibility)accessibility; -{ - switch (accessibility) { - case VALLegacyAccessibilityWhenUnlocked: - return (__bridge id)kSecAttrAccessibleWhenUnlocked; - case VALLegacyAccessibilityAfterFirstUnlock: - return (__bridge id)kSecAttrAccessibleAfterFirstUnlock; - case VALLegacyAccessibilityAlways: - return (__bridge id)kSecAttrAccessibleAlways; -#if (TARGET_OS_IPHONE && __IPHONE_8_0) || __MAC_10_10 - case VALLegacyAccessibilityWhenPasscodeSetThisDeviceOnly: - return (__bridge id)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly; -#endif - case VALLegacyAccessibilityWhenUnlockedThisDeviceOnly: - return (__bridge id)kSecAttrAccessibleWhenUnlockedThisDeviceOnly; - case VALLegacyAccessibilityAfterFirstUnlockThisDeviceOnly: - return (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; - case VALLegacyAccessibilityAlwaysThisDeviceOnly: - return (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly; - } -} - -/// Programatically grab the required prefix for the shared access group (i.e. Bundle Seed ID). The value for the kSecAttrAccessGroup key in queries for data that is shared between apps must be of the format bundleSeedID.sharedAccessGroup. For more information on the Bundle Seed ID, see https://developer.apple.com/library/ios/qa/qa1713/_index.html -+ (nullable NSString *)_sharedAccessGroupPrefix; -{ - NSDictionary *query = @{ (__bridge NSString *)kSecClass : (__bridge NSString *)kSecClassGenericPassword, - (__bridge id)kSecAttrAccount : @"SharedAccessGroupAlwaysAccessiblePrefixPlaceholder", - (__bridge id)kSecReturnAttributes : @YES, - (__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly }; - - CFTypeRef outTypeRef = NULL; - - OSStatus status = VALAtomicSecItemCopyMatching((__bridge CFDictionaryRef)query, &outTypeRef); - NSDictionary *queryResult = (__bridge_transfer NSDictionary *)outTypeRef; - - if (status == errSecItemNotFound) { - status = VALAtomicSecItemAdd((__bridge CFDictionaryRef)query, &outTypeRef); - queryResult = (__bridge_transfer NSDictionary *)outTypeRef; - } - - VALCheckCondition(status == errSecSuccess, nil, @"Could not find shared access group prefix."); - - NSString *const accessGroup = queryResult[(__bridge id)kSecAttrAccessGroup]; - NSArray *const components = [accessGroup componentsSeparatedByString:@"."]; - NSString *const bundleSeedID = components.firstObject; - - return bundleSeedID; -} - -#pragma mark - Initialization - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(identifier.length > 0, nil, @"Valet requires an identifier"); - VALCheckCondition(accessibility > 0, nil, @"Valet requires a valid accessibility setting"); - - self = [super init]; - - _baseQuery = [[self class] mutableBaseQueryWithIdentifier:identifier accessibility:accessibility initializer:_cmd]; - _identifier = [identifier copy]; - _sharedAcrossApplications = NO; - _accessibility = accessibility; - _lockForSetAndRemoveOperations = [NSLock new]; - - return [[self class] sharedValetForValet:self]; -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(sharedAccessGroupIdentifier.length > 0, nil, @"Valet requires a sharedAccessGroupIdentifier"); - VALCheckCondition(accessibility > 0, nil, @"Valet requires a valid accessibility setting"); - - self = [super init]; - - _baseQuery = [[self class] mutableBaseQueryWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessibility:accessibility initializer:_cmd]; - - _identifier = [sharedAccessGroupIdentifier copy]; - _sharedAcrossApplications = YES; - _accessibility = accessibility; - _lockForSetAndRemoveOperations = [NSLock new]; - - return [[self class] sharedValetForValet:self]; -} - -#pragma mark - NSObject - -- (BOOL)isEqual:(id)object; -{ - VALLegacyValet *const otherValet = (VALLegacyValet *)object; - return [otherValet isKindOfClass:[VALLegacyValet class]] && [self isEqualToValet:otherValet]; -} - -- (NSUInteger)hash; -{ - return [self.secServiceIdentifier hash]; -} - -- (nonnull NSString *)description; -{ - return [NSString stringWithFormat:@"%@: %@ %@%@", [super description], self.identifier, (self.sharedAcrossApplications ? @"Shared " : @""), VALStringForAccessibility(self.accessibility)]; -} - -#pragma mark - NSCopying - -- (nonnull instancetype)copyWithZone:(nullable NSZone *)zone; -{ - // We're immutable, so just return self. - return self; -} - -#pragma mark - Public Methods - -- (BOOL)isEqualToValet:(nonnull VALLegacyValet *)otherValet; -{ - return [self.baseQuery isEqualToDictionary:otherValet.baseQuery]; -} - -- (BOOL)canAccessKeychain; -{ - __block BOOL canAccessKeychain = NO; - VALExecuteBlockInLock(^{ - NSString *const canaryValue = @"VAL_KeychainCanaryPassword"; - - NSString *const retrievedCanaryValue = [self stringForKey:VALCanAccessKeychainCanaryKey]; - if ([canaryValue isEqualToString:retrievedCanaryValue]) { - canAccessKeychain = YES; - - } else { - // Canary value can't be found. Manually add the value to see if we can pull it back out. - NSMutableDictionary *query = [self.baseQuery mutableCopy]; - [query addEntriesFromDictionary:[self _secItemFormatDictionaryWithKey:VALCanAccessKeychainCanaryKey]]; - query[(__bridge id)kSecValueData] = [canaryValue dataUsingEncoding:NSUTF8StringEncoding]; - (void)VALAtomicSecItemAdd((__bridge CFDictionaryRef)query, NULL); - - NSString *const retrievedCanaryValueAfterAdding = [self stringForKey:VALCanAccessKeychainCanaryKey]; - canAccessKeychain = [canaryValue isEqualToString:retrievedCanaryValueAfterAdding]; - } - - }, self.lockForSetAndRemoveOperations); - - return canAccessKeychain; -} - -- (BOOL)setObject:(nonnull NSData *)value forKey:(nonnull NSString *)key; -{ - return [self setObject:value forKey:key options:nil]; -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key; -{ - return [self objectForKey:key options:nil status:NULL]; -} - -- (BOOL)setString:(nonnull NSString *)string forKey:(nonnull NSString *)key; -{ - return [self setString:string forKey:key options:nil]; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key; -{ - return [self stringForKey:key options:nil status:NULL]; -} - -- (BOOL)containsObjectForKey:(nonnull NSString *)key; -{ - OSStatus status = [self containsObjectForKey:key options:nil]; - BOOL const keyAlreadyInKeychain = (status == errSecSuccess); - return keyAlreadyInKeychain; -} - -- (nonnull NSSet *)allKeys; -{ - return [self allKeysWithOptions:nil]; -} - -#pragma mark - Public Methods - Removal - -- (BOOL)removeObjectForKey:(NSString *)key; -{ - return [self removeObjectForKey:key options:nil]; -} - -- (BOOL)removeAllObjects; -{ - return [self removeAllObjectsWithOptions:nil]; -} - -#pragma mark - Public Methods - Migration - -- (nullable NSError *)migrateObjectsMatchingQuery:(nonnull NSDictionary *)secItemQuery removeOnCompletion:(BOOL)remove; -{ - VALCheckCondition(secItemQuery.allKeys.count > 0, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"Migration requires secItemQuery to contain values."); - VALCheckCondition(secItemQuery[(__bridge id)kSecMatchLimit] != (__bridge id)kSecMatchLimitOne, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"Migration requires kSecMatchLimit to be set to kSecMatchLimitAll."); - VALCheckCondition(secItemQuery[(__bridge id)kSecReturnData] != (__bridge id)kCFBooleanTrue, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"kSecReturnData is not supported in a migration query."); - VALCheckCondition(secItemQuery[(__bridge id)kSecReturnAttributes] != (__bridge id)kCFBooleanFalse, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"Migration requires kSecReturnAttributes to be set to kCFBooleanTrue."); - VALCheckCondition(secItemQuery[(__bridge id)kSecReturnRef] != (__bridge id)kCFBooleanTrue, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"kSecReturnRef is not supported in a migration query."); - VALCheckCondition(secItemQuery[(__bridge id)kSecReturnPersistentRef] != (__bridge id)kCFBooleanFalse, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"Migration requires SecReturnPersistentRef to be set to kCFBooleanTrue."); - VALCheckCondition([secItemQuery[(__bridge id)kSecClass] isKindOfClass:[NSString class]], [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorInvalidQuery userInfo:nil], @"Migration requires a kSecClass to be set to a valid kSecClass string."); - - NSMutableDictionary *const query = [secItemQuery mutableCopy]; - query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll; - query[(__bridge id)kSecReturnAttributes] = @YES; - query[(__bridge id)kSecReturnData] = @NO; - query[(__bridge id)kSecReturnRef] = @NO; - query[(__bridge id)kSecReturnPersistentRef] = @YES; - - CFTypeRef outTypeRef = NULL; - NSArray *queryResult = nil; - - OSStatus status = VALAtomicSecItemCopyMatching((__bridge CFDictionaryRef)query, &outTypeRef); - queryResult = (__bridge_transfer NSArray *)outTypeRef; - if (status == errSecItemNotFound) { - return [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorNoItemsToMigrateFound userInfo:nil];; - } - - VALCheckCondition(status == errSecSuccess, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorCouldNotReadKeychain userInfo:nil], @"Could not copy items matching secItemQuery"); - - // Now that we have the persistent refs with attributes, get the data associated with each keychain entry. - NSMutableArray *const queryResultWithData = [NSMutableArray new]; - for (NSDictionary *const keychainEntry in queryResult) { - CFTypeRef outValueRef = NULL; - status = VALAtomicSecItemCopyMatching((__bridge CFDictionaryRef)@{ (__bridge id)kSecValuePersistentRef : keychainEntry[(__bridge id)kSecValuePersistentRef], (__bridge id)kSecReturnData : @YES }, &outValueRef); - NSData *const data = (__bridge_transfer NSData *)outValueRef; - - VALCheckCondition(status == errSecSuccess, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorCouldNotReadKeychain userInfo:nil], @"Could not copy items matching secItemQuery"); - VALCheckCondition(data.length > 0, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorDataInQueryResultInvalid userInfo:nil], @"Can not migrate keychain entry with no value data"); - - NSMutableDictionary *const keychainEntryWithData = [keychainEntry mutableCopy]; - keychainEntryWithData[(__bridge id)kSecValueData] = data; - - [queryResultWithData addObject:keychainEntryWithData]; - } - - // Sanity check that we are capable of migrating the data. - NSMutableSet *const keysToMigrate = [NSMutableSet new]; - for (NSDictionary *const keychainEntry in queryResultWithData) { - NSString *const key = keychainEntry[(__bridge id)kSecAttrAccount]; - if ([key isKindOfClass:[NSString class]] && [key isEqualToString:VALCanAccessKeychainCanaryKey]) { - // We don't care about this key. Move along. - continue; - } - - NSData *const data = keychainEntry[(__bridge id)kSecValueData]; - - VALCheckCondition(key.length > 0, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorKeyInQueryResultInvalid userInfo:nil], @"Can not migrate keychain entry with no key"); - VALCheckCondition(data.length > 0, [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorDataInQueryResultInvalid userInfo:nil], @"Can not migrate keychain entry with no value data"); - VALCheckCondition(![keysToMigrate containsObject:key], [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorDuplicateKeyInQueryResult userInfo:nil], @"Can not migrate keychain entry for key %@ since there are multiple values for key %@ in query %@", key, key, secItemQuery); - VALCheckCondition(![self containsObjectForKey:key], [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorKeyInQueryResultAlreadyExistsInValet userInfo:nil], @"Can not migrate keychain entry for key %@ since %@ already exists in %@", key, key, self); - [keysToMigrate addObject:key]; - } - - // If all looks good, actually migrate. - NSMutableArray *const alreadyMigratedKeys = [NSMutableArray new]; - for (NSDictionary *const keychainEntry in queryResultWithData) { - NSString *const key = keychainEntry[(__bridge id)kSecAttrAccount]; - NSData *const data = keychainEntry[(__bridge id)kSecValueData]; - - if ([self setObject:data forKey:key]) { - [alreadyMigratedKeys addObject:key]; - - } else { - // Something went wrong. Remove all migrated items. - for (NSString *const alreadyMigratedKey in alreadyMigratedKeys) { - [self removeObjectForKey:alreadyMigratedKey]; - } - - return [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorCouldNotWriteToKeychain userInfo:nil]; - } - } - - // Remove data if requested. - if (remove) { - NSMutableDictionary *const removeQuery = [secItemQuery mutableCopy]; -#if !TARGET_OS_IPHONE - // This line must exist on OS X, but must not exist on iOS. - removeQuery[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll; -#endif - - status = VALAtomicSecItemDelete((__bridge CFDictionaryRef)removeQuery); - if (status != errSecSuccess) { - // Something went wrong. Remove all migrated items. - for (NSString *const alreadyMigratedKey in alreadyMigratedKeys) { - [self removeObjectForKey:alreadyMigratedKey]; - } - - return [NSError errorWithDomain:VALMigrationErrorDomain code:VALMigrationErrorRemovalFailed userInfo:nil]; - } - } - - return nil; -} - -- (nullable NSError *)migrateObjectsFromValet:(VALLegacyValet *)valet removeOnCompletion:(BOOL)remove; -{ - return [self migrateObjectsMatchingQuery:valet.baseQuery removeOnCompletion:remove]; -} - -#pragma mark - Protected Methods - -- (BOOL)setObject:(nonnull NSData *)value forKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -{ - VALCheckCondition(key.length > 0, NO, @"Can not set a value with an empty key."); - VALCheckCondition(value.length > 0, NO, @"Can not set an empty value."); - - NSMutableDictionary *const query = [self.baseQuery mutableCopy]; - [query addEntriesFromDictionary:[self _secItemFormatDictionaryWithKey:key]]; - if (options.count > 0) { - [query addEntriesFromDictionary:options]; - } - - __block OSStatus status = errSecNotAvailable; -#if TARGET_OS_IPHONE - VALExecuteBlockInLock(^{ - if ([self containsObjectForKey:key]) { - // The item already exists, so just update it. - status = VALAtomicSecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)@{ (__bridge id)kSecValueData : value }); - - } else { - // No previous item found, add the new one. - NSMutableDictionary *keychainData = [query mutableCopy]; - keychainData[(__bridge id)kSecValueData] = value; - - status = VALAtomicSecItemAdd((__bridge CFDictionaryRef)keychainData, NULL); - - if (status == errSecMissingEntitlement) { - NSLog(@"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.\n\nMore information: https://forums.developer.apple.com/thread/4743\n"); - } - } - }, self.lockForSetAndRemoveOperations); -#else - VALExecuteBlockInLock(^{ - // Never update an existing keychain item on OS X, since the existing item could have unauthorized apps in the Access Control List. Fixes zero-day Keychain vuln found here: https://drive.google.com/file/d/0BxxXk1d3yyuZOFlsdkNMSGswSGs/view - (void)VALAtomicSecItemDelete((__bridge CFDictionaryRef)query); - - // If there were an entry in the keychain for this value, we just deleted it. So just add the new value. - NSMutableDictionary *keychainData = [query mutableCopy]; - keychainData[(__bridge id)kSecValueData] = value; - - status = VALAtomicSecItemAdd((__bridge CFDictionaryRef)keychainData, NULL); - }, self.lockForSetAndRemoveOperations); -#endif - - return (status == errSecSuccess); -} - -- (nullable NSData *)objectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options status:(nullable inout OSStatus *)status; -{ - VALCheckCondition(key.length > 0, nil, @"Can not retrieve value with empty key."); - NSMutableDictionary *query = [self.baseQuery mutableCopy]; - [query addEntriesFromDictionary:[self _secItemFormatDictionaryWithKey:key]]; - [query addEntriesFromDictionary:@{ (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne, - (__bridge id)kSecReturnData : @YES }]; - if (options.count > 0) { - [query addEntriesFromDictionary:options]; - } - - CFTypeRef outTypeRef = NULL; - OSStatus const copyMatchingStatus = VALAtomicSecItemCopyMatching((__bridge CFDictionaryRef)query, &outTypeRef); - if (copyMatchingStatus == errSecMissingEntitlement) { - NSLog(@"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.\n\nMore information: https://forums.developer.apple.com/thread/4743\n"); - } - - if (status != NULL) { - *status = copyMatchingStatus; - } - - NSData *const value = (__bridge_transfer NSData *)outTypeRef; - return (copyMatchingStatus == errSecSuccess) ? value : nil; -} - -- (BOOL)setString:(nonnull NSString *)string forKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -{ - VALCheckCondition(string.length > 0, NO, @"Can not set empty string for key."); - NSData *const stringData = [string dataUsingEncoding:NSUTF8StringEncoding]; - if (stringData.length > 0) { - return [self setObject:stringData forKey:key options:options]; - } - - return NO; -} - -- (nullable NSString *)stringForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options status:(nullable inout OSStatus *)status; -{ - NSData *const stringData = [self objectForKey:key options:options status:status]; - if (stringData.length > 0) { - return [[NSString alloc] initWithBytes:stringData.bytes length:stringData.length encoding:NSUTF8StringEncoding]; - } - - return nil; -} - -- (OSStatus)containsObjectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -{ - VALCheckCondition(key.length > 0, errSecParam, @"Can not check if empty key exists in the keychain."); - - NSMutableDictionary *const query = [self.baseQuery mutableCopy]; - [query addEntriesFromDictionary:[self _secItemFormatDictionaryWithKey:key]]; - if (options.count > 0) { - [query addEntriesFromDictionary:options]; - } - - OSStatus const status = VALAtomicSecItemCopyMatching((__bridge CFDictionaryRef)query, NULL); - return status; -} - -- (nonnull NSSet *)allKeysWithOptions:(nullable NSDictionary *)options; -{ - NSSet *keys = [NSSet set]; - NSMutableDictionary *const query = [self.baseQuery mutableCopy]; - [query addEntriesFromDictionary:@{ (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll, - (__bridge id)kSecReturnAttributes : @YES }]; - if (options.count > 0) { - [query addEntriesFromDictionary:options]; - } - - CFTypeRef outTypeRef = NULL; - NSDictionary *queryResult = nil; - - OSStatus status = VALAtomicSecItemCopyMatching((__bridge CFDictionaryRef)query, &outTypeRef); - queryResult = (__bridge_transfer NSDictionary *)outTypeRef; - if (status == errSecSuccess) { - if ([queryResult isKindOfClass:[NSArray class]]) { - NSMutableSet *allKeys = [NSMutableSet new]; - for (NSDictionary *const attributes in queryResult) { - // There were many matches. - if (attributes[(__bridge id)kSecAttrAccount]) { - [allKeys addObject:attributes[(__bridge id)kSecAttrAccount]]; - } - } - - keys = [allKeys copy]; - } else if (queryResult[(__bridge id)kSecAttrAccount]) { - // There was only one match. - keys = [NSSet setWithObject:queryResult[(__bridge id)kSecAttrAccount]]; - } - } - - return keys; -} - -- (BOOL)removeObjectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -{ - VALCheckCondition(key.length > 0, NO, @"Can not remove object for empty key from the keychain."); - - NSMutableDictionary *const query = [self.baseQuery mutableCopy]; - [query addEntriesFromDictionary:[self _secItemFormatDictionaryWithKey:key]]; - if (options.count > 0) { - [query addEntriesFromDictionary:options]; - } - - __block OSStatus status = errSecNotAvailable; - VALExecuteBlockInLock(^{ - status = VALAtomicSecItemDelete((__bridge CFDictionaryRef)query); - }, self.lockForSetAndRemoveOperations); - - // We succeeded as long as we can confirm that the item is not in the keychain. - return (status != errSecInteractionNotAllowed && status != errSecMissingEntitlement); -} - -- (BOOL)removeAllObjectsWithOptions:(nullable NSDictionary *)options; -{ - for (NSString *const key in [self allKeys]) { - if (![self removeObjectForKey:key options:options]) { - return NO; - } - } - - return YES; -} - -#pragma mark - Properties - -- (nonnull NSString *)secServiceIdentifier; -{ - return self.baseQuery[(__bridge id)kSecAttrService]; -} - -#pragma mark - Private Methods - -- (nonnull NSDictionary *)_secItemFormatDictionaryWithKey:(nonnull NSString *)key; -{ - VALCheckCondition(key.length > 0, @{}, @"Must provide a valid key"); - return @{ (__bridge id)kSecAttrAccount : key }; -} - -@end diff --git a/Sources/LegacyValet/VALLegacyValet_Protected.h b/Sources/LegacyValet/VALLegacyValet_Protected.h deleted file mode 100644 index f5abc32b..00000000 --- a/Sources/LegacyValet/VALLegacyValet_Protected.h +++ /dev/null @@ -1,48 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "VALLegacyValet.h" - - -extern NSString * __nonnull VALStringForAccessibility(VALLegacyAccessibility accessibility); -extern void VALAtomicSecItemLock(__nonnull dispatch_block_t block); - - -@interface VALLegacyValet () - -/// Ensures the atomicity for set and remove operations by limiting ourselves to one instance per configuration. -/// @return An existing valet object with the same configuration as the valet provided if one exists, or the passed in valet. -+ (nonnull id)sharedValetForValet:(nonnull VALLegacyValet *)valet; - -/// Creates a base query given the injected properties. Do not override. -+ (nullable NSMutableDictionary *)mutableBaseQueryWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility initializer:(nonnull SEL)initializer; - -/// Creates a base query for shared access group Valets given the injected properties. Do not override. -+ (nullable NSMutableDictionary *)mutableBaseQueryWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility initializer:(nonnull SEL)initializer; - -/// Stores the root query to be used in all SecItem queries. -@property (nonnull, copy, readonly) NSDictionary *baseQuery; - -- (BOOL)setObject:(nonnull NSData *)value forKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -- (nullable NSData *)objectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options status:(nullable inout OSStatus *)status; -- (BOOL)setString:(nonnull NSString *)string forKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -- (nullable NSString *)stringForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options status:(nullable inout OSStatus *)status; -- (OSStatus)containsObjectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -- (nonnull NSSet *)allKeysWithOptions:(nullable NSDictionary *)options; -- (BOOL)removeObjectForKey:(nonnull NSString *)key options:(nullable NSDictionary *)options; -- (BOOL)removeAllObjectsWithOptions:(nullable NSDictionary *)options; - -@end diff --git a/Sources/LegacyValet/VALSynchronizableValet.m b/Sources/LegacyValet/VALSynchronizableValet.m deleted file mode 100644 index 0d56114c..00000000 --- a/Sources/LegacyValet/VALSynchronizableValet.m +++ /dev/null @@ -1,89 +0,0 @@ -// Created by Dan Federman on 3/16/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#import "VALSynchronizableValet.h" -#import "VALLegacyValet_Protected.h" - -#import "ValetDefines.h" - - -@implementation VALSynchronizableValet - -@synthesize baseQuery = _baseQuery; - -#pragma mark - Class Methods - -+ (BOOL)supportsSynchronizableKeychainItems; -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wtautological-compare" - -#define SUPPORTS_SYNCHRONIZABLE_KEYCHAIN_MAC (TARGET_OS_MAC && __MAC_10_9) -#define SUPPORTS_SYNCHRONIZABLE_KEYCHAIN_IOS (TARGET_OS_IPHONE && (__IPHONE_8_2 || (__IPHONE_7_0 && !TARGET_IPHONE_SIMULATOR))) - -#if SUPPORTS_SYNCHRONIZABLE_KEYCHAIN_MAC || SUPPORTS_SYNCHRONIZABLE_KEYCHAIN_IOS - return (&kSecAttrSynchronizable != NULL && &kSecAttrSynchronizableAny != NULL); -#else - return NO; -#endif - -#pragma clang diagnostic pop -} - -#pragma mark - Private Class Methods - -+ (void)_augmentBaseQuery:(nonnull NSMutableDictionary *)mutableBaseQuery; -{ -#if SUPPORTS_SYNCHRONIZABLE_KEYCHAIN_MAC || SUPPORTS_SYNCHRONIZABLE_KEYCHAIN_IOS - mutableBaseQuery[(__bridge id)kSecAttrSynchronizable] = @YES; -#endif -} - -#pragma mark - Initialization - -- (nullable instancetype)initWithIdentifier:(nonnull NSString *)identifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(accessibility == VALLegacyAccessibilityWhenUnlocked || accessibility == VALLegacyAccessibilityAfterFirstUnlock || accessibility == VALLegacyAccessibilityAlways, nil, @"Accessibility must not be scoped to this device"); - VALCheckCondition([[self class] supportsSynchronizableKeychainItems], nil, @"This device does not support synchronizing data to iCloud."); - - self = [super initWithIdentifier:identifier accessibility:accessibility]; - - NSMutableDictionary *const baseQuery = [[self class] mutableBaseQueryWithIdentifier:identifier - accessibility:accessibility - initializer:_cmd]; - [[self class] _augmentBaseQuery:baseQuery]; - _baseQuery = baseQuery; - - return [[self class] sharedValetForValet:self]; -} - -- (nullable instancetype)initWithSharedAccessGroupIdentifier:(nonnull NSString *)sharedAccessGroupIdentifier accessibility:(VALLegacyAccessibility)accessibility; -{ - VALCheckCondition(accessibility == VALLegacyAccessibilityWhenUnlocked || accessibility == VALLegacyAccessibilityAfterFirstUnlock || accessibility == VALLegacyAccessibilityAlways, nil, @"Accessibility must not be scoped to this device"); - VALCheckCondition([[self class] supportsSynchronizableKeychainItems], nil, @"This device does not support synchronizing data to iCloud."); - - self = [super initWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier accessibility:accessibility]; - - NSMutableDictionary *const baseQuery = [[self class] mutableBaseQueryWithSharedAccessGroupIdentifier:sharedAccessGroupIdentifier - accessibility:accessibility - initializer:_cmd]; - [[self class] _augmentBaseQuery:baseQuery]; - _baseQuery = baseQuery; - - return [[self class] sharedValetForValet:self]; -} - -@end diff --git a/Sources/LegacyValet/ValetDefines.h b/Sources/LegacyValet/ValetDefines.h deleted file mode 100644 index d4811fdc..00000000 --- a/Sources/LegacyValet/ValetDefines.h +++ /dev/null @@ -1,39 +0,0 @@ -// Created by Dan Federman on 2/11/15. -// Copyright 2015 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -/** - Throws a caught exception and returns "result" if "condition" is false. - - Example: - VALCheckCondition(isProperlyConfigured, nil, @"Foo was not properly configured."); - - */ -#define VALCheckCondition(condition, result, desc, ...) \ - do { \ - const BOOL conditionResult = !!(condition); \ - if (!conditionResult) { \ - @try { \ - [NSException raise:@"Valet API Misuse" format:(desc), ##__VA_ARGS__]; \ - } @catch (NSException *exception) { \ - NSLog(@"Valet API Misuse: %s %@", __PRETTY_FUNCTION__, exception.reason); \ - return result;\ - } \ - } \ - } while(0) - - -/// Error returned from Security API when the application is not entitled to perform the requested action. -#define errSecMissingEntitlement -34018 diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift deleted file mode 100644 index c3b653ad..00000000 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SecureEnclaveBackwardsCompatibilityTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -// Created by Dan Federman on 3/3/18. -// Copyright © 2018 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -@testable import Valet -import LegacyValet -import XCTest - - -extension SecureEnclaveIntegrationTests { - - @available(*, deprecated) - func test_backwardsCompatibility_withLegacyValet() throws - { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() && testEnvironmentSupportsWhenPasscodeSet() else { - return - } - - let deprecatedValet = VALLegacySecureEnclaveValet(identifier: valet.identifier.description)! - XCTAssertTrue(deprecatedValet.setString(passcode, forKey: key)) - XCTAssertEqual(passcode, try valet.string(forKey: key, withPrompt: "")) - } - -} diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift deleted file mode 100644 index a5775b1d..00000000 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift +++ /dev/null @@ -1,37 +0,0 @@ -// Created by Dan Federman on 3/3/18. -// Copyright © 2018 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -@testable import Valet -import LegacyValet -import XCTest - - -extension SinglePromptSecureEnclaveIntegrationTests { - - @available(*, deprecated) - func test_backwardsCompatibility_withLegacyValet() throws - { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() && testEnvironmentSupportsWhenPasscodeSet() else { - return - } - - let deprecatedValet = VALLegacySinglePromptSecureEnclaveValet(identifier: valet().identifier.description)! - XCTAssertTrue(deprecatedValet.setString(passcode, forKey: key)) - XCTAssertEqual(passcode, try valet().string(forKey: key, withPrompt: "")) - } - -} diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SynchronizableBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SynchronizableBackwardsCompatibilityTests.swift deleted file mode 100644 index 0408ef0a..00000000 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/SynchronizableBackwardsCompatibilityTests.swift +++ /dev/null @@ -1,54 +0,0 @@ -// Created by Dan Federman on 3/3/18. -// Copyright © 2018 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -@testable import Valet -import LegacyValet -import XCTest - - -extension CloudIntegrationTests { - - // MARK: Backwards Compatibility - - func test_backwardsCompatibility_withLegacyValet() throws { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() else { - return - } - - let identifier = Identifier(nonEmpty: "BackwardsCompatibilityTest")! - try Valet.iCloudCurrentAndLegacyPermutations(with: identifier).forEach { permutation, legacyValet in - legacyValet.setString(passcode, forKey: key) - - XCTAssertNotNil(legacyValet.string(forKey: key)) - XCTAssertEqual(legacyValet.string(forKey: key), try permutation.string(forKey: key), "\(permutation) was not able to read from legacy counterpart: \(legacyValet)") - } - } - - func test_backwardsCompatibility_withSharedAccessGroupLegacyValet() throws { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() else { - return - } - - try Valet.iCloudCurrentAndLegacyPermutations(with: Valet.sharedAccessGroupIdentifier).forEach { permutation, legacyValet in - legacyValet.setString(passcode, forKey: key) - - XCTAssertNotNil(legacyValet.string(forKey: key)) - XCTAssertEqual(legacyValet.string(forKey: key), try permutation.string(forKey: key), "\(permutation) was not able to read from legacy counterpart: \(legacyValet)") - } - } - -} diff --git a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/ValetBackwardsCompatibilityTests.swift b/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/ValetBackwardsCompatibilityTests.swift deleted file mode 100644 index 6ab7b921..00000000 --- a/Tests/ValetIntegrationTests/BackwardsCompatibilityTests/ValetBackwardsCompatibilityTests.swift +++ /dev/null @@ -1,191 +0,0 @@ -// Created by Dan Federman on 3/3/18. -// Copyright © 2018 Square, Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -//    http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -import Foundation -@testable import Valet -import LegacyValet -import XCTest - - -// MARK: – Backwards Compatibility Extensions - - -internal extension Valet { - - var legacyIdentifier: String { - switch service { - case let .sharedGroup(sharedAccessGroupIdentifier, _, _): - return sharedAccessGroupIdentifier.groupIdentifier - case let .standard(identifier, _): - return identifier.description - #if os(macOS) - case let .sharedGroupOverride(identifier, _): - return identifier.groupIdentifier - case let .standardOverride(identifier, _): - return identifier.description - #endif - } - } - - var legacyAccessibility: VALLegacyAccessibility { - switch accessibility { - case .afterFirstUnlock: return .afterFirstUnlock - case .afterFirstUnlockThisDeviceOnly: return .afterFirstUnlockThisDeviceOnly - case .whenPasscodeSetThisDeviceOnly: return .whenPasscodeSetThisDeviceOnly - case .whenUnlocked: return .whenUnlocked - case .whenUnlockedThisDeviceOnly: return .whenUnlockedThisDeviceOnly - } - } - - var legacyValet: VALLegacyValet { - switch configuration { - case .valet: - switch service { - case .standard: - return VALLegacyValet(identifier: legacyIdentifier, accessibility: legacyAccessibility)! - case .sharedGroup: - return VALLegacyValet(sharedAccessGroupIdentifier: legacyIdentifier, accessibility: legacyAccessibility)! - #if os(macOS) - case .standardOverride, - .sharedGroupOverride: - fatalError("There is no legacy Valet for a service override valet") - #endif - } - case .iCloud: - switch service { - case .standard: - return VALSynchronizableValet(identifier: legacyIdentifier, accessibility: legacyAccessibility)! - case .sharedGroup: - return VALSynchronizableValet(sharedAccessGroupIdentifier: legacyIdentifier, accessibility: legacyAccessibility)! - #if os(macOS) - case .standardOverride, - .sharedGroupOverride: - fatalError("There is no legacy Valet for a service override valet") - #endif - } - - default: - fatalError() - } - } - - // MARK: Permutations - - class func currentAndLegacyPermutations(with identifier: Identifier) -> [(Valet, VALLegacyValet)] { - permutations(with: identifier).map { - ($0, $0.legacyValet) - } - } - - class func currentAndLegacyPermutations(with identifier: SharedGroupIdentifier) -> [(Valet, VALLegacyValet)] { - permutations(with: identifier).map { - ($0, $0.legacyValet) - } - } - - class func iCloudCurrentAndLegacyPermutations(with identifier: Identifier) -> [(Valet, VALSynchronizableValet)] { - iCloudPermutations(with: identifier).map { - ($0, $0.legacyValet as! VALSynchronizableValet) - } - } - - class func iCloudCurrentAndLegacyPermutations(with identifier: SharedGroupIdentifier) -> [(Valet, VALSynchronizableValet)] { - iCloudPermutations(with: identifier).map { - ($0, $0.legacyValet as! VALSynchronizableValet) - } - } -} - -// MARK: - Tests - -class ValetBackwardsCompatibilityIntegrationTests: ValetIntegrationTests { - - // MARK: Tests - - func test_backwardsCompatibility_withLegacyValet() throws { - try Valet.currentAndLegacyPermutations(with: vanillaValet.identifier).forEach { permutation, legacyValet in - legacyValet.setString(passcode, forKey: key) - - XCTAssertNotNil(legacyValet.string(forKey: key)) - if #available(OSX 10.15, *) { - #if os(macOS) - try permutation.migrateObjectsFromPreCatalina() - #endif - } - XCTAssertEqual(legacyValet.string(forKey: key), try permutation.string(forKey: key), "\(permutation) was not able to read from legacy counterpart: \(legacyValet)") - } - } - - func test_backwardsCompatibility_withLegacySharedAccessGroupValet() throws { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() else { - return - } - try Valet.currentAndLegacyPermutations(with: Valet.sharedAccessGroupIdentifier).forEach { permutation, legacyValet in - legacyValet.setString(passcode, forKey: key) - - XCTAssertNotNil(legacyValet.string(forKey: key)) - if #available(OSX 10.15, *) { - #if os(macOS) - try permutation.migrateObjectsFromPreCatalina() - #endif - } - XCTAssertEqual(legacyValet.string(forKey: key), try permutation.string(forKey: key), "\(permutation) was not able to read from legacy counterpart: \(legacyValet)") - } - } - - func test_migrateObjectsFromAlwaysAccessibleValet_forwardsCompatibility_fromLegacyValet() throws { - let alwaysAccessibleLegacyValet = VALLegacyValet(identifier: vanillaValet.identifier.description, accessibility: .always)! - alwaysAccessibleLegacyValet.setString(passcode, forKey: key) - - let valet = Valet.valet(with: vanillaValet.identifier, accessibility: .afterFirstUnlock) - XCTAssertNoThrow(try valet.migrateObjectsFromAlwaysAccessibleValet(removeOnCompletion: true)) - XCTAssertEqual(try valet.string(forKey: key), passcode) - } - - func test_migrateObjectsFromAlwaysAccessibleThisDeviceOnlyValet_forwardsCompatibility_fromLegacyValet() throws { - let alwaysAccessibleLegacyValet = VALLegacyValet(identifier: vanillaValet.identifier.description, accessibility: .alwaysThisDeviceOnly)! - alwaysAccessibleLegacyValet.setString(passcode, forKey: key) - - let valet = Valet.valet(with: vanillaValet.identifier, accessibility: .afterFirstUnlockThisDeviceOnly) - XCTAssertNoThrow(try valet.migrateObjectsFromAlwaysAccessibleThisDeviceOnlyValet(removeOnCompletion: true)) - XCTAssertEqual(try valet.string(forKey: key), passcode) - } - - func test_migrateObjectsFromAlwaysAccessibleValet_forwardsCompatibility_withLegacySharedAccessGroupValet() throws { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() else { - return - } - let alwaysAccessibleLegacyValet = VALLegacyValet(sharedAccessGroupIdentifier: Valet.sharedAccessGroupIdentifier.groupIdentifier, accessibility: .always)! - alwaysAccessibleLegacyValet.setString(passcode, forKey: key) - - let valet = Valet.sharedGroupValet(with: Valet.sharedAccessGroupIdentifier, accessibility: .afterFirstUnlock) - XCTAssertNoThrow(try valet.migrateObjectsFromAlwaysAccessibleValet(removeOnCompletion: true)) - XCTAssertEqual(try valet.string(forKey: key), passcode) - } - - func test_migrateObjectsFromAlwaysAccessibleThisDeviceOnlyValet_forwardsCompatibility_withLegacySharedAccessGroupValet() throws { - guard testEnvironmentIsSignedOrDoesNotRequireEntitlement() else { - return - } - let alwaysAccessibleLegacyValet = VALLegacyValet(sharedAccessGroupIdentifier: Valet.sharedAccessGroupIdentifier.groupIdentifier, accessibility: .alwaysThisDeviceOnly)! - alwaysAccessibleLegacyValet.setString(passcode, forKey: key) - - let valet = Valet.sharedGroupValet(with: Valet.sharedAccessGroupIdentifier, accessibility: .afterFirstUnlockThisDeviceOnly) - XCTAssertNoThrow(try valet.migrateObjectsFromAlwaysAccessibleThisDeviceOnlyValet(removeOnCompletion: true)) - XCTAssertEqual(try valet.string(forKey: key), passcode) - } - -} diff --git a/Valet.xcodeproj/project.pbxproj b/Valet.xcodeproj/project.pbxproj index 834bd070..ecc528ff 100644 --- a/Valet.xcodeproj/project.pbxproj +++ b/Valet.xcodeproj/project.pbxproj @@ -13,14 +13,6 @@ 1612FD1422A9C95500FC1142 /* CloudIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0022A9C95400FC1142 /* CloudIntegrationTests.swift */; }; 1612FD1522A9C95500FC1142 /* CloudIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0022A9C95400FC1142 /* CloudIntegrationTests.swift */; }; 1612FD1622A9C95500FC1142 /* CloudIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0022A9C95400FC1142 /* CloudIntegrationTests.swift */; }; - 1612FD1722A9C95500FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0222A9C95400FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift */; }; - 1612FD1822A9C95500FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0222A9C95400FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift */; }; - 1612FD1A22A9C95500FC1142 /* SynchronizableBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0322A9C95400FC1142 /* SynchronizableBackwardsCompatibilityTests.swift */; }; - 1612FD1B22A9C95500FC1142 /* SynchronizableBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0322A9C95400FC1142 /* SynchronizableBackwardsCompatibilityTests.swift */; }; - 1612FD1D22A9C95500FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0422A9C95400FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift */; }; - 1612FD1E22A9C95500FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0422A9C95400FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift */; }; - 1612FD2022A9C95500FC1142 /* ValetBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0522A9C95400FC1142 /* ValetBackwardsCompatibilityTests.swift */; }; - 1612FD2122A9C95500FC1142 /* ValetBackwardsCompatibilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0522A9C95400FC1142 /* ValetBackwardsCompatibilityTests.swift */; }; 1612FD2322A9C95500FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0622A9C95400FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift */; }; 1612FD2422A9C95500FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0622A9C95400FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift */; }; 1612FD2622A9C95500FC1142 /* ValetIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0722A9C95400FC1142 /* ValetIntegrationTests.swift */; }; @@ -101,29 +93,6 @@ 1612FE1022A9CC3E00FC1142 /* LegacyValet.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 1612FE0822A9CC3E00FC1142 /* LegacyValet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 1612FE4722A9CD2000FC1142 /* LegacyValet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1612FDD322A9CB2200FC1142 /* LegacyValet.framework */; }; 1612FE5022A9D2F600FC1142 /* LegacyValet.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1612FE0822A9CC3E00FC1142 /* LegacyValet.framework */; }; - 16212CC323072C5D00C84B17 /* VALLegacySecureEnclaveValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CBF23072C5D00C84B17 /* VALLegacySecureEnclaveValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CC623072C5D00C84B17 /* VALLegacySecureEnclaveValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CBF23072C5D00C84B17 /* VALLegacySecureEnclaveValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CC723072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CC023072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CCA23072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CC023072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CCB23072C5D00C84B17 /* VALLegacyValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CC123072C5D00C84B17 /* VALLegacyValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CCE23072C5D00C84B17 /* VALLegacyValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CC123072C5D00C84B17 /* VALLegacyValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CCF23072C5D00C84B17 /* VALSynchronizableValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CC223072C5D00C84B17 /* VALSynchronizableValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CD223072C5D00C84B17 /* VALSynchronizableValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CC223072C5D00C84B17 /* VALSynchronizableValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CDB23072C6F00C84B17 /* VALLegacySecureEnclaveValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD323072C6E00C84B17 /* VALLegacySecureEnclaveValet.m */; }; - 16212CDE23072C6F00C84B17 /* VALLegacySecureEnclaveValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD323072C6E00C84B17 /* VALLegacySecureEnclaveValet.m */; }; - 16212CDF23072C6F00C84B17 /* VALLegacyValet_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CD423072C6E00C84B17 /* VALLegacyValet_Protected.h */; }; - 16212CE223072C6F00C84B17 /* VALLegacyValet_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CD423072C6E00C84B17 /* VALLegacyValet_Protected.h */; }; - 16212CE723072C6F00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD623072C6E00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m */; }; - 16212CEB23072C6F00C84B17 /* VALSynchronizableValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD723072C6E00C84B17 /* VALSynchronizableValet.m */; }; - 16212CEE23072C6F00C84B17 /* VALSynchronizableValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD723072C6E00C84B17 /* VALSynchronizableValet.m */; }; - 16212CEF23072C6F00C84B17 /* VALLegacyValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD823072C6E00C84B17 /* VALLegacyValet.m */; }; - 16212CF223072C6F00C84B17 /* VALLegacyValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD823072C6E00C84B17 /* VALLegacyValet.m */; }; - 16212CF323072C6F00C84B17 /* ValetDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CD923072C6F00C84B17 /* ValetDefines.h */; }; - 16212CF623072C6F00C84B17 /* ValetDefines.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CD923072C6F00C84B17 /* ValetDefines.h */; }; - 16212CF723072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CDA23072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h */; }; - 16212CFA23072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CDA23072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h */; }; - 16212CFC23072CB700C84B17 /* LegacyValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CFB23072CB700C84B17 /* LegacyValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 16212CFF23072CB700C84B17 /* LegacyValet.h in Headers */ = {isa = PBXBuildFile; fileRef = 16212CFB23072CB700C84B17 /* LegacyValet.h */; settings = {ATTRIBUTES = (Public, ); }; }; 165CDDC9204B26D400C96C2E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165CDDC8204B26D400C96C2E /* AppDelegate.swift */; }; 165CDDCB204B26D400C96C2E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165CDDCA204B26D400C96C2E /* ViewController.swift */; }; 165CDDCE204B26D400C96C2E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 165CDDCC204B26D400C96C2E /* Main.storyboard */; }; @@ -197,7 +166,6 @@ 32E7115B2336B03800018E15 /* SinglePromptSecureEnclaveValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD7922A9CAAB00FC1142 /* SinglePromptSecureEnclaveValet.swift */; }; 32E7115C2336B03800018E15 /* SinglePromptSecureEnclaveValet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD7922A9CAAB00FC1142 /* SinglePromptSecureEnclaveValet.swift */; }; 32E7115E2336B90800018E15 /* SinglePromptSecureEnclaveTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1612FD0A22A9C95500FC1142 /* SinglePromptSecureEnclaveTests.swift */; }; - 32E7115F2336B98800018E15 /* VALLegacySinglePromptSecureEnclaveValet.m in Sources */ = {isa = PBXBuildFile; fileRef = 16212CD623072C6E00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m */; }; 32F51A662CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; 32F51A672CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; 32F51A682CB9038D0034C42C /* WeakStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F51A652CB9038D0034C42C /* WeakStorage.swift */; }; @@ -451,10 +419,6 @@ 1612FCFE22A9C95400FC1142 /* MacTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacTests.swift; sourceTree = ""; }; 1612FCFF22A9C95400FC1142 /* SecItemTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecItemTests.swift; sourceTree = ""; }; 1612FD0022A9C95400FC1142 /* CloudIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudIntegrationTests.swift; sourceTree = ""; }; - 1612FD0222A9C95400FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift; sourceTree = ""; }; - 1612FD0322A9C95400FC1142 /* SynchronizableBackwardsCompatibilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizableBackwardsCompatibilityTests.swift; sourceTree = ""; }; - 1612FD0422A9C95400FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureEnclaveBackwardsCompatibilityTests.swift; sourceTree = ""; }; - 1612FD0522A9C95400FC1142 /* ValetBackwardsCompatibilityTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValetBackwardsCompatibilityTests.swift; sourceTree = ""; }; 1612FD0622A9C95400FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SinglePromptSecureEnclaveIntegrationTests.swift; sourceTree = ""; }; 1612FD0722A9C95400FC1142 /* ValetIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValetIntegrationTests.swift; sourceTree = ""; }; 1612FD0822A9C95400FC1142 /* SecureEnclaveIntegrationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecureEnclaveIntegrationTests.swift; sourceTree = ""; }; @@ -479,18 +443,6 @@ 1612FDD322A9CB2200FC1142 /* LegacyValet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LegacyValet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1612FDF022A9CBC900FC1142 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1612FE0822A9CC3E00FC1142 /* LegacyValet.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = LegacyValet.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 16212CBF23072C5D00C84B17 /* VALLegacySecureEnclaveValet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VALLegacySecureEnclaveValet.h; path = LegacyValet/Public/VALLegacySecureEnclaveValet.h; sourceTree = ""; }; - 16212CC023072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VALLegacySinglePromptSecureEnclaveValet.h; path = LegacyValet/Public/VALLegacySinglePromptSecureEnclaveValet.h; sourceTree = ""; }; - 16212CC123072C5D00C84B17 /* VALLegacyValet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VALLegacyValet.h; path = LegacyValet/Public/VALLegacyValet.h; sourceTree = ""; }; - 16212CC223072C5D00C84B17 /* VALSynchronizableValet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VALSynchronizableValet.h; path = LegacyValet/Public/VALSynchronizableValet.h; sourceTree = ""; }; - 16212CD323072C6E00C84B17 /* VALLegacySecureEnclaveValet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VALLegacySecureEnclaveValet.m; path = LegacyValet/VALLegacySecureEnclaveValet.m; sourceTree = ""; }; - 16212CD423072C6E00C84B17 /* VALLegacyValet_Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VALLegacyValet_Protected.h; path = LegacyValet/VALLegacyValet_Protected.h; sourceTree = ""; }; - 16212CD623072C6E00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VALLegacySinglePromptSecureEnclaveValet.m; path = LegacyValet/VALLegacySinglePromptSecureEnclaveValet.m; sourceTree = ""; }; - 16212CD723072C6E00C84B17 /* VALSynchronizableValet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VALSynchronizableValet.m; path = LegacyValet/VALSynchronizableValet.m; sourceTree = ""; }; - 16212CD823072C6E00C84B17 /* VALLegacyValet.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = VALLegacyValet.m; path = LegacyValet/VALLegacyValet.m; sourceTree = ""; }; - 16212CD923072C6F00C84B17 /* ValetDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ValetDefines.h; path = LegacyValet/ValetDefines.h; sourceTree = ""; }; - 16212CDA23072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = VALLegacySecureEnclaveValet_Protected.h; path = LegacyValet/VALLegacySecureEnclaveValet_Protected.h; sourceTree = ""; }; - 16212CFB23072CB700C84B17 /* LegacyValet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LegacyValet.h; path = LegacyValet/Public/LegacyValet.h; sourceTree = ""; }; 163F018D23D77E9800BBD3E3 /* Valet watchOS Test Host App.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Valet watchOS Test Host App.entitlements"; sourceTree = ""; }; 165CDDC6204B26D400C96C2E /* Valet tvOS Test Host App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Valet tvOS Test Host App.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 165CDDC8204B26D400C96C2E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -691,7 +643,6 @@ 1612FCFF22A9C95400FC1142 /* SecItemTests.swift */, 1612FD0022A9C95400FC1142 /* CloudIntegrationTests.swift */, 32DC88912475BA5F005A9BFA /* KeychainIntegrationTests.swift */, - 1612FD0122A9C95400FC1142 /* BackwardsCompatibilityTests */, 1612FD0622A9C95400FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift */, 1612FD0722A9C95400FC1142 /* ValetIntegrationTests.swift */, 1612FD0822A9C95400FC1142 /* SecureEnclaveIntegrationTests.swift */, @@ -699,17 +650,6 @@ path = ValetIntegrationTests; sourceTree = ""; }; - 1612FD0122A9C95400FC1142 /* BackwardsCompatibilityTests */ = { - isa = PBXGroup; - children = ( - 1612FD0222A9C95400FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift */, - 1612FD0322A9C95400FC1142 /* SynchronizableBackwardsCompatibilityTests.swift */, - 1612FD0422A9C95400FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift */, - 1612FD0522A9C95400FC1142 /* ValetBackwardsCompatibilityTests.swift */, - ); - path = BackwardsCompatibilityTests; - sourceTree = ""; - }; 1612FD0922A9C95500FC1142 /* ValetTests */ = { isa = PBXGroup; children = ( @@ -758,33 +698,6 @@ path = Internal; sourceTree = ""; }; - 161FB60D22F8EBB700E8F785 /* LegacyValet */ = { - isa = PBXGroup; - children = ( - 16212CD923072C6F00C84B17 /* ValetDefines.h */, - 16212CDA23072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h */, - 16212CD323072C6E00C84B17 /* VALLegacySecureEnclaveValet.m */, - 16212CD623072C6E00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m */, - 16212CD423072C6E00C84B17 /* VALLegacyValet_Protected.h */, - 16212CD823072C6E00C84B17 /* VALLegacyValet.m */, - 16212CD723072C6E00C84B17 /* VALSynchronizableValet.m */, - 161FB64F22F8EBE200E8F785 /* Public */, - ); - name = LegacyValet; - sourceTree = ""; - }; - 161FB64F22F8EBE200E8F785 /* Public */ = { - isa = PBXGroup; - children = ( - 16212CFB23072CB700C84B17 /* LegacyValet.h */, - 16212CBF23072C5D00C84B17 /* VALLegacySecureEnclaveValet.h */, - 16212CC023072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h */, - 16212CC123072C5D00C84B17 /* VALLegacyValet.h */, - 16212CC223072C5D00C84B17 /* VALSynchronizableValet.h */, - ); - name = Public; - sourceTree = ""; - }; 165CDDC7204B26D400C96C2E /* Valet tvOS Test Host App */ = { isa = PBXGroup; children = ( @@ -868,7 +781,6 @@ children = ( 1612FDF022A9CBC900FC1142 /* Info.plist */, 1612FD6A22A9CAAB00FC1142 /* Valet */, - 161FB60D22F8EBB700E8F785 /* LegacyValet */, ); path = Sources; sourceTree = ""; @@ -978,14 +890,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 16212CCF23072C5D00C84B17 /* VALSynchronizableValet.h in Headers */, - 16212CC723072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h in Headers */, - 16212CF323072C6F00C84B17 /* ValetDefines.h in Headers */, - 16212CC323072C5D00C84B17 /* VALLegacySecureEnclaveValet.h in Headers */, - 16212CFC23072CB700C84B17 /* LegacyValet.h in Headers */, - 16212CDF23072C6F00C84B17 /* VALLegacyValet_Protected.h in Headers */, - 16212CCB23072C5D00C84B17 /* VALLegacyValet.h in Headers */, - 16212CF723072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -993,14 +897,6 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - 16212CD223072C5D00C84B17 /* VALSynchronizableValet.h in Headers */, - 16212CCA23072C5D00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.h in Headers */, - 16212CF623072C6F00C84B17 /* ValetDefines.h in Headers */, - 16212CC623072C5D00C84B17 /* VALLegacySecureEnclaveValet.h in Headers */, - 16212CFF23072CB700C84B17 /* LegacyValet.h in Headers */, - 16212CE223072C6F00C84B17 /* VALLegacyValet_Protected.h in Headers */, - 16212CCE23072C5D00C84B17 /* VALLegacyValet.h in Headers */, - 16212CFA23072C6F00C84B17 /* VALLegacySecureEnclaveValet_Protected.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1654,10 +1550,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16212CEB23072C6F00C84B17 /* VALSynchronizableValet.m in Sources */, - 16212CDB23072C6F00C84B17 /* VALLegacySecureEnclaveValet.m in Sources */, - 16212CE723072C6F00C84B17 /* VALLegacySinglePromptSecureEnclaveValet.m in Sources */, - 16212CEF23072C6F00C84B17 /* VALLegacyValet.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1665,10 +1557,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16212CEE23072C6F00C84B17 /* VALSynchronizableValet.m in Sources */, - 32E7115F2336B98800018E15 /* VALLegacySinglePromptSecureEnclaveValet.m in Sources */, - 16212CDE23072C6F00C84B17 /* VALLegacySecureEnclaveValet.m in Sources */, - 16212CF223072C6F00C84B17 /* VALLegacyValet.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1859,21 +1747,17 @@ 1612FD3522A9C95500FC1142 /* ValetTests.swift in Sources */, 1612FD2922A9C95500FC1142 /* SecureEnclaveIntegrationTests.swift in Sources */, 1612FD2322A9C95500FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift in Sources */, - 1612FD1722A9C95500FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift in Sources */, 167E250223D624EF00889121 /* MigrationErrorTests.swift in Sources */, - 1612FD1D22A9C95500FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift in Sources */, 1612FD2F22A9C95500FC1142 /* SecureEnclaveTests.swift in Sources */, 1612FD3222A9C95500FC1142 /* CloudTests.swift in Sources */, 1612FD2C22A9C95500FC1142 /* SinglePromptSecureEnclaveTests.swift in Sources */, 169E9A6723D181DC001B69F5 /* VALSinglePromptSecureEnclaveValetTests.m in Sources */, 1612FD2622A9C95500FC1142 /* ValetIntegrationTests.swift in Sources */, 32DC88932475BB9F005A9BFA /* KeychainIntegrationTests.swift in Sources */, - 1612FD2022A9C95500FC1142 /* ValetBackwardsCompatibilityTests.swift in Sources */, 169E9A6D23D181DC001B69F5 /* VALSecureEnclaveValetTests.m in Sources */, 167E251023D6328E00889121 /* ConfigurationTests.swift in Sources */, 167E250723D62CAA00889121 /* CloudAccessibilityTests.swift in Sources */, 167E24FE23D6235000889121 /* KeychainErrorTests.swift in Sources */, - 1612FD1A22A9C95500FC1142 /* SynchronizableBackwardsCompatibilityTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1887,8 +1771,6 @@ 1612FD3622A9C95500FC1142 /* ValetTests.swift in Sources */, 1612FD2A22A9C95500FC1142 /* SecureEnclaveIntegrationTests.swift in Sources */, 1612FD2422A9C95500FC1142 /* SinglePromptSecureEnclaveIntegrationTests.swift in Sources */, - 1612FD1822A9C95500FC1142 /* SinglePromptSecureEnclaveBackwardsCompatibilityTests.swift in Sources */, - 1612FD1E22A9C95500FC1142 /* SecureEnclaveBackwardsCompatibilityTests.swift in Sources */, 1612FD3022A9C95500FC1142 /* SecureEnclaveTests.swift in Sources */, 1612FD3322A9C95500FC1142 /* CloudTests.swift in Sources */, 1612FD3822A9C96900FC1142 /* MacTests.swift in Sources */, @@ -1901,8 +1783,6 @@ 169E9A6E23D181DC001B69F5 /* VALSecureEnclaveValetTests.m in Sources */, 169E9A6B23D181DC001B69F5 /* VALValetTests.m in Sources */, 1612FD2722A9C95500FC1142 /* ValetIntegrationTests.swift in Sources */, - 1612FD2122A9C95500FC1142 /* ValetBackwardsCompatibilityTests.swift in Sources */, - 1612FD1B22A9C95500FC1142 /* SynchronizableBackwardsCompatibilityTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 189a9f15ac55a0a389688c65bf2f4a17457b9e7c Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:37:39 -0700 Subject: [PATCH 13/16] Update README --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 514b1e8a..e7871a7e 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ Valet guarantees that reading and writing operations will succeed as long as wri ## 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 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. 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. @@ -268,6 +268,12 @@ You'll also need to continue reading through the [migration from Valet 3](#migra 1. 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](https://docs.swift.org/swift-book/LanguageGuide/ErrorHandling.html#ID541) 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](#reading-and-writing). 1. 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](https://github.com/square/Valet/pull/218), 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](#sharing-secrets-among-multiple-applications-using-an-app-groups-entitlement). +### Migrating from Valet 4 + +1. Most `throw`ing methods now utilize [typed throws](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md), which may render certain `catch` statements obsolete. +1. `SecureEnclaveValet`'s `withPrompt` API were removed on tvOS and watchOS, as recent API updates revealed that this API never actually showed a prompt on device. New API were added to perform the same actions without a custom prompt. +1. `SinglePromptSecureEnclaveValet` was removed from watchOS, as recent API updates revealed this API did not work as intended on watchOS. If you were previously deploying a ``SinglePromptSecureEnclaveValet`` on watchOS, use the method TODO ADD MIGRATION METHOD HERE on a `SecureEnclaveValet` to migrate your key:value pairs to a `SecureEnclaveValet`. + ## Contributing We’re glad you’re interested in Valet, and we’d love to see where you take it. Please read our [contributing guidelines](Contributing.md) prior to submitting a Pull Request. From b96e47913ed99ee588f6c98c75594b695be7c3fb Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 09:56:16 -0700 Subject: [PATCH 14/16] Add migration method --- README.md | 2 +- Sources/Valet/SecureEnclaveValet.swift | 34 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e7871a7e..e63e267f 100644 --- a/README.md +++ b/README.md @@ -272,7 +272,7 @@ You'll also need to continue reading through the [migration from Valet 3](#migra 1. Most `throw`ing methods now utilize [typed throws](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0413-typed-throws.md), which may render certain `catch` statements obsolete. 1. `SecureEnclaveValet`'s `withPrompt` API were removed on tvOS and watchOS, as recent API updates revealed that this API never actually showed a prompt on device. New API were added to perform the same actions without a custom prompt. -1. `SinglePromptSecureEnclaveValet` was removed from watchOS, as recent API updates revealed this API did not work as intended on watchOS. If you were previously deploying a ``SinglePromptSecureEnclaveValet`` on watchOS, use the method TODO ADD MIGRATION METHOD HERE on a `SecureEnclaveValet` to migrate your key:value pairs to a `SecureEnclaveValet`. +1. `SinglePromptSecureEnclaveValet` was removed from watchOS, as recent API updates revealed this API did not work as intended on watchOS. If you were previously deploying a ``SinglePromptSecureEnclaveValet`` on watchOS, use the method `migrateObjectsFromSinglePromptSecureEnclaveValet(removeOnCompletion:)` on a `SecureEnclaveValet` with the same identifiers and access control to migrate your existing key:value pairs. ## Contributing diff --git a/Sources/Valet/SecureEnclaveValet.swift b/Sources/Valet/SecureEnclaveValet.swift index 5c965063..16d68598 100644 --- a/Sources/Valet/SecureEnclaveValet.swift +++ b/Sources/Valet/SecureEnclaveValet.swift @@ -260,6 +260,26 @@ public final class SecureEnclaveValet: NSObject, Sendable { try migrateObjects(matching: valet.baseKeychainQuery, removeOnCompletion: removeOnCompletion) } +#if os(watchOS) + /// Call this method to migrate from a `SinglePromptSecureEnclaveValet` used on watchOS. + /// This method migrates objects set on a `SinglePromptSecureEnclaveValet` with the same identifier and access control to the receiver. + /// - Parameter removeOnCompletion: If `true`, the migrated data will be removed from the keychain if the migration succeeds. + /// - Throws: An error of type `KeychainError` or `MigrationError`. + /// - Note: The keychain is not modified if an error is thrown. + @objc + public func migrateObjectsFromSinglePromptSecureEnclaveValet(removeOnCompletion: Bool) throws { + try execute(in: lock) { + try Keychain.migrateObjects( + matching: service + .asSinglePromptSecureEnclave(withAccessControl: accessControl) + .generateBaseQuery(), + into: baseKeychainQuery, + removeOnCompletion: removeOnCompletion + ) + } + } +#endif + // MARK: Internal Properties internal let service: Service @@ -335,3 +355,17 @@ extension SecureEnclaveValet { #endif } + + +#if os(watchOS) +extension Service { + func asSinglePromptSecureEnclave(withAccessControl accessControl: SecureEnclaveAccessControl) -> Service { + switch self { + case let .standard(identifier, _): + .standard(identifier, .singlePromptSecureEnclave(accessControl)) + case let .sharedGroup(sharedGroupIdentifier, identifier, _): + .sharedGroup(sharedGroupIdentifier, identifier, .singlePromptSecureEnclave(accessControl)) + } + } +} +#endif From c96a895d73e6e2ff6deedbca601625cc68524419 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 10:01:19 -0700 Subject: [PATCH 15/16] Update missing documentation --- Sources/Valet/SecureEnclaveValet.swift | 3 ++- Sources/Valet/SinglePromptSecureEnclaveValet.swift | 3 ++- Sources/Valet/Valet.swift | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/Valet/SecureEnclaveValet.swift b/Sources/Valet/SecureEnclaveValet.swift index 16d68598..14c95bc4 100644 --- a/Sources/Valet/SecureEnclaveValet.swift +++ b/Sources/Valet/SecureEnclaveValet.swift @@ -40,7 +40,8 @@ public final class SecureEnclaveValet: NSObject, Sendable { } /// - Parameters: - /// - identifier: A non-empty string that must correspond with the value for keychain-access-groups in your Entitlements file. + /// - groupIdentifier: The identifier for the Valet's shared access group. Must correspond with the value for keychain-access-groups in your Entitlements file. + /// - identifier: An optional additional uniqueness identifier. Using this identifier allows for the creation of separate, sandboxed Valets within the same shared access group. /// - accessControl: The desired access control for the SecureEnclaveValet. /// - Returns: A SecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team. public class func sharedGroupValet(with groupIdentifier: SharedGroupIdentifier, identifier: Identifier? = nil, accessControl: SecureEnclaveAccessControl) -> SecureEnclaveValet { diff --git a/Sources/Valet/SinglePromptSecureEnclaveValet.swift b/Sources/Valet/SinglePromptSecureEnclaveValet.swift index 589fd005..e702b6fe 100644 --- a/Sources/Valet/SinglePromptSecureEnclaveValet.swift +++ b/Sources/Valet/SinglePromptSecureEnclaveValet.swift @@ -43,7 +43,8 @@ public final class SinglePromptSecureEnclaveValet: NSObject, @unchecked Sendable } /// - Parameters: - /// - identifier: A non-empty identifier that must correspond with the value for keychain-access-groups in your Entitlements file. + /// - groupIdentifier: The identifier for the Valet's shared access group. Must correspond with the value for keychain-access-groups in your Entitlements file. + /// - identifier: An optional additional uniqueness identifier. Using this identifier allows for the creation of separate, sandboxed Valets within the same shared access group. /// - accessControl: The desired access control for the SinglePromptSecureEnclaveValet. /// - Returns: A SinglePromptSecureEnclaveValet that reads/writes keychain elements that can be shared across applications written by the same development team. public class func sharedGroupValet(with groupIdentifier: SharedGroupIdentifier, identifier: Identifier? = nil, accessControl: SecureEnclaveAccessControl) -> SinglePromptSecureEnclaveValet { diff --git a/Sources/Valet/Valet.swift b/Sources/Valet/Valet.swift index 9f5649da..93fbaf15 100644 --- a/Sources/Valet/Valet.swift +++ b/Sources/Valet/Valet.swift @@ -50,7 +50,8 @@ public final class Valet: NSObject, Sendable { } /// - Parameters: - /// - identifier: The identifier for the Valet's shared access group. Must correspond with the value for keychain-access-groups in your Entitlements file. + /// - groupIdentifier: The identifier for the Valet's shared access group. Must correspond with the value for keychain-access-groups in your Entitlements file. + /// - identifier: An optional additional uniqueness identifier. Using this identifier allows for the creation of separate, sandboxed Valets within the same shared access group. /// - accessibility: The desired accessibility for the Valet. /// - Returns: A Valet (synchronized with iCloud) that reads/writes keychain elements that can be shared across applications written by the same development team. public class func iCloudSharedGroupValet(with groupIdentifier: SharedGroupIdentifier, identifier: Identifier? = nil, accessibility: CloudAccessibility) -> Valet { From 969d2c6353d191b1881c602f20622b3332c7e289 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 14 Oct 2024 10:03:14 -0700 Subject: [PATCH 16/16] Right-size timeout for consistency --- Tests/ValetIntegrationTests/ValetIntegrationTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift index 991644a9..8068fac1 100644 --- a/Tests/ValetIntegrationTests/ValetIntegrationTests.swift +++ b/Tests/ValetIntegrationTests/ValetIntegrationTests.swift @@ -712,7 +712,7 @@ class ValetIntegrationTests: XCTestCase await fulfillment(of: [ expectation, - ], timeout: 10.0) + ], timeout: 5.0) } func test_stringForKey_canReadDataWrittenToValetAllocatedOnDifferentThread() async @@ -744,7 +744,7 @@ class ValetIntegrationTests: XCTestCase await fulfillment(of: [ expectation, - ], timeout: 10.0) + ], timeout: 5.0) } // MARK: Removal