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