/ protocol / SecProtocolConfigurationTest.m
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