/ OSX / libsecurity_codesigning / lib / csutilities.cpp
csutilities.cpp
  1  /*
  2   * Copyright (c) 2006-2013 Apple Inc. All Rights Reserved.
  3   * 
  4   * @APPLE_LICENSE_HEADER_START@
  5   * 
  6   * This file contains Original Code and/or Modifications of Original Code
  7   * as defined in and that are subject to the Apple Public Source License
  8   * Version 2.0 (the 'License'). You may not use this file except in
  9   * compliance with the License. Please obtain a copy of the License at
 10   * http://www.opensource.apple.com/apsl/ and read it before using this
 11   * file.
 12   * 
 13   * The Original Code and all software distributed under the License are
 14   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 15   * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 16   * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 17   * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 18   * Please see the License for the specific language governing rights and
 19   * limitations under the License.
 20   * 
 21   * @APPLE_LICENSE_HEADER_END@
 22   */
 23  
 24  //
 25  // csutilities - miscellaneous utilities for the code signing implementation
 26  //
 27  
 28  #include "csutilities.h"
 29  #include <libDER/DER_Encode.h>
 30  #include <libDER/DER_Keys.h>
 31  #include <libDER/asn1Types.h>
 32  #include <libDER/oids.h>
 33  #include <security_asn1/SecAsn1Coder.h>
 34  #include <security_asn1/SecAsn1Templates.h>
 35  #include <Security/SecCertificatePriv.h>
 36  #include <Security/SecCertificate.h>
 37  #include <Security/SecPolicyPriv.h>
 38  #include <utilities/SecAppleAnchorPriv.h>
 39  #include <utilities/SecInternalReleasePriv.h>
 40  #include "requirement.h"
 41  #include <security_utilities/hashing.h>
 42  #include <security_utilities/debugging.h>
 43  #include <security_utilities/errors.h>
 44  #include <sys/mount.h>
 45  #include <sys/utsname.h>
 46  #include <errno.h>
 47  #include <sys/attr.h>
 48  #include <sys/xattr.h>
 49  #include <libgen.h>
 50  #include "debugging.h"
 51  
 52  extern "C" {
 53  
 54  /* Decode a choice of UTCTime or GeneralizedTime to a CFAbsoluteTime. Return
 55   an absoluteTime if the date was valid and properly decoded.  Return
 56   NULL_TIME otherwise. */
 57  CFAbsoluteTime SecAbsoluteTimeFromDateContent(DERTag tag, const uint8_t *bytes,
 58  											  size_t length);
 59  
 60  }
 61  	
 62  namespace Security {
 63  namespace CodeSigning {
 64  
 65  
 66  //
 67  // Test for the canonical Apple CA certificate
 68  //
 69  bool isAppleCA(SecCertificateRef cert)
 70  {
 71  	SecAppleTrustAnchorFlags flags = 0;
 72  	if (SecIsInternalRelease())
 73  		flags |= kSecAppleTrustAnchorFlagsIncludeTestAnchors;
 74  	return SecIsAppleTrustAnchor(cert, flags);
 75  }
 76  
 77  
 78  //
 79  // Calculate the canonical hash of a certificate, given its raw (DER) data.
 80  //
 81  void hashOfCertificate(const void *certData, size_t certLength, SHA1::Digest digest)
 82  {
 83  	SHA1 hasher;
 84  	hasher(certData, certLength);
 85  	hasher.finish(digest);
 86  }
 87  
 88  
 89  //
 90  // Ditto, given a SecCertificateRef
 91  //
 92  void hashOfCertificate(SecCertificateRef cert, SHA1::Digest digest)
 93  {
 94  	assert(cert);
 95      hashOfCertificate(SecCertificateGetBytePtr(cert), SecCertificateGetLength(cert), digest);
 96  }
 97  
 98  
 99  //
100  // One-stop hash-certificate-and-compare
101  //
102  bool verifyHash(SecCertificateRef cert, const Hashing::Byte *digest)
103  {
104  	SHA1::Digest dig;
105  	hashOfCertificate(cert, dig);
106  	return !memcmp(dig, digest, SHA1::digestLength);
107  }
108  
109  #if TARGET_OS_OSX
110  //
111  // Check to see if a certificate contains a particular field, by OID. This works for extensions,
112  // even ones not recognized by the local CL. It does not return any value, only presence.
113  //
114  bool certificateHasField(SecCertificateRef cert, const CSSM_OID &oid)
115  {
116  	CFDataRef oidData = NULL;
117  	CFDataRef data = NULL;
118  	bool isCritical = false;
119  	bool matched = false;
120  
121  	oidData = CFDataCreateWithBytesNoCopy(NULL, oid.Data, oid.Length,
122  	                                      kCFAllocatorNull);
123  	if (!(cert && oidData)) {
124  		goto out;
125  	}
126  	data = SecCertificateCopyExtensionValue(cert, oidData, &isCritical);
127  	if (data == NULL) {
128  		goto out;
129  	}
130  	matched = true;
131  out:
132  	if (data) {
133  		CFRelease(data);
134  	}
135  	if (oidData) {
136  		CFRelease(oidData);
137  	}
138  	return matched;
139  }
140  
141  
142  //
143  // Retrieve X.509 policy extension OIDs, if any.
144  // This currently ignores policy qualifiers.
145  //
146  bool certificateHasPolicy(SecCertificateRef cert, const CSSM_OID &policyOid)
147  {
148  	bool matched = false;
149  	CFDataRef oidData = CFDataCreateWithBytesNoCopy(NULL, policyOid.Data, policyOid.Length,
150  	                                      kCFAllocatorNull);
151  	if (!(cert && oidData)) {
152  		goto out;
153  	}
154  	matched = SecPolicyCheckCertCertificatePolicy(cert, oidData);
155  out:
156  	if (oidData) {
157  		CFRelease(oidData);
158  	}
159  	return matched;
160  }
161  
162  	
163  CFDateRef certificateCopyFieldDate(SecCertificateRef cert, const CSSM_OID &policyOid)
164  {
165  	CFDataRef oidData = NULL;
166  	CFDateRef value = NULL;
167  	CFDataRef data = NULL;
168  	SecAsn1CoderRef coder = NULL;
169  	CSSM_DATA str = { 0 };
170  	CFAbsoluteTime time = 0.0;
171  	OSStatus status = 0;
172  	bool isCritical;
173  	
174  	oidData = CFDataCreateWithBytesNoCopy(NULL, policyOid.Data, policyOid.Length,
175  										  kCFAllocatorNull);
176  	
177  	if (oidData == NULL) {
178  		goto out;
179  	}
180  	
181  	data = SecCertificateCopyExtensionValue(cert, oidData, &isCritical);
182  	
183  	if (data == NULL) {
184  		goto out;
185  	}
186  	
187  	status = SecAsn1CoderCreate(&coder);
188  	if (status != 0) {
189  		goto out;
190  	}
191  	
192  	// We currently only support UTF8 strings.
193  	status = SecAsn1Decode(coder, CFDataGetBytePtr(data), CFDataGetLength(data),
194  						   kSecAsn1UTF8StringTemplate, &str);
195  	if (status != 0) {
196  		goto out;
197  	}
198  	
199  	time = SecAbsoluteTimeFromDateContent(ASN1_GENERALIZED_TIME,
200  										  str.Data, str.Length);
201  										  
202  	if (time == 0.0) {
203  		goto out;
204  	}
205  
206  	value = CFDateCreate(NULL, time);
207  out:
208  	if (coder) {
209  		SecAsn1CoderRelease(coder);
210  	}
211  	if (data) {
212  		CFRelease(data);
213  	}
214  	if (oidData) {
215  		CFRelease(oidData);
216  	}
217  	
218  	return value;
219  }
220  #endif
221  
222  //
223  // Copyfile
224  //
225  Copyfile::Copyfile()
226  {
227  	if (!(mState = copyfile_state_alloc()))
228  		UnixError::throwMe();
229  }
230  	
231  void Copyfile::set(uint32_t flag, const void *value)
232  {
233  	check(::copyfile_state_set(mState, flag, value));
234  }
235  
236  void Copyfile::get(uint32_t flag, void *value)
237  {
238  	check(::copyfile_state_set(mState, flag, value));
239  }
240  	
241  void Copyfile::operator () (const char *src, const char *dst, copyfile_flags_t flags)
242  {
243  	check(::copyfile(src, dst, mState, flags));
244  }
245  
246  void Copyfile::check(int rc)
247  {
248  	if (rc < 0)
249  		UnixError::throwMe();
250  }
251  
252  
253  //
254  // MessageTracer support
255  //
256  MessageTrace::MessageTrace(const char *domain, const char *signature)
257  {
258  	mAsl = asl_new(ASL_TYPE_MSG);
259  	if (domain)
260  		asl_set(mAsl, "com.apple.message.domain", domain);
261  	if (signature)
262  		asl_set(mAsl, "com.apple.message.signature", signature);
263  }
264  
265  void MessageTrace::add(const char *key, const char *format, ...)
266  {
267  	va_list args;
268  	va_start(args, format);
269  	char value[200];
270  	vsnprintf(value, sizeof(value), format, args);
271  	va_end(args);
272  	asl_set(mAsl, (string("com.apple.message.") + key).c_str(), value);
273  }
274  	
275  void MessageTrace::send(const char *format, ...)
276  {
277  	va_list args;
278  	va_start(args, format);
279  	asl_vlog(NULL, mAsl, ASL_LEVEL_NOTICE, format, args);
280  	va_end(args);
281  }
282  
283  
284  
285  // Resource limited async workers for doing work on nested bundles
286  LimitedAsync::LimitedAsync(bool async)
287  {
288  	// validate multiple resources concurrently if bundle resides on solid-state media
289  
290  	// How many async workers to spin off. If zero, validating only happens synchronously.
291  	long async_workers = 0;
292  
293  	long ncpu = sysconf(_SC_NPROCESSORS_ONLN);
294  
295  	if (async && ncpu > 0)
296  		async_workers = ncpu - 1; // one less because this thread also validates
297  
298  	mResourceSemaphore = new Dispatch::Semaphore(async_workers);
299  }
300  
301  LimitedAsync::LimitedAsync(LimitedAsync &limitedAsync)
302  {
303  	mResourceSemaphore = new Dispatch::Semaphore(*limitedAsync.mResourceSemaphore);
304  }
305  
306  LimitedAsync::~LimitedAsync()
307  {
308  	delete mResourceSemaphore;
309  }
310  
311  bool LimitedAsync::perform(Dispatch::Group &groupRef, void (^block)()) {
312  	__block Dispatch::SemaphoreWait wait(*mResourceSemaphore, DISPATCH_TIME_NOW);
313  
314  	if (wait.acquired()) {
315  		dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
316  
317  		groupRef.enqueue(defaultQueue, ^{
318  			// Hold the semaphore count until the worker is done validating.
319  			Dispatch::SemaphoreWait innerWait(wait);
320  			block();
321  		});
322  		return true;
323  	} else {
324  		block();
325  		return false;
326  	}
327  }
328  
329  bool isOnRootFilesystem(const char *path)
330  {
331  	int rc = 0;
332  	struct statfs sfb;
333  
334  	rc = statfs(path, &sfb);
335  	if (rc != 0) {
336  		secerror("Unable to check if path is on rootfs: %d, %s", errno, path);
337  		return false;
338  	}
339  	return ((sfb.f_flags & MNT_ROOTFS) == MNT_ROOTFS);
340  }
341  
342  bool pathExists(const char *path)
343  {
344  	int rc;
345  
346  	if (!path) {
347  		secerror("path is NULL");
348  		return false;
349  	}
350  
351  	rc = access(path, F_OK);
352  	if (rc != 0) {
353  		if (errno != ENOENT) {
354  			secerror("Unable to check if path exists: %d, %s", errno, path);
355  		}
356  		return false;
357  	}
358  
359  	return true;
360  }
361  
362  bool pathMatchesXattrFilenameSpec(const char *path)
363  {
364  	char *baseName = NULL;
365  	bool ret = false;
366  
367  	if (!path) {
368  		secerror("path is NULL");
369  		goto done;
370  	}
371  
372  	// Extra byte for NULL storage.
373  	baseName = (char *)malloc(strlen(path) + 1);
374  	if (!baseName) {
375  		secerror("Unable to allocate space for storing basename: %d [%s]", errno, strerror(errno));
376  		goto done;
377  	}
378  
379  	// basename_r will return a "/" if path is only slashes. It will return
380  	// a "." for a NULL/empty path. Both of these cases are handled by the logic
381  	// later. The only situation where basename_r will return a NULL is when path
382  	// is longer than MAXPATHLEN.
383  
384  	if (basename_r(path, baseName) == NULL) {
385  		secerror("Could not get basename of %s: %d [%s]", path, errno, strerror(errno));
386  		goto done;
387  	}
388  
389  	// The file name must start with "._", followed by the name
390  	// of the file for which it stores the xattrs. Hence, its length
391  	// must be at least three --> 2 for "._" and 1 for a non-empty file
392  	// name.
393  	if (strlen(baseName) < 3) {
394  		goto done;
395  	}
396  
397  	if (baseName[0] != '.' || baseName[1] != '_') {
398  		goto done;
399  	}
400  
401  	ret = true;
402  
403  done:
404  	if (baseName) {
405  		free(baseName);
406  	}
407  
408  	return ret;
409  }
410  
411  bool pathIsRegularFile(const char *path)
412  {
413  	if (!path) {
414  		secerror("path is NULL");
415  		return false;
416  	}
417  
418  	struct stat sb;
419  	if (stat(path, &sb)) {
420  		secerror("Unable to stat %s: %d [%s]", path, errno, strerror(errno));
421  		return false;
422  	}
423  
424  	return (sb.st_mode & S_IFREG) == S_IFREG;
425  }
426  
427  bool pathHasXattrs(const char *path)
428  {
429  	if (!path) {
430  		secerror("path is NULL");
431  		return false;
432  	}
433  
434  	ssize_t xattrSize = listxattr(path, NULL, 0, 0);
435  	if (xattrSize == -1) {
436  		secerror("Unable to acquire the xattr list from %s", path);
437  		return false;
438  	}
439  
440  	return (xattrSize > 0);
441  }
442  
443  bool pathFileSystemUsesXattrFiles(const char *path)
444  {
445  	struct _VolumeCapabilitiesWrapped {
446  		uint32_t length;
447  		vol_capabilities_attr_t volume_capabilities;
448  	} __attribute__((aligned(4), packed));
449  
450  	struct attrlist attr_list;
451  	struct _VolumeCapabilitiesWrapped volume_cap_wrapped;
452  	struct statfs sfb;
453  
454  	if (!path) {
455  		secerror("path is NULL");
456  		return false;
457  	}
458  
459  	int ret = statfs(path, &sfb);
460  	if (ret != 0) {
461  		secerror("Unable to convert %s to its filesystem mount [statfs failed]: %d [%s]", path, errno, strerror(errno));
462  		return false;
463  	}
464  	path = sfb.f_mntonname;
465  
466  	memset(&attr_list, 0, sizeof(attr_list));
467  	attr_list.bitmapcount = ATTR_BIT_MAP_COUNT;
468  	attr_list.volattr = ATTR_VOL_INFO | ATTR_VOL_CAPABILITIES;
469  
470  	ret = getattrlist(path, &attr_list, &volume_cap_wrapped, sizeof(volume_cap_wrapped), 0);
471  	if (ret) {
472  		secerror("Unable to get volume capabilities from %s: %d [%s]", path, errno, strerror(errno));
473  		return false;
474  	}
475  
476  	if (volume_cap_wrapped.length != sizeof(volume_cap_wrapped)) {
477  		secerror("getattrlist return length incorrect, expected %lu, got %u", sizeof(volume_cap_wrapped), volume_cap_wrapped.length);
478  		return false;
479  	}
480  
481  	// The valid bit tells us whether the corresponding bit in capabilities is valid
482  	// or not. For file systems where the valid bit isn't set, we can safely assume that
483  	// extended attributes aren't supported natively.
484  
485  	bool xattr_valid = (volume_cap_wrapped.volume_capabilities.valid[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) == VOL_CAP_INT_EXTENDED_ATTR;
486  	if (!xattr_valid) {
487  		return true;
488  	}
489  
490  	bool xattr_capability = (volume_cap_wrapped.volume_capabilities.capabilities[VOL_CAPABILITIES_INTERFACES] & VOL_CAP_INT_EXTENDED_ATTR) == VOL_CAP_INT_EXTENDED_ATTR;
491  	if (!xattr_capability) {
492  		return true;
493  	}
494  
495  	return false;
496  }
497  
498  bool pathIsValidXattrFile(const string fullPath, const char *scope)
499  {
500  	// Confirm that fullPath begins from root.
501  	if (fullPath[0] != '/') {
502  		secinfo(scope, "%s isn't a full path, but a relative path", fullPath.c_str());
503  		return false;
504  	}
505  
506  	// Confirm that fullPath is a regular file.
507  	if (!pathIsRegularFile(fullPath.c_str())) {
508  		secinfo(scope, "%s isn't a regular file", fullPath.c_str());
509  		return false;
510  	}
511  
512  	// Check that the file name matches the Xattr file spec.
513  	if (!pathMatchesXattrFilenameSpec(fullPath.c_str())) {
514  		secinfo(scope, "%s doesn't match Xattr file path spec", fullPath.c_str());
515  		return false;
516  	}
517  
518  	// We are guaranteed to have at least one "/" by virtue of fullPath
519  	// being a path from the root of the filesystem hierarchy.
520  	//
521  	// We construct the real file name by copying everything up to
522  	// the last "/", adding the "/" back in, then skipping
523  	// over the backslash (+1) and the "._" (+2) in the rest of the
524  	// string.
525  
526  	size_t lastBackSlash = fullPath.find_last_of("/");
527  	const string realFilePath = fullPath.substr(0, lastBackSlash) + "/" + fullPath.substr(lastBackSlash + 1 + 2);
528  
529  	if (!pathExists(realFilePath.c_str())) {
530  		secinfo(scope, "%s does not exist, forcing resource validation on %s", realFilePath.c_str(), fullPath.c_str());
531  		return false;
532  	}
533  
534  	// Lastly, we need to confirm that the real file contains some xattrs. If not,
535  	// then the file represented by fullPath isn't an xattr file.
536  	if (!pathHasXattrs(realFilePath.c_str())) {
537  		secinfo(scope, "%s does not contain xattrs, forcing resource validation on %s", realFilePath.c_str(), fullPath.c_str());
538  		return false;
539  	}
540  
541  	return true;
542  }
543  
544  string pathRemaining(string fullPath, string prefix)
545  {
546  	if ((fullPath.length() < prefix.length()) ||
547  		(prefix.length() == 0) ||
548  		(fullPath.length() == 0) ||
549  		!isPathPrefix(prefix, fullPath)) {
550  		return "";
551  	}
552  
553  	size_t currentPosition = prefix.length();
554  	if (prefix[currentPosition-1] != '/') {
555  		// If the prefix doesn't already end with a /, add one to the position so the remaining
556  		// doesn't start with one.
557  		currentPosition += 1;
558  	}
559  
560  	// Ensure we're not indexing outside the bounds of fullPath.
561  	if (currentPosition >= fullPath.length()) {
562  		return "";
563  	}
564  
565  	return fullPath.substr(currentPosition, string::npos);
566  }
567  
568  bool isPathPrefix(string prefixPath, string fullPath)
569  {
570  	size_t pos = fullPath.find(prefixPath);
571  	if (pos == 0) {
572  		// If they're a perfect match, its not really a path prefix.
573  		if (prefixPath.length() == fullPath.length()) {
574  			return false;
575  		}
576  
577  		// Ensure the prefix starts a relative path under the prefix.
578  		if (prefixPath.back() == '/') {
579  			// If the prefix ends with a delimeter, we're good.
580  			return true;
581  		} else {
582  			// Otherwise, the next character in the fullPath needs to be a delimeter.
583  			return fullPath.at(prefixPath.length()) == '/';
584  		}
585  	}
586  	return false;
587  }
588  
589  bool iterateLargestSubpaths(string path, bool (^pathHandler)(string))
590  {
591  	size_t lastPossibleSlash = path.length();
592  	size_t currentPosition = 0;
593  	bool stopped = false;
594  
595  	while (!stopped) {
596  		currentPosition = path.find_last_of("/", lastPossibleSlash);
597  		if (currentPosition == string::npos || currentPosition == 0) {
598  			break;
599  		}
600  
601  		// Erase from the current position to the end of the string.
602  		path.erase(currentPosition, string::npos);
603  		stopped = pathHandler(path);
604  		if (!stopped) {
605  			if (currentPosition == 0) {
606  				break;
607  			}
608  			lastPossibleSlash = currentPosition - 1;
609  		}
610  	}
611  	return stopped;
612  }
613  
614  
615  } // end namespace CodeSigning
616  } // end namespace Security