#include "SFFileVault.h"
#include "ExecCLITool.h"
#include <Security/SecKeychain.h>
#include <Security/KCCursor.h>
#include <Security/cfutilities.h>
#include <Security/Keychains.h>
#include <Security/KCUtilities.h>
#include <Security/globals.h>
#include <Security/Certificate.h>
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
#include <Security/SecKeychainAPIPriv.h>
#include "SecFileVaultCert.h"
#pragma mark -------------------- Environment Variables --------------------
#define HDIUTIL_PATH "/usr/bin/hdiutil" // environment var -> HDIUTIL_PATH
#define HDID_PATH "/usr/bin/hdid" // environment var -> HDID_PATH
#define MOUNT_HFS_PATH "/sbin/mount_hfs" // environment var -> MOUNT_HFS_PATH
#define UMOUNT_PATH "/sbin/umount" // environment var -> UMOUNT_PATH
const char * const SFFileVault:: _defaultMasterKeychainPath = "/System/Library/Keychains/";
const char * const SFFileVault::_masterKeychainName = "FileVaultMaster";
#pragma mark -------------------- SFFileVault implementation --------------------
OSStatus SFFileVault::mount(CFStringRef password, CFURLRef certificate, CFURLRef dmgin,
CFURLRef mountpoint, CFStringRef *devicepath)
{
const Boolean resolveAgainstBase = true;
char imageFileString[PATH_MAX + 1];
if (!CFURLGetFileSystemRepresentation(dmgin, resolveAgainstBase, reinterpret_cast<UInt8 *>(imageFileString), PATH_MAX))
MacOSError::throwMe(paramErr);
if (certificate)
MacOSError::throwMe(unimpErr);
ExecCLITool rt;
rt.input(password,true); rt.run(HDID_PATH,"HDID_PATH", "-nomount", "-stdinpass", "-plist", imageFileString, NULL);
CFRef<CFStringRef> devicePathString = extractDevicepath(rt); if (!devicePathString)
MacOSError::throwMe(paramErr);
const char *devpath = CFStringGetCStringPtr(devicePathString, kCFStringEncodingMacRoman);
if (!devpath)
MacOSError::throwMe(ioErr);
char mountpointString[PATH_MAX + 1];
if (!CFURLGetFileSystemRepresentation(mountpoint, resolveAgainstBase, reinterpret_cast<UInt8 *>(mountpointString), PATH_MAX))
MacOSError::throwMe(paramErr);
rt.run(MOUNT_HFS_PATH,"MOUNT_HFS_PATH", devpath, mountpointString, NULL);
*devicepath = CFStringCreateCopy(NULL, devicePathString);
return noErr;
}
OSStatus SFFileVault::mastermount(CFURLRef dmgin, CFURLRef mountpoint, CFStringRef *devicepath)
{
CFStringRef password = NULL;
CFURLRef certificate = NULL;
getCertificate(&certificate);
OSStatus status = mount(password, certificate, dmgin, mountpoint, devicepath);
return status;
}
OSStatus SFFileVault::unmount(CFURLRef mountpoint, CFStringRef devicepath)
{
ExecCLITool rt;
Boolean resolveAgainstBase = true;
char mountpointString[PATH_MAX + 1];
if (!CFURLGetFileSystemRepresentation(mountpoint, resolveAgainstBase, reinterpret_cast<UInt8 *>(mountpointString), PATH_MAX))
MacOSError::throwMe(paramErr);
rt.run(UMOUNT_PATH,"UMOUNT_PATH", "-f", mountpointString, NULL);
const char *devpath = CFStringGetCStringPtr(devicepath, kCFStringEncodingMacRoman);
if (!devpath)
MacOSError::throwMe(paramErr);
return rt.run(HDIUTIL_PATH,"HDIUTIL_PATH", "detach", devpath, NULL);
}
OSStatus SFFileVault::userChangePassword(CFStringRef password, CFStringRef devicepath)
{
MacOSError::throwMe(unimpErr);
ExecCLITool rt;
const char *devpath = CFStringGetCStringPtr(devicepath, kCFStringEncodingMacRoman);
if (!devpath)
MacOSError::throwMe(paramErr);
rt.input(password,true); return rt.run(HDIUTIL_PATH,"HDIUTIL_PATH", "chpass", devpath, NULL);
return noErr;
}
OSStatus SFFileVault::makeMasterPassword(CFStringRef masterPasswordPassword, SecKeychainRef *keychainRef)
{
char masterKeychainPath[PATH_MAX + 1];
const char *envPath = getenv("FILEVAULT_MASTER_PATH"); if (!envPath)
envPath = _defaultMasterKeychainPath;
snprintf(masterKeychainPath, sizeof(masterKeychainPath), "%s%s.keychain", envPath, _masterKeychainName);
const char *mpass = CFStringGetCStringPtr(masterPasswordPassword, kCFStringEncodingMacRoman);
if (!mpass)
MacOSError::throwMe(paramErr);
const UInt32 passwordLength = strlen(mpass);
KeychainCore::Keychain keychain = KeychainCore::globals().storageManager.make(Required(&masterKeychainPath),false);
try
{
keychain->create(passwordLength, mpass);
}
catch (const MacOSError &err)
{
if (err.osStatus()!=errSecDuplicateKeychain)
throw;
}
catch (const CssmCommonError &err)
{
if (err.cssmError()!=CSSMERR_DL_DATASTORE_ALREADY_EXISTS)
throw;
}
RequiredParam(keychainRef)=keychain->handle();
SecFileVaultCert fvc;
CFStringRef hostName = CFSTR("com.apple.fv");
CFStringRef userName = CFSTR("User Name");
CFDataRef certData = NULL; OSStatus status = fvc.createPair(hostName,userName,*keychainRef,&certData);
if (status)
MacOSError::throwMe(status);
status = writeCertificateFile(certData);
if (status)
MacOSError::throwMe(status);
return noErr;
}
OSStatus SFFileVault::create(CFStringRef password, CFURLRef certificate, CFURLRef dmgout,
CFStringRef volumeName, CFStringRef sizeSpec)
{
ExecCLITool rt;
if (!volumeName)
MacOSError::throwMe(paramErr);
const char *volname = CFStringGetCStringPtr(volumeName, kCFStringEncodingMacRoman);
if (!volname)
MacOSError::throwMe(paramErr);
if (!sizeSpec)
MacOSError::throwMe(paramErr);
const char *sizestr = CFStringGetCStringPtr(sizeSpec, kCFStringEncodingMacRoman);
if (!sizestr)
MacOSError::throwMe(paramErr);
CFRef<CFStringRef> fileString = CFURLCopyFileSystemPath(dmgout, kCFURLPOSIXPathStyle);
if (!fileString)
MacOSError::throwMe(paramErr);
const char *fname = CFStringGetCStringPtr(fileString, kCFStringEncodingMacRoman);
if (!fname)
MacOSError::throwMe(paramErr);
const char *certificateParamString = certificate?"-certificate":"-layout"; CFStringRef certificateFileString = certificate?CFURLCopyFileSystemPath(certificate, kCFURLPOSIXPathStyle):NULL;
if (certificate && !certificateFileString)
MacOSError::throwMe(paramErr);
const char *certFileString = certificate?CFStringGetCStringPtr(certificateFileString, kCFStringEncodingMacRoman):"SPUD";
if (certificate && !certFileString)
MacOSError::throwMe(paramErr);
rt.input(password,true); OSStatus status = rt.run(HDIUTIL_PATH,"HDIUTIL_PATH", "create", "-encryption", "CEncryptedEncoding",
"-stdinpass", "-type", "SPARSE", "-fs", "HFS+", "-volname", volname, "-size", sizestr,
certificateParamString, certFileString, fname, NULL);
if (certificateFileString)
CFRelease(certificateFileString);
return status;
}
Boolean SFFileVault::masterPasswordEnabled(SecKeychainRef *keychainRef)
{
char masterKeychain[PATH_MAX + 1];
snprintf(masterKeychain, sizeof(masterKeychain), "%s.keychain", getKeychainPath());
SecKeychainRef tmpKeychainRef=KeychainCore::globals().storageManager.make(masterKeychain, false)->handle();
if (tmpKeychainRef == NULL)
return false;
if (keychainRef)
*keychainRef = tmpKeychainRef;
else
CFRelease(tmpKeychainRef);
return true;
}
OSStatus SFFileVault::changeMasterPasswordPassword(CFStringRef oldPassword,CFStringRef newPassword)
{
SecKeychainRef keychainRef;
if (!masterPasswordEnabled(&keychainRef))
MacOSError::throwMe(errSecNoSuchKeychain);
std::string oldpw = cfString(oldPassword); std::string newpw = cfString(newPassword);
KeychainCore::Keychain keychain = KeychainCore::Keychain::optional(keychainRef);
keychain->changePassphrase (oldpw.length(), oldpw.c_str(), newpw.length(), newpw.c_str());
CFRelease(keychainRef);
return noErr;
}
#pragma mark -------------------- Helpers --------------------
#define SYSTEM_ENTITIES_KEY CFSTR("system-entities")
#define CONTENT_HINT_KEY CFSTR("content-hint")
#define DEV_ENTRY_KEY CFSTR("dev-entry")
#define APPLE_HFS_KEY CFSTR("Apple_HFS")
CFStringRef SFFileVault::extractDevicepath(const ExecCLITool& rt)
{
CFRef<CFDataRef> tableData = CFDataCreate(NULL,reinterpret_cast<const UInt8 *>(rt.data()),rt.length());
CFStringRef errorString = NULL;
CFRef<CFDictionaryRef> devTable = static_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(NULL,
tableData, kCFPropertyListImmutable, &errorString));
if (errorString != NULL)
{
CFRelease(errorString);
return NULL;
}
CFRef<CFArrayRef> sysEntities = static_cast<CFArrayRef>(CFDictionaryGetValue(devTable,SYSTEM_ENTITIES_KEY));
if (sysEntities == NULL)
return NULL;
CFIndex dictionaryCount = CFArrayGetCount(sysEntities);
for (CFIndex ix=0;ix < dictionaryCount;ix++)
{
CFRef<CFDictionaryRef> dict = static_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(sysEntities, ix));
CFRef<CFStringRef> deviceEntryString = static_cast<CFStringRef>(CFDictionaryGetValue(dict,CONTENT_HINT_KEY));
if (CFEqual(deviceEntryString, APPLE_HFS_KEY)) return static_cast<CFStringRef>(CFDictionaryGetValue(dict,DEV_ENTRY_KEY));
}
return NULL;
}
OSStatus SFFileVault::getCertificate(CFURLRef *certificateFile)
{
MacOSError::throwMe(unimpErr);
char masterKeychain[PATH_MAX + 1];
snprintf(masterKeychain, sizeof(masterKeychain), "%s.keychain", getKeychainPath());
KeychainCore::Keychain keychain = KeychainCore::globals().storageManager.make(Required(&masterKeychain),false);
KeychainCore::StorageManager::KeychainList keychains;
KeychainCore::globals().storageManager.optionalSearchList(keychain, keychains);
KeychainCore::KCCursor cursor(keychains, kSecCertificateItemClass, NULL);
KeychainCore::Item item;
if (!cursor->next(item))
CssmError::throwMe(errSecItemNotFound);
return noErr;
}
OSStatus SFFileVault::writeCertificateFile(CFDataRef certData)
{
const char *certFile = getCertificateFileName();
OSStatus status = writeFile(certFile, CFDataGetBytePtr(certData), CFDataGetLength(certData));
if (certFile)
::free(const_cast<char *>(certFile));
return status;
}
const char *SFFileVault::getKeychainPath()
{
char masterKeychainPath[PATH_MAX + 1];
const char *envPath = getenv("FILEVAULT_MASTER_PATH"); if (!envPath)
envPath = _defaultMasterKeychainPath;
snprintf(masterKeychainPath, sizeof(masterKeychainPath), "%s%s", envPath, _masterKeychainName);
size_t sz = strlen(masterKeychainPath)+1;
char *path = static_cast<char *>(malloc(sz));
strncpy(path,masterKeychainPath,sz);
return static_cast<const char *>(path);
}
const char *SFFileVault::getCertificateFileName()
{
char certFile[PATH_MAX + 1];
snprintf(certFile, sizeof(certFile), "%s.cer", getKeychainPath());
size_t sz = strlen(certFile)+1;
char *path = static_cast<char *>(malloc(sz));
strncpy(path,certFile,sz);
return static_cast<const char *>(path);
}
int SFFileVault::writeFile(const char *fileName, const unsigned char *bytes, unsigned int numBytes)
{
int fd = open(fileName, O_RDWR | O_CREAT | O_TRUNC, 0600);
if (fd <= 0)
return errno;
if (lseek(fd, 0, SEEK_SET) < 0)
return errno;
int rtn = write(fd, bytes, (size_t)numBytes);
rtn = (rtn != static_cast<int>(numBytes))?EIO:0;
close(fd);
return rtn;
}
#pragma mark -------------------- Unused --------------------