+ 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;
+ }