OTPrepareOperation.m   [plain text]

/*
* Copyright (c) 2018 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@
*/

#if OCTAGON

#import "utilities/debugging.h"
#import <Security/SecKey.h>
#import <Security/SecKeyPriv.h>

#import "keychain/ot/OTCuttlefishContext.h"
#import "keychain/ot/OTOperationDependencies.h"
#import "keychain/ot/OTPrepareOperation.h"

#import "keychain/TrustedPeersHelper/TrustedPeersHelperProtocol.h"
#import "keychain/ot/ObjCImprovements.h"

@interface OTPrepareOperation ()
@property OTOperationDependencies* deps;
@property NSOperation* finishedOp;
@end

@implementation OTPrepareOperation
@synthesize intendedState = _intendedState;
@synthesize nextState = _nextState;

- (instancetype)initWithDependencies:(OTOperationDependencies*)dependencies
intendedState:(OctagonState*)intendedState
errorState:(OctagonState*)errorState
deviceInfo:(OTDeviceInformation*)deviceInfo
policyOverride:(TPPolicyVersion* _Nullable)policyOverride
epoch:(uint64_t)epoch
{
if((self = [super init])) {
_deps = dependencies;

_deviceInfo = deviceInfo;
_epoch = epoch;

_intendedState = intendedState;
_nextState = errorState;

_policyOverride = policyOverride;
}
return self;
}

- (void)groupStart
{
secnotice("octagon", "preparing an identity");

self.finishedOp = [[NSOperation alloc] init];
[self dependOnBeforeGroupFinished:self.finishedOp];

NSString* bottleSalt = nil;

NSError *authKitError = nil;
NSString *altDSID = [self.deps.authKitAdapter primaryiCloudAccountAltDSID:&authKitError];

if (altDSID) {
bottleSalt = altDSID;
}
else {
secnotice("octagon-sos", "AuthKit doesn't know about the altDSID: %@", authKitError);

NSError* accountError = nil;
OTAccountMetadataClassC* account = [self.deps.stateHolder loadOrCreateAccountMetadata:&accountError];

if(account && !accountError) {
secnotice("octagon", "retrieved account, altdsid is: %@", account.altDSID);
bottleSalt = account.altDSID;
}
if(accountError || !account){
secerror("failed to rerieve account object: %@", accountError);
}
}
WEAKIFY(self);

// But, if this device is SOS-enabled and SOS is present, use the SOS octagon keys (if present)
NSData* signingKeyPersistRef = nil;
NSData* encryptionKeyPersistRef = nil;
if(self.deps.sosAdapter.sosEnabled) {
secnotice("octagon-sos", "Investigating use of Octagon keys from SOS identity");

NSError* error = nil;
id<CKKSSelfPeer> sosSelf = [self.deps.sosAdapter currentSOSSelf:&error];

if(!sosSelf || error) {
secnotice("octagon-sos", "Failed to get the current SOS self: %@", error);
} else {
// Fetch the persistent references for our signing and encryption keys
OSStatus status = errSecSuccess;
CFDataRef cfSigningKeyPersistRef = NULL;
status = SecKeyCopyPersistentRef(sosSelf.signingKey.secKey, &cfSigningKeyPersistRef);
if(status != errSecSuccess || !cfSigningKeyPersistRef) {
secnotice("octagon-sos", "Failed to get the persistent ref for our SOS signing key: %d", (int)status);
} else {
CFDataRef cfEncryptionKeyPersistRef = NULL;
status = SecKeyCopyPersistentRef(sosSelf.encryptionKey.secKey, &cfEncryptionKeyPersistRef);
if(status != errSecSuccess || !cfEncryptionKeyPersistRef) {
secnotice("octagon-sos", "Failed to get the persistent ref for our SOS encryption key: %d", (int)status);
CFReleaseNull(cfSigningKeyPersistRef);
CFReleaseNull(cfEncryptionKeyPersistRef);
} else {
// We only want to use these keys if we successfully have both
signingKeyPersistRef = CFBridgingRelease(cfSigningKeyPersistRef);
encryptionKeyPersistRef = CFBridgingRelease(cfEncryptionKeyPersistRef);
}
}
}
}

NSError* persistError = nil;
BOOL persisted = [self.deps.stateHolder persistOctagonJoinAttempt:OTAccountMetadataClassC_AttemptedAJoinState_ATTEMPTED error:&persistError];
if(!persisted || persistError) {
secerror("octagon: failed to save 'attempted join' state: %@", persistError);
}

[self.deps.cuttlefishXPCWrapper prepareWithContainer:self.deps.containerName
context:self.deps.contextID
epoch:self.epoch
machineID:self.deviceInfo.machineID
bottleSalt:bottleSalt
bottleID:[NSUUID UUID].UUIDString
modelID:self.deviceInfo.modelID
deviceName:self.deviceInfo.deviceName
serialNumber:self.deviceInfo.serialNumber
osVersion:self.deviceInfo.osVersion
policyVersion:self.policyOverride
policySecrets:nil
signingPrivKeyPersistentRef:signingKeyPersistRef
encPrivKeyPersistentRef:encryptionKeyPersistRef
reply:^(NSString * _Nullable peerID,
NSData * _Nullable permanentInfo,
NSData * _Nullable permanentInfoSig,
NSData * _Nullable stableInfo,
NSData * _Nullable stableInfoSig,
NSSet<NSString*>* _Nullable syncingViews,
TPPolicy* _Nullable syncingPolicy,
NSError * _Nullable error) {
STRONGIFY(self);
[[CKKSAnalytics logger] logResultForEvent:OctagonEventPrepareIdentity hardFailure:true result:error];
if(error) {
secerror("octagon: Error preparing identity: %@", error);
self.error = error;
[self runBeforeGroupFinished:self.finishedOp];
} else {
secnotice("octagon", "Prepared: %@ %@ %@", peerID, permanentInfo, permanentInfoSig);
self.peerID = peerID;
self.permanentInfo = permanentInfo;
self.permanentInfoSig = permanentInfoSig;
self.stableInfo = stableInfo;
self.stableInfoSig = stableInfoSig;

NSError* localError = nil;

secnotice("octagon-ckks", "New syncing policy: %@ views: %@", syncingPolicy, syncingViews);

BOOL persisted = [self.deps.stateHolder persistAccountChanges:^OTAccountMetadataClassC * _Nullable(OTAccountMetadataClassC * _Nonnull metadata) {
metadata.peerID = peerID;
metadata.syncingViews = [syncingViews mutableCopy];
[metadata setTPPolicy:syncingPolicy];

return metadata;
} error:&localError];

if(!persisted || localError) {
secnotice("octagon", "Couldn't persist metadata: %@", localError);
self.error = localError;
[self runBeforeGroupFinished:self.finishedOp];
return;
}

// Let CKKS know of the new policy, so it can spin up
[self.deps.viewManager setSyncingViews:syncingViews sortingPolicy:syncingPolicy];

self.nextState = self.intendedState;
[self runBeforeGroupFinished:self.finishedOp];
}
}];
}

@end

#endif // OCTAGON