X-Git-Url: https://git.saurik.com/apple/securityd.git/blobdiff_plain/ba39f75c6e9968804642b48cacd141ae417d913e..4cd1cad0dea00daa03e1b54fdf2797a02373ad5b:/src/codesigdb.cpp?ds=inline diff --git a/src/codesigdb.cpp b/src/codesigdb.cpp index d8094fa..c0b7868 100644 --- a/src/codesigdb.cpp +++ b/src/codesigdb.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved. + * Copyright (c) 2003-2008 Apple Inc. All Rights Reserved. * * @APPLE_LICENSE_HEADER_START@ * @@ -31,6 +31,7 @@ #include "agentquery.h" #include #include +#include // @@ -65,20 +66,15 @@ DbKey::DbKey(char type, const CssmData &key, bool perUser, uid_t user) // // A subclass of Identity made of whole cloth (from a raw CodeSignature ACL information) // -class AclIdentity : public CodeSignatures::Identity { -public: - AclIdentity(const CodeSigning::Signature *sig, const char *comment) - : mHash(*sig), mPath(comment ? comment : "") { } - AclIdentity(const CssmData &hash, const char *comment) - : mHash(hash), mPath(comment ? comment : "") { } +struct AclIdentity : public CodeSignatures::Identity { + AclIdentity(const CssmData hash, string path) : mHash(hash), mPath(path) { } -protected: - std::string getPath() const { return mPath; } - const CssmData getHash(CodeSigning::OSXSigner &) const { return mHash; } - + string getPath() const { return mPath; } + const CssmData getHash() const { return mHash; } + private: const CssmData mHash; - std::string mPath; + const string mPath; }; @@ -147,7 +143,7 @@ bool CodeSignatures::find(Identity &id, uid_t user) if (id.mState != Identity::untried) return id.mState == Identity::valid; try { - DbKey userKey('H', id.getHash(mSigner), true, user); + DbKey userKey('H', id.getHash(), true, user); CssmData linkValue; if (mDb.get(userKey, linkValue)) { id.mName = string(linkValue.interpretedAs(), linkValue.length()); @@ -155,7 +151,7 @@ bool CodeSignatures::find(Identity &id, uid_t user) id.mState = Identity::valid; return true; } - DbKey sysKey('H', id.getHash(mSigner)); + DbKey sysKey('H', id.getHash()); if (mDb.get(sysKey, linkValue)) { id.mName = string(linkValue.interpretedAs(), linkValue.length()); IFDUMPING("equiv", id.debugDump("found/system")); @@ -171,16 +167,11 @@ bool CodeSignatures::find(Identity &id, uid_t user) void CodeSignatures::makeLink(Identity &id, const string &ident, bool forUser, uid_t user) { - DbKey key('H', id.getHash(mSigner), forUser, user); + DbKey key('H', id.getHash(), forUser, user); if (!mDb.put(key, StringData(ident))) UnixError::throwMe(); } -void CodeSignatures::makeApplication(const std::string &name, const std::string &path) -{ - //@@@ create app record and fill (later) -} - // // Administrative manipulation calls @@ -194,8 +185,8 @@ void CodeSignatures::addLink(const CssmData &oldHash, const CssmData &newHash, UnixError::throwMe(EACCES); if (!forSystem) // in fact, for now we don't allow per-user calls at all UnixError::throwMe(EACCES); - AclIdentity oldCode(oldHash, name.c_str()); - AclIdentity newCode(newHash, name.c_str()); + AclIdentity oldCode(oldHash, name); + AclIdentity newCode(newHash, name); secdebug("codesign", "addlink for name %s", name.c_str()); StLock _(mDatabaseLock); if (oldCode) { @@ -226,24 +217,188 @@ void CodeSignatures::removeLink(const CssmData &hash, const char *name, bool for // -// Verify signature matches +// Verify signature matches. +// This ends up getting called when a CodeSignatureAclSubject is validated. +// The OSXVerifier describes what we require of the client code; the process represents +// the requesting client; and the context gives us access to the ACL and its environment +// in case we want to, well, creatively rewrite it for some reason. // bool CodeSignatures::verify(Process &process, - const CodeSigning::Signature *trustedSignature, const CssmData *comment) + const OSXVerifier &verifier, const AclValidationContext &context) { secdebug("codesign", "start verify"); - // if we have no client code, we cannot possibly match this - if (!process.clientCode()) { + StLock _(process); + SecCodeRef code = process.currentGuest(); + if (!code) { secdebug("codesign", "no code base: fail"); return false; } + if (SecRequirementRef requirement = verifier.requirement()) { + // If the ACL contains a code signature (requirement), we won't match against unsigned code at all. + // The legacy hash is ignored (it's for use by pre-Leopard systems). + secdebug("codesign", "CS requirement present; ignoring legacy hashes"); + Server::active().longTermActivity(); + switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, requirement)) { + case noErr: + secdebug("codesign", "CS verify passed"); + return true; + case errSecCSUnsigned: + secdebug("codesign", "CS verify against unsigned binary failed"); + return false; + default: + secdebug("codesign", "CS verify failed OSStatus=%d", int32_t(rc)); + return false; + } + } + switch (matchSignedClientToLegacyACL(process, code, verifier, context)) { + case noErr: // handled, allow access + return true; + case errSecCSUnsigned: // unsigned client, complete legacy case + secdebug("codesign", "no CS requirement - using legacy hash"); + return verifyLegacy(process, + CssmData::wrap(verifier.legacyHash(), SHA1::digestLength), + verifier.path()); + default: // client unsuitable, reject this match + return false; + } +} + + +// +// See if we can rewrite the ACL from legacy to Code Signing form without losing too much security. +// Returns true if the present validation should succeed (we probably rewrote the ACL). +// Returns false if the present validation shouldn't succeed based on what we did here (we may still +// have rewritten the ACL, in principle). +// +// Note that these checks add nontrivial overhead to ACL processing. We want to eventually phase +// this out, or at least make it an option that doesn't run all the time - perhaps an "extra legacy +// effort" per-client mode bit. +// +static string trim(string s, char delimiter) +{ + string::size_type p = s.rfind(delimiter); + if (p != string::npos) + s = s.substr(p + 1); + return s; +} + +static string trim(string s, char delimiter, string suffix) +{ + s = trim(s, delimiter); + int preLength = s.length() - suffix.length(); + if (preLength > 0 && s.substr(preLength) == suffix) + s = s.substr(0, preLength); + return s; +} + +OSStatus CodeSignatures::matchSignedClientToLegacyACL(Process &process, + SecCodeRef code, const OSXVerifier &verifier, const AclValidationContext &context) +{ + // + // Check whether we seem to be matching a legacy .Mac ACL against a member of the .Mac group + // + if (SecurityServerAcl::looksLikeLegacyDotMac(context)) { + Server::active().longTermActivity(); + CFRef dotmac; + MacOSError::check(SecRequirementCreateGroup(CFSTR("dot-mac"), NULL, kSecCSDefaultFlags, &dotmac.aref())); + if (SecCodeCheckValidity(code, kSecCSDefaultFlags, dotmac) == noErr) { + secdebug("codesign", "client is a dot-mac application; update the ACL accordingly"); + + // create a suitable AclSubject (this is the above-the-API-line way) + CFRef reqdata; + MacOSError::check(SecRequirementCopyData(dotmac, kSecCSDefaultFlags, &reqdata.aref())); + RefPointer subject = new CodeSignatureAclSubject(NULL, "group://dot-mac"); + subject->add((const BlobCore *)CFDataGetBytePtr(reqdata)); + + // add it to the ACL and pass the access check (we just quite literally did it above) + SecurityServerAcl::addToStandardACL(context, subject); + return noErr; + } + } + + // + // Get best names for the ACL (legacy) subject and the (signed) client + // + CFRef info; + MacOSError::check(SecCodeCopySigningInformation(code, kSecCSSigningInformation, &info.aref())); + CFStringRef signingIdentity = CFStringRef(CFDictionaryGetValue(info, kSecCodeInfoIdentifier)); + if (!signingIdentity) // unsigned + return errSecCSUnsigned; + + string bundleName; // client + if (CFDictionaryRef infoList = CFDictionaryRef(CFDictionaryGetValue(info, kSecCodeInfoPList))) + if (CFStringRef name = CFStringRef(CFDictionaryGetValue(infoList, kCFBundleNameKey))) + bundleName = trim(cfString(name), '.'); + if (bundleName.empty()) // fall back to signing identifier + bundleName = trim(cfString(signingIdentity), '.'); + + string aclName = trim(verifier.path(), '/', ".app"); // ACL + + secdebug("codesign", "matching signed client \"%s\" against legacy ACL \"%s\"", + bundleName.c_str(), aclName.c_str()); + + // + // Check whether we're matching a signed APPLE application against a legacy ACL by the same name + // + if (bundleName == aclName) { + const unsigned char reqData[] = { // "anchor apple", version 1 blob, embedded here + 0xfa, 0xde, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03 + }; + CFRef apple; + MacOSError::check(SecRequirementCreateWithData(CFTempData(reqData, sizeof(reqData)), + kSecCSDefaultFlags, &apple.aref())); + Server::active().longTermActivity(); + switch (OSStatus rc = SecCodeCheckValidity(code, kSecCSDefaultFlags, apple)) { + case noErr: + { + secdebug("codesign", "withstands strict scrutiny; quietly adding new ACL"); + RefPointer wrap = new OSXCodeWrap(code); + RefPointer subject = new CodeSignatureAclSubject(OSXVerifier(wrap)); + SecurityServerAcl::addToStandardACL(context, subject); + return noErr; + } + default: + secdebug("codesign", "validation fails with rc=%d, rejecting", int32_t(rc)); + return rc; + } + secdebug("codesign", "does not withstand strict scrutiny; ask the user"); + QueryCodeCheck query; + query.inferHints(process); + if (!query(verifier.path().c_str())) { + secdebug("codesign", "user declined equivalence: cancel the access"); + CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); + } + RefPointer wrap = new OSXCodeWrap(code); + RefPointer subject = new CodeSignatureAclSubject(OSXVerifier(wrap)); + SecurityServerAcl::addToStandardACL(context, subject); + return noErr; + } - // first of all, if the signature directly matches the client's code, we're obviously fine + // not close enough to even ask - this can't match + return errSecCSReqFailed; +} + + +// +// Perform legacy hash verification. +// This is the pre-Leopard (Tiger, Panther) code path. Here we only have legacy hashes +// (called, confusingly, "signatures"), which we're matching against suitably computed +// "signatures" (hashes) on the requesting application. We consult the CodeEquivalenceDatabase +// in a doomed attempt to track changes made to applications through updates, and issue +// equivalence dialogs to users if we have a name match (but hash mismatch). That's all +// there was before Code Signing; and that's what you'll continue to get if the requesting +// application is unsigned. Until we throw the whole mess out altogether, hopefully by +// the Next Big Cat After Leopard. +// +bool CodeSignatures::verifyLegacy(Process &process, const CssmData &signature, string path) +{ + // First of all, if the signature directly matches the client's code, we're obviously fine // we don't even need the database for that... Identity &clientIdentity = process; try { - if (clientIdentity.getHash(mSigner) == CssmData(*trustedSignature)) { + if (clientIdentity.getHash() == signature) { secdebug("codesign", "direct match: pass"); return true; } @@ -252,8 +407,10 @@ bool CodeSignatures::verify(Process &process, return false; } - // ah well. Establish mediator objects for database signature links - AclIdentity aclIdentity(trustedSignature, comment ? comment->interpretedAs() : NULL); +#if CONSULT_LEGACY_CODE_EQUIVALENCE_DATABASE + + // Ah well. Establish mediator objects for database signature links + AclIdentity aclIdentity(signature, path); uid_t user = process.uid(); { @@ -284,8 +441,7 @@ bool CodeSignatures::verify(Process &process, // The names match - we have a possible update. // Take the UI lock now to serialize "update rushes". - Server::active().longTermActivity(); - StLock uiLocker(mUILock); + LongtermStLock uiLocker(mUILock); // re-read the database in case some other thread beat us to the update { @@ -308,8 +464,8 @@ bool CodeSignatures::verify(Process &process, query.inferHints(process); if (!query(aclIdentity.path().c_str())) { - secdebug("codesign", "user declined equivalence: fail"); - return false; + secdebug("codesign", "user declined equivalence: cancel the access"); + CssmError::throwMe(CSSM_ERRCODE_USER_CANCELED); } // take the database lock back for real @@ -336,12 +492,17 @@ bool CodeSignatures::verify(Process &process, // the De Novo case: no links, must create everything string ident = clientIdentity.name(); - makeApplication(ident, clientIdentity.path()); makeLink(clientIdentity, ident, true, user); makeLink(aclIdentity, ident, true, user); mDb.flush(); secdebug("codesign", "new linkages established: pass"); return true; + +#else /* ignore Code Equivalence Database */ + + return false; + +#endif } @@ -381,9 +542,8 @@ void CodeSignatures::Identity::debugDump(const char *how) const how = "dump"; dump("IDENTITY (%s) path=%s", how, getPath().c_str()); dump(" name=%s hash=", mName.empty() ? "(unset)" : mName.c_str()); - CodeSigning::OSXSigner signer; - dumpData(getHash(signer)); + dumpData(getHash()); dump("\n"); } -#endif //DEBUGDUMP \ No newline at end of file +#endif //DEBUGDUMP