CKKSCurrentKeyPointer.m [plain text]
/*
* Copyright (c) 2017 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#import "CKKSCurrentKeyPointer.h"
#if OCTAGON
#import "keychain/ckks/CKKSStates.h"
#import "keychain/categories/NSError+UsefulConstructors.h"
@implementation CKKSCurrentKeyPointer
- (instancetype)initForClass:(CKKSKeyClass*)keyclass
currentKeyUUID:(NSString*)currentKeyUUID
zoneID:(CKRecordZoneID*)zoneID
encodedCKRecord: (NSData*) encodedrecord
{
if(self = [super initWithCKRecordType: SecCKRecordCurrentKeyType encodedCKRecord:encodedrecord zoneID:zoneID]) {
_keyclass = keyclass;
_currentKeyUUID = currentKeyUUID;
if(self.currentKeyUUID == nil) {
ckkserror_global("currentkey", "created a CKKSCurrentKey with a nil currentKeyUUID. Why?");
}
}
return self;
}
- (NSString*)description {
return [NSString stringWithFormat:@"<CKKSCurrentKeyPointer(}
- (instancetype)copyWithZone:(NSZone*)zone {
CKKSCurrentKeyPointer* copy = [super copyWithZone:zone];
copy.keyclass = [self.keyclass copyWithZone:zone];
copy.currentKeyUUID = [self.currentKeyUUID copyWithZone:zone];
return copy;
}
- (BOOL)isEqual: (id) object {
if(![object isKindOfClass:[CKKSCurrentKeyPointer class]]) {
return NO;
}
CKKSCurrentKeyPointer* obj = (CKKSCurrentKeyPointer*) object;
return ([self.zoneID isEqual: obj.zoneID] &&
((self.currentKeyUUID == nil && obj.currentKeyUUID == nil) || [self.currentKeyUUID isEqual: obj.currentKeyUUID]) &&
((self.keyclass == nil && obj.keyclass == nil) || [self.keyclass isEqual:obj.keyclass]) &&
YES) ? YES : NO;
}
#pragma mark - CKKSCKRecordHolder methods
- (NSString*) CKRecordName {
return self.keyclass;
}
- (CKRecord*) updateCKRecord: (CKRecord*) record zoneID: (CKRecordZoneID*) zoneID {
if(![record.recordType isEqualToString: SecCKRecordCurrentKeyType]) {
@throw [NSException
exceptionWithName:@"WrongCKRecordTypeException"
reason:[NSString stringWithFormat: @"CKRecordType ( userInfo:nil];
}
// The record name should already match keyclass...
if(![record.recordID.recordName isEqualToString: self.keyclass]) {
@throw [NSException
exceptionWithName:@"WrongCKRecordNameException"
reason:[NSString stringWithFormat: @"CKRecord name ( userInfo:nil];
}
// Set the parent reference
record[SecCKRecordParentKeyRefKey] = [[CKReference alloc] initWithRecordID: [[CKRecordID alloc] initWithRecordName: self.currentKeyUUID zoneID: zoneID] action: CKReferenceActionNone];
return record;
}
- (bool) matchesCKRecord: (CKRecord*) record {
if(![record.recordType isEqualToString: SecCKRecordCurrentKeyType]) {
return false;
}
if(![record.recordID.recordName isEqualToString: self.keyclass]) {
return false;
}
if(![[record[SecCKRecordParentKeyRefKey] recordID].recordName isEqualToString: self.currentKeyUUID]) {
return false;
}
return true;
}
- (void) setFromCKRecord: (CKRecord*) record {
if(![record.recordType isEqualToString: SecCKRecordCurrentKeyType]) {
@throw [NSException
exceptionWithName:@"WrongCKRecordTypeException"
reason:[NSString stringWithFormat: @"CKRecordType ( userInfo:nil];
}
[self setStoredCKRecord:record];
// TODO: verify this is a real keyclass
self.keyclass = (CKKSKeyClass*) record.recordID.recordName;
self.currentKeyUUID = [record[SecCKRecordParentKeyRefKey] recordID].recordName;
if(self.currentKeyUUID == nil) {
ckkserror_global("currentkey", "No current key UUID in record! How/why? }
}
#pragma mark - Load from database
+ (instancetype _Nullable)fromDatabase:(CKKSKeyClass*)keyclass zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error
{
return [self fromDatabaseWhere: @{@"keyclass": keyclass, @"ckzone":zoneID.zoneName} error: error];
}
+ (instancetype _Nullable)tryFromDatabase:(CKKSKeyClass*)keyclass zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error
{
return [self tryFromDatabaseWhere: @{@"keyclass": keyclass, @"ckzone":zoneID.zoneName} error: error];
}
+ (instancetype _Nullable)forKeyClass:(CKKSKeyClass*)keyclass withKeyUUID:(NSString*)keyUUID zoneID:(CKRecordZoneID*)zoneID error:(NSError * __autoreleasing *)error
{
NSError* localerror = nil;
CKKSCurrentKeyPointer* current = [self tryFromDatabase: keyclass zoneID:zoneID error: &localerror];
if(localerror) {
if(error) {
*error = localerror;
}
return nil;
}
if(current) {
current.currentKeyUUID = keyUUID;
return current;
}
return [[CKKSCurrentKeyPointer alloc] initForClass: keyclass currentKeyUUID: keyUUID zoneID:zoneID encodedCKRecord:nil];
}
+ (NSArray<CKKSCurrentKeyPointer*>*)all:(CKRecordZoneID*)zoneID error: (NSError * __autoreleasing *) error {
return [self allWhere:@{@"ckzone":zoneID.zoneName} error:error];
}
+ (bool) deleteAll:(CKRecordZoneID*) zoneID error: (NSError * __autoreleasing *) error {
bool ok = [CKKSSQLDatabaseObject deleteFromTable:[self sqlTable] where: @{@"ckzone":zoneID.zoneName} connection:nil error: error];
if(ok) {
secdebug("ckksitem", "Deleted all } else {
secdebug("ckksitem", "Couldn't delete all }
return ok;
}
#pragma mark - CKKSSQLDatabaseObject methods
+ (NSString*) sqlTable {
return @"currentkeys";
}
+ (NSArray<NSString*>*) sqlColumns {
return @[@"keyclass", @"currentKeyUUID", @"ckzone", @"ckrecord"];
}
- (NSDictionary<NSString*,NSString*>*) whereClauseToFindSelf {
return @{@"keyclass": self.keyclass, @"ckzone":self.zoneID.zoneName};
}
- (NSDictionary<NSString*,NSString*>*) sqlValues {
return @{@"keyclass": self.keyclass,
@"currentKeyUUID": CKKSNilToNSNull(self.currentKeyUUID),
@"ckzone": CKKSNilToNSNull(self.zoneID.zoneName),
@"ckrecord": CKKSNilToNSNull([self.encodedCKRecord base64EncodedStringWithOptions:0]),
};
}
+ (instancetype)fromDatabaseRow:(NSDictionary<NSString*, CKKSSQLResult*>*)row {
return [[CKKSCurrentKeyPointer alloc] initForClass:(CKKSKeyClass*)row[@"keyclass"].asString
currentKeyUUID:row[@"currentKeyUUID"].asString
zoneID:[[CKRecordZoneID alloc] initWithZoneName:row[@"ckzone"].asString ownerName:CKCurrentUserDefaultName]
encodedCKRecord:row[@"ckrecord"].asBase64DecodedData];
}
+ (BOOL)intransactionRecordChanged:(CKRecord*)record
resync:(BOOL)resync
flagHandler:(id<OctagonStateFlagHandler>)flagHandler
error:(NSError**)error
{
// Pull out the old CKP, if it exists
NSError* ckperror = nil;
CKKSCurrentKeyPointer* oldckp = [CKKSCurrentKeyPointer tryFromDatabase:((CKKSKeyClass*)record.recordID.recordName) zoneID:record.recordID.zoneID error:&ckperror];
if(ckperror) {
ckkserror("ckkskey", record.recordID.zoneID, "error loading ckp: }
if(resync) {
if(!oldckp) {
ckkserror("ckksresync", record.recordID.zoneID, "BUG: No current key pointer matching resynced CloudKit record: } else if(![oldckp matchesCKRecord:record]) {
ckkserror("ckksresync", record.recordID.zoneID, "BUG: Local current key pointer doesn't match resynced CloudKit record: } else {
ckksnotice("ckksresync", record.recordID.zoneID, "Current key pointer has 'changed', but it matches our local copy: }
}
NSError* localerror = nil;
CKKSCurrentKeyPointer* currentkey = [[CKKSCurrentKeyPointer alloc] initWithCKRecord:record];
bool saved = [currentkey saveToDatabase:&localerror];
if(!saved || localerror != nil) {
ckkserror("ckkskey", record.recordID.zoneID, "Couldn't save current key pointer to database: ckksinfo("ckkskey", record.recordID.zoneID, "CKRecord was
if(error) {
*error = localerror;
}
return NO;
}
if([oldckp matchesCKRecord:record]) {
ckksnotice("ckkskey", record.recordID.zoneID, "Current key pointer modification doesn't change anything interesting; skipping reprocess: } else {
// We've saved a new key in the database; trigger a rekey operation.
[flagHandler _onqueueHandleFlag:CKKSFlagKeyStateProcessRequested];
}
return YES;
}
@end
@implementation CKKSCurrentKeySet
-(instancetype)initForZoneName:(NSString*)zoneName {
if((self = [super init])) {
_viewName = zoneName;
}
return self;
}
+ (CKKSCurrentKeySet*)loadForZone:(CKRecordZoneID*)zoneID
{
CKKSCurrentKeySet* set = [[CKKSCurrentKeySet alloc] initForZoneName:zoneID.zoneName];
NSError* error = nil;
set.currentTLKPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassTLK zoneID:zoneID error:&error];
set.currentClassAPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassA zoneID:zoneID error:&error];
set.currentClassCPointer = [CKKSCurrentKeyPointer tryFromDatabase: SecCKKSKeyClassC zoneID:zoneID error:&error];
set.tlk = set.currentTLKPointer.currentKeyUUID ? [CKKSKey tryFromDatabase:set.currentTLKPointer.currentKeyUUID zoneID:zoneID error:&error] : nil;
set.classA = set.currentClassAPointer.currentKeyUUID ? [CKKSKey tryFromDatabase:set.currentClassAPointer.currentKeyUUID zoneID:zoneID error:&error] : nil;
set.classC = set.currentClassCPointer.currentKeyUUID ? [CKKSKey tryFromDatabase:set.currentClassCPointer.currentKeyUUID zoneID:zoneID error:&error] : nil;
set.tlkShares = [CKKSTLKShareRecord allForUUID:set.currentTLKPointer.currentKeyUUID zoneID:zoneID error:&error];
set.pendingTLKShares = nil;
set.proposed = NO;
set.error = error;
return set;
}
-(NSString*)description {
if(self.error) {
return [NSString stringWithFormat:@"<CKKSCurrentKeySet( self.viewName,
self.currentTLKPointer.currentKeyUUID, self.tlk,
self.currentClassAPointer.currentKeyUUID, self.classA,
self.currentClassCPointer.currentKeyUUID, self.classC,
self.proposed,
self.error];
} else {
return [NSString stringWithFormat:@"<CKKSCurrentKeySet( self.viewName,
self.currentTLKPointer.currentKeyUUID, self.tlk,
self.currentClassAPointer.currentKeyUUID, self.classA,
self.currentClassCPointer.currentKeyUUID, self.classC,
self.proposed];
}
}
- (instancetype)copyWithZone:(NSZone*)zone {
CKKSCurrentKeySet* copy = [[[self class] alloc] init];
copy.currentTLKPointer = [self.currentTLKPointer copyWithZone:zone];
copy.currentClassAPointer = [self.currentClassAPointer copyWithZone:zone];
copy.currentClassCPointer = [self.currentClassCPointer copyWithZone:zone];
copy.tlk = [self.tlk copyWithZone:zone];
copy.classA = [self.classA copyWithZone:zone];
copy.classC = [self.classC copyWithZone:zone];
copy.proposed = self.proposed;
copy.error = [self.error copyWithZone:zone];
return copy;
}
- (CKKSKeychainBackedKeySet* _Nullable)asKeychainBackedSet:(NSError**)error
{
if(!self.tlk.keycore ||
!self.classA.keycore ||
!self.classC.keycore) {
if(error) {
*error = [NSError errorWithDomain:CKKSErrorDomain
code:CKKSKeysMissing
description:@"unable to make keychain backed set; key is missing"];
}
return nil;
}
return [[CKKSKeychainBackedKeySet alloc] initWithTLK:self.tlk.keycore
classA:self.classA.keycore
classC:self.classC.keycore
newUpload:self.proposed];
}
@end
#endif // OCTAGON