Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AccessControl support to KeychainSwift #191

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions Demo/Base.lproj/Main.storyboard
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11173.2" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="11143.2"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="22684"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
Expand All @@ -16,10 +16,11 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hWK-Bu-8u4">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="hWK-Bu-8u4">
<rect key="frame" x="111.66666666666669" y="206" width="170" height="35"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<state key="normal" title="Delete from Keychain">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
Expand All @@ -28,7 +29,8 @@
<action selector="onDeleteTapped:" destination="BYZ-38-t0r" eventType="touchUpInside" id="AY0-oE-tv6"/>
</connections>
</button>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b4T-JG-Zbo">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b4T-JG-Zbo">
<rect key="frame" x="130" y="151" width="133" height="35"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<state key="normal" title="Save in Keychain">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
Expand All @@ -38,21 +40,27 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NDv-PR-OK3">
<rect key="frame" x="24" y="433" width="337" height="20.333333333333314"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="Hello World!" borderStyle="roundedRect" placeholder="Text to save in KeychainSwift" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="xkp-Od-gCQ">
<rect key="frame" x="16" y="93" width="361" height="34"/>
<fontDescription key="fontDescription" type="system" pointSize="14"/>
<textInputTraits key="textInputTraits"/>
</textField>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="w7p-LD-g9j"/>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="w7p-LD-g9j">
<rect key="frame" x="222" y="261" width="51" height="31"/>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Synchronizable" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pZ7-Ub-QmV">
<rect key="frame" x="88" y="266.33333333333331" width="117" height="20.333333333333314"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QP7-SR-8QJ">
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="system" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="QP7-SR-8QJ">
<rect key="frame" x="123" y="378" width="147" height="35"/>
<fontDescription key="fontDescription" style="UICTFontTextStyleHeadline"/>
<state key="normal" title="Get from Keychain">
<color key="titleShadowColor" red="0.5" green="0.5" blue="0.5" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
Expand All @@ -61,25 +69,38 @@
<action selector="onGetTapped:" destination="BYZ-38-t0r" eventType="touchUpInside" id="OdD-Mn-cTm"/>
</connections>
</button>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="DAp-er-6cj">
<rect key="frame" x="222" y="307" width="51" height="31"/>
</switch>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="use Access Control" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZmW-v4-Kwh">
<rect key="frame" x="59" y="312" width="147" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<gestureRecognizers/>
<constraints>
<constraint firstItem="QP7-SR-8QJ" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" id="2Qi-WJ-pvW"/>
<constraint firstItem="w7p-LD-g9j" firstAttribute="centerX" secondItem="8bC-Xf-vdC" secondAttribute="centerX" constant="50" id="5De-mx-U8l"/>
<constraint firstItem="DAp-er-6cj" firstAttribute="leading" secondItem="w7p-LD-g9j" secondAttribute="leading" id="7rQ-Af-BBQ"/>
<constraint firstItem="w7p-LD-g9j" firstAttribute="top" secondItem="hWK-Bu-8u4" secondAttribute="bottom" constant="20" id="BMz-4l-Y3k"/>
<constraint firstItem="NDv-PR-OK3" firstAttribute="top" secondItem="QP7-SR-8QJ" secondAttribute="bottom" constant="20" id="HOc-KB-LXB"/>
<constraint firstItem="pZ7-Ub-QmV" firstAttribute="centerY" secondItem="w7p-LD-g9j" secondAttribute="centerY" id="Iau-2m-FSy"/>
<constraint firstItem="DAp-er-6cj" firstAttribute="leading" secondItem="ZmW-v4-Kwh" secondAttribute="trailing" constant="16" id="OOI-pu-CJj"/>
<constraint firstAttribute="centerX" secondItem="hWK-Bu-8u4" secondAttribute="centerX" id="VJ4-XC-zFq"/>
<constraint firstAttribute="centerX" secondItem="b4T-JG-Zbo" secondAttribute="centerX" id="cbI-es-jn6"/>
<constraint firstItem="NDv-PR-OK3" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" constant="8" id="f2v-aI-fuD"/>
<constraint firstItem="QP7-SR-8QJ" firstAttribute="top" secondItem="w7p-LD-g9j" secondAttribute="bottom" constant="40" id="fPe-Dn-3wy"/>
<constraint firstItem="QP7-SR-8QJ" firstAttribute="top" secondItem="DAp-er-6cj" secondAttribute="bottom" constant="40" id="fPe-Dn-3wy"/>
<constraint firstItem="b4T-JG-Zbo" firstAttribute="top" secondItem="xkp-Od-gCQ" secondAttribute="bottom" constant="24" id="hTO-Zq-9F4"/>
<constraint firstAttribute="trailingMargin" secondItem="xkp-Od-gCQ" secondAttribute="trailing" id="jW7-wf-Lg2"/>
<constraint firstItem="hWK-Bu-8u4" firstAttribute="top" secondItem="b4T-JG-Zbo" secondAttribute="bottom" constant="20" id="oVb-ZZ-R8a"/>
<constraint firstItem="w7p-LD-g9j" firstAttribute="leading" secondItem="pZ7-Ub-QmV" secondAttribute="trailing" constant="17" id="rR7-DW-fh2"/>
<constraint firstItem="xkp-Od-gCQ" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leadingMargin" id="sBp-QB-I3r"/>
<constraint firstItem="DAp-er-6cj" firstAttribute="top" secondItem="w7p-LD-g9j" secondAttribute="bottom" constant="15" id="uGk-uc-iYk"/>
<constraint firstAttribute="trailingMargin" secondItem="NDv-PR-OK3" secondAttribute="trailing" constant="16" id="vAI-Yb-Kci"/>
<constraint firstItem="ZmW-v4-Kwh" firstAttribute="centerY" secondItem="DAp-er-6cj" secondAttribute="centerY" id="z9t-LG-SD2"/>
<constraint firstItem="xkp-Od-gCQ" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" constant="34" id="zhk-dr-xC7"/>
</constraints>
<connections>
Expand All @@ -89,6 +110,7 @@
<connections>
<outlet property="synchronizableSwitch" destination="w7p-LD-g9j" id="xKt-Tv-MBN"/>
<outlet property="textField" destination="xkp-Od-gCQ" id="lld-hT-WbW"/>
<outlet property="useAccessControlSwitch" destination="DAp-er-6cj" id="207-0D-JLo"/>
<outlet property="valueLabel" destination="NDv-PR-OK3" id="O2G-6d-xCJ"/>
</connections>
</viewController>
Expand All @@ -99,6 +121,7 @@
</connections>
</tapGestureRecognizer>
</objects>
<point key="canvasLocation" x="129" y="-17"/>
</scene>
</scenes>
</document>
2 changes: 2 additions & 0 deletions Demo/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSFaceIDUsageDescription</key>
<string>Authenticate to access the items in keychain using appropriate access control.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand Down
13 changes: 11 additions & 2 deletions Demo/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ class ViewController: UIViewController {

@IBOutlet weak var synchronizableSwitch: UISwitch!

let keychain = KeychainSwift()
@IBOutlet weak var useAccessControlSwitch: UISwitch!

let keychain = KeychainSwift()

override func viewDidLoad() {
super.viewDidLoad()
Expand All @@ -24,7 +26,14 @@ class ViewController: UIViewController {

if let text = textField.text {
keychain.synchronizable = synchronizableSwitch.isOn
keychain.set(text, forKey: TegKeychainDemo_keyName)
if useAccessControlSwitch.isOn {
keychain.set(text,
forKey: TegKeychainDemo_keyName,
withAccess: .accessibleWhenUnlocked,
usingAccessControl: .userPresence)
} else {
keychain.set(text, forKey: TegKeychainDemo_keyName)
}
updateValueLabel()
}
}
Expand Down
29 changes: 22 additions & 7 deletions Sources/KeychainSwift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,14 @@ open class KeychainSwift {
*/
@discardableResult
open func set(_ value: String, forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
withAccess access: KeychainSwiftAccessOptions? = nil,
usingAccessControl accessControl: SecAccessControlCreateFlags? = nil) -> Bool {

if let value = value.data(using: String.Encoding.utf8) {
return set(value, forKey: key, withAccess: access)
return set(value,
forKey: key,
withAccess: access,
usingAccessControl: accessControl)
}

return false
Expand All @@ -82,27 +86,38 @@ open class KeychainSwift {

*/
@discardableResult
open func set(_ value: Data, forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil) -> Bool {
open func set(_ value: Data,
forKey key: String,
withAccess access: KeychainSwiftAccessOptions? = nil,
usingAccessControl accessControl: SecAccessControlCreateFlags? = nil) -> Bool {

// The lock prevents the code to be run simultaneously
// from multiple threads which may result in crashing
lock.lock()
defer { lock.unlock() }

deleteNoLock(key) // Delete any existing key before saving it

let accessible = access?.value ?? KeychainSwiftAccessOptions.defaultOption.value

let prefixedKey = keyWithPrefix(key)

var query: [String : Any] = [
KeychainSwiftConstants.klass : kSecClassGenericPassword,
KeychainSwiftConstants.attrAccount : prefixedKey,
KeychainSwiftConstants.valueData : value,
KeychainSwiftConstants.accessible : accessible
//KeychainSwiftConstants.accessible : accessible
]

let accessible = access ?? KeychainSwiftAccessOptions.defaultOption
if let accessControl,
let accessControlFlag = accessible.getAccessControl(withControl: accessControl) {
/// Setting accessControl with its its accessibility value.
query[KeychainSwiftConstants.accessControl] = accessControlFlag
} else {
/// Setting (just) accessible value into query.
query[KeychainSwiftConstants.accessible] = accessible.value
}


query = addAccessGroupWhenPresent(query)
query = addSynchronizableIfRequired(query, addingItems: true)
lastQueryParameters = query
Expand Down
62 changes: 46 additions & 16 deletions Sources/KeychainSwiftAccessOptions.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Security
import Foundation

/**

Expand Down Expand Up @@ -59,25 +60,54 @@ public enum KeychainSwiftAccessOptions {
}

var value: String {
switch self {
case .accessibleWhenUnlocked:
return toString(kSecAttrAccessibleWhenUnlocked)

case .accessibleWhenUnlockedThisDeviceOnly:
return toString(kSecAttrAccessibleWhenUnlockedThisDeviceOnly)

case .accessibleAfterFirstUnlock:
return toString(kSecAttrAccessibleAfterFirstUnlock)

case .accessibleAfterFirstUnlockThisDeviceOnly:
return toString(kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)

case .accessibleWhenPasscodeSetThisDeviceOnly:
return toString(kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly)
}
return toString(self.rawValue)
}

func toString(_ value: CFString) -> String {
return KeychainSwiftConstants.toString(value)
}
}


extension KeychainSwiftAccessOptions {
/// Setting RawValue as its CFString value.
var rawValue : CFString {
switch self {
case .accessibleWhenUnlocked:
return kSecAttrAccessibleWhenUnlocked
case .accessibleWhenUnlockedThisDeviceOnly:
return kSecAttrAccessibleWhenUnlockedThisDeviceOnly
case .accessibleAfterFirstUnlock:
return kSecAttrAccessibleAfterFirstUnlock
case .accessibleAfterFirstUnlockThisDeviceOnly:
return kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
case .accessibleWhenPasscodeSetThisDeviceOnly:
return kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
}
}
}

extension KeychainSwiftAccessOptions {


/// Craete and erturn an SecAccessControl, with indicated access control and access options.
/// - Parameter flags: Access control value.
/// - Returns: Object used in query to keychain.
func getAccessControl(withControl flags: SecAccessControlCreateFlags) -> SecAccessControl? {

var error : Unmanaged<CFError>? = nil
guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
self.rawValue,
flags,
&error) else {
#if DEBUG
if let error {
print("Error in creating flag:", error)
}
#endif
return nil
}
return accessControl

}
}
Loading