/ OSX / sec / Security / SecSharedCredential.m
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  }