From 84aacf34eae6543be9f0280b2015385f91e5c2c6 Mon Sep 17 00:00:00 2001 From: Apple Date: Wed, 29 Jan 2020 22:18:58 +0000 Subject: [PATCH] Security-58286.270.3.0.1.tar.gz --- .../lib/SecAssessment.cpp | 18 ++++ .../lib/SecAssessment.h | 1 + OSX/libsecurity_codesigning/lib/policydb.cpp | 8 ++ Security.exp-in | 1 + keychain/ckks/CKKS.h | 2 + keychain/ckks/CKKS.m | 2 + keychain/ckks/CKKSCKAccountStateTracker.h | 20 +++++ keychain/ckks/CKKSCKAccountStateTracker.m | 63 +++++++++++++ keychain/ckks/CKKSDeviceStateEntry.h | 10 +++ keychain/ckks/CKKSDeviceStateEntry.m | 89 ++++++++++++++++++- keychain/ckks/CKKSKeychainView.m | 9 +- keychain/ckks/CKKSViewManager.m | 4 +- .../ckks/tests/CKKSDeviceStateUploadTests.m | 57 ++++++++++++ keychain/ckks/tests/CKKSSQLTests.m | 2 + keychain/ckks/tests/CKKSTests.m | 29 ++++++ .../tests/CloudKitKeychainSyncingMockXCTest.h | 3 + .../tests/CloudKitKeychainSyncingMockXCTest.m | 24 +++++ 17 files changed, 336 insertions(+), 6 deletions(-) diff --git a/OSX/libsecurity_codesigning/lib/SecAssessment.cpp b/OSX/libsecurity_codesigning/lib/SecAssessment.cpp index f6aee169..9c40ad59 100644 --- a/OSX/libsecurity_codesigning/lib/SecAssessment.cpp +++ b/OSX/libsecurity_codesigning/lib/SecAssessment.cpp @@ -26,6 +26,7 @@ #include "policyengine.h" #include "xpcengine.h" #include "csutilities.h" +#include "xar++.h" #include #include #include @@ -583,6 +584,23 @@ Boolean SecAssessmentTicketRegister(CFDataRef ticketData, CFErrorRef *errors) END_CSAPI_ERRORS1(false) } +Boolean SecAssessmentRegisterPackageTicket(CFURLRef packageURL, CFErrorRef* errors) +{ + BEGIN_CSAPI + + string path = cfString(packageURL); + Xar xar(path.c_str()); + + if (!xar) { + MacOSError::throwMe(errSecParam); + } + + xar.registerStapledNotarization(); + return true; + + END_CSAPI_ERRORS1(false) +} + Boolean SecAssessmentTicketLookup(CFDataRef hash, SecCSDigestAlgorithm hashType, SecAssessmentTicketFlags flags, double *date, CFErrorRef *errors) { BEGIN_CSAPI diff --git a/OSX/libsecurity_codesigning/lib/SecAssessment.h b/OSX/libsecurity_codesigning/lib/SecAssessment.h index 620c5857..631b418c 100644 --- a/OSX/libsecurity_codesigning/lib/SecAssessment.h +++ b/OSX/libsecurity_codesigning/lib/SecAssessment.h @@ -323,6 +323,7 @@ enum { kSecAssessmentTicketFlagForceOnlineCheck = 1 << 0, // force an online check }; Boolean SecAssessmentTicketRegister(CFDataRef ticketData, CFErrorRef *errors); +Boolean SecAssessmentRegisterPackageTicket(CFURLRef packageURL, CFErrorRef* errors) API_AVAILABLE(macos(10.14.6)); Boolean SecAssessmentTicketLookup(CFDataRef hash, SecCSDigestAlgorithm hashType, SecAssessmentTicketFlags flags, double *date, CFErrorRef *errors); #ifdef __cplusplus diff --git a/OSX/libsecurity_codesigning/lib/policydb.cpp b/OSX/libsecurity_codesigning/lib/policydb.cpp index 7d5eba99..bfe213e5 100644 --- a/OSX/libsecurity_codesigning/lib/policydb.cpp +++ b/OSX/libsecurity_codesigning/lib/policydb.cpp @@ -364,6 +364,14 @@ void PolicyDatabase::upgradeDatabase() devIdRequirementUpgrades.commit(); } + + simpleFeature("notarized_documents", ^{ + SQLite::Statement addNotarizedDocs(*this, + "INSERT INTO authority (type, allow, flags, priority, label, requirement) " + " VALUES (3, 1, 2, 5.0, 'Notarized Developer ID', " + " 'anchor apple generic and certificate 1[field.1.2.840.113635.100.6.2.6] exists and certificate leaf[field.1.2.840.113635.100.6.1.13] exists and notarized')"); + addNotarizedDocs.execute(); + }); } diff --git a/Security.exp-in b/Security.exp-in index b4dab54e..f69fb59b 100644 --- a/Security.exp-in +++ b/Security.exp-in @@ -1621,6 +1621,7 @@ _SecAssessmentUpdate _SecAssessmentCopyUpdate _SecAssessmentControl _SecAssessmentGetTypeID +_SecAssessmentRegisterPackageTicket _SecAssessmentTicketLookup _SecAssessmentTicketRegister _kSecAssessmentContextKeyOperation diff --git a/keychain/ckks/CKKS.h b/keychain/ckks/CKKS.h index 6e35c084..3b1d4568 100644 --- a/keychain/ckks/CKKS.h +++ b/keychain/ckks/CKKS.h @@ -131,6 +131,8 @@ extern NSString* const SecCKRecordItemRefKey; /* Device State CKRecord Keys */ extern NSString* const SecCKRecordDeviceStateType; extern NSString* const SecCKRecordCirclePeerID; +extern NSString* const SecCKRecordOctagonPeerID; +extern NSString* const SecCKRecordOctagonStatus; extern NSString* const SecCKRecordCircleStatus; extern NSString* const SecCKRecordKeyState; extern NSString* const SecCKRecordCurrentTLK; diff --git a/keychain/ckks/CKKS.m b/keychain/ckks/CKKS.m index 3d80c47d..7d13cb38 100644 --- a/keychain/ckks/CKKS.m +++ b/keychain/ckks/CKKS.m @@ -98,6 +98,8 @@ NSString* const SecCKRecordCurrentItemType = @"currentitem"; NSString* const SecCKRecordItemRefKey = @"item"; NSString* const SecCKRecordDeviceStateType = @"devicestate"; +NSString* const SecCKRecordOctagonPeerID = @"octagonpeerid"; +NSString* const SecCKRecordOctagonStatus = @"octagonstatus"; NSString* const SecCKRecordCirclePeerID = @"peerid"; NSString* const SecCKRecordCircleStatus = @"circle"; NSString* const SecCKRecordKeyState = @"keystate"; diff --git a/keychain/ckks/CKKSCKAccountStateTracker.h b/keychain/ckks/CKKSCKAccountStateTracker.h index 5ca81f2c..b5c6ff3f 100644 --- a/keychain/ckks/CKKSCKAccountStateTracker.h +++ b/keychain/ckks/CKKSCKAccountStateTracker.h @@ -57,6 +57,26 @@ typedef NS_ENUM(NSInteger, CKKSAccountStatus) { - (instancetype)init:(SOSCCStatus)status error:error; @end +/* CliqueStatus */ + +typedef NS_ENUM(NSInteger, CliqueStatus) { + CliqueStatusIn = 0, /*There is a clique and I am in it*/ + CliqueStatusNotIn = 1, /*There is a clique and I am not in it - you should get a voucher to join or tell another peer to trust us*/ + CliqueStatusPending = 2, /*For compatibility, keeping the pending state */ + CliqueStatusAbsent = 3, /*There is no clique - you can establish one */ + CliqueStatusNoCloudKitAccount = 4, /* no cloudkit account present */ + CliqueStatusError = -1 /*unable to determine circle status, inspect CFError to find out why */ +}; +NSString* OTCliqueStatusToString(CliqueStatus status); +CliqueStatus OTCliqueStatusFromString(NSString* str); + +@interface OTCliqueStatusWrapper : NSObject +@property (readonly) CliqueStatus status; +- (instancetype)initWithStatus:(CliqueStatus)status; +@end + +/* End of clique status */ + @protocol CKKSAccountStateListener - (void)ckAccountStatusChange:(CKKSAccountStatus)oldStatus to:(CKKSAccountStatus)currentStatus; @end diff --git a/keychain/ckks/CKKSCKAccountStateTracker.m b/keychain/ckks/CKKSCKAccountStateTracker.m index f108eca3..17a732b9 100644 --- a/keychain/ckks/CKKSCKAccountStateTracker.m +++ b/keychain/ckks/CKKSCKAccountStateTracker.m @@ -553,4 +553,67 @@ } @end + +@implementation OTCliqueStatusWrapper +- (instancetype)initWithStatus:(CliqueStatus)status +{ + if((self = [super init])) { + _status = status; + } + return self; +} + +- (BOOL)isEqual:(id)object +{ + if(![object isKindOfClass:[OTCliqueStatusWrapper class]]) { + return NO; + } + + OTCliqueStatusWrapper* obj = (OTCliqueStatusWrapper*)object; + return obj.status == self.status; +} +- (NSString*)description +{ + return [NSString stringWithFormat:@"", OTCliqueStatusToString(self.status)]; +} +@end + +NSString* OTCliqueStatusToString(CliqueStatus status) +{ + switch(status) { + case CliqueStatusIn: + return @"CliqueStatusIn"; + case CliqueStatusNotIn: + return @"CliqueStatusNotIn"; + case CliqueStatusPending: + return @"CliqueStatusPending"; + case CliqueStatusAbsent: + return @"CliqueStatusAbsent"; + case CliqueStatusNoCloudKitAccount: + return @"CliqueStatusNoCloudKitAccount"; + case CliqueStatusError: + return @"CliqueStatusError"; + }; +} +CliqueStatus OTCliqueStatusFromString(NSString* str) +{ + if([str isEqualToString: @"CliqueStatusIn"]) { + return CliqueStatusIn; + } else if([str isEqualToString: @"CliqueStatusNotIn"]) { + return CliqueStatusNotIn; + } else if([str isEqualToString: @"CliqueStatusPending"]) { + return CliqueStatusPending; + } else if([str isEqualToString: @"CliqueStatusAbsent"]) { + return CliqueStatusAbsent; + } else if([str isEqualToString: @"CliqueStatusNoCloudKitAccount"]) { + return CliqueStatusNoCloudKitAccount; + } else if([str isEqualToString: @"CliqueStatusError"]) { + return CliqueStatusError; + } + + return CliqueStatusError; +} + + + #endif // OCTAGON diff --git a/keychain/ckks/CKKSDeviceStateEntry.h b/keychain/ckks/CKKSDeviceStateEntry.h index d7b07910..d2215ad4 100644 --- a/keychain/ckks/CKKSDeviceStateEntry.h +++ b/keychain/ckks/CKKSDeviceStateEntry.h @@ -32,6 +32,7 @@ #import #import "keychain/ckks/CKKS.h" #import "keychain/ckks/CKKSRecordHolder.h" +#import "keychain/ckks/CKKSCKAccountStateTracker.h" #import "keychain/ckks/CKKSSQLDatabaseObject.h" NS_ASSUME_NONNULL_BEGIN @@ -52,7 +53,14 @@ NS_ASSUME_NONNULL_BEGIN @property (nullable) NSDate* lastUnlockTime; @property (nullable) NSString* circlePeerID; +@property (nullable) NSString* octagonPeerID; + @property SOSCCStatus circleStatus; + +// Some devices don't have Octagon, and won't upload this. Therefore, it might not be present, +// and I'd rather not coerce to "error" or "absent" +@property (nullable) OTCliqueStatusWrapper* octagonStatus; + @property (nullable) CKKSZoneKeyState* keyState; @property (nullable) NSString* currentTLKUUID; @@ -68,6 +76,8 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initForDevice:(NSString* _Nullable)device osVersion:(NSString* _Nullable)osVersion lastUnlockTime:(NSDate* _Nullable)lastUnlockTime + octagonPeerID:(NSString* _Nullable)octagonPeerID + octagonStatus:(OTCliqueStatusWrapper* _Nullable)octagonStatus circlePeerID:(NSString* _Nullable)circlePeerID circleStatus:(SOSCCStatus)circleStatus keyState:(CKKSZoneKeyState* _Nullable)keyState diff --git a/keychain/ckks/CKKSDeviceStateEntry.m b/keychain/ckks/CKKSDeviceStateEntry.m index e520e181..9c33721c 100644 --- a/keychain/ckks/CKKSDeviceStateEntry.m +++ b/keychain/ckks/CKKSDeviceStateEntry.m @@ -38,6 +38,8 @@ - (instancetype)initForDevice:(NSString*)device osVersion:(NSString*)osVersion lastUnlockTime:(NSDate*)lastUnlockTime + octagonPeerID:(NSString*)octagonPeerID + octagonStatus:(OTCliqueStatusWrapper*)octagonStatus circlePeerID:(NSString*)circlePeerID circleStatus:(SOSCCStatus)circleStatus keyState:(CKKSZoneKeyState*)keyState @@ -54,6 +56,9 @@ _osVersion = osVersion; _lastUnlockTime = lastUnlockTime; + _octagonPeerID = octagonPeerID; + _octagonStatus = octagonStatus; + _circleStatus = circleStatus; _keyState = keyState; @@ -72,7 +77,7 @@ // kSOSCCError is -1, but without a size. // make it a special number if(status == kSOSCCError) { - [NSNumber numberWithInt:kSOSCCErrorPositive]; + return [NSNumber numberWithInt:kSOSCCErrorPositive]; } return [NSNumber numberWithInt:status]; } @@ -104,6 +109,48 @@ } } +- (id)cliqueStatusToCKType:(OTCliqueStatusWrapper* _Nullable)status { + if(!status) { + return nil; + } + // kSOSCCError is -1, but without a size. + // make it a special number + if(status.status == CliqueStatusError) { + return [NSNumber numberWithInt:kSOSCCErrorPositive]; + } + return [NSNumber numberWithInt:(int)status.status]; +} + ++ (OTCliqueStatusWrapper* _Nullable)cktypeToOTCliqueStatusWrapper:(id)object { + if(object == nil) { + return nil; + } + if(![object isKindOfClass:[NSNumber class]]) { + return nil; + } + NSNumber* number = (NSNumber*)object; + + uint32_t n = [number unsignedIntValue]; + + switch(n) { + case (uint32_t)CliqueStatusIn: + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusIn]; + case (uint32_t)CliqueStatusNotIn: + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusNotIn]; + case (uint32_t)CliqueStatusPending: + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusPending]; + case (uint32_t)CliqueStatusAbsent: + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusAbsent]; + case (uint32_t)CliqueStatusNoCloudKitAccount: + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusNoCloudKitAccount]; + case (uint32_t)kSOSCCErrorPositive: // Use the magic number + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusError]; + default: + secerror("ckks: %d is not an OTCliqueStatus?", n); + return [[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusError];; + } +} + +(NSString*)nameFromCKRecordID:(CKRecordID*)recordID { // Strip off the prefix from the recordName NSString* prefix = @"ckid-"; @@ -118,13 +165,15 @@ -(NSString*)description { NSDate* updated = self.storedCKRecord.modificationDate; - return [NSString stringWithFormat:@"", + return [NSString stringWithFormat:@"", self.device, self.circlePeerID, + self.octagonPeerID, self.osVersion, self.lastUnlockTime, self.zoneID.zoneName, SOSAccountGetSOSCCStatusString(self.circleStatus), + self.octagonStatus ? OTCliqueStatusToString(self.octagonStatus.status) : @"CliqueMissing", self.keyState, self.currentTLKUUID, self.currentClassAUUID, @@ -144,6 +193,8 @@ ((self.device == nil && obj.device == nil) || [self.device isEqual: obj.device]) && ((self.osVersion == nil && obj.osVersion == nil) || [self.osVersion isEqual:obj.osVersion]) && ((self.lastUnlockTime == nil && obj.lastUnlockTime == nil) || [self.lastUnlockTime isEqual:obj.lastUnlockTime]) && + ((self.octagonPeerID == nil && obj.octagonPeerID == nil) || [self.octagonPeerID isEqual: obj.octagonPeerID]) && + ((self.octagonStatus == nil && obj.octagonStatus == nil) || [self.octagonStatus isEqual: obj.octagonStatus]) && ((self.circlePeerID == nil && obj.circlePeerID == nil) || [self.circlePeerID isEqual: obj.circlePeerID]) && (self.circleStatus == obj.circleStatus) && ((self.keyState == nil && obj.keyState == nil) || [self.keyState isEqual: obj.keyState]) && @@ -194,6 +245,9 @@ record[SecCKSRecordOSVersionKey] = self.osVersion; record[SecCKSRecordLastUnlockTime] = self.lastUnlockTime; + record[SecCKRecordOctagonPeerID] = self.octagonPeerID; + record[SecCKRecordOctagonStatus] = [self cliqueStatusToCKType:self.octagonStatus]; + record[SecCKRecordCircleStatus] = [self sosCCStatusToCKType: self.circleStatus]; record[SecCKRecordKeyState] = CKKSZoneKeyToNumber(self.keyState); @@ -234,6 +288,16 @@ return false; } + if((!(self.octagonPeerID == nil && record[SecCKRecordOctagonPeerID] == nil)) && + ![record[SecCKRecordOctagonPeerID] isEqualToString:self.octagonPeerID]) { + return false; + } + + if((!(self.octagonStatus == nil && record[SecCKRecordOctagonStatus] == nil)) && + [self.octagonStatus isEqual: [CKKSDeviceStateEntry cktypeToOTCliqueStatusWrapper:record[SecCKRecordOctagonStatus]]]) { + return false; + } + if([self cktypeToSOSCCStatus: record[SecCKRecordCircleStatus]] != self.circleStatus) { return false; } @@ -269,9 +333,13 @@ self.lastUnlockTime = record[SecCKSRecordLastUnlockTime]; self.device = [CKKSDeviceStateEntry nameFromCKRecordID: record.recordID]; + self.octagonPeerID = record[SecCKRecordOctagonPeerID]; + self.octagonStatus = [CKKSDeviceStateEntry cktypeToOTCliqueStatusWrapper:record[SecCKRecordOctagonStatus]]; + self.circlePeerID = record[SecCKRecordCirclePeerID]; self.circleStatus = [self cktypeToSOSCCStatus:record[SecCKRecordCircleStatus]]; + self.keyState = CKKSZoneKeyRecover(record[SecCKRecordKeyState]); self.currentTLKUUID = [[record[SecCKRecordCurrentTLK] recordID] recordName]; @@ -286,6 +354,8 @@ } + (NSArray*)sqlColumns { + // NOTE: due to schedule reasons, in this release we can't modify the DB schema, so octagon status and peer ID can't be stored + // in normal columns. So, they're stored in the ckrecord field. Good luck! return @[@"device", @"ckzone", @"osversion", @"lastunlock", @"peerid", @"circlestatus", @"keystate", @"currentTLK", @"currentClassA", @"currentClassC", @"ckrecord"]; } @@ -313,9 +383,12 @@ + (instancetype)fromDatabaseRow:(NSDictionary*)row { NSISO8601DateFormatter* dateFormat = [[NSISO8601DateFormatter alloc] init]; - return [[CKKSDeviceStateEntry alloc] initForDevice:row[@"device"] + CKKSDeviceStateEntry* entry = + [[CKKSDeviceStateEntry alloc] initForDevice:row[@"device"] osVersion:CKKSNSNullToNil(row[@"osversion"]) lastUnlockTime:[row[@"lastunlock"] isEqual: [NSNull null]] ? nil : [dateFormat dateFromString: row[@"lastunlock"]] + octagonPeerID:nil + octagonStatus:nil circlePeerID:CKKSNSNullToNil(row[@"peerid"]) circleStatus:SOSAccountGetSOSCCStatusFromString((__bridge CFStringRef) CKKSNSNullToNil(row[@"circlestatus"])) keyState:CKKSNSNullToNil(row[@"keystate"]) @@ -325,6 +398,16 @@ zoneID:[[CKRecordZoneID alloc] initWithZoneName: row[@"ckzone"] ownerName:CKCurrentUserDefaultName] encodedCKRecord:CKKSUnbase64NullableString(row[@"ckrecord"]) ]; + + // NOTE: due to schedule reasons, in this release we can't modify the DB schema, so octagon status and peer ID can't be stored + // in normal columns. So, if they're stored in the ckrecord field, use that! + CKRecord* record = entry.storedCKRecord; + if(record) { + entry.octagonPeerID = record[SecCKRecordOctagonPeerID]; + entry.octagonStatus = [self cktypeToOTCliqueStatusWrapper:record[SecCKRecordOctagonStatus]]; + } + + return entry; } @end diff --git a/keychain/ckks/CKKSKeychainView.m b/keychain/ckks/CKKSKeychainView.m index 07e5b987..89cd02ad 100644 --- a/keychain/ckks/CKKSKeychainView.m +++ b/keychain/ckks/CKKSKeychainView.m @@ -1243,7 +1243,12 @@ return true; } for(CKKSDeviceStateEntry* device in allDeviceStates) { - if([trustedPeerIDs containsObject:device.circlePeerID]) { + if(device.octagonPeerID != nil) { + ckksnotice("ckkskey", self, "An Octagon-capable device has been in this account; not resetting: (%@)", device); + return true; + } + + if([trustedPeerIDs containsObject:device.circlePeerID] || [trustedPeerIDs containsObject:device.octagonPeerID]) { // Is this a recent DSE? If it's older than the deadline, skip it if([device.storedCKRecord.modificationDate compare:trustedDeadline] == NSOrderedAscending) { ckksnotice("ckkskey", self, "Trusted device state (%@) is too old; ignoring", device); @@ -2236,6 +2241,8 @@ CKKSDeviceStateEntry* newcdse = [[CKKSDeviceStateEntry alloc] initForDevice:accountTracker.ckdeviceID osVersion:SecCKKSHostOSVersion() lastUnlockTime:lastUnlockDay + octagonPeerID:nil + octagonStatus:nil circlePeerID:accountTracker.accountCirclePeerID circleStatus:accountTracker.currentCircleStatus.status keyState:self.keyHierarchyState diff --git a/keychain/ckks/CKKSViewManager.m b/keychain/ckks/CKKSViewManager.m index afd9afd2..2de04ab9 100644 --- a/keychain/ckks/CKKSViewManager.m +++ b/keychain/ckks/CKKSViewManager.m @@ -199,7 +199,7 @@ } NSMutableDictionary* values = [NSMutableDictionary dictionary]; - BOOL inCircle = (strongSelf.accountTracker.currentCircleStatus == kSOSCCInCircle); + BOOL inCircle = (strongSelf.accountTracker.currentCircleStatus.status == kSOSCCInCircle); if (inCircle) { [[CKKSAnalytics logger] setDateProperty:[NSDate date] forKey:CKKSAnalyticsLastInCircle]; } @@ -224,7 +224,7 @@ if(!strongSelf) { return nil; } - BOOL inCircle = strongSelf.accountTracker && strongSelf.accountTracker.currentCircleStatus == kSOSCCInCircle; + BOOL inCircle = strongSelf.accountTracker && strongSelf.accountTracker.currentCircleStatus.status == kSOSCCInCircle; NSMutableDictionary* values = [NSMutableDictionary dictionary]; CKKSKeychainView* view = [strongSelf findOrCreateView:viewName]; NSDate* dateOfLastSyncClassA = [[CKKSAnalytics logger] dateOfLastSuccessForEvent:CKKSEventProcessIncomingQueueClassA inView:view]; diff --git a/keychain/ckks/tests/CKKSDeviceStateUploadTests.m b/keychain/ckks/tests/CKKSDeviceStateUploadTests.m index 5148b6e7..2c1fa981 100644 --- a/keychain/ckks/tests/CKKSDeviceStateUploadTests.m +++ b/keychain/ckks/tests/CKKSDeviceStateUploadTests.m @@ -71,6 +71,10 @@ XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it"); XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle"); + + XCTAssertNil(record[SecCKRecordOctagonPeerID], "octagon peer ID should be missing"); + XCTAssertNil(record[SecCKRecordOctagonStatus], "octagon status should be missing"); + XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateReady), "Device is in ready"); XCTAssertEqualObjects([record[SecCKRecordCurrentTLK] recordID].recordName, zoneKeys.tlk.uuid, "Correct TLK uuid"); @@ -113,6 +117,10 @@ XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it"); XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle"); + + XCTAssertNil(record[SecCKRecordOctagonPeerID], "octagon peer ID should be missing"); + XCTAssertNil(record[SecCKRecordOctagonStatus], "octagon status should be missing"); + XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateReady), "Device is in ready"); XCTAssertEqualObjects([record[SecCKRecordCurrentTLK] recordID].recordName, zoneKeys.tlk.uuid, "Correct TLK uuid"); @@ -222,6 +230,10 @@ XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID matches what we gave it"); XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device is in circle"); + + XCTAssertNil(record[SecCKRecordOctagonPeerID], "octagon peer ID should be missing"); + XCTAssertNil(record[SecCKRecordOctagonStatus], "octagon status should be missing"); + XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateReady), "Device is in ready"); XCTAssertEqualObjects([record[SecCKRecordCurrentTLK] recordID].recordName, zoneKeys.tlk.uuid, "Correct TLK uuid"); @@ -266,6 +278,10 @@ XCTAssertEqualObjects(record[SecCKRecordCirclePeerID], strongSelf.circlePeerID, "peer ID should matche what we gave it"); XCTAssertEqualObjects(record[SecCKRecordCircleStatus], [NSNumber numberWithInt:kSOSCCInCircle], "device should be in circle"); + + XCTAssertNil(record[SecCKRecordOctagonPeerID], "octagon peer ID should be missing"); + XCTAssertNil(record[SecCKRecordOctagonStatus], "octagon status should be missing"); + XCTAssertEqualObjects(record[SecCKRecordKeyState], CKKSZoneKeyToNumber(SecCKKSZoneKeyStateWaitForTLK), "Device should be in waitfortlk"); XCTAssertNil([record[SecCKRecordCurrentTLK] recordID].recordName, "Should have no TLK uuid"); @@ -297,6 +313,8 @@ CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:@"otherdevice" osVersion:@"fake-version" lastUnlockTime:date + octagonPeerID:nil + octagonStatus:nil circlePeerID:@"asdfasdf" circleStatus:kSOSCCInCircle keyState:SecCKKSZoneKeyStateReady @@ -311,6 +329,8 @@ CKKSDeviceStateEntry* oldcdse = [[CKKSDeviceStateEntry alloc] initForDevice:@"olderotherdevice" osVersion:nil // old-style, no OSVersion or lastUnlockTime lastUnlockTime:nil + octagonPeerID:nil + octagonStatus:nil circlePeerID:@"olderasdfasdf" circleStatus:kSOSCCInCircle keyState:SecCKKSZoneKeyStateReady @@ -321,6 +341,21 @@ encodedCKRecord:nil]; [self.keychainZone addToZone:[oldcdse CKRecordWithZoneID:self.keychainZoneID]]; + CKKSDeviceStateEntry* octagonOnly = [[CKKSDeviceStateEntry alloc] initForDevice:@"octagon-only" + osVersion:@"octagon-version" + lastUnlockTime:date + octagonPeerID:@"octagon-peer-ID" + octagonStatus:[[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusNotIn] + circlePeerID:nil + circleStatus:kSOSCCError + keyState:SecCKKSZoneKeyStateReady + currentTLKUUID:zoneKeys.tlk.uuid + currentClassAUUID:zoneKeys.classA.uuid + currentClassCUUID:zoneKeys.classC.uuid + zoneID:self.keychainZoneID + encodedCKRecord:nil]; + [self.keychainZone addToZone:[octagonOnly CKRecordWithZoneID:self.keychainZoneID]]; + // Trigger a notification (with hilariously fake data) [self.keychainView notifyZoneChange:nil]; [self.keychainView waitForFetchAndIncomingQueueProcessing]; @@ -334,11 +369,14 @@ CKKSDeviceStateEntry* item = nil; CKKSDeviceStateEntry* olderotherdevice = nil; + CKKSDeviceStateEntry* octagondevice = nil; for(CKKSDeviceStateEntry* dbcdse in cdses) { if([dbcdse.device isEqualToString:@"otherdevice"]) { item = dbcdse; } else if([dbcdse.device isEqualToString:@"olderotherdevice"]) { olderotherdevice = dbcdse; + } else if([dbcdse.device isEqualToString:@"octagon-only"]) { + octagondevice = dbcdse; } } XCTAssertNotNil(item, "Found a cdse for otherdevice"); @@ -352,6 +390,8 @@ XCTAssertEqualObjects(item.currentTLKUUID, zoneKeys.tlk.uuid, "correct tlk uuid"); XCTAssertEqualObjects(item.currentClassAUUID, zoneKeys.classA.uuid, "correct classA uuid"); XCTAssertEqualObjects(item.currentClassCUUID, zoneKeys.classC.uuid, "correct classC uuid"); + XCTAssertNil(item.octagonPeerID, "should have no octagon peerID"); + XCTAssertNil(item.octagonStatus, "should have no octagon status"); XCTAssertNotNil(olderotherdevice, "Should have found a cdse for olderotherdevice"); @@ -364,6 +404,23 @@ XCTAssertEqualObjects(olderotherdevice.currentTLKUUID, zoneKeys.tlk.uuid, "correct tlk uuid"); XCTAssertEqualObjects(olderotherdevice.currentClassAUUID, zoneKeys.classA.uuid, "correct classA uuid"); XCTAssertEqualObjects(olderotherdevice.currentClassCUUID, zoneKeys.classC.uuid, "correct classC uuid"); + XCTAssertNil(olderotherdevice.octagonPeerID, "should have no octagon peerID"); + XCTAssertNil(olderotherdevice.octagonStatus, "should have no octagon status"); + + + XCTAssertNotNil(octagondevice, "Should have found a cdse for octagondevice"); + XCTAssertEqualObjects(octagonOnly, octagondevice, "Saved item should match pre-cloudkit item"); + XCTAssertEqualObjects(octagondevice.osVersion, @"octagon-version", "osVersion should be right"); + XCTAssertEqualObjects(octagondevice.lastUnlockTime, date, "correct date"); + XCTAssertEqualObjects(octagondevice.octagonPeerID, @"octagon-peer-ID", "correct octagon peer id"); + XCTAssertNotNil(octagondevice.octagonStatus, "should have an octagon status"); + XCTAssertEqual(octagondevice.octagonStatus.status, CliqueStatusNotIn, "correct octagon status"); + XCTAssertEqual(octagondevice.circleStatus, kSOSCCError, "correct SOS circle state"); + XCTAssertNil(octagondevice.circlePeerID, "correct peer id"); + XCTAssertEqualObjects(octagondevice.keyState, SecCKKSZoneKeyStateReady, "correct key state"); + XCTAssertEqualObjects(octagondevice.currentTLKUUID, zoneKeys.tlk.uuid, "correct tlk uuid"); + XCTAssertEqualObjects(octagondevice.currentClassAUUID, zoneKeys.classA.uuid, "correct classA uuid"); + XCTAssertEqualObjects(octagondevice.currentClassCUUID, zoneKeys.classC.uuid, "correct classC uuid"); return false; }]; diff --git a/keychain/ckks/tests/CKKSSQLTests.m b/keychain/ckks/tests/CKKSSQLTests.m index 7225246c..71d6acc3 100644 --- a/keychain/ckks/tests/CKKSSQLTests.m +++ b/keychain/ckks/tests/CKKSSQLTests.m @@ -276,6 +276,8 @@ CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:testUUID osVersion:@"faux-version" lastUnlockTime:nil + octagonPeerID:@"peerID" + octagonStatus:nil circlePeerID:@"asdf" circleStatus:kSOSCCInCircle keyState:SecCKKSZoneKeyStateReady diff --git a/keychain/ckks/tests/CKKSTests.m b/keychain/ckks/tests/CKKSTests.m index 33ea65b4..a0147c12 100644 --- a/keychain/ckks/tests/CKKSTests.m +++ b/keychain/ckks/tests/CKKSTests.m @@ -1298,6 +1298,33 @@ XCTAssertFalse(self.keychainZone.flag, "Zone flag should have been reset to false"); } +- (void)testDoNotResetCloudKitZoneFromWaitForTLKDueToAncientOctagonDeviceState { + [self putFakeKeyHierarchyInCloudKit:self.keychainZoneID]; + + // CKKS should not reset this zone, because some Octagon device appeared on this account ever + self.silentZoneDeletesAllowed = true; + [self putFakeOctagonOnlyDeviceStatusInCloudKit:self.keychainZoneID]; + + NSDateComponents* offset = [[NSDateComponents alloc] init]; + [offset setDay:-46]; + NSDate* updateTime = [[NSCalendar currentCalendar] dateByAddingComponents:offset toDate:[NSDate date] options:0]; + for(CKRecord* record in self.keychainZone.currentDatabase.allValues) { + if([record.recordType isEqualToString:SecCKRecordDeviceStateType] || [record.recordType isEqualToString:SecCKRecordTLKShareType]) { + record.creationDate = updateTime; + record.modificationDate = updateTime; + } + } + + self.keychainZone.flag = true; + [self startCKKSSubsystem]; + + XCTAssertEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateWaitForTLK] wait:20*NSEC_PER_SEC], @"Key state should become 'waitfortlk'"); + XCTAssertTrue(self.keychainZone.flag, "Zone flag should not have been reset to false"); + + // And ensure it doesn't go on to 'reset' + XCTAssertNotEqual(0, [self.keychainView.keyHierarchyConditions[SecCKKSZoneKeyStateResettingZone] wait:100*NSEC_PER_MSEC], @"Key state should not become 'resetzone'"); +} + - (void)testAcceptExistingKeyHierarchy { // Test starts with no keys in CKKS database, but one in our fake CloudKit. // Test also begins with the TLK having arrived in the local keychain (via SOS) @@ -3022,6 +3049,8 @@ CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:self.ckDeviceID osVersion:@"fake-record" lastUnlockTime:[NSDate date] + octagonPeerID:nil + octagonStatus:nil circlePeerID:self.circlePeerID circleStatus:kSOSCCInCircle keyState:SecCKKSZoneKeyStateWaitForTLK diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h index a2a8c8d6..32020bd3 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h +++ b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h @@ -75,6 +75,9 @@ NS_ASSUME_NONNULL_BEGIN - (void)putFakeDeviceStatusInCloudKit:(CKRecordZoneID*)zoneID zonekeys:(ZoneKeys*)zonekeys; +- (void)putFakeOctagonOnlyDeviceStatusInCloudKit:(CKRecordZoneID*)zoneID zonekeys:(ZoneKeys*)zonekeys; +- (void)putFakeOctagonOnlyDeviceStatusInCloudKit:(CKRecordZoneID*)zoneID; + - (void)SOSPiggyBackAddToKeychain:(NSDictionary*)piggydata; - (NSMutableDictionary*)SOSPiggyBackCopyFromKeychain; - (NSMutableArray*)SOSPiggyICloudIdentities; diff --git a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m index b82da1c0..200b7954 100644 --- a/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m +++ b/keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m @@ -323,6 +323,8 @@ CKKSDeviceStateEntry* dse = [[CKKSDeviceStateEntry alloc] initForDevice:self.remoteSOSOnlyPeer.peerID osVersion:@"faux-version" lastUnlockTime:nil + octagonPeerID:nil + octagonStatus:nil circlePeerID:self.remoteSOSOnlyPeer.peerID circleStatus:kSOSCCInCircle keyState:SecCKKSZoneKeyStateReady @@ -338,6 +340,28 @@ [self putFakeDeviceStatusInCloudKit:zoneID zonekeys:self.keys[zoneID]]; } +- (void)putFakeOctagonOnlyDeviceStatusInCloudKit:(CKRecordZoneID*)zoneID zonekeys:(ZoneKeys*)zonekeys { + CKKSDeviceStateEntry* dse = [[CKKSDeviceStateEntry alloc] initForDevice:self.remoteSOSOnlyPeer.peerID + osVersion:@"faux-version" + lastUnlockTime:nil + octagonPeerID:@"octagon-fake-peer-id" + octagonStatus:[[OTCliqueStatusWrapper alloc] initWithStatus:CliqueStatusIn] + circlePeerID:nil + circleStatus:kSOSCCError + keyState:SecCKKSZoneKeyStateReady + currentTLKUUID:zonekeys.tlk.uuid + currentClassAUUID:zonekeys.classA.uuid + currentClassCUUID:zonekeys.classC.uuid + zoneID:zoneID + encodedCKRecord:nil]; + [self.zones[zoneID] addToZone:dse zoneID:zoneID]; +} + +- (void)putFakeOctagonOnlyDeviceStatusInCloudKit:(CKRecordZoneID*)zoneID { + [self putFakeOctagonOnlyDeviceStatusInCloudKit:zoneID zonekeys:self.keys[zoneID]]; +} + + - (void)putFakeKeyHierarchyInCloudKit: (CKRecordZoneID*)zoneID { ZoneKeys* zonekeys = [self createFakeKeyHierarchy: zoneID oldTLK:nil]; -- 2.45.2