// // Perform any preambles required to be a securityd client in good standing. // This includes initial setup calls, thread registration, fork management, // and (Code Signing) guest status. // void ClientSession::activate() { // Guard against fork-without-exec. If we are the child of a fork // (that has not exec'ed), our apparent connection to SecurityServer // is just a mirage, and we better reset it. if (mHasForked()) { secinfo("SSclnt", "process has forked (now pid=%d) - resetting connection object", getpid()); mGlobal.reset(); } // now pick up the (new or existing) connection state Global &global = mGlobal(); Thread &thread = global.thread(); if (!thread) { // first time for this thread - use abbreviated registration IPCN(ucsp_client_setupThread(UCSP_ARGS, mach_task_self())); thread.registered = true; secinfo("SSclnt", "Thread registered with %s", mContactName); } // if the thread's guest state has changed, tell securityd if (thread.currentGuest != thread.lastGuest) { IPCN(ucsp_client_setGuest(UCSP_ARGS, thread.currentGuest, kSecCSDefaultFlags)); thread.lastGuest = thread.currentGuest; secinfo("SSclnt", "switched guest state to 0x%x", thread.currentGuest); } }
void PCSCMonitor::loadSoftToken(Bundle *tokendBundle) { try { string bundleName = tokendBundle->identifier(); // prepare a virtual reader, removing any existing one (this would kill a previous tokend) assert(mReaders.find(bundleName) == mReaders.end()); // not already present RefPointer<Reader> reader = new Reader(tokenCache(), bundleName); // now launch the tokend RefPointer<TokenDaemon> tokend = new TokenDaemon(tokendBundle, reader->name(), reader->pcscState(), reader->cache); if (tokend->state() == ServerChild::dead) { // ah well, this one's no good secinfo("pcsc", "softtoken %s tokend launch failed", bundleName.c_str()); Syslog::notice("Software token %s failed to run", tokendBundle->canonicalPath().c_str()); return; } // probe the (single) tokend if (!tokend->probe()) { // non comprende... secinfo("pcsc", "softtoken %s probe failed", bundleName.c_str()); Syslog::notice("Software token %s refused operation", tokendBundle->canonicalPath().c_str()); return; } // okay, this seems to work. Set it up mReaders.insert(make_pair(reader->name(), reader)); reader->insertToken(tokend); Syslog::notice("Software token %s activated", bundleName.c_str()); } catch (...) { secinfo("pcsc", "exception loading softtoken %s - continuing", tokendBundle->identifier().c_str()); } }
PathList::PathList(const string &subPath, const char *suffix /* = NULL */, const char *envar /* = NULL */, bool forUser /* = true */) : mSuffix(suffix) { if (envar) if (const char *envPath = getenv(envar)) { #if !defined(NDEBUG) if (envPath[0] == '!') { // envar="!path" -> single-item override (debugging only) mDebugOverride = envPath + 1; secinfo("pathlist", "%p env(\"%s\") overrides to \"%s\"", this, envar, mDebugOverride.c_str()); return; } #endif //NDEBUG // treat envPath as a classic colon-separated list of directories secinfo("pathlist", "%p configuring from env(\"%s\")", this, envar); while (const char *p = strchr(envPath, ':')) { addDirectory(string(envPath, p - envPath)); envPath = p + 1; } addDirectory(envPath); return; } // no joy from environment variables secinfo("pathlist", "%p configuring from default path set \"%s\"", this, subPath.c_str()); if (forUser) secinfo("pathlist", "user search list not yet implemented"); addDirectory("/Library/" + subPath); addDirectory("/System/Library/" + subPath); }
// // Notify an (interested) caller that a securityd-mediated ACL change // MAY have happened on a key object involved in an operation. This allows // such callers to re-encode key blobs for storage. // void ClientSession::notifyAclChange(KeyHandle key, CSSM_ACL_AUTHORIZATION_TAG tag) { if (mCallback) { secinfo("keyacl", "ACL change key %u operation %u", key, tag); mCallback(mCallbackContext, *this, key, tag); } else secinfo("keyacl", "dropped ACL change notice for key %u operation %u", key, tag); }
// // Common utility for finding the registered securityd port for the current // session. This does not cache the port anywhere, though it does effectively // cache the name. // Port ClientSession::findSecurityd() { if (!mContactName) { mContactName = getenv(SECURITYSERVER_BOOTSTRAP_ENV); if (!mContactName) mContactName = SECURITYSERVER_BOOTSTRAP_NAME; } secinfo("SSclnt", "Locating %s", mContactName); Port serverPort = Bootstrap().lookup2(mContactName); secinfo("SSclnt", "contacting %s at port %d (version %d)", mContactName, serverPort.port(), SSPROTOVERSION); return serverPort; }
// // Remove some types of readers // void PCSCMonitor::clearReaders(Reader::Type type) { if (!mReaders.empty()) { secinfo("pcsc", "%ld readers present - clearing type %d", mReaders.size(), type); for (ReaderMap::iterator it = mReaders.begin(); it != mReaders.end(); ) { ReaderMap::iterator cur = it++; Reader *reader = cur->second; if (reader->isType(type)) { secinfo("pcsc", "removing reader %s", reader->name().c_str()); reader->kill(); // prepare to die mReaders.erase(cur); } } } }
// // Timer action. Perform the initial PCSC subsystem initialization. // This runs (shortly) after securityd is fully functional and the // server loop has started. // void PCSCMonitor::action() { switch (mServiceLevel) { case forcedOff: secinfo("pcsc", "smartcard operation is FORCED OFF"); break; case externalDaemon: secinfo("pcsc", "using PCSC"); startSoftTokens(); // Start PCSC reader watching thread. (new Watcher(server, tokenCache(), mReaders))->run(); break; } }
// // ClearReferences clears the reference set but does not propagate // anything; it is NOT recursive. // void NodeCore::clearReferences() { StLock<Mutex> _(*this); secinfo("ssnode", "%p clearing all %d references", this, int(mReferences.size())); mReferences.erase(mReferences.begin(), mReferences.end()); }
// // "Load" a plugin, given its MDS path. At this layer, we are performing // a purely physical load operation. No code in the plugin is called. // If "built-in plugins" are enabled, the moduleTable will come pre-initialized // with certain paths. Since we consult this table before going to disk, this // means that we'll pick these up first *as long as the paths match exactly*. // There is nothing magical in the path strings themselves, other than by // convention. (The convention is "*NAME", which conveniently does not match // any actual file path.) // Plugin *ModuleLoader::operator () (const string &path) { Plugin * &plugin = mPlugins[path]; if (!plugin) { secinfo("cssm", "ModuleLoader(): creating plugin %s", path.c_str()); plugin = new LoadablePlugin(path.c_str()); } else { secinfo("cssm", "ModuleLoader(): FOUND plugin %s, isLoaded %s", path.c_str(), plugin->isLoaded() ? "TRUE" : "FALSE"); if(!plugin->isLoaded()) { plugin->load(); } } return plugin; }
void SharedMemoryListener::action () { secinfo("notify", "Posted notification to clients."); secdebug("MDSPRIVACY","[%03d] Posted notification to clients", mUID); notify_post (mSegmentName.c_str ()); mActive = false; }
static int nfs_negotiate_security(const struct dentry *parent, const struct dentry *dentry, rpc_authflavor_t *flavor) { struct page *page; struct nfs4_secinfo_flavors *flavors; int (*secinfo)(struct inode *, const struct qstr *, struct nfs4_secinfo_flavors *); int ret = -EPERM; secinfo = NFS_PROTO(parent->d_inode)->secinfo; if (secinfo != NULL) { page = alloc_page(GFP_KERNEL); if (!page) { ret = -ENOMEM; goto out; } flavors = page_address(page); ret = secinfo(parent->d_inode, &dentry->d_name, flavors); *flavor = nfs_find_best_sec(flavors); put_page(page); } out: return ret; }
// // Construct a Process object. // Process::Process(TaskPort taskPort, const ClientSetupInfo *info, const CommonCriteria::AuditToken &audit) : mTaskPort(taskPort), mByteFlipped(false), mPid(mTaskPort.pid()), mUid(0), mGid(0) { StLock<Mutex> _(*this); // set parent session parent(Session::find(audit.sessionId(), true)); // let's take a look at our wannabe client... if (mTaskPort.pid() != mPid) { secnotice("SS", "Task/pid setup mismatch pid=%d task=%d(%d)", mPid, mTaskPort.port(), mTaskPort.pid()); CssmError::throwMe(CSSMERR_CSSM_ADDIN_AUTHENTICATE_FAILED); // you lied! } setup(info); ClientIdentification::setup(this->pid()); // NB: ServerChild::find() should only be used to determine // *existence*. Don't use the returned Child object for anything else, // as it is not protected against its underlying process's destruction. if (this->pid() == getpid() // called ourselves (through some API). Do NOT record this as a "dirty" transaction || ServerChild::find<ServerChild>(this->pid())) // securityd's child; do not mark this txn dirty VProc::Transaction::deactivate(); secinfo("SS", "%p client new: pid:%d session:%d %s taskPort:%d uid:%d gid:%d", this, this->pid(), this->session().sessionId(), (char *)codePath(this->processCode()).c_str(), taskPort.port(), mUid, mGid); }
// // Notification message objects // Listener::Notification::Notification(NotificationDomain inDomain, NotificationEvent inEvent, uint32 seq, const CssmData &inData) : domain(inDomain), event(inEvent), sequence(seq), data(Allocator::standard(), inData) { secinfo("notify", "%p notification created domain 0x%x event %d seq %d", this, domain, event, sequence); }
// // Initialize the blob header for a given version // void CommonBlob::initialize(uint32 version) { magic = magicNumber; secinfo("integrity", "creating a keychain with version %d", version); this->blobVersion = version; }
// // What our (non-primary) load threads do // void MachServer::LoadThread::action() { //@@@ race condition?! can server exit before helpers thread gets here? // register the worker thread and go server.addThread(this); try { secinfo("machserver", "start thread"); server.runServerThread(true); secinfo("machserver", "end thread"); } catch (...) { // fell out of server loop by error. Let the thread go quietly secinfo("machserver", "end thread (due to error)"); } server.removeThread(this); }
// // Clean up a Process object // Process::~Process() { secinfo("SS", "%p client release: %d", this, this->pid()); // release our name for the process's task port if (mTaskPort) mTaskPort.destroy(); }
// // Register a memory block for deferred release. // void MachServer::releaseWhenDone(Allocator &alloc, void *memory) { if (memory) { set<Allocation> &releaseSet = perThread().deferredAllocations; assert(releaseSet.find(Allocation(memory, alloc)) == releaseSet.end()); secinfo("machserver", "allocing register %p with alloc %p", memory, &alloc); releaseSet.insert(Allocation(memory, alloc)); } }
// // Handle a port death or deallocation by removing all Listeners using that port. // Returns true iff we had one. // bool Listener::remove(Port port) { typedef ListenerMap::iterator Iterator; StLock<Mutex> _(setLock); pair<Iterator, Iterator> range = listeners.equal_range(port); if (range.first == range.second) return false; // not one of ours assert(range.first != listeners.end()); secinfo("notify", "remove port %d", port.port()); #if !defined(NDEBUG) for (Iterator it = range.first; it != range.second; it++) { assert(it->first == port); secinfo("notify", "%p listener removed", it->second.get()); } #endif //NDEBUG listeners.erase(range.first, range.second); port.destroy(); return true; // got it }
// // Listener basics // Listener::Listener(NotificationDomain dom, NotificationMask evs, mach_port_t port) : domain(dom), events(evs) { assert(events); // what's the point? // register in listener set StLock<Mutex> _(setLock); listeners.insert(ListenerMap::value_type(port, this)); secinfo("notify", "%p created for domain 0x%x events 0x%x port %d", this, dom, evs, port); }
RefPointer<Listener::Notification> Listener::JitterBuffer::popNotification() { JBuffer::iterator it = mBuffer.find(mNotifyLast + 1); // have next message? if (it == mBuffer.end()) return NULL; // nothing here else { RefPointer<Notification> result = it->second; // save value mBuffer.erase(it); // remove from buffer secinfo("notify-jit", "%p retrieved from jitter buffer", result.get()); return result; // return it } }
// // Initiate service. // This call will take control of the current thread and use it to service // incoming requests. The thread will not be released until an error happens, which // will cause an exception to be thrown. In other words, this never returns normally. // We may also be creating additional threads to service concurrent requests // as appropriate. // @@@ Msg-errors in additional threads are not acted upon. // void MachServer::run(mach_msg_size_t maxSize, mach_msg_options_t options) { // establish server-global (thread-shared) parameters mMaxSize = maxSize; mMsgOptions = options; // establish the thread pool state // (don't need managerLock since we're the only thread as of yet) idleCount = workerCount = 1; nextCheckTime = Time::now() + workerTimeout; leastIdleWorkers = 1; highestWorkerCount = 1; // run server loop in initial (immortal) thread secinfo("machserver", "start thread"); runServerThread(false); secinfo("machserver", "end thread"); // primary server thread exited somehow (not currently possible) assert(false); }
// // Jitter buffering // bool Listener::JitterBuffer::inSequence(Notification *message) { if (message->sequence == mNotifyLast + 1) { // next in sequence mNotifyLast++; // record next sequence return true; // go ahead } else { secinfo("notify-jit", "%p out of sequence (last %d got %d); buffering", message, mNotifyLast, message->sequence); mBuffer[message->sequence] = message; // save for later return false; // hold your fire } }
// // Run through the accumulated deferred allocations and release them. // This is done automatically on every pass through the server loop; // it must be called by subclasses that implement their loop in some // other way. // @@@X Needs to be thread local // void MachServer::releaseDeferredAllocations() { set<Allocation> &releaseSet = perThread().deferredAllocations; for (set<Allocation>::iterator it = releaseSet.begin(); it != releaseSet.end(); it++) { secinfo("machserver", "releasing alloc at %p with %p", it->addr, it->allocator); // before we release the deferred allocation, zap it so that secrets aren't left in memory size_t memSize = malloc_size(it->addr); bzero(it->addr, memSize); it->allocator->free(it->addr); } releaseSet.erase(releaseSet.begin(), releaseSet.end()); }
bool MachServer::processTimer() { Timer *top; { StLock<Mutex> _(managerLock); // could have multiple threads trying this if (!(top = static_cast<Timer *>(timers.pop(Time::now())))) return false; // nothing (more) to be done now } // drop lock; work has been retrieved try { secinfo("machserver", "timer start: %p, %d, %f", top, top->longTerm(), Time::now().internalForm()); StLock<MachServer::Timer, &MachServer::Timer::select, &MachServer::Timer::unselect> _t(*top); if (top->longTerm()) { StLock<MachServer, &MachServer::busy, &MachServer::idle> _(*this); top->action(); } else { top->action(); } secinfo("machserver", "timer end (false)"); } catch (...) { secinfo("machserver", "timer end (true)"); } return true; }
CFDataRef SecFDERecoveryUnwrapCRSKWithPrivKey(SecKeychainRef keychain, const FVPrivateKeyHeader *inHeader) { CFDataRef result = NULL; OSStatus __secapiresult = 0; try { result = decodePrivateKeyHeader(keychain, Required(inHeader)); } catch (const MacOSError &err) { __secapiresult=err.osStatus(); } catch (const CommonError &err) { __secapiresult=SecKeychainErrFromOSStatus(err.osStatus()); } catch (const std::bad_alloc &) { __secapiresult=errSecAllocate; } catch (...) { __secapiresult=errSecInternalComponent; } secinfo("FDERecovery", "SecFDERecoveryUnwrapCRSKWithPrivKey: %d", (int)__secapiresult); return result; }
static void encodePrivateKeyHeader(const CssmData &inBlob, CFDataRef certificate, FVPrivateKeyHeader &outHeader) { CssmClient::CL cl(gGuidAppleX509CL); const CssmData cert(const_cast<UInt8 *>(CFDataGetBytePtr(certificate)), CFDataGetLength(certificate)); CSSM_KEY_PTR key; if (CSSM_RETURN rv = CSSM_CL_CertGetKeyInfo(cl->handle(), &cert, &key)) CssmError::throwMe(rv); Security::CssmClient::CSP fCSP(gGuidAppleCSP); // Set it up so the cl is used to free key and key->KeyData // CssmAutoData _keyData(cl.allocator()); // _keyData.set(CssmData::overlay(key->KeyData)); CssmAutoData _key(cl.allocator()); _key.set(reinterpret_cast<uint8 *>(key), sizeof(*key)); CssmClient::Key cKey(fCSP, *key); /* Given a CSSM_KEY_PTR in any format, obtain the SHA-1 hash of the * associated key blob. * Key is specified in CSSM_CSP_CreatePassThroughContext. * Hash is allocated by the CSP, in the App's memory, and returned * in *outData. */ CssmClient::PassThrough passThrough(fCSP); passThrough.key(key); void *outData; passThrough(CSSM_APPLECSP_KEYDIGEST, NULL, &outData); CssmData *cssmData = reinterpret_cast<CssmData *>(outData); assert(cssmData->Length <= sizeof(outHeader.publicKeyHash)); outHeader.publicKeyHashSize = (uint32_t)cssmData->Length; memcpy(outHeader.publicKeyHash, cssmData->Data, cssmData->Length); fCSP.allocator().free(cssmData->Data); fCSP.allocator().free(cssmData); /* Now encrypt the blob with the public key. */ CssmClient::Encrypt encrypt(fCSP, key->KeyHeader.AlgorithmId); encrypt.key(cKey); CssmData clearBuf(outHeader.encryptedBlob, sizeof(outHeader.encryptedBlob)); CssmAutoData remData(fCSP.allocator()); encrypt.padding(CSSM_PADDING_PKCS1); outHeader.encryptedBlobSize = (uint32_t)encrypt.encrypt(inBlob, clearBuf, remData.get()); if (outHeader.encryptedBlobSize > sizeof(outHeader.encryptedBlob)) secinfo("FDERecovery", "encodePrivateKeyHeader: encrypted blob too big: %d", outHeader.encryptedBlobSize); }
// // Construct the process-global state object. // The ModuleNexus construction magic will ensure that this happens uniquely // even if the face of multithreaded attack. // ClientSession::Global::Global() { // find server port serverPort = findSecurityd(); mach_port_t originPort = MACH_PORT_NULL; IPCN(ucsp_client_verifyPrivileged2(serverPort.port(), mig_get_reply_port(), &securitydCreds, &rcode, &originPort)); if (originPort != serverPort.port()) CssmError::throwMe(CSSM_ERRCODE_VERIFICATION_FAILURE); mach_port_mod_refs(mach_task_self(), originPort, MACH_PORT_RIGHT_SEND, -1); // send identification/setup message static const char extForm[] = "?:obsolete"; ClientSetupInfo info = { 0x1234, SSPROTOVERSION }; // cannot use UCSP_ARGS here because it uses mGlobal() -> deadlock Thread &thread = this->thread(); IPCN(ucsp_client_setup(serverPort, thread.replyPort, &securitydCreds, &rcode, mach_task_self(), info, extForm)); thread.registered = true; // as a side-effect of setup call above IFDEBUG(serverPort.requestNotify(thread.replyPort)); secinfo("SSclnt", "contact with %s established", mContactName); }
CFDataRef decodePrivateKeyHeader(SecKeychainRef keychain, const FVPrivateKeyHeader &inHeader) { // kSecKeyLabel is defined in libsecurity_keychain/lib/SecKey.h SecKeychainAttribute attrs[] = { { 6 /* kSecKeyLabel */, inHeader.publicKeyHashSize, const_cast<uint8 *>(inHeader.publicKeyHash) } }; SecKeychainAttributeList attrList = { sizeof(attrs) / sizeof(SecKeychainAttribute), attrs }; CSSM_CSP_HANDLE cspHandle = 0; const CSSM_KEY *cssmKey = NULL; const CSSM_ACCESS_CREDENTIALS *accessCred = NULL; CSSM_CC_HANDLE cc = 0; SecKeychainSearchRef _searchRef; throwIfError(SecKeychainSearchCreateFromAttributes(keychain, (SecItemClass) CSSM_DL_DB_RECORD_PRIVATE_KEY, &attrList, &_searchRef)); CFRef<SecKeychainSearchRef> searchRef(_searchRef); SecKeychainItemRef _item; if (SecKeychainSearchCopyNext(searchRef, &_item) != 0) { return NULL; // XXX possibly should throw here? } CFRef<SecKeyRef> keyItem(reinterpret_cast<SecKeyRef>(_item)); throwIfError(SecKeyGetCSPHandle(keyItem, &cspHandle)); throwIfError(SecKeyGetCSSMKey(keyItem, &cssmKey)); throwIfError(SecKeyGetCredentials(keyItem, CSSM_ACL_AUTHORIZATION_DECRYPT, kSecCredentialTypeDefault, &accessCred)); throwIfError(CSSM_CSP_CreateAsymmetricContext(cspHandle, cssmKey->KeyHeader.AlgorithmId, accessCred, cssmKey, CSSM_PADDING_PKCS1, &cc)); CFDataRef result; try { CssmMemoryFunctions memFuncs; throwIfError(CSSM_GetAPIMemoryFunctions(cspHandle, &memFuncs)); CssmMemoryFunctionsAllocator allocator(memFuncs); const CssmData cipherBuf(const_cast<uint8 *>(inHeader.encryptedBlob), inHeader.encryptedBlobSize); CssmAutoData clearBuf(allocator); CssmAutoData remData(allocator); size_t bytesDecrypted; CSSM_RETURN crx = CSSM_DecryptData(cc, &cipherBuf, 1, &clearBuf.get(), 1, &bytesDecrypted, &remData.get()); secinfo("FDERecovery", "decodePrivateKeyHeader: CSSM_DecryptData result: %d", crx); throwIfError(crx); // throwIfError(CSSM_DecryptData(cc, &cipherBuf, 1, &clearBuf.get(), 1, &bytesDecrypted, &remData.get())); clearBuf.length(bytesDecrypted); // rawKey.copy(clearBuf.get()); result = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)clearBuf.get().data(), clearBuf.get().length()); // result = parseKeyBlob(clearBuf.get()); } catch(...) { CSSM_DeleteContext(cc); throw; } throwIfError(CSSM_DeleteContext(cc)); return result; }
void MachServer::remove(Port receiver) { secinfo("machserver", "port remove: %d", receiver.port()); mPortSet -= receiver; }
// // Add and remove extra listening ports. // Messages directed to those ports are dispatched through the main handler. // To get automatic call-out to another handler, use the Handler class. // void MachServer::add(Port receiver) { secinfo("machserver", "port add: %d", receiver.port()); mPortSet += receiver; }