//
// 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 {
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);
}
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.
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
}
}
+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