]> git.saurik.com Git - apple/security.git/blob - SecurityTool/macOS/trusted_cert_ssl.m
Security-59306.61.1.tar.gz
[apple/security.git] / SecurityTool / macOS / trusted_cert_ssl.m
1 /*
2 * Copyright (c) 2019 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 * verify_ssl.m
24 */
25
26
27 #import "trusted_cert_ssl.h"
28 #include <Foundation/Foundation.h>
29 #include <CoreServices/CoreServices.h>
30 #include <Network/Network.h>
31
32 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
33
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37
38 #if TARGET_OS_MAC
39 #include <Security/oidscert.h>
40 #include <Security/oidsattr.h>
41 #include <Security/oidsalg.h>
42 #include <Security/x509defs.h>
43 #include <Security/cssmapi.h>
44 #include <Security/cssmapple.h>
45 #endif
46
47 #include <Security/certextensions.h>
48 #include <Security/SecKeychain.h>
49 #include <Security/SecKeychainItem.h>
50 #include <Security/SecImportExport.h>
51 #include <Security/SecIdentity.h>
52 #include <Security/SecIdentitySearch.h>
53 #include <Security/SecKey.h>
54 #include <Security/SecCertificate.h>
55 #include <Security/SecTrust.h>
56 #include <Security/SecProtocolOptions.h>
57
58 #include <SecurityFoundation/SFCertificateData.h>
59
60 #include <nw/private.h>
61
62
63 @interface TLSConnection : NSObject
64
65 @property NSURL *url;
66 @property NSError *error;
67 @property SecTrustRef trust;
68 @property BOOL finished;
69 @property BOOL udp; // default is NO (use tcp)
70 @property int verbose;
71 @property dispatch_queue_t queue;
72 @property nw_connection_t connection;
73
74 - (id)initWithURLString:(const char *)urlstr verbose:(int)level;
75 - (void)dealloc;
76 - (nw_connection_t)createConnection;
77 - (void)startConnection;
78 - (void)waitForConnection;
79 - (NSError*)error;
80 - (SecTrustRef)trust;
81
82 + (BOOL)isNetworkURL:(const char *)urlstr;
83
84 @end
85
86 #define ANSI_RED "\x1b[31m"
87 #define ANSI_GREEN "\x1b[32m"
88 #define ANSI_YELLOW "\x1b[33m"
89 #define ANSI_BLUE "\x1b[34m"
90 #define ANSI_MAGENTA "\x1b[35m"
91 #define ANSI_CYAN "\x1b[36m"
92 #define ANSI_RESET "\x1b[0m"
93
94 #if OBJC_ARC_DISABLED
95 #define NW_RETAIN(obj) nw_retain(obj)
96 #define NW_RELEASE(obj) nw_release(obj)
97 #define SEC_RELEASE(obj) sec_release(obj)
98 #define OBJ_RELEASE(obj) [obj release]
99 #define SUPER_DEALLOC [super dealloc]
100 #else
101 #define NW_RETAIN(obj)
102 #define NW_RELEASE(obj)
103 #define SEC_RELEASE(obj)
104 #define OBJ_RELEASE(obj)
105 #define SUPER_DEALLOC
106 #endif
107
108 @implementation TLSConnection
109
110 - (id)initWithURLString:(const char *)urlstr verbose:(int)level
111 {
112 _url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%s", urlstr]];
113 _udp = NO;
114 _finished = NO;
115 _verbose = level;
116 _error = nil;
117 _trust = NULL;
118 _queue = dispatch_get_main_queue();
119 _connection = [self createConnection];
120
121 return self;
122 }
123
124 - (void)dealloc
125 {
126 if (_connection) {
127 NW_RELEASE(_connection);
128 _connection = NULL;
129 }
130 if (_error) {
131 OBJ_RELEASE(_error);
132 _error = nil;
133 }
134 if (_url) {
135 OBJ_RELEASE(_url);
136 _url = nil;
137 }
138 if (_trust) {
139 CFRelease(_trust);
140 _trust = NULL;
141 }
142 SUPER_DEALLOC;
143 }
144
145 - (nw_connection_t)createConnection
146 {
147 const char *host = [[self.url host] UTF8String];
148 const char *port = [[[self.url port] stringValue] UTF8String];
149 if (!host) {
150 if (_verbose > 0) { fprintf(stderr, "Unable to continue without a hostname (is URL valid?)\n"); }
151 self.finished = YES;
152 return NULL;
153 }
154 nw_endpoint_t endpoint = nw_endpoint_create_host(host, (port) ? port : "443");
155 nw_parameters_configure_protocol_block_t configure_tls = ^(nw_protocol_options_t _Nonnull options) {
156 sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(options);
157 sec_protocol_options_set_verify_block(sec_options, ^(
158 sec_protocol_metadata_t _Nonnull metadata, sec_trust_t _Nonnull trust_ref, sec_protocol_verify_complete_t _Nonnull complete) {
159 SecTrustRef trust = sec_trust_copy_ref(trust_ref);
160 if (trust) {
161 CFRetain(trust);
162 if (self.trust) { CFRelease(self.trust); }
163 self.trust = trust;
164 }
165 CFErrorRef error = NULL;
166 BOOL allow = SecTrustEvaluateWithError(trust, &error);
167 if (error) {
168 if (self.error) { OBJ_RELEASE(self.error); }
169 self.error = (__bridge NSError *)error;
170 }
171 complete(allow);
172 }, self.queue);
173 };
174 nw_parameters_t parameters = nw_parameters_create_secure_tcp(configure_tls, NW_PARAMETERS_DEFAULT_CONFIGURATION);
175 nw_parameters_set_indefinite(parameters, false); // so we don't enter the 'waiting' state on TLS failure
176 nw_connection_t connection = nw_connection_create(endpoint, parameters);
177 NW_RELEASE(endpoint);
178 NW_RELEASE(parameters);
179 return connection;
180 }
181
182 - (void)startConnection
183 {
184 nw_connection_set_queue(_connection, _queue);
185 NW_RETAIN(_connection); // Hold a reference until cancelled
186
187 nw_connection_set_state_changed_handler(_connection, ^(nw_connection_state_t state, nw_error_t error) {
188 nw_endpoint_t remote = nw_connection_copy_endpoint(self.connection);
189 errno = error ? nw_error_get_error_code(error) : 0;
190 const char *protocol = self.udp ? "udp" : "tcp";
191 const char *host = nw_endpoint_get_hostname(remote);
192 uint16_t port = nw_endpoint_get_port(remote);
193 // note: this code does not handle or expect nw_connection_state_waiting,
194 // since the connection parameters specified a definite connection.
195 if (state == nw_connection_state_failed) {
196 if (self.verbose > 0) {
197 fprintf(stderr, "connection to %s port %u (%s) failed\n", host, port, protocol);
198 }
199 // Cancel the connection, so we go to nw_connection_state_cancelled
200 nw_connection_cancel(self.connection);
201
202 } else if (state == nw_connection_state_ready) {
203 if (self.verbose > 0) {
204 fprintf(stderr, "connection to %s port %u (%s) opened\n", host, port, protocol);
205 }
206 // Once we get the SecTrustRef, we can cancel the connection
207 nw_connection_cancel(self.connection);
208
209 } else if (state == nw_connection_state_cancelled) {
210 if (self.verbose > 0) {
211 fprintf(stderr, "connection to %s port %u (%s) closed\n", host, port, protocol);
212 }
213 nw_connection_cancel(self.connection); // cancel to be safe (should be a no-op)
214 NW_RELEASE(_connection); // release the reference and set flag to exit loop
215 self.finished = YES;
216 }
217 NW_RELEASE(remote);
218 });
219
220 nw_connection_start(_connection);
221 }
222
223 - (void)waitForConnection
224 {
225 while (_finished == NO) {
226 NSDate *cycleTime = [NSDate dateWithTimeIntervalSinceNow:0.1];
227 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:cycleTime];
228 }
229 }
230
231 + (BOOL)isNetworkURL:(const char *)urlstr
232 {
233 if (urlstr) {
234 NSArray *schemes = @[@"https",@"ldaps"];
235 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlstr]];
236 if (url && [schemes containsObject:[url scheme]]) {
237 return YES;
238 }
239 }
240 return NO;
241 }
242
243 @end
244
245 static NSString *errorStringForKey(NSString *key)
246 {
247 NSString *errstr = nil;
248 // %%% note: these dictionary keys currently do not have exported constants
249 if (![key length] || [key isEqualToString:@"StatusCodes"]) {
250 return errstr; // skip empty and legacy numeric errors
251 } else if ([key isEqualToString:@"SSLHostname"]) {
252 errstr = @"Host name not found in Subject Alternative Name extension";
253 } else if ([key isEqualToString:@"TemporalValidity"]) {
254 errstr = @"Certificate has expired, or is not yet valid (check date)";
255 } else if ([key isEqualToString:@"KeySize"]) {
256 errstr = @"Certificate uses a key size which is considered too weak";
257 } else if ([key isEqualToString:@"SignatureHashAlgorithms"]) {
258 errstr = @"Certificate uses a signing algorithm which is considered too weak";
259 } else if ([key isEqualToString:@"KeyUsage"]) {
260 errstr = @"The Key Usage extension does not permit this use for the certificate";
261 } else if ([key isEqualToString:@"ExtendedKeyUsage"]) {
262 errstr = @"The Extended Key Usage extension does not permit this use for the certificate";
263 } else if ([key isEqualToString:@"Revocation"]) {
264 errstr = @"Certificate has been revoked and cannot be used";
265 } else if ([key isEqualToString:@"BlackListedLeaf"]) {
266 errstr = @"Certificate has been blocked and cannot be used";
267 } else if ([key isEqualToString:@"AnchorTrusted"]) {
268 errstr = @"The root of the certificate chain is not trusted";
269 } else if ([key isEqualToString:@"MissingIntermediate"]) {
270 errstr = @"Unable to find next certificate in the chain";
271 } else if ([key isEqualToString:@"NonEmptySubject"]) {
272 errstr = @"Certificate has no subject, and SAN is missing or not marked critical";
273 } else if ([key isEqualToString:@"BasicCertificateProcessing"]) {
274 errstr = @"Certificate not standards compliant (RFC 5280|CABF Baseline Requirements)";
275 } else if ([key isEqualToString:@"NameConstraints"]) {
276 errstr = @"Certificate violates name constraints placed on issuing CA";
277 } else if ([key isEqualToString:@"PolicyConstraints"]) {
278 errstr = @"Certificate violates policy constraints placed on issuing CA";
279 } else if ([key isEqualToString:@"CTRequired"]) {
280 errstr = @"Certificate Transparency validation is required but missing";
281 } else if ([key isEqualToString:@"ValidityPeriodMaximums"]) {
282 errstr = @"Certificate exceeds maximum allowable validity period (normally 825 days)";
283 } else if ([key isEqualToString:@"ServerAuthEKU"]) {
284 errstr = @"The Extended Key Usage extension does not permit server authentication";
285 } else if ([key isEqualToString:@"UnparseableExtension"]) {
286 errstr = @"Unable to parse a standard extension (corrupt or invalid format detected)";
287 }
288
289 if (errstr) {
290 errstr = [NSString stringWithFormat:@"%@ [%@]", errstr, key];
291 } else {
292 errstr = [NSString stringWithFormat:@"[%@]", key];
293 }
294 return errstr;
295 }
296
297 void printErrorDetails(SecTrustRef trust)
298 {
299 CFDictionaryRef result = SecTrustCopyResult(trust);
300 CFArrayRef properties = SecTrustCopyProperties(trust);
301 NSArray *props = (__bridge NSArray *)properties;
302 NSArray *details = [(__bridge NSDictionary *)result objectForKey:@"TrustResultDetails"];
303 if (!props || !details) {
304 if (result) { CFRelease(result); }
305 if (properties) { CFRelease(properties); }
306 return;
307 }
308 // Preflight to see if there are any errors to display
309 CFIndex errorCount = 0, chainLength = [details count];
310 for (CFIndex chainIndex = 0; chainIndex < chainLength; chainIndex++) {
311 NSDictionary *certDetails = (NSDictionary *)[details objectAtIndex:chainIndex];
312 errorCount += [certDetails count];
313 }
314 if (!errorCount) {
315 if (result) { CFRelease(result); }
316 if (properties) { CFRelease(properties); }
317 return;
318 }
319
320 // Display per-certificate errors
321 fprintf(stdout, "---\nCertificate errors\n");
322 for (CFIndex chainIndex = 0; chainIndex < chainLength; chainIndex++) {
323 NSDictionary *certProps = (NSDictionary *)[props objectAtIndex:chainIndex];
324 NSString *certTitle = (NSString *)[certProps objectForKey:(__bridge NSString*)kSecPropertyTypeTitle];
325 fprintf(stdout, " %ld: %s\n", (long)chainIndex, [certTitle UTF8String]);
326 NSDictionary *certDetails = (NSDictionary *)[details objectAtIndex:chainIndex];
327 NSEnumerator *keyEnumerator = [certDetails keyEnumerator];
328 NSString *key;
329 while ((key = (NSString*)[keyEnumerator nextObject])) {
330 NSString *str = errorStringForKey(key);
331 if (!str) { continue; }
332 fprintf(stdout, ANSI_RED " %s" ANSI_RESET "\n", [str UTF8String]);
333 }
334 }
335 fflush(stdout);
336
337 CFRelease(result);
338 CFRelease(properties);
339 }
340
341 void printExtendedResults(SecTrustRef trust)
342 {
343 CFDictionaryRef trustResults = SecTrustCopyResult(trust);
344 if (!trustResults) { return; }
345 fprintf(stdout, "---\n");
346
347 NSDictionary *results = (__bridge NSDictionary *)trustResults;
348 NSString *orgName = [results objectForKey:(NSString *)kSecTrustOrganizationName];
349 CFBooleanRef isEV = (__bridge CFBooleanRef)[results objectForKey:(NSString *)kSecTrustExtendedValidation];
350 if (isEV == kCFBooleanTrue) {
351 fprintf(stdout, "Extended Validation (EV) confirmed for \"" ANSI_GREEN "%s" ANSI_RESET "\"\n", [orgName UTF8String]);
352 } else {
353 fprintf(stdout, "No extended validation result found\n");
354 }
355 CFBooleanRef isCT = (__bridge CFBooleanRef)[results objectForKey:(NSString *)kSecTrustCertificateTransparency];
356 CFBooleanRef isCTW = (__bridge CFBooleanRef)[results objectForKey:(NSString *)kSecTrustCertificateTransparencyWhiteList];
357 if (isCT == kCFBooleanTrue) {
358 fprintf(stdout, "Certificate Transparency (CT) status: " ANSI_GREEN "verified" ANSI_RESET "\n");
359 } else if (isCTW == kCFBooleanTrue) {
360 fprintf(stdout, "Certificate Transparency requirement waived for approved EV certificate\n");
361 } else {
362 fprintf(stdout, "Certificate Transparency (CT) status: " ANSI_RED "not verified" ANSI_RESET "\n");
363 fprintf(stdout, "Unable to find at least 2 signed certificate timestamps (SCTs) from approved logs\n");
364 }
365 fflush(stdout);
366 CFRelease(trustResults);
367 }
368
369 int evaluate_ssl(const char *urlstr, int verbose, SecTrustRef * CF_RETURNS_RETAINED trustRef)
370 {
371 @autoreleasepool {
372 if (trustRef) {
373 *trustRef = NULL;
374 }
375 if (![TLSConnection isNetworkURL:urlstr]) {
376 return 2;
377 }
378 TLSConnection *tls = [[TLSConnection alloc] initWithURLString:urlstr verbose:verbose];
379 [tls startConnection];
380 [tls waitForConnection];
381
382 NSError *error = [tls error];
383 if (verbose && error) {
384 fprintf(stderr, "NSError: { ");
385 CFShow((__bridge CFErrorRef)error);
386 fprintf(stderr,"}\n");
387 }
388
389 SecTrustRef trust = [tls trust];
390 if (trustRef && trust) {
391 CFRetain(trust);
392 *trustRef = trust;
393 }
394 OBJ_RELEASE(tls);
395 tls = nil;
396 }
397 return 0;
398 }
399
400 static bool isHex(unichar c)
401 {
402 return ((c >= 0x30 && c <= 0x39) || /* 0..9 */
403 (c >= 0x41 && c <= 0x46) || /* A..F */
404 (c >= 0x61 && c <= 0x66)); /* a..f */
405 }
406
407 static bool isEOL(unichar c)
408 {
409 return (c == 0x0D || c == 0x0A);
410 }
411
412 CF_RETURNS_RETAINED CFStringRef CopyCertificateTextRepresentation(SecCertificateRef certificate)
413 {
414 if (!certificate) {
415 return NULL;
416 }
417 @autoreleasepool {
418 NSData *certData = [[[SFCertificateData alloc] initWithCertificate:certificate] tabDelimitedTextData];
419 NSString *certStr = [[NSString alloc] initWithData:certData encoding:NSUnicodeStringEncoding];
420 NSMutableString *outStr = [NSMutableString stringWithCapacity:0];
421 [outStr appendString:certStr];
422
423 // process the output for readability by changing tabs to spaces
424 CFIndex index, count = [outStr length];
425 for (index = 1; index < count; index++) {
426 unichar c = [outStr characterAtIndex:index];
427 unichar p = [outStr characterAtIndex:index-1];
428 if (isEOL(p)) { // start of line
429 while (c == 0x09) { // convert tabs to spaces until non-tab found
430 [outStr replaceCharactersInRange:NSMakeRange(index, 1) withString:@" "];
431 c = [outStr characterAtIndex:++index];
432 }
433 } else if (c == 0x09) { // tab found between label and value
434 if (p == 0x20) { // continue the run of spaces
435 [outStr replaceCharactersInRange:NSMakeRange(index, 1) withString:@" "];
436 } else { // insert colon delimiter and space
437 [outStr replaceCharactersInRange:NSMakeRange(index, 1) withString:@": "];
438 count++; // we inserted an extra character
439 }
440 }
441 }
442 // remove spaces in hexadecimal data representations for compactness
443 count = [outStr length];
444 index = 0;
445 while (++index < count) {
446 unichar c = [outStr characterAtIndex:index];
447 unichar p = [outStr characterAtIndex:index-1];
448 // possible start of hex data run occurs after colon delimiter
449 if (p == 0x3A && c == 0x20) {
450 CFIndex start = index;
451 CFIndex len = 0;
452 while ((start+len+3 < count)) {
453 // scan for repeating three-character pattern
454 unichar first = [outStr characterAtIndex:start+len+0];
455 if (first != 0x20) {
456 break;
457 }
458 unichar second = [outStr characterAtIndex:start+len+1];
459 unichar third = [outStr characterAtIndex:start+len+2];
460 unichar last = [outStr characterAtIndex:start+len+3];
461 if (isHex(second) && isHex(third)) {
462 len += 3;
463 } else if (isEOL(second) && isHex(third) && isHex(last)) {
464 len += 4; // pattern continues on next line
465 } else {
466 break;
467 }
468 }
469 if (len > 0) {
470 // skip over the first space after the colon, which we want to keep
471 for (CFIndex idx = start+1; idx < count && idx < start+len; idx++) {
472 c = [outStr characterAtIndex:idx];
473 if (c == 0x20 || isEOL(c)) {
474 [outStr deleteCharactersInRange:NSMakeRange(idx,1)];
475 count--; // we removed a character from the total length
476 len--; // our substring also has one less character
477 }
478 }
479 }
480 }
481 }
482 return CFBridgingRetain(outStr);
483 }
484 }
485