Merge branch 'feature/feature-touchidunlock'

# Conflicts:
#	MacPass.xcodeproj/project.pbxproj
#	MacPass/Base.lproj/PasswordInputView.xib
#	MacPass/MPDocument.m
This commit is contained in:
Michael Starke
2022-07-07 19:52:19 +02:00
15 changed files with 448 additions and 51 deletions

View File

@@ -319,6 +319,7 @@
6021FE9818E1650F00C3BC51 /* DatabaseSettingsWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 6021FE9A18E1650F00C3BC51 /* DatabaseSettingsWindow.xib */; };
7837112C225540D1009BD28D /* PluginRepositoryBrowserView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7837112E225540D1009BD28D /* PluginRepositoryBrowserView.xib */; };
78E1F8B022E3A5D600E738AE /* AutotypeDoctorReportViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 78E1F8B222E3A5D600E738AE /* AutotypeDoctorReportViewController.xib */; };
AF105CF325FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.m in Sources */ = {isa = PBXBuildFile; fileRef = AF105CF125FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.m */; };
FA13910C1F9CD9EB0033D256 /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = FA13910A1F9CD9EB0033D256 /* Localizable.stringsdict */; };
FA9FD3271FB5E8F4003CEDD6 /* AutotypeCandidateSelectionView.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA9FD3291FB5E8F4003CEDD6 /* AutotypeCandidateSelectionView.xib */; };
FA9FD32C1FB5EDD3003CEDD6 /* AutotypeBuilderView.xib in Resources */ = {isa = PBXBuildFile; fileRef = FA9FD32E1FB5EDD3003CEDD6 /* AutotypeBuilderView.xib */; };
@@ -1114,6 +1115,8 @@
ABE8662E2316617500201125 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
ABE8662F2316617500201125 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/AutotypeDoctorReportViewController.strings"; sourceTree = "<group>"; };
ABE86630231662D200201125 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/PluginDataView.strings"; sourceTree = "<group>"; };
AF105CF125FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MPTouchIdCompositeKeyStore.m; sourceTree = "<group>"; };
AF105CF225FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MPTouchIdCompositeKeyStore.h; sourceTree = "<group>"; };
BB3E050C1FE9D1CA00F0B46F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/AutotypeCandidateSelectionView.strings; sourceTree = "<group>"; };
BB3E050D1FE9D1CB00F0B46F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = nl; path = nl.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
BB3E050E1FE9D1CC00F0B46F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/ReferenceBuilderView.strings; sourceTree = "<group>"; };
@@ -1369,6 +1372,8 @@
4C4B7EF717A4B335000234C7 /* MPUniqueCharactersFormatter.m */,
4C3C4EAD18D7039300153127 /* MPValueTransformerHelper.h */,
4C3C4EAE18D7039300153127 /* MPValueTransformerHelper.m */,
AF105CF225FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.h */,
AF105CF125FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.m */,
4C52197F273D192C00C719D3 /* MPOpenURLHandler.h */,
4C521980273D192C00C719D3 /* MPOpenURLHandler.m */,
);
@@ -2324,6 +2329,7 @@
4C978E0D19AE54AB003067DF /* MPFlagsHelper.m in Sources */,
4C6F228919A4A7F90012310C /* MPAutotypeClear.m in Sources */,
4C0B038C18E36DA400B9F9C9 /* MPFixAutotypeWindowController.m in Sources */,
AF105CF325FE5B2000C4FD3C /* MPTouchIdCompositeKeyStore.m in Sources */,
4C7679BF1D76D6D8001F33D6 /* MPErrorRecoveryAttempter.m in Sources */,
4CAD338F205169D30068587E /* MPPluginRepositoryItem.m in Sources */,
4C9BFFFB1FD19B5400264B16 /* MPPrettyPasswordTransformer.m in Sources */,
@@ -3232,6 +3238,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = MacPassAppIcon;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MacPass/MacPass.entitlements;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${CURRENT_PROJECT_VERSION}";
@@ -3262,6 +3269,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = MacPassAppIcon;
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CODE_SIGN_ENTITLEMENTS = MacPass/MacPass.entitlements;
CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = "${CURRENT_PROJECT_VERSION}";

View File

@@ -27,19 +27,19 @@
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customView translatesAutoresizingMaskIntoConstraints="NO" id="1">
<rect key="frame" x="0.0" y="0.0" width="411" height="520"/>
<rect key="frame" x="0.0" y="0.0" width="417" height="664"/>
<subviews>
<box autoresizesSubviews="NO" verticalHuggingPriority="500" borderType="line" title="Autotype" translatesAutoresizingMaskIntoConstraints="NO" id="P9N-HM-wER">
<rect key="frame" x="17" y="115" width="377" height="385"/>
<rect key="frame" x="17" y="246" width="383" height="398"/>
<view key="contentView" id="faU-Ok-HJ3">
<rect key="frame" x="3" y="3" width="371" height="367"/>
<rect key="frame" x="3" y="3" width="377" height="380"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<stackView distribution="fill" orientation="vertical" alignment="leading" horizontalStackHuggingPriority="250" verticalStackHuggingPriority="250" horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="j52-9L-k7c">
<rect key="frame" x="16" y="17" width="339" height="340"/>
<rect key="frame" x="16" y="17" width="345" height="353"/>
<subviews>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="hMJ-Mo-xOM">
<rect key="frame" x="-2" y="298" width="343" height="42"/>
<rect key="frame" x="-2" y="311" width="349" height="42"/>
<textFieldCell key="cell" controlSize="small" id="H37-ku-aTc">
<font key="font" metaFont="smallSystem"/>
<string key="title">Autotype might not work properly. Some issues where found that prevent Autotype or Global Autotype to work. Please run the Autotype Doctor to fix those issues.</string>
@@ -48,7 +48,7 @@
</textFieldCell>
</textField>
<button horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="jai-b6-Qv4">
<rect key="frame" x="-6" y="262" width="177" height="32"/>
<rect key="frame" x="-7" y="276" width="171" height="32"/>
<buttonCell key="cell" type="push" title="Run Autotype Doctor…" bezelStyle="rounded" alignment="center" borderStyle="border" inset="2" id="NP0-R3-m6n">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
@@ -58,14 +58,14 @@
</connections>
</button>
<button horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="tik-Ar-FJg">
<rect key="frame" x="-2" y="245" width="343" height="18"/>
<rect key="frame" x="-2" y="258" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Enable global Autotype" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="1qb-Rd-jYu">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<stackView distribution="fill" orientation="horizontal" alignment="centerY" horizontalStackHuggingPriority="249.99998474121094" verticalStackHuggingPriority="249.99998474121094" translatesAutoresizingMaskIntoConstraints="NO" id="d6A-Vb-CMt">
<rect key="frame" x="0.0" y="218" width="281" height="21"/>
<rect key="frame" x="0.0" y="230" width="281" height="21"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="buI-Wb-o3V">
<rect key="frame" x="-2" y="3" width="57" height="16"/>
@@ -107,14 +107,14 @@
</customSpacing>
</stackView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uZQ-Gd-hfu">
<rect key="frame" x="-2" y="194" width="343" height="18"/>
<rect key="frame" x="-2" y="205" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Always show confirmation before executing Autotype" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="rrU-70-Ara">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField horizontalHuggingPriority="249" verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="cUt-aB-oib">
<rect key="frame" x="-2" y="146" width="343" height="42"/>
<rect key="frame" x="-2" y="156" width="349" height="42"/>
<textFieldCell key="cell" controlSize="small" selectable="YES" id="VjU-Hz-cu4">
<font key="font" metaFont="smallSystem"/>
<string key="title">If enabled, a dialog will show up before Autotype is executed even if only a single match was found to prevent accidental input and wrong matches</string>
@@ -123,42 +123,42 @@
</textFieldCell>
</textField>
<button horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="DAX-V8-Say">
<rect key="frame" x="-2" y="122" width="343" height="18"/>
<rect key="frame" x="-2" y="131" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Include Entry Title for matches" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="tmL-dT-D0G">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="8Wz-lo-AXG">
<rect key="frame" x="-2" y="100" width="343" height="18"/>
<rect key="frame" x="-2" y="107" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Include Entry URL for matches" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="TzR-00-Vp3">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="TiO-ah-BlR">
<rect key="frame" x="-2" y="78" width="343" height="18"/>
<rect key="frame" x="-2" y="83" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Include Entry URL Host for matches" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="B1D-j9-L8x">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button horizontalHuggingPriority="249" translatesAutoresizingMaskIntoConstraints="NO" id="9MH-jx-GpG">
<rect key="frame" x="-2" y="56" width="343" height="18"/>
<rect key="frame" x="-2" y="59" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Include Entry Tags for matches" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="rbu-G7-MT8">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<button horizontalHuggingPriority="249" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="VK8-z4-2ci">
<rect key="frame" x="-2" y="34" width="343" height="18"/>
<rect key="frame" x="-2" y="35" width="347" height="18"/>
<buttonCell key="cell" type="check" title="Interpret ⌃ as ⌘" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="QfO-yG-l3F">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="wbP-A9-jpw">
<rect key="frame" x="-2" y="0.0" width="343" height="28"/>
<rect key="frame" x="-2" y="0.0" width="349" height="28"/>
<textFieldCell key="cell" sendsActionOnEndEditing="YES" title="If enabled, every {CONTROL} command will be sent as ⌘. Only disable this if you are sure you need to." id="QRy-CY-ENC">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -205,20 +205,20 @@
</constraints>
</box>
<box autoresizesSubviews="NO" verticalHuggingPriority="500" borderType="line" title="Preview" translatesAutoresizingMaskIntoConstraints="NO" id="VVs-b5-cX9">
<rect key="frame" x="17" y="16" width="377" height="95"/>
<rect key="frame" x="17" y="145" width="383" height="97"/>
<view key="contentView" id="ww4-uR-8gP">
<rect key="frame" x="3" y="3" width="371" height="77"/>
<rect key="frame" x="3" y="3" width="377" height="79"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button translatesAutoresizingMaskIntoConstraints="NO" id="LNw-5t-TS4">
<rect key="frame" x="14" y="51" width="178" height="18"/>
<rect key="frame" x="14" y="52" width="182" height="18"/>
<buttonCell key="cell" type="check" title="Enable Quicklook Preview" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="ERs-ct-Eyx">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="V6l-R9-hIj">
<rect key="frame" x="14" y="17" width="343" height="28"/>
<rect key="frame" x="14" y="17" width="349" height="28"/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" title="If enabled attached files will be copied to a temporary location for preview and deleted after the preview is closed." id="WmI-IB-Aso">
<font key="font" metaFont="smallSystem"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
@@ -239,17 +239,57 @@
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="V6l-R9-hIj" secondAttribute="trailing" constant="16" id="ivY-UD-QFJ"/>
</constraints>
</box>
<box title="Keychain" translatesAutoresizingMaskIntoConstraints="NO" id="ChZ-ku-FOP">
<rect key="frame" x="17" y="16" width="383" height="125"/>
<view key="contentView" id="ocY-lR-P2Z">
<rect key="frame" x="3" y="3" width="377" height="107"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="u5z-ST-gTK">
<rect key="frame" x="9" y="60" width="154" height="32"/>
<buttonCell key="cell" type="push" title="Renew TouchID Key" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="b4n-g4-CtW">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="RenewTouchIdKey:" target="-2" id="dl7-WD-Abu"/>
</connections>
</button>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="9kv-ns-mQx">
<rect key="frame" x="14" y="16" width="349" height="42"/>
<textFieldCell key="cell" controlSize="small" sendsActionOnEndEditing="YES" id="LyC-Hd-ecK">
<font key="font" metaFont="smallSystem"/>
<string key="title">MacPass will no longer be able to unlock any Database with TouchID until it is successfully unlocked with the password and or keyfile.</string>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="9kv-ns-mQx" secondAttribute="bottom" constant="16" id="INC-l7-zwP"/>
<constraint firstItem="u5z-ST-gTK" firstAttribute="leading" secondItem="ocY-lR-P2Z" secondAttribute="leading" constant="16" id="KDt-yM-fh3"/>
<constraint firstItem="9kv-ns-mQx" firstAttribute="top" secondItem="u5z-ST-gTK" secondAttribute="bottom" constant="9" id="RjN-pw-qLc"/>
<constraint firstItem="9kv-ns-mQx" firstAttribute="leading" secondItem="u5z-ST-gTK" secondAttribute="leading" id="X5L-Rj-Wky"/>
<constraint firstItem="u5z-ST-gTK" firstAttribute="top" secondItem="ocY-lR-P2Z" secondAttribute="top" constant="20" symbolic="YES" id="guH-ws-dzX"/>
<constraint firstAttribute="trailing" secondItem="9kv-ns-mQx" secondAttribute="trailing" constant="16" id="icu-An-dNK"/>
<constraint firstAttribute="trailing" relation="lessThanOrEqual" secondItem="u5z-ST-gTK" secondAttribute="trailing" priority="240" constant="16" id="rni-sA-a7w"/>
</constraints>
</view>
</box>
</subviews>
<constraints>
<constraint firstItem="ChZ-ku-FOP" firstAttribute="trailing" secondItem="ww4-uR-8gP" secondAttribute="trailing" id="39G-i3-XQa"/>
<constraint firstItem="ChZ-ku-FOP" firstAttribute="top" secondItem="VVs-b5-cX9" secondAttribute="bottom" constant="8" symbolic="YES" id="3ub-U2-KCr"/>
<constraint firstItem="P9N-HM-wER" firstAttribute="top" secondItem="1" secondAttribute="top" constant="20" symbolic="YES" id="8fy-q5-VJy"/>
<constraint firstAttribute="bottom" secondItem="VVs-b5-cX9" secondAttribute="bottom" constant="20" symbolic="YES" id="TZB-qe-7eg"/>
<constraint firstItem="ChZ-ku-FOP" firstAttribute="leading" secondItem="ww4-uR-8gP" secondAttribute="leading" id="BQW-Zd-udd"/>
<constraint firstAttribute="bottom" secondItem="ChZ-ku-FOP" secondAttribute="bottom" constant="20" symbolic="YES" id="DBk-vZ-yOT"/>
<constraint firstItem="VVs-b5-cX9" firstAttribute="top" secondItem="P9N-HM-wER" secondAttribute="bottom" constant="8" symbolic="YES" id="VCX-JW-cBe"/>
<constraint firstItem="VVs-b5-cX9" firstAttribute="trailing" secondItem="P9N-HM-wER" secondAttribute="trailing" id="k9i-4T-WY0"/>
<constraint firstAttribute="trailing" secondItem="P9N-HM-wER" secondAttribute="trailing" constant="20" id="n5w-Cw-Bbt"/>
<constraint firstItem="P9N-HM-wER" firstAttribute="leading" secondItem="1" secondAttribute="leading" constant="20" id="ulV-xL-ldJ"/>
<constraint firstItem="VVs-b5-cX9" firstAttribute="leading" secondItem="P9N-HM-wER" secondAttribute="leading" id="z4a-9C-78h"/>
</constraints>
<point key="canvasLocation" x="-1204" y="-1287"/>
<point key="canvasLocation" x="-1204.5" y="-1287.5"/>
</customView>
</objects>
</document>

View File

@@ -40,4 +40,11 @@ FOUNDATION_EXPORT NSString *const MPPluginUTI;
FOUNDATION_EXPORT NSString *const MPBundleHelpURLKey;
FOUNDATION_EXPORT NSString *const MPBundlePluginRepositoryURLKey;
FOUNDATION_EXPORT NSString *const MPPluginCompatibilityURLKey;
/**
Keychain Keys
*/
extern NSString *const TouchIdUnlockPublicKeyTag;
extern NSString *const TouchIdUnlockPrivateKeyTag;
#endif

View File

@@ -31,3 +31,6 @@ NSString *const MPBundleHelpURLKey = @"MPHelpURL";
NSString *const MPBundlePluginRepositoryURLKey = @"MPPluginRepositoryURL";
NSString *const MPPluginCompatibilityURLKey = @"MPPluginCompatibilityURLKey";
NSString *const TouchIdUnlockPublicKeyTag = @"com.hicknhacksoftware.macpass.publickey";
NSString *const TouchIdUnlockPrivateKeyTag = @"com.hicknhacksoftware.macpass.privatekey";

View File

@@ -108,13 +108,13 @@ FOUNDATION_EXPORT NSString *const MPDocumentGroupKey;
/**
* Decrypts the database with the given password and keyfile
*
* @param password The password to unlock the db with, can be nil. This is not the same as an empty string @""
* @param keyFileURL URL for the keyfile to use, can be nil
* @param compositeKey The CompositeKey to unlock the db.
* @param keyFileURL URL for the keyfile that was used to create the compositeKey. Can be nil.
* @param error Pointer to an NSError pointer of error reporting.
*
* @return YES if the document was unlocked sucessfully, NO otherwise. Consult the error object for details
*/
- (BOOL)unlockWithPassword:(NSString *)password keyFileURL:(NSURL *)keyFileURL error:(NSError *__autoreleasing*)error;
- (BOOL)unlockWithPassword:(KPKCompositeKey *)compositeKey keyFileURL:(NSURL *)keyFileURL error:(NSError *__autoreleasing*)error;
/**
* Changes the password of the database. Some sanity checks are applied and the change is aborted if the new values aren't valid
*

View File

@@ -422,13 +422,9 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou
MPPasswordInputController *passwordInputController = [[MPPasswordInputController alloc] init];
[passwordInputController requestPasswordWithMessage:NSLocalizedString(@"EXTERN_CHANGE_OF_MASTERKEY", @"The master key was changed by an external program!")
cancelLabel:NSLocalizedString(@"ABORT_MERGE_KEEP_MINE", @"Button label to abort a merge on a file with changed master key!")
completionHandler:^BOOL(NSString *password, NSURL *keyURL, BOOL didCancel, NSError *__autoreleasing *error) {
[self.windowForSheet endSheet:sheet returnCode:(didCancel ? NSModalResponseCancel : NSModalResponseOK)];
if(!didCancel) {
NSData *keyFileData = keyURL ? [NSData dataWithContentsOfURL:keyURL] : nil;
KPKCompositeKey *compositeKey = [[KPKCompositeKey alloc] init];
[compositeKey addKey:[KPKKey keyWithPassword:password]];
[compositeKey addKey:[KPKKey keyWithKeyFileData:keyFileData]];
completionHandler:^BOOL(KPKCompositeKey *compositeKey, NSURL* keyURL, BOOL didCancel, NSError *__autoreleasing *error) {
[self.windowForSheet endSheet:sheet returnCode:(didCancel ? NSModalResponseCancel : NSModalResponseOK)];
if(!didCancel) {
[self _mergeWithContentsFromURL:url key:compositeKey options:options];
}
// just return yes regardless since we will display the sheet again if needed!
@@ -494,13 +490,9 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou
}
- (BOOL)unlockWithPassword:(NSString *)password keyFileURL:(NSURL *)keyFileURL error:(NSError *__autoreleasing*)error {
- (BOOL)unlockWithPassword:(KPKCompositeKey *)compositeKey keyFileURL:(NSURL *)keyFileURL error:(NSError *__autoreleasing*)error{
// TODO: Make this API asynchronous
NSData *keyFileData = keyFileURL ? [NSData dataWithContentsOfURL:keyFileURL] : nil;
self.compositeKey = [[KPKCompositeKey alloc] init];
[self.compositeKey addKey:[KPKKey keyWithPassword:password]];
[self.compositeKey addKey:[KPKKey keyWithKeyFileData:keyFileData]];
self.compositeKey = compositeKey;
self.tree = [[KPKTree alloc] initWithData:self.encryptedData key:self.compositeKey error:error];
BOOL isUnlocked = (nil != self.tree);
@@ -856,7 +848,7 @@ NSString *const MPDocumentGroupKey = @"MPDocumentGrou
userInfo:@{ MPDocumentEntryKey: lastDuplicate }];
}
}
- (void)duplicateGroup:(id)sender {
for(KPKGroup *group in self.selectedGroups) {

View File

@@ -337,12 +337,11 @@ typedef void (^MPPasswordChangedBlock)(BOOL didChangePassword);
self.passwordInputController = [[MPPasswordInputController alloc] init];
}
self.contentViewController = self.passwordInputController;
[self.passwordInputController requestPasswordWithMessage:message cancelLabel:nil completionHandler:^BOOL(NSString *password, NSURL *keyURL, BOOL didCancel, NSError *__autoreleasing *error) {
[self.passwordInputController requestPasswordWithMessage:message cancelLabel:nil completionHandler:^BOOL(KPKCompositeKey* compositeKey, NSURL* keyURL, BOOL didCancel, NSError *__autoreleasing *error) {
if(didCancel) {
return NO;
}
return [((MPDocument *)self.document) unlockWithPassword:password keyFileURL:keyURL error:error];
return [((MPDocument *)self.document) unlockWithPassword:compositeKey keyFileURL:keyURL error:error ];
}];
}

View File

@@ -24,6 +24,7 @@
#import "MPSettingsHelper.h"
#import "MPIconHelper.h"
#import "MPAutotypeDoctor.h"
#import "MPConstants.h"
#import "DDHotKeyCenter.h"
#import "DDHotKey+MacPassAdditions.h"
@@ -133,4 +134,32 @@
- (void)runAutotypeDoctor:(id)sender {
[MPAutotypeDoctor.defaultDoctor runChecksAndPresentResults];
}
#pragma mark -
#pragma mark Keychain Actions
- (IBAction)RenewTouchIdKey:(id)sender {
NSData* publicKeyTag = [TouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *publicKeyQuery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: publicKeyTag,
(id)kSecReturnRef: @YES,
};
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)publicKeyQuery);
if (status != errSecSuccess) {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSLog(@"Error while trying to delete public key from Keychain: %@", description);
}
NSData* privateKeyTag = [TouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *privateKeyQuery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: privateKeyTag,
(id)kSecReturnRef: @YES,
};
status = SecItemDelete((__bridge CFDictionaryRef)privateKeyQuery);
if (status != errSecSuccess) {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSLog(@"Error while trying to delete private key from Keychain: %@", description);
}
}
@end

View File

@@ -21,14 +21,14 @@
//
#import "MPViewController.h"
#import "KeePassKit/KeePassKit.h"
@class KPKCompositeKey;
@interface MPPasswordInputController : MPViewController <NSTouchBarDelegate>
typedef BOOL (^passwordInputCompletionBlock)(NSString *password, NSURL *keyURL, BOOL didCancel, NSError *__autoreleasing*error);
typedef BOOL (^passwordInputCompletionBlock)(KPKCompositeKey *key, NSURL* keyFileURL, BOOL didCancel, NSError *__autoreleasing*error);
- (void)requestPasswordWithCompletionHandler:(passwordInputCompletionBlock)completionHandler;
- (void)requestPasswordWithMessage:(NSString *)message cancelLabel:(NSString *)cancelLabel completionHandler:(passwordInputCompletionBlock)completionHandler;

View File

@@ -27,6 +27,9 @@
#import "MPSettingsHelper.h"
#import "MPPathControl.h"
#import "MPTouchBarButtonCreator.h"
#import "MPSettingsHelper.h"
#import "MPConstants.h"
#import "MPTouchIdCompositeKeyStore.h"
#import "HNHUi/HNHUi.h"
@@ -44,6 +47,8 @@
@property (weak) IBOutlet NSButton *enablePasswordCheckBox;
@property (weak) IBOutlet NSButton *unlockButton;
@property (weak) IBOutlet NSButton *cancelButton;
@property (weak) IBOutlet NSButton *touchIdButton;
@property (weak) IBOutlet NSButton *touchIdEnabledButton;
@property (copy) NSString *message;
@property (copy) NSString *cancelLabel;
@@ -81,6 +86,13 @@
[self.enablePasswordCheckBox bind:NSValueBinding toObject:self withKeyPath:NSStringFromSelector(@selector(enablePassword)) options:nil];
[self.togglePasswordButton bind:NSEnabledBinding toObject:self withKeyPath:NSStringFromSelector(@selector(enablePassword)) options:nil];
[self.passwordTextField bind:NSEnabledBinding toObject:self withKeyPath:NSStringFromSelector(@selector(enablePassword)) options:nil];
NSUserDefaultsController *defaultsController = [NSUserDefaultsController sharedUserDefaultsController];
[self.touchIdEnabledButton bind:NSValueBinding toObject:defaultsController withKeyPath:[MPSettingsHelper defaultControllerPathForKey:kMPSettingsKeyEntryTouchIdEnabled] options:nil];
self.touchIdEnabledButton.hidden = true;
if (@available(macOS 10.13.4, *)) {
self.touchIdEnabledButton.hidden = false;
[self _touchIdUpdateToolTip];
}
[self _reset];
}
@@ -95,10 +107,6 @@
[self _reset];
}
- (void)requestPasswordWithCompletionHandler:(passwordInputCompletionBlock)completionHandler {
[self requestPasswordWithMessage:nil cancelLabel:nil completionHandler:completionHandler];
}
#pragma mark Properties
- (void)setEnablePassword:(BOOL)enablePassword {
if(_enablePassword != enablePassword) {
@@ -127,8 +135,24 @@
NSString *password = self.enablePassword ? self.passwordTextField.stringValue : nil;
BOOL cancel = (sender == self.cancelButton);
BOOL result = self.completionHandler(password, self.keyPathControl.URL, cancel, &error);
if(cancel || result) {
NSURL* keyURL = self.keyPathControl.URL;
NSData *keyFileData = keyURL ? [NSData dataWithContentsOfURL:keyURL] : nil;
KPKKey* passwordKey = [KPKKey keyWithPassword:password];
KPKKey* fileKey = [KPKKey keyWithKeyFileData:keyFileData];
KPKCompositeKey* compositeKey = [[KPKCompositeKey alloc] init];
[compositeKey addKey:passwordKey];
[compositeKey addKey:fileKey];
/* After the completion handler finished we no longer have a windowController set */
NSString* documentKey = NULL;
bool documentKeyValid = [self _touchIdGetKeyForCurrentDocument:&documentKey];
BOOL result = self.completionHandler(compositeKey, keyURL, cancel, &error);
if(result) {
if(documentKeyValid) {
[self _touchIdUpdateKeyForCurrentDocument:compositeKey forDocumentKey:documentKey];
}
return;
}
if(cancel) {
return;
}
[self _showError:error];
@@ -138,6 +162,203 @@
}
}
- (void) _touchIdUpdateKeyForCurrentDocument: (KPKCompositeKey*)compositeKey forDocumentKey: (NSString*) documentKey{
NSData* encryptedKey = [self _touchIdEncryptCompositeKey:compositeKey];
[MPTouchIdCompositeKeyStore.defaultStore save:encryptedKey forDocumentKey:documentKey];
}
- (void) _touchIdCreateAndAddRSAKeyPair {
CFErrorRef error = NULL;
NSString* publicKeyLabel = @"MacPass TouchID Feature Public Key";
NSString* privateKeyLabel = @"MacPass TouchID Feature Private Key";
NSData* publicKeyTag = [TouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSData* privateKeyTag = [TouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
SecAccessControlRef access = NULL;
if (@available(macOS 10.13.4, *)) {
SecAccessControlCreateFlags flags = kSecAccessControlBiometryCurrentSet;
if (@available(macOS 10.15, *)) {
flags |= kSecAccessControlWatch | kSecAccessControlOr;
}
access = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags,
&error);
if(access == NULL) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to create AccessControl for TouchID unlock feature: %@", [err description]);
return;
}
NSDictionary* attributes = @{
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecAttrKeySizeInBits: @2048,
(id)kSecAttrSynchronizable: @NO,
(id)kSecPrivateKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: privateKeyTag,
(id)kSecAttrLabel: privateKeyLabel,
(id)kSecAttrAccessControl: (__bridge id)access
},
(id)kSecPublicKeyAttrs:
@{ (id)kSecAttrIsPermanent: @YES,
(id)kSecAttrApplicationTag: publicKeyTag,
(id)kSecAttrLabel: publicKeyLabel,
},
};
SecKeyRef result = SecKeyCreateRandomKey((__bridge CFDictionaryRef)attributes, &error);
if(result == NULL) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to create a RSA keypair for TouchID unlock feature: %@", [err description]);
}
else {
CFRelease(result);
}
}
else {
return;
}
}
- (NSData*) _touchIdEncryptCompositeKey: (KPKCompositeKey*) compositeKey {
NSData* encryptedKey = nil;
NSData* keyData = [NSKeyedArchiver archivedDataWithRootObject:compositeKey];
NSData* tag = [TouchIdUnlockPublicKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *getquery = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
(id)kSecReturnRef: @YES,
};
SecKeyRef publicKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery, (CFTypeRef *)&publicKey);
if (status != errSecSuccess) {
[self _touchIdCreateAndAddRSAKeyPair];
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)getquery, (CFTypeRef *)&publicKey);
if (status != errSecSuccess) {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSLog(@"Error while trying to query public key from Keychain: %@", description);
return nil;
}
}
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM;
BOOL canEncrypt = SecKeyIsAlgorithmSupported(publicKey, kSecKeyOperationTypeEncrypt, algorithm);
if(canEncrypt) {
CFErrorRef error = NULL;
encryptedKey = (NSData*)CFBridgingRelease(SecKeyCreateEncryptedData(publicKey, algorithm, (__bridge CFDataRef)keyData, &error));
if (!encryptedKey) {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to decrypt the CompositeKey for TouchID unlock: %@", [err description]);
}
}
else {
NSLog(@"The key retreived from the Keychain is unable to encrypt data");
}
if (publicKey) {
CFRelease(publicKey);
}
return encryptedKey;
}
- (KPKCompositeKey*) _touchIdDecryptCompositeKey: (NSData*) encryptedKey {
KPKCompositeKey* result = nil;
if(encryptedKey != nil) {
NSData* tag = [TouchIdUnlockPrivateKeyTag dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *queryPrivateKey = @{
(id)kSecClass: (id)kSecClassKey,
(id)kSecAttrApplicationTag: tag,
(id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
(id)kSecReturnRef: @YES,
};
SecKeyRef privateKey = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)queryPrivateKey, (CFTypeRef *)&privateKey);
if (status == errSecSuccess) {
SecKeyAlgorithm algorithm = kSecKeyAlgorithmRSAEncryptionOAEPSHA256AESGCM;
BOOL canDecrypt = SecKeyIsAlgorithmSupported(privateKey, kSecKeyOperationTypeDecrypt, algorithm);
if(canDecrypt) {
CFErrorRef error = NULL;
NSData* clearText = (NSData*)CFBridgingRelease(SecKeyCreateDecryptedData(privateKey, algorithm, (__bridge CFDataRef)encryptedKey, &error));
if (clearText) {
result = [NSKeyedUnarchiver unarchiveObjectWithData:clearText];
}
else {
NSError *err = CFBridgingRelease(error);
NSLog(@"Error while trying to decrypt password for TouchID unlock: %@", [err description]);
}
}
else {
NSLog(@"Key does not support decryption");
}
}
else {
NSString* description = (__bridge NSString*)SecCopyErrorMessageString(status, NULL);
NSLog(@"Error while trying to retrive private key for decryption: %@", description);
}
if (privateKey) {
CFRelease(privateKey);
}
}
return result;
}
- (bool) _touchIdGetKeyForCurrentDocument: (NSString**) result {
*result = NULL;
NSDocument* currentDocument = self.windowController.document;
if(currentDocument != NULL && currentDocument.fileURL != NULL && currentDocument.fileURL.lastPathComponent != NULL) {
*result = [NSString stringWithFormat:kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat, currentDocument.fileURL.lastPathComponent];
return true;
}
return false;
}
- (bool) _touchIdIsUnlockAvailable {
NSData* unused = NULL;
bool encryptedKeyAvailableForDocument = [self _touchIdGetEncrypedKeyMaterial:&unused];
return encryptedKeyAvailableForDocument;
}
- (bool) _touchIdGetEncrypedKeyMaterial: (NSData**) result {
NSString* documentKey = NULL;
*result = NULL;
if(![self _touchIdGetKeyForCurrentDocument:&documentKey]) {
return false;
}
return [MPTouchIdCompositeKeyStore.defaultStore load:result forDocumentKey:documentKey];
}
- (IBAction)unlockWithTouchID:(id)sender {
NSData* encryptedKey = NULL;
if(![self _touchIdGetEncrypedKeyMaterial:&encryptedKey]) {
[self.touchIdButton setEnabled:false];
return;
}
KPKCompositeKey* compositeKey = [self _touchIdDecryptCompositeKey:encryptedKey];
if(compositeKey == NULL) {
[self.touchIdButton setEnabled:false];
return;
}
NSError* error;
bool success = self.completionHandler(compositeKey, NULL, false, &error);
if(success) {
return;
}
[self.touchIdButton setEnabled:false];
[self _showError:error];
}
- (IBAction)touchIdEnabledChanged:(id)sender {
[self _touchIdUpdateToolTip];
}
- (void) _touchIdUpdateToolTip {
switch(self.touchIdEnabledButton.state) {
case NSControlStateValueOn:
self.touchIdEnabledButton.toolTip = NSLocalizedString(@"TOOLTIP_TOUCHID_ENABELD", @"Tooltip displayed when TouchID is is fully enabeld");
case NSControlStateValueOff:
self.touchIdEnabledButton.toolTip = NSLocalizedString(@"TOOLTIP_TOUCHID_DISABLED", @"Tooltip displayed when TouchID is disabled");
case NSControlStateValueMixed:
default:
self.touchIdEnabledButton.toolTip = NSLocalizedString(@"TOOLTIP_TOUCHID_TRANSIENT", @"Tooltip displayed when TouchID is in transient (inmemory) mode");
}
}
- (IBAction)resetKeyFile:(id)sender {
/* If the reset was triggered by ourselves we want to preselect the keyfile */
if(sender == self) {
@@ -153,6 +374,9 @@
self.enablePassword = YES;
self.passwordTextField.stringValue = @"";
self.messageInfoTextField.hidden = (nil == self.message);
self.touchIdButton.hidden = ![self _touchIdIsUnlockAvailable];
[self.touchIdButton setEnabled:true];
if(self.message) {
self.messageInfoTextField.stringValue = self.message;
self.messageImageView.image = [NSImage imageNamed:NSImageNameInfo];
@@ -236,5 +460,4 @@
}
}
@end

View File

@@ -22,6 +22,10 @@
#import <Cocoa/Cocoa.h>
/* TouchID */
APPKIT_EXTERN NSString *const kMPSettingsKeyEntryTouchIdEnabled;
APPKIT_EXTERN NSString *const kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat;
/* Clipboard */
APPKIT_EXTERN NSString *const kMPSettingsKeyPasteboardClearTimeout;
APPKIT_EXTERN NSString *const kMPSettingsKeyClearPasteboardOnQuit;

View File

@@ -67,6 +67,9 @@ NSString *const kMPSettingsKeyAutotypeMatchHost = @"Au
NSString *const kMPSettingsKeyAutotypeMatchTags = @"AutotypeMatchTags";
NSString *const kMPSettingsKeyGloablAutotypeAlwaysShowCandidateSelection = @"GloablAutotypeAlwaysShowCandidateSelection";
NSString *const kMPSettingsKeyEntryTouchIdEnabled = @"EnableSubsequentUnlocksWithTouchID";
NSString *const kMPSettingsKeyEntryTouchIdDatabaseEncryptedKeyFormat = @"EncryptedDatabaseKeyForTouchID-%@";
NSString *const kMPSettingsKeyEntrySearchFilterContext = @"EntrySearchFilterContext";
NSString *const kMPSettingsKeyEnableQuicklookPreview = @"EnableQuicklookPreview";

View File

@@ -0,0 +1,21 @@
//
// MPTouchIdCompositeKeyStore.h
// MacPass
//
// Created by Julius Zint on 14.03.21.
// Copyright © 2021 HicknHack Software GmbH. All rights reserved.
//
#ifndef MPTouchIdCompositeKeyStore_h
#define MPTouchIdCompositeKeyStore_h
static NSMutableDictionary* touchIDSecuredPasswords;
@interface MPTouchIdCompositeKeyStore : NSObject
@property (class, strong, readonly) MPTouchIdCompositeKeyStore *defaultStore;
- (void) save:(NSData*) encryptedCompositeKey forDocumentKey:(NSString*) documentKey;
- (bool) load:(NSData**) encryptedCompositeKey forDocumentKey:(NSString*) documentKey;
@end
#endif /* MPTouchIdCompositeKeyStore_h */

View File

@@ -0,0 +1,64 @@
//
// MPTouchIdCompositeKeyStore.m
// MacPass
//
// Created by Julius Zint on 14.03.21.
// Copyright © 2021 HicknHack Software GmbH. All rights reserved.
//
#import "MPSettingsHelper.h"
#import "MPTouchIdCompositeKeyStore.h"
@implementation MPTouchIdCompositeKeyStore
+ (instancetype)defaultStore {
static MPTouchIdCompositeKeyStore *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[MPTouchIdCompositeKeyStore alloc] init];
if(touchIDSecuredPasswords == NULL) {
touchIDSecuredPasswords = [[NSMutableDictionary alloc]init];
}
});
return instance;
}
- (void) save: (NSData*) encryptedCompositeKey forDocumentKey:(NSString*) documentKey {
long touchIdMode = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyEntryTouchIdEnabled];
if (touchIdMode == NSControlStateValueMixed) {
[NSUserDefaults.standardUserDefaults removeObjectForKey:documentKey];
if(encryptedCompositeKey != NULL) {
[touchIDSecuredPasswords setObject:encryptedCompositeKey forKey:documentKey];
}
}
else if(touchIdMode == NSControlStateValueOn) {
[touchIDSecuredPasswords removeObjectForKey:documentKey];
if(encryptedCompositeKey != NULL) {
[NSUserDefaults.standardUserDefaults setObject:encryptedCompositeKey forKey:documentKey];
}
}
else {
[NSUserDefaults.standardUserDefaults removeObjectForKey:documentKey];
[touchIDSecuredPasswords removeObjectForKey:documentKey];
}
}
- (bool) load: (NSData**) encryptedCompositeKey forDocumentKey: (NSString*) documentKey {
long touchIdMode = [NSUserDefaults.standardUserDefaults integerForKey:kMPSettingsKeyEntryTouchIdEnabled];
NSData* transientKey = [touchIDSecuredPasswords valueForKey:documentKey];
NSData* persistentKey =[NSUserDefaults.standardUserDefaults dataForKey:documentKey];
if(transientKey == NULL && persistentKey == NULL) {
return false;
}
if(transientKey == NULL || persistentKey == NULL) {
*encryptedCompositeKey = transientKey == NULL ? persistentKey : transientKey;
return true;
}
if(touchIdMode == NSControlStateValueOn) {
*encryptedCompositeKey = persistentKey;
return true;
}
*encryptedCompositeKey = transientKey;
return true;
}
@end

View File

@@ -6,5 +6,9 @@
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(AppIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER)</string>
</array>
</dict>
</plist>