SecSharedCredential.m
1 /* 2 * Copyright (c) 2020 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 * 23 * SecSharedCredential.m - Retrieve shared credentials with AuthenticationServices. 24 * 25 */ 26 27 #include <Security/SecSharedCredential.h> 28 #include <Security/SecBasePriv.h> 29 #include <utilities/SecCFError.h> 30 #include <utilities/SecCFWrappers.h> 31 #include "SecItemInternal.h" 32 #include <dlfcn.h> 33 34 #import <Foundation/Foundation.h> 35 #import <AuthenticationServices/AuthenticationServices.h> 36 #ifdef DARLING 37 #import <AppKit/NSApplication.h> 38 #endif 39 40 // Forward declaration of the primary function implemented in this file 41 OSStatus SecCopySharedWebCredentialSyncUsingAuthSvcs(CFStringRef fqdn, CFStringRef account, CFArrayRef *credentials, CFErrorRef *error); 42 43 // Classes we will load dynamically 44 static Class kASAuthorizationClass = NULL; 45 static Class kASAuthorizationControllerClass = NULL; 46 static Class kASAuthorizationPasswordProviderClass = NULL; 47 static Class kASPasswordCredentialClass = NULL; 48 static Class kUIApplicationClass = NULL; 49 static Class kNSApplicationClass = NULL; 50 51 static void loadAuthenticationServices(void) { 52 static dispatch_once_t onceToken; 53 dispatch_once(&onceToken, ^{ 54 const char *path = "/System/Library/Frameworks/AuthenticationServices.framework/AuthenticationServices"; 55 if ( [NSProcessInfo processInfo].macCatalystApp == YES ) { 56 path = "/System/iOSSupport/System/Library/Frameworks/AuthenticationServices.framework/AuthenticationServices"; 57 } 58 void* lib_handle = dlopen(path, RTLD_LAZY); 59 if (lib_handle != NULL) { 60 kASAuthorizationClass = NSClassFromString(@"ASAuthorization"); 61 kASAuthorizationControllerClass = NSClassFromString(@"ASAuthorizationController"); 62 kASAuthorizationPasswordProviderClass = NSClassFromString(@"ASAuthorizationPasswordProvider"); 63 kASPasswordCredentialClass = NSClassFromString(@"ASPasswordCredential"); 64 } 65 }); 66 } 67 68 static void loadUIKit(void) { 69 static dispatch_once_t onceToken; 70 dispatch_once(&onceToken, ^{ 71 const char *path = "/System/Library/Frameworks/UIKit.framework/UIKit"; 72 if ( [NSProcessInfo processInfo].macCatalystApp == YES ) { 73 path = "/System/Library/iOSSupport/System/Library/Frameworks/UIKit.framework/UIKit"; 74 } 75 void* lib_handle = dlopen(path, RTLD_LAZY); 76 if (lib_handle != NULL) { 77 kUIApplicationClass = NSClassFromString(@"UIApplication"); 78 } 79 }); 80 } 81 82 static void loadAppKit(void) { 83 static dispatch_once_t onceToken; 84 dispatch_once(&onceToken, ^{ 85 const char *path = "/System/Library/Frameworks/AppKit.framework/AppKit"; 86 void* lib_handle = dlopen(path, RTLD_LAZY); 87 if (lib_handle != NULL) { 88 kNSApplicationClass = NSClassFromString(@"NSApplication"); 89 } 90 }); 91 } 92 93 static Class ASAuthorizationClass() { 94 loadAuthenticationServices(); 95 return kASAuthorizationClass; 96 } 97 98 static Class ASAuthorizationControllerClass() { 99 loadAuthenticationServices(); 100 return kASAuthorizationControllerClass; 101 } 102 103 static Class ASAuthorizationPasswordProviderClass() { 104 loadAuthenticationServices(); 105 return kASAuthorizationPasswordProviderClass; 106 } 107 108 static Class ASPasswordCredentialClass() { 109 loadAuthenticationServices(); 110 return kASPasswordCredentialClass; 111 } 112 113 static Class UIApplicationClass() { 114 loadUIKit(); 115 return kUIApplicationClass; 116 } 117 118 static Class NSApplicationClass() { 119 loadAppKit(); 120 return kNSApplicationClass; 121 } 122 123 @interface SharedCredentialController : NSObject 124 <ASAuthorizationControllerDelegate, 125 ASAuthorizationControllerPresentationContextProviding> 126 127 -(ASPasswordCredential *)passwordCredential; 128 129 @end 130 131 @implementation SharedCredentialController { 132 ASAuthorizationPasswordProvider *_provider; 133 ASAuthorizationController *_controller; 134 ASPasswordCredential *_passwordCredential; 135 dispatch_semaphore_t _semaphore; 136 NSError *_error; 137 OSStatus _result; 138 } 139 140 - (void)dealloc { 141 // Don't want any further callbacks since we are going away 142 _controller.delegate = nil; 143 _controller.presentationContextProvider = nil; 144 } 145 146 - (void)_requestCredential { 147 if (!_provider) { 148 _provider = [[ASAuthorizationPasswordProviderClass() alloc] init]; 149 } 150 if (!_controller) { 151 _controller = [[ASAuthorizationControllerClass() alloc] initWithAuthorizationRequests:@[ [_provider createRequest] ]]; 152 } 153 _controller.delegate = self; 154 _controller.presentationContextProvider = self; 155 _semaphore = dispatch_semaphore_create(0); 156 _result = errSecItemNotFound; 157 _error = nil; 158 159 [_controller performRequests]; 160 } 161 162 - (ASPasswordCredential *)passwordCredential { 163 if (_passwordCredential) { 164 return _passwordCredential; 165 } 166 BOOL shouldRequest = YES; // ( [NSProcessInfo processInfo].macCatalystApp == YES ); 167 if (shouldRequest) { 168 [self _requestCredential]; 169 // wait synchronously until user picks a credential or cancels 170 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); 171 } else { 172 // unable to return a shared credential: <rdar://problem/59958701> 173 _result = errSecItemNotFound; 174 _error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:_result userInfo:NULL]; 175 } 176 return _passwordCredential; 177 } 178 179 - (NSError *)error { 180 return _error; 181 } 182 183 - (OSStatus)result { 184 return _result; 185 } 186 187 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization { 188 secinfo("swcagent", "SWC received didCompleteWithAuthorization"); 189 ASPasswordCredential *passwordCredential = authorization.credential; 190 if (![passwordCredential isKindOfClass:[ASPasswordCredentialClass() class]]) { 191 _passwordCredential = nil; 192 _result = errSecItemNotFound; 193 } else { 194 _passwordCredential = passwordCredential; 195 _result = errSecSuccess; 196 } 197 dispatch_semaphore_signal(_semaphore); 198 } 199 200 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error { 201 secinfo("swcagent", "SWC received didCompleteWithError"); 202 _passwordCredential = nil; 203 _error = error; 204 _result = errSecItemNotFound; 205 dispatch_semaphore_signal(_semaphore); 206 } 207 208 - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller 209 { 210 ASPresentationAnchor anchorWindow = nil; 211 #if TARGET_OS_OSX 212 if ( [NSProcessInfo processInfo].macCatalystApp == NO ) { 213 anchorWindow = [[NSApplicationClass() sharedApplication] keyWindow]; 214 } 215 #endif 216 if (!anchorWindow) { 217 anchorWindow = [[UIApplicationClass() sharedApplication] keyWindow]; 218 } 219 return anchorWindow; 220 } 221 222 @end 223 224 OSStatus SecCopySharedWebCredentialSyncUsingAuthSvcs(CFStringRef fqdn, CFStringRef account, CFArrayRef *credentials, CFErrorRef *error) { 225 SharedCredentialController *controller = [[SharedCredentialController alloc] init]; 226 ASPasswordCredential *passwordCredential = [controller passwordCredential]; 227 OSStatus status = [controller result]; 228 NSArray *returnedCredentials = @[]; 229 if (status != errSecSuccess) { 230 secinfo("swcagent", "SecCopySharedWebCredentialSyncUsingAuthSvcs received result %d", (int)status); 231 if (error) { 232 *error = (CFErrorRef)CFBridgingRetain([controller error]); 233 } 234 } else if (passwordCredential) { 235 // Use the .user and .password of the passwordCredential to satisfy the SWC interface. 236 NSDictionary *credential = @{ 237 (id)kSecAttrServer : (__bridge NSString*)fqdn, 238 (id)kSecAttrAccount : passwordCredential.user, 239 (id)kSecSharedPassword : passwordCredential.password, 240 }; 241 returnedCredentials = @[ credential ]; 242 } else { 243 secinfo("swcagent", "SecCopySharedWebCredentialSyncUsingAuthSvcs found no credential"); 244 status = errSecItemNotFound; 245 } 246 if (credentials) { 247 *credentials = (CFArrayRef)CFBridgingRetain(returnedCredentials); 248 } 249 return status; 250 }