]> git.saurik.com Git - apple/securityd.git/blobdiff - src/codesigdb.cpp
securityd-55199.3.tar.gz
[apple/securityd.git] / src / codesigdb.cpp
index d8094fa3d89dacf19bca228edc53aaab2c905ea0..c0b78686c51a697497daaa79d9cc8652674bf166 100644 (file)
@@ -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 <security_utilities/memutils.h>
 #include <security_utilities/logging.h>
+#include <Security/SecRequirementPriv.h>
 
 
 //
@@ -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<const char>(), 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<const char>(), 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<Mutex> _(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<Mutex> _(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<SecRequirementRef> 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<CFDataRef> reqdata;
+                       MacOSError::check(SecRequirementCopyData(dotmac, kSecCSDefaultFlags, &reqdata.aref()));
+                       RefPointer<CodeSignatureAclSubject> 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<CFDictionaryRef> 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<SecRequirementRef> 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<OSXCode> wrap = new OSXCodeWrap(code);
+                               RefPointer<AclSubject> 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<OSXCode> wrap = new OSXCodeWrap(code);
+               RefPointer<AclSubject> 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<const char>() : 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<Mutex> 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