/ keychain / SecureObjectSync / SOSAccountFullPeerInfo.m
SOSAccountFullPeerInfo.m
  1  /*
  2   * Copyright (c) 2013-2014 Apple Inc. All Rights Reserved.
  3   *
  4   * @APPLE_LICENSE_HEADER_START@
  5   * 
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   * 
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   *
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  #include <AssertMacros.h>
 25  
 26  #include <stdio.h>
 27  #include "SOSAccountPriv.h"
 28  #include "keychain/SecureObjectSync/SOSInternal.h"
 29  #include "SOSViews.h"
 30  #include "SOSPeerInfoV2.h"
 31  #include "SOSPeerInfoPriv.h"
 32  #import "keychain/SecureObjectSync/SOSAccountPriv.h"
 33  #import "keychain/SecureObjectSync/SOSAccountTrust.h"
 34  #import "keychain/SecureObjectSync/SOSAccountTrustClassic.h"
 35  #include "keychain/SecureObjectSync/SOSAccountTrustClassic+Circle.h"
 36  #include "keychain/SecureObjectSync/SOSAccountTrustClassic+Expansion.h"
 37  
 38  static CFStringRef kicloud_identity_name = CFSTR("Cloud Identity");
 39  
 40  SecKeyRef SOSAccountCopyDeviceKey(SOSAccount* account, CFErrorRef *error) {
 41      SecKeyRef privateKey = NULL;
 42      if(account.peerPublicKey) {
 43          privateKey = SecKeyCopyMatchingPrivateKey(account.peerPublicKey, error);
 44      } else {
 45          SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No identity to get key from"));
 46      }
 47      return privateKey;
 48  }
 49  
 50  SOSFullPeerInfoRef CopyCloudKeychainIdentity(SOSPeerInfoRef cloudPeer, CFErrorRef *error) {
 51      return SOSFullPeerInfoCreateCloudIdentity(NULL, cloudPeer, error);
 52  }
 53  
 54  
 55  static CF_RETURNS_RETAINED SecKeyRef GeneratePermanentFullECKey_internal(int keySize, CFStringRef name, CFTypeRef accessibility, CFBooleanRef sync,  CFErrorRef* error)
 56  {
 57      SecKeyRef full_key = NULL;
 58  
 59      CFNumberRef key_size_num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &keySize);
 60  
 61      CFDictionaryRef priv_key_attrs = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
 62                                                                    kSecAttrIsPermanent,    kCFBooleanTrue,
 63                                                                    NULL);
 64  
 65      CFDictionaryRef keygen_parameters = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
 66                                                                       kSecAttrKeyType,        kSecAttrKeyTypeEC,
 67                                                                       kSecAttrKeySizeInBits,  key_size_num,
 68                                                                       kSecPrivateKeyAttrs,    priv_key_attrs,
 69                                                                       kSecAttrAccessible,     accessibility,
 70                                                                       kSecAttrAccessGroup,    kSOSInternalAccessGroup,
 71                                                                       kSecAttrLabel,          name,
 72                                                                       kSecAttrSynchronizable, sync,
 73                                                                       kSecUseTombstones,      kCFBooleanTrue,
 74                                                                       NULL);
 75  
 76      CFReleaseNull(priv_key_attrs);
 77  
 78      CFReleaseNull(key_size_num);
 79      OSStatus status = SecKeyGeneratePair(keygen_parameters, NULL, &full_key);
 80      CFReleaseNull(keygen_parameters);
 81  
 82      if (status)
 83          secerror("status: %ld", (long)status);
 84      if (status != errSecSuccess && error != NULL && *error == NULL) {
 85          *error = CFErrorCreate(kCFAllocatorDefault, kCFErrorDomainOSStatus, status, NULL);
 86      }
 87  
 88      return full_key;
 89  }
 90  
 91  CF_RETURNS_RETAINED SecKeyRef GeneratePermanentFullECKey(int keySize, CFStringRef name, CFErrorRef* error) {
 92      return GeneratePermanentFullECKey_internal(keySize, name, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kCFBooleanFalse, error);
 93  }
 94  
 95  static CF_RETURNS_RETAINED SecKeyRef GeneratePermanentFullECKeyForCloudIdentity(int keySize, CFStringRef name, CFErrorRef* error) {
 96      return GeneratePermanentFullECKey_internal(keySize, name, kSecAttrAccessibleWhenUnlocked, kCFBooleanTrue, error);
 97  }
 98  
 99  static SecKeyRef sosKeyForLabel(CFStringRef label) {
100      CFTypeRef queryResult = NULL;
101      SecKeyRef retval = NULL;
102      CFDictionaryRef query = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
103          kSecMatchLimit,            kSecMatchLimitOne,
104          kSecClass,                 kSecClassKey,
105          kSecAttrKeyClass,          kSecAttrKeyClassPrivate,
106          kSecAttrSynchronizable,    kSecAttrSynchronizableAny,
107          kSecAttrAccessGroup,       kSOSInternalAccessGroup,
108          kSecAttrLabel,              label,
109          kSecReturnRef,             kCFBooleanTrue,
110          NULL);
111      OSStatus stat = SecItemCopyMatching(query, &queryResult);
112      if(errSecSuccess == stat) {
113          retval = (SecKeyRef) queryResult;
114          secnotice("iCloudIdentity", "Got key for label (%@)", label);
115      } else {
116          secnotice("iCloudIdentity", "Failed query(%d) for %@", (int) stat, label);
117      }
118      CFReleaseNull(query);
119      return retval;
120  }
121  
122  void SOSiCloudIdentityPrivateKeyForEach(void (^complete)(SecKeyRef privKey)) {
123      CFTypeRef queryResult = NULL;
124      CFArrayRef iCloudPrivKeys = NULL;
125  
126      CFDictionaryRef query = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
127          kSecMatchLimit,            kSecMatchLimitAll,
128          kSecClass,                 kSecClassKey,
129          kSecAttrKeyClass,          kSecAttrKeyClassPrivate,
130          kSecAttrSynchronizable,    kSecAttrSynchronizableAny,
131          kSecAttrAccessGroup,       kSOSInternalAccessGroup,
132          kSecReturnAttributes,      kCFBooleanTrue,
133          NULL);
134  
135      if((errSecSuccess == SecItemCopyMatching(query, &queryResult)) && (iCloudPrivKeys = asArray(queryResult, NULL))) {
136          secnotice("iCloudIdentity", "Screening %ld icloud private key candidates", (long)CFArrayGetCount(iCloudPrivKeys));
137          CFReleaseNull(query);
138      } else {
139          secnotice("iCloudIdentity", "Can't get iCloud Identity private key candidates");
140          CFReleaseNull(query);
141          CFReleaseNull(queryResult);
142          return;
143      }
144  
145      CFArrayForEach(iCloudPrivKeys, ^(const void *value) {
146          CFDictionaryRef privKeyDict = (CFDictionaryRef) value;
147          CFStringRef label = CFDictionaryGetValue(privKeyDict, kSecAttrLabel);
148          if(!label) {
149              return;
150          }
151          if(CFStringHasPrefix(label, CFSTR("Cloud Identity"))) {
152              SecKeyRef privKey = sosKeyForLabel(label);
153              if(privKey) {
154                  complete(privKey);
155                  CFReleaseNull(privKey);
156              }
157          }
158      });
159      CFReleaseNull(queryResult);
160  }
161  
162  bool SOSAccountHasCircle(SOSAccount* account, CFErrorRef* error) {
163      SOSAccountTrustClassic *trust = account.trust;
164      SOSCircleRef circle = trust.trustedCircle;
165  
166      if (!circle)
167          SOSCreateErrorWithFormat(kSOSErrorNoCircle, NULL, error, NULL, CFSTR("No trusted circle"));
168  
169      return circle != NULL;
170  }
171  
172  bool SOSAccountHasFullPeerInfo(SOSAccount* account, CFErrorRef* error) {
173      bool hasPeer = false;
174      SOSAccountTrustClassic *trust = account.trust;
175      SOSFullPeerInfoRef identity = trust.fullPeerInfo;
176  
177      require(SOSAccountHasCircle(account, error), fail);
178  
179      hasPeer = identity != NULL;
180  
181      if (!hasPeer)
182          SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("No peer for circle"));
183  
184  fail:
185      return hasPeer;
186  }
187  
188  bool SOSAccountIsAccountIdentity(SOSAccount* account, SOSPeerInfoRef peer_info, CFErrorRef *error)
189  {
190      SOSFullPeerInfoRef identity = NULL;
191  
192      SOSAccountTrustClassic *trust = account.trust;
193      identity = trust.fullPeerInfo;
194      return CFEqualSafe(peer_info, SOSFullPeerInfoGetPeerInfo(identity));
195  }
196  
197  bool SOSAccountFullPeerInfoVerify(SOSAccount* account, SecKeyRef privKey, CFErrorRef *error) {
198      SOSFullPeerInfoRef identity = NULL;
199  
200      SOSAccountTrustClassic *trust = account.trust;
201      identity = trust.fullPeerInfo;
202      if(!identity) return false;
203      SecKeyRef pubKey = SecKeyCreatePublicFromPrivate(privKey);
204      bool retval = SOSPeerInfoApplicationVerify(SOSFullPeerInfoGetPeerInfo(identity), pubKey, error);
205      CFReleaseNull(pubKey);
206      return retval;
207  }
208  
209  static bool UpdateKeyName(SecKeyRef key, SOSPeerInfoRef peer, CFErrorRef* error)
210  {
211      CFDictionaryRef query = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
212                                                           kSecClass,             kSecClassKey,
213                                                           kSecAttrSynchronizable,kCFBooleanTrue,
214                                                           kSecUseTombstones,     kCFBooleanTrue,
215                                                           kSecValueRef,          key,
216                                                           NULL);
217      
218      CFStringRef new_name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL,
219                                                      CFSTR("Cloud Identity - '%@'"), SOSPeerInfoGetPeerID(peer));
220      
221      CFDictionaryRef change = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
222                                                            kSecAttrLabel,        new_name,
223                                                            NULL);
224      
225      bool result = SecError(SecItemUpdate(query, change), error, CFSTR("Couldn't update name"));
226      
227      CFReleaseNull(new_name);
228      CFReleaseNull(query);
229      CFReleaseNull(change);
230      return result;
231  }
232  
233  SOSPeerInfoRef GenerateNewCloudIdentityPeerInfo(CFErrorRef *error) {
234      SecKeyRef cloud_key = GeneratePermanentFullECKeyForCloudIdentity(256, kicloud_identity_name, error);
235      SOSPeerInfoRef cloud_peer = NULL;
236      
237      CFDictionaryRef gestalt = NULL;
238      
239      require_action_quiet(cloud_key, fail, SecError(errSecAllocate, error, CFSTR("Can't generate keypair for icloud identity")));
240  
241      gestalt = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
242                                                             kPIUserDefinedDeviceNameKey, CFSTR("iCloud"),
243                                                             NULL);
244      require_action_quiet(gestalt, fail, SecError(errSecAllocate, error, CFSTR("Can't allocate gestalt")));
245      
246      cloud_peer = SOSPeerInfoCreateCloudIdentity(kCFAllocatorDefault, gestalt, cloud_key, error);
247      
248      require(cloud_peer, fail);
249      
250      UpdateKeyName(cloud_key, cloud_peer, error);
251  
252  fail:
253      CFReleaseNull(gestalt);
254      CFReleaseNull(cloud_key);
255      
256      return cloud_peer;
257  }
258  
259  
260  bool SOSAccountUpdatePeerInfo(SOSAccount*  account, CFStringRef updateDescription, CFErrorRef *error, bool (^update)(SOSFullPeerInfoRef fpi, CFErrorRef *error)) {
261  
262      if (!account.hasPeerInfo)
263          return true;
264  
265      bool result = update(account.fullPeerInfo, error);
266  
267      if (result && SOSAccountHasCircle(account, NULL)) {
268          return [account.trust modifyCircle:account.circle_transport err:error action:^(SOSCircleRef circle_to_change) {
269              secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for %@", updateDescription);
270              return SOSCircleUpdatePeerInfo(circle_to_change, account.peerInfo);
271          }];
272      }
273      
274      return result;
275  }
276  
277  
278  bool SOSAccountUpdatePeerInfoAndPush(SOSAccount* account, CFStringRef updateDescription, CFErrorRef *error,
279                                              bool (^update)(SOSPeerInfoRef pi, CFErrorRef *error)) {
280      return SOSAccountUpdatePeerInfo(account, updateDescription, error, ^bool(SOSFullPeerInfoRef fpi, CFErrorRef *localError) {
281          return SOSFullPeerInfoUpdate(fpi, localError, ^SOSPeerInfoRef(SOSPeerInfoRef pi, SecKeyRef peerPriv, CFErrorRef *localError) {
282              SOSPeerInfoRef newPI = SOSPeerInfoCreateCopy(kCFAllocatorDefault, pi, localError);
283              if(update(newPI, error)) {
284                  if(peerPriv && SOSPeerInfoSign(peerPriv, newPI, localError)) {
285                      secnotice("circleOp", "Signed Peerinfo to update");
286                      return newPI;
287                  }
288              }
289              secnotice("circleOp", "Failed updating PeerInfo");
290              CFReleaseNull(newPI);
291              return NULL;
292          });
293      });
294  }