]> git.saurik.com Git - apple/security.git/commitdiff
Security-58286.270.3.0.1.tar.gz macos-10146 v58286.270.3.0.1
authorApple <opensource@apple.com>
Wed, 29 Jan 2020 22:18:58 +0000 (22:18 +0000)
committerApple <opensource@apple.com>
Wed, 29 Jan 2020 22:18:58 +0000 (22:18 +0000)
17 files changed:
OSX/libsecurity_codesigning/lib/SecAssessment.cpp
OSX/libsecurity_codesigning/lib/SecAssessment.h
OSX/libsecurity_codesigning/lib/policydb.cpp
Security.exp-in
keychain/ckks/CKKS.h
keychain/ckks/CKKS.m
keychain/ckks/CKKSCKAccountStateTracker.h
keychain/ckks/CKKSCKAccountStateTracker.m
keychain/ckks/CKKSDeviceStateEntry.h
keychain/ckks/CKKSDeviceStateEntry.m
keychain/ckks/CKKSKeychainView.m
keychain/ckks/CKKSViewManager.m
keychain/ckks/tests/CKKSDeviceStateUploadTests.m
keychain/ckks/tests/CKKSSQLTests.m
keychain/ckks/tests/CKKSTests.m
keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.h
keychain/ckks/tests/CloudKitKeychainSyncingMockXCTest.m

index f6aee169badbac8f85966d537258f944dedf6c8c..9c40ad59d23cfeeb1653a694a83ccbe223c8403e 100644 (file)
@@ -26,6 +26,7 @@
 #include "policyengine.h"
 #include "xpcengine.h"
 #include "csutilities.h"
+#include "xar++.h"
 #include <CoreFoundation/CFRuntime.h>
 #include <CoreFoundation/CFBundlePriv.h>
 #include <security_utilities/globalizer.h>
@@ -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
index 620c585781f20d75ee29c52d2ddbbf4ac02a6012..631b418cfec0754775bb2dba4e8932aa23eedbba 100644 (file)
@@ -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
index 7d5eba993060a9e599845bcbd60aeb79822efbbc..bfe213e5ef1a7c810ced5af01082c4708494d9af 100644 (file)
@@ -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();
+       });
 }
 
 
index b4dab54e45809ab8c6f7656eefc59e21b4e07892..f69fb59bb7e8106a4a9599a8f3bd69015795d877 100644 (file)
@@ -1621,6 +1621,7 @@ _SecAssessmentUpdate
 _SecAssessmentCopyUpdate
 _SecAssessmentControl
 _SecAssessmentGetTypeID
+_SecAssessmentRegisterPackageTicket
 _SecAssessmentTicketLookup
 _SecAssessmentTicketRegister
 _kSecAssessmentContextKeyOperation
index 6e35c084c11facffb5853d5beaba203197afc3ad..3b1d456846b72781fede2d528d92820c038610b5 100644 (file)
@@ -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;
index 3d80c47d6bba92c3ce51dfcdba4cae60f63b7886..7d13cb38b40c34d25753946a13c56a37b81bf864 100644 (file)
@@ -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";
index 5ca81f2cd7c77ad207af58d3d8efd2cdbdb8b2e2..b5c6ff3f3741f4c463be8906baabd792fdf9019a 100644 (file)
@@ -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 <NSObject>
 - (void)ckAccountStatusChange:(CKKSAccountStatus)oldStatus to:(CKKSAccountStatus)currentStatus;
 @end
index f108eca33926f1bdb08d554fb780818b3576d419..17a732b99309f3601fb538827262ad14c8e79fe1 100644 (file)
 }
 @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:@"<CliqueStatus: %@>", 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
index d7b079107d735d64ba81d8445209baae09900996..d2215ad47d7f3def7513767b9389969cecbf31f9 100644 (file)
@@ -32,6 +32,7 @@
 #import <CloudKit/CloudKit.h>
 #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
index e520e1814d0f20bf82108c507b1bb489a9a07b4f..9c33721cd14401b7736de2239f7e80e57f0c076b 100644 (file)
@@ -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];
 }
     }
 }
 
+- (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-";
 -(NSString*)description {
     NSDate* updated = self.storedCKRecord.modificationDate;
 
-    return [NSString stringWithFormat:@"<CKKSDeviceStateEntry(%@,%@,%@,%@,%@): %@ %@ %@ %@ %@ upd:%@>",
+    return [NSString stringWithFormat:@"<CKKSDeviceStateEntry(%@,%@,%@,%@,%@,%@): %@ %@ %@ %@ %@ %@ upd:%@>",
             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,
             ((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]) &&
     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);
 
         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;
     }
     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];
 }
 
 + (NSArray<NSString*>*)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"];
 }
 
 + (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"])
                                                 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
index 07e5b98780a9509758ce5543bc461d4dc510d0cb..89cd02add344e72d0c80b779876486ad45ee0def 100644 (file)
         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);
     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
index afd9afd2e9270e63f209c11a425a5bb22d315aae..2de04ab9bea25a282e2dbe7dbf928d9b4b9aa546 100644 (file)
         }
 
         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];
         }
             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];
index 5148b6e7a9769c898ec5bbae51353fe68f99c984..2c1fa9810f4ad412623f33ac93538af24cdd81d9 100644 (file)
 
                     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");
 
                     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");
 
                     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");
 
                     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");
     CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:@"otherdevice"
                                                                    osVersion:@"fake-version"
                                                               lastUnlockTime:date
+                                                               octagonPeerID:nil
+                                                               octagonStatus:nil
                                                                 circlePeerID:@"asdfasdf"
                                                                 circleStatus:kSOSCCInCircle
                                                                     keyState:SecCKKSZoneKeyStateReady
     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
                                                                 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];
 
         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");
         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");
         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;
     }];
index 7225246ccbf8c1741b7a7413d54eb9e19e58aae1..71d6acc350b072c6c409dfd9665cdef6fbc7a07f 100644 (file)
     CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:testUUID
                                                                    osVersion:@"faux-version"
                                                               lastUnlockTime:nil
+                                                               octagonPeerID:@"peerID"
+                                                               octagonStatus:nil
                                                                 circlePeerID:@"asdf"
                                                                 circleStatus:kSOSCCInCircle
                                                                     keyState:SecCKKSZoneKeyStateReady
index 33ea65b40ff4e7c757f17f30695efd32f139a6ed..a0147c122f4d54ce9127a8f87aed22a7722dcd27 100644 (file)
     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)
         CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:self.ckDeviceID
                                                                        osVersion:@"fake-record"
                                                                   lastUnlockTime:[NSDate date]
+                                                                   octagonPeerID:nil
+                                                                   octagonStatus:nil
                                                                     circlePeerID:self.circlePeerID
                                                                     circleStatus:kSOSCCInCircle
                                                                         keyState:SecCKKSZoneKeyStateWaitForTLK
index a2a8c8d6d141bd95e40892080f004e50067ba7ea..32020bd370343dfc18d9d900fa7288e79aafc44d 100644 (file)
@@ -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<NSData*>*)SOSPiggyICloudIdentities;
index b82da1c019946e97e58e8ce26f10967e204db752..200b79543a19f66db65202187a9b40ffcf2b768a 100644 (file)
     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
     [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];