2 // SecTrustExceptionResetCount.m
6 #import <Foundation/Foundation.h>
7 #import "SecTrustExceptionResetCount.h"
9 #import <utilities/SecCFWrappers.h>
10 #import <utilities/SecFileLocations.h>
12 static NSString *kExceptionResetCountKey = @"ExceptionResetCount";
13 static NSString *exceptionResetCounterFile = @"com.apple.security.exception_reset_counter.plist";
15 /* Returns the path to the, existing or internally-created, 'exceptionResetCounterFile' file. */
16 static NSString *SecPlistFileExistsInKeychainDirectory(CFErrorRef *error) {
17 NSString *status = NULL;
19 NSString *path = [(__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)exceptionResetCounterFile) path];
21 secerror("Unable to address permanent storage for '%{public}@'.", exceptionResetCounterFile);
23 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENOENT, NULL);
27 secinfo("trust", "'%{public}@' is at '%{public}@'.", exceptionResetCounterFile, path);
29 NSFileManager *fm = [NSFileManager defaultManager];
31 secerror("Failed to initialize the file manager in '%{public}s'.", __func__);
33 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENOMEM, NULL);
39 bool fileExists = [fm fileExistsAtPath:path isDirectory:&isDir];
41 secerror("'%{public}@' is a directory. (not a file)", path);
43 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EISDIR, NULL);
48 secdebug("trust", "'%{public}@' already exists.", path);
53 if (![fm createFileAtPath:path contents:nil attributes:nil]) {
54 secerror("Failed to create permanent storage at '%{public}@'.", path);
56 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EIO, NULL);
60 secinfo("trust", "'%{public}@' has been created.", path);
66 static uint64_t SecReadPlistFromFileInKeychainDirectory(CFErrorRef *error) {
69 CFErrorRef localError = NULL;
70 NSString *path = SecPlistFileExistsInKeychainDirectory(&localError);
75 secerror("Permanent storage for the exceptions epoch is unavailable.");
79 secinfo("trust", "Permanent storage for the exceptions epoch is missing. Defaulting to value %llu.", value);
83 NSMutableDictionary *plDict = [NSMutableDictionary dictionaryWithContentsOfFile:path];
85 secerror("Failed to read from permanent storage at '%{public}@' or the data is bad. Defaulting to value %llu.", path, value);
87 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENXIO, NULL);
92 id valueObject = [plDict objectForKey:kExceptionResetCountKey];
94 secinfo("trust", "Could not find key '%{public}@'. Defaulting to value %llu.", kExceptionResetCountKey, value);
96 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENXIO, NULL);
100 if (![valueObject isKindOfClass:[NSNumber class]]) {
101 secerror("The value for key '%{public}@' is not a number.", kExceptionResetCountKey);
103 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EDOM, NULL);
108 value = [valueObject unsignedIntValue];
110 secinfo("trust", "'%{public}@' is %llu.", kExceptionResetCountKey, value);
114 static bool SecWritePlistToFileInKeychainDirectory(uint64_t exceptionResetCount, CFErrorRef *error) {
117 CFErrorRef localError = NULL;
118 SecPlistFileExistsInKeychainDirectory(&localError);
123 secerror("Permanent storage for the exceptions epoch is unavailable.");
127 NSString *path = [(__bridge_transfer NSURL*)SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)exceptionResetCounterFile) path];
129 secerror("Unable to address permanent storage for '%{public}@'.", exceptionResetCounterFile);
131 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EIO, NULL);
136 NSMutableDictionary *dataToSave = [NSMutableDictionary new];
138 secerror("Failed to allocate memory for the exceptions epoch structure.");
140 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENOMEM, NULL);
144 dataToSave[@"Version"] = [NSNumber numberWithUnsignedInteger:1];
145 dataToSave[kExceptionResetCountKey] = [NSNumber numberWithUnsignedInteger:exceptionResetCount];
147 status = [dataToSave writeToFile:path atomically:YES];
149 secerror("Failed to write to permanent storage at '%{public}@'.", path);
151 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EIO, NULL);
156 secinfo("trust", "'%{public}@' has been committed to permanent storage at '%{public}@'.", kExceptionResetCountKey, path);
160 uint64_t SecTrustServerGetExceptionResetCount(CFErrorRef *error) {
161 CFErrorRef localError = NULL;
162 uint64_t exceptionResetCount = SecReadPlistFromFileInKeychainDirectory(&localError);
163 /* Treat ENXIO as a transient error; I/O seems to be working but we have failed to read the current epoch.
164 * That's expected when epoch is still 0 and there is nothing to store in permanent storage. (and later read)
166 if (localError && CFEqualSafe(CFErrorGetDomain(localError), kCFErrorDomainPOSIX) && CFErrorGetCode(localError) == ENXIO) {
167 CFRelease(localError);
170 if (error && localError) {
173 secinfo("trust", "exceptionResetCount: %llu (%s)", exceptionResetCount, error ? (*error ? "Error" : "OK") : "N/A");
174 return exceptionResetCount;
177 bool SecTrustServerIncrementExceptionResetCount(CFErrorRef *error) {
180 uint64_t currentExceptionResetCount = SecTrustServerGetExceptionResetCount(error);
181 if (error && *error) {
182 secerror("Failed to increment the extensions epoch.");
185 if (currentExceptionResetCount >= INT64_MAX) {
186 secerror("Current exceptions epoch value is too large. (%llu) Won't increment.", currentExceptionResetCount);
188 *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ERANGE, NULL);
193 return SecWritePlistToFileInKeychainDirectory(currentExceptionResetCount + 1, error);