/*
- * Copyright (c) 2003-2004 Apple Computer, Inc. All Rights Reserved.
+ * Copyright (c) 2003-2008 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
#include "agentquery.h"
#include <security_utilities/memutils.h>
#include <security_utilities/logging.h>
+#include <Security/SecRequirementPriv.h>
//
//
// 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;
};
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());
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"));
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
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) {
//
-// 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;
}
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();
{
// 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
{
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
// 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
}
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