si-20-sectrust-policies.m [plain text]
/*
* Copyright (c) 2016-2017 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/* INSTRUCTIONS FOR ADDING NEW SUBTESTS:
* 1. Add the certificates, as DER-encoded files with the 'cer' extension, to OSX/shared_regressions/si-20-sectrust-policies-data/
* NOTE: If your cert needs to be named with "(i[Pp]hone|i[Pp]ad|i[Pp]od)", you need to make two copies -- one named properly
* and another named such that it doesn't match that regex. Use the regex trick below for TARGET_OS_TV to make sure your test
* works.
* 2. Add a new dictionary to the test plist (OSX/shared_regressions/si-20-sectrust-policies-data/PinningPolicyTrustTest.plist).
* This dictionary must include: (see constants below)
* MajorTestName
* MinorTestName
* Policies
* Leaf
* Intermediates
* ExpectedResult
* It is strongly recommended that all test dictionaries include the Anchors and VerifyDate keys.
* Addtional optional keys are defined below.
*/
/* INSTRUCTIONS FOR DEBUGGING SUBTESTS:
* Add a debugging.plist to OSX/shared_regressions/si-20-sectrust-policies-data/ containing only those subtest dictionaries
* you want to debug.
*/
#include "shared_regressions.h"
#include <AssertMacros.h>
#import <Foundation/Foundation.h>
#include <utilities/SecInternalReleasePriv.h>
#include <utilities/SecCFRelease.h>
#include <Security/SecCertificate.h>
#include <Security/SecCertificatePriv.h>
#include <Security/SecPolicyPriv.h>
#include <Security/SecTrust.h>
/* Key Constants for Test Dictionaries */
const NSString *kSecTrustTestMajorTestName = @"MajorTestName"; /* Required; value: string */
const NSString *kSecTrustTestMinorTestName = @"MinorTestName"; /* Required; value: string */
const NSString *kSecTrustTestPolicies = @"Policies"; /* Required; value: dictionary or array of dictionaries */
const NSString *kSecTrustTestLeaf = @"Leaf"; /* Required; value: string */
const NSString *kSecTrustTestIntermediates = @"Intermediates"; /* Required; value: string or array of strings */
const NSString *kSecTrustTestAnchors = @"Anchors"; /* Recommended; value: string or array of strings */
const NSString *kSecTrustTestVerifyDate = @"VerifyDate"; /* Recommended; value: date */
const NSString *kSecTrustTestExpectedResult = @"ExpectedResult"; /* Required; value: number */
const NSString *kSecTrustTestChainLength = @"ChainLength"; /* Optional; value: number */
const NSString *kSecTrustTestEnableTestCerts= @"EnableTestCertificates"; /* Optional; value: string */
/* Key Constants for Policies Dictionaries */
const NSString *kSecTrustTestPolicyOID = @"PolicyIdentifier"; /* Required; value: string */
const NSString *kSecTrustTestPolicyProperties = @"Properties"; /* Optional; value: dictionary, see Policy Value Constants, SecPolicy.h */
const NSString *kSecTrustTestPinningPolicyResources = @"si-20-sectrust-policies-data";
@interface TestObject : NSObject
@property (readonly) NSMutableArray *certificates;
@property (readonly) NSMutableArray *policies;
@property (readonly) NSMutableArray *anchors;
@property (readonly) NSString *fullTestName;
- (id)initWithMajorTestName:(NSString *)majorTestName minorTestName:(NSString *)minorTestName;
- (bool)addLeafToCertificates:(NSString *)leafName;
- (bool)addCertsToArray:(id)pathsObj outputArray:(NSMutableArray *)outArray;
- (bool)addIntermediatesToCertificates:(id)intermediatesObj;
- (bool)addPolicies:(id)policiesObj;
- (bool)addAnchors:(id)anchorsObj;
@end
@implementation TestObject
#if TARGET_OS_TV
/* Mastering removes all files named i[Pp]hone, so dynamically replace any i[Pp]hone's with
* iPh0ne. We have two copies in the resources directory. */
- (NSString *)replaceiPhoneNamedFiles:(NSString *)filename {
NSRegularExpression *regularExpression = [NSRegularExpression regularExpressionWithPattern:@"iphone"
options:NSRegularExpressionCaseInsensitive
error:nil];
NSString *newfilename = [regularExpression stringByReplacingMatchesInString:filename
options:0
range:NSMakeRange(0, [filename length])
withTemplate:@"iPh0ne"];
return newfilename;
}
#endif
- (id)init {
self = [super init];
return self;
}
- (id)initWithMajorTestName:(NSString *)majorTestName minorTestName:(NSString *)minorTestName {
if ((self = [super init])) {
_fullTestName = [[majorTestName stringByAppendingString:@"-"] stringByAppendingString:minorTestName];
}
return self;
}
- (bool)addLeafToCertificates:(NSString *)leafName {
SecCertificateRef cert;
NSString *path = nil, *filename = nil;
require_action_quiet(leafName, errOut,
fail("#if TARGET_OS_TV
filename = [self replaceiPhoneNamedFiles:leafName];
#else
filename = leafName;
#endif
path = [[NSBundle mainBundle]
pathForResource:filename
ofType:@"cer"
inDirectory:(NSString *)kSecTrustTestPinningPolicyResources];
require_action_quiet(path, errOut, fail(" cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
require_action_quiet(cert, errOut,
fail(" _fullTestName, path));
_certificates = [[NSMutableArray alloc] initWithObjects:(__bridge id)cert, nil];
CFReleaseNull(cert);
require_action_quiet(_certificates, errOut,
fail(" _fullTestName));
return true;
errOut:
return false;
}
- (bool)addCertsToArray:(id)pathsObj outputArray:(NSMutableArray *)outArray {
__block SecCertificateRef cert = NULL;
__block NSString* path = nil, *filename = nil;
require_action_quiet(pathsObj, errOut,
fail("
if ([pathsObj isKindOfClass:[NSString class]]) {
/* Only one cert path */
#if TARGET_OS_TV
filename = [self replaceiPhoneNamedFiles:pathsObj];
#else
filename = pathsObj;
#endif
path = [[NSBundle mainBundle]
pathForResource:filename
ofType:@"cer"
inDirectory:(NSString *)kSecTrustTestPinningPolicyResources];
require_action_quiet(path, errOut, fail(" _fullTestName, filename));
cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
require_action_quiet(cert, errOut,
fail(" _fullTestName, path));
[outArray addObject:(__bridge id)cert];
CFReleaseNull(cert);
}
else if ([pathsObj isKindOfClass:[NSArray class]]) {
/* Test has more than one intermediate */
[(NSArray *)pathsObj enumerateObjectsUsingBlock:^(NSString *resource, NSUInteger idx, BOOL *stop) {
#if TARGET_OS_TV
filename = [self replaceiPhoneNamedFiles:resource];
#else
filename = resource;
#endif
path = [[NSBundle mainBundle]
pathForResource:filename
ofType:@"cer"
inDirectory:(NSString *)kSecTrustTestPinningPolicyResources];
require_action_quiet(path, blockOut,
fail(" self->_fullTestName, (unsigned long)idx, filename));
cert = SecCertificateCreateWithData(NULL, (CFDataRef)[NSData dataWithContentsOfFile:path]);
require_action_quiet(cert, blockOut,
fail(" self->_fullTestName, (unsigned long) idx, path));
[outArray addObject:(__bridge id)cert];
CFReleaseNull(cert);
return;
blockOut:
CFReleaseNull(cert);
*stop = YES;
}];
}
else {
fail(" goto errOut;
}
return true;
errOut:
CFReleaseNull(cert);
return false;
}
- (bool)addIntermediatesToCertificates:(id)intermediatesObj {
require_action_quiet(intermediatesObj, errOut,
fail("
require_action_quiet([self addCertsToArray:intermediatesObj outputArray:_certificates], errOut,
fail("
if ([intermediatesObj isKindOfClass:[NSString class]]) {
require_action_quiet([_certificates count] == 2, errOut,
fail(" } else if ([intermediatesObj isKindOfClass:[NSArray class]]) {
require_action_quiet([_certificates count] == [(NSArray *)intermediatesObj count] + 1, errOut,
fail(" }
return true;
errOut:
return false;
}
- (bool)addPolicies:(id)policiesObj {
__block SecPolicyRef policy = NULL;
require_action_quiet(policiesObj, errOut,
fail("
_policies = [[NSMutableArray alloc] init];
require_action_quiet(_policies, errOut,
fail(" if ([policiesObj isKindOfClass:[NSDictionary class]]) {
/* Test has only one policy */
NSString *policyIdentifier = [(NSDictionary *)policiesObj objectForKey:kSecTrustTestPolicyOID];
NSDictionary *policyProperties = [(NSDictionary *)policiesObj objectForKey:kSecTrustTestPolicyProperties];
require_action_quiet(policyIdentifier, errOut, fail("
policy = SecPolicyCreateWithProperties((__bridge CFStringRef)policyIdentifier,
(__bridge CFDictionaryRef)policyProperties);
require_action_quiet(policy, errOut,
fail(" _fullTestName, policyIdentifier));
[_policies addObject:(__bridge id)policy];
CFReleaseNull(policy);
}
else if ([policiesObj isKindOfClass:[NSArray class]]) {
/* Test more than one intermediate */
[(NSArray *)policiesObj enumerateObjectsUsingBlock:^(NSDictionary *policyDict, NSUInteger idx, BOOL *stop) {
NSString *policyIdentifier = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyOID];
NSDictionary *policyProperties = [(NSDictionary *)policyDict objectForKey:kSecTrustTestPolicyProperties];
require_action_quiet(policyIdentifier, blockOut, fail("
policy = SecPolicyCreateWithProperties((__bridge CFStringRef)policyIdentifier,
(__bridge CFDictionaryRef)policyProperties);
require_action_quiet(policy, blockOut,
fail(" self->_fullTestName, policyIdentifier));
[self->_policies addObject:(__bridge id)policy];
CFReleaseNull(policy);
return;
blockOut:
CFReleaseNull(policy);
*stop = YES;
}];
require_action_quiet([(NSArray *)policiesObj count] == [_policies count], errOut,
fail(" }
else {
fail(" goto errOut;
}
return true;
errOut:
CFReleaseNull(policy);
return false;
}
- (bool)addAnchors:(id)anchorsObj {
require_action_quiet(anchorsObj, errOut,
fail("
_anchors = [[NSMutableArray alloc] init];
require_action_quiet(_anchors, errOut,
fail(" require_action_quiet([self addCertsToArray:anchorsObj outputArray:_anchors], errOut,
fail("
if ([anchorsObj isKindOfClass:[NSString class]]) {
require_action_quiet([_anchors count] == 1, errOut,
fail(" } else if ([anchorsObj isKindOfClass:[NSArray class]]) {
require_action_quiet([_anchors count] == [(NSArray *)anchorsObj count], errOut,
fail(" }
return true;
errOut:
return false;
}
@end
void (^runTestForObject)(id, NSUInteger, BOOL *) =
^(NSDictionary *testDict, NSUInteger idx, BOOL *stop) {
NSString *majorTestName = nil, *minorTestName = nil;
TestObject *test = nil;
SecTrustRef trust = NULL;
SecTrustResultType trustResult = kSecTrustResultInvalid;
NSDate *verifyDate = nil;
NSNumber *expectedResult = nil, *chainLen = nil;
/* Test certificates work by default on internal builds. We still need this to
* determine whether to expect failure for production devices. */
bool enableTestCertificates = (bool)[testDict objectForKey:kSecTrustTestEnableTestCerts];
/* Test name, for documentation purposes */
majorTestName = [testDict objectForKey:kSecTrustTestMajorTestName];
minorTestName = [testDict objectForKey:kSecTrustTestMinorTestName];
require_action_quiet(majorTestName && minorTestName, testOut,
fail("Failed to create test names for test test = [[TestObject alloc] initWithMajorTestName:majorTestName minorTestName:minorTestName];
require_action_quiet((test), testOut, fail("
/* Populate the certificates array */
require_quiet([test addLeafToCertificates:[testDict objectForKey:kSecTrustTestLeaf]], testOut);
require_quiet([test addIntermediatesToCertificates:[testDict objectForKey:kSecTrustTestIntermediates]], testOut);
/* Create the policies */
require_quiet([test addPolicies:[testDict objectForKey:kSecTrustTestPolicies]], testOut);
/* Create the trust object */
require_noerr_action_quiet(SecTrustCreateWithCertificates((__bridge CFArrayRef)test.certificates,
(__bridge CFArrayRef)test.policies,
&trust),
testOut,
fail("
/* Optionally set anchors in trust object */
if ([testDict objectForKey:kSecTrustTestAnchors]) {
require_quiet([test addAnchors:[testDict objectForKey:kSecTrustTestAnchors]], testOut);
require_noerr_action_quiet(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)test.anchors),
testOut,
fail(" }
/* Set optional date in trust object */
verifyDate = [testDict objectForKey:kSecTrustTestVerifyDate];
if (verifyDate) {
require_noerr_action_quiet(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)verifyDate), testOut,
fail(" verifyDate));
}
/* Evaluate */
require_noerr_action_quiet(SecTrustEvaluate(trust, &trustResult), testOut,
fail("
/* Check results */
require_action_quiet(expectedResult = [testDict objectForKey:kSecTrustTestExpectedResult],
testOut, fail("
/* If we enabled test certificates on a non-internal device, expect a failure instead of succees. */
if (enableTestCertificates && !SecIsInternalRelease() && ([expectedResult unsignedIntValue] == 4)) {
ok(trustResult == 5,
" test.fullTestName, trustResult, 5);
} else {
ok(trustResult == [expectedResult unsignedIntValue],
" test.fullTestName, trustResult, [expectedResult unsignedIntValue]);
}
require_quiet(trustResult == [expectedResult unsignedIntValue], testOut);
require_quiet(chainLen = [testDict objectForKey:kSecTrustTestChainLength], testOut);
require_action_quiet(SecTrustGetCertificateCount(trust) == [chainLen longValue], testOut,
fail(" test.fullTestName, SecTrustGetCertificateCount(trust), [chainLen longValue]));
testOut:
CFReleaseNull(trust);
};
static void tests(void)
{
NSURL *testPlist = nil;
NSArray *testsArray = nil;
testPlist = [[NSBundle mainBundle] URLForResource:@"debugging" withExtension:@"plist"
subdirectory:(NSString *)kSecTrustTestPinningPolicyResources ];
if (!testPlist) {
testPlist = [[NSBundle mainBundle] URLForResource:nil withExtension:@"plist"
subdirectory:(NSString *)kSecTrustTestPinningPolicyResources ];
}
require_action_quiet(testPlist, exit,
fail("Failed to get tests plist from
testsArray = [NSArray arrayWithContentsOfURL: testPlist];
require_action_quiet(testsArray, exit,
fail("Failed to create array from plist"));
plan_tests((int)[testsArray count]);
[testsArray enumerateObjectsUsingBlock:runTestForObject];
exit:
return;
}
int si_20_sectrust_policies(int argc, char *const *argv)
{
@autoreleasepool {
tests();
}
return 0;
}