#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:}
- (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 : 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 : 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: }
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: 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: } else {
secinfo("octagon-sos", "Peer( }
}
// 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: 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: 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: } 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: } else {
secerror("octagon-sos: Created SPKI for 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: 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: if(error) {
*error = peerError;
}
return nil;
}
return [self peerPublicSigningKeySPKIs:peerSet];
}
}
@end
#endif // OCTAGON