SecProtocolConfigurationTest.m
1 // 2 // SecProtocolConfigurationTest.m 3 // SecureTransportTests 4 // 5 6 #import <XCTest/XCTest.h> 7 8 #include <os/log.h> 9 #include <dlfcn.h> 10 #include <sys/param.h> 11 12 #import "SecProtocolConfiguration.h" 13 #import "SecProtocolPriv.h" 14 #import "SecProtocolInternal.h" 15 16 #import <nw/private.h> // Needed for the mock protocol 17 18 #define SEC_PROTOCOL_METADATA_VALIDATE(m, r) \ 19 if (((void *)(m) == NULL) || ((size_t)(m) == 0)) { \ 20 return (r); \ 21 } 22 23 typedef struct mock_protocol { 24 struct nw_protocol protocol; 25 char *name; 26 } *mock_protocol_t; 27 28 static nw_protocol_t 29 _mock_protocol_create_extended(nw_protocol_identifier_const_t identifier, 30 nw_endpoint_t endpoint, 31 nw_parameters_t parameters) 32 { 33 mock_protocol_t handle = (mock_protocol_t)calloc(1, sizeof(struct mock_protocol)); 34 if (handle == NULL) { 35 return NULL; 36 } 37 38 struct nw_protocol_callbacks *callbacks = (struct nw_protocol_callbacks *)malloc(sizeof(struct nw_protocol_callbacks)); 39 memset(callbacks, 0, sizeof(struct nw_protocol_callbacks)); 40 41 handle->protocol.callbacks = callbacks; 42 handle->protocol.handle = (void *)handle; 43 44 return &handle->protocol; 45 } 46 47 static bool 48 mock_protocol_register_extended(nw_protocol_identifier_const_t identifier, 49 nw_protocol_create_extended_f create_extended_function) 50 { 51 static void *libnetworkImage = NULL; 52 static dispatch_once_t onceToken; 53 static bool (*_nw_protocol_register_extended)(nw_protocol_identifier_const_t, nw_protocol_create_extended_f) = NULL; 54 55 dispatch_once(&onceToken, ^{ 56 libnetworkImage = dlopen("/usr/lib/libnetwork.dylib", RTLD_LAZY | RTLD_LOCAL); 57 if (NULL != libnetworkImage) { 58 _nw_protocol_register_extended = (__typeof(_nw_protocol_register_extended))dlsym(libnetworkImage, "nw_protocol_register_extended"); 59 if (NULL == _nw_protocol_register_extended) { 60 os_log_error(OS_LOG_DEFAULT, "dlsym libnetwork nw_protocol_register_extended"); 61 } 62 } else { 63 os_log_error(OS_LOG_DEFAULT, "dlopen libnetwork"); 64 } 65 }); 66 67 if (_nw_protocol_register_extended == NULL) { 68 return false; 69 } 70 71 return _nw_protocol_register_extended(identifier, create_extended_function); 72 } 73 74 static nw_protocol_identifier_t 75 _mock_protocol_identifier(const char *name, size_t name_len) 76 { 77 static struct nw_protocol_identifier mock_identifier = {}; 78 static dispatch_once_t onceToken; 79 80 dispatch_once(&onceToken, ^{ 81 memset(&mock_identifier, 0, sizeof(mock_identifier)); 82 83 strlcpy((char *)mock_identifier.name, name, name_len); 84 85 mock_identifier.level = nw_protocol_level_application; 86 mock_identifier.mapping = nw_protocol_mapping_one_to_one; 87 88 mock_protocol_register_extended(&mock_identifier, _mock_protocol_create_extended); 89 }); 90 91 return &mock_identifier; 92 } 93 94 static void * _Nullable 95 mock_protocol_allocate_metadata(__unused nw_protocol_definition_t definition) 96 { 97 return calloc(1, sizeof(struct sec_protocol_metadata_content)); 98 } 99 100 static void 101 mock_protocol_deallocate_metadata(__unused nw_protocol_definition_t definition, void *metadata) 102 { 103 sec_protocol_metadata_content_t content = (sec_protocol_metadata_content_t)metadata; 104 if (content) { 105 // pass 106 } 107 free(content); 108 } 109 110 static void 111 mock_protocol_set_metadata_allocator(nw_protocol_definition_t definition, nw_protocol_definition_allocate_f allocator, nw_protocol_definition_deallocate_f deallocator) 112 { 113 static void *libnetworkImage = NULL; 114 static dispatch_once_t onceToken; 115 static void (*_nw_protocol_definition_set_metadata_allocator)(nw_protocol_definition_t, nw_protocol_definition_allocate_f, nw_protocol_definition_deallocate_f) = NULL; 116 117 dispatch_once(&onceToken, ^{ 118 libnetworkImage = dlopen("/usr/lib/libnetwork.dylib", RTLD_LAZY | RTLD_LOCAL); 119 if (NULL != libnetworkImage) { 120 _nw_protocol_definition_set_metadata_allocator = (__typeof(_nw_protocol_definition_set_metadata_allocator))dlsym(libnetworkImage, "nw_protocol_definition_set_metadata_allocator"); 121 if (NULL == _nw_protocol_definition_set_metadata_allocator) { 122 os_log_error(OS_LOG_DEFAULT, "dlsym libnetwork nw_protocol_definition_set_metadata_allocator"); 123 } 124 } else { 125 os_log_error(OS_LOG_DEFAULT, "dlopen libnetwork"); 126 } 127 }); 128 129 if (_nw_protocol_definition_set_metadata_allocator == NULL) { 130 return; 131 } 132 133 _nw_protocol_definition_set_metadata_allocator(definition, allocator, deallocator); 134 } 135 136 static void * _Nullable 137 mock_protocol_copy_options(__unused nw_protocol_definition_t definition, void *options) 138 { 139 void *new_options = calloc(1, sizeof(struct sec_protocol_options_content)); 140 141 sec_protocol_options_content_t copy = (sec_protocol_options_content_t)new_options; 142 sec_protocol_options_content_t original = (sec_protocol_options_content_t)options; 143 144 copy->min_version = original->min_version; 145 copy->max_version = original->max_version; 146 copy->disable_sni = original->disable_sni; 147 copy->enable_fallback_attempt = original->enable_fallback_attempt; 148 copy->enable_false_start = original->enable_false_start; 149 copy->enable_tickets = original->enable_tickets; 150 copy->enable_sct = original->enable_sct; 151 copy->enable_ocsp = original->enable_ocsp; 152 copy->enable_resumption = original->enable_resumption; 153 copy->enable_renegotiation = original->enable_renegotiation; 154 copy->enable_early_data = original->enable_early_data; 155 156 if (original->server_name) { 157 copy->server_name = strdup(original->server_name); 158 } 159 if (original->identity) { 160 copy->identity = original->identity; 161 } 162 if (original->application_protocols) { 163 copy->application_protocols = xpc_copy(original->application_protocols); 164 } 165 if (original->ciphersuites) { 166 copy->ciphersuites = xpc_copy(original->ciphersuites); 167 } 168 if (original->dh_params) { 169 copy->dh_params = original->dh_params; 170 } 171 if (original->key_update_block) { 172 copy->key_update_block = original->key_update_block; 173 copy->key_update_queue = original->key_update_queue; 174 } 175 if (original->challenge_block) { 176 copy->challenge_block = original->challenge_block; 177 copy->challenge_queue = original->challenge_queue; 178 } 179 if (original->verify_block) { 180 copy->verify_block = original->verify_block; 181 copy->verify_queue = original->verify_queue; 182 } 183 if (original->session_state) { 184 copy->session_state = original->session_state; 185 } 186 if (original->session_update_block) { 187 copy->session_update_block = original->session_update_block; 188 copy->session_update_queue = original->session_update_queue; 189 } 190 if (original->pre_shared_keys) { 191 copy->pre_shared_keys = xpc_copy(original->pre_shared_keys); 192 } 193 194 return new_options; 195 } 196 197 static void * _Nullable 198 mock_protocol_allocate_options(__unused nw_protocol_definition_t definition) 199 { 200 return calloc(1, sizeof(struct sec_protocol_options_content)); 201 } 202 203 static void 204 mock_protocol_deallocate_options(__unused nw_protocol_definition_t definition, void *options) 205 { 206 sec_protocol_options_content_t content = (sec_protocol_options_content_t)options; 207 if (content) { 208 // pass 209 } 210 free(content); 211 } 212 213 static void 214 mock_protocol_set_options_allocator(nw_protocol_definition_t definition, 215 nw_protocol_definition_allocate_f allocate_function, 216 nw_protocol_definition_copy_f copy_function, 217 nw_protocol_definition_deallocate_f deallocate_function) 218 { 219 static void *libnetworkImage = NULL; 220 static dispatch_once_t onceToken; 221 static void (*_nw_protocol_definition_set_options_allocator)(nw_protocol_definition_t, nw_protocol_definition_allocate_f, nw_protocol_definition_copy_f, nw_protocol_definition_deallocate_f) = NULL; 222 223 dispatch_once(&onceToken, ^{ 224 libnetworkImage = dlopen("/usr/lib/libnetwork.dylib", RTLD_LAZY | RTLD_LOCAL); 225 if (NULL != libnetworkImage) { 226 _nw_protocol_definition_set_options_allocator = (__typeof(_nw_protocol_definition_set_options_allocator))dlsym(libnetworkImage, "nw_protocol_definition_set_options_allocator"); 227 if (NULL == _nw_protocol_definition_set_options_allocator) { 228 os_log_error(OS_LOG_DEFAULT, "dlsym libnetwork nw_protocol_definition_set_options_allocator"); 229 } 230 } else { 231 os_log_error(OS_LOG_DEFAULT, "dlopen libnetwork"); 232 } 233 }); 234 235 if (_nw_protocol_definition_set_options_allocator == NULL) { 236 return; 237 } 238 239 _nw_protocol_definition_set_options_allocator(definition, allocate_function, copy_function, deallocate_function); 240 } 241 242 static nw_protocol_definition_t 243 mock_protocol_definition_create_with_identifier(nw_protocol_identifier_const_t identifier) 244 { 245 static void *libnetworkImage = NULL; 246 static dispatch_once_t onceToken; 247 static nw_protocol_definition_t (*_nw_protocol_definition_create_with_identifier)(nw_protocol_identifier_const_t) = NULL; 248 249 dispatch_once(&onceToken, ^{ 250 libnetworkImage = dlopen("/usr/lib/libnetwork.dylib", RTLD_LAZY | RTLD_LOCAL); 251 if (NULL != libnetworkImage) { 252 _nw_protocol_definition_create_with_identifier = (__typeof(_nw_protocol_definition_create_with_identifier))dlsym(libnetworkImage, "nw_protocol_definition_create_with_identifier"); 253 if (NULL == _nw_protocol_definition_create_with_identifier) { 254 os_log_error(OS_LOG_DEFAULT, "dlsym libnetwork nw_protocol_definition_create_with_identifier"); 255 } 256 } else { 257 os_log_error(OS_LOG_DEFAULT, "dlopen libnetwork"); 258 } 259 }); 260 261 if (_nw_protocol_definition_create_with_identifier == NULL) { 262 return NULL; 263 } 264 265 return _nw_protocol_definition_create_with_identifier(identifier); 266 } 267 268 static nw_protocol_definition_t 269 mock_protocol_copy_definition(void) 270 { 271 static nw_protocol_definition_t definition = NULL; 272 static dispatch_once_t onceToken; 273 dispatch_once(&onceToken, ^{ 274 const char *mock_protocol_name = "SecProtocolConfigTestMock"; 275 definition = mock_protocol_definition_create_with_identifier(_mock_protocol_identifier(mock_protocol_name, strlen(mock_protocol_name))); 276 mock_protocol_set_options_allocator(definition, 277 mock_protocol_allocate_options, 278 mock_protocol_copy_options, 279 mock_protocol_deallocate_options); 280 mock_protocol_set_metadata_allocator(definition, 281 mock_protocol_allocate_metadata, 282 mock_protocol_deallocate_metadata); 283 284 }); 285 286 return definition; 287 } 288 289 #pragma clang diagnostic push 290 #pragma clang diagnostic ignored "-Wdeprecated-declarations" 291 static SSLProtocol 292 protocol_string_to_version(const char *protocol) 293 { 294 if (protocol == NULL) { 295 return kSSLProtocolUnknown; 296 } 297 298 const char *tlsv10 = "TLSv1.0"; 299 const char *tlsv11 = "TLSv1.1"; 300 const char *tlsv12 = "TLSv1.2"; 301 const char *tlsv13 = "TLSv1.3"; 302 303 if (strlen(protocol) == strlen(tlsv10) && strncmp(protocol, tlsv10, strlen(protocol)) == 0) { 304 return kTLSProtocol1; 305 } else if (strlen(protocol) == strlen(tlsv11) && strncmp(protocol, tlsv11, strlen(protocol)) == 0) { 306 return kTLSProtocol11; 307 } else if (strlen(protocol) == strlen(tlsv12) && strncmp(protocol, tlsv12, strlen(protocol)) == 0) { 308 return kTLSProtocol12; 309 } else if (strlen(protocol) == strlen(tlsv13) && strncmp(protocol, tlsv13, strlen(protocol)) == 0) { 310 return kTLSProtocol13; 311 } 312 313 return kSSLProtocolUnknown; 314 } 315 #pragma clang diagnostic pop 316 317 @interface SecProtocolConfigurationTest : XCTestCase 318 @end 319 320 @implementation SecProtocolConfigurationTest 321 322 - (void)setUp { 323 } 324 325 - (void)tearDown { 326 } 327 328 - (sec_protocol_options_t)create_sec_protocol_options { 329 static void *libnetworkImage = NULL; 330 static dispatch_once_t onceToken; 331 332 static sec_protocol_options_t (*_nw_protocol_create_options)(nw_protocol_definition_t) = NULL; 333 334 dispatch_once(&onceToken, ^{ 335 libnetworkImage = dlopen("/usr/lib/libnetwork.dylib", RTLD_LAZY | RTLD_LOCAL); 336 if (NULL != libnetworkImage) { 337 _nw_protocol_create_options = (__typeof(_nw_protocol_create_options))dlsym(libnetworkImage, "nw_protocol_create_options"); 338 if (NULL == _nw_protocol_create_options) { 339 os_log_error(OS_LOG_DEFAULT, "dlsym libnetwork _nw_protocol_create_options"); 340 } 341 } else { 342 os_log_error(OS_LOG_DEFAULT, "dlopen libnetwork"); 343 } 344 }); 345 346 if (_nw_protocol_create_options == NULL) { 347 return nil; 348 } 349 350 return (sec_protocol_options_t)_nw_protocol_create_options(mock_protocol_copy_definition()); 351 } 352 353 static bool 354 isLocalTLD(NSString *host) 355 { 356 if ([host length] == 0) { 357 return false; 358 } 359 if ([host hasSuffix:@".local"] || [host hasSuffix:@".local."]) { 360 return true; 361 } 362 if ([host rangeOfString:@"."].location == NSNotFound) { 363 return true; 364 } 365 return false; 366 } 367 368 - (void)testExampleFile:(NSURL *)path 369 { 370 NSData *exampleData = [[NSData alloc] initWithContentsOfURL:path]; 371 NSDictionary *exampleATS = [NSJSONSerialization JSONObjectWithData:exampleData options:kNilOptions error:nil]; 372 XCTAssertNotNil(exampleATS, @"Loading %@ failed", path); 373 if (!exampleATS) { 374 return; 375 } 376 377 sec_protocol_configuration_builder_t builder = sec_protocol_configuration_builder_create((__bridge CFDictionaryRef)exampleATS, true); 378 sec_protocol_configuration_t configuration = sec_protocol_configuration_create_with_builder(builder); 379 XCTAssertTrue(configuration != nil, @"failed to build configuration"); 380 if (!configuration) { 381 return; 382 } 383 384 __block bool allows_local_networking = false; 385 [exampleATS enumerateKeysAndObjectsUsingBlock:^(id _key, id _obj, BOOL *stop) { 386 NSString *key = (NSString *)_key; 387 if ([key isEqualToString:@"NSAllowsLocalNetworking"]) { 388 NSNumber *value = (NSNumber *)_obj; 389 if (value) { 390 allows_local_networking = [value boolValue]; 391 } 392 } 393 }]; 394 395 [exampleATS enumerateKeysAndObjectsUsingBlock:^(id _key, id _obj, BOOL *stop) { 396 NSString *key = (NSString *)_key; 397 if ([key isEqualToString:@"NSExceptionDomains"]) { 398 NSDictionary *domain_map = (NSDictionary *)_obj; 399 [domain_map enumerateKeysAndObjectsUsingBlock:^(id _domain, id _domain_entry, BOOL *_domain_stop) { 400 NSString *domain = (NSString *)_domain; 401 NSDictionary *entry = (NSDictionary *)_domain_entry; 402 403 #define BOOLEAN_FOR_KEY(key, value, default) \ 404 bool value = default; \ 405 { \ 406 NSNumber *nsValue = [entry valueForKey:key]; \ 407 if (nsValue) { \ 408 value = [nsValue boolValue]; \ 409 } \ 410 } 411 #define STRING_FOR_KEY(key, value, default) \ 412 NSString *value = default; \ 413 { \ 414 NSString *nsValue = [entry valueForKey:key]; \ 415 if (nsValue) { \ 416 value = nsValue; \ 417 } \ 418 } 419 BOOLEAN_FOR_KEY(@"NSExceptionAllowsInsecureHTTPLoads", allows_http, false); 420 BOOLEAN_FOR_KEY(@"NSIncludesSubdomains", includes_subdomains, false); 421 BOOLEAN_FOR_KEY(@"NSExceptionRequiresForwardSecrecy", requires_pfs, false); 422 STRING_FOR_KEY(@"NSExceptionMinimumTLSVersion", minimum_tls, @"TLSv1.2"); 423 #undef STRING_FOR_KEY 424 #undef BOOLEAN_FOR_KEY 425 426 SSLProtocol minimum_protocol_version = protocol_string_to_version([minimum_tls cStringUsingEncoding:NSUTF8StringEncoding]); 427 428 sec_protocol_options_t options = [self create_sec_protocol_options]; 429 sec_protocol_options_t transformed = sec_protocol_configuration_copy_transformed_options_for_host(configuration, options, [domain cStringUsingEncoding:NSUTF8StringEncoding]); 430 sec_protocol_options_access_handle(transformed, ^bool(void *handle) { 431 sec_protocol_options_content_t content = (sec_protocol_options_content_t)handle; 432 SEC_PROTOCOL_METADATA_VALIDATE(content, false); 433 434 XCTAssertTrue(content->ats_required == true); 435 XCTAssertTrue(content->min_version == minimum_protocol_version); 436 if (requires_pfs) { 437 XCTAssertTrue(content->ciphersuites != nil); 438 } else { 439 XCTAssertTrue(content->ciphersuites == nil); 440 } 441 }); 442 443 bool is_direct = isLocalTLD(domain); 444 bool tls_required = sec_protocol_configuration_tls_required_for_host(configuration, [domain cStringUsingEncoding:NSUTF8StringEncoding], is_direct); 445 if (is_direct) { 446 // If the hostname is direct, then we permit it if the NSAllowsLocalNetworking exception is set. 447 XCTAssertTrue(allows_local_networking != tls_required); 448 } else { 449 // Otherwise, we require TLS it the NSExceptionAllowsInsecureHTTPLoads flag is set. 450 XCTAssertTrue(allows_http != tls_required); 451 } 452 }]; 453 } 454 }]; 455 } 456 457 - (void)testExampleATSDictionaries { 458 NSArray <NSURL *>* testFiles = [[NSBundle bundleForClass:[self class]]URLsForResourcesWithExtension:@".json" subdirectory:@"."]; 459 [testFiles enumerateObjectsUsingBlock:^(NSURL* _Nonnull path, __unused NSUInteger idx, BOOL * _Nonnull stop) { 460 [self testExampleFile:path]; 461 }]; 462 } 463 464 @end