2 * Copyright (c) 2020 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
23 * SecSharedCredential.m - Retrieve shared credentials with AuthenticationServices.
27 #include <Security/SecSharedCredential.h>
28 #include <Security/SecBasePriv.h>
29 #include <utilities/SecCFError.h>
30 #include <utilities/SecCFWrappers.h>
31 #include "SecItemInternal.h"
34 #import <Foundation/Foundation.h>
35 #import <AuthenticationServices/AuthenticationServices.h>
37 // Forward declaration of the primary function implemented in this file
38 OSStatus SecCopySharedWebCredentialSyncUsingAuthSvcs(CFStringRef fqdn, CFStringRef account, CFArrayRef *credentials, CFErrorRef *error);
40 // Classes we will load dynamically
41 static Class kASAuthorizationClass = NULL;
42 static Class kASAuthorizationControllerClass = NULL;
43 static Class kASAuthorizationPasswordProviderClass = NULL;
44 static Class kASPasswordCredentialClass = NULL;
45 static Class kUIApplicationClass = NULL;
46 static Class kNSApplicationClass = NULL;
48 static void loadAuthenticationServices(void) {
49 static dispatch_once_t onceToken;
50 dispatch_once(&onceToken, ^{
51 const char *path = "/System/Library/Frameworks/AuthenticationServices.framework/AuthenticationServices";
52 if ( [NSProcessInfo processInfo].macCatalystApp == YES ) {
53 path = "/System/iOSSupport/System/Library/Frameworks/AuthenticationServices.framework/AuthenticationServices";
55 void* lib_handle = dlopen(path, RTLD_LAZY);
56 if (lib_handle != NULL) {
57 kASAuthorizationClass = NSClassFromString(@"ASAuthorization");
58 kASAuthorizationControllerClass = NSClassFromString(@"ASAuthorizationController");
59 kASAuthorizationPasswordProviderClass = NSClassFromString(@"ASAuthorizationPasswordProvider");
60 kASPasswordCredentialClass = NSClassFromString(@"ASPasswordCredential");
65 static void loadUIKit(void) {
66 static dispatch_once_t onceToken;
67 dispatch_once(&onceToken, ^{
68 const char *path = "/System/Library/Frameworks/UIKit.framework/UIKit";
69 if ( [NSProcessInfo processInfo].macCatalystApp == YES ) {
70 path = "/System/Library/iOSSupport/System/Library/Frameworks/UIKit.framework/UIKit";
72 void* lib_handle = dlopen(path, RTLD_LAZY);
73 if (lib_handle != NULL) {
74 kUIApplicationClass = NSClassFromString(@"UIApplication");
79 static void loadAppKit(void) {
80 static dispatch_once_t onceToken;
81 dispatch_once(&onceToken, ^{
82 const char *path = "/System/Library/Frameworks/AppKit.framework/AppKit";
83 void* lib_handle = dlopen(path, RTLD_LAZY);
84 if (lib_handle != NULL) {
85 kNSApplicationClass = NSClassFromString(@"NSApplication");
90 static Class ASAuthorizationClass() {
91 loadAuthenticationServices();
92 return kASAuthorizationClass;
95 static Class ASAuthorizationControllerClass() {
96 loadAuthenticationServices();
97 return kASAuthorizationControllerClass;
100 static Class ASAuthorizationPasswordProviderClass() {
101 loadAuthenticationServices();
102 return kASAuthorizationPasswordProviderClass;
105 static Class ASPasswordCredentialClass() {
106 loadAuthenticationServices();
107 return kASPasswordCredentialClass;
110 static Class UIApplicationClass() {
112 return kUIApplicationClass;
115 static Class NSApplicationClass() {
117 return kNSApplicationClass;
120 @interface SharedCredentialController : NSObject
121 <ASAuthorizationControllerDelegate,
122 ASAuthorizationControllerPresentationContextProviding>
124 -(ASPasswordCredential *)passwordCredential;
128 @implementation SharedCredentialController {
129 ASAuthorizationPasswordProvider *_provider;
130 ASAuthorizationController *_controller;
131 ASPasswordCredential *_passwordCredential;
132 dispatch_semaphore_t _semaphore;
138 // Don't want any further callbacks since we are going away
139 _controller.delegate = nil;
140 _controller.presentationContextProvider = nil;
143 - (void)_requestCredential {
145 _provider = [[ASAuthorizationPasswordProviderClass() alloc] init];
148 _controller = [[ASAuthorizationControllerClass() alloc] initWithAuthorizationRequests:@[ [_provider createRequest] ]];
150 _controller.delegate = self;
151 _controller.presentationContextProvider = self;
152 _semaphore = dispatch_semaphore_create(0);
153 _result = errSecItemNotFound;
156 [_controller performRequests];
159 - (ASPasswordCredential *)passwordCredential {
160 if (_passwordCredential) {
161 return _passwordCredential;
163 BOOL shouldRequest = YES; // ( [NSProcessInfo processInfo].macCatalystApp == YES );
165 [self _requestCredential];
166 // wait synchronously until user picks a credential or cancels
167 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
169 // unable to return a shared credential: <rdar://problem/59958701>
170 _result = errSecItemNotFound;
171 _error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:_result userInfo:NULL];
173 return _passwordCredential;
184 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithAuthorization:(ASAuthorization *)authorization {
185 secinfo("swcagent", "SWC received didCompleteWithAuthorization");
186 ASPasswordCredential *passwordCredential = authorization.credential;
187 if (![passwordCredential isKindOfClass:[ASPasswordCredentialClass() class]]) {
188 _passwordCredential = nil;
189 _result = errSecItemNotFound;
191 _passwordCredential = passwordCredential;
192 _result = errSecSuccess;
194 dispatch_semaphore_signal(_semaphore);
197 - (void)authorizationController:(ASAuthorizationController *)controller didCompleteWithError:(NSError *)error {
198 secinfo("swcagent", "SWC received didCompleteWithError");
199 _passwordCredential = nil;
201 _result = errSecItemNotFound;
202 dispatch_semaphore_signal(_semaphore);
205 - (ASPresentationAnchor)presentationAnchorForAuthorizationController:(ASAuthorizationController *)controller
207 ASPresentationAnchor anchorWindow = nil;
209 if ( [NSProcessInfo processInfo].macCatalystApp == NO ) {
210 anchorWindow = [[NSApplicationClass() sharedApplication] keyWindow];
214 anchorWindow = [[UIApplicationClass() sharedApplication] keyWindow];
221 OSStatus SecCopySharedWebCredentialSyncUsingAuthSvcs(CFStringRef fqdn, CFStringRef account, CFArrayRef *credentials, CFErrorRef *error) {
222 SharedCredentialController *controller = [[SharedCredentialController alloc] init];
223 ASPasswordCredential *passwordCredential = [controller passwordCredential];
224 OSStatus status = [controller result];
225 NSArray *returnedCredentials = @[];
226 if (status != errSecSuccess) {
227 secinfo("swcagent", "SecCopySharedWebCredentialSyncUsingAuthSvcs received result %d", (int)status);
229 *error = (CFErrorRef)CFBridgingRetain([controller error]);
231 } else if (passwordCredential) {
232 // Use the .user and .password of the passwordCredential to satisfy the SWC interface.
233 NSDictionary *credential = @{
234 (id)kSecAttrServer : (__bridge NSString*)fqdn,
235 (id)kSecAttrAccount : passwordCredential.user,
236 (id)kSecSharedPassword : passwordCredential.password,
238 returnedCredentials = @[ credential ];
240 secinfo("swcagent", "SecCopySharedWebCredentialSyncUsingAuthSvcs found no credential");
241 status = errSecItemNotFound;
244 *credentials = (CFArrayRef)CFBridgingRetain(returnedCredentials);