#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>
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
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
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();
+ });
}
_SecAssessmentCopyUpdate
_SecAssessmentControl
_SecAssessmentGetTypeID
+_SecAssessmentRegisterPackageTicket
_SecAssessmentTicketLookup
_SecAssessmentTicketRegister
_kSecAssessmentContextKeyOperation
/* 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;
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";
- (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
}
@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
#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
@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;
- (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
- (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
_osVersion = osVersion;
_lastUnlockTime = lastUnlockTime;
+ _octagonPeerID = octagonPeerID;
+ _octagonStatus = octagonStatus;
+
_circleStatus = circleStatus;
_keyState = keyState;
// 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
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
}
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];
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;
}];
CKKSDeviceStateEntry* cdse = [[CKKSDeviceStateEntry alloc] initForDevice:testUUID
osVersion:@"faux-version"
lastUnlockTime:nil
+ octagonPeerID:@"peerID"
+ octagonStatus:nil
circlePeerID:@"asdf"
circleStatus:kSOSCCInCircle
keyState:SecCKKSZoneKeyStateReady
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
- (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;
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];