systemkeychain.cpp [plain text]
#include <security_cdsa_client/securestorage.h>
#include <security_cdsa_client/cryptoclient.h>
#include <security_cdsa_utilities/uniformrandom.h>
#include <security_utilities/devrandom.h>
#include <security_utilities/errors.h>
#include <security_cdsa_client/wrapkey.h>
#include <security_cdsa_client/genkey.h>
#include <security_cdsa_utilities/Schema.h>
#include <security_cdsa_utilities/cssmendian.h>
#include <securityd_client/ssblob.h>
#include <securityd_client/ssclient.h>
#include <Security/cssmapple.h>
#include <cstdarg>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <launch.h>
#include <syslog.h>
#include <sysexits.h>
#include <dispatch/dispatch.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "TokenIDHelper.h"
using namespace SecurityServer;
using namespace CssmClient;
using namespace UnixPlusPlus;
static const char *unlockConfig = kSystemUnlockFile;
const char *systemKCName = kSystemKeychainDir kSystemKeychainName;
bool verbose = false;
bool createIfNeeded = false;
bool force = false;
static const CSSM_DB_ATTRIBUTE_INFO dlInfoLabel = {
CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
{(char*)"Label"},
CSSM_DB_ATTRIBUTE_FORMAT_BLOB
};
void usage();
void createSystemKeychain(const char *kcName, const char *passphrase);
void extract(const char *srcName, const char *dstName);
void test(const char *kcName);
void notice(const char *fmt, ...);
void fail(const char *fmt, ...);
void labelForMasterKey(Db &db, CssmOwnedData &data);
void deleteKey(Db &db, const CssmData &label);
void addReferralRecord(Db &srcDb, const DLDbIdentifier &ident, CssmData *refData, CFDataRef labelData);
void createUnlockConfig(CSP &csp, Db &db);
void createAKeychain(const char *kcName, const char *passphrase, CSP &csp, DL &dl, Db &db, Key *masterKeyRef);
OSStatus createTokenProtectedKeychain(const char *kcName);
void createMasterKey(CSP &csp, Key &masterKeyRef);
void flattenKey (const CssmKey &rawKey, CssmDataContainer &flatKey);
void systemKeychainCheck();
int main (int argc, char * argv[])
{
enum Action {
showUsage,
setupSystem,
copyKey,
testUnlock,
tokenProtectedKCCreate,
systemKeychainCheckDaemon
} action = showUsage;
extern int optind;
extern char *optarg;
int arg;
OSStatus status;
while ((arg = getopt(argc, argv, "cCdfk:stvT:")) != -1) {
switch (arg) {
case 'c':
createIfNeeded = true;
break;
case 'C':
action = setupSystem;
break;
case 'd':
action = systemKeychainCheckDaemon;
break;
case 'f':
force = true;
break;
case 'k':
systemKCName = optarg;
break;
case 's':
action = copyKey;
break;
case 't':
action = testUnlock;
break;
case 'v':
verbose = true;
break;
case 'T': action = tokenProtectedKCCreate;
systemKCName = optarg;
break;
default:
usage();
}
}
try {
switch (action) {
case setupSystem:
if (optind < argc - 1)
usage();
createSystemKeychain(systemKCName, argv[optind]);
break;
case copyKey:
if (optind == argc)
usage();
do {
extract(argv[optind], systemKCName);
} while (argv[++optind]);
break;
case testUnlock:
test(systemKCName);
break;
case tokenProtectedKCCreate:
if (status = createTokenProtectedKeychain(systemKCName))
{
cssmPerror("unable to create token protected keychain", status);
exit(1);
}
break;
case systemKeychainCheckDaemon:
systemKeychainCheck();
exit(0);
break;
default:
usage();
}
exit(0);
} catch (const CssmError &error) {
cssmPerror(systemKCName, error.error);
exit(1);
} catch (const UnixError &error) {
fail("%s: %s", systemKCName, strerror(error.error));
exit(1);
} catch (const MacOSError &error) {
cssmPerror(systemKCName, error.error);
exit(1);
}
catch (...) {
fail("Unexpected exception");
exit(1);
}
}
void usage()
{
fprintf(stderr, "Usage: systemkeychain -C [passphrase] # (re)create system root keychain"
"\n\tsystemkeychain [-k destination-keychain] -s source-keychain ..."
"\n\tsystemkeychain -T token-protected-keychain-name"
"\n");
exit(2);
}
void createSystemKeychain(const char *kcName, const char *passphrase)
{
if (!strcmp(kcName, kSystemKeychainDir kSystemKeychainName))
::mkdir(kSystemKeychainDir, 0755);
CSP csp(gGuidAppleCSPDL);
DL dl(gGuidAppleCSPDL);
Db db(dl, kcName);
Key masterKey;
createMasterKey(csp, masterKey);
createAKeychain(kcName, passphrase, csp, dl, db, &masterKey);
createUnlockConfig(csp, db);
notice("%s installed as system keychain", kcName);
}
void createAKeychain(const char *kcName, const char *passphrase, CSP &csp, DL &dl, Db &db, Key *masterKeyRef)
{
Allocator &alloc = db->allocator();
AutoCredentials cred(alloc); CSSM_CSP_HANDLE cspHandle = csp->handle();
if (passphrase) {
cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
new(alloc) ListElement(StringData(passphrase)));
cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
new(alloc) ListElement(CSSM_SAMPLE_TYPE_PASSWORD),
new(alloc) ListElement(StringData(passphrase)));
db->accessCredentials(&cred);
}
else
{
notice("warning: this keychain cannot be unlocked with any passphrase");
if (!masterKeyRef) MacOSError::throwMe(paramErr);
cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_CHANGE_LOCK,
new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
new(alloc) ListElement(CssmData::wrap(cspHandle)),
new(alloc) ListElement(CssmData::wrap(static_cast<const CssmKey &>(*masterKeyRef))));
cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
new(alloc) ListElement(CssmData::wrap(cspHandle)),
new(alloc) ListElement(CssmData::wrap(static_cast<const CssmKey &>(*masterKeyRef))),
new(alloc) ListElement(CssmData()));
db->accessCredentials(&cred);
}
db->dbInfo(&KeychainCore::Schema::DBInfo); try {
db->create();
} catch (const CssmError &error) {
if (error.error == CSSMERR_DL_DATASTORE_ALREADY_EXISTS && force) {
notice("recreating %s", kcName);
unlink(kcName);
db->create();
} else
throw;
}
chmod(db->name(), 0644);
}
void createMasterKey(CSP &csp, Key &masterKeyRef)
{
CssmClient::GenerateKey generate(csp, CSSM_ALGID_3DES_3KEY_EDE, 64 * 3);
masterKeyRef = generate(KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE));
}
void createUnlockConfig(CSP &csp, Db &db)
{
DeriveKey derive(csp, CSSM_ALGID_KEYCHAIN_KEY, CSSM_ALGID_3DES_3KEY, 3 * 64);
CSSM_DL_DB_HANDLE dlDb = db->handle();
CssmData dlDbData = CssmData::wrap(dlDb);
CssmKey refKey;
KeySpec spec(CSSM_KEYUSE_ANY,
CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE);
derive(&dlDbData, spec, refKey);
CssmKey rawKey;
WrapKey wrap(csp, CSSM_ALGID_NONE);
wrap(refKey, rawKey);
UnlockBlob blob;
blob.initialize(0);
CssmAutoData dbBlobData(Allocator::standard());
SecurityServer::ClientSession client(dbBlobData.allocator, dbBlobData.allocator);
db->copyBlob(dbBlobData.get());
DbBlob *dbBlob = dbBlobData.get().interpretedAs<DbBlob>();
memcpy(&blob.signature, &dbBlob->randomSignature, sizeof(dbBlob->randomSignature));
memcpy(blob.masterKey, rawKey.data(), sizeof(blob.masterKey));
string tempFile(string(unlockConfig) + ",");
FileDesc blobFile(tempFile, O_WRONLY | O_CREAT | O_TRUNC, 0400);
if (blobFile.write(blob) != sizeof(blob)) {
unlink(tempFile.c_str());
fail("unable to write %s", tempFile.c_str());
}
blobFile.close();
::rename(tempFile.c_str(), unlockConfig);
}
void extract(const char *srcName, const char *dstName)
{
using namespace KeychainCore;
CSPDL cspdl(gGuidAppleCSPDL);
Db srcDb(cspdl, srcName);
Db dstDb(cspdl, dstName);
try {
dstDb->open();
} catch (const CssmError &err) {
if (err.error == CSSMERR_DL_DATASTORE_DOESNOT_EXIST && createIfNeeded) {
notice("creating %s", dstName);
dstDb->create();
} else
throw;
}
DeriveKey derive(cspdl, CSSM_ALGID_KEYCHAIN_KEY, CSSM_ALGID_3DES_3KEY, 3 * 64);
CSSM_DL_DB_HANDLE dstDlDb = dstDb->handle();
derive.add(CSSM_ATTRIBUTE_DL_DB_HANDLE, dstDlDb);
CSSM_DL_DB_HANDLE srcDlDb = srcDb->handle();
CssmData dlDbData = CssmData::wrap(srcDlDb);
CssmAutoData keyLabel(Allocator::standard());
labelForMasterKey(srcDb, keyLabel);
KeySpec spec(CSSM_KEYUSE_ANY,
CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_SENSITIVE,
keyLabel);
CssmKey masterKey;
try {
derive(&dlDbData, spec, masterKey);
} catch (const CssmError &error) {
if (error.error != CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA)
throw;
if (!force)
fail("existing key in %s not overwritten. Use -f to replace it.", dstDb->name());
notice("replacing existing record in %s", dstDb->name());
deleteKey(dstDb, keyLabel);
derive(&dlDbData, spec, masterKey);
}
addReferralRecord(srcDb, dstDb->dlDbIdentifier(), NULL, NULL);
notice("%s can now be unlocked with a key in %s", srcName, dstName);
}
void addReferralRecord(Db &srcDb, const DLDbIdentifier &ident, CssmData *refData, CFDataRef label)
{
using namespace KeychainCore;
uint32 referralType = (!refData)?CSSM_APPLE_UNLOCK_TYPE_KEY_DIRECT:CSSM_APPLE_UNLOCK_TYPE_WRAPPED_PRIVATE;
try {
CssmAutoData masterKeyLabel(Allocator::standard());
CssmData externalLabel;
if (label)
{
externalLabel.Data = const_cast<uint8 *>(CFDataGetBytePtr(label));
externalLabel.Length = CFDataGetLength(label);
}
else
labelForMasterKey(srcDb, masterKeyLabel);
CssmAutoDbRecordAttributeData refAttrs(9);
refAttrs.add(Schema::kUnlockReferralType, uint32(referralType));
refAttrs.add(Schema::kUnlockReferralDbName, ident.dbName());
refAttrs.add(Schema::kUnlockReferralDbNetname, CssmData());
refAttrs.add(Schema::kUnlockReferralDbGuid, ident.ssuid().guid());
refAttrs.add(Schema::kUnlockReferralDbSSID, ident.ssuid().subserviceId());
refAttrs.add(Schema::kUnlockReferralDbSSType, ident.ssuid().subserviceType());
refAttrs.add(Schema::kUnlockReferralKeyLabel, label?externalLabel:masterKeyLabel.get());
refAttrs.add(Schema::kUnlockReferralKeyAppTag, CssmData());
refAttrs.add(Schema::kUnlockReferralPrintName,
StringData("Keychain Unlock Referral Record"));
CssmData emptyRefData;
if (!refData)
refData = &emptyRefData;
try {
srcDb->insert(CSSM_DL_DB_RECORD_UNLOCK_REFERRAL,
&refAttrs,
refData);
secdebug("kcreferral", "referral record stored in %s", srcDb->name());
} catch (const CssmError &e) {
if (e.error != CSSMERR_DL_INVALID_RECORDTYPE)
throw;
secdebug("kcreferral", "adding referral schema relation to %s", srcDb->name());
srcDb->createRelation(CSSM_DL_DB_RECORD_UNLOCK_REFERRAL, "CSSM_DL_DB_RECORD_UNLOCK_REFERRAL",
Schema::UnlockReferralSchemaAttributeCount,
Schema::UnlockReferralSchemaAttributeList,
Schema::UnlockReferralSchemaIndexCount,
Schema::UnlockReferralSchemaIndexList);
srcDb->insert(CSSM_DL_DB_RECORD_UNLOCK_REFERRAL,
&refAttrs,
refData);
secdebug("kcreferral", "referral record inserted in %s (on retry)", srcDb->name());
}
} catch (...) {
notice("kcreferral", "cannot store referral in %s", srcDb->name());
throw;
}
}
#include <Security/SecKey.h>
#include <Security/SecKeychain.h>
#include <Security/SecKeychainItem.h>
OSStatus createTokenProtectedKeychain(const char *kcName)
{
SecKeychainRef keychainRef = NULL;
SecKeyRef publicKeyRef = NULL;
const CSSM_KEY *pcssmKey;
CssmKey pubEncryptionKey;
CssmKey nullWrappedPubEncryptionKey;
CFDataRef label = NULL;
OSStatus status = findFirstEncryptionPublicKeyOnToken(&publicKeyRef, &keychainRef, &label);
if (status)
return status;
status = SecKeyGetCSSMKey(publicKeyRef, &pcssmKey);
if (status)
return status;
pubEncryptionKey = *pcssmKey;
CSSM_DL_DB_HANDLE dstdldbHandle;
status = SecKeychainGetDLDBHandle(keychainRef, &dstdldbHandle);
if (status)
return status;
char *tokenName;
CSSM_SUBSERVICE_UID tokenUID;
CSSM_RETURN cx;
cx = CSSM_DL_GetDbNameFromHandle (dstdldbHandle, &tokenName);
if (cx)
return cx;
cx = CSSM_GetSubserviceUIDFromHandle (dstdldbHandle.DLHandle, &tokenUID);
if (cx)
return cx;
secdebug("kcreferral", "Key found on token with subserviceId: %d", tokenUID.SubserviceId);
DLDbIdentifier tokenIdentifier(tokenName, Guid(gGuidAppleSdCSPDL), tokenUID.SubserviceId,
CSSM_SERVICE_CSP | CSSM_SERVICE_DL , NULL);
CSP csp(gGuidAppleCSPDL);
DL dl(gGuidAppleCSPDL);
Db srcdb(dl, kcName);
Key masterKey;
createMasterKey(csp, masterKey);
createAKeychain(kcName, NULL, csp, dl, srcdb, &masterKey);
CssmClient::Key publicEncryptionKey(csp, pubEncryptionKey, true);
UnwrapKey tunwrap(csp, CSSM_ALGID_NONE);
CSSM_KEYUSE uu = CSSM_KEYUSE_ANY;
CSSM_KEYATTR_FLAGS attrs = CSSM_KEYATTR_RETURN_REF | CSSM_KEYATTR_EXTRACTABLE;
tunwrap(publicEncryptionKey, KeySpec(uu, attrs), nullWrappedPubEncryptionKey);
CssmClient::Key nullWrappedPubEncryptionKeyX(csp, nullWrappedPubEncryptionKey, true);
CssmKey wrappedKey;
WrapKey wrap(csp, CSSM_ALGID_RSA); wrap.key(nullWrappedPubEncryptionKeyX);
wrap.mode(CSSM_ALGMODE_NONE);
wrap.padding(CSSM_PADDING_PKCS1);
wrap.add(CSSM_ATTRIBUTE_WRAPPED_KEY_FORMAT, uint32(CSSM_KEYBLOB_WRAPPED_FORMAT_PKCS7));
wrap(masterKey, wrappedKey);
CssmDataContainer flatKey;
flattenKey(wrappedKey, flatKey);
addReferralRecord(srcdb, tokenIdentifier, &flatKey, label);
if (publicKeyRef)
CFRelease(publicKeyRef);
if (label)
CFRelease(label);
return noErr;
}
void flattenKey(const CssmKey &rawKey, CssmDataContainer &flatKey)
{
CssmKey rawKeyCopy(rawKey);
const uint32 keyDataLength = rawKeyCopy.length();
uint32 sz = (sizeof(CSSM_KEY) + keyDataLength);
flatKey.Data = flatKey.mAllocator.alloc<uint8>(sz);
flatKey.Length = sz;
Security::h2ni(rawKeyCopy.KeyHeader);
memcpy(flatKey.Data, &rawKeyCopy.header(), sizeof(CSSM_KEYHEADER));
memcpy(flatKey.Data + sizeof(CSSM_KEYHEADER), &rawKeyCopy.keyData(), sizeof(CSSM_DATA));
memcpy(flatKey.Data + sizeof(CSSM_KEYHEADER) + sizeof(CSSM_DATA), rawKeyCopy.data(), keyDataLength);
}
void systemKeychainCheck()
{
launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
launch_data_t checkinResponse = launch_msg(checkinRequest);
if (!checkinResponse) {
syslog(LOG_ERR, "unable to check in with launchd %m");
exit(EX_UNAVAILABLE);
}
launch_data_type_t replyType = launch_data_get_type(checkinResponse);
if (LAUNCH_DATA_DICTIONARY == replyType) {
launch_data_t sockets = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_SOCKETS);
if (NULL == sockets) {
syslog(LOG_ERR, "Unable to find sockets to listen to");
exit(EX_DATAERR);
}
launch_data_t listening_fd_array = launch_data_dict_lookup(sockets, "Listener");
if (NULL == listening_fd_array || launch_data_array_get_count(listening_fd_array) <= 0) {
syslog(LOG_ERR, "No sockets to listen to");
exit(EX_DATAERR);
}
__block uint32 requestsProcessedSinceLastTimeoutCheck = 0;
dispatch_queue_t makeDoneFile = dispatch_queue_create("com.apple.security.make-done-file", NULL);
dispatch_set_target_queue(makeDoneFile, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_suspend(makeDoneFile);
for(unsigned int i = 0; i < launch_data_array_get_count(listening_fd_array); i++) {
int serverSocket = launch_data_get_fd(launch_data_array_get_index(listening_fd_array, i));
dispatch_source_t connectRequest = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, serverSocket, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(connectRequest, ^{
sockaddr_un remoteAddress;
socklen_t remoteAddressLength = sizeof(remoteAddress);
int connection = accept(serverSocket, (sockaddr*)&remoteAddress, &remoteAddressLength);
if (connection < 0) {
syslog(LOG_NOTICE, "accept failure %m");
} else {
close(connection);
}
if (requestsProcessedSinceLastTimeoutCheck < INT_MAX) {
requestsProcessedSinceLastTimeoutCheck++;
}
});
dispatch_async(makeDoneFile, ^{
struct sockaddr_un socketName;
socklen_t socketNameLength = sizeof(socketName);
int rc = getsockname(serverSocket, (struct sockaddr *)&socketName, &socketNameLength);
if (0 != rc) {
syslog(LOG_ERR, "Can't getsockname fd#%d %m", serverSocket);
return;
}
const char *searchFor = ".socket";
const char *replaceWith = ".done";
int searchForLength = strlen(searchFor);
int pathLength = strlen(socketName.sun_path);
if (pathLength > searchForLength && socketName.sun_path[0] == '/') {
strcpy(socketName.sun_path + pathLength - searchForLength, replaceWith);
syslog(LOG_ERR, "done file: %s", socketName.sun_path);
int fd = open(socketName.sun_path, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR|S_IRGRP|S_IROTH);
if (fd < 0) {
syslog(LOG_ERR, "Could not create done file %s: %m", socketName.sun_path);
return;
} else {
if (-1 == close(fd)) {
syslog(LOG_ERR, "Could not close file %s (fd#%d): %m", socketName.sun_path, fd);
return;
}
}
} else {
syslog(LOG_ERR, "Unexpected socket name %s", socketName.sun_path);
}
});
dispatch_resume(connectRequest);
}
launch_data_free(checkinResponse);
int64_t timeoutInterval = 30 * NSEC_PER_SEC;
dispatch_source_t timeoutCheck = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(timeoutCheck, ^{
if (requestsProcessedSinceLastTimeoutCheck) {
requestsProcessedSinceLastTimeoutCheck = 0;
} else {
exit(0);
}
});
dispatch_source_set_timer(timeoutCheck, dispatch_time(DISPATCH_TIME_NOW, timeoutInterval), timeoutInterval, timeoutInterval / 4);
dispatch_async(makeDoneFile, ^{
dispatch_resume(timeoutCheck);
});
int rc = system("/usr/libexec/security-checksystem");
if (rc) {
int exit_code = EX_SOFTWARE;
if (rc < 0) {
syslog(LOG_ERR, "Failure %d running security-checksystem");
} else {
syslog(LOG_ERR, "Could not run security-checksystem failure %m");
exit_code = EX_OSERR;
}
exit(exit_code);
}
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_resume(makeDoneFile);
});
dispatch_main();
} else if (LAUNCH_DATA_ERRNO == replyType) {
errno = launch_data_get_errno(checkinResponse);
syslog(LOG_ERR, "launchd checkin error %m");
exit(EX_OSERR);
} else {
syslog(LOG_ERR, "launchd unexpected message type: %d", launch_data_get_type(checkinResponse));
exit(EX_OSERR);
}
}
void test(const char *kcName)
{
CSP csp(gGuidAppleCSPDL);
DL dl(gGuidAppleCSPDL);
Db db(dl, kcName);
printf("Testing system unlock of %s\n", kcName);
printf("(If you are prompted for a passphrase, cancel)\n");
try {
db->lock();
db->unlock();
notice("System unlock is working");
} catch (...) {
fail("System unlock is NOT working\n");
}
}
void labelForMasterKey(Db &db, CssmOwnedData &label)
{
char signature[8];
UniformRandomBlobs<DevRandomGenerator>().random(signature);
label = StringData("*UNLOCK*"); label.append(signature, sizeof(signature));
assert(label.length() == 8 + sizeof(signature));
}
void deleteKey(Db &db, const CssmData &label)
{
DbCursor search(db);
search->recordType(CSSM_DL_DB_RECORD_SYMMETRIC_KEY);
search->add(CSSM_DB_EQUAL, dlInfoLabel, label);
DbUniqueRecord id;
if (search->next(NULL, NULL, id))
id->deleteRecord();
}
void notice(const char *fmt, ...)
{
if (verbose) {
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
putchar('\n');
va_end(args);
}
}
void fail(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
putchar('\n');
va_end(args);
exit(1);
}