1/* 2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved. 3 * 4 * The contents of this file constitute Original Code as defined in and are 5 * subject to the Apple Public Source License Version 1.2 (the 'License'). 6 * You may not use this file except in compliance with the License. Please obtain 7 * a copy of the License at http://www.apple.com/publicsource and read it before 8 * using this file. 9 * 10 * This Original Code and all software distributed under the License are 11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS 12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT 13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the 15 * specific language governing rights and limitations under the License. 16 */ 17 18 19// 20// systemkeychain command - set up and manipulate system-unlocked keychains 21// 22#include <security_cdsa_client/securestorage.h> 23#include <security_cdsa_client/cryptoclient.h> 24#include <security_cdsa_utilities/uniformrandom.h> 25#include <security_utilities/devrandom.h> 26#include <security_utilities/errors.h> 27#include <security_cdsa_client/wrapkey.h> 28#include <security_cdsa_client/genkey.h> 29#include <security_cdsa_utilities/Schema.h> 30#include <security_cdsa_utilities/cssmendian.h> 31#include <securityd_client/ssblob.h> 32#include <securityd_client/ssclient.h> 33#include <Security/cssmapple.h> 34#include <cstdarg> 35#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h> 36#include <launch.h> 37#include <syslog.h> 38#include <sysexits.h> 39#include <dispatch/dispatch.h> 40#include <sys/socket.h> 41#include <sys/un.h> 42#include "TokenIDHelper.h" 43 44using namespace SecurityServer; 45using namespace CssmClient; 46using namespace UnixPlusPlus; 47 48 49static const char *unlockConfig = kSystemUnlockFile; 50 51 52// 53// Values set from command-line options 54// 55const char *systemKCName = kSystemKeychainDir kSystemKeychainName; 56bool verbose = false; 57bool createIfNeeded = false; 58bool force = false; 59 60 61// 62// CSSM record attribute names 63// 64static const CSSM_DB_ATTRIBUTE_INFO dlInfoLabel = { 65 CSSM_DB_ATTRIBUTE_NAME_AS_STRING, 66 {(char*)"Label"}, 67 CSSM_DB_ATTRIBUTE_FORMAT_BLOB 68}; 69 70 71 72// 73// Local functions 74void usage(); 75void createSystemKeychain(const char *kcName, const char *passphrase); 76void extract(const char *srcName, const char *dstName); 77void test(const char *kcName); 78 79void notice(const char *fmt, ...); 80void fail(const char *fmt, ...); 81 82void labelForMasterKey(Db &db, CssmOwnedData &data); 83void deleteKey(Db &db, const CssmData &label); // delete key with this label 84 85void addReferralRecord(Db &srcDb, const DLDbIdentifier &ident, CssmData *refData, CFDataRef labelData); 86void createUnlockConfig(CSP &csp, Db &db); 87void createAKeychain(const char *kcName, const char *passphrase, CSP &csp, DL &dl, Db &db, Key *masterKeyRef); 88OSStatus createTokenProtectedKeychain(const char *kcName); 89void createMasterKey(CSP &csp, Key &masterKeyRef); 90void flattenKey (const CssmKey &rawKey, CssmDataContainer &flatKey); 91void systemKeychainCheck(); 92 93// 94// Main program: parse options and dispatch, catching exceptions 95// 96int main (int argc, char * argv[]) 97{ 98 enum Action { 99 showUsage, 100 setupSystem, 101 copyKey, 102 testUnlock, 103 tokenProtectedKCCreate, 104 systemKeychainCheckDaemon 105 } action = showUsage; 106 107 extern int optind; 108 extern char *optarg; 109 int arg; 110 OSStatus status; 111 while ((arg = getopt(argc, argv, "cCdfk:stvT:")) != -1) { 112 switch (arg) { 113 case 'c': 114 createIfNeeded = true; 115 break; 116 case 'C': 117 action = setupSystem; 118 break; 119 case 'd': 120 action = systemKeychainCheckDaemon; 121 break; 122 case 'f': 123 force = true; 124 break; 125 case 'k': 126 systemKCName = optarg; 127 break; 128 case 's': 129 action = copyKey; 130 break; 131 case 't': 132 action = testUnlock; 133 break; 134 case 'v': 135 verbose = true; 136 break; 137 case 'T': // Create token protected keychain 138 action = tokenProtectedKCCreate; 139 systemKCName = optarg; 140 break; 141 default: 142 usage(); 143 } 144 } 145 try { 146 switch (action) { 147 case setupSystem: 148 if (optind < argc - 1) 149 usage(); 150 createSystemKeychain(systemKCName, argv[optind]); 151 break; 152 case copyKey: 153 if (optind == argc) 154 usage(); 155 do { 156 extract(argv[optind], systemKCName); 157 } while (argv[++optind]); 158 break; 159 case testUnlock: 160 test(systemKCName); 161 break; 162 case tokenProtectedKCCreate: 163 if ((status = createTokenProtectedKeychain(systemKCName))) 164 { 165 cssmPerror("unable to create token protected keychain", status); 166 exit(1); 167 } 168 break; 169 case systemKeychainCheckDaemon: 170 systemKeychainCheck(); 171 exit(0); 172 break; 173 default: 174 usage(); 175 } 176 exit(0); 177 } catch (const CssmError &error) { 178 cssmPerror(systemKCName, error.error); 179 exit(1); 180 } catch (const UnixError &error) { 181 fail("%s: %s", systemKCName, strerror(error.error)); 182 exit(1); 183 } catch (const MacOSError &error) { 184 cssmPerror(systemKCName, error.error); 185 exit(1); 186 } 187 catch (...) { 188 fail("Unexpected exception"); 189 exit(1); 190 } 191} 192 193 194// 195// Partial usage message (some features aren't worth emphasizing...) 196// 197void usage() 198{ 199 fprintf(stderr, "Usage: systemkeychain -C [passphrase] # (re)create system root keychain" 200 "\n\tsystemkeychain [-k destination-keychain] -s source-keychain ..." 201 "\n\tsystemkeychain -T token-protected-keychain-name" 202 "\n"); 203 exit(2); 204} 205 206 207// 208// Create a keychain and set it up as the system-root secret 209// 210void createSystemKeychain(const char *kcName, const char *passphrase) 211{ 212 // for the default path only, make sure the directory exists 213 if (!strcmp(kcName, kSystemKeychainDir kSystemKeychainName)) 214 ::mkdir(kSystemKeychainDir, 0755); 215 216 CSP csp(gGuidAppleCSPDL); 217 DL dl(gGuidAppleCSPDL); 218 219 // create the keychain, using appropriate credentials 220 Db db(dl, kcName); 221 Key masterKey; 222 223 createMasterKey(csp, masterKey); 224 createAKeychain(kcName, passphrase, csp, dl, db, &masterKey); 225 createUnlockConfig(csp, db); 226 227 notice("%s installed as system keychain", kcName); 228} 229 230void createAKeychain(const char *kcName, const char *passphrase, CSP &csp, DL &dl, Db &db, Key *masterKeyRef) 231{ 232 // create the keychain, using appropriate credentials 233 Allocator &alloc = db->allocator(); 234 AutoCredentials cred(alloc); // will leak, but we're quitting soon :-) 235 CSSM_CSP_HANDLE cspHandle = csp->handle(); 236 if (passphrase) { 237 // use this passphrase 238 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, 239 new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), 240 new(alloc) ListElement(StringData(passphrase))); 241 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, 242 new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD), 243 new(alloc) ListElement(StringData(passphrase))); 244 db->accessCredentials(&cred); 245 } 246 else 247 { 248 // generate a random key 249 notice("warning: this keychain cannot be unlocked with any passphrase"); 250 if (!masterKeyRef) // caller does not need it returned for later use 251 MacOSError::throwMe(paramErr); 252 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK, 253 new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY), 254 new(alloc) ListElement(CssmData::wrap(cspHandle)), 255 new(alloc) ListElement(CssmData::wrap(static_cast<const CssmKey &>(*masterKeyRef)))); 256 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK, 257 new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY), 258 new(alloc) ListElement(CssmData::wrap(cspHandle)), 259 new(alloc) ListElement(CssmData::wrap(static_cast<const CssmKey &>(*masterKeyRef))), 260 new(alloc) ListElement(CssmData())); 261 db->accessCredentials(&cred); 262 } 263 db->dbInfo(&KeychainCore::Schema::DBInfo); // Set the standard schema 264 try { 265 db->create(); 266 } catch (const CssmError &error) { 267 if (error.error == CSSMERR_DL_DATASTORE_ALREADY_EXISTS && force) { 268 notice("recreating %s", kcName); 269 unlink(kcName); 270 db->create(); 271 } else 272 throw; 273 } 274 chmod(db->name(), 0644); 275} 276 277void createMasterKey(CSP &csp, Key &masterKeyRef) 278{ 279 // generate a random key 280 CssmClient::GenerateKey generate(csp, CSSM_ALGID_3DES_3KEY_EDE, 64 * 3); 281 masterKeyRef = generate(KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE)); 282} 283 284void createUnlockConfig(CSP &csp, Db &db) 285{ 286 // extract the key into the CSPDL 287 DeriveKey derive(csp, CSSM_ALGID_KEYCHAIN_KEY, CSSM_ALGID_3DES_3KEY, 3 * 64); 288 CSSM_DL_DB_HANDLE dlDb = db->handle(); 289 CssmData dlDbData = CssmData::wrap(dlDb); 290 CssmKey refKey; 291 KeySpec spec(CSSM_KEYUSE_ANY, 292 CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE); 293 derive(&dlDbData, spec, refKey); 294 295 // now extract the raw keybits 296 CssmKey rawKey; 297 WrapKey wrap(csp, CSSM_ALGID_NONE); 298 wrap(refKey, rawKey); 299 300 // form the evidence record 301 UnlockBlob blob; 302 blob.initialize(0); 303 304 CssmAutoData dbBlobData(Allocator::standard()); 305 SecurityServer::ClientSession client(dbBlobData.allocator, dbBlobData.allocator); 306 db->copyBlob(dbBlobData.get()); 307 DbBlob *dbBlob = dbBlobData.get().interpretedAs<DbBlob>(); 308 309 memcpy(&blob.signature, &dbBlob->randomSignature, sizeof(dbBlob->randomSignature)); 310 memcpy(blob.masterKey, rawKey.data(), sizeof(blob.masterKey)); 311 312 // write it out, forcibly overwriting an existing file 313 string tempFile(string(unlockConfig) + ","); 314 FileDesc blobFile(tempFile, O_WRONLY | O_CREAT | O_TRUNC, 0400); 315 if (blobFile.write(blob) != sizeof(blob)) { 316 unlink(tempFile.c_str()); 317 fail("unable to write %s", tempFile.c_str()); 318 } 319 blobFile.close(); 320 ::rename(tempFile.c_str(), unlockConfig); 321} 322 323// 324// Extract the master secret from a keychain and install it in another keychain for unlocking 325// 326void extract(const char *srcName, const char *dstName) 327{ 328 using namespace KeychainCore; 329 330 CSPDL cspdl(gGuidAppleCSPDL); 331 332 // open source database 333 Db srcDb(cspdl, srcName); 334 335 // open destination database 336 Db dstDb(cspdl, dstName); 337 try { 338 dstDb->open(); 339 } catch (const CssmError &err) { 340 if (err.error == CSSMERR_DL_DATASTORE_DOESNOT_EXIST && createIfNeeded) { 341 notice("creating %s", dstName); 342 dstDb->create(); 343 } else 344 throw; 345 } 346 347 // extract master key and place into destination keychain 348 DeriveKey derive(cspdl, CSSM_ALGID_KEYCHAIN_KEY, CSSM_ALGID_3DES_3KEY, 3 * 64); 349 CSSM_DL_DB_HANDLE dstDlDb = dstDb->handle(); 350 derive.add(CSSM_ATTRIBUTE_DL_DB_HANDLE, dstDlDb); 351 CSSM_DL_DB_HANDLE srcDlDb = srcDb->handle(); 352 CssmData dlDbData = CssmData::wrap(srcDlDb); 353 CssmAutoData keyLabel(Allocator::standard()); 354 labelForMasterKey(srcDb, keyLabel); 355 KeySpec spec(CSSM_KEYUSE_ANY, 356 CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE, 357 keyLabel); 358 CssmKey masterKey; 359 try { 360 derive(&dlDbData, spec, masterKey); 361 } catch (const CssmError &error) { 362 if (error.error != CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA) 363 throw; 364 if (!force) 365 fail("existing key in %s not overwritten. Use -f to replace it.", dstDb->name()); 366 notice("replacing existing record in %s", dstDb->name()); 367 deleteKey(dstDb, keyLabel); 368 derive(&dlDbData, spec, masterKey); 369 } 370 371 addReferralRecord(srcDb, dstDb->dlDbIdentifier(), NULL, NULL); 372 373 notice("%s can now be unlocked with a key in %s", srcName, dstName); 374} 375 376void addReferralRecord(Db &srcDb, const DLDbIdentifier &ident, CssmData *refData, CFDataRef label) 377{ 378 // If you have a Db dstDb, call with dstDb->dlDbIdentifier() as second parameter 379 using namespace KeychainCore; 380 // now add a referral record to the source database 381 uint32 referralType = (!refData)?CSSM_APPLE_UNLOCK_TYPE_KEY_DIRECT:CSSM_APPLE_UNLOCK_TYPE_WRAPPED_PRIVATE; 382 try { 383 // build attribute vector 384 CssmAutoData masterKeyLabel(Allocator::standard()); 385 CssmData externalLabel; 386 if (label) 387 { 388 externalLabel.Data = const_cast<uint8 *>(CFDataGetBytePtr(label)); 389 externalLabel.Length = CFDataGetLength(label); 390 } 391 else 392 labelForMasterKey(srcDb, masterKeyLabel); 393 CssmAutoDbRecordAttributeData refAttrs(9); 394 refAttrs.add(Schema::kUnlockReferralType, uint32(referralType)); 395 refAttrs.add(Schema::kUnlockReferralDbName, ident.dbName()); 396 refAttrs.add(Schema::kUnlockReferralDbNetname, CssmData()); 397 refAttrs.add(Schema::kUnlockReferralDbGuid, ident.ssuid().guid()); 398 refAttrs.add(Schema::kUnlockReferralDbSSID, ident.ssuid().subserviceId()); 399 refAttrs.add(Schema::kUnlockReferralDbSSType, ident.ssuid().subserviceType()); 400 refAttrs.add(Schema::kUnlockReferralKeyLabel, label?externalLabel:masterKeyLabel.get()); 401 refAttrs.add(Schema::kUnlockReferralKeyAppTag, CssmData()); 402 refAttrs.add(Schema::kUnlockReferralPrintName, 403 StringData("Keychain Unlock Referral Record")); 404 405 // no reference data for this form 406 CssmData emptyRefData; 407 if (!refData) 408 refData = &emptyRefData; 409 410 try { 411 srcDb->insert(CSSM_DL_DB_RECORD_UNLOCK_REFERRAL, 412 &refAttrs, 413 refData); 414 secdebug("kcreferral", "referral record stored in %s", srcDb->name()); 415 } catch (const CssmError &e) { 416 if (e.error != CSSMERR_DL_INVALID_RECORDTYPE) 417 throw; 418 419 // Create the referral relation and retry 420 secdebug("kcreferral", "adding referral schema relation to %s", srcDb->name()); 421 srcDb->createRelation(CSSM_DL_DB_RECORD_UNLOCK_REFERRAL, "CSSM_DL_DB_RECORD_UNLOCK_REFERRAL", 422 Schema::UnlockReferralSchemaAttributeCount, 423 Schema::UnlockReferralSchemaAttributeList, 424 Schema::UnlockReferralSchemaIndexCount, 425 Schema::UnlockReferralSchemaIndexList); 426 srcDb->insert(CSSM_DL_DB_RECORD_UNLOCK_REFERRAL, 427 &refAttrs, 428 refData); 429 secdebug("kcreferral", "referral record inserted in %s (on retry)", srcDb->name()); 430 } 431 } catch (...) { 432 notice("kcreferral", "cannot store referral in %s", srcDb->name()); 433 throw; 434 } 435} 436 437// 438// Create a keychain protected with an asymmetric key stored on a token (e.g. smartcard) 439// See createSystemKeychain for a similar call 440// 441#include <Security/SecKey.h> 442#include <Security/SecKeychain.h> 443#include <Security/SecKeychainItem.h> 444 445OSStatus createTokenProtectedKeychain(const char *kcName) 446{ 447 // In the notation of "extract", srcDb is the keychain to be unlocked 448 // (e.g. login keychain), and dstDb is the one to unlock with (e.g. smartcard/token) 449 // @@@ A later enhancement should allow lookup by public key hash 450 451 SecKeychainRef keychainRef = NULL; 452 SecKeyRef publicKeyRef = NULL; 453 const CSSM_KEY *pcssmKey; 454 CssmKey pubEncryptionKey; 455 CssmKey nullWrappedPubEncryptionKey; 456 CFDataRef label = NULL; 457 OSStatus status = findFirstEncryptionPublicKeyOnToken(&publicKeyRef, &keychainRef, &label); 458 if (status) 459 return status; 460 461 status = SecKeyGetCSSMKey(publicKeyRef, &pcssmKey); 462 if (status) 463 return status; 464 pubEncryptionKey = *pcssmKey; 465 466 CSSM_DL_DB_HANDLE dstdldbHandle; 467 status = SecKeychainGetDLDBHandle(keychainRef, &dstdldbHandle); 468 if (status) 469 return status; 470 471 char *tokenName; 472 CSSM_SUBSERVICE_UID tokenUID; 473 474 CSSM_RETURN cx; 475 cx = CSSM_DL_GetDbNameFromHandle (dstdldbHandle, &tokenName); 476 if (cx) 477 return cx; 478 479 cx = CSSM_GetSubserviceUIDFromHandle (dstdldbHandle.DLHandle, &tokenUID); 480 if (cx) 481 return cx; 482 483 secdebug("kcreferral", "Key found on token with subserviceId: %d", tokenUID.SubserviceId); 484 DLDbIdentifier tokenIdentifier(tokenName, Guid(gGuidAppleSdCSPDL), tokenUID.SubserviceId, 485 CSSM_SERVICE_CSP | CSSM_SERVICE_DL , NULL); 486 487 CSP csp(gGuidAppleCSPDL); 488 DL dl(gGuidAppleCSPDL); 489 490 // create the keychain, using appropriate credentials 491 Db srcdb(dl, kcName); 492 Key masterKey; 493 494 createMasterKey(csp, masterKey); 495 createAKeychain(kcName, NULL, csp, dl, srcdb, &masterKey); 496 497 // Turn the raw key into a reference key in the local csp with a NULL unwrap 498 CssmClient::Key publicEncryptionKey(csp, pubEncryptionKey, true); 499 500 UnwrapKey tunwrap(csp, CSSM_ALGID_NONE); 501 CSSM_KEYUSE uu = CSSM_KEYUSE_ANY; 502 CSSM_KEYATTR_FLAGS attrs = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE; 503 tunwrap(publicEncryptionKey, KeySpec(uu, attrs), nullWrappedPubEncryptionKey); 504 CssmClient::Key nullWrappedPubEncryptionKeyX(csp, nullWrappedPubEncryptionKey, true); 505 506 // use a CMS wrap to encrypt the key 507 CssmKey wrappedKey; 508 WrapKey wrap(csp, CSSM_ALGID_RSA); // >>>> will be key on token -- WrapKey wrap 509 wrap.key(nullWrappedPubEncryptionKeyX); 510 wrap.mode(CSSM_ALGMODE_NONE); 511 wrap.padding(CSSM_PADDING_PKCS1); 512 // only add if not CSSM_KEYBLOB_WRAPPED_FORMAT_NONE 513 wrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7)); 514 wrap(masterKey, wrappedKey); 515 516 CssmDataContainer flatKey; 517 flattenKey(wrappedKey, flatKey); 518 addReferralRecord(srcdb, tokenIdentifier, &flatKey, label); 519 520 if (publicKeyRef) 521 CFRelease(publicKeyRef); 522 if (label) 523 CFRelease(label); 524 525 return noErr; 526} 527 528void flattenKey(const CssmKey &rawKey, CssmDataContainer &flatKey) 529{ 530 // Flatten the raw input key naively: key header then key data 531 // We also convert it to network byte order in case the referral 532 // record ends up being used on a different machine 533 // A CSSM_KEY is a CSSM_KEYHEADER followed by a CSSM_DATA 534 535 CssmKey rawKeyCopy(rawKey); 536 const uint32 keyDataLength = (uint32)rawKeyCopy.length(); 537 uint32 sz = (sizeof(CSSM_KEY) + keyDataLength); 538 flatKey.Data = flatKey.mAllocator.alloc<uint8>(sz); 539 flatKey.Length = sz; 540 541 Security::h2ni(rawKeyCopy.KeyHeader); // convert it to network byte order 542 543 // Now copy: header, then key struct, then key data 544 memcpy(flatKey.Data, &rawKeyCopy.header(), sizeof(CSSM_KEYHEADER)); 545 memcpy(flatKey.Data + sizeof(CSSM_KEYHEADER), &rawKeyCopy.keyData(), sizeof(CSSM_DATA)); 546 memcpy(flatKey.Data + sizeof(CSSM_KEYHEADER) + sizeof(CSSM_DATA), rawKeyCopy.data(), keyDataLength); 547 // Note that the Data pointer in the CSSM_DATA portion will not be meaningful when unpacked later 548 // We will also fill in the unflattened key length based on the size of flatKey 549} 550 551void systemKeychainCheck() 552{ 553 launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN); 554 launch_data_t checkinResponse = launch_msg(checkinRequest); 555 556 if (!checkinResponse) { 557 syslog(LOG_ERR, "unable to check in with launchd %m"); 558 exit(EX_UNAVAILABLE); 559 } 560 561 launch_data_type_t replyType = launch_data_get_type(checkinResponse); 562 563 if (LAUNCH_DATA_DICTIONARY == replyType) { 564 launch_data_t sockets = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_SOCKETS); 565 if (NULL == sockets) { 566 syslog(LOG_ERR, "Unable to find sockets to listen to"); 567 exit(EX_DATAERR); 568 } 569 launch_data_t listening_fd_array = launch_data_dict_lookup(sockets, "Listener"); 570 if (NULL == listening_fd_array || launch_data_array_get_count(listening_fd_array) <= 0) { 571 syslog(LOG_ERR, "No sockets to listen to"); 572 exit(EX_DATAERR); 573 } 574 575 __block uint32 requestsProcessedSinceLastTimeoutCheck = 0; 576 dispatch_queue_t makeDoneFile = dispatch_queue_create("com.apple.security.make-done-file", NULL); 577 // Use low priority I/O for making the done files -- keeping this process running a little longer 578 // is cheaper then defering other I/O 579 dispatch_set_target_queue(makeDoneFile, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)); 580 // We suspend makeDoneFile until the keychain check is complete 581 dispatch_suspend(makeDoneFile); 582 583 // In theory there can be multiple sockets to listen to, and for the sake of not letting you set something 584 // up in launchd that doesn't get handled we have a little loop. 585 for(unsigned int i = 0; i < launch_data_array_get_count(listening_fd_array); i++) { 586 int serverSocket = launch_data_get_fd(launch_data_array_get_index(listening_fd_array, i)); 587 // Note the target queue is the main queue so the handler will not run until after we check the system keychain 588 dispatch_source_t connectRequest = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, serverSocket, 0, dispatch_get_main_queue()); 589 dispatch_source_set_event_handler(connectRequest, ^{ 590 sockaddr_un remoteAddress; 591 socklen_t remoteAddressLength = sizeof(remoteAddress); 592 int connection = accept(serverSocket, (sockaddr*)&remoteAddress, &remoteAddressLength); 593 if (connection < 0) { 594 syslog(LOG_NOTICE, "accept failure %m"); 595 } else { 596 close(connection); 597 } 598 599 if (requestsProcessedSinceLastTimeoutCheck < INT_MAX) { 600 requestsProcessedSinceLastTimeoutCheck++; 601 } 602 }); 603 604 dispatch_async(makeDoneFile, ^{ 605 // All of the errors in here are "merely" logged, failure to write the done file just 606 // means we will be spawned at some future time and given a chance to do it again. 607 struct sockaddr_un socketName; 608 socklen_t socketNameLength = sizeof(socketName); 609 int rc = getsockname(serverSocket, (struct sockaddr *)&socketName, &socketNameLength); 610 if (0 != rc) { 611 syslog(LOG_ERR, "Can't getsockname fd#%d %m", serverSocket); 612 return; 613 } 614 615 // If searchFor or replaceWith is ever changed, make sure strlen(searchFor) <= strlen(replaceWith), 616 // or change the code below to check for overflowing sun_path's length. 617 const char *searchFor = ".socket"; 618 const char *replaceWith = ".done"; 619 size_t searchForLength = strlen(searchFor); 620 size_t pathLength = strlen(socketName.sun_path); 621 if (pathLength > searchForLength && socketName.sun_path[0] == '/') { 622 strcpy(socketName.sun_path + pathLength - searchForLength, replaceWith); 623 syslog(LOG_ERR, "done file: %s", socketName.sun_path); 624 int fd = open(socketName.sun_path, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR|S_IRGRP|S_IROTH); 625 if (fd < 0) { 626 syslog(LOG_ERR, "Could not create done file %s: %m", socketName.sun_path); 627 return; 628 } else { 629 if (-1 == close(fd)) { 630 syslog(LOG_ERR, "Could not close file %s (fd#%d): %m", socketName.sun_path, fd); 631 return; 632 } 633 } 634 } else { 635 syslog(LOG_ERR, "Unexpected socket name %s", socketName.sun_path); 636 } 637 }); 638 639 dispatch_resume(connectRequest); 640 } 641 642 launch_data_free(checkinResponse); 643 644 int64_t timeoutInterval = 30 * NSEC_PER_SEC; 645 dispatch_source_t timeoutCheck = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); 646 dispatch_source_set_event_handler(timeoutCheck, ^{ 647 if (requestsProcessedSinceLastTimeoutCheck) { 648 requestsProcessedSinceLastTimeoutCheck = 0; 649 } else { 650 exit(0); 651 } 652 }); 653 dispatch_source_set_timer(timeoutCheck, dispatch_time(DISPATCH_TIME_NOW, timeoutInterval), timeoutInterval, timeoutInterval / 4); 654 dispatch_async(makeDoneFile, ^{ 655 // The idle timeout starts after we write the done file 656 dispatch_resume(timeoutCheck); 657 }); 658 659 int rc = system("/usr/libexec/security-checksystem"); 660 if (rc) { 661 int exit_code = EX_SOFTWARE; 662 if (rc < 0) { 663 syslog(LOG_ERR, "Failure %d running security-checksystem", rc); 664 } else { 665 syslog(LOG_ERR, "Could not run security-checksystem failure %m"); 666 exit_code = EX_OSERR; 667 } 668 669 exit(exit_code); 670 } 671 672 // Start the first idle check after we handle the first batch of requests (this makes 673 // no difference unless the keychain check somehow takes more then timeoutInterval +/-25%, 674 // but will prevent a premature exit in that unlikely case). 675 dispatch_async(dispatch_get_main_queue(), ^{ 676 dispatch_resume(makeDoneFile); 677 }); 678 679 dispatch_main(); 680 // NOTE: dispatch_main does not return. 681 } else if (LAUNCH_DATA_ERRNO == replyType) { 682 errno = launch_data_get_errno(checkinResponse); 683 syslog(LOG_ERR, "launchd checkin error %m"); 684 exit(EX_OSERR); 685 } else { 686 syslog(LOG_ERR, "launchd unexpected message type: %d", launch_data_get_type(checkinResponse)); 687 exit(EX_OSERR); 688 } 689} 690 691// 692// Run a simple test to see if the system-root keychain can auto-unlock. 693// This isn't trying really hard to diagnose any problems; it's just a yay-or-nay check. 694// 695void test(const char *kcName) 696{ 697 CSP csp(gGuidAppleCSPDL); 698 DL dl(gGuidAppleCSPDL); 699 700 // lock, then unlock the keychain 701 Db db(dl, kcName); 702 printf("Testing system unlock of %s\n", kcName); 703 printf("(If you are prompted for a passphrase, cancel)\n"); 704 try { 705 db->lock(); 706 db->unlock(); 707 notice("System unlock is working"); 708 } catch (...) { 709 fail("System unlock is NOT working\n"); 710 } 711} 712 713 714// 715// Utility functions 716// 717void labelForMasterKey(Db &db, CssmOwnedData &label) 718{ 719 // create a random signature 720 char signature[8]; 721 UniformRandomBlobs<DevRandomGenerator>().random(signature); 722 723 // concatenate prefix string with random signature 724 label = StringData("*UNLOCK*"); // 8 bytes exactly 725 label.append(signature, sizeof(signature)); 726 assert(label.length() == 8 + sizeof(signature)); 727} 728 729 730void deleteKey(Db &db, const CssmData &label) 731{ 732 DbCursor search(db); 733 search->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY); 734 search->add(CSSM_DB_EQUAL, dlInfoLabel, label); 735 DbUniqueRecord id; 736 if (search->next(NULL, NULL, id)) 737 id->deleteRecord(); 738} 739 740 741// 742// Message helpers 743// 744void notice(const char *fmt, ...) 745{ 746 if (verbose) { 747 va_list args; 748 va_start(args, fmt); 749 vprintf(fmt, args); 750 putchar('\n'); 751 va_end(args); 752 } 753} 754 755void fail(const char *fmt, ...) 756{ 757 va_list args; 758 va_start(args, fmt); 759 vprintf(fmt, args); 760 putchar('\n'); 761 va_end(args); 762 exit(1); 763} 764