From 02b2aca600d4a0fe6fb259262bd6808ef889acde Mon Sep 17 00:00:00 2001 From: Apple Date: Tue, 26 Jan 2021 19:59:20 +0000 Subject: [PATCH] Security-59754.80.3.tar.gz --- Analytics/SQLite/SFSQLite.m | 2 - OSX/sec/Security/SecCertificate.c | 4 +- OSX/sec/Security/SecFrameworkStrings.h | 1 + OSX/sec/Security/SecPolicy.c | 48 ++ OSX/sec/Security/SecPolicy.list | 1 + OSX/sec/Security/SecPolicyChecks.list | 1 + Security.xcodeproj/project.pbxproj | 6 + tests/TrustTests/EvaluationTests/CTTests.m | 196 ++++++ trust/headers/SecPolicyPriv.h | 25 + trust/trustd/CertificateTransparency.h | 32 + trust/trustd/CertificateTransparency.m | 705 +++++++++++++++++++++ trust/trustd/OTATrustUtilities.h | 7 +- trust/trustd/OTATrustUtilities.m | 97 ++- trust/trustd/SecPolicyServer.c | 697 ++------------------ trust/trustd/SecPolicyServer.h | 3 + trust/trustd/SecTrustServer.h | 15 - 16 files changed, 1158 insertions(+), 682 deletions(-) create mode 100644 trust/trustd/CertificateTransparency.h create mode 100644 trust/trustd/CertificateTransparency.m diff --git a/Analytics/SQLite/SFSQLite.m b/Analytics/SQLite/SFSQLite.m index e0f1a8dd..d3d9acd3 100644 --- a/Analytics/SQLite/SFSQLite.m +++ b/Analytics/SQLite/SFSQLite.m @@ -301,7 +301,6 @@ allDone: */ - (void)attemptProperDatabasePermissions { -#if TARGET_OS_IPHONE NSFileManager* fm = [NSFileManager defaultManager]; [fm setAttributes:@{NSFilePosixPermissions : [NSNumber numberWithShort:0666]} ofItemAtPath:_path @@ -312,7 +311,6 @@ allDone: [fm setAttributes:@{NSFilePosixPermissions : [NSNumber numberWithShort:0666]} ofItemAtPath:[NSString stringWithFormat:@"%@-shm",_path] error:nil]; -#endif } - (BOOL)openWithError:(NSError **)error { diff --git a/OSX/sec/Security/SecCertificate.c b/OSX/sec/Security/SecCertificate.c index dd5f50d4..d9571455 100644 --- a/OSX/sec/Security/SecCertificate.c +++ b/OSX/sec/Security/SecCertificate.c @@ -1249,13 +1249,15 @@ static bool isAppleExtensionOID(const DERItem *extnID) static const uint8_t appleComponentExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0b }; static const uint8_t appleSigningExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0c }; static const uint8_t appleEncryptionExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0d }; + static const uint8_t appleExternalEncryptionExtensionArc[8] = { 0x2a,0x86,0x48,0x86,0xf7,0x63,0x64,0x0f }; if (!extnID || !extnID->data || (extnID->length <= sizeof(appleExtensionArc))) { return false; } return (!memcmp(extnID->data, appleExtensionArc, sizeof(appleExtensionArc)) || !memcmp(extnID->data, appleComponentExtensionArc, sizeof(appleComponentExtensionArc)) || !memcmp(extnID->data, appleSigningExtensionArc, sizeof(appleSigningExtensionArc)) || - !memcmp(extnID->data, appleEncryptionExtensionArc, sizeof(appleEncryptionExtensionArc))); + !memcmp(extnID->data, appleEncryptionExtensionArc, sizeof(appleEncryptionExtensionArc)) || + !memcmp(extnID->data, appleExternalEncryptionExtensionArc, sizeof(appleExternalEncryptionExtensionArc))); } static bool isCCCExtensionOID(const DERItem *extnID) diff --git a/OSX/sec/Security/SecFrameworkStrings.h b/OSX/sec/Security/SecFrameworkStrings.h index 0d6db54a..62286360 100644 --- a/OSX/sec/Security/SecFrameworkStrings.h +++ b/OSX/sec/Security/SecFrameworkStrings.h @@ -331,6 +331,7 @@ __BEGIN_DECLS #define SEC_TRUST_ERROR_ValidityPeriodMaximums SecStringWithDefaultValue("Certificate exceeds maximum temporal validity period", "Trust", 0, "Certificate exceeds maximum temporal validity period", "Error for certificates that exceed the system's maximum temporal validity") #define SEC_TRUST_ERROR_ServerAuthEKU SecStringWithDefaultValue("Extended key usage does not match certificate usage", "Trust", 0, "Extended key usage does not match certificate usage", "Error for extended key usage mismatch") #define SEC_TRUST_ERROR_UnparseableExtension SecStringWithDefaultValue("Unable to parse known extension", "Trust", 0, "Unable to parse known extension", "Error for unparseable known extensions") +#define SEC_TRUST_ERROR_NonTlsCTRequired SecStringWithDefaultValue("Certificate Transparency validation required but missing", "Trust", 0, "Certificate Transparency validation required but missing", "Error for missing Certificate Transparency validation") __END_DECLS diff --git a/OSX/sec/Security/SecPolicy.c b/OSX/sec/Security/SecPolicy.c index 5e32b6eb..2945918e 100644 --- a/OSX/sec/Security/SecPolicy.c +++ b/OSX/sec/Security/SecPolicy.c @@ -355,6 +355,8 @@ SecPolicyRef SecPolicyCreateWithProperties(CFTypeRef policyIdentifier, policy = SecPolicyCreateAppleComponentCertificate(rootDigest); } else if (CFEqual(policyIdentifier, kSecPolicyAppleAggregateMetricTransparency)) { policy = SecPolicyCreateAggregateMetricTransparency(!client); + } else if (CFEqual(policyIdentifier, kSecPolicyAppleAggregateMetricEncryption)) { + policy = SecPolicyCreateAggregateMetricEncryption(!client); } /* For a couple of common patterns we use the macro, but some of the * policies are deprecated (or not yet available), so we need to ignore the warning. */ @@ -4516,3 +4518,49 @@ errOut: CFReleaseSafe(options); return result; } + +SecPolicyRef SecPolicyCreateAggregateMetricEncryption(bool facilitator) +{ + CFMutableDictionaryRef options = NULL; + SecPolicyRef result = NULL; + + require(options = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks), errOut); + + SecPolicyAddBasicX509Options(options); + + /* Anchored to the Apple Roots */ + require(SecPolicyAddAppleAnchorOptions(options, kSecPolicyNameAggregateMetricEncryption), errOut); + + /* Exactly 3 certs in the chain */ + require(SecPolicyAddChainLengthOptions(options, 3), errOut); + + /* Intermediate marker OID matches AAICA 6 */ + add_element(options, kSecPolicyCheckIntermediateMarkerOid, CFSTR("1.2.840.113635.100.6.2.26")); + + /* Leaf marker OID matches expected OID for either Facilitator or Partner */ + if (facilitator) { + add_leaf_marker_string(options, CFSTR("1.2.840.113635.100.15.2")); + } else { + add_leaf_marker_string(options, CFSTR("1.2.840.113635.100.15.3")); + } + + /* Check revocation using any available method */ + add_element(options, kSecPolicyCheckRevocation, kSecPolicyCheckRevocationAny); + + /* RSA key sizes are 2048-bit or larger. EC key sizes are P-256 or larger. */ + require(SecPolicyAddStrongKeySizeOptions(options), errOut); + + /* Require CT */ + if (!SecIsInternalRelease() || !isCFPreferenceInSecurityDomain(CFSTR("disableAggregateMetricsCTCheck"))) { + add_element(options, kSecPolicyCheckNonTlsCTRequired, kCFBooleanTrue); + } + + require(result = SecPolicyCreate(kSecPolicyAppleAggregateMetricEncryption, + kSecPolicyNameAggregateMetricEncryption, options), errOut); + +errOut: + CFReleaseSafe(options); + return result; +} diff --git a/OSX/sec/Security/SecPolicy.list b/OSX/sec/Security/SecPolicy.list index bd0ddbc4..641f5d46 100644 --- a/OSX/sec/Security/SecPolicy.list +++ b/OSX/sec/Security/SecPolicy.list @@ -105,3 +105,4 @@ POLICYMACRO(AccessoryUpdateSigning, 99, E, AccessoryUpdateSignin POLICYMACRO(EscrowServiceIdKeySigning, 100, E, AppleEscrowServiceIdKeySigning, , Y, EscrowServiceIdKeySigning) POLICYMACRO(PCSEscrowServiceIdKeySigning, 101, E, ApplePCSEscrowServiceIdKeySigning, , Y, PCSEscrowServiceIdKeySigning) POLICYMACRO(AggregateMetricTransparency, 102, E, AggregateMetricTransparency, , , AggregateMetricTransparency) +POLICYMACRO(AggregateMetricEncryption, 103, E, AggregateMetricEncryption, , , AggregateMetricEncryption) diff --git a/OSX/sec/Security/SecPolicyChecks.list b/OSX/sec/Security/SecPolicyChecks.list index 91f55d0f..8711e54e 100644 --- a/OSX/sec/Security/SecPolicyChecks.list +++ b/OSX/sec/Security/SecPolicyChecks.list @@ -95,6 +95,7 @@ POLICYCHECKMACRO(IssuerNameConstraints, F, B, , , , 0x8001211F, errS POLICYCHECKMACRO(ValidityPeriodMaximums, R, C, , A, , 0x8001210D, errSecCertificateValidityPeriodTooLong) //CSSMERR_TP_CERT_SUSPENDED POLICYCHECKMACRO(ServerAuthEKU, R, U, , A, , 0x80012407, errSecInvalidExtendedKeyUsage) //CSSMERR_APPLETP_INVALID_EXTENDED_KEY_USAGE POLICYCHECKMACRO(UnparseableExtension, R, C, , , O, 0x80012410, errSecUnknownCertExtension) //CSSMERR_APPLETP_UNKNOWN_CERT_EXTEN +POLICYCHECKMACRO(NonTlsCTRequired, R, T, , A, , 0x80012114, errSecVerifyActionFailed) //CSSMERR_TP_VERIFY_ACTION_FAILED /******************************************************** ******************* Feature Toggles ********************* diff --git a/Security.xcodeproj/project.pbxproj b/Security.xcodeproj/project.pbxproj index 05c36438..6ff0fdc5 100644 --- a/Security.xcodeproj/project.pbxproj +++ b/Security.xcodeproj/project.pbxproj @@ -2525,6 +2525,7 @@ D47AB2D12356B2FE005A3801 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D47AB2CA2356AD72005A3801 /* Network.framework */; }; D47AB2D22356B325005A3801 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D47AB2CA2356AD72005A3801 /* Network.framework */; }; D47AB2D62357955F005A3801 /* Network.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D47AB2CA2356AD72005A3801 /* Network.framework */; }; + D47C457A255244AD00460750 /* CertificateTransparency.m in Sources */ = {isa = PBXBuildFile; fileRef = D47C4579255244AD00460750 /* CertificateTransparency.m */; }; D47CA65D1EB036450038E2BB /* libMobileGestalt.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = D47CA65C1EB036450038E2BB /* libMobileGestalt.dylib */; }; D47DCCB523427C7D00B80E37 /* md.m in Sources */ = {isa = PBXBuildFile; fileRef = D47DCCB423427C7D00B80E37 /* md.m */; }; D47E69401E92F75D002C8CF6 /* si-61-pkcs12.c in Sources */ = {isa = PBXBuildFile; fileRef = DCC78DD91D8085FC00865A7C /* si-61-pkcs12.c */; }; @@ -12827,6 +12828,8 @@ D47A085B2486EC1A000F2C49 /* AppleExternalRootCertificates.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppleExternalRootCertificates.h; sourceTree = ""; }; D47A55892466100A0039285D /* MSUDataAccessor.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MSUDataAccessor.framework; path = System/Library/PrivateFrameworks/MSUDataAccessor.framework; sourceTree = SDKROOT; }; D47AB2CA2356AD72005A3801 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = System/Library/Frameworks/Network.framework; sourceTree = SDKROOT; }; + D47C4579255244AD00460750 /* CertificateTransparency.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CertificateTransparency.m; sourceTree = ""; }; + D47C4730255246FC00460750 /* CertificateTransparency.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CertificateTransparency.h; sourceTree = ""; }; D47C56AB1DCA831C00E18518 /* lib_ios_x64.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = lib_ios_x64.xcconfig; path = xcconfig/lib_ios_x64.xcconfig; sourceTree = ""; }; D47C56AF1DCA841D00E18518 /* lib_ios_x64_shim.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = lib_ios_x64_shim.xcconfig; path = xcconfig/lib_ios_x64_shim.xcconfig; sourceTree = ""; }; D47CA65C1EB036450038E2BB /* libMobileGestalt.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libMobileGestalt.dylib; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.0.Internal.sdk/usr/lib/libMobileGestalt.dylib; sourceTree = DEVELOPER_DIR; }; @@ -24380,6 +24383,8 @@ D4B68C65211A8186009FED69 /* trustd_spi.c */, D47DCCB423427C7D00B80E37 /* md.m */, D47DCCB723427C8D00B80E37 /* md.h */, + D47C4579255244AD00460750 /* CertificateTransparency.m */, + D47C4730255246FC00460750 /* CertificateTransparency.h */, D43DBED71E99D17100C04AEA /* nameconstraints.c */, D43DBED81E99D17100C04AEA /* nameconstraints.h */, D43DBED91E99D17100C04AEA /* OTATrustUtilities.m */, @@ -34420,6 +34425,7 @@ 5F84950222DFB505008B3EFB /* SecTrustExceptionResetCount.m in Sources */, D43DBF041E99D1CA00C04AEA /* SecOCSPCache.c in Sources */, D43DBF051E99D1CA00C04AEA /* SecOCSPRequest.c in Sources */, + D47C457A255244AD00460750 /* CertificateTransparency.m in Sources */, D4B68C68211A827C009FED69 /* trustd_spi.c in Sources */, D43761671EB2996C00954447 /* SecRevocationNetworking.m in Sources */, D4EF3221215F0F7F000A31A5 /* SecTrustStoreServer.m in Sources */, diff --git a/tests/TrustTests/EvaluationTests/CTTests.m b/tests/TrustTests/EvaluationTests/CTTests.m index 40fdd7c4..b95fddaa 100644 --- a/tests/TrustTests/EvaluationTests/CTTests.m +++ b/tests/TrustTests/EvaluationTests/CTTests.m @@ -1944,3 +1944,199 @@ errOut: } @end + +// MARK: - +// MARK: Non-TLS CT tests + +@interface NonTlsCTTests : TrustEvaluationTestCase +@end + +@implementation NonTlsCTTests ++ (void)setUp { + [super setUp]; + NSURL *trustedLogsURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"CTlogs" + withExtension:@"plist" + subdirectory:@"si-82-sectrust-ct-data"]; + trustedCTLogs = [NSArray arrayWithContentsOfURL:trustedLogsURL]; +} + +- (SecPolicyRef)nonTlsCTRequiredPolicy +{ + SecPolicyRef policy = SecPolicyCreateBasicX509(); + SecPolicySetOptionsValue(policy, kSecPolicyCheckNonTlsCTRequired, kCFBooleanTrue); + return policy; +} + +#if !TARGET_OS_BRIDGE +/* Skip tests on bridgeOS where we don't do MobileAsset updates */ +- (void)testNoMACheckIn { + SecCertificateRef system_root = NULL, system_server_after = NULL; + SecTrustRef trust = NULL; + SecPolicyRef policy = [self nonTlsCTRequiredPolicy]; + NSArray *enforce_anchors = nil; + NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:562340800.0]; // October 27, 2018 at 6:46:40 AM PDT + + /* Mock a failing MobileAsset so we don't enforce via MobileAsset */ + id mockFailedMA = OCMClassMock([MAAsset class]); + OCMStub([mockFailedMA startCatalogDownload:[OCMArg any] + options:[OCMArg any] + then:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE((NSInteger){MADownloadFailed}), nil])]); + SecOTAPKIResetCurrentAssetVersion(NULL); + + require_action(system_root = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_root"], + errOut, fail("failed to create system root")); + require_action(system_server_after = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_server_after"], + errOut, fail("failed to create server cert")); + + enforce_anchors = @[ (__bridge id)system_root ]; + require_noerr_action(SecTrustCreateWithCertificates(system_server_after, policy, &trust), errOut, fail("failed to create trust")); + require_noerr_action(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)enforce_anchors), errOut, fail("failed to set anchors")); + require_noerr_action(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), errOut, fail("failed to set verify date")); + + // Out-of-date asset, test cert without CT passes + ok(SecTrustEvaluateWithError(trust, NULL), "non-CT cert failed with out-of-date asset"); + +errOut: + CFReleaseNull(system_root); + CFReleaseNull(system_server_after); + CFReleaseNull(policy); + CFReleaseNull(trust); +} + +- (void)testKillSwitch { + SecCertificateRef system_root = NULL, system_server_after = NULL; + SecTrustRef trust = NULL; + SecPolicyRef policy = [self nonTlsCTRequiredPolicy]; + NSArray *enforce_anchors = nil; + NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:562340800.0]; // October 27, 2018 at 6:46:40 AM PDT + + /* Mock setting a kill switch */ + UpdateKillSwitch((__bridge NSString *)kOTAPKIKillSwitchNonTLSCT, true); + + require_action(system_root = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_root"], + errOut, fail("failed to create system root")); + require_action(system_server_after = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_server_after"], + errOut, fail("failed to create system server cert")); + + enforce_anchors = @[ (__bridge id)system_root ]; + require_noerr_action(SecTrustCreateWithCertificates(system_server_after, policy, &trust), errOut, fail("failed to create trust")); + require_noerr_action(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)enforce_anchors), errOut, fail("failed to set anchors")); + require_noerr_action(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), errOut, fail("failed to set verify date")); + + // CT kill switch enabled so test cert without CT passes + ok(SecTrustEvaluateWithError(trust, NULL), "non-CT cert failed with kill switch enabled"); + + /* Remove the kill switch */ + UpdateKillSwitch((__bridge NSString *)kOTAPKIKillSwitchNonTLSCT, false); + +errOut: + CFReleaseNull(system_root); + CFReleaseNull(system_server_after); + CFReleaseNull(policy); + CFReleaseNull(trust); +} + +- (void) testWithMACheckIn { + SecCertificateRef system_root = NULL, system_server_after = NULL; + SecTrustRef trust = NULL; + SecPolicyRef policy = [self nonTlsCTRequiredPolicy]; + NSArray *enforce_anchors = nil; + NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:562340800.0]; // October 27, 2018 at 6:46:40 AM PDT + CFErrorRef error = nil; + + /* Mock a successful mobile asset check-in so that we enforce CT */ + XCTAssertTrue(UpdateOTACheckInDate(), "failed to set check-in date as now"); + + require_action(system_root = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_root"], + errOut, fail("failed to create system root")); + require_action(system_server_after = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_server_after"], + errOut, fail("failed to create system server cert")); + + enforce_anchors = @[ (__bridge id)system_root ]; + require_noerr_action(SecTrustCreateWithCertificates(system_server_after, policy, &trust), errOut, fail("failed to create trust")); + require_noerr_action(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)enforce_anchors), errOut, fail("failed to set anchors")); + require_noerr_action(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), errOut, fail("failed to set verify date")); + + // test system cert after date without CT fails (with check-in) + is(SecTrustEvaluateWithError(trust, &error), false, "non-CT cert with in-date asset succeeded"); + if (error) { + is(CFErrorGetCode(error), errSecVerifyActionFailed, "got wrong error code for non-ct cert, got %ld, expected %d", + (long)CFErrorGetCode(error), (int)errSecVerifyActionFailed); + } else { + fail("expected trust evaluation to fail and it did not."); + } + +errOut: + CFReleaseNull(system_root); + CFReleaseNull(system_server_after); + CFReleaseNull(policy); + CFReleaseNull(trust); + CFReleaseNull(error); +} +#endif // !TARGET_OS_BRIDGE + +- (void)testWithTrustedLogs { + SecCertificateRef system_root = NULL, system_server_after = NULL; + SecTrustRef trust = NULL; + SecPolicyRef policy = [self nonTlsCTRequiredPolicy]; + NSArray *enforce_anchors = nil; + NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:562340800.0]; // October 27, 2018 at 6:46:40 AM PDT + CFErrorRef error = nil; + + require_action(system_root = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_root"], + errOut, fail("failed to create system root")); + require_action(system_server_after = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_server_after"], + errOut, fail("failed to create system server cert")); + + enforce_anchors = @[ (__bridge id)system_root ]; + require_noerr_action(SecTrustCreateWithCertificates(system_server_after, policy, &trust), errOut, fail("failed to create trust")); + require_noerr_action(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)enforce_anchors), errOut, fail("failed to set anchors")); + require_noerr_action(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), errOut, fail("failed to set verify date")); + + // set trusted logs to trigger enforcing behavior + require_noerr_action(SecTrustSetTrustedLogs(trust, (__bridge CFArrayRef)trustedCTLogs), errOut, fail("failed to set trusted logs")); + + // test system cert without CT fails (with trusted logs) + is(SecTrustEvaluateWithError(trust, &error), false, "non-CT cert with trusted logs succeeded"); + if (error) { + is(CFErrorGetCode(error), errSecVerifyActionFailed, "got wrong error code for non-ct cert, got %ld, expected %d", + (long)CFErrorGetCode(error), (int)errSecVerifyActionFailed); + } else { + fail("expected trust evaluation to fail and it did not."); + } + +errOut: + CFReleaseNull(system_root); + CFReleaseNull(system_server_after); + CFReleaseNull(policy); + CFReleaseNull(trust); + CFReleaseNull(error); +} + +- (void) testSuccess { + SecCertificateRef system_root = NULL, leaf = NULL; + SecTrustRef trust = NULL; + SecPolicyRef policy = [self nonTlsCTRequiredPolicy]; + NSArray *enforce_anchors = nil; + NSDate *date = [NSDate dateWithTimeIntervalSinceReferenceDate:562340800.0]; // October 27, 2018 at 6:46:40 AM PDT + + require_action(system_root = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_root"], + errOut, fail("failed to create system root")); + require_action(leaf = (__bridge SecCertificateRef)[CTTests SecCertificateCreateFromResource:@"enforcement_system_server_after_scts"], + errOut, fail("failed to create system server cert")); + + enforce_anchors = @[ (__bridge id)system_root ]; + require_noerr_action(SecTrustCreateWithCertificates(leaf, policy, &trust), errOut, fail("failed to create trust")); + require_noerr_action(SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)enforce_anchors), errOut, fail("failed to set anchors")); + require_noerr_action(SecTrustSetVerifyDate(trust, (__bridge CFDateRef)date), errOut, fail("failed to set verify date")); + require_noerr_action(SecTrustSetTrustedLogs(trust, (__bridge CFArrayRef)trustedCTLogs), errOut, fail("failed to set trusted logs")); + ok(SecTrustEvaluateWithError(trust, NULL), "CT cert failed"); + +errOut: + CFReleaseNull(system_root); + CFReleaseNull(leaf); + CFReleaseNull(policy); + CFReleaseNull(trust); +} + +@end diff --git a/trust/headers/SecPolicyPriv.h b/trust/headers/SecPolicyPriv.h index 85f6237f..7caac7ee 100644 --- a/trust/headers/SecPolicyPriv.h +++ b/trust/headers/SecPolicyPriv.h @@ -197,6 +197,8 @@ extern const CFStringRef kSecPolicyApplePCSEscrowServiceIdKeySigning API_AVAILABLE(macos(10.15.6), ios(13.5.5)); extern const CFStringRef kSecPolicyAppleAggregateMetricTransparency API_AVAILABLE(macos(10.15.6), ios(13.6), watchos(6.2), tvos(13.4)); +extern const CFStringRef kSecPolicyAppleAggregateMetricEncryption + API_AVAILABLE(macos(11.1), ios(14.3), watchos(7.2), tvos(14.3)); /*! @@ -1852,6 +1854,28 @@ __nullable CF_RETURNS_RETAINED SecPolicyRef SecPolicyCreateAggregateMetricTransparency(bool facilitator) API_AVAILABLE(macos(10.15.6), ios(13.6), watchos(6.2), tvos(13.4)); +/*! + @function SecPolicyCreateAggregateMetricEncryption + @abstract Returns a policy object for verifying Aggregate Metric Encryption certificates + @param facilitator A boolean to indicate whether the facilitator or partner encryption + certificate is being checked. + @discussion The resulting policy uses the Basic X.509 policy with validity check and + pinning options: + * The chain is anchored to any of the Apple Root CAs. + * There are exactly 3 certs in the chain. + * The intermediate has a marker extension with OID 1.2.840.113635.100.6.2.26. + * The leaf has a marker extension with OID 1.2.840.113635.100.15.2 if facilitator is true or + 1.2.840.113635.100.15.3 if facilitator is false. + * Revocation is checked via any available method. + * RSA key sizes are 2048-bit or larger. EC key sizes are P-256 or larger. + * Require a positive CT verification result using the non-TLS CT log list + @result A policy object. The caller is responsible for calling CFRelease on this when + it is no longer needed. + */ +__nullable CF_RETURNS_RETAINED +SecPolicyRef SecPolicyCreateAggregateMetricEncryption(bool facilitator) + API_AVAILABLE(macos(11.1), ios(14.3), watchos(7.2), tvos(14.3)); + /* * Legacy functions (OS X only) */ @@ -1944,6 +1968,7 @@ extern const CFStringRef kSecPolicyCheckMissingIntermediate; extern const CFStringRef kSecPolicyCheckNameConstraints; extern const CFStringRef kSecPolicyCheckNoNetworkAccess; extern const CFStringRef kSecPolicyCheckNonEmptySubject; +extern const CFStringRef kSecPolicyCheckNonTlsCTRequired; extern const CFStringRef kSecPolicyCheckNotCA; extern const CFStringRef kSecPolicyCheckNotValidBefore; extern const CFStringRef kSecPolicyCheckPinningRequired; diff --git a/trust/trustd/CertificateTransparency.h b/trust/trustd/CertificateTransparency.h new file mode 100644 index 00000000..b51c32e6 --- /dev/null +++ b/trust/trustd/CertificateTransparency.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020 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@ + */ + +#ifndef _SECURITY_CERTIFICATE_TRANSPARENCY_H_ +#define _SECURITY_CERTIFICATE_TRANSPARENCY_H_ + +#include "trust/trustd/SecPolicyServer.h" + +void SecPolicyCheckCT(SecPVCRef pvc); +bool SecPolicyCheckNonTlsCT(SecPVCRef pvc); + +#endif /* _SECURITY_CERTIFICATE_TRANSPARENCY_H_ */ diff --git a/trust/trustd/CertificateTransparency.m b/trust/trustd/CertificateTransparency.m new file mode 100644 index 00000000..a10647e3 --- /dev/null +++ b/trust/trustd/CertificateTransparency.m @@ -0,0 +1,705 @@ +/* + * Copyright (c) 2020 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@ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "trust/trustd/SecTrustServer.h" +#include "trust/trustd/SecPolicyServer.h" +#include "trust/trustd/SecOCSPResponse.h" +#include "trust/trustd/OTATrustUtilities.h" +#include "trust/trustd/SecCertificateServer.h" +#include "trust/trustd/CertificateTransparency.h" + +const CFStringRef kSecCTRetirementDateKey = CFSTR("expiry"); // For backwards compatibility, retirement date is represented with the "expiry" key +const CFStringRef kSecCTReadOnlyDateKey = CFSTR("frozen"); // For backwards compatibility, read-only date is represented with the "frozen" key +const CFStringRef kSecCTShardStartDateKey = CFSTR("start_inclusive"); +const CFStringRef kSecCTShardEndDateKey = CFSTR("end_exclusive"); +const CFStringRef kSecCTPublicKeyKey = CFSTR("key"); + +enum { + kSecCTEntryTypeCert = 0, + kSecCTEntryTypePreCert = 1, +}; + +/*** + +struct { + Version sct_version; // 1 byte + LogID id; // 32 bytes + uint64 timestamp; // 8 bytes + CtExtensions extensions; // 2 bytes len field, + n bytes data + digitally-signed struct { // 1 byte hash alg, 1 byte sig alg, n bytes signature + Version sct_version; + SignatureType signature_type = certificate_timestamp; + uint64 timestamp; + LogEntryType entry_type; + select(entry_type) { + case x509_entry: ASN.1Cert; + case precert_entry: PreCert; + } signed_entry; + CtExtensions extensions; + }; +} SignedCertificateTimestamp; + +***/ + +static const +SecAsn1Oid *oidForSigAlg(SSL_HashAlgorithm hash, SSL_SignatureAlgorithm alg) +{ + switch(alg) { + case SSL_SignatureAlgorithmRSA: + switch (hash) { + case SSL_HashAlgorithmSHA1: + return &CSSMOID_SHA1WithRSA; + case SSL_HashAlgorithmSHA256: + return &CSSMOID_SHA256WithRSA; + case SSL_HashAlgorithmSHA384: + return &CSSMOID_SHA384WithRSA; + default: + break; + } + case SSL_SignatureAlgorithmECDSA: + switch (hash) { + case SSL_HashAlgorithmSHA1: + return &CSSMOID_ECDSA_WithSHA1; + case SSL_HashAlgorithmSHA256: + return &CSSMOID_ECDSA_WithSHA256; + case SSL_HashAlgorithmSHA384: + return &CSSMOID_ECDSA_WithSHA384; + default: + break; + } + default: + break; + } + + return NULL; +} + + +static size_t SSLDecodeUint16(const uint8_t *p) +{ + return (p[0]<<8 | p[1]); +} + +static uint8_t *SSLEncodeUint16(uint8_t *p, size_t len) +{ + p[0] = (len >> 8)&0xff; + p[1] = (len & 0xff); + return p+2; +} + +static uint8_t *SSLEncodeUint24(uint8_t *p, size_t len) +{ + p[0] = (len >> 16)&0xff; + p[1] = (len >> 8)&0xff; + p[2] = (len & 0xff); + return p+3; +} + + +static +uint64_t SSLDecodeUint64(const uint8_t *p) +{ + uint64_t u = 0; + for(int i=0; i<8; i++) { + u=(u<<8)|p[0]; + p++; + } + return u; +} + + +static CFDataRef copy_x509_entry_from_chain(SecPVCRef pvc) +{ + SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + + CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 3+SecCertificateGetLength(leafCert)); + + CFDataSetLength(data, 3+SecCertificateGetLength(leafCert)); + + uint8_t *q = CFDataGetMutableBytePtr(data); + q = SSLEncodeUint24(q, SecCertificateGetLength(leafCert)); + memcpy(q, SecCertificateGetBytePtr(leafCert), SecCertificateGetLength(leafCert)); + + return data; +} + + +static CFDataRef copy_precert_entry_from_chain(SecPVCRef pvc) +{ + SecCertificateRef leafCert = NULL; + SecCertificateRef issuer = NULL; + CFDataRef issuerKeyHash = NULL; + CFDataRef tbs_precert = NULL; + CFMutableDataRef data= NULL; + + require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts. + leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + issuer = SecPVCGetCertificateAtIndex(pvc, 1); + + require(leafCert, out); + require(issuer, out); // Those two would likely indicate an internal error, since we already checked the chain length above. + issuerKeyHash = SecCertificateCopySubjectPublicKeyInfoSHA256Digest(issuer); + tbs_precert = SecCertificateCopyPrecertTBS(leafCert); + + require(issuerKeyHash, out); + require(tbs_precert, out); + data = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert)); + CFDataSetLength(data, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert)); + + uint8_t *q = CFDataGetMutableBytePtr(data); + memcpy(q, CFDataGetBytePtr(issuerKeyHash), CFDataGetLength(issuerKeyHash)); q += CFDataGetLength(issuerKeyHash); // issuer key hash + q = SSLEncodeUint24(q, CFDataGetLength(tbs_precert)); + memcpy(q, CFDataGetBytePtr(tbs_precert), CFDataGetLength(tbs_precert)); + +out: + CFReleaseSafe(issuerKeyHash); + CFReleaseSafe(tbs_precert); + return data; +} + +static +CFAbsoluteTime TimestampToCFAbsoluteTime(uint64_t ts) +{ + return (ts / 1000) - kCFAbsoluteTimeIntervalSince1970; +} + +static +uint64_t TimestampFromCFAbsoluteTime(CFAbsoluteTime at) +{ + return (uint64_t)(at + kCFAbsoluteTimeIntervalSince1970) * 1000; +} + +static bool isSCTValidForLogData(CFDictionaryRef logData, int entry_type, CFAbsoluteTime sct_time, CFAbsoluteTime cert_expiry_date) { + /* only embedded SCTs can be used from retired logs. */ + if(entry_type==kSecCTEntryTypeCert && CFDictionaryContainsKey(logData, kSecCTRetirementDateKey)) { + return false; + } + + /* SCTs from after the transition to read-only are not valid (and indicate a operator failure) */ + CFDateRef frozen_date = CFDictionaryGetValue(logData, kSecCTReadOnlyDateKey); + if (frozen_date && (sct_time > CFDateGetAbsoluteTime(frozen_date))) { + secerror("Frozen CT log issued SCT after freezing (log=%@)\n", logData); + return false; + } + + /* If the log is temporally sharded, the certificate expiry date must be within the temporal shard window */ + CFDateRef start_inclusive = CFDictionaryGetValue(logData, kSecCTShardStartDateKey); + CFDateRef end_exclusive = CFDictionaryGetValue(logData, kSecCTShardEndDateKey); + if (start_inclusive && (cert_expiry_date < CFDateGetAbsoluteTime(start_inclusive))) { + return false; + } + if (end_exclusive && (cert_expiry_date >= CFDateGetAbsoluteTime(end_exclusive))) { + return false; + } + + return true; +} + + +/* + If the 'sct' is valid, add it to the validatingLogs dictionary. + + Inputs: + - validatingLogs: mutable dictionary to which to add the log that validate this SCT. + - sct: the SCT date + - entry_type: 0 for x509 cert, 1 for precert. + - entry: the cert or precert data. + - vt: verification time timestamp (as used in SCTs: ms since 1970 Epoch) + - trustedLog: Dictionary contain the Trusted Logs. + + The SCT is valid if: + - It decodes properly. + - Its timestamp is less than 'verifyTime'. + - It is signed by a log in 'trustedLogs'. + - If entry_type = 0, the log must be currently qualified. + - If entry_type = 1, the log may be expired. + + If the SCT is valid, it's added to the validatinLogs dictionary using the log dictionary as the key, and the timestamp as value. + If an entry for the same log already existing in the dictionary, the entry is replaced only if the timestamp of this SCT is earlier. + + */ +static CFDictionaryRef getSCTValidatingLog(CFDataRef sct, int entry_type, CFDataRef entry, uint64_t vt, CFAbsoluteTime cert_expiry_date, CFDictionaryRef trustedLogs, CFAbsoluteTime *sct_at) +{ + uint8_t version; + const uint8_t *logID; + const uint8_t *timestampData; + uint64_t timestamp; + size_t extensionsLen; + const uint8_t *extensionsData; + uint8_t hashAlg; + uint8_t sigAlg; + size_t signatureLen; + const uint8_t *signatureData; + SecKeyRef pubKey = NULL; + uint8_t *signed_data = NULL; + const SecAsn1Oid *oid = NULL; + SecAsn1AlgId algId; + CFDataRef logIDData = NULL; + CFDictionaryRef result = 0; + + const uint8_t *p = CFDataGetBytePtr(sct); + size_t len = CFDataGetLength(sct); + + require(len>=43, out); + + version = p[0]; p++; len--; + logID = p; p+=32; len-=32; + timestampData = p; p+=8; len-=8; + extensionsLen = SSLDecodeUint16(p); p+=2; len-=2; + + require(len>=extensionsLen, out); + extensionsData = p; p+=extensionsLen; len-=extensionsLen; + + require(len>=4, out); + hashAlg=p[0]; p++; len--; + sigAlg=p[0]; p++; len--; + signatureLen = SSLDecodeUint16(p); p+=2; len-=2; + require(len==signatureLen, out); /* We do not tolerate any extra data after the signature */ + signatureData = p; + + /* verify version: only v1(0) is supported */ + if(version!=0) { + secerror("SCT version unsupported: %d\n", version); + goto out; + } + + /* verify timestamp not in the future */ + timestamp = SSLDecodeUint64(timestampData); + if(timestamp > vt) { + secerror("SCT is in the future: %llu > %llu\n", timestamp, vt); + goto out; + } + + uint8_t *q; + + /* signed entry */ + size_t signed_data_len = 12 + CFDataGetLength(entry) + 2 + extensionsLen ; + signed_data = malloc(signed_data_len); + require(signed_data, out); + q = signed_data; + *q++ = version; + *q++ = 0; // certificate_timestamp + memcpy(q, timestampData, 8); q+=8; + q = SSLEncodeUint16(q, entry_type); // logentry type: 0=cert 1=precert + memcpy(q, CFDataGetBytePtr(entry), CFDataGetLength(entry)); q += CFDataGetLength(entry); + q = SSLEncodeUint16(q, extensionsLen); + memcpy(q, extensionsData, extensionsLen); + + logIDData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, logID, 32, kCFAllocatorNull); + + CFDictionaryRef logData = CFDictionaryGetValue(trustedLogs, logIDData); + CFAbsoluteTime sct_time = TimestampToCFAbsoluteTime(timestamp); + require(logData && isSCTValidForLogData(logData, entry_type, sct_time, cert_expiry_date), out); + + CFDataRef logKeyData = CFDictionaryGetValue(logData, kSecCTPublicKeyKey); + require(logKeyData, out); // This failing would be an internal logic error + pubKey = SecKeyCreateFromSubjectPublicKeyInfoData(kCFAllocatorDefault, logKeyData); + require(pubKey, out); + + oid = oidForSigAlg(hashAlg, sigAlg); + require(oid, out); + + algId.algorithm = *oid; + algId.parameters.Data = NULL; + algId.parameters.Length = 0; + + if(SecKeyDigestAndVerify(pubKey, &algId, signed_data, signed_data_len, signatureData, signatureLen)==0) { + *sct_at = sct_time; + result = logData; + } else { + secerror("SCT signature failed (log=%@)\n", logData); + } + +out: + CFReleaseSafe(logIDData); + CFReleaseSafe(pubKey); + free(signed_data); + return result; +} + + +static void addValidatingLog(CFMutableDictionaryRef validatingLogs, CFDictionaryRef log, CFAbsoluteTime sct_at) +{ + CFDateRef validated_time = CFDictionaryGetValue(validatingLogs, log); + + if(validated_time==NULL || (sct_at < CFDateGetAbsoluteTime(validated_time))) { + CFDateRef sct_time = CFDateCreate(kCFAllocatorDefault, sct_at); + CFDictionarySetValue(validatingLogs, log, sct_time); + CFReleaseSafe(sct_time); + } +} + +static CFArrayRef copy_ocsp_scts(SecPVCRef pvc) +{ + CFMutableArrayRef SCTs = NULL; + SecCertificateRef leafCert = NULL; + SecCertificateRef issuer = NULL; + CFArrayRef ocspResponsesData = NULL; + SecOCSPRequestRef ocspRequest = NULL; + + ocspResponsesData = SecPathBuilderCopyOCSPResponses(pvc->builder); + require_quiet(ocspResponsesData, out); + + require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts. + leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + issuer = SecPVCGetCertificateAtIndex(pvc, 1); + + require(leafCert, out); + require(issuer, out); // not quiet: Those two would likely indicate an internal error, since we already checked the chain length above. + ocspRequest = SecOCSPRequestCreate(leafCert, issuer); + + SCTs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); + require(SCTs, out); + + CFArrayForEach(ocspResponsesData, ^(const void *value) { + /* TODO: Should the builder already have the appropriate SecOCSPResponseRef ? */ + SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value); + if(ocspResponse && SecOCSPGetResponseStatus(ocspResponse)==kSecOCSPSuccess) { + SecOCSPSingleResponseRef ocspSingleResponse = SecOCSPResponseCopySingleResponse(ocspResponse, ocspRequest); + if(ocspSingleResponse) { + CFArrayRef singleResponseSCTs = SecOCSPSingleResponseCopySCTs(ocspSingleResponse); + if(singleResponseSCTs) { + CFArrayAppendArray(SCTs, singleResponseSCTs, CFRangeMake(0, CFArrayGetCount(singleResponseSCTs))); + CFRelease(singleResponseSCTs); + } + SecOCSPSingleResponseDestroy(ocspSingleResponse); + } + } + if(ocspResponse) SecOCSPResponseFinalize(ocspResponse); + }); + + if(CFArrayGetCount(SCTs)==0) { + CFReleaseNull(SCTs); + } + +out: + CFReleaseSafe(ocspResponsesData); + if(ocspRequest) + SecOCSPRequestFinalize(ocspRequest); + + return SCTs; +} + +static bool find_validating_logs(SecPVCRef pvc, CFDictionaryRef trustedLogs, + CFDictionaryRef CF_RETURNS_RETAINED * _Nonnull outCurrentLogsValidatingScts, + CFDictionaryRef CF_RETURNS_RETAINED * _Nonnull outLogsValidatingEmbeddedScts, + bool * _Nonnull out_at_least_one_currently_valid_external, + bool * _Nonnull out_at_least_one_currently_valid_embedded) { + SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + CFArrayRef embeddedScts = SecCertificateCopySignedCertificateTimestamps(leafCert); + CFArrayRef builderScts = SecPathBuilderCopySignedCertificateTimestamps(pvc->builder); + CFArrayRef ocspScts = copy_ocsp_scts(pvc); + CFDataRef precertEntry = copy_precert_entry_from_chain(pvc); + CFDataRef x509Entry = copy_x509_entry_from_chain(pvc); + __block CFAbsoluteTime certExpiry = SecCertificateNotValidAfter(leafCert); + bool result = NO; + + // This eventually contain list of logs who validated the SCT. + CFMutableDictionaryRef currentLogsValidatingScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + CFMutableDictionaryRef logsValidatingEmbeddedScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + + uint64_t vt = TimestampFromCFAbsoluteTime(SecPVCGetVerifyTime(pvc)); + + __block bool at_least_one_currently_valid_external = 0; + __block bool at_least_one_currently_valid_embedded = 0; + + require(logsValidatingEmbeddedScts, out); + require(currentLogsValidatingScts, out); + + /* Skip if there are no SCTs. */ + bool no_scts = (embeddedScts && CFArrayGetCount(embeddedScts) > 0) || + (builderScts && CFArrayGetCount(builderScts) > 0) || + (ocspScts && CFArrayGetCount(ocspScts) > 0); + require_quiet(no_scts, out); + + if(trustedLogs && CFDictionaryGetCount(trustedLogs) > 0) { // Don't bother trying to validate SCTs if we don't have any trusted logs. + if(embeddedScts && precertEntry) { // Don't bother if we could not get the precert. + CFArrayForEach(embeddedScts, ^(const void *value){ + CFAbsoluteTime sct_at; + CFDictionaryRef log = getSCTValidatingLog(value, 1, precertEntry, vt, certExpiry, trustedLogs, &sct_at); + if(log) { + addValidatingLog(logsValidatingEmbeddedScts, log, sct_at); + if(!CFDictionaryContainsKey(log, kSecCTRetirementDateKey)) { + addValidatingLog(currentLogsValidatingScts, log, sct_at); + at_least_one_currently_valid_embedded = true; + } + } + }); + } + + if(builderScts && x509Entry) { // Don't bother if we could not get the cert. + CFArrayForEach(builderScts, ^(const void *value){ + CFAbsoluteTime sct_at; + CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, certExpiry, trustedLogs, &sct_at); + if(log) { + addValidatingLog(currentLogsValidatingScts, log, sct_at); + at_least_one_currently_valid_external = true; + } + }); + } + + if(ocspScts && x509Entry) { + CFArrayForEach(ocspScts, ^(const void *value){ + CFAbsoluteTime sct_at; + CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, certExpiry, trustedLogs, &sct_at); + if(log) { + addValidatingLog(currentLogsValidatingScts, log, sct_at); + at_least_one_currently_valid_external = true; + } + }); + } + } + + if (CFDictionaryGetCount(currentLogsValidatingScts) > 0) { + result = true; + *outCurrentLogsValidatingScts = CFRetainSafe(currentLogsValidatingScts); + *outLogsValidatingEmbeddedScts = CFRetainSafe(logsValidatingEmbeddedScts); + *out_at_least_one_currently_valid_embedded = at_least_one_currently_valid_embedded; + *out_at_least_one_currently_valid_external = at_least_one_currently_valid_external; + } + +out: + CFReleaseSafe(logsValidatingEmbeddedScts); + CFReleaseSafe(currentLogsValidatingScts); + CFReleaseSafe(builderScts); + CFReleaseSafe(embeddedScts); + CFReleaseSafe(ocspScts); + CFReleaseSafe(precertEntry); + CFReleaseSafe(x509Entry); + return result; +} + +static bool verify_tls_ct_policy(SecPVCRef pvc, CFDictionaryRef currentLogsValidatingScts, CFDictionaryRef logsValidatingEmbeddedScts, + bool at_least_one_currently_valid_external, bool at_least_one_currently_valid_embedded, + CFIndex * _Nonnull outTrustedSCTCount) +{ + if (!logsValidatingEmbeddedScts || !currentLogsValidatingScts) { + return false; + } + + __block CFAbsoluteTime issuanceTime = SecPVCGetVerifyTime(pvc); + SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + __block CFIndex trustedSCTCount = CFDictionaryGetCount(currentLogsValidatingScts); + + /* We now have 2 sets of logs that validated those SCTS, count them and make a final decision. + + Current Policy: + is_ct = (A1 AND A2) OR (B1 AND B2). + + A1: embedded SCTs from 2+ to 5+ logs valid at issuance time + A2: At least one embedded SCT from a currently valid log. + + B1: SCTs from 2 currently valid logs (from any source) + B2: At least 1 external SCT from a currently valid log. + + */ + + bool hasValidExternalSCT = (at_least_one_currently_valid_external && CFDictionaryGetCount(currentLogsValidatingScts)>=2); + bool hasValidEmbeddedSCT = (at_least_one_currently_valid_embedded); + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + bool result = false; + + if (hasValidEmbeddedSCT) { + /* Calculate issuance time based on timestamp of SCTs from current logs */ + CFDictionaryForEach(currentLogsValidatingScts, ^(const void *key, const void *value) { + CFDictionaryRef log = key; + if(!CFDictionaryContainsKey(log, kSecCTRetirementDateKey)) { + // Log is still qualified + CFDateRef ts = (CFDateRef) value; + CFAbsoluteTime timestamp = CFDateGetAbsoluteTime(ts); + if(timestamp < issuanceTime) { + issuanceTime = timestamp; + } + } + }); + SecCertificatePathVCSetIssuanceTime(path, issuanceTime); + } + if (hasValidExternalSCT) { + /* Note: since external SCT validates this cert, we do not need to + override issuance time here. If the cert also has a valid embedded + SCT, issuanceTime will be calculated and set in the block above. */ + result = true; + } else if (hasValidEmbeddedSCT) { + __block int lifetime; // in Months + __block unsigned once_or_current_qualified_embedded = 0; + + /* Count Logs */ + __block bool failed_once_check = false; + CFDictionaryForEach(logsValidatingEmbeddedScts, ^(const void *key, const void *value) { + CFDictionaryRef log = key; + CFDateRef ts = value; + CFDateRef expiry = CFDictionaryGetValue(log, kSecCTRetirementDateKey); + if (expiry == NULL) { // Currently qualified OR + once_or_current_qualified_embedded++; + } else if (CFDateCompare(ts, expiry, NULL) == kCFCompareLessThan && // Once qualified. That is, qualified at the time of SCT AND + issuanceTime < CFDateGetAbsoluteTime(expiry)) { // at the time of issuance.) + once_or_current_qualified_embedded++; + trustedSCTCount++; + } else { + failed_once_check = true; + } + }); + + SecCFCalendarDoWithZuluCalendar(^(CFCalendarRef zuluCalendar) { + int _lifetime; + CFCalendarGetComponentDifference(zuluCalendar, + SecCertificateNotValidBefore(leafCert), + SecCertificateNotValidAfter(leafCert), + 0, "M", &_lifetime); + lifetime = _lifetime; + }); + + unsigned requiredEmbeddedSctsCount; + + if (lifetime < 15) { + requiredEmbeddedSctsCount = 2; + } else if (lifetime <= 27) { + requiredEmbeddedSctsCount = 3; + } else if (lifetime <= 39) { + requiredEmbeddedSctsCount = 4; + } else { + requiredEmbeddedSctsCount = 5; + } + + if(once_or_current_qualified_embedded >= requiredEmbeddedSctsCount){ + result = true; + } + } + + *outTrustedSCTCount = trustedSCTCount; + return result; +} + +static void report_ct_analytics(SecPVCRef pvc, CFDictionaryRef currentLogsValidatingScts, CFIndex trustedSCTCount) +{ + /* Record analytics data for CT */ + TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(pvc->builder); + if (!analytics) { + return; + } + + SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); + CFArrayRef embeddedScts = SecCertificateCopySignedCertificateTimestamps(leafCert); + CFArrayRef builderScts = SecPathBuilderCopySignedCertificateTimestamps(pvc->builder); + CFArrayRef ocspScts = copy_ocsp_scts(pvc); + + uint32_t sctCount = 0; + /* Count the total number of SCTs we found and report where we got them */ + if (embeddedScts && CFArrayGetCount(embeddedScts) > 0) { + analytics->sct_sources |= TA_SCTEmbedded; + sctCount += CFArrayGetCount(embeddedScts); + } + if (builderScts && CFArrayGetCount(builderScts) > 0) { + analytics->sct_sources |= TA_SCT_TLS; + sctCount += CFArrayGetCount(builderScts); + } + if (ocspScts && CFArrayGetCount(ocspScts) > 0) { + analytics->sct_sources |= TA_SCT_OCSP; + sctCount += CFArrayGetCount(ocspScts); + } + /* Report how many of those SCTs were once or currently qualified */ + analytics->number_trusted_scts = (uint32_t)trustedSCTCount; + /* Report how many SCTs we got */ + analytics->number_scts = sctCount; + /* Only one current SCT -- close to failure */ + if (CFDictionaryGetCount(currentLogsValidatingScts) == 1) { + analytics->ct_one_current = true; + } + + CFReleaseNull(embeddedScts); + CFReleaseNull(builderScts); + CFReleaseNull(ocspScts); +} + +void SecPolicyCheckCT(SecPVCRef pvc) +{ + CFDictionaryRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder); + if (!trustedLogs) { + SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef(); + trustedLogs = SecOTAPKICopyTrustedCTLogs(otapkiref); + CFReleaseSafe(otapkiref); + } + + CFDictionaryRef currentLogsValidatingScts = NULL; + CFDictionaryRef logsValidatingEmbeddedScts = NULL; + bool at_least_one_currently_valid_external = false; + bool at_least_one_currently_valid_embedded = false; + CFIndex trustedSCTCount = 0; + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + + SecCertificatePathVCSetIsCT(path, false); + if (find_validating_logs(pvc, trustedLogs, ¤tLogsValidatingScts, &logsValidatingEmbeddedScts, &at_least_one_currently_valid_external, &at_least_one_currently_valid_embedded)) { + if (verify_tls_ct_policy(pvc, currentLogsValidatingScts, logsValidatingEmbeddedScts, at_least_one_currently_valid_external, at_least_one_currently_valid_embedded, &trustedSCTCount)) { + SecCertificatePathVCSetIsCT(path, true); + } + report_ct_analytics(pvc, currentLogsValidatingScts, trustedSCTCount); + } + + CFReleaseNull(currentLogsValidatingScts); + CFReleaseNull(logsValidatingEmbeddedScts); + CFReleaseNull(trustedLogs); +} + +bool SecPolicyCheckNonTlsCT(SecPVCRef pvc) +{ + CFDictionaryRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder); + if (!trustedLogs) { + SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef(); + trustedLogs = SecOTAPKICopyNonTlsTrustedCTLogs(otapkiref); + CFReleaseSafe(otapkiref); + } + + CFDictionaryRef currentLogsValidatingScts = NULL; + CFDictionaryRef logsValidatingEmbeddedScts = NULL; + bool at_least_one_currently_valid_external = false; + bool at_least_one_currently_valid_embedded = false; + CFIndex trustedSCTCount = 0; + SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); + bool result = false; + + SecCertificatePathVCSetIsCT(path, false); + if (find_validating_logs(pvc, trustedLogs, ¤tLogsValidatingScts, &logsValidatingEmbeddedScts, &at_least_one_currently_valid_external, &at_least_one_currently_valid_embedded)) { + if (verify_tls_ct_policy(pvc, currentLogsValidatingScts, logsValidatingEmbeddedScts, at_least_one_currently_valid_external, at_least_one_currently_valid_embedded, &trustedSCTCount)) { + result = true; + } + report_ct_analytics(pvc, currentLogsValidatingScts, trustedSCTCount); + } + + CFReleaseNull(currentLogsValidatingScts); + CFReleaseNull(logsValidatingEmbeddedScts); + CFReleaseNull(trustedLogs); + return result; +} diff --git a/trust/trustd/OTATrustUtilities.h b/trust/trustd/OTATrustUtilities.h index 4b770f9d..5d3d1bb7 100644 --- a/trust/trustd/OTATrustUtilities.h +++ b/trust/trustd/OTATrustUtilities.h @@ -154,16 +154,21 @@ NSNumber *SecOTAPKIGetSamplingRateForEvent(SecOTAPKIRef otapkiRef, NSString *eve CFArrayRef SecOTAPKICopyAppleCertificateAuthorities(SecOTAPKIRef otapkiRef); extern const CFStringRef kOTAPKIKillSwitchCT; +extern const CFStringRef kOTAPKIKillSwitchNonTLSCT; bool SecOTAPKIKillSwitchEnabled(SecOTAPKIRef otapkiRef, CFStringRef switchKey); // SPI to return the array of currently trusted Escrow certificates CF_EXPORT CFArrayRef SecOTAPKICopyCurrentEscrowCertificates(uint32_t escrowRootType, CFErrorRef* error); -// SPI to return the array of currently trusted CT logs +// SPI to return the array of currently (TLS) trusted CT logs CF_EXPORT CFDictionaryRef SecOTAPKICopyCurrentTrustedCTLogs(CFErrorRef* error); +// SPI to return the array of currently non-TLS trusted CT logs +CF_EXPORT +CFDictionaryRef SecOTAPKICopyNonTlsTrustedCTLogs(SecOTAPKIRef otapkiRef); + // SPI to return dictionary of CT log matching specified key id */ CF_EXPORT CFDictionaryRef SecOTAPKICopyCTLogForKeyID(CFDataRef keyID, CFErrorRef* error); diff --git a/trust/trustd/OTATrustUtilities.m b/trust/trustd/OTATrustUtilities.m index 4ac7be77..84d1344e 100644 --- a/trust/trustd/OTATrustUtilities.m +++ b/trust/trustd/OTATrustUtilities.m @@ -206,11 +206,16 @@ NSString *kOTATrustContentVersionKey = @"MobileAssetContentVersion"; NSString *kOTATrustLastCheckInKey = @"MobileAssetLastCheckIn"; NSString *kOTATrustContextFilename = @"OTAPKIContext.plist"; NSString *kOTATrustTrustedCTLogsFilename = @"TrustedCTLogs.plist"; +NSString *kOTATrustTrustedCTLogsNonTLSFilename = @"TrustedCTLogs_nonTLS.plist"; NSString *kOTATrustAnalyticsSamplingRatesFilename = @"AnalyticsSamplingRates.plist"; NSString *kOTATrustAppleCertifcateAuthoritiesFilename = @"AppleCertificateAuthorities.plist"; NSString *kOTASecExperimentConfigFilename = @"SecExperimentAssets.plist"; +/* A device will honor a kill switch until it gets a new asset xml that sets the kill switch value to 0/false + * OR the asset is (completely) reset to shipping version. Such resets can happen if asset files cannot be + * read properly or if the OS is updated and contains a newer asset version or pinning DB version. */ const CFStringRef kOTAPKIKillSwitchCT = CFSTR("CTKillSwitch"); +const CFStringRef kOTAPKIKillSwitchNonTLSCT = CFSTR("CTKillSwitch_nonTLS"); #if !TARGET_OS_BRIDGE NSString *OTATrustMobileAssetType = @"com.apple.MobileAsset.PKITrustSupplementals"; @@ -446,6 +451,7 @@ static void DeleteOldAssetData(void) { if (SecOTAPKIIsSystemTrustd()) { /* Delete the asset files, but keep the check-in time and version */ DeleteFileWithName(kOTATrustTrustedCTLogsFilename); + DeleteFileWithName(kOTATrustTrustedCTLogsNonTLSFilename); DeleteFileWithName(kOTATrustAnalyticsSamplingRatesFilename); DeleteFileWithName(kOTATrustAppleCertifcateAuthoritiesFilename); } @@ -506,6 +512,11 @@ static BOOL CopyFileToDisk(NSString *filename, NSURL *localURL, NSError **error) return NO; } +static void DisableKillSwitches() { + UpdateOTAContextOnDisk((__bridge NSString*)kOTAPKIKillSwitchCT, @0, nil); + UpdateOTAContextOnDisk((__bridge NSString*)kOTAPKIKillSwitchNonTLSCT, @0, nil); +} + static void GetKillSwitchAttributes(NSDictionary *attributes) { bool killSwitchEnabled = false; @@ -519,6 +530,16 @@ static void GetKillSwitchAttributes(NSDictionary *attributes) { killSwitchEnabled = true; } + // Non-TLS CT Kill Switch + ctKillSwitch = [attributes objectForKey:(__bridge NSString*)kOTAPKIKillSwitchNonTLSCT]; + if (isNSNumber(ctKillSwitch)) { + NSError *error = nil; + UpdateOTAContextOnDisk((__bridge NSString*)kOTAPKIKillSwitchNonTLSCT, ctKillSwitch, &error); + UpdateKillSwitch((__bridge NSString*)kOTAPKIKillSwitchNonTLSCT, [ctKillSwitch boolValue]); + secnotice("OTATrust", "got non-TLS CT kill switch = %d", [ctKillSwitch boolValue]); + killSwitchEnabled = true; + } + /* Other kill switches TBD. * When adding one, make sure to add to the Analytics Samplers since these kill switches * are installed before the full asset is downloaded and installed. (A device can have the @@ -846,6 +867,7 @@ static void InitializeOTATrustAsset(dispatch_queue_t queue) { int out_token3 = 0; notify_register_dispatch(kOTATrustKillSwitchNotification, &out_token3, queue, ^(int __unused token) { UpdateKillSwitch((__bridge NSString*)kOTAPKIKillSwitchCT, InitializeKillSwitch((__bridge NSString*)kOTAPKIKillSwitchCT)); + UpdateKillSwitch((__bridge NSString*)kOTAPKIKillSwitchNonTLSCT, InitializeKillSwitch((__bridge NSString*)kOTAPKIKillSwitchNonTLSCT)); }); } } @@ -1054,13 +1076,13 @@ CFDictionaryRef SecOTAPKICreateTrustedCTLogsDictionaryFromArray(CFArrayRef trust } } -static CF_RETURNS_RETAINED CFDictionaryRef InitializeTrustedCTLogs() { +static CF_RETURNS_RETAINED CFDictionaryRef InitializeTrustedCTLogs(NSString *filename) { @autoreleasepool { NSArray *trustedCTLogs = nil; NSError *error = nil; #if !TARGET_OS_BRIDGE if (ShouldInitializeWithAsset()) { - trustedCTLogs = [NSArray arrayWithContentsOfURL:GetAssetFileURL(kOTATrustTrustedCTLogsFilename) error:&error]; + trustedCTLogs = [NSArray arrayWithContentsOfURL:GetAssetFileURL(filename) error:&error]; if (!isNSArray(trustedCTLogs)) { secerror("OTATrust: failed to read CT list from asset data: %@", error); LogRemotely(OTATrustLogLevelError, &error); @@ -1071,7 +1093,7 @@ static CF_RETURNS_RETAINED CFDictionaryRef InitializeTrustedCTLogs() { } #endif if (!isNSArray(trustedCTLogs)) { - trustedCTLogs = [NSArray arrayWithContentsOfURL:SecSystemTrustStoreCopyResourceNSURL(kOTATrustTrustedCTLogsFilename)]; + trustedCTLogs = [NSArray arrayWithContentsOfURL:SecSystemTrustStoreCopyResourceNSURL(filename)]; } if (isNSArray(trustedCTLogs)) { return CFBridgingRetain(ConvertTrustedCTLogsArrayToDictionary(trustedCTLogs)); @@ -1456,6 +1478,7 @@ struct _OpaqueSecOTAPKI { CFSetRef _grayListSet; CFDictionaryRef _allowList; CFDictionaryRef _trustedCTLogs; + CFDictionaryRef _nonTlsTrustedCTLogs; CFURLRef _pinningList; CFArrayRef _escrowCertificates; CFArrayRef _escrowPCSCertificates; @@ -1473,6 +1496,7 @@ struct _OpaqueSecOTAPKI { CFDictionaryRef _secExperimentConfig; uint64_t _secExperimentAssetVersion; bool _ctKillSwitch; + bool _nonTlsCtKillSwitch; }; CFGiblisFor(SecOTAPKI) @@ -1495,6 +1519,7 @@ static void SecOTAPKIDestroy(CFTypeRef cf) { CFReleaseNull(otapkiref->_anchorLookupTable); CFReleaseNull(otapkiref->_trustedCTLogs); + CFReleaseNull(otapkiref->_nonTlsTrustedCTLogs); CFReleaseNull(otapkiref->_pinningList); CFReleaseNull(otapkiref->_eventSamplingRates); CFReleaseNull(otapkiref->_appleCAs); @@ -1547,6 +1572,7 @@ static uint64_t GetAssetVersion(CFErrorRef *error) { *error = CFRetainSafe((__bridge CFErrorRef)nserror); } DeleteOldAssetData(); + DisableKillSwitches(); } #endif return version; @@ -1610,7 +1636,8 @@ static SecOTAPKIRef SecOTACreate() { // (now loaded lazily in SecOTAPKICopyAllowList) // Get the trusted Certificate Transparency Logs - otapkiref->_trustedCTLogs = InitializeTrustedCTLogs(); + otapkiref->_trustedCTLogs = InitializeTrustedCTLogs(kOTATrustTrustedCTLogsFilename); + otapkiref->_nonTlsTrustedCTLogs = InitializeTrustedCTLogs(kOTATrustTrustedCTLogsNonTLSFilename); // Get the pinning list otapkiref->_pinningList = InitializePinningList(); @@ -1677,9 +1704,11 @@ static SecOTAPKIRef SecOTACreate() { /* Initialize our update handling */ InitializeOTATrustAsset(kOTABackgroundQueue); otapkiref->_ctKillSwitch = InitializeKillSwitch((__bridge NSString*)kOTAPKIKillSwitchCT); + otapkiref->_nonTlsCtKillSwitch = InitializeKillSwitch((__bridge NSString*)kOTAPKIKillSwitchNonTLSCT); InitializeOTASecExperimentAsset(kOTABackgroundQueue); #else // TARGET_OS_BRIDGE otapkiref->_ctKillSwitch = true; // bridgeOS never enforces CT + otapkiref->_nonTlsCtKillSwitch = true; #endif // TARGET_OS_BRIDGE return otapkiref; @@ -1738,6 +1767,8 @@ void UpdateKillSwitch(NSString *key, bool value) { dispatch_sync(kOTAQueue, ^{ if ([key isEqualToString:(__bridge NSString*)kOTAPKIKillSwitchCT]) { kCurrentOTAPKIRef->_ctKillSwitch = value; + } else if ([key isEqualToString:(__bridge NSString*)kOTAPKIKillSwitchNonTLSCT]) { + kCurrentOTAPKIRef->_nonTlsCtKillSwitch = value; } }); } @@ -1749,6 +1780,7 @@ static BOOL UpdateFromAsset(NSURL *localURL, NSNumber *asset_version, NSError ** return NO; } __block NSArray *newTrustedCTLogs = NULL; + __block NSArray *newNonTlsTrustedCTLogs = NULL; __block uint64_t version = [asset_version unsignedLongLongValue]; __block NSDictionary *newAnalyticsSamplingRates = NULL; __block NSArray *newAppleCAs = NULL; @@ -1762,6 +1794,15 @@ static BOOL UpdateFromAsset(NSURL *localURL, NSNumber *asset_version, NSError ** return NO; } + NSURL *nonTLSTrustedCTLogsFileLoc = [NSURL URLWithString:kOTATrustTrustedCTLogsNonTLSFilename + relativeToURL:localURL]; + newNonTlsTrustedCTLogs = [NSArray arrayWithContentsOfURL:nonTLSTrustedCTLogsFileLoc error:error]; + if (!newNonTlsTrustedCTLogs) { + secerror("OTATrust: unable to create TrustedCTLogs_nonTLS from asset file: %@", error ? *error: nil); + LogRemotely(OTATrustLogLevelError, error); + return NO; + } + NSURL *AnalyticsSamplingRatesFileLoc = [NSURL URLWithString:kOTATrustAnalyticsSamplingRatesFilename relativeToURL:localURL]; newAnalyticsSamplingRates = [NSDictionary dictionaryWithContentsOfURL:AnalyticsSamplingRatesFileLoc error:error]; @@ -1784,14 +1825,21 @@ static BOOL UpdateFromAsset(NSURL *localURL, NSNumber *asset_version, NSError ** dispatch_sync(kOTAQueue, ^{ secnotice("OTATrust", "updating asset version from %llu to %llu", kCurrentOTAPKIRef->_assetVersion, version); CFRetainAssign(kCurrentOTAPKIRef->_trustedCTLogs, (__bridge CFDictionaryRef)ConvertTrustedCTLogsArrayToDictionary(newTrustedCTLogs)); + CFRetainAssign(kCurrentOTAPKIRef->_nonTlsTrustedCTLogs, (__bridge CFDictionaryRef)ConvertTrustedCTLogsArrayToDictionary(newNonTlsTrustedCTLogs)); CFRetainAssign(kCurrentOTAPKIRef->_eventSamplingRates, (__bridge CFDictionaryRef)newAnalyticsSamplingRates); CFRetainAssign(kCurrentOTAPKIRef->_appleCAs, (__bridge CFArrayRef)newAppleCAs); kCurrentOTAPKIRef->_assetVersion = version; }); - /* Write the data to disk (so that we don't have to re-download the asset on re-launch) */ - DeleteAssetFromDisk(); + /* Reset the current files, version, and checkin so that in the case of write failures, we'll re-try + * to update the data. We don't call DeleteAssetFromDisk() here to preserve any kill switches. */ + DeleteOldAssetData(); + UpdateOTAContext(@(0), nil); + UpdateOTAContextOnDisk(kOTATrustLastCheckInKey, [NSDate dateWithTimeIntervalSince1970:0], nil); + + /* Write the data to disk (so that we don't have to re-download the asset on re-launch). */ if (CopyFileToDisk(kOTATrustTrustedCTLogsFilename, TrustedCTLogsFileLoc, error) && + CopyFileToDisk(kOTATrustTrustedCTLogsNonTLSFilename, nonTLSTrustedCTLogsFileLoc, error) && CopyFileToDisk(kOTATrustAnalyticsSamplingRatesFilename, AnalyticsSamplingRatesFileLoc, error) && CopyFileToDisk(kOTATrustAppleCertifcateAuthoritiesFilename, AppleCAsFileLoc, error) && UpdateOTAContext(asset_version, error)) { // Set version and check-in time last (after success) @@ -1916,6 +1964,23 @@ CFDictionaryRef SecOTAPKICopyTrustedCTLogs(SecOTAPKIRef otapkiRef) { return result; } +CFDictionaryRef SecOTAPKICopyNonTlsTrustedCTLogs(SecOTAPKIRef otapkiRef) { + CFDictionaryRef result = NULL; + if (NULL == otapkiRef) { + return result; + } + +#if !TARGET_OS_BRIDGE + /* Trigger periodic background MA checks in system trustd + * We also check on trustd launch and listen for notifications. */ + TriggerPeriodicOTATrustAssetChecks(kOTABackgroundQueue); +#endif + + result = otapkiRef->_nonTlsTrustedCTLogs; + CFRetainSafe(result); + return result; +} + CFURLRef SecOTAPKICopyPinningList(SecOTAPKIRef otapkiRef) { if (NULL == otapkiRef) { return NULL; @@ -2107,6 +2172,8 @@ bool SecOTAPKIKillSwitchEnabled(SecOTAPKIRef otapkiRef, CFStringRef key) { } if (CFEqualSafe(key, kOTAPKIKillSwitchCT)) { return otapkiRef->_ctKillSwitch; + } else if (CFEqualSafe(key, kOTAPKIKillSwitchNonTLSCT)) { + return otapkiRef->_nonTlsCtKillSwitch; } return false; } @@ -2166,14 +2233,24 @@ CFDictionaryRef SecOTAPKICopyCTLogForKeyID(CFDataRef keyID, CFErrorRef* error) { return NULL; } - CFDictionaryRef trustedLogs = SecOTAPKICopyTrustedCTLogs(otapkiref); + /* Get the log lists */ + CFDictionaryRef trustedTlsLogs = SecOTAPKICopyTrustedCTLogs(otapkiref); + CFDictionaryRef trustedNonTlsLogs = SecOTAPKICopyNonTlsTrustedCTLogs(otapkiref); CFReleaseNull(otapkiref); - if (!trustedLogs) { + if (!trustedTlsLogs || !trustedNonTlsLogs) { + CFReleaseNull(trustedTlsLogs); + CFReleaseNull(trustedNonTlsLogs); return NULL; } - CFDictionaryRef logDict = CFDictionaryGetValue(trustedLogs, keyID); + + /* Find the log */ + CFDictionaryRef logDict = CFDictionaryGetValue(trustedTlsLogs, keyID); + if (!logDict) { + logDict = CFDictionaryGetValue(trustedNonTlsLogs, keyID); + } CFRetainSafe(logDict); - CFReleaseSafe(trustedLogs); + CFReleaseNull(trustedTlsLogs); + CFReleaseNull(trustedNonTlsLogs); return logDict; } diff --git a/trust/trustd/SecPolicyServer.c b/trust/trustd/SecPolicyServer.c index e0abcb94..063b0e1b 100644 --- a/trust/trustd/SecPolicyServer.c +++ b/trust/trustd/SecPolicyServer.c @@ -25,25 +25,33 @@ * SecPolicyServer.c - Engine for evaluating certificate paths against trust policies. */ -#include "trust/trustd/SecPolicyServer.h" -#include -#include -#include -#include "trust/trustd/policytree.h" -#include "trust/trustd/nameconstraints.h" -#include +#include #include -#include +#include + +#include +#include #include -#include -#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include #include #include #include #include #include #include -#include +#include +#include +#include +#include #include #include #include @@ -51,9 +59,10 @@ #include #include #include -#include -#include -#include + +#include "trust/trustd/SecPolicyServer.h" +#include "trust/trustd/policytree.h" +#include "trust/trustd/nameconstraints.h" #include "trust/trustd/SecTrustServer.h" #include "trust/trustd/SecTrustLoggingServer.h" #include "trust/trustd/SecRevocationServer.h" @@ -61,12 +70,9 @@ #include "trust/trustd/SecCertificateSource.h" #include "trust/trustd/SecOCSPResponse.h" #include "trust/trustd/SecTrustStoreServer.h" -#include -#include -#include -#include "OTATrustUtilities.h" -#include "personalization.h" -#include +#include "trust/trustd/OTATrustUtilities.h" +#include "trust/trustd/personalization.h" +#include "trust/trustd/CertificateTransparency.h" #if !TARGET_OS_IPHONE #include @@ -95,9 +101,6 @@ static void secdumpdata(CFDataRef data, const char *name) { ****************** SecPolicy object ******************** ********************************************************/ -static SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix); -static CFIndex SecPVCGetCertificateCount(SecPVCRef pvc); -static CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc); static SecTrustSettingsResult SecPVCGetTrustSettingsResult(SecPVCRef pvc, SecCertificateRef certificate, CFArrayRef constraints); static CFMutableDictionaryRef gSecPolicyLeafCallbacks = NULL; @@ -1328,633 +1331,6 @@ certificatePolicies or extendedKeyUsage extensions. */ } - -/* - * MARK: Certificate Transparency support - */ -const CFStringRef kSecCTRetirementDateKey = CFSTR("expiry"); // For backwards compatibility, retirement date is represented with the "expiry" key -const CFStringRef kSecCTReadOnlyDateKey = CFSTR("frozen"); // For backwards compatibility, read-only date is represented with the "frozen" key -const CFStringRef kSecCTShardStartDateKey = CFSTR("start_inclusive"); -const CFStringRef kSecCTShardEndDateKey = CFSTR("end_exclusive"); -const CFStringRef kSecCTPublicKeyKey = CFSTR("key"); - -enum { - kSecCTEntryTypeCert = 0, - kSecCTEntryTypePreCert = 1, -}; - -/*** - -struct { - Version sct_version; // 1 byte - LogID id; // 32 bytes - uint64 timestamp; // 8 bytes - CtExtensions extensions; // 2 bytes len field, + n bytes data - digitally-signed struct { // 1 byte hash alg, 1 byte sig alg, n bytes signature - Version sct_version; - SignatureType signature_type = certificate_timestamp; - uint64 timestamp; - LogEntryType entry_type; - select(entry_type) { - case x509_entry: ASN.1Cert; - case precert_entry: PreCert; - } signed_entry; - CtExtensions extensions; - }; -} SignedCertificateTimestamp; - -***/ - -#include - -static const -SecAsn1Oid *oidForSigAlg(SSL_HashAlgorithm hash, SSL_SignatureAlgorithm alg) -{ - switch(alg) { - case SSL_SignatureAlgorithmRSA: - switch (hash) { - case SSL_HashAlgorithmSHA1: - return &CSSMOID_SHA1WithRSA; - case SSL_HashAlgorithmSHA256: - return &CSSMOID_SHA256WithRSA; - case SSL_HashAlgorithmSHA384: - return &CSSMOID_SHA384WithRSA; - default: - break; - } - case SSL_SignatureAlgorithmECDSA: - switch (hash) { - case SSL_HashAlgorithmSHA1: - return &CSSMOID_ECDSA_WithSHA1; - case SSL_HashAlgorithmSHA256: - return &CSSMOID_ECDSA_WithSHA256; - case SSL_HashAlgorithmSHA384: - return &CSSMOID_ECDSA_WithSHA384; - default: - break; - } - default: - break; - } - - return NULL; -} - - -static size_t SSLDecodeUint16(const uint8_t *p) -{ - return (p[0]<<8 | p[1]); -} - -static uint8_t *SSLEncodeUint16(uint8_t *p, size_t len) -{ - p[0] = (len >> 8)&0xff; - p[1] = (len & 0xff); - return p+2; -} - -static uint8_t *SSLEncodeUint24(uint8_t *p, size_t len) -{ - p[0] = (len >> 16)&0xff; - p[1] = (len >> 8)&0xff; - p[2] = (len & 0xff); - return p+3; -} - - -static -uint64_t SSLDecodeUint64(const uint8_t *p) -{ - uint64_t u = 0; - for(int i=0; i<8; i++) { - u=(u<<8)|p[0]; - p++; - } - return u; -} - -#include -#include -#include - - -static CFDataRef copy_x509_entry_from_chain(SecPVCRef pvc) -{ - SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); - - CFMutableDataRef data = CFDataCreateMutable(kCFAllocatorDefault, 3+SecCertificateGetLength(leafCert)); - - CFDataSetLength(data, 3+SecCertificateGetLength(leafCert)); - - uint8_t *q = CFDataGetMutableBytePtr(data); - q = SSLEncodeUint24(q, SecCertificateGetLength(leafCert)); - memcpy(q, SecCertificateGetBytePtr(leafCert), SecCertificateGetLength(leafCert)); - - return data; -} - - -static CFDataRef copy_precert_entry_from_chain(SecPVCRef pvc) -{ - SecCertificateRef leafCert = NULL; - SecCertificateRef issuer = NULL; - CFDataRef issuerKeyHash = NULL; - CFDataRef tbs_precert = NULL; - CFMutableDataRef data= NULL; - - require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts. - leafCert = SecPVCGetCertificateAtIndex(pvc, 0); - issuer = SecPVCGetCertificateAtIndex(pvc, 1); - - require(leafCert, out); - require(issuer, out); // Those two would likely indicate an internal error, since we already checked the chain length above. - issuerKeyHash = SecCertificateCopySubjectPublicKeyInfoSHA256Digest(issuer); - tbs_precert = SecCertificateCopyPrecertTBS(leafCert); - - require(issuerKeyHash, out); - require(tbs_precert, out); - data = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert)); - CFDataSetLength(data, CFDataGetLength(issuerKeyHash) + 3 + CFDataGetLength(tbs_precert)); - - uint8_t *q = CFDataGetMutableBytePtr(data); - memcpy(q, CFDataGetBytePtr(issuerKeyHash), CFDataGetLength(issuerKeyHash)); q += CFDataGetLength(issuerKeyHash); // issuer key hash - q = SSLEncodeUint24(q, CFDataGetLength(tbs_precert)); - memcpy(q, CFDataGetBytePtr(tbs_precert), CFDataGetLength(tbs_precert)); - -out: - CFReleaseSafe(issuerKeyHash); - CFReleaseSafe(tbs_precert); - return data; -} - -static -CFAbsoluteTime TimestampToCFAbsoluteTime(uint64_t ts) -{ - return (ts / 1000) - kCFAbsoluteTimeIntervalSince1970; -} - -static -uint64_t TimestampFromCFAbsoluteTime(CFAbsoluteTime at) -{ - return (uint64_t)(at + kCFAbsoluteTimeIntervalSince1970) * 1000; -} - -static bool isSCTValidForLogData(CFDictionaryRef logData, int entry_type, CFAbsoluteTime sct_time, CFAbsoluteTime cert_expiry_date) { - /* only embedded SCTs can be used from retired logs. */ - if(entry_type==kSecCTEntryTypeCert && CFDictionaryContainsKey(logData, kSecCTRetirementDateKey)) { - return false; - } - - /* SCTs from after the transition to read-only are not valid (and indicate a operator failure) */ - CFDateRef frozen_date = CFDictionaryGetValue(logData, kSecCTReadOnlyDateKey); - if (frozen_date && (sct_time > CFDateGetAbsoluteTime(frozen_date))) { - secerror("Frozen CT log issued SCT after freezing (log=%@)\n", logData); - return false; - } - - /* If the log is temporally sharded, the certificate expiry date must be within the temporal shard window */ - CFDateRef start_inclusive = CFDictionaryGetValue(logData, kSecCTShardStartDateKey); - CFDateRef end_exclusive = CFDictionaryGetValue(logData, kSecCTShardEndDateKey); - if (start_inclusive && (cert_expiry_date < CFDateGetAbsoluteTime(start_inclusive))) { - return false; - } - if (end_exclusive && (cert_expiry_date >= CFDateGetAbsoluteTime(end_exclusive))) { - return false; - } - - return true; -} - - -/* - If the 'sct' is valid, add it to the validatingLogs dictionary. - - Inputs: - - validatingLogs: mutable dictionary to which to add the log that validate this SCT. - - sct: the SCT date - - entry_type: 0 for x509 cert, 1 for precert. - - entry: the cert or precert data. - - vt: verification time timestamp (as used in SCTs: ms since 1970 Epoch) - - trustedLog: Dictionary contain the Trusted Logs. - - The SCT is valid if: - - It decodes properly. - - Its timestamp is less than 'verifyTime'. - - It is signed by a log in 'trustedLogs'. - - If entry_type = 0, the log must be currently qualified. - - If entry_type = 1, the log may be expired. - - If the SCT is valid, it's added to the validatinLogs dictionary using the log dictionary as the key, and the timestamp as value. - If an entry for the same log already existing in the dictionary, the entry is replaced only if the timestamp of this SCT is earlier. - - */ -static CFDictionaryRef getSCTValidatingLog(CFDataRef sct, int entry_type, CFDataRef entry, uint64_t vt, CFAbsoluteTime cert_expiry_date, CFDictionaryRef trustedLogs, CFAbsoluteTime *sct_at) -{ - uint8_t version; - const uint8_t *logID; - const uint8_t *timestampData; - uint64_t timestamp; - size_t extensionsLen; - const uint8_t *extensionsData; - uint8_t hashAlg; - uint8_t sigAlg; - size_t signatureLen; - const uint8_t *signatureData; - SecKeyRef pubKey = NULL; - uint8_t *signed_data = NULL; - const SecAsn1Oid *oid = NULL; - SecAsn1AlgId algId; - CFDataRef logIDData = NULL; - CFDictionaryRef result = 0; - - const uint8_t *p = CFDataGetBytePtr(sct); - size_t len = CFDataGetLength(sct); - - require(len>=43, out); - - version = p[0]; p++; len--; - logID = p; p+=32; len-=32; - timestampData = p; p+=8; len-=8; - extensionsLen = SSLDecodeUint16(p); p+=2; len-=2; - - require(len>=extensionsLen, out); - extensionsData = p; p+=extensionsLen; len-=extensionsLen; - - require(len>=4, out); - hashAlg=p[0]; p++; len--; - sigAlg=p[0]; p++; len--; - signatureLen = SSLDecodeUint16(p); p+=2; len-=2; - require(len==signatureLen, out); /* We do not tolerate any extra data after the signature */ - signatureData = p; - - /* verify version: only v1(0) is supported */ - if(version!=0) { - secerror("SCT version unsupported: %d\n", version); - goto out; - } - - /* verify timestamp not in the future */ - timestamp = SSLDecodeUint64(timestampData); - if(timestamp > vt) { - secerror("SCT is in the future: %llu > %llu\n", timestamp, vt); - goto out; - } - - uint8_t *q; - - /* signed entry */ - size_t signed_data_len = 12 + CFDataGetLength(entry) + 2 + extensionsLen ; - signed_data = malloc(signed_data_len); - require(signed_data, out); - q = signed_data; - *q++ = version; - *q++ = 0; // certificate_timestamp - memcpy(q, timestampData, 8); q+=8; - q = SSLEncodeUint16(q, entry_type); // logentry type: 0=cert 1=precert - memcpy(q, CFDataGetBytePtr(entry), CFDataGetLength(entry)); q += CFDataGetLength(entry); - q = SSLEncodeUint16(q, extensionsLen); - memcpy(q, extensionsData, extensionsLen); - - logIDData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, logID, 32, kCFAllocatorNull); - - CFDictionaryRef logData = CFDictionaryGetValue(trustedLogs, logIDData); - CFAbsoluteTime sct_time = TimestampToCFAbsoluteTime(timestamp); - require(logData && isSCTValidForLogData(logData, entry_type, sct_time, cert_expiry_date), out); - - CFDataRef logKeyData = CFDictionaryGetValue(logData, kSecCTPublicKeyKey); - require(logKeyData, out); // This failing would be an internal logic error - pubKey = SecKeyCreateFromSubjectPublicKeyInfoData(kCFAllocatorDefault, logKeyData); - require(pubKey, out); - - oid = oidForSigAlg(hashAlg, sigAlg); - require(oid, out); - - algId.algorithm = *oid; - algId.parameters.Data = NULL; - algId.parameters.Length = 0; - - if(SecKeyDigestAndVerify(pubKey, &algId, signed_data, signed_data_len, signatureData, signatureLen)==0) { - *sct_at = sct_time; - result = logData; - } else { - secerror("SCT signature failed (log=%@)\n", logData); - } - -out: - CFReleaseSafe(logIDData); - CFReleaseSafe(pubKey); - free(signed_data); - return result; -} - - -static void addValidatingLog(CFMutableDictionaryRef validatingLogs, CFDictionaryRef log, CFAbsoluteTime sct_at) -{ - CFDateRef validated_time = CFDictionaryGetValue(validatingLogs, log); - - if(validated_time==NULL || (sct_at < CFDateGetAbsoluteTime(validated_time))) { - CFDateRef sct_time = CFDateCreate(kCFAllocatorDefault, sct_at); - CFDictionarySetValue(validatingLogs, log, sct_time); - CFReleaseSafe(sct_time); - } -} - -static CFArrayRef copy_ocsp_scts(SecPVCRef pvc) -{ - CFMutableArrayRef SCTs = NULL; - SecCertificateRef leafCert = NULL; - SecCertificateRef issuer = NULL; - CFArrayRef ocspResponsesData = NULL; - SecOCSPRequestRef ocspRequest = NULL; - - ocspResponsesData = SecPathBuilderCopyOCSPResponses(pvc->builder); - require_quiet(ocspResponsesData, out); - - require_quiet(SecPVCGetCertificateCount(pvc)>=2, out); //we need the issuer key for precerts. - leafCert = SecPVCGetCertificateAtIndex(pvc, 0); - issuer = SecPVCGetCertificateAtIndex(pvc, 1); - - require(leafCert, out); - require(issuer, out); // not quiet: Those two would likely indicate an internal error, since we already checked the chain length above. - ocspRequest = SecOCSPRequestCreate(leafCert, issuer); - - SCTs = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - require(SCTs, out); - - CFArrayForEach(ocspResponsesData, ^(const void *value) { - /* TODO: Should the builder already have the appropriate SecOCSPResponseRef ? */ - SecOCSPResponseRef ocspResponse = SecOCSPResponseCreate(value); - if(ocspResponse && SecOCSPGetResponseStatus(ocspResponse)==kSecOCSPSuccess) { - SecOCSPSingleResponseRef ocspSingleResponse = SecOCSPResponseCopySingleResponse(ocspResponse, ocspRequest); - if(ocspSingleResponse) { - CFArrayRef singleResponseSCTs = SecOCSPSingleResponseCopySCTs(ocspSingleResponse); - if(singleResponseSCTs) { - CFArrayAppendArray(SCTs, singleResponseSCTs, CFRangeMake(0, CFArrayGetCount(singleResponseSCTs))); - CFRelease(singleResponseSCTs); - } - SecOCSPSingleResponseDestroy(ocspSingleResponse); - } - } - if(ocspResponse) SecOCSPResponseFinalize(ocspResponse); - }); - - if(CFArrayGetCount(SCTs)==0) { - CFReleaseNull(SCTs); - } - -out: - CFReleaseSafe(ocspResponsesData); - if(ocspRequest) - SecOCSPRequestFinalize(ocspRequest); - - return SCTs; -} - -static void SecPolicyCheckCT(SecPVCRef pvc) -{ - SecCertificateRef leafCert = SecPVCGetCertificateAtIndex(pvc, 0); - CFArrayRef embeddedScts = SecCertificateCopySignedCertificateTimestamps(leafCert); - CFArrayRef builderScts = SecPathBuilderCopySignedCertificateTimestamps(pvc->builder); - CFDictionaryRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder); - CFArrayRef ocspScts = copy_ocsp_scts(pvc); - CFDataRef precertEntry = copy_precert_entry_from_chain(pvc); - CFDataRef x509Entry = copy_x509_entry_from_chain(pvc); - __block uint32_t trustedSCTCount = 0; - __block CFAbsoluteTime issuanceTime = SecPVCGetVerifyTime(pvc); - __block CFAbsoluteTime certExpiry = SecCertificateNotValidAfter(leafCert); - TA_CTFailureReason failureReason = TA_CTNoFailure; - - if (!trustedLogs) { - SecOTAPKIRef otapkiref = SecOTAPKICopyCurrentOTAPKIRef(); - trustedLogs = SecOTAPKICopyTrustedCTLogs(otapkiref); - CFReleaseSafe(otapkiref); - } - - // This eventually contain list of logs who validated the SCT. - CFMutableDictionaryRef currentLogsValidatingScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - CFMutableDictionaryRef logsValidatingEmbeddedScts = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); - - uint64_t vt = TimestampFromCFAbsoluteTime(SecPVCGetVerifyTime(pvc)); - - __block bool at_least_one_currently_valid_external = 0; - __block bool at_least_one_currently_valid_embedded = 0; - __block bool unknown_log = 0; - __block bool disqualified_log = 0; - - require(logsValidatingEmbeddedScts, out); - require(currentLogsValidatingScts, out); - - /* Skip if there are no SCTs. */ - bool no_scts = (embeddedScts && CFArrayGetCount(embeddedScts) > 0) || - (builderScts && CFArrayGetCount(builderScts) > 0) || - (ocspScts && CFArrayGetCount(ocspScts) > 0); - require_action_quiet(no_scts, out, - TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(pvc->builder); - if (analytics) { - analytics->ct_failure_reason = TA_CTNoSCTs; - } - ); - - if(trustedLogs && CFDictionaryGetCount(trustedLogs) > 0) { // Don't bother trying to validate SCTs if we don't have any trusted logs. - if(embeddedScts && precertEntry) { // Don't bother if we could not get the precert. - CFArrayForEach(embeddedScts, ^(const void *value){ - CFAbsoluteTime sct_at; - CFDictionaryRef log = getSCTValidatingLog(value, 1, precertEntry, vt, certExpiry, trustedLogs, &sct_at); - if(log) { - addValidatingLog(logsValidatingEmbeddedScts, log, sct_at); - if(!CFDictionaryContainsKey(log, kSecCTRetirementDateKey)) { - addValidatingLog(currentLogsValidatingScts, log, sct_at); - at_least_one_currently_valid_embedded = true; - trustedSCTCount++; - } else { - disqualified_log = true; - } - } else { - unknown_log = true; - } - }); - } - - if(builderScts && x509Entry) { // Don't bother if we could not get the cert. - CFArrayForEach(builderScts, ^(const void *value){ - CFAbsoluteTime sct_at; - CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, certExpiry, trustedLogs, &sct_at); - if(log) { - addValidatingLog(currentLogsValidatingScts, log, sct_at); - at_least_one_currently_valid_external = true; - trustedSCTCount++; - } else { - unknown_log = true; - } - }); - } - - if(ocspScts && x509Entry) { - CFArrayForEach(ocspScts, ^(const void *value){ - CFAbsoluteTime sct_at; - CFDictionaryRef log = getSCTValidatingLog(value, 0, x509Entry, vt, certExpiry, trustedLogs, &sct_at); - if(log) { - addValidatingLog(currentLogsValidatingScts, log, sct_at); - at_least_one_currently_valid_external = true; - trustedSCTCount++; - } else { - unknown_log = true; - } - }); - } - } else { - failureReason = TA_CTMissingLogs; - } - - - /* We now have 2 sets of logs that validated those SCTS, count them and make a final decision. - - Current Policy: - is_ct = (A1 AND A2) OR (B1 AND B2). - - A1: embedded SCTs from 2+ to 5+ logs valid at issuance time - A2: At least one embedded SCT from a currently valid log. - - B1: SCTs from 2 currently valid logs (from any source) - B2: At least 1 external SCT from a currently valid log. - - */ - - bool hasValidExternalSCT = (at_least_one_currently_valid_external && CFDictionaryGetCount(currentLogsValidatingScts)>=2); - bool hasValidEmbeddedSCT = (at_least_one_currently_valid_embedded); - SecCertificatePathVCRef path = SecPathBuilderGetPath(pvc->builder); - SecCertificatePathVCSetIsCT(path, false); - - if (hasValidEmbeddedSCT) { - /* Calculate issuance time based on timestamp of SCTs from current logs */ - CFDictionaryForEach(currentLogsValidatingScts, ^(const void *key, const void *value) { - CFDictionaryRef log = key; - if(!CFDictionaryContainsKey(log, kSecCTRetirementDateKey)) { - // Log is still qualified - CFDateRef ts = (CFDateRef) value; - CFAbsoluteTime timestamp = CFDateGetAbsoluteTime(ts); - if(timestamp < issuanceTime) { - issuanceTime = timestamp; - } - } - }); - SecCertificatePathVCSetIssuanceTime(path, issuanceTime); - } - if (hasValidExternalSCT) { - /* Note: since external SCT validates this cert, we do not need to - override issuance time here. If the cert also has a valid embedded - SCT, issuanceTime will be calculated and set in the block above. */ - SecCertificatePathVCSetIsCT(path, true); - } else if (hasValidEmbeddedSCT) { - __block int lifetime; // in Months - __block unsigned once_or_current_qualified_embedded = 0; - - /* Count Logs */ - __block bool failed_once_check = false; - CFDictionaryForEach(logsValidatingEmbeddedScts, ^(const void *key, const void *value) { - CFDictionaryRef log = key; - CFDateRef ts = value; - CFDateRef expiry = CFDictionaryGetValue(log, kSecCTRetirementDateKey); - if (expiry == NULL) { // Currently qualified OR - once_or_current_qualified_embedded++; - } else if (CFDateCompare(ts, expiry, NULL) == kCFCompareLessThan && // Once qualified. That is, qualified at the time of SCT AND - issuanceTime < CFDateGetAbsoluteTime(expiry)) { // at the time of issuance.) - once_or_current_qualified_embedded++; - trustedSCTCount++; - } else { - failed_once_check = true; - } - }); - - SecCFCalendarDoWithZuluCalendar(^(CFCalendarRef zuluCalendar) { - int _lifetime; - CFCalendarGetComponentDifference(zuluCalendar, - SecCertificateNotValidBefore(leafCert), - SecCertificateNotValidAfter(leafCert), - 0, "M", &_lifetime); - lifetime = _lifetime; - }); - - unsigned requiredEmbeddedSctsCount; - - if (lifetime < 15) { - requiredEmbeddedSctsCount = 2; - } else if (lifetime <= 27) { - requiredEmbeddedSctsCount = 3; - } else if (lifetime <= 39) { - requiredEmbeddedSctsCount = 4; - } else { - requiredEmbeddedSctsCount = 5; - } - - if(once_or_current_qualified_embedded >= requiredEmbeddedSctsCount){ - SecCertificatePathVCSetIsCT(path, true); - } else { - /* Not enough "once or currently qualified" SCTs */ - if (failed_once_check) { - failureReason = TA_CTEmbeddedNotEnoughDisqualified; - } else if (unknown_log) { - failureReason = TA_CTEmbeddedNotEnoughUnknown; - } else { - failureReason = TA_CTEmbeddedNotEnough; - } - } - } else if (!at_least_one_currently_valid_embedded && !at_least_one_currently_valid_external) { - /* No currently valid SCTs */ - if (disqualified_log) { - failureReason = TA_CTNoCurrentSCTsDisqualifiedLog; - } else if (unknown_log) { - failureReason = TA_CTNoCurrentSCTsUnknownLog; - } - } else if (at_least_one_currently_valid_external) { - /* One presented current SCT but failed total current check */ - if (disqualified_log) { - failureReason = TA_CTPresentedNotEnoughDisqualified; - } else if (unknown_log) { - failureReason = TA_CTPresentedNotEnoughUnknown; - } else { - failureReason = TA_CTPresentedNotEnough; - } - } - - /* Record analytics data for CT */ - TrustAnalyticsBuilder *analytics = SecPathBuilderGetAnalyticsData(pvc->builder); - require_quiet(analytics, out); - uint32_t sctCount = 0; - /* Count the total number of SCTs we found and report where we got them */ - if (embeddedScts && CFArrayGetCount(embeddedScts) > 0) { - analytics->sct_sources |= TA_SCTEmbedded; - sctCount += CFArrayGetCount(embeddedScts); - } - if (builderScts && CFArrayGetCount(builderScts) > 0) { - analytics->sct_sources |= TA_SCT_TLS; - sctCount += CFArrayGetCount(builderScts); - } - if (ocspScts && CFArrayGetCount(ocspScts) > 0) { - analytics->sct_sources |= TA_SCT_OCSP; - sctCount += CFArrayGetCount(ocspScts); - } - /* Report how many of those SCTs were once or currently qualified */ - analytics->number_trusted_scts = trustedSCTCount; - /* Report how many SCTs we got */ - analytics->number_scts = sctCount; - /* Why we failed */ - analytics->ct_failure_reason = failureReason; - /* Only one current SCT -- close to failure */ - if (CFDictionaryGetCount(currentLogsValidatingScts) == 1) { - analytics->ct_one_current = true; - } -out: - CFReleaseSafe(logsValidatingEmbeddedScts); - CFReleaseSafe(currentLogsValidatingScts); - CFReleaseSafe(builderScts); - CFReleaseSafe(embeddedScts); - CFReleaseSafe(ocspScts); - CFReleaseSafe(precertEntry); - CFReleaseSafe(trustedLogs); - CFReleaseSafe(x509Entry); -} - static bool checkPolicyOidData(SecPVCRef pvc, CFDataRef oid) { CFIndex ix, count = SecPVCGetCertificateCount(pvc); DERItem key_value; @@ -2622,6 +1998,21 @@ static void SecPolicyCheckNotCA(SecPVCRef pvc, CFStringRef key) { } } +static void SecPolicyCheckNonTlsCTRequired(SecPVCRef pvc, CFStringRef key) { + // Skip if kill switch enabled or log list not updated + SecOTAPKIRef otaref = SecOTAPKICopyCurrentOTAPKIRef(); + CFDictionaryRef trustedLogs = SecPathBuilderCopyTrustedLogs(pvc->builder); + if (!SecOTAPKIKillSwitchEnabled(otaref, kOTAPKIKillSwitchNonTLSCT) && + (SecOTAPKIAssetStalenessLessThanSeconds(otaref, kSecOTAPKIAssetStalenessDisable) || trustedLogs)) { + // Check CT against the non-TLS log list + if (!SecPolicyCheckNonTlsCT(pvc)) { + SecPVCSetResult(pvc, key, 0, kCFBooleanFalse); + } + } + CFReleaseNull(otaref); + CFReleaseNull(trustedLogs); +} + void SecPolicyServerInitialize(void) { gSecPolicyLeafCallbacks = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, NULL); @@ -2712,15 +2103,15 @@ SecPolicyRef SecPVCGetPolicy(SecPVCRef pvc) { return (SecPolicyRef)CFArrayGetValueAtIndex(pvc->policies, pvc->policyIX); } -static CFIndex SecPVCGetCertificateCount(SecPVCRef pvc) { +CFIndex SecPVCGetCertificateCount(SecPVCRef pvc) { return SecPathBuilderGetCertificateCount(pvc->builder); } -static SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix) { +SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix) { return SecPathBuilderGetCertificateAtIndex(pvc->builder, ix); } -static CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc) { +CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc) { return SecPathBuilderGetVerifyTime(pvc->builder); } diff --git a/trust/trustd/SecPolicyServer.h b/trust/trustd/SecPolicyServer.h index 44811a01..ab22155d 100644 --- a/trust/trustd/SecPolicyServer.h +++ b/trust/trustd/SecPolicyServer.h @@ -45,6 +45,9 @@ void SecPVCInit(SecPVCRef pvc, SecPathBuilderRef builder, CFArrayRef policies); void SecPVCDelete(SecPVCRef pvc); void SecPVCSetPath(SecPVCRef pvc, SecCertificatePathVCRef path); SecPolicyRef SecPVCGetPolicy(SecPVCRef pv); +SecCertificateRef SecPVCGetCertificateAtIndex(SecPVCRef pvc, CFIndex ix); +CFIndex SecPVCGetCertificateCount(SecPVCRef pvc); +CFAbsoluteTime SecPVCGetVerifyTime(SecPVCRef pvc); /* Set the string result as the reason for the sub policy check key failing. The policy check function should continue processing if diff --git a/trust/trustd/SecTrustServer.h b/trust/trustd/SecTrustServer.h index 50233ad5..10bcf29c 100644 --- a/trust/trustd/SecTrustServer.h +++ b/trust/trustd/SecTrustServer.h @@ -152,20 +152,6 @@ typedef CF_OPTIONS(uint8_t, TA_SCTSource) { TA_SCT_TLS = 1 << 2, }; -typedef CF_ENUM(uint8_t, TA_CTFailureReason) { - TA_CTNoFailure = 0, - TA_CTNoSCTs = 1, - TA_CTMissingLogs = 2, - TA_CTNoCurrentSCTsUnknownLog = 3, - TA_CTNoCurrentSCTsDisqualifiedLog = 4, - TA_CTPresentedNotEnoughUnknown = 5, - TA_CTPresentedNotEnoughDisqualified = 6, - TA_CTPresentedNotEnough = 7, - TA_CTEmbeddedNotEnoughUnknown = 8, - TA_CTEmbeddedNotEnoughDisqualified = 9, - TA_CTEmbeddedNotEnough = 10, -}; - typedef CF_OPTIONS(uint8_t, TAValidStatus) { TAValidDefinitelyOK = 1 << 0, TAValidProbablyOK = 1 << 1, @@ -185,7 +171,6 @@ typedef struct { TA_SCTSource sct_sources; uint32_t number_scts; uint32_t number_trusted_scts; - TA_CTFailureReason ct_failure_reason; bool ct_one_current; // CAIssuer bool ca_issuer_cache_hit; -- 2.45.2