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 | */ |
---|
39 | CFTypeRef 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 | |
---|
51 | int 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 | |
---|
61 | int 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 | |
---|
74 | int 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 | */ |
---|
95 | static 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 | |
---|
154 | static 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 | |
---|
234 | static 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 | |
---|
240 | int 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 | |
---|