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 if ((self = [super init])) {
113 _url = [[NSURL alloc] initWithString:[NSString stringWithFormat:@"%s", urlstr]];
119 _queue = dispatch_get_main_queue();
120 _connection = [self createConnection];
129 NW_RELEASE(_connection);
147 - (nw_connection_t)createConnection
149 const char *host = [[self.url host] UTF8String];
150 const char *port = [[[self.url port] stringValue] UTF8String];
152 if (_verbose > 0) { fprintf(stderr, "Unable to continue without a hostname (is URL valid?)\n"); }
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);
164 if (self.trust) { CFRelease(self.trust); }
167 CFErrorRef error = NULL;
168 BOOL allow = SecTrustEvaluateWithError(trust, &error);
170 if (self.error) { OBJ_RELEASE(self.error); }
171 self.error = (__bridge NSError *)error;
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);
184 - (void)startConnection
186 nw_connection_set_queue(_connection, _queue);
187 NW_RETAIN(_connection); // Hold a reference until cancelled
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);
201 // Cancel the connection, so we go to nw_connection_state_cancelled
202 nw_connection_cancel(self.connection);
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);
208 // Once we get the SecTrustRef, we can cancel the connection
209 nw_connection_cancel(self.connection);
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);
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
222 nw_connection_start(_connection);
225 - (void)waitForConnection
227 while (_finished == NO) {
228 NSDate *cycleTime = [NSDate dateWithTimeIntervalSinceNow:0.1];
229 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:cycleTime];
233 + (BOOL)isNetworkURL:(const char *)urlstr
236 NSArray *schemes = @[@"https",@"ldaps"];
237 NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlstr]];
238 if (url && [schemes containsObject:[url scheme]]) {
247 static NSString *errorStringForKey(NSString *key)
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)";
292 errstr = [NSString stringWithFormat:@"%@ [%@]", errstr, key];
294 errstr = [NSString stringWithFormat:@"[%@]", key];
299 void printErrorDetails(SecTrustRef trust)
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); }
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];
317 if (result) { CFRelease(result); }
318 if (properties) { CFRelease(properties); }
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];
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]);
340 CFRelease(properties);
343 void printExtendedResults(SecTrustRef trust)
345 CFDictionaryRef trustResults = SecTrustCopyResult(trust);
346 if (!trustResults) { return; }
347 fprintf(stdout, "---\n");
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]);
355 fprintf(stdout, "No extended validation result found\n");
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");
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");
368 CFRelease(trustResults);
371 int evaluate_ssl(const char *urlstr, int verbose, SecTrustRef * CF_RETURNS_RETAINED trustRef)
377 if (![TLSConnection isNetworkURL:urlstr]) {
380 TLSConnection *tls = [[TLSConnection alloc] initWithURLString:urlstr verbose:verbose];
381 [tls startConnection];
382 [tls waitForConnection];
384 NSError *error = [tls error];
385 if (verbose && error) {
386 fprintf(stderr, "NSError: { ");
387 CFShow((__bridge CFErrorRef)error);
388 fprintf(stderr,"}\n");
391 SecTrustRef trust = [tls trust];
392 if (trustRef && trust) {
402 static bool isHex(unichar c)
404 return ((c >= 0x30 && c <= 0x39) || /* 0..9 */
405 (c >= 0x41 && c <= 0x46) || /* A..F */
406 (c >= 0x61 && c <= 0x66)); /* a..f */
409 static bool isEOL(unichar c)
411 return (c == 0x0D || c == 0x0A);
414 CF_RETURNS_RETAINED CFStringRef CopyCertificateTextRepresentation(SecCertificateRef certificate)
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];
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];
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
444 // remove spaces in hexadecimal data representations for compactness
445 count = [outStr length];
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;
454 while ((start+len+3 < count)) {
455 // scan for repeating three-character pattern
456 unichar first = [outStr characterAtIndex:start+len+0];
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)) {
465 } else if (isEOL(second) && isHex(third) && isHex(last)) {
466 len += 4; // pattern continues on next line
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
484 return CFBridgingRetain(outStr);