source: trunk/dports/security/certsync/files/certsync.m @ 111188

Last change on this file since 111188 was 111188, checked in by landonf@…, 6 years ago

Add additional debugging to try to narrow down the buildbot failure.

Issue: #40088

File size: 14.2 KB
Line 
1/*
2 * Author: Landon Fuller <landonf@plausiblelabs.com>
3 * Copyright (c) 2008-2013 Plausible Labs Cooperative, Inc.
4 * All rights reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person
7 * obtaining a copy of this software and associated documentation
8 * files (the "Software"), to deal in the Software without
9 * restriction, including without limitation the rights to use,
10 * copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the
12 * Software is furnished to do so, subject to the following
13 * conditions:
14 *
15 * The above copyright notice and this permission notice shall be
16 * included in all copies or substantial portions of the Software.
17 *
18 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 * OTHER DEALINGS IN THE SOFTWARE.
26 */
27
28#import <Foundation/Foundation.h>
29#import <AvailabilityMacros.h>
30
31#import <unistd.h>
32#import <stdio.h>
33
34#import <objc/message.h>
35
36/* Allow building with SDKs < 10.6 */
37#ifndef MAC_OS_X_VERSION_10_6
38#define MAC_OS_X_VERSION_10_6 1060
39#endif /* !MAC_OS_X_VERSION_10_6 */
40
41/* Allow building with SDKs < 10.5 */
42#ifndef MAC_OS_X_VERSION_10_5
43#define MAC_OS_X_VERSION_10_5 1050
44#endif /* !MAC_OS_X_VERSION_10_5 */
45
46#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
47/* errSecSuccess was not defined until 10.6 */
48#define errSecSuccess noErr
49
50/* NSDataWritingAtomic was not defined until 10.6 */
51#define NSDataWritingAtomic NSAtomicWrite
52#endif
53
54/* A wrapper class that may be used to pass configuration through the
55 * FSEvent callback API */
56@interface MPCertSyncConfig : NSObject {
57@public
58    BOOL userAnchors;
59    NSString *outputFile;
60}
61@end
62
63@implementation MPCertSyncConfig
64- (void) dealloc {
65    [outputFile release];
66    [super dealloc];
67}
68@end
69
70/**
71 * Add CoreFoundation object to the current autorelease pool.
72 *
73 * @param cfObj Object to add to the current autorelease pool.
74 */
75CFTypeRef PLCFAutorelease (CFTypeRef cfObj) {
76    return [(id)cfObj autorelease];
77}
78
79int nsvfprintf (FILE *stream, NSString *format, va_list args) {
80    int retval;
81   
82    NSString *str;
83    str = (NSString *) CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef) format, args);
84    retval = fprintf(stream, "%s", [str UTF8String]);
85    [str release];
86   
87    return retval;
88}
89
90int nsfprintf (FILE *stream, NSString *format, ...) {
91    va_list ap;
92    int retval;
93   
94    va_start(ap, format);
95    {
96        retval = nsvfprintf(stream, format, ap);
97    }
98    va_end(ap);
99   
100    return retval;
101}
102
103int nsprintf (NSString *format, ...) {
104    va_list ap;
105    int retval;
106   
107    va_start(ap, format);
108    {
109        retval = nsvfprintf(stderr, format, ap);
110    }
111    va_end(ap);
112   
113    return retval;
114}
115
116/**
117 * Fetch all trusted roots for the given @a domain.
118 *
119 * @param domain The trust domain to query.
120 * @param outError On error, will contain an NSError instance describing the failure.
121 *
122 * @return Returns a (possibly empty) array of certificates on success, nil on failure.
123 */
124static NSArray *certificatesForTrustDomain (SecTrustSettingsDomain domain, NSError **outError) {
125    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
126    CFArrayRef certs = nil;
127    OSStatus err;
128   
129    /* Fetch all certificates in the given domain */
130    err = SecTrustSettingsCopyCertificates(domain, &certs);
131    if (err == errSecSuccess) {
132        PLCFAutorelease(certs);
133    } else if (err == errSecNoTrustSettings ) {
134        /* No data */
135       
136        [pool release];
137        return [NSArray array];
138    } else if (err != errSecSuccess) {
139        /* Lookup failed */
140        if (outError != NULL)
141            *outError = [[NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo:nil] retain];
142       
143        [pool release];
144        [*outError autorelease];
145        return nil;
146    }
147   
148    /* Extract trusted roots */
149    NSMutableArray *results = [NSMutableArray arrayWithCapacity: CFArrayGetCount(certs)];
150    for (id certObj in (NSArray *) certs) {
151        SecCertificateRef cert = (SecCertificateRef) certObj;
152       
153        /* Fetch the trust settings */
154        CFArrayRef trustSettings = nil;
155        err = SecTrustSettingsCopyTrustSettings(cert, domain, &trustSettings);
156        if (err != errSecSuccess) {
157            /* Shouldn't happen */
158            nsfprintf(stderr, @"Failed to fetch trust settings\n");
159            continue;
160        } else {
161            PLCFAutorelease(trustSettings);
162        }
163       
164        /* If empty, trust for everything (as per the Security Framework documentation) */
165        if (CFArrayGetCount(trustSettings) == 0) {
166            [results addObject: certObj];
167        } else {
168            /* Otherwise, walk the properties and evaluate the trust settings result */
169            for (NSDictionary *trustProps in (NSArray *) trustSettings) {
170                CFNumberRef settingsResultNum;
171                SInt32 settingsResult;
172               
173                settingsResultNum = (CFNumberRef) [trustProps objectForKey: (id) kSecTrustSettingsResult];
174                CFNumberGetValue(settingsResultNum, kCFNumberSInt32Type, &settingsResult);
175               
176                /* If a root, add to the result set */
177                if (settingsResult == kSecTrustSettingsResultTrustRoot || settingsResult == kSecTrustSettingsResultTrustAsRoot) {
178                    [results addObject: certObj];
179                    break;
180                }
181            }
182        }
183    }
184
185    [results retain];
186    [pool release];
187    return [results autorelease];
188}
189
190static int exportCertificates (BOOL userAnchors, NSString *outputFile) {
191    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
192
193    /*
194     * Fetch all certificates
195     */
196   
197    NSMutableArray *anchors = [NSMutableArray array];
198    NSArray *result;
199    NSError *error;
200    OSStatus err;
201
202    /* Current user */
203    if (userAnchors) {
204        result = certificatesForTrustDomain(kSecTrustSettingsDomainUser, &error);
205        if (result != nil) {
206            [anchors addObjectsFromArray: result];
207        } else {
208            nsfprintf(stderr, @"Failed to fetch user anchors: %@\n", error);
209            [pool release];
210            return EXIT_FAILURE;
211        }
212    }
213   
214    /* Admin */
215    result = certificatesForTrustDomain(kSecTrustSettingsDomainAdmin, &error);
216    if (result != nil) {
217        [anchors addObjectsFromArray: result];
218    } else {
219        nsfprintf(stderr, @"Failed to fetch admin anchors: %@\n", error);
220        [pool release];
221        return EXIT_FAILURE;
222    }
223   
224    /* System */
225    result = certificatesForTrustDomain(kSecTrustSettingsDomainSystem, &error);
226    if (result != nil) {
227        [anchors addObjectsFromArray: result];
228    } else {
229        nsfprintf(stderr, @"Failed to fetch system anchors: %@\n", error);
230        [pool release];
231        return EXIT_FAILURE;
232    }
233   
234    for (id certObj in result) {
235        CFErrorRef cferror = NULL;
236        CFStringRef subject;
237
238#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6
239        if (SecCertificateCopyShortDescription != NULL) {
240            subject = PLCFAutorelease(SecCertificateCopyShortDescription(NULL, (SecCertificateRef) certObj, &cferror));
241        } else {
242            subject = PLCFAutorelease(SecCertificateCopySubjectSummary((SecCertificateRef) certObj));
243        }
244#elif MAC_OS_X_VERSION_MAX_ALLOWED == MAC_OS_X_VERSION_10_6
245        subject = PLCFAutorelease(SecCertificateCopySubjectSummary((SecCertificateRef) certObj));
246#elif MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
247        if ((err = SecCertificateCopyCommonName((SecCertificateRef) certObj, &subject)) == errSecSuccess && subject != NULL) {
248            PLCFAutorelease(subject);
249        } else {
250            /* In the case that the CN is simply unavailable, provide a more useful error code */
251            if (err == errSecSuccess)
252                err = errSecNoSuchAttr;
253   
254            NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys: @"SecCertificateCopyCommonName() failed", NSLocalizedDescriptionKey, nil];
255            cferror = (CFErrorRef) [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo: userInfo];
256            subject = NULL;
257        }
258#endif
259
260        if (subject == NULL) {
261            nsfprintf(stderr, @"Failed to extract certificate description: %@\n", cferror);
262        } else {
263            nsfprintf(stderr, @"Found %@\n", subject);
264        }
265    }
266   
267    /*
268     * Perform export
269     */
270    CFDataRef pemData;
271   
272    /* Prefer the non-deprecated SecItemExport on Mac OS X >= 10.7. We use an ifdef to keep the code buildable with earlier SDKs, too. */
273    nsfprintf(stderr, @"Exporting certificates from the keychain\n");
274#if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_6
275    if (SecItemExport != NULL) {
276        err = SecItemExport((CFArrayRef) anchors, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &pemData);
277    } else {
278        err = SecKeychainItemExport((CFArrayRef) anchors, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &pemData);
279    }
280#else
281    err = SecKeychainItemExport((CFArrayRef) anchors, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &pemData);
282#endif
283    PLCFAutorelease(pemData);
284
285    if (err != errSecSuccess) {
286        nsfprintf(stderr, @"Failed to export certificates: %@\n", [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo:nil]);
287        [pool release];
288        return EXIT_FAILURE;
289    }
290
291    nsfprintf(stderr, @"Writing exported certificates\n");
292    if (outputFile == nil) {
293        NSString *str = [[[NSString alloc] initWithData: (NSData *) pemData encoding:NSUTF8StringEncoding] autorelease];
294        nsfprintf(stdout, @"%@", str);
295    } else {
296        if (![(NSData *) pemData writeToFile: outputFile options: NSDataWritingAtomic error: &error]) {
297            nsfprintf(stderr, @"Failed to write to pem output file: %@\n", error);
298            [pool release];
299            return EXIT_FAILURE;
300        }
301    }
302   
303    [pool release];
304    return EXIT_SUCCESS;
305}
306
307static void usage (const char *progname) {
308    fprintf(stderr, "Usage: %s [-u] [-o <output file>]\n", progname);
309    fprintf(stderr, "\t-u\t\t\tInclude the current user's anchor certificates.\n");
310    fprintf(stderr, "\t-s\t\t\tDo not exit; observe the system keychain(s) for changes and update the output file accordingly.");
311    fprintf(stderr, "\t-o <output file>\tWrite the PEM certificates to the target file, rather than stdout\n");
312}
313
314static void certsync_keychain_cb (ConstFSEventStreamRef streamRef, void *clientCallBackInfo, size_t numEvents, void *eventPaths, const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[])
315{
316    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
317
318    MPCertSyncConfig *config = (MPCertSyncConfig *) clientCallBackInfo;
319
320    int ret;
321    if ((ret = exportCertificates(config->userAnchors, config->outputFile)) != EXIT_SUCCESS)
322        exit(ret);
323
324    [pool release];
325}
326
327int main (int argc, char * const argv[]) {
328    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
329
330    /* Parse the command line arguments */
331    BOOL userAnchors = NO;
332    BOOL runServer = NO;
333    NSString *outputFile = nil;
334   
335    int ch;
336    while ((ch = getopt(argc, argv, "hsuo:")) != -1) {
337        switch (ch) {
338            case 'u':
339                userAnchors = YES;
340                break;
341               
342            case 's':
343                runServer = YES;
344                break;
345               
346            case 'o':
347                outputFile = [NSString stringWithUTF8String: optarg];
348                break;
349
350            case 'h':
351                usage(argv[0]);
352                exit(EXIT_SUCCESS);
353
354            default:
355                usage(argv[0]);
356                exit(EXIT_FAILURE);
357        }
358    }
359    argc -= optind;
360    argv += optind;
361   
362    /* Perform single-shot export  */
363    if (!runServer)
364        return exportCertificates(userAnchors, outputFile);
365   
366    /* Formulate the list of directories to observe; We use FSEvents rather than SecKeychainAddCallback(), as during testing the keychain
367     * API never actually fired a callback for the target keychains. */
368    FSEventStreamRef eventStream;
369    {
370        NSAutoreleasePool *streamPool = [[NSAutoreleasePool alloc] init];
371
372        NSSearchPathDomainMask searchPathDomains = NSLocalDomainMask|NSSystemDomainMask;
373        if (userAnchors)
374            searchPathDomains |= NSUserDomainMask;
375
376        NSArray *libraryDirectories = NSSearchPathForDirectoriesInDomains(NSAllLibrariesDirectory, searchPathDomains, YES);
377        NSMutableArray *keychainDirectories = [NSMutableArray arrayWithCapacity: [libraryDirectories count]];
378        for (NSString *dir in libraryDirectories) {
379            [keychainDirectories addObject: [dir stringByAppendingPathComponent: @"Keychains"]];
380            [keychainDirectories addObject: [dir stringByAppendingPathComponent: @"Security/Trust Settings"]];
381        }
382
383        /* Configure the listener */
384        MPCertSyncConfig *config = [[[MPCertSyncConfig alloc] init] autorelease];
385        config->userAnchors = userAnchors;
386        config->outputFile = [outputFile retain];
387
388        FSEventStreamContext ctx = {
389            .version = 0,
390            .info = config,
391            .retain = CFRetain,
392            .release = CFRelease,
393            .copyDescription = CFCopyDescription
394        };
395        eventStream = FSEventStreamCreate(NULL, certsync_keychain_cb, &ctx, (CFArrayRef)keychainDirectories, kFSEventStreamEventIdSinceNow, 0.0, kFSEventStreamCreateFlagUseCFTypes);
396        FSEventStreamScheduleWithRunLoop(eventStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
397        FSEventStreamStart(eventStream);
398       
399        [streamPool release];
400    }
401
402    /* Perform an initial one-shot export, and then run forever */
403    {
404    NSAutoreleasePool *shotPool = [[NSAutoreleasePool alloc] init];
405        int ret;
406        if ((ret = exportCertificates(userAnchors, outputFile)) != EXIT_SUCCESS)
407            return EXIT_FAILURE;
408        [shotPool release];
409    }
410
411    CFRunLoopRun();
412    FSEventStreamRelease(eventStream);
413    [pool release];
414
415    return EXIT_SUCCESS;
416}
417
Note: See TracBrowser for help on using the repository browser.