SecTrustExceptionResetCount.m   [plain text]


//
//  SecTrustExceptionResetCount.m
//  Security_ios
//

#import <Foundation/Foundation.h>
#import "SecTrustExceptionResetCount.h"

#import <utilities/SecCFWrappers.h>
#import <utilities/SecFileLocations.h>
#import "trust/trustd/trustdFileLocations.h"

static NSString *kExceptionResetCountKey = @"ExceptionResetCount";
static NSString *exceptionResetCounterFile = @"com.apple.security.exception_reset_counter.plist";

static NSURL *ExceptionsResetCounterOldUrl (void) {
    return CFBridgingRelease(SecCopyURLForFileInKeychainDirectory((__bridge CFStringRef)exceptionResetCounterFile));
}

static NSURL *ExceptionsResetCounterUrl(void) {
    return CFBridgingRelease(SecCopyURLForFileInPrivateUserTrustdDirectory((__bridge CFStringRef)exceptionResetCounterFile));
}

static uint64_t ReadExceptionsCountFromUrl(NSURL *url, CFErrorRef *error) {
    @autoreleasepool {
        uint64_t value = 0;
        NSError *nserror = nil;
        NSMutableDictionary *plDict = [[NSDictionary dictionaryWithContentsOfURL:url error:&nserror] mutableCopy];
        if (!plDict) {
            secerror("Failed to read from permanent storage at '%{public}@' or the data is bad. Defaulting to value %llu.", url, value);
            if (error) {
                *error = (CFErrorRef)CFBridgingRetain(nserror);
            }
            return value;
        }

        id valueObject = [plDict objectForKey:kExceptionResetCountKey];
        if (!valueObject) {
            secinfo("trust", "Could not find key '%{public}@'. Defaulting to value %llu.", kExceptionResetCountKey, value);
            if (error) {
                *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENXIO, NULL);
            }
            return value;
        }
        if (![valueObject isKindOfClass:[NSNumber class]]) {
            secerror("The value for key '%{public}@' is not a number.", kExceptionResetCountKey);
            if (error) {
                *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, EDOM, NULL);
            }
            return value;
        }

        value = [valueObject unsignedIntValue];

        secinfo("trust", "'%{public}@' is %llu.", kExceptionResetCountKey, value);
        return value;
    }
}

static bool WriteExceptionsCounterToUrl(uint64_t exceptionResetCount, NSURL *url, CFErrorRef *error) {
    @autoreleasepool {
        bool status = false;
        NSMutableDictionary *dataToSave = [NSMutableDictionary new];
        if (!dataToSave) {
            secerror("Failed to allocate memory for the exceptions epoch structure.");
            if (error) {
                *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ENOMEM, NULL);
            }
            return status;
        }
        dataToSave[@"Version"] = [NSNumber numberWithUnsignedInteger:1];
        dataToSave[kExceptionResetCountKey] = [NSNumber numberWithUnsignedInteger:exceptionResetCount];

        NSError *nserror = nil;
        status = [dataToSave writeToClassDURL:url permissions:0600 error:&nserror];
        if (!status) {
            secerror("Failed to write to permanent storage at '%{public}@'.", url);
            if (error) {
                *error = (CFErrorRef)CFBridgingRetain(nserror);
            }
            return status;
        }

        secinfo("trust", "'%{public}@' has been committed to permanent storage at '%{public}@'.", kExceptionResetCountKey, url);
        return status;
    }
}

uint64_t SecTrustServerGetExceptionResetCount(CFErrorRef *error) {
    CFErrorRef localError = NULL;
    uint64_t exceptionResetCount = ReadExceptionsCountFromUrl(ExceptionsResetCounterUrl(), &localError);
    if (localError) {
        if (error) {
            *error = localError;
        } else {
            CFReleaseNull(localError);
        }
    }
    secinfo("trust", "exceptionResetCount: %llu (%s)", exceptionResetCount, error ? (*error ? "Error" : "OK") : "N/A");
    return exceptionResetCount;
}

bool SecTrustServerIncrementExceptionResetCount(CFErrorRef *error) {
    bool status = false;

    uint64_t currentExceptionResetCount = SecTrustServerGetExceptionResetCount(error);
    if (error && *error) {
        secerror("Failed to increment the extensions epoch.");
        return status;
    }
    if (currentExceptionResetCount >= INT64_MAX) {
        secerror("Current exceptions epoch value is too large. (%llu) Won't increment.", currentExceptionResetCount);
        if (error) {
            *error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, ERANGE, NULL);
        }
        return status;
    }

    return WriteExceptionsCounterToUrl(currentExceptionResetCount + 1, ExceptionsResetCounterUrl(), error);
}

void SecTrustServerMigrateExceptionsResetCount(void) {
    CFErrorRef error = NULL;
    (void)ReadExceptionsCountFromUrl(ExceptionsResetCounterUrl(), &error);
    if (!error) {
        secdebug("trust", "already migrated exceptions reset counter");
        return;
    }
    CFReleaseNull(error);
    secdebug("trust", "migrating exceptions reset counter");
    uint64_t currentExceptionResetCount = ReadExceptionsCountFromUrl(ExceptionsResetCounterOldUrl(), NULL);

    if (!WriteExceptionsCounterToUrl(currentExceptionResetCount, ExceptionsResetCounterUrl(), &error)) {
        secerror("Failed to migrate exceptions reset count: %@", error);
        CFReleaseNull(error);
        return;
    }

    WithPathInKeychainDirectory((__bridge CFStringRef)exceptionResetCounterFile, ^(const char *utf8String) {
        remove(utf8String);
    });
}