2 * Copyright (c) 2019 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
27 #import "trusted_cert_ssl.h"
28 #include <Foundation/Foundation.h>
29 #include <CoreServices/CoreServices.h>
30 #include <Network/Network.h>
32 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
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>
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>
58 #include <SecurityFoundation/SFCertificateData.h>
60 #include <nw/private.h>
63 @interface TLSConnection : NSObject
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;
74 - (id)initWithURLString:(const char *)urlstr verbose:(int)level;
76 - (nw_connection_t)createConnection;
77 - (void)startConnection;
78 - (void)waitForConnection;
82 + (BOOL)isNetworkURL:(const char *)urlstr;
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"
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]
101 #define NW_RETAIN(obj)
102 #define NW_RELEASE(obj)
103 #define SEC_RELEASE(obj)
104 #define OBJ_RELEASE(obj)
105 #define SUPER_DEALLOC
108 @implementation TLSConnection
110 - (id)initWithURLString:(const char *)urlstr verbose:(int)level
112 _url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%s", urlstr]];
118 _queue = dispatch_get_main_queue();
119 _connection = [self createConnection];
127 NW_RELEASE(_connection);
145 - (nw_connection_t)createConnection
147 const char *host = [[self.url host] UTF8String];
148 const char *port = [[[self.url port] stringValue] UTF8String];
150 if (_verbose > 0) { fprintf(stderr, "Unable to continue without a hostname (is URL valid?)\n"); }
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);
162 if (self.trust) { CFRelease(self.trust); }
165 CFErrorRef error = NULL;
166 BOOL allow = SecTrustEvaluateWithError(trust, &error);
168 if (self.error) { OBJ_RELEASE(self.error); }
169 self.error = (__bridge NSError *)error;
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);
182 - (void)startConnection
184 nw_connection_set_queue(_connection, _queue);
185 NW_RETAIN(_connection); // Hold a reference until cancelled
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);
199 // Cancel the connection, so we go to nw_connection_state_cancelled
200 nw_connection_cancel(self.connection);
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);
206 // Once we get the SecTrustRef, we can cancel the connection
207 nw_connection_cancel(self.connection);
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);
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
220 nw_connection_start(_connection);
223 - (void)waitForConnection
225 while (_finished == NO) {
226 NSDate *cycleTime = [NSDate dateWithTimeIntervalSinceNow:0.1];
227 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:cycleTime];
231 + (BOOL)isNetworkURL:(const char *)urlstr
234 NSArray *schemes = @[@"https",@"ldaps"];
235 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlstr]];
236 if (url && [schemes containsObject:[url scheme]]) {
245 static NSString *errorStringForKey(NSString *key)
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)";
290 errstr = [NSString stringWithFormat:@"%@ [%@]", errstr, key];
292 errstr = [NSString stringWithFormat:@"[%@]", key];
297 void printErrorDetails(SecTrustRef trust)
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); }
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];
315 if (result) { CFRelease(result); }
316 if (properties) { CFRelease(properties); }
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];
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]);
338 CFRelease(properties);
341 void printExtendedResults(SecTrustRef trust)
343 CFDictionaryRef trustResults = SecTrustCopyResult(trust);
344 if (!trustResults) { return; }
345 fprintf(stdout, "---\n");
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]);
353 fprintf(stdout, "No extended validation result found\n");
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");
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");
366 CFRelease(trustResults);
369 int evaluate_ssl(const char *urlstr, int verbose, SecTrustRef * CF_RETURNS_RETAINED trustRef)
375 if (![TLSConnection isNetworkURL:urlstr]) {
378 TLSConnection *tls = [[TLSConnection alloc] initWithURLString:urlstr verbose:verbose];
379 [tls startConnection];
380 [tls waitForConnection];
382 NSError *error = [tls error];
383 if (verbose && error) {
384 fprintf(stderr, "NSError: { ");
385 CFShow((__bridge CFErrorRef)error);
386 fprintf(stderr,"}\n");
389 SecTrustRef trust = [tls trust];
390 if (trustRef && trust) {
400 static bool isHex(unichar c)
402 return ((c >= 0x30 && c <= 0x39) || /* 0..9 */
403 (c >= 0x41 && c <= 0x46) || /* A..F */
404 (c >= 0x61 && c <= 0x66)); /* a..f */
407 static bool isEOL(unichar c)
409 return (c == 0x0D || c == 0x0A);
412 CF_RETURNS_RETAINED CFStringRef CopyCertificateTextRepresentation(SecCertificateRef certificate)
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];
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];
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
442 // remove spaces in hexadecimal data representations for compactness
443 count = [outStr length];
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;
452 while ((start+len+3 < count)) {
453 // scan for repeating three-character pattern
454 unichar first = [outStr characterAtIndex:start+len+0];
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)) {
463 } else if (isEOL(second) && isHex(third) && isHex(last)) {
464 len += 4; // pattern continues on next line
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
482 return CFBridgingRetain(outStr);