Ticket #35474: certsync.m

File certsync.m, 9.0 KB (added by landonf (Landon Fuller), 10 years ago)
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 <unistd.h>
30#import <stdio.h>
31
32#import <objc/message.h>
33
34/**
35 * Add CoreFoundation object to the current autorelease pool.
36 *
37 * @param cfObj Object to add to the current autorelease pool.
38 */
39CFTypeRef PLCFAutorelease (CFTypeRef cfObj) {
40    /* ARC forbids the use of @selector(autorelease), so we have to get creative */
41    static SEL autorelease;
42    static dispatch_once_t pred;
43    dispatch_once(&pred, ^{
44        autorelease = sel_getUid("autorelease");
45    });
46   
47    /* Cast and hand-dispatch */
48    return ((CFTypeRef (*)(CFTypeRef, SEL)) objc_msgSend)(cfObj, autorelease);
49}
50
51int nsvfprintf (FILE *stream, NSString *format, va_list args) {
52    int retval;
53   
54    NSString *str;
55    str = (__bridge_transfer NSString *) CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef) format, args);
56    retval = fprintf(stream, "%s", [str UTF8String]);
57   
58    return retval;
59}
60
61int nsfprintf (FILE *stream, NSString *format, ...) {
62    va_list ap;
63    int retval;
64   
65    va_start(ap, format);
66    {
67        retval = nsvfprintf(stream, format, ap);
68    }
69    va_end(ap);
70   
71    return retval;
72}
73
74int nsprintf (NSString *format, ...) {
75    va_list ap;
76    int retval;
77   
78    va_start(ap, format);
79    {
80        retval = nsvfprintf(stderr, format, ap);
81    }
82    va_end(ap);
83   
84    return retval;
85}
86
87/**
88 * Fetch all trusted roots for the given @a domain.
89 *
90 * @param domain The trust domain to query.
91 * @param outError On error, will contain an NSError instance describing the failure.
92 *
93 * @return Returns a (possibly empty) array of certificates on success, nil on failure.
94 */
95static NSArray *certificatesForTrustDomain (SecTrustSettingsDomain domain, NSError **outError) {
96    CFArrayRef certs = nil;
97    OSStatus err;
98   
99    /* Fetch all certificates in the given domain */
100    err = SecTrustSettingsCopyCertificates(domain, &certs);
101    if (err == errSecSuccess) {
102        PLCFAutorelease(certs);
103    } else if (err == errSecNoTrustSettings ) {
104        /* No data */
105        return [NSArray array];
106       
107    } else if (err != errSecSuccess) {
108        /* Lookup failed */
109        if (outError != NULL)
110            *outError = [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo:nil];
111        return nil;
112    }
113   
114    /* Extract trusted roots */
115    NSMutableArray *results = [NSMutableArray arrayWithCapacity: CFArrayGetCount(certs)];
116    for (id certObj in (__bridge NSArray *) certs) {
117        SecCertificateRef cert = (__bridge SecCertificateRef) certObj;
118       
119        /* Fetch the trust settings */
120        CFArrayRef trustSettings = nil;
121        err = SecTrustSettingsCopyTrustSettings(cert, domain, &trustSettings);
122        if (err != errSecSuccess) {
123            /* Shouldn't happen */
124            nsfprintf(stderr, @"Failed to fetch trust settings\n");
125            continue;
126        } else {
127            PLCFAutorelease(trustSettings);
128        }
129       
130        /* If empty, trust for everything (as per the Security Framework documentation) */
131        if (CFArrayGetCount(trustSettings) == 0) {
132            [results addObject: certObj];
133        } else {
134            /* Otherwise, walk the properties and evaluate the trust settings result */
135            for (NSDictionary *trustProps in (__bridge NSArray *) trustSettings) {
136                CFNumberRef settingsResultNum;
137                SInt32 settingsResult;
138               
139                settingsResultNum = (__bridge CFNumberRef) [trustProps objectForKey: (__bridge id) kSecTrustSettingsResult];
140                CFNumberGetValue(settingsResultNum, kCFNumberSInt32Type, &settingsResult);
141               
142                /* If a root, add to the result set */
143                if (settingsResult == kSecTrustSettingsResultTrustRoot || settingsResult == kSecTrustSettingsResultTrustAsRoot) {
144                    [results addObject: certObj];
145                    break;
146                }
147            }
148        }
149    }
150   
151    return results;
152}
153
154static int exportCertificates (BOOL userAnchors, NSString *outputFile) {
155    /*
156     * Fetch all certificates
157     */
158   
159    NSMutableArray *anchors = [NSMutableArray array];
160    NSArray *result;
161    NSError *error;
162   
163    /* Current user */
164    if (userAnchors) {
165        result = certificatesForTrustDomain(kSecTrustSettingsDomainUser, &error);
166        if (result != nil) {
167            [anchors addObjectsFromArray: result];
168        } else {
169            nsfprintf(stderr, @"Failed to fetch user anchors: %@\n", error);
170            return EXIT_FAILURE;
171        }
172    }
173   
174    /* Admin */
175    result = certificatesForTrustDomain(kSecTrustSettingsDomainAdmin, &error);
176    if (result != nil) {
177        [anchors addObjectsFromArray: result];
178    } else {
179        nsfprintf(stderr, @"Failed to fetch admin anchors: %@\n", error);
180        return EXIT_FAILURE;
181    }
182   
183    /* System */
184    result = certificatesForTrustDomain(kSecTrustSettingsDomainSystem, &error);
185    if (result != nil) {
186        [anchors addObjectsFromArray: result];
187    } else {
188        nsfprintf(stderr, @"Failed to fetch system anchors: %@\n", error);
189        return EXIT_FAILURE;
190    }
191   
192    for (id certObj in result) {
193        CFErrorRef cferror;
194        CFStringRef subject = SecCertificateCopyShortDescription(NULL, (__bridge SecCertificateRef) certObj, &cferror);
195        if (subject == NULL) {
196            nsfprintf(stderr, @"Failed to extract certificate description: %@\n", cferror);
197            return EXIT_FAILURE;
198        } else {
199            nsfprintf(stderr, @"Extracting %@\n", subject);
200        }
201    }
202   
203    /*
204     * Perform export
205     */
206    CFDataRef pemData;
207    OSStatus err;
208   
209    /* Prefer the non-deprecated SecItemExport on Mac OS X >= 10.7 */
210    if (NO && SecItemExport != NULL) {
211        err = SecItemExport((__bridge CFArrayRef) anchors, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &pemData);
212    } else {
213        err = SecKeychainItemExport((__bridge CFArrayRef) anchors, kSecFormatPEMSequence, kSecItemPemArmour, NULL, &pemData);
214    }
215   
216    if (err != errSecSuccess) {
217        nsfprintf(stderr, @"Failed to export certificates: %@\n", [NSError errorWithDomain: NSOSStatusErrorDomain code: err userInfo:nil]);
218        return EXIT_FAILURE;
219    }
220
221    if (outputFile == nil) {
222        NSString *str = [[NSString alloc] initWithData: (__bridge NSData *) pemData encoding:NSUTF8StringEncoding];
223        nsfprintf(stdout, @"%@", str);
224    } else {
225        if (![(__bridge NSData *) pemData writeToFile: outputFile options: NSDataWritingAtomic error: &error]) {
226            nsfprintf(stderr, @"Failed to write to pem output file: %@\n", error);
227            return EXIT_FAILURE;
228        }
229    }
230   
231    return EXIT_SUCCESS;
232}
233
234static void usage (const char *progname) {
235    fprintf(stderr, "Usage: %s [-u] [-o <output file>]\n", progname);
236    fprintf(stderr, "\t-u\t\t\tInclude the current user's anchor certificates.\n");
237    fprintf(stderr, "\t-o <output file>\tWrite the PEM certificates to the target file, rather than stdout\n");
238}
239
240int main (int argc, char * const argv[]) {
241    @autoreleasepool {
242        /* Parse the command line arguments */
243        BOOL userAnchors = NO;
244        NSString *outputFile = nil;
245       
246        int ch;
247        while ((ch = getopt(argc, argv, "huo:")) != -1) {
248            switch (ch) {
249                case 'u':
250                    userAnchors = YES;
251                    break;
252                   
253                case 'o':
254                    outputFile = [NSString stringWithUTF8String: optarg];
255                    break;
256
257                case 'h':
258                    usage(argv[0]);
259                    exit(EXIT_SUCCESS);
260
261                default:
262                    usage(argv[0]);
263                    exit(EXIT_FAILURE);
264            }
265        }
266        argc -= optind;
267        argv += optind;
268
269        return exportCertificates(userAnchors, outputFile);
270    }
271}
272