/ SecurityTool / sharedTool / ct_exceptions.m
ct_exceptions.m
  1  /*
  2   * Copyright (c) 2018 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   * ct_exceptions.m
 24   */
 25  
 26  #import <Foundation/Foundation.h>
 27  #include <Security/SecTrustSettingsPriv.h>
 28  #include <Security/SecCertificatePriv.h>
 29  #include <utilities/fileIo.h>
 30  #include <utilities/SecCFWrappers.h>
 31  
 32  #include "SecurityCommands.h"
 33  
 34  NSString* toolAppID = @"com.apple.security";
 35  
 36  static int addCertFile(const char *fileName, NSMutableArray *array) {
 37      SecCertificateRef certRef = NULL;
 38      NSData *data = NULL;
 39      unsigned char *buf = NULL;
 40      size_t numBytes;
 41      int rtn = 0;
 42  
 43      if (readFileSizet(fileName, &buf, &numBytes)) {
 44          rtn = -1;
 45          goto errOut;
 46      }
 47  
 48      data = [NSData dataWithBytes:buf length:numBytes];
 49      certRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data);
 50      if (!certRef) {
 51          certRef = SecCertificateCreateWithPEM(NULL, (__bridge CFDataRef)data);
 52          if (!certRef) {
 53              rtn = -1;
 54              goto errOut;
 55          }
 56      }
 57  
 58      [array addObject:(__bridge id)certRef];
 59  
 60  errOut:
 61      /* Cleanup */
 62      free(buf);
 63      CFReleaseNull(certRef);
 64      return rtn;
 65  }
 66  
 67  static int returnCFError(CFErrorRef CF_CONSUMED error) {
 68      CFStringRef errorString = CFErrorCopyDescription(error);
 69      CFStringPerformWithCString(errorString, ^(const char *utf8Str) {
 70          fprintf(stderr, "Failed to copy CT exceptions: %s\n", utf8Str);
 71      });
 72      CFIndex errCode = CFErrorGetCode(error);
 73      CFReleaseNull(error);
 74      return (int)errCode;
 75  }
 76  
 77  static int resetExceptions(bool resetCerts, bool resetDomains) {
 78      bool result = false;
 79      CFErrorRef error = NULL;
 80      if (resetCerts) {
 81          NSDictionary *resetCertsDict = @{ (__bridge NSString*)kSecCTExceptionsCAsKey: @[] };
 82          result = SecTrustStoreSetCTExceptions(NULL, (__bridge CFDictionaryRef)resetCertsDict, &error);
 83      }
 84      if (!error && resetDomains) {
 85          NSDictionary *resetDomainsDict = @{ (__bridge NSString*)kSecCTExceptionsDomainsKey : @[] };
 86          result = SecTrustStoreSetCTExceptions(NULL, (__bridge CFDictionaryRef)resetDomainsDict, &error);
 87      }
 88      if (!result) {
 89          return returnCFError(error);
 90      }
 91      return 0;
 92  }
 93  
 94  static int addExceptions(CFStringRef key, NSArray *newExceptions) {
 95      CFErrorRef error = NULL;
 96      NSDictionary *currentExceptions = CFBridgingRelease(SecTrustStoreCopyCTExceptions((__bridge CFStringRef)toolAppID, &error));
 97      if (!currentExceptions && error) {
 98          return returnCFError(error);
 99      }
100  
101      NSMutableArray *exceptionsForKey = nil;
102      if (currentExceptions && currentExceptions[(__bridge NSString*)key]) {
103          exceptionsForKey = [currentExceptions[(__bridge NSString*)key] mutableCopy];
104          [exceptionsForKey addObjectsFromArray:newExceptions];
105      } else {
106          exceptionsForKey = [newExceptions copy];
107      }
108  
109      NSDictionary *newExceptionsDict = @{ (__bridge NSString*)key: exceptionsForKey };
110      bool result = SecTrustStoreSetCTExceptions(NULL, (__bridge CFDictionaryRef)newExceptionsDict, &error);
111      if (!result) {
112          return returnCFError(error);
113      }
114  
115      return 0;
116  }
117  
118  int add_ct_exceptions(int argc, char * const *argv) {
119      int arg;
120  
121      bool resetDomains = false;
122      bool resetCerts = false;
123  
124      NSMutableArray *domains = [NSMutableArray array];
125      NSMutableArray *certs = [NSMutableArray array];
126      NSDictionary *plist = nil;
127  
128      /* parse args */
129      if (argc == 1) {
130          return SHOW_USAGE_MESSAGE;
131      }
132  
133      while ((arg = getopt(argc, argv, "d:c:r:p:")) != -1) {
134          switch(arg) {
135              case 'd': {
136                  NSString *domain = [NSString stringWithCString:optarg encoding:NSUTF8StringEncoding];
137                  [domains addObject:domain];
138                  break;
139              }
140              case 'c':
141                  if (addCertFile(optarg, certs)) {
142                      fprintf(stderr, "Failed to read cert file\n");
143                      return 1;
144                  }
145                  break;
146              case 'r':
147                  if (!strcmp(optarg, "all")) {
148                      resetDomains = true;
149                      resetCerts = true;
150                  } else if (!strcmp(optarg, "domain")) {
151                      resetDomains = true;
152                  } else if (!strcmp(optarg, "cert")) {
153                      resetCerts = true;
154                  } else {
155                      return SHOW_USAGE_MESSAGE;
156                  }
157                  break;
158              case 'p':
159                  plist = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithCString:optarg encoding:NSUTF8StringEncoding]];
160                  break;
161              case '?':
162              default:
163                  return SHOW_USAGE_MESSAGE;
164          }
165      }
166  
167      /* handle reset operation */
168      if (resetCerts || resetDomains) {
169          return resetExceptions(resetCerts, resetDomains);
170      }
171  
172      /* set plist */
173      if (plist) {
174          CFErrorRef error = NULL;
175          bool result = SecTrustStoreSetCTExceptions(NULL, (__bridge CFDictionaryRef)plist, &error);
176          if (!result) {
177              return returnCFError(error);
178          } else {
179              return 0;
180          }
181      }
182  
183      /* add domains */
184      int status = 0;
185      if ([domains count]) {
186          status = addExceptions(kSecCTExceptionsDomainsKey, domains);
187      }
188      if (status != 0) {
189          fprintf(stderr, "failed to add domain exceptions\n");
190          return status;
191      }
192  
193      /* add certs */
194      if ([certs count]) {
195          NSMutableArray<NSDictionary *>*valuesForCAsKey = [NSMutableArray arrayWithCapacity:[certs count]];
196          [certs enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
197              SecCertificateRef cert = (__bridge SecCertificateRef)obj;
198              NSData* hash = CFBridgingRelease(SecCertificateCopySubjectPublicKeyInfoSHA256Digest(cert));
199              NSDictionary *value = @{ (__bridge NSString*)kSecCTExceptionsHashAlgorithmKey:@"sha256",
200                                       (__bridge NSString*)kSecCTExceptionsSPKIHashKey:hash };
201              [valuesForCAsKey addObject:value];
202          }];
203          status = addExceptions(kSecCTExceptionsCAsKey, valuesForCAsKey);
204      }
205      if (status != 0) {
206          fprintf(stderr, "failed to add cert exceptions\n");
207          return status;
208      }
209  
210      return 0;
211  }
212  
213  static int printExceptions(CFStringRef key, NSDictionary *allExceptions) {
214      if (!allExceptions || !allExceptions[(__bridge NSString*)key] ||
215          [allExceptions[(__bridge NSString*)key] count] == 0) {
216          CFStringPerformWithCString(key, ^(const char *utf8Str) {
217              fprintf(stdout, "No CT Exceptions for %s\n", utf8Str);
218          });
219          return 0;
220      }
221  
222      NSArray *exceptionsForKey = allExceptions[(__bridge NSString*)key];
223      NSMutableString *exceptionsString = [NSMutableString stringWithFormat:@"\t%@ : [",key];
224      [exceptionsForKey enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
225          if ([obj isKindOfClass:[NSString class]]) {
226              if (idx == 0) {
227                  [exceptionsString appendFormat:@"\"%@\"",obj];
228              } else {
229                  [exceptionsString appendFormat:@", \"%@\"", obj];
230              }
231          } else if ([obj isKindOfClass:[NSDictionary class]]) {
232              if (idx == 0) {
233                  [exceptionsString appendString:@"\n\t    "];
234              } else {
235                  [exceptionsString appendString:@"\t    "];
236              }
237              [exceptionsString appendFormat:@"\"%@:", obj[(__bridge NSString*)kSecCTExceptionsHashAlgorithmKey]];
238              NSString *hashHex = CFBridgingRelease(CFDataCopyHexString((__bridge CFDataRef)obj[(__bridge NSString*)kSecCTExceptionsSPKIHashKey]));
239              [exceptionsString appendString:hashHex];
240              if ([exceptionsForKey count] == idx + 1) { // last entry
241                  [exceptionsString appendString:@"\"\n"];
242              } else {
243                  [exceptionsString appendString:@"\",\n"];
244              }
245          }
246      }];
247      [exceptionsString appendString:@"]\n"];
248      CFStringPerformWithCString((__bridge CFStringRef)exceptionsString, ^(const char *utf8Str) {
249          fprintf(stdout, "\n%s\n", utf8Str);
250      });
251  
252      return 0;
253  }
254  
255  int show_ct_exceptions(int argc, char * const *argv) {
256      int arg;
257      bool allExceptions = false;
258      NSString *identifier = nil;
259      bool domainExceptions = false;
260      bool certExceptions = false;
261  
262      /* parse args */
263      while ((arg = getopt(argc, argv, "ai:dc")) != -1) {
264          switch(arg) {
265              case 'a':
266                  allExceptions = true;
267                  break;
268              case 'i':
269                  identifier = [NSString stringWithCString:optarg encoding:NSUTF8StringEncoding];
270                  break;
271              case 'd':
272                  domainExceptions = true;
273                  break;
274              case 'c':
275                  certExceptions = true;
276                  break;
277              case '?':
278              default:
279                  return SHOW_USAGE_MESSAGE;
280          }
281      }
282  
283      if (!domainExceptions && !certExceptions) {
284          /* Nothing specified, show both */
285          domainExceptions = true;
286          certExceptions = true;
287      }
288  
289      if (allExceptions) {
290          identifier = nil;
291          fprintf(stdout, "Showing exceptions for all apps\n");
292      } else if (!identifier) {
293          identifier = toolAppID;
294      }
295  
296      if (identifier) {
297          CFStringPerformWithCString((__bridge CFStringRef)identifier, ^(const char *utf8Str) {
298              fprintf(stdout, "Showing exceptions for %s\n", utf8Str);
299          });
300      }
301  
302      CFErrorRef error = NULL;
303      NSDictionary *results = CFBridgingRelease(SecTrustStoreCopyCTExceptions((__bridge CFStringRef)identifier, &error));
304  
305      /* Copy failed, return error */
306      if (!results && error) {
307          return returnCFError(error);
308      }
309  
310      /* print domain exceptions */
311      int status = 0;
312      if (domainExceptions) {
313          status = printExceptions(kSecCTExceptionsDomainsKey, results);
314      }
315      if (status != 0) {
316          fprintf(stderr, "failed to print domain exceptions\n");
317          return status;
318      }
319  
320      /* print cert exceptions */
321      if (certExceptions) {
322          status = printExceptions(kSecCTExceptionsCAsKey, results);
323      }
324      if (status != 0) {
325          fprintf(stderr, "failed to print cert exceptions\n");
326          return status;
327      }
328  
329  
330      return 0;
331  }