OTSOSAdapter.m   [plain text]

#if OCTAGON
#import <dispatch/dispatch.h>
#import <notify.h>

#import "keychain/ot/OTSOSAdapter.h"

#import "keychain/SecureObjectSync/SOSCloudCircleInternal.h"
#include "keychain/SecureObjectSync/SOSViews.h"

#import "keychain/securityd/SOSCloudCircleServer.h"
#import "OSX/utilities/SecCFWrappers.h"

#import "keychain/categories/NSError+UsefulConstructors.h"

#import "keychain/ckks/CKKS.h"
#import "keychain/ckks/CKKSLockStateTracker.h"
#import "keychain/ckks/CKKSListenerCollection.h"
#import "keychain/SecureObjectSync/SOSAccount.h"
#import "keychain/SecureObjectSync/SOSAccountPriv.h"

#import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
#import "keychain/ot/OTDefines.h"
#import "keychain/ot/OTConstants.h"
#import "keychain/ckks/CKKSAnalytics.h"

@interface OTSOSActualAdapter ()
@property CKKSListenerCollection* peerChangeListeners;
@end

@implementation OTSOSActualAdapter
@synthesize sosEnabled;
@synthesize essential = _essential;
@synthesize providerID = _providerID;

+ (NSSet<NSString*>*)sosCKKSViewList
{
static NSSet<NSString*>* list = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
list = CFBridgingRelease(SOSViewCopyViewSet(kViewSetCKKS));
});
return list;
}

- (instancetype)initAsEssential:(BOOL)essential {
if((self = [super init])) {
self.sosEnabled = true;

_essential = essential;

_providerID = @"[OTSOSActualAdapter]";
_peerChangeListeners = [[CKKSListenerCollection alloc] initWithName:@"ckks-sos"];

__typeof(self) weakSelf = self;
// If this is a live server, register with notify
if(!SecCKKSTestsEnabled()) {
int token = 0;
notify_register_dispatch(kSOSCCCircleOctagonKeysChangedNotification, &token, dispatch_get_global_queue(QOS_CLASS_UTILITY, 0), ^(int t) {
// Since SOS doesn't change the self peer, we can reliably just send "trusted peers changed"; it'll be mostly right
secnotice("octagon-sos", "Received a notification that the SOS Octagon peer set changed");
[weakSelf sendTrustedPeerSetChangedUpdate];
});
}
}
return self;
}

- (NSString*)description
{
return [NSString stringWithFormat:@"<OTSOSActualAdapter e:%d>", self.essential];
}

- (SOSCCStatus)circleStatus:(NSError**)error
{
CFErrorRef cferror = nil;
SOSCCStatus status = SOSCCThisDeviceIsInCircle(&cferror);
if(error && cferror) {
*error = CFBridgingRelease(cferror);
} else {
CFReleaseNull(cferror);
}
return status;
}

- (id<CKKSSelfPeer> _Nullable)currentSOSSelf:(NSError**)error
{
__block SFECKeyPair* signingPrivateKey = nil;
__block SFECKeyPair* encryptionPrivateKey = nil;

__block NSError* localerror = nil;

CFErrorRef cferror = nil;

SOSCCStatus circleStatus = [self circleStatus:&localerror];
if(circleStatus != kSOSCCInCircle) {
if(!localerror) {
localerror = [NSError errorWithDomain:CKKSErrorDomain
code:CKKSNoPeersAvailable
description:@"Not in SOS circle, but no error returned"];
}
secerror("octagon-sos: Not in circle : %@ %@", SOSAccountGetSOSCCStatusString(circleStatus), localerror);
if(error) {
*error = localerror;
}
return nil;
}

SOSPeerInfoRef egoPeerInfo = SOSCCCopyMyPeerInfo(&cferror);
NSString* egoPeerID = egoPeerInfo ? (NSString*)CFBridgingRelease(CFRetainSafe(SOSPeerInfoGetPeerID(egoPeerInfo))) : nil;
CFReleaseNull(egoPeerInfo);

if(!egoPeerID || cferror) {
localerror = CFBridgingRelease(cferror);
if(!localerror) {
localerror = [NSError errorWithDomain:CKKSErrorDomain
code:CKKSNoPeersAvailable
description:@"No SOS peer info available, but no error returned"];
}

secerror("octagon-sos: Error fetching self peer : %@", cferror);
if(error) {
*error = localerror;
}
return nil;
}

SOSCCPerformWithAllOctagonKeys(^(SecKeyRef octagonEncryptionKey, SecKeyRef octagonSigningKey, CFErrorRef cferror) {
if(cferror) {
localerror = (__bridge NSError*)cferror;
return;
}
if (!cferror && octagonEncryptionKey && octagonSigningKey) {
signingPrivateKey = [[SFECKeyPair alloc] initWithSecKey:octagonSigningKey];
encryptionPrivateKey = [[SFECKeyPair alloc] initWithSecKey:octagonEncryptionKey];
} else {
localerror = [NSError errorWithDomain:CKKSErrorDomain
code:CKKSNoPeersAvailable
description:@"Not all SOS peer keys available, but no error returned"];
}
});

if(localerror) {
if(![[CKKSLockStateTracker globalTracker] isLockedError:localerror]) {
secerror("octagon-sos: Error fetching self encryption keys: %@", localerror);
}
if(error) {
*error = localerror;
}
return nil;
}

CKKSSOSSelfPeer* selfPeer = [[CKKSSOSSelfPeer alloc] initWithSOSPeerID:egoPeerID
encryptionKey:encryptionPrivateKey
signingKey:signingPrivateKey
viewList:[OTSOSActualAdapter sosCKKSViewList]];
return selfPeer;
}

- (CKKSSelves * _Nullable)fetchSelfPeers:(NSError *__autoreleasing _Nullable * _Nullable)error {
id<CKKSSelfPeer> peer = [self currentSOSSelf:error];
if(!peer) {
return nil;
}

return [[CKKSSelves alloc] initWithCurrent:peer allSelves:nil];
}

- (NSSet<id<CKKSRemotePeerProtocol>>* _Nullable)fetchTrustedPeers:(NSError**)error
{
__block NSMutableSet<id<CKKSRemotePeerProtocol>>* peerSet = [NSMutableSet set];

__block NSError* localError = nil;

SOSCCPerformWithTrustedPeers(^(CFSetRef sosPeerInfoRefs, CFErrorRef cfTrustedPeersError) {
if(cfTrustedPeersError) {
secerror("octagon-sos: Error fetching trusted peers: %@", cfTrustedPeersError);
if(localError) {
localError = (__bridge NSError*)cfTrustedPeersError;
}
}

CFSetForEach(sosPeerInfoRefs, ^(const void* voidPeer) {
CFErrorRef cfPeerError = NULL;
SOSPeerInfoRef sosPeerInfoRef = (SOSPeerInfoRef)voidPeer;

if(!sosPeerInfoRef) {
return;
}

CFStringRef cfpeerID = SOSPeerInfoGetPeerID(sosPeerInfoRef);
SecKeyRef cfOctagonSigningKey = NULL, cfOctagonEncryptionKey = NULL;

cfOctagonSigningKey = SOSPeerInfoCopyOctagonSigningPublicKey(sosPeerInfoRef, &cfPeerError);
if (cfOctagonSigningKey) {
cfOctagonEncryptionKey = SOSPeerInfoCopyOctagonEncryptionPublicKey(sosPeerInfoRef, &cfPeerError);
}

if(cfOctagonSigningKey == NULL || cfOctagonEncryptionKey == NULL) {
// Don't log non-debug for -50; it almost always just means this peer didn't have octagon keys
if(cfPeerError == NULL
|| !(CFEqualSafe(CFErrorGetDomain(cfPeerError), kCFErrorDomainOSStatus) && (CFErrorGetCode(cfPeerError) == errSecParam)))
{
secerror("octagon-sos: error fetching octagon keys for peer: %@ %@", sosPeerInfoRef, cfPeerError);
} else {
secinfo("octagon-sos", "Peer(%@) doesn't have Octagon keys, but this is expected: %@", cfpeerID, cfPeerError);
}
}

// Add all peers to the trust set: old-style SOS peers will just have null keys
SFECPublicKey* signingPublicKey = cfOctagonSigningKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonSigningKey] : nil;
SFECPublicKey* encryptionPublicKey = cfOctagonEncryptionKey ? [[SFECPublicKey alloc] initWithSecKey:cfOctagonEncryptionKey] : nil;

CKKSSOSPeer* peer = [[CKKSSOSPeer alloc] initWithSOSPeerID:(__bridge NSString*)cfpeerID
encryptionPublicKey:encryptionPublicKey
signingPublicKey:signingPublicKey
viewList:[OTSOSActualAdapter sosCKKSViewList]];
[peerSet addObject:peer];

CFReleaseNull(cfOctagonSigningKey);
CFReleaseNull(cfOctagonEncryptionKey);
CFReleaseNull(cfPeerError);
});
});

if(error && localError) {
*error = localError;
}

return peerSet;
}

- (BOOL)preloadOctagonKeySetOnAccount:(id<CKKSSelfPeer>)currentSelfPeer error:(NSError**)error {
// in case we don't have the keys, don't try to update them
if (currentSelfPeer.publicSigningKey.secKey == NULL || currentSelfPeer.publicEncryptionKey.secKey == NULL) {
secnotice("octagon-preload-keys", "no octagon keys available skipping updating SOS record");
return YES;
}

__block CFDataRef signingFullKey = CFBridgingRetain(currentSelfPeer.signingKey.keyData);
__block CFDataRef encryptionFullKey = CFBridgingRetain(currentSelfPeer.encryptionKey.keyData);
__block SecKeyRef octagonSigningPubSecKey = CFRetainSafe(currentSelfPeer.publicSigningKey.secKey);
__block SecKeyRef octagonEncryptionPubSecKey = CFRetainSafe(currentSelfPeer.publicEncryptionKey.secKey);
__block SecKeyRef signingFullKeyRef = CFRetainSafe(currentSelfPeer.signingKey.secKey);
__block SecKeyRef encryptionFullKeyRef = CFRetainSafe(currentSelfPeer.encryptionKey.secKey);

SFAnalyticsActivityTracker *tracker = [[[CKKSAnalytics class] logger] startLogSystemMetricsForActivityNamed:OctagonSOSAdapterUpdateKeys];

__block BOOL ret;
__block NSError *localError = nil;

/* WARNING! Please be very very careful passing keys to this routine. Note the slightly different variations of keys*/
SOSCCPerformPreloadOfAllOctagonKeys(signingFullKey, encryptionFullKey,
signingFullKeyRef, encryptionFullKeyRef,
octagonSigningPubSecKey, octagonEncryptionPubSecKey,
^(CFErrorRef cferror) {
[tracker stopWithEvent: OctagonSOSAdapterUpdateKeys result:(__bridge NSError * _Nullable)(cferror)];
if(cferror) {
secerror("octagon-preload-keys: failed to preload Octagon keys in SOS:%@", cferror);
ret = NO;
localError = (__bridge NSError *)cferror;
} else {
ret = YES;
secnotice("octagon-preload-keys", "successfully preloaded Octagon keys in SOS!");
}
CFRelease(signingFullKey);
CFRelease(encryptionFullKey);
CFRelease(octagonSigningPubSecKey);
CFRelease(octagonEncryptionPubSecKey);
CFRelease(signingFullKeyRef);
CFRelease(encryptionFullKeyRef);
});
if (error) {
*error = localError;
}
return ret;

}

- (BOOL)updateOctagonKeySetWithAccount:(id<CKKSSelfPeer>)currentSelfPeer error:(NSError**)error {

// in case we don't have the keys, don't try to update them
if (currentSelfPeer.publicSigningKey.secKey == NULL || currentSelfPeer.publicEncryptionKey.secKey == NULL) {
secnotice("octagon-sos", "no octagon keys available skipping updating SOS record");
return YES;
}

__block CFDataRef signingFullKey = CFBridgingRetain(currentSelfPeer.signingKey.keyData);
__block CFDataRef encryptionFullKey = CFBridgingRetain(currentSelfPeer.encryptionKey.keyData);
__block CFDataRef signingPublicKey = CFBridgingRetain(currentSelfPeer.publicSigningKey.keyData);
__block CFDataRef encryptionPublicKey = CFBridgingRetain(currentSelfPeer.publicEncryptionKey.keyData);
__block SecKeyRef octagonSigningPubSecKey = CFRetainSafe(currentSelfPeer.publicSigningKey.secKey);
__block SecKeyRef octagonEncryptionPubSecKey = CFRetainSafe(currentSelfPeer.publicEncryptionKey.secKey);

SFAnalyticsActivityTracker *tracker = [[[CKKSAnalytics class] logger] startLogSystemMetricsForActivityNamed:OctagonSOSAdapterUpdateKeys];

__block BOOL ret;
__block NSError *localError = nil;

/* WARNING! Please be very very careful passing keys to this routine. Note the slightly different variations of keys*/
SOSCCPerformUpdateOfAllOctagonKeys(signingFullKey, encryptionFullKey,
signingPublicKey, encryptionPublicKey,
octagonSigningPubSecKey, octagonEncryptionPubSecKey,
^(CFErrorRef cferror) {
[tracker stopWithEvent: OctagonSOSAdapterUpdateKeys result:(__bridge NSError * _Nullable)(cferror)];
if(cferror) {
secerror("octagon-sos: failed to update Octagon keys in SOS:%@", cferror);
ret = NO;
localError = (__bridge NSError *)cferror;
} else {
ret = YES;
secnotice("octagon-sos", "successfully updated Octagon keys in SOS!");
}
CFRelease(signingFullKey);
CFRelease(encryptionFullKey);
CFRelease(signingPublicKey);
CFRelease(encryptionPublicKey);
CFRelease(octagonSigningPubSecKey);
CFRelease(octagonEncryptionPubSecKey);
});
if (error) {
*error = localError;
}
return ret;
}

- (BOOL)updateCKKS4AllStatus:(BOOL)status error:(NSError**)error
{
CFErrorRef cferror = nil;
bool result = SOSCCSetCKKS4AllStatus(status, &cferror);

NSError* localerror = CFBridgingRelease(cferror);

if(!result || localerror) {
secerror("octagon-sos: failed to update CKKS4All status in SOS: %@", localerror);
} else {
secnotice("octagon-sos", "successfully updated CKKS4All status in SOS to '%@'", status ? @"supported" : @"not supported");
}

if(error && localerror) {
*error = localerror;
}
return result;
}

- (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener {
[self.peerChangeListeners registerListener:listener];
}

- (void)sendSelfPeerChangedUpdate {
[self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
[listener selfPeerChanged: self];
}];
}

- (void)sendTrustedPeerSetChangedUpdate {
[self.peerChangeListeners iterateListeners: ^(id<CKKSPeerUpdateListener> listener) {
[listener trustedPeerSetChanged: self];
}];
}

- (nonnull CKKSPeerProviderState *)currentState {
__block CKKSPeerProviderState* result = nil;

[SOSAccount performOnQuietAccountQueue: ^{
NSError* selfPeersError = nil;
CKKSSelves* currentSelfPeers = [self fetchSelfPeers:&selfPeersError];

NSError* trustedPeersError = nil;
NSSet<id<CKKSRemotePeerProtocol>>* currentTrustedPeers = [self fetchTrustedPeers:&trustedPeersError];

result = [[CKKSPeerProviderState alloc] initWithPeerProviderID:self.providerID
essential:self.essential
selfPeers:currentSelfPeers
selfPeersError:selfPeersError
trustedPeers:currentTrustedPeers
trustedPeersError:trustedPeersError];
}];

return result;
}

- (BOOL)safariViewSyncingEnabled:(NSError**)error
{
CFErrorRef viewCFError = NULL;
SOSViewResultCode result = SOSCCView(kSOSViewAutofillPasswords, kSOSCCViewQuery, &viewCFError);

if(viewCFError) {
if(error) {
*error = CFBridgingRelease(viewCFError);
} else {
CFReleaseNull(viewCFError);
}
return NO;
}

return result == kSOSCCViewMember;
}
@end

@implementation OTSOSMissingAdapter
@synthesize sosEnabled;
@synthesize providerID = _providerID;
@synthesize essential = _essential;

- (instancetype)init {
if((self = [super init])) {
self.sosEnabled = false;
_providerID = @"[OTSOSMissingAdapter]";

// This adapter is never going to return anything, so you probably shouldn't ever consider it Must Succeed
_essential = NO;
}
return self;
}

- (SOSCCStatus)circleStatus:(NSError**)error
{
return kSOSCCNotInCircle;
}

- (id<CKKSSelfPeer> _Nullable)currentSOSSelf:(NSError**)error
{
if(error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
}
return nil;
}

- (NSSet<id<CKKSRemotePeerProtocol>>* _Nullable)fetchTrustedPeers:(NSError**)error
{
if(error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
}
return nil;
}

- (BOOL)updateOctagonKeySetWithAccount:(nonnull id<CKKSSelfPeer>)currentSelfPeer error:(NSError *__autoreleasing _Nullable * _Nullable)error {
if(error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
}
return NO;
}

- (BOOL)updateCKKS4AllStatus:(BOOL)status error:(NSError**)error
{
if(error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
}
return NO;
}

- (CKKSSelves * _Nullable)fetchSelfPeers:(NSError * _Nullable __autoreleasing * _Nullable)error
{
if(error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
}
return nil;
}

- (void)registerForPeerChangeUpdates:(nonnull id<CKKSPeerUpdateListener>)listener
{
// no op
}

- (void)sendSelfPeerChangedUpdate
{
// no op
}

- (void)sendTrustedPeerSetChangedUpdate
{
// no op
}

- (nonnull CKKSPeerProviderState *)currentState {
NSError* unimplementedError = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
return [[CKKSPeerProviderState alloc] initWithPeerProviderID:self.providerID
essential:self.essential
selfPeers:nil
selfPeersError:unimplementedError
trustedPeers:nil
trustedPeersError:unimplementedError];
}

- (BOOL)safariViewSyncingEnabled:(NSError**)error
{
return NO;
}

- (BOOL)preloadOctagonKeySetOnAccount:(nonnull id<CKKSSelfPeer>)currentSelfPeer error:(NSError *__autoreleasing _Nullable * _Nullable)error {
if(error) {
*error = [NSError errorWithDomain:NSOSStatusErrorDomain
code:errSecUnimplemented
description:@"SOS unsupported on this platform"];
}
return NO;
}

@end

@implementation OTSOSAdapterHelpers

+ (NSArray<NSData*>*)peerPublicSigningKeySPKIs:(NSSet<id<CKKSPeer>>* _Nullable)peerSet
{
NSMutableArray<NSData*>* publicSigningSPKIs = [NSMutableArray array];

for(id<CKKSPeer> peer in peerSet) {
NSData* spki = [peer.publicSigningKey encodeSubjectPublicKeyInfo];
if(!spki) {
secerror("octagon-sos: Can't create SPKI for peer: %@", peer);
} else {
secerror("octagon-sos: Created SPKI for peer: %@", peer);
[publicSigningSPKIs addObject:spki];
}
}
return publicSigningSPKIs;
}

+ (NSArray<NSData*>* _Nullable)peerPublicSigningKeySPKIsForCircle:(id<OTSOSAdapter>)sosAdapter error:(NSError**)error
{
NSError* peerError = nil;
SOSCCStatus sosStatus = [sosAdapter circleStatus:&peerError];

if(sosStatus != kSOSCCInCircle || peerError) {
secerror("octagon-sos: Not in circle; not preapproving keys: %@ (%@)", SOSAccountGetSOSCCStatusString(sosStatus), peerError);
if(error) {
*error = peerError;
}
return nil;
} else {
// We're in-circle: preapprove these peers
NSError* peerError = nil;

NSSet<id<CKKSRemotePeerProtocol>>* peerSet = [sosAdapter fetchTrustedPeers:&peerError];

if(!peerSet || peerError) {
secerror("octagon-sos: Can't fetch trusted peer SPKIs: %@", peerError);
if(error) {
*error = peerError;
}
return nil;
}

return [self peerPublicSigningKeySPKIs:peerSet];
}
}
@end
#endif // OCTAGON