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