X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/866f8763175ff60e4fa455b92b5eb660a12fe6c7..refs/heads/master:/OSX/libsecurity_codesigning/lib/csutilities.cpp?ds=sidebyside diff --git a/OSX/libsecurity_codesigning/lib/csutilities.cpp b/OSX/libsecurity_codesigning/lib/csutilities.cpp index e25e7b58..ff0f15dd 100644 --- a/OSX/libsecurity_codesigning/lib/csutilities.cpp +++ b/OSX/libsecurity_codesigning/lib/csutilities.cpp @@ -24,16 +24,41 @@ // // csutilities - miscellaneous utilities for the code signing implementation // + #include "csutilities.h" +#include +#include +#include +#include +#include +#include #include +#include +#include #include #include #include "requirement.h" #include #include #include +#include #include +#include +#include +#include +#include +#include "debugging.h" + +extern "C" { + +/* Decode a choice of UTCTime or GeneralizedTime to a CFAbsoluteTime. Return + an absoluteTime if the date was valid and properly decoded. Return + NULL_TIME otherwise. */ +CFAbsoluteTime SecAbsoluteTimeFromDateContent(DERTag tag, const uint8_t *bytes, + size_t length); +} + namespace Security { namespace CodeSigning { @@ -67,13 +92,7 @@ void hashOfCertificate(const void *certData, size_t certLength, SHA1::Digest dig void hashOfCertificate(SecCertificateRef cert, SHA1::Digest digest) { assert(cert); -#if TARGET_OS_OSX - CSSM_DATA certData; - MacOSError::check(SecCertificateGetData(cert, &certData)); - hashOfCertificate(certData.Data, certData.Length, digest); -#else hashOfCertificate(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert), digest); -#endif } @@ -94,36 +113,32 @@ bool verifyHash(SecCertificateRef cert, const Hashing::Byte *digest) // bool certificateHasField(SecCertificateRef cert, const CSSM_OID &oid) { - assert(cert); - CSSM_DATA *value; - switch (OSStatus rc = SecCertificateCopyFirstFieldValue(cert, &oid, &value)) { - case errSecSuccess: - MacOSError::check(SecCertificateReleaseFirstFieldValue(cert, &oid, value)); - return true; // extension found by oid - case errSecUnknownTag: - break; // oid not recognized by CL - continue below - default: - MacOSError::throwMe(rc); // error: fail + CFDataRef oidData = NULL; + CFDataRef data = NULL; + bool isCritical = false; + bool matched = false; + + oidData = CFDataCreateWithBytesNoCopy(NULL, oid.Data, oid.Length, + kCFAllocatorNull); + if (!(cert && oidData)) { + goto out; } - - // check the CL's bag of unrecognized extensions - CSSM_DATA **values; - bool found = false; - if (SecCertificateCopyFieldValues(cert, &CSSMOID_X509V3CertificateExtensionCStruct, &values)) - return false; // no unrecognized extensions - no match - if (values) - for (CSSM_DATA **p = values; *p; p++) { - const CSSM_X509_EXTENSION *ext = (const CSSM_X509_EXTENSION *)(*p)->Data; - if (oid == ext->extnId) { - found = true; - break; - } - } - MacOSError::check(SecCertificateReleaseFieldValues(cert, &CSSMOID_X509V3CertificateExtensionCStruct, values)); - return found; + data = SecCertificateCopyExtensionValue(cert, oidData, &isCritical); + if (data == NULL) { + goto out; + } + matched = true; +out: + if (data) { + CFRelease(data); + } + if (oidData) { + CFRelease(oidData); + } + return matched; } - - + + // // Retrieve X.509 policy extension OIDs, if any. // This currently ignores policy qualifiers. @@ -131,26 +146,77 @@ bool certificateHasField(SecCertificateRef cert, const CSSM_OID &oid) bool certificateHasPolicy(SecCertificateRef cert, const CSSM_OID &policyOid) { bool matched = false; - assert(cert); - CSSM_DATA *data; - if (OSStatus rc = SecCertificateCopyFirstFieldValue(cert, &CSSMOID_CertificatePolicies, &data)) - MacOSError::throwMe(rc); - if (data && data->Data && data->Length == sizeof(CSSM_X509_EXTENSION)) { - const CSSM_X509_EXTENSION *ext = (const CSSM_X509_EXTENSION *)data->Data; - assert(ext->format == CSSM_X509_DATAFORMAT_PARSED); - const CE_CertPolicies *policies = (const CE_CertPolicies *)ext->value.parsedValue; - if (policies) - for (unsigned int n = 0; n < policies->numPolicies; n++) { - const CE_PolicyInformation &cp = policies->policies[n]; - if (cp.certPolicyId == policyOid) { - matched = true; - break; - } - } - } - SecCertificateReleaseFirstFieldValue(cert, &CSSMOID_PolicyConstraints, data); + CFDataRef oidData = CFDataCreateWithBytesNoCopy(NULL, policyOid.Data, policyOid.Length, + kCFAllocatorNull); + if (!(cert && oidData)) { + goto out; + } + matched = SecPolicyCheckCertCertificatePolicy(cert, oidData); +out: + if (oidData) { + CFRelease(oidData); + } return matched; } + + +CFDateRef certificateCopyFieldDate(SecCertificateRef cert, const CSSM_OID &policyOid) +{ + CFDataRef oidData = NULL; + CFDateRef value = NULL; + CFDataRef data = NULL; + SecAsn1CoderRef coder = NULL; + CSSM_DATA str = { 0 }; + CFAbsoluteTime time = 0.0; + OSStatus status = 0; + bool isCritical; + + oidData = CFDataCreateWithBytesNoCopy(NULL, policyOid.Data, policyOid.Length, + kCFAllocatorNull); + + if (oidData == NULL) { + goto out; + } + + data = SecCertificateCopyExtensionValue(cert, oidData, &isCritical); + + if (data == NULL) { + goto out; + } + + status = SecAsn1CoderCreate(&coder); + if (status != 0) { + goto out; + } + + // We currently only support UTF8 strings. + status = SecAsn1Decode(coder, CFDataGetBytePtr(data), CFDataGetLength(data), + kSecAsn1UTF8StringTemplate, &str); + if (status != 0) { + goto out; + } + + time = SecAbsoluteTimeFromDateContent(ASN1_GENERALIZED_TIME, + str.Data, str.Length); + + if (time == 0.0) { + goto out; + } + + value = CFDateCreate(NULL, time); +out: + if (coder) { + SecAsn1CoderRelease(coder); + } + if (data) { + CFRelease(data); + } + if (oidData) { + CFRelease(oidData); + } + + return value; +} #endif // @@ -260,5 +326,220 @@ bool LimitedAsync::perform(Dispatch::Group &groupRef, void (^block)()) { } } +bool isOnRootFilesystem(const char *path) +{ + int rc = 0; + struct statfs sfb; + + rc = statfs(path, &sfb); + if (rc != 0) { + secerror("Unable to check if path is on rootfs: %d, %s", errno, path); + return false; + } + return ((sfb.f_flags & MNT_ROOTFS) == MNT_ROOTFS); +} + +bool pathExists(const char *path) +{ + int rc; + + if (!path) { + secerror("path is NULL"); + return false; + } + + rc = access(path, F_OK); + if (rc != 0) { + if (errno != ENOENT) { + secerror("Unable to check if path exists: %d, %s", errno, path); + } + return false; + } + + return true; +} + +bool pathMatchesXattrFilenameSpec(const char *path) +{ + char *baseName = NULL; + bool ret = false; + + if (!path) { + secerror("path is NULL"); + goto done; + } + + // Extra byte for NULL storage. + baseName = (char *)malloc(strlen(path) + 1); + if (!baseName) { + secerror("Unable to allocate space for storing basename: %d [%s]", errno, strerror(errno)); + goto done; + } + + // basename_r will return a "/" if path is only slashes. It will return + // a "." for a NULL/empty path. Both of these cases are handled by the logic + // later. The only situation where basename_r will return a NULL is when path + // is longer than MAXPATHLEN. + + if (basename_r(path, baseName) == NULL) { + secerror("Could not get basename of %s: %d [%s]", path, errno, strerror(errno)); + goto done; + } + + // The file name must start with "._", followed by the name + // of the file for which it stores the xattrs. Hence, its length + // must be at least three --> 2 for "._" and 1 for a non-empty file + // name. + if (strlen(baseName) < 3) { + goto done; + } + + if (baseName[0] != '.' || baseName[1] != '_') { + goto done; + } + + ret = true; + +done: + if (baseName) { + free(baseName); + } + + return ret; +} + +bool pathIsRegularFile(const char *path) +{ + if (!path) { + secerror("path is NULL"); + return false; + } + + struct stat sb; + if (stat(path, &sb)) { + secerror("Unable to stat %s: %d [%s]", path, errno, strerror(errno)); + return false; + } + + return (sb.st_mode & S_IFREG) == S_IFREG; +} + +bool pathHasXattrs(const char *path) +{ + if (!path) { + secerror("path is NULL"); + return false; + } + + ssize_t xattrSize = listxattr(path, NULL, 0, 0); + if (xattrSize == -1) { + secerror("Unable to acquire the xattr list from %s", path); + return false; + } + + return (xattrSize > 0); +} + +bool pathFileSystemUsesXattrFiles(const char *path) +{ + struct _VolumeCapabilitiesWrapped { + uint32_t length; + vol_capabilities_attr_t volume_capabilities; + } __attribute__((aligned(4), packed)); + + struct attrlist attr_list; + struct _VolumeCapabilitiesWrapped volume_cap_wrapped; + struct statfs sfb; + + if (!path) { + secerror("path is NULL"); + return false; + } + + int ret = statfs(path, &sfb); + if (ret != 0) { + secerror("Unable to convert %s to its filesystem mount [statfs failed]: %d [%s]", path, errno, strerror(errno)); + return false; + } + path = sfb.f_mntonname; + + memset(&attr_list, 0, sizeof(attr_list)); + attr_list.bitmapcount = ATTR_BIT_MAP_COUNT; + attr_list.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES; + + ret = getattrlist(path, &attr_list, &volume_cap_wrapped, sizeof(volume_cap_wrapped), 0); + if (ret) { + secerror("Unable to get volume capabilities from %s: %d [%s]", path, errno, strerror(errno)); + return false; + } + + if (volume_cap_wrapped.length != sizeof(volume_cap_wrapped)) { + secerror("getattrlist return length incorrect, expected %lu, got %u", sizeof(volume_cap_wrapped), volume_cap_wrapped.length); + return false; + } + + // The valid bit tells us whether the corresponding bit in capabilities is valid + // or not. For file systems where the valid bit isn't set, we can safely assume that + // extended attributes aren't supported natively. + + bool xattr_valid = (volume_cap_wrapped.volume_capabilities.valid[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) == VOL_CAP_INT_EXTENDED_ATTR; + if (!xattr_valid) { + return true; + } + + bool xattr_capability = (volume_cap_wrapped.volume_capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) == VOL_CAP_INT_EXTENDED_ATTR; + if (!xattr_capability) { + return true; + } + + return false; +} + +bool pathIsValidXattrFile(const string fullPath, const char *scope) +{ + // Confirm that fullPath begins from root. + if (fullPath[0] != '/') { + secinfo(scope, "%s isn't a full path, but a relative path", fullPath.c_str()); + return false; + } + + // Confirm that fullPath is a regular file. + if (!pathIsRegularFile(fullPath.c_str())) { + secinfo(scope, "%s isn't a regular file", fullPath.c_str()); + return false; + } + + // Check that the file name matches the Xattr file spec. + if (!pathMatchesXattrFilenameSpec(fullPath.c_str())) { + secinfo(scope, "%s doesn't match Xattr file path spec", fullPath.c_str()); + return false; + } + + // We are guaranteed to have at least one "/" by virtue of fullPath + // being a path from the root of the filesystem hierarchy. + // + // We construct the real file name by copying everything up to + // the last "/", adding the "/" back in, then skipping + // over the backslash (+1) and the "._" (+2) in the rest of the + // string. + + size_t lastBackSlash = fullPath.find_last_of("/"); + const string realFilePath = fullPath.substr(0, lastBackSlash) + "/" + fullPath.substr(lastBackSlash + 1 + 2); + + if (!pathExists(realFilePath.c_str())) { + secinfo(scope, "%s does not exist, forcing resource validation on %s", realFilePath.c_str(), fullPath.c_str()); + return false; + } + + // Lastly, we need to confirm that the real file contains some xattrs. If not, + // then the file represented by fullPath isn't an xattr file. + if (!pathHasXattrs(realFilePath.c_str())) { + secinfo(scope, "%s does not contain xattrs, forcing resource validation on %s", realFilePath.c_str(), fullPath.c_str()); + return false; + } + + return true; +} + } // end namespace CodeSigning } // end namespace Security