]> git.saurik.com Git - apple/security.git/blobdiff - OSX/libsecurity_codesigning/lib/csutilities.cpp
Security-59754.80.3.tar.gz
[apple/security.git] / OSX / libsecurity_codesigning / lib / csutilities.cpp
index c6f0231bf5fc6e38db9a60fb439642935330a7bf..ff0f15dd6b6a9ae73548fa0f48bbe3398f1a0917 100644 (file)
 //
 // csutilities - miscellaneous utilities for the code signing implementation
 //
+
 #include "csutilities.h"
+#include <libDER/DER_Encode.h>
+#include <libDER/DER_Keys.h>
+#include <libDER/asn1Types.h>
+#include <libDER/oids.h>
+#include <security_asn1/SecAsn1Coder.h>
+#include <security_asn1/SecAsn1Templates.h>
 #include <Security/SecCertificatePriv.h>
+#include <Security/SecCertificate.h>
+#include <Security/SecPolicyPriv.h>
 #include <utilities/SecAppleAnchorPriv.h>
 #include <utilities/SecInternalReleasePriv.h>
-#include <security_codesigning/requirement.h>
+#include "requirement.h"
 #include <security_utilities/hashing.h>
 #include <security_utilities/debugging.h>
 #include <security_utilities/errors.h>
+#include <sys/mount.h>
 #include <sys/utsname.h>
+#include <errno.h>
+#include <sys/attr.h>
+#include <sys/xattr.h>
+#include <libgen.h>
+#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,9 +92,7 @@ void hashOfCertificate(const void *certData, size_t certLength, SHA1::Digest dig
 void hashOfCertificate(SecCertificateRef cert, SHA1::Digest digest)
 {
        assert(cert);
-       CSSM_DATA certData;
-       MacOSError::check(SecCertificateGetData(cert, &certData));
-       hashOfCertificate(certData.Data, certData.Length, digest);
+    hashOfCertificate(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert), digest);
 }
 
 
@@ -83,43 +106,39 @@ bool verifyHash(SecCertificateRef cert, const Hashing::Byte *digest)
        return !memcmp(dig, digest, SHA1::digestLength);
 }
 
-
+#if TARGET_OS_OSX
 //
 // Check to see if a certificate contains a particular field, by OID. This works for extensions,
 // even ones not recognized by the local CL. It does not return any value, only presence.
 //
 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.
@@ -127,27 +146,78 @@ 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
 
 //
 // Copyfile
@@ -256,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