//
// reqinterp - Requirement language (exprOp) interpreter
//
+
#include "reqinterp.h"
#include "codesigning_dtrace.h"
#include <Security/SecTrustSettingsPriv.h>
#include <sys/csr.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFUnserialize.h>
+#include <libDER/oids.h>
#include "csutilities.h"
+#include "notarization.h"
+#include "legacydevid.h"
+
+#define WAITING_FOR_LIB_AMFI_INTERFACE 1
+
+#if WAITING_FOR_LIB_AMFI_INTERFACE
+#define __mac_syscall __sandbox_ms
+#include <security/mac.h>
+
+#define AMFI_INTF_CD_HASH_LEN 20
+#endif
namespace Security {
namespace CodeSigning {
Match match(*this);
return certFieldValue(key, match, cert);
}
+#if TARGET_OS_OSX
case opCertGeneric:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
Match match(*this);
return certFieldGeneric(key, match, cert);
}
+ case opCertFieldDate:
+ {
+ SecCertificateRef cert = mContext->cert(get<int32_t>());
+ string key = getString();
+ Match match(*this);
+ return certFieldDate(key, match, cert);
+ }
case opCertPolicy:
{
SecCertificateRef cert = mContext->cert(get<int32_t>());
Match match(*this);
return certFieldPolicy(key, match, cert);
}
+#endif
case opTrustedCert:
return trustedCert(get<int32_t>());
case opTrustedCerts:
int32_t targetPlatform = get<int32_t>();
return mContext->directory && mContext->directory->platform == targetPlatform;
}
+ case opNotarized:
+ {
+ return isNotarized(mContext);
+ }
+ case opLegacyDevID:
+ {
+ return meetsDeveloperIDLegacyAllowedPolicy(mContext);
+ }
default:
// opcode not recognized - handle generically if possible, fail otherwise
if (op & (opGenericFalse | opGenericSkip)) {
if (mContext->info) // we have an Info.plist
if (CFTypeRef value = CFDictionaryGetValue(mContext->info, CFTempString(key)))
return match(value);
- return false;
+ return match(kCFNull);
}
if (mContext->entitlements) // we have an Info.plist
if (CFTypeRef value = CFDictionaryGetValue(mContext->entitlements, CFTempString(key)))
return match(value);
- return false;
+ return match(kCFNull);
}
bool Requirement::Interpreter::certFieldValue(const string &key, const Match &match, SecCertificateRef cert)
{
+#if TARGET_OS_OSX
// no cert, no chance
if (cert == NULL)
return false;
// a table of recognized keys for the "certificate[foo]" syntax
static const struct CertField {
const char *name;
- const CSSM_OID *oid;
+ const DERItem *oid;
} certFields[] = {
- { "subject.C", &CSSMOID_CountryName },
- { "subject.CN", &CSSMOID_CommonName },
- { "subject.D", &CSSMOID_Description },
- { "subject.L", &CSSMOID_LocalityName },
+ { "subject.C", &oidCountryName},
+ { "subject.CN", &oidCommonName },
+ { "subject.D", &oidDescription },
+ { "subject.L", &oidLocalityName },
// { "subject.C-L", &CSSMOID_CollectiveLocalityName }, // missing from Security.framework headers
- { "subject.O", &CSSMOID_OrganizationName },
- { "subject.C-O", &CSSMOID_CollectiveOrganizationName },
- { "subject.OU", &CSSMOID_OrganizationalUnitName },
- { "subject.C-OU", &CSSMOID_CollectiveOrganizationalUnitName },
- { "subject.ST", &CSSMOID_StateProvinceName },
- { "subject.C-ST", &CSSMOID_CollectiveStateProvinceName },
- { "subject.STREET", &CSSMOID_StreetAddress },
- { "subject.C-STREET", &CSSMOID_CollectiveStreetAddress },
- { "subject.UID", &CSSMOID_UserID },
+ { "subject.O", &oidOrganizationName },
+ { "subject.C-O", &oidCollectiveOrganizationName },
+ { "subject.OU", &oidOrganizationalUnitName},
+ { "subject.C-OU", &oidCollectiveOrganizationalUnitName},
+ { "subject.ST", &oidStateOrProvinceName},
+ { "subject.C-ST", &oidCollectiveStateOrProvinceName },
+ { "subject.STREET", &oidStreetAddress },
+ { "subject.C-STREET", &oidCollectiveStreetAddress },
+ { "subject.UID", &oidUserId },
{ NULL, NULL }
};
// DN-component single-value match
for (const CertField *cf = certFields; cf->name; cf++)
if (cf->name == key) {
- CFRef<CFStringRef> value;
- OSStatus rc = SecCertificateCopySubjectComponent(cert, cf->oid, &value.aref());
- if (rc) {
- secinfo("csinterp", "cert %p lookup for DN.%s failed rc=%d", cert, key.c_str(), (int)rc);
+ CFRef<CFStringRef> value(SecCertificateCopySubjectAttributeValue(cert, (DERItem *)cf->oid));
+ if (!value.get()) {
+ secinfo("csinterp", "cert %p lookup for DN.%s failed", cert, key.c_str());
return false;
}
return match(value);
// unrecognized key. Fail but do not abort to promote backward compatibility down the road
secinfo("csinterp", "cert field notation \"%s\" not understood", key.c_str());
+#endif
return false;
}
-
+#if TARGET_OS_OSX
bool Requirement::Interpreter::certFieldGeneric(const string &key, const Match &match, SecCertificateRef cert)
{
// the key is actually a (binary) OID value
bool Requirement::Interpreter::certFieldGeneric(const CssmOid &oid, const Match &match, SecCertificateRef cert)
{
- return cert && certificateHasField(cert, oid) && match(kCFBooleanTrue);
+ return cert && match(certificateHasField(cert, oid) ? (CFTypeRef)kCFBooleanTrue : (CFTypeRef)kCFNull);
+}
+
+bool Requirement::Interpreter::certFieldDate(const string &key, const Match &match, SecCertificateRef cert)
+{
+ // the key is actually a (binary) OID value
+ CssmOid oid((char *)key.data(), key.length());
+ return certFieldDate(oid, match, cert);
+}
+
+bool Requirement::Interpreter::certFieldDate(const CssmOid &oid, const Match &match, SecCertificateRef cert)
+{
+ CFTypeRef value = cert != NULL ? certificateCopyFieldDate(cert, oid) : NULL;
+ bool matching = match(value != NULL ? value : kCFNull);
+
+ if (value) {
+ CFRelease(value);
+ }
+
+ return matching;
}
bool Requirement::Interpreter::certFieldPolicy(const string &key, const Match &match, SecCertificateRef cert)
bool Requirement::Interpreter::certFieldPolicy(const CssmOid &oid, const Match &match, SecCertificateRef cert)
{
- return cert && certificateHasPolicy(cert, oid) && match(kCFBooleanTrue);
+ return cert && match(certificateHasPolicy(cert, oid) ? (CFTypeRef)kCFBooleanTrue : (CFTypeRef)kCFNull);
}
-
+#endif
//
// Check the Apple-signed condition
bool Requirement::Interpreter::appleLocalAnchored()
{
- static CFArrayRef additionalTrustedCertificates = NULL;
+ static CFArrayRef additionalTrustedCertificates = NULL;
- if (csr_check(CSR_ALLOW_APPLE_INTERNAL))
+ if (csr_check(CSR_ALLOW_APPLE_INTERNAL)) {
return false;
+ }
+
+ if (mContext->forcePlatform) {
+ return true;
+ }
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
return false;
}
+#if WAITING_FOR_LIB_AMFI_INTERFACE
+// These bits are here until we get get a new build alias for libamfi-interface.
+
+#define MAC_AMFI_POLICY_NAME "AMFI"
+
+#define AMFI_SYSCALL_CDHASH_IN_TRUSTCACHE 95
+
+typedef struct amfi_cdhash_in_trustcache_ {
+ uint8_t cdhash[20];
+ uint64_t result;
+} amfi_cdhash_in_trustcache_t;
+
+static int
+__amfi_interface_cdhash_in_trustcache(const uint8_t cdhash[], uint64_t* trustcache_result)
+{
+ amfi_cdhash_in_trustcache_t args;
+ static_assert(AMFI_INTF_CD_HASH_LEN == sizeof(args.cdhash), "Error: cdhash length mismatch");
+ int err;
+ memcpy(args.cdhash, cdhash, sizeof(args.cdhash));
+ args.result = 0;
+ err = __mac_syscall(MAC_AMFI_POLICY_NAME, AMFI_SYSCALL_CDHASH_IN_TRUSTCACHE, &args);
+ if (err) {
+ err = errno;
+ }
+ *trustcache_result = args.result;
+ return err;
+}
+
+static int
+amfi_interface_cdhash_in_trustcache(const uint8_t cdhash[], size_t cdhash_len, uint64_t* trustcache_result)
+{
+ int err = EINVAL;
+
+ if (cdhash == nullptr || cdhash_len != AMFI_INTF_CD_HASH_LEN || trustcache_result == nullptr) {
+ goto lb_end;
+ }
+ *trustcache_result = 0;
+
+ err = __amfi_interface_cdhash_in_trustcache(cdhash, trustcache_result);
+
+lb_end:
+ return err;
+}
+#endif
+
+bool Requirement::Interpreter::inTrustCache()
+{
+ uint64_t result = 0;
+ CFRef<CFDataRef> cdhashRef = mContext->directory->cdhash(true);
+ const uint8_t *cdhash = CFDataGetBytePtr(cdhashRef);
+ size_t cdhash_len = CFDataGetLength(cdhashRef);
+ int err = amfi_interface_cdhash_in_trustcache(cdhash, cdhash_len, &result);
+ return (err == 0) && (result != 0);
+}
+
bool Requirement::Interpreter::appleSigned()
{
- if (appleAnchored()) {
- if (SecCertificateRef intermed = mContext->cert(-2)) // first intermediate
- // first intermediate common name match (exact)
- if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed)
- && certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed))
- return true;
+ if (inTrustCache()) {
+ return true;
+ }
+ else if (appleAnchored()) {
+ if (SecCertificateRef intermed = mContext->cert(-2)) // first intermediate
+ // first intermediate common name match (exact)
+ if (certFieldValue("subject.CN", Match(appleIntermediateCN, matchEqual), intermed)
+ && certFieldValue("subject.O", Match(appleIntermediateO, matchEqual), intermed))
+ return true;
} else if (appleLocalAnchored()) {
return true;
}
- return false;
+ return false;
}
{
// get certificate bytes
if (cert) {
- CSSM_DATA certData;
- MacOSError::check(SecCertificateGetData(cert, &certData));
-
- // verify hash
- SHA1 hasher;
- hasher(certData.Data, certData.Length);
+ SHA1 hasher;
+ hasher(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert));
return hasher.verify(digest);
}
return false;
//
SecTrustSettingsResult Requirement::Interpreter::trustSetting(SecCertificateRef cert, bool isAnchor)
{
+ // XXX: Not supported on embedded yet due to lack of supporting API
+#if TARGET_OS_OSX
// the SPI input is the uppercase hex form of the SHA-1 of the certificate...
assert(cert);
SHA1::Digest digest;
::free(errors);
MacOSError::throwMe(rc);
}
+#else
+ return kSecTrustSettingsResultUnspecified;
+#endif
}
Requirement::Interpreter::Match::Match(Interpreter &interp)
{
switch (mOp = interp.get<MatchOperation>()) {
+ case matchAbsent:
case matchExists:
break;
case matchEqual:
case matchGreaterEqual:
mValue.take(makeCFString(interp.getString()));
break;
+ case matchOn:
+ case matchBefore:
+ case matchAfter:
+ case matchOnOrBefore:
+ case matchOnOrAfter: {
+ mValue.take(CFDateCreate(NULL, interp.getAbsoluteTime()));
+ break;
+ }
default:
// Assume this (unknown) match type has a single data argument.
// This gives us a chance to keep the instruction stream aligned.
if (!candidate)
return false;
+ if (candidate == kCFNull) {
+ return mOp == matchAbsent; // only 'absent' matches
+ }
+
// interpret an array as matching alternatives (any one succeeds)
if (CFGetTypeID(candidate) == CFArrayGetTypeID()) {
CFArrayRef array = CFArrayRef(candidate);
}
switch (mOp) {
+ case matchAbsent:
+ return false; // it exists, so it cannot be absent
case matchExists: // anything but NULL and boolean false "exists"
return !CFEqual(candidate, kCFBooleanFalse);
case matchEqual: // equality works for all CF types
return CFEqual(candidate, mValue);
case matchContains:
- if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
+ if (isStringValue() && CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
- if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(value)), 0, NULL))
+ if (CFStringFindWithOptions(value, cfStringValue(), CFRangeMake(0, CFStringGetLength(value)), 0, NULL))
return true;
}
return false;
case matchBeginsWith:
- if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
+ if (isStringValue() && CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
- if (CFStringFindWithOptions(value, mValue, CFRangeMake(0, CFStringGetLength(mValue)), 0, NULL))
+ if (CFStringFindWithOptions(value, cfStringValue(), CFRangeMake(0, CFStringGetLength(cfStringValue())), 0, NULL))
return true;
}
return false;
case matchEndsWith:
- if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
+ if (isStringValue() && CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
- CFIndex matchLength = CFStringGetLength(mValue);
+ CFIndex matchLength = CFStringGetLength(cfStringValue());
CFIndex start = CFStringGetLength(value) - matchLength;
if (start >= 0)
- if (CFStringFindWithOptions(value, mValue, CFRangeMake(start, matchLength), 0, NULL))
+ if (CFStringFindWithOptions(value, cfStringValue(), CFRangeMake(start, matchLength), 0, NULL))
return true;
}
return false;
return inequality(candidate, kCFCompareNumerically, kCFCompareGreaterThan, false);
case matchGreaterEqual:
return inequality(candidate, kCFCompareNumerically, kCFCompareLessThan, false);
+ case matchOn:
+ case matchBefore:
+ case matchAfter:
+ case matchOnOrBefore:
+ case matchOnOrAfter: {
+ if (!isDateValue() || CFGetTypeID(candidate) != CFDateGetTypeID()) {
+ return false;
+ }
+
+ CFComparisonResult res = CFDateCompare((CFDateRef)candidate, cfDateValue(), NULL);
+
+ switch (mOp) {
+ case matchOn: return res == 0;
+ case matchBefore: return res < 0;
+ case matchAfter: return res > 0;
+ case matchOnOrBefore: return res <= 0;
+ case matchOnOrAfter: return res >= 0;
+ default: abort();
+ }
+ }
default:
// unrecognized match types can never match
return false;
bool Requirement::Interpreter::Match::inequality(CFTypeRef candidate, CFStringCompareFlags flags,
CFComparisonResult outcome, bool negate) const
{
- if (CFGetTypeID(candidate) == CFStringGetTypeID()) {
+ if (isStringValue() && CFGetTypeID(candidate) == CFStringGetTypeID()) {
CFStringRef value = CFStringRef(candidate);
- if ((CFStringCompare(value, mValue, flags) == outcome) == negate)
+ if ((CFStringCompare(value, cfStringValue(), flags) == outcome) == negate)
return true;
}
return false;