2 // SOSAccountTrustClassicIdentity.m
7 #import <Foundation/Foundation.h>
8 #include <AssertMacros.h>
9 #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
10 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
11 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Identity.h"
12 #import "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
13 #import <SecurityFoundation/SFSigningOperation.h>
14 #import <SecurityFoundation/SFKey.h>
15 #import <SecurityFoundation/SFKey_Private.h>
16 #import <SecurityFoundation/SFDigestOperation.h>
18 #import "Analytics/Clients/SOSAnalytics.h"
21 #import "keychain/SecureObjectSync/SOSViews.h"
23 @implementation SOSAccountTrustClassic (Identity)
25 -(bool)isLockedError:(NSError *)error {
27 ([error.domain isEqualToString:(__bridge NSString*)kSecErrorDomain])
28 && error.code == errSecInteractionNotAllowed;
31 -(bool) updateFullPeerInfo:(SOSAccount*)account minimum:(CFSetRef)minimumViews excluded:(CFSetRef)excludedViews
33 if (self.trustedCircle && self.fullPeerInfo) {
34 if(SOSFullPeerInfoUpdateToCurrent(self.fullPeerInfo, minimumViews, excludedViews)) {
35 [self modifyCircle:account.circle_transport err:NULL action:^(SOSCircleRef circle_to_change) {
36 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
37 return SOSCircleUpdatePeerInfo(circle_to_change, self.peerInfo);
45 -(SOSFullPeerInfoRef) getMyFullPeerInfo
47 return self.trustedCircle ? self.fullPeerInfo : NULL;
50 -(bool) fullPeerInfoVerify:(SecKeyRef) privKey err:(CFErrorRef *)error
52 if(!self.fullPeerInfo) return false;
53 SecKeyRef pubKey = SecKeyCreatePublicFromPrivate(privKey);
54 bool retval = SOSPeerInfoApplicationVerify(self.peerInfo, pubKey, error);
55 CFReleaseNull(pubKey);
59 -(bool) hasFullPeerInfo:(CFErrorRef*) error
62 if(![self hasCircle:error]){
65 hasPeer = self.fullPeerInfo != NULL;
68 SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("No peer for circle"));
73 -(SOSFullPeerInfoRef) CopyAccountIdentityPeerInfo
75 return SOSFullPeerInfoCopyFullPeerInfo(self.fullPeerInfo);
78 - (SecKeyRef)randomPermanentFullECKey:(int)keysize name:(NSString *)name error:(CFErrorRef*)cferror CF_RETURNS_RETAINED
80 return GeneratePermanentFullECKey(keysize, (__bridge CFStringRef)name, cferror);
83 // Check that cached values of what is in keychain with what we have in the peer info,
84 // if they ware the same, we could read the items while this process was alive, assume
87 - (bool)haveConfirmedOctagonKeys
89 bool haveSigningKey = false;
90 bool haveEncryptionKey = false;
92 SecKeyRef signingKey = SOSFullPeerInfoCopyOctagonPublicSigningKey(self.fullPeerInfo, NULL);
93 if (self.cachedOctagonSigningKey && CFEqualSafe(signingKey, self.cachedOctagonSigningKey)) {
94 haveSigningKey = true;
96 secerror("circleChange: No extant octagon signing key");
99 SecKeyRef encrytionKey = SOSFullPeerInfoCopyOctagonPublicEncryptionKey(self.fullPeerInfo, NULL);
100 if (self.cachedOctagonEncryptionKey && CFEqualSafe(encrytionKey, self.cachedOctagonEncryptionKey)) {
101 haveEncryptionKey = true;
103 secerror("circleChange: No extant octagon encryption key");
106 CFReleaseNull(signingKey);
107 CFReleaseNull(encrytionKey);
109 return haveSigningKey && haveEncryptionKey;
113 - (void)ensureOctagonPeerKeys:(SOSKVSCircleStorageTransport*)circleTransport
116 NSString* octagonKeyName;
117 SecKeyRef octagonSigningFullKey = NULL;
118 SecKeyRef octagonEncryptionFullKey = NULL;
120 // check if we already confirmed the keys
121 if ([self haveConfirmedOctagonKeys]) {
125 bool changedSelf = false;
127 CFErrorRef copyError = NULL;
128 octagonSigningFullKey = SOSFullPeerInfoCopyOctagonSigningKey(self.fullPeerInfo, ©Error);
129 if(copyError && ![self isLockedError:(__bridge NSError *)copyError]) {
130 secerror("circleChange: Error fetching Octagon signing key: %@", copyError);
133 // Cache that public key we found, to so that we don't need to make the roundtrip though
134 // keychain to get them item, if we don't find a key, try to create a new key if the error
135 // is specifically "couldn't find key", "couldn't read key", or "something went very very wrong".
136 // Otherwise, log a fatal error.
138 if (octagonSigningFullKey) {
139 secnotice("circleChange", "Already have Octagon signing key");
140 CFReleaseNull(self->_cachedOctagonSigningKey);
141 _cachedOctagonSigningKey = SecKeyCopyPublicKey(octagonSigningFullKey);
143 // Ensure that the agrp is correct.
144 SOSCCEnsureAccessGroupOfKey(_cachedOctagonSigningKey, @"sync", (__bridge NSString*)kSOSInternalAccessGroup);
146 } else if (octagonSigningFullKey == NULL && copyError &&
147 ((CFEqualSafe(CFErrorGetDomain(copyError), kCFErrorDomainOSStatus) && CFErrorGetCode(copyError) == errSecItemNotFound) ||
148 (CFEqualSafe(CFErrorGetDomain(copyError), kCFErrorDomainOSStatus) && CFErrorGetCode(copyError) == errSecDecode) ||
149 (CFEqualSafe(CFErrorGetDomain(copyError), kCFErrorDomainOSStatus) && CFErrorGetCode(copyError) == errSecParam)))
151 octagonKeyName = [NSString stringWithFormat:@"Octagon Peer Signing ID for %@", SOSCircleGetName(self.trustedCircle)];
152 CFErrorRef cferror = NULL;
153 octagonSigningFullKey = [self randomPermanentFullECKey:384 name:octagonKeyName error:&cferror];
154 if(cferror || !octagonSigningFullKey) {
155 secerror("circleChange: Error creating Octagon signing key: %@", cferror);
157 SOSFullPeerInfoUpdateOctagonSigningKey(self.fullPeerInfo, octagonSigningFullKey, &cferror);
159 secerror("circleChange: Error upgrading Octagon signing key: %@", cferror);
161 secnotice("circleChange", "Successfully created new Octagon signing key");
166 CFReleaseNull(cferror);
167 } else if((octagonSigningFullKey == NULL || copyError) && ![self isLockedError:(__bridge NSError *)copyError]) {
168 secerror("error is too scary, not creating new Octagon signing key: %@", copyError);
170 [[SOSAnalytics logger] logResultForEvent:@"SOSCheckOctagonSigningKey" hardFailure:true result:(__bridge NSError*)copyError];
174 CFReleaseNull(copyError);
175 CFReleaseNull(octagonSigningFullKey);
177 // Now do the same thing for encryption key
179 CFReleaseNull(copyError);
180 octagonEncryptionFullKey = SOSFullPeerInfoCopyOctagonEncryptionKey(self.fullPeerInfo, ©Error);
181 if(copyError && ![self isLockedError:(__bridge NSError *)copyError]) {
182 secerror("circleChange: Error fetching Octagon encryption key: %@", copyError);
185 if (octagonEncryptionFullKey) {
186 secnotice("circleChange", "Already have Octagon encryption key");
187 CFReleaseNull(self->_cachedOctagonEncryptionKey);
188 _cachedOctagonEncryptionKey = SecKeyCopyPublicKey(octagonEncryptionFullKey);
190 SOSCCEnsureAccessGroupOfKey(_cachedOctagonEncryptionKey, @"sync", (__bridge NSString*)kSOSInternalAccessGroup);
191 } else if (octagonEncryptionFullKey == NULL && copyError &&
192 ((CFEqualSafe(CFErrorGetDomain(copyError), kCFErrorDomainOSStatus) && CFErrorGetCode(copyError) == errSecItemNotFound) ||
193 (CFEqualSafe(CFErrorGetDomain(copyError), kCFErrorDomainOSStatus) && CFErrorGetCode(copyError) == errSecDecode) ||
194 (CFEqualSafe(CFErrorGetDomain(copyError), kCFErrorDomainOSStatus) && CFErrorGetCode(copyError) == errSecParam)))
196 octagonKeyName = [NSString stringWithFormat:@"Octagon Peer Encryption ID for %@", SOSCircleGetName(self.trustedCircle)];
197 CFErrorRef cferror = NULL;
198 octagonEncryptionFullKey = [self randomPermanentFullECKey:384 name:octagonKeyName error:&cferror];
199 if(cferror || !octagonEncryptionFullKey) {
200 secerror("circleChange: Error creating Octagon encryption key: %@", cferror);
202 SOSFullPeerInfoUpdateOctagonEncryptionKey(self.fullPeerInfo, octagonEncryptionFullKey, &cferror);
204 secerror("circleChange: Error upgrading Octagon encryption key: %@", cferror);
206 secnotice("circleChange", "Successfully created new Octagon encryption key");
211 CFReleaseNull(cferror);
213 } else if((octagonEncryptionFullKey == NULL || copyError) && ![self isLockedError:(__bridge NSError *)copyError]) {
214 secerror("error is too scary, not creating new Octagon encryption key: %@", copyError);
216 [[SOSAnalytics logger] logResultForEvent:@"SOSCheckOctagonEncryptionKey" hardFailure:true result:(__bridge NSError*)copyError];
219 CFReleaseNull(copyError);
220 CFReleaseNull(octagonEncryptionFullKey);
223 [self modifyCircle:circleTransport err:NULL action:^bool (SOSCircleRef circle_to_change) {
224 return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(self.fullPeerInfo));
230 -(bool) ensureFullPeerAvailable:(SOSAccount*) account err:(CFErrorRef *) error
232 require_action_quiet(self.trustedCircle, fail, SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("Don't have circle")));
234 if (self.fullPeerInfo == NULL || !SOSFullPeerInfoPrivKeyExists(self.fullPeerInfo)) {
235 if(self.fullPeerInfo) { // fullPeerInfo where privkey is gone
236 secnotice("circleOps", "FullPeerInfo has no matching private key - resetting FPI and attendant keys");
237 CFReleaseNull(self->fullPeerInfo);
238 if(self->peerInfo) CFReleaseNull(self->peerInfo);
239 if(self->_cachedOctagonSigningKey) CFReleaseNull(self->_cachedOctagonSigningKey);
240 if(self->_cachedOctagonEncryptionKey) CFReleaseNull(self->_cachedOctagonEncryptionKey);
243 SecKeyRef fullKey = NULL;
244 bool skipInitialSync = false; // This will be set if the set of initial sync views is empty
245 NSString* octagonKeyName;
246 CFStringRef keyName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("ID for %@-%@"), SOSPeerGestaltGetName((__bridge CFDictionaryRef)(account.gestalt)), SOSCircleGetName(self.trustedCircle));
247 fullKey = [self randomPermanentFullECKey:256 name:(__bridge NSString *)keyName error:NULL];
249 octagonKeyName = [@"Octagon Peer Signing " stringByAppendingString:(__bridge NSString*)keyName];
251 SecKeyRef octagonSigningFullKey = NULL;
252 if (account.octagonSigningFullKeyRef != NULL) {
253 octagonSigningFullKey = CFRetainSafe(account.octagonSigningFullKeyRef);
255 octagonSigningFullKey = [self randomPermanentFullECKey:384 name:octagonKeyName error:NULL];
258 octagonKeyName = [@"Octagon Peer Encryption " stringByAppendingString:(__bridge NSString*)keyName];
260 SecKeyRef octagonEncryptionFullKey = NULL;
261 if (account.octagonEncryptionFullKeyRef != NULL){
262 octagonEncryptionFullKey = CFRetainSafe(account.octagonEncryptionFullKeyRef);
264 octagonEncryptionFullKey = [self randomPermanentFullECKey:384 name:octagonKeyName error:NULL];
267 if (fullKey && octagonSigningFullKey && octagonEncryptionFullKey) {
268 CFMutableSetRef initialViews = SOSViewCopyViewSet(kViewSetInitial);
269 CFMutableSetRef initialSyncDoneViews = SOSViewCopyViewSet(kViewSetAlwaysOn);
270 CFSetRef defaultViews = SOSViewCopyViewSet(kViewSetDefault);
271 CFSetRef backupViews = SOSViewCopyViewSet(kViewSetRequiredForBackup);
273 CFSetUnion(initialSyncDoneViews, defaultViews);
275 // If there are no "initialViews" then we're basically through initial sync - so setup alwaysOn and default views
276 if(CFSetGetCount(initialViews) == 0) {
277 skipInitialSync = true;
278 CFSetUnion(initialViews, initialSyncDoneViews);
280 CFSetUnion(initialViews, backupViews);
282 // setting fullPeerInfo takes an extra ref, so...
283 self.fullPeerInfo = nil;
284 SOSFullPeerInfoRef fpi = SOSFullPeerInfoCreateWithViews(kCFAllocatorDefault, (__bridge CFDictionaryRef)(account.gestalt), (__bridge CFDataRef)(account.backup_key), initialViews, fullKey, octagonSigningFullKey, octagonEncryptionFullKey, error);
285 self.fullPeerInfo = fpi;
286 SecKeyRef pubKey = SOSFullPeerInfoCopyPubKey(fpi, NULL);
287 account.peerPublicKey = pubKey;
288 CFReleaseNull(pubKey);
289 if(!account.peerPublicKey) {
290 secnotice("circleOp", "Failed to copy peer public key for account object");
294 CFDictionaryRef v2dictionaryTestUpdates = [self getValueFromExpansion:kSOSTestV2Settings err:NULL];
295 if(v2dictionaryTestUpdates) SOSFullPeerInfoUpdateV2Dictionary(self.fullPeerInfo, v2dictionaryTestUpdates, NULL);
297 if(!skipInitialSync) {
298 [self pendEnableViewSet:initialSyncDoneViews];
299 [self setValueInExpansion:kSOSUnsyncedViewsKey value:kCFBooleanTrue err:NULL];
302 CFReleaseNull(initialViews);
303 CFReleaseNull(backupViews);
304 CFReleaseNull(initialSyncDoneViews);
305 CFReleaseNull(defaultViews);
308 secerror("No full_key: %@:", error ? *error : NULL);
312 CFReleaseNull(fullKey);
313 CFReleaseNull(octagonSigningFullKey);
314 CFReleaseNull(octagonEncryptionFullKey);
315 CFReleaseNull(keyName);
319 return self.fullPeerInfo != NULL;
321 -(bool) isMyPeerActive:(CFErrorRef*) error
323 return (self.peerInfo ? SOSCircleHasActivePeer(self.trustedCircle, self.peerInfo, error) : false);
326 -(void) purgeIdentity
328 if (self.fullPeerInfo) {
329 // Purge private key but don't return error if we can't.
330 CFErrorRef purgeError = NULL;
331 if (!SOSFullPeerInfoPurgePersistentKey(self.fullPeerInfo, &purgeError)) {
332 secwarning("Couldn't purge persistent keys for %@ [%@]", self.fullPeerInfo, purgeError);
334 CFReleaseNull(purgeError);
336 self.fullPeerInfo=nil;