]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKS.m
Security-58286.251.4.tar.gz
[apple/security.git] / keychain / ckks / CKKS.m
1 /*
2 * Copyright (c) 2016 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 <dispatch/dispatch.h>
25 #import <Foundation/Foundation.h>
26 #include <sys/sysctl.h>
27 #if OCTAGON
28 #import <CloudKit/CloudKit.h>
29 #endif
30
31 #include <utilities/debugging.h>
32 #include <securityd/SecItemServer.h>
33 #include <Security/SecItemPriv.h>
34
35 #import <Foundation/Foundation.h>
36 #import "keychain/ckks/CKKS.h"
37 #import "keychain/ckks/CKKSKeychainView.h"
38 #import "keychain/ckks/CKKSViewManager.h"
39 #import "keychain/ckks/CKKSKey.h"
40
41 #import "keychain/ot/OTManager.h"
42 const SecCKKSItemEncryptionVersion currentCKKSItemEncryptionVersion = CKKSItemEncryptionVersion2;
43
44 NSString* const SecCKKSActionAdd = @"add";
45 NSString* const SecCKKSActionDelete = @"delete";
46 NSString* const SecCKKSActionModify = @"modify";
47
48 CKKSItemState* const SecCKKSStateNew = (CKKSItemState*) @"new";
49 CKKSItemState* const SecCKKSStateUnauthenticated = (CKKSItemState*) @"unauthenticated";
50 CKKSItemState* const SecCKKSStateInFlight = (CKKSItemState*) @"inflight";
51 CKKSItemState* const SecCKKSStateReencrypt = (CKKSItemState*) @"reencrypt";
52 CKKSItemState* const SecCKKSStateError = (CKKSItemState*) @"error";
53 CKKSItemState* const SecCKKSStateZoneMismatch = (CKKSItemState*) @"zone_mismatch";
54 CKKSItemState* const SecCKKSStateDeleted = (CKKSItemState*) @"deleted";
55
56 CKKSProcessedState* const SecCKKSProcessedStateLocal = (CKKSProcessedState*) @"local";
57 CKKSProcessedState* const SecCKKSProcessedStateRemote = (CKKSProcessedState*) @"remote";
58
59 CKKSKeyClass* const SecCKKSKeyClassTLK = (CKKSKeyClass*) @"tlk";
60 CKKSKeyClass* const SecCKKSKeyClassA = (CKKSKeyClass*) @"classA";
61 CKKSKeyClass* const SecCKKSKeyClassC = (CKKSKeyClass*) @"classC";
62
63 NSString* SecCKKSContainerName = @"com.apple.security.keychain";
64 bool SecCKKSContainerUsePCS = false;
65
66 NSString* const SecCKKSSubscriptionID = @"keychain-changes";
67 NSString* const SecCKKSAPSNamedPort = @"com.apple.securityd.aps";
68
69 NSString* const SecCKRecordItemType = @"item";
70 NSString* const SecCKRecordHostOSVersionKey = @"uploadver";
71 NSString* const SecCKRecordEncryptionVersionKey = @"encver";
72 NSString* const SecCKRecordDataKey = @"data";
73 NSString* const SecCKRecordParentKeyRefKey = @"parentkeyref";
74 NSString* const SecCKRecordWrappedKeyKey = @"wrappedkey";
75 NSString* const SecCKRecordGenerationCountKey = @"gen";
76
77 NSString* const SecCKRecordPCSServiceIdentifier = @"pcsservice";
78 NSString* const SecCKRecordPCSPublicKey = @"pcspublickey";
79 NSString* const SecCKRecordPCSPublicIdentity = @"pcspublicidentity";
80 NSString* const SecCKRecordServerWasCurrent = @"server_wascurrent";
81
82 NSString* const SecCKRecordIntermediateKeyType = @"synckey";
83 NSString* const SecCKRecordKeyClassKey = @"class";
84
85 NSString* const SecCKRecordTLKShareType = @"tlkshare";
86 NSString* const SecCKRecordSenderPeerID = @"sender";
87 NSString* const SecCKRecordReceiverPeerID = @"receiver";
88 NSString* const SecCKRecordReceiverPublicEncryptionKey = @"receiverPublicEncryptionKey";
89 NSString* const SecCKRecordCurve = @"curve";
90 NSString* const SecCKRecordEpoch = @"epoch";
91 NSString* const SecCKRecordPoisoned = @"poisoned";
92 NSString* const SecCKRecordSignature = @"signature";
93 NSString* const SecCKRecordVersion = @"version";
94
95 NSString* const SecCKRecordCurrentKeyType = @"currentkey";
96
97 NSString* const SecCKRecordCurrentItemType = @"currentitem";
98 NSString* const SecCKRecordItemRefKey = @"item";
99
100 NSString* const SecCKRecordDeviceStateType = @"devicestate";
101 NSString* const SecCKRecordCirclePeerID = @"peerid";
102 NSString* const SecCKRecordCircleStatus = @"circle";
103 NSString* const SecCKRecordKeyState = @"keystate";
104 NSString* const SecCKRecordCurrentTLK = @"currentTLK";
105 NSString* const SecCKRecordCurrentClassA = @"currentClassA";
106 NSString* const SecCKRecordCurrentClassC = @"currentClassC";
107 NSString* const SecCKSRecordLastUnlockTime = @"lastunlock";
108 NSString* const SecCKSRecordOSVersionKey = @"osver";
109
110 NSString* const SecCKRecordManifestType = @"manifest";
111 NSString* const SecCKRecordManifestDigestValueKey = @"digest_value";
112 NSString* const SecCKRecordManifestGenerationCountKey = @"generation_count";
113 NSString* const SecCKRecordManifestLeafRecordIDsKey = @"leaf_records";
114 NSString* const SecCKRecordManifestPeerManifestRecordIDsKey = @"peer_manifests";
115 NSString* const SecCKRecordManifestCurrentItemsKey = @"current_items";
116 NSString* const SecCKRecordManifestSignaturesKey = @"signatures";
117 NSString* const SecCKRecordManifestSignerIDKey = @"signer_id";
118 NSString* const SecCKRecordManifestSchemaKey = @"schema";
119
120 NSString* const SecCKRecordManifestLeafType = @"manifest_leaf";
121 NSString* const SecCKRecordManifestLeafDERKey = @"der";
122 NSString* const SecCKRecordManifestLeafDigestKey = @"digest";
123
124 CKKSZoneKeyState* const SecCKKSZoneKeyStateReady = (CKKSZoneKeyState*) @"ready";
125 CKKSZoneKeyState* const SecCKKSZoneKeyStateReadyPendingUnlock = (CKKSZoneKeyState*) @"readypendingunlock";
126 CKKSZoneKeyState* const SecCKKSZoneKeyStateError = (CKKSZoneKeyState*) @"error";
127 CKKSZoneKeyState* const SecCKKSZoneKeyStateCancelled = (CKKSZoneKeyState*) @"cancelled";
128
129 CKKSZoneKeyState* const SecCKKSZoneKeyStateInitializing = (CKKSZoneKeyState*) @"initializing";
130 CKKSZoneKeyState* const SecCKKSZoneKeyStateInitialized = (CKKSZoneKeyState*) @"initialized";
131 CKKSZoneKeyState* const SecCKKSZoneKeyStateFetch = (CKKSZoneKeyState*) @"fetching";
132 CKKSZoneKeyState* const SecCKKSZoneKeyStateFetchComplete = (CKKSZoneKeyState*) @"fetchcomplete";
133 CKKSZoneKeyState* const SecCKKSZoneKeyStateNeedFullRefetch = (CKKSZoneKeyState*) @"needrefetch";
134 CKKSZoneKeyState* const SecCKKSZoneKeyStateWaitForTLK = (CKKSZoneKeyState*) @"waitfortlk";
135 CKKSZoneKeyState* const SecCKKSZoneKeyStateWaitForUnlock = (CKKSZoneKeyState*) @"waitforunlock";
136 CKKSZoneKeyState* const SecCKKSZoneKeyStateUnhealthy = (CKKSZoneKeyState*) @"unhealthy";
137 CKKSZoneKeyState* const SecCKKSZoneKeyStateBadCurrentPointers = (CKKSZoneKeyState*) @"badcurrentpointers";
138 CKKSZoneKeyState* const SecCKKSZoneKeyStateNewTLKsFailed = (CKKSZoneKeyState*) @"newtlksfailed";
139 CKKSZoneKeyState* const SecCKKSZoneKeyStateHealTLKShares = (CKKSZoneKeyState*) @"healtlkshares";
140 CKKSZoneKeyState* const SecCKKSZoneKeyStateHealTLKSharesFailed = (CKKSZoneKeyState*) @"healtlksharesfailed";
141 CKKSZoneKeyState* const SecCKKSZoneKeyStateWaitForFixupOperation = (CKKSZoneKeyState*) @"waitforfixupoperation";
142 CKKSZoneKeyState* const SecCKKSZoneKeyStateResettingZone = (CKKSZoneKeyState*) @"resetzone";
143 CKKSZoneKeyState* const SecCKKSZoneKeyStateResettingLocalData = (CKKSZoneKeyState*) @"resetlocal";
144 CKKSZoneKeyState* const SecCKKSZoneKeyStateLoggedOut = (CKKSZoneKeyState*) @"loggedout";
145 CKKSZoneKeyState* const SecCKKSZoneKeyStateZoneCreationFailed = (CKKSZoneKeyState*) @"zonecreationfailed";
146 CKKSZoneKeyState* const SecCKKSZoneKeyStateProcess = (CKKSZoneKeyState*) @"process";
147
148 NSDictionary<CKKSZoneKeyState*, NSNumber*>* CKKSZoneKeyStateMap(void) {
149 static NSDictionary<CKKSZoneKeyState*, NSNumber*>* map = nil;
150 static dispatch_once_t onceToken;
151 dispatch_once(&onceToken, ^{
152 map = @{
153 SecCKKSZoneKeyStateReady: @0U,
154 SecCKKSZoneKeyStateError: @1U,
155 SecCKKSZoneKeyStateCancelled: @2U,
156
157 SecCKKSZoneKeyStateInitializing: @3U,
158 SecCKKSZoneKeyStateInitialized: @4U,
159 SecCKKSZoneKeyStateFetchComplete: @5U,
160 SecCKKSZoneKeyStateWaitForTLK: @6U,
161 SecCKKSZoneKeyStateWaitForUnlock: @7U,
162 SecCKKSZoneKeyStateUnhealthy: @8U,
163 SecCKKSZoneKeyStateBadCurrentPointers: @9U,
164 SecCKKSZoneKeyStateNewTLKsFailed: @10U,
165 SecCKKSZoneKeyStateNeedFullRefetch: @11U,
166 SecCKKSZoneKeyStateHealTLKShares: @12U,
167 SecCKKSZoneKeyStateHealTLKSharesFailed:@13U,
168 SecCKKSZoneKeyStateWaitForFixupOperation:@14U,
169 SecCKKSZoneKeyStateReadyPendingUnlock: @15U,
170 SecCKKSZoneKeyStateFetch: @16U,
171 SecCKKSZoneKeyStateResettingZone: @17U,
172 SecCKKSZoneKeyStateResettingLocalData: @18U,
173 SecCKKSZoneKeyStateLoggedOut: @19U,
174 SecCKKSZoneKeyStateZoneCreationFailed: @20U,
175 };
176 });
177 return map;
178 }
179
180 NSDictionary<NSNumber*, CKKSZoneKeyState*>* CKKSZoneKeyStateInverseMap(void) {
181 static NSDictionary<NSNumber*, CKKSZoneKeyState*>* backwardMap = nil;
182 static dispatch_once_t onceToken;
183 dispatch_once(&onceToken, ^{
184 NSDictionary<CKKSZoneKeyState*, NSNumber*>* forwardMap = CKKSZoneKeyStateMap();
185 backwardMap = [NSDictionary dictionaryWithObjects:[forwardMap allKeys] forKeys:[forwardMap allValues]];
186 });
187 return backwardMap;
188 }
189
190 NSNumber* CKKSZoneKeyToNumber(CKKSZoneKeyState* state) {
191 if(!state) {
192 return CKKSZoneKeyStateMap()[SecCKKSZoneKeyStateError];
193 }
194 NSNumber* result = CKKSZoneKeyStateMap()[state];
195 if(result) {
196 return result;
197 }
198 return CKKSZoneKeyStateMap()[SecCKKSZoneKeyStateError];
199 }
200 CKKSZoneKeyState* CKKSZoneKeyRecover(NSNumber* stateNumber) {
201 if(!stateNumber) {
202 return SecCKKSZoneKeyStateError;
203 }
204 CKKSZoneKeyState* result = CKKSZoneKeyStateInverseMap()[stateNumber];
205 if(result) {
206 return result;
207 }
208 return SecCKKSZoneKeyStateError;
209 }
210
211 bool CKKSKeyStateTransient(CKKSZoneKeyState* state) {
212 // Easier to compare against a blacklist of end states
213 bool nontransient = [state isEqualToString:SecCKKSZoneKeyStateReady] ||
214 [state isEqualToString:SecCKKSZoneKeyStateReadyPendingUnlock] ||
215 [state isEqualToString:SecCKKSZoneKeyStateWaitForTLK] ||
216 [state isEqualToString:SecCKKSZoneKeyStateWaitForUnlock] ||
217 [state isEqualToString:SecCKKSZoneKeyStateError] ||
218 [state isEqualToString:SecCKKSZoneKeyStateCancelled];
219 return !nontransient;
220 }
221
222 const NSUInteger SecCKKSItemPaddingBlockSize = 20;
223
224 NSString* const SecCKKSAggdPropagationDelay = @"com.apple.security.ckks.propagationdelay";
225 NSString* const SecCKKSAggdPrimaryKeyConflict = @"com.apple.security.ckks.pkconflict";
226 NSString* const SecCKKSAggdViewKeyCount = @"com.apple.security.ckks.keycount";
227 NSString* const SecCKKSAggdItemReencryption = @"com.apple.security.ckks.reencrypt";
228
229 NSString* const SecCKKSUserDefaultsSuite = @"com.apple.security.ckks";
230
231 NSString* const CKKSErrorDomain = @"CKKSErrorDomain";
232 NSString* const CKKSServerExtensionErrorDomain = @"CKKSServerExtensionErrorDomain";
233
234 #if OCTAGON
235 static bool enableCKKS = true;
236 static bool testCKKS = false;
237
238 bool SecCKKSIsEnabled(void) {
239 if([CKDatabase class] == nil) {
240 // CloudKit is not linked. We cannot bring CKKS up; disable it with prejudice.
241 secerror("CKKS: CloudKit.framework appears to not be linked. Cannot enable CKKS (on pain of crash).");
242 return false;
243 }
244
245 return enableCKKS;
246 }
247
248 bool SecCKKSEnable() {
249 enableCKKS = true;
250 return enableCKKS;
251 }
252
253 bool SecCKKSDisable() {
254 enableCKKS = false;
255 return enableCKKS;
256 }
257
258 bool SecCKKSResetSyncing(void) {
259 [CKKSViewManager resetManager: true setTo: nil];
260 return SecCKKSIsEnabled();
261 }
262
263 bool SecCKKSTestsEnabled(void) {
264 return testCKKS;
265 }
266
267 bool SecCKKSTestsEnable(void) {
268 if([CKDatabase class] == nil) {
269 // CloudKit is not linked. We cannot bring CKKS up; disable it with prejudice.
270 secerror("CKKS: CloudKit.framework appears to not be linked. Cannot enable CKKS testing.");
271 testCKKS = false;
272 return false;
273 }
274
275 testCKKS = true;
276 return testCKKS;
277 }
278
279 bool SecCKKSTestsDisable(void) {
280 testCKKS = false;
281 return testCKKS;
282 }
283
284 // Feature flags to twiddle behavior
285 static bool CKKSSyncManifests = false;
286 bool SecCKKSSyncManifests(void) {
287 return CKKSSyncManifests;
288 }
289 bool SecCKKSEnableSyncManifests() {
290 CKKSSyncManifests = true;
291 return CKKSSyncManifests;
292 }
293 bool SecCKKSSetSyncManifests(bool value) {
294 CKKSSyncManifests = value;
295 return CKKSSyncManifests;
296 }
297
298 static bool CKKSEnforceManifests = false;
299 bool SecCKKSEnforceManifests(void) {
300 return CKKSEnforceManifests;
301 }
302 bool SecCKKSEnableEnforceManifests() {
303 CKKSEnforceManifests = true;
304 return CKKSEnforceManifests;
305 }
306 bool SecCKKSSetEnforceManifests(bool value) {
307 CKKSEnforceManifests = value;
308 return CKKSEnforceManifests;
309 }
310
311 // defaults write com.apple.security.ckks reduce-rate-limiting YES
312 static bool CKKSReduceRateLimiting = false;
313 bool SecCKKSReduceRateLimiting(void) {
314 static dispatch_once_t onceToken;
315 dispatch_once(&onceToken, ^{
316 // Use the default value as above, or apply the preferences value if it exists
317 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
318 NSString* key = @"reduce-rate-limiting";
319 [defaults registerDefaults: @{key: CKKSReduceRateLimiting ? @YES : @NO}];
320
321 CKKSReduceRateLimiting = !![defaults boolForKey:@"reduce-rate-limiting"];
322 secnotice("ckks", "reduce-rate-limiting is %@", CKKSReduceRateLimiting ? @"on" : @"off");
323 });
324
325 return CKKSReduceRateLimiting;
326 }
327
328 bool SecCKKSSetReduceRateLimiting(bool value) {
329 (void) SecCKKSReduceRateLimiting(); // Call this once to read the defaults write
330 CKKSReduceRateLimiting = value;
331 secnotice("ckks", "reduce-rate-limiting is now %@", CKKSReduceRateLimiting ? @"on" : @"off");
332 return CKKSReduceRateLimiting;
333 }
334
335 // Here's a mechanism for CKKS feature flags with default values from NSUserDefaults:
336 /*static bool CKKSShareTLKs = true;
337 bool SecCKKSShareTLKs(void) {
338 return true;
339 static dispatch_once_t onceToken;
340 dispatch_once(&onceToken, ^{
341 // Use the default value as above, or apply the preferences value if it exists
342 NSUserDefaults* defaults = [[NSUserDefaults alloc] initWithSuiteName:SecCKKSUserDefaultsSuite];
343 [defaults registerDefaults: @{@"tlksharing": CKKSShareTLKs ? @YES : @NO}];
344
345 CKKSShareTLKs = !![defaults boolForKey:@"tlksharing"];
346 secnotice("ckksshare", "TLK sharing is %@", CKKSShareTLKs ? @"on" : @"off");
347 });
348
349 return CKKSShareTLKs;
350 }*/
351
352 // Feature flags to twiddle behavior for tests
353 static bool CKKSDisableAutomaticUUID = false;
354 bool SecCKKSTestDisableAutomaticUUID(void) {
355 #if DEBUG
356 return CKKSDisableAutomaticUUID;
357 #else
358 return false;
359 #endif
360 }
361 void SecCKKSTestSetDisableAutomaticUUID(bool set) {
362 CKKSDisableAutomaticUUID = set;
363 }
364
365 static bool CKKSDisableSOS = false;
366 bool SecCKKSTestDisableSOS(void) {
367 #if DEBUG
368 return CKKSDisableSOS;
369 #else
370 return false;
371 #endif
372 }
373 void SecCKKSTestSetDisableSOS(bool set) {
374 CKKSDisableSOS = set;
375 }
376
377
378 static bool CKKSDisableKeyNotifications = false;
379 bool SecCKKSTestDisableKeyNotifications(void) {
380 #if DEBUG
381 return CKKSDisableKeyNotifications;
382 #else
383 return false;
384 #endif
385 }
386 void SecCKKSTestSetDisableKeyNotifications(bool set) {
387 CKKSDisableKeyNotifications = set;
388 }
389
390 void SecCKKSTestResetFlags(void) {
391 SecCKKSTestSetDisableAutomaticUUID(false);
392 SecCKKSTestSetDisableSOS(false);
393 SecCKKSTestSetDisableKeyNotifications(false);
394 }
395
396 #else /* NO OCTAGON */
397
398 bool SecCKKSIsEnabled(void) {
399 secerror("CKKS was disabled at compile time.");
400 return false;
401 }
402
403 bool SecCKKSEnable() {
404 return false;
405 }
406
407 bool SecCKKSDisable() {
408 return false;
409 }
410
411 bool SecCKKSResetSyncing(void) {
412 return SecCKKSIsEnabled();
413 }
414
415 #endif /* OCTAGON */
416
417
418
419 void SecCKKSInitialize(SecDbRef db) {
420 #if OCTAGON
421 @autoreleasepool {
422 CKKSViewManager* manager = [CKKSViewManager manager];
423 [manager initializeZones];
424
425 SecDbAddNotifyPhaseBlock(db, ^(SecDbConnectionRef dbconn, SecDbTransactionPhase phase, SecDbTransactionSource source, CFArrayRef changes) {
426 SecCKKSNotifyBlock(dbconn, phase, source, changes);
427 });
428
429 [manager.completedSecCKKSInitialize fulfill];
430 }
431 #endif
432 }
433
434 void SecCKKSNotifyBlock(SecDbConnectionRef dbconn, SecDbTransactionPhase phase, SecDbTransactionSource source, CFArrayRef changes) {
435 #if OCTAGON
436 if(phase == kSecDbTransactionDidRollback) {
437 return;
438 }
439
440 // Ignore our own changes, otherwise we'd infinite-loop.
441 if(source == kSecDbCKKSTransaction) {
442 secinfo("ckks", "Ignoring kSecDbCKKSTransaction notification");
443 return;
444 }
445
446 CFArrayForEach(changes, ^(CFTypeRef r) {
447 SecDbItemRef deleted = NULL;
448 SecDbItemRef added = NULL;
449
450 SecDbEventTranslateComponents(r, (CFTypeRef*) &deleted, (CFTypeRef*) &added);
451
452 if(!added && !deleted) {
453 secerror("CKKS: SecDbEvent gave us garbage: %@", r);
454 return;
455 }
456
457 [[CKKSViewManager manager] handleKeychainEventDbConnection: dbconn source:source added: added deleted: deleted];
458 });
459 #endif
460 }
461
462 void SecCKKS24hrNotification() {
463 #if OCTAGON
464 @autoreleasepool {
465 [[CKKSViewManager manager] xpc24HrNotification];
466 }
467 #endif
468 }
469
470 void CKKSRegisterSyncStatusCallback(CFStringRef cfuuid, SecBoolCFErrorCallback cfcallback) {
471 #if OCTAGON
472 // Keep plumbing, but transition to NS.
473 SecBoolNSErrorCallback nscallback = ^(bool result, NSError* err) {
474 cfcallback(result, (__bridge CFErrorRef) err);
475 };
476
477 [[CKKSViewManager manager] registerSyncStatusCallback: (__bridge NSString*) cfuuid callback:nscallback];
478 #endif
479 }
480
481 void SecCKKSPerformLocalResync() {
482 #if OCTAGON
483 secnotice("ckks", "Local keychain was reset; performing local resync");
484 [[CKKSViewManager manager] rpcResyncLocal:nil reply:^(NSError *result) {
485 if(result) {
486 secnotice("ckks", "Local keychain reset resync finished with an error: %@", result);
487 } else {
488 secnotice("ckks", "Local keychain reset resync finished successfully");
489 }
490 }];
491 #endif
492 }
493
494 NSString* SecCKKSHostOSVersion()
495 {
496 #ifdef PLATFORM
497 // Use complicated macro magic to get the string value passed in as preprocessor define PLATFORM.
498 #define PLATFORM_VALUE(f) #f
499 #define PLATFORM_OBJCSTR(f) @PLATFORM_VALUE(f)
500 NSString* platform = (PLATFORM_OBJCSTR(PLATFORM));
501 #undef PLATFORM_OBJCSTR
502 #undef PLATFORM_VALUE
503 #else
504 NSString* platform = "unknown";
505 #warning No PLATFORM defined; why?
506 #endif
507
508 NSString* osversion = nil;
509
510 // If we can get the build information from sysctl, use it.
511 char release[256];
512 size_t releasesize = sizeof(release);
513 bool haveSysctlInfo = true;
514 haveSysctlInfo &= (0 == sysctlbyname("kern.osrelease", release, &releasesize, NULL, 0));
515
516 char version[256];
517 size_t versionsize = sizeof(version);
518 haveSysctlInfo &= (0 == sysctlbyname("kern.osversion", version, &versionsize, NULL, 0));
519
520 if(haveSysctlInfo) {
521 // Null-terminate for extra safety
522 release[sizeof(release)-1] = '\0';
523 version[sizeof(version)-1] = '\0';
524 osversion = [NSString stringWithFormat:@"%s (%s)", release, version];
525 }
526
527 if(!osversion) {
528 // Otherwise, use the not-really-supported fallback.
529 osversion = [[NSProcessInfo processInfo] operatingSystemVersionString];
530
531 // subtly improve osversion (but it's okay if that does nothing)
532 osversion = [osversion stringByReplacingOccurrencesOfString:@"Version" withString:@""];
533 }
534
535 return [NSString stringWithFormat:@"%@ %@", platform, osversion];
536 }