#include "createFVMaster.h"
#include "readline_cssm.h"
#include "security_tool.h"
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <Security/SecKeychain.h>
#include <Security/SecCertificate.h>
#include <Security/SecKeychain.h>
#include <Security/oidsalg.h>
#include <Security/oidsattr.h>
#include <limits.h>
#include <utilities/SecCFRelease.h>
#include "srCdsaUtils.h"
#include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
const char * const _masterKeychainName = "FileVaultMaster.keychain";
const char * const _masterKeychainPath = "./FileVaultMaster";
#define SR_KEY_ALGORITHM CSSM_ALGID_RSA
#define SR_KEY_SIZE_IN_BITS 1024
#define SR2_KEY_SIZE_IN_BITS 2048 // Recommended size for FileVault2 (FDE)
#define SR_CERT_SIGNATURE_ALGORITHM CSSM_ALGID_SHA256WithRSA
#define SR_CERT_SIGNATURE_ALG_OID CSSMOID_SHA256WithRSA
OSStatus makeMasterPassword(const char *fvmkcName, const char *masterPasswordPassword, uint32 keySizeInBits, SecKeychainRef *keychainRef);
OSStatus createPair(CFStringRef hostName,CFStringRef userName,SecKeychainRef keychainRef, uint32 keySizeInBits, CFDataRef *cert);
OSStatus generateKeyPair(CSSM_CSP_HANDLE cspHand, CSSM_DL_DB_HANDLE dlDbHand, CSSM_ALGORITHMS keyAlg,
uint32 keySizeInBits, const char *keyLabel, CSSM_KEY_PTR *pubKeyPtr, CSSM_KEY_PTR *privKeyPtr);
OSStatus createRootCert(CSSM_TP_HANDLE tpHand, CSSM_CL_HANDLE clHand, CSSM_CSP_HANDLE cspHand,
CSSM_KEY_PTR subjPubKey, CSSM_KEY_PTR signerPrivKey, const char *hostName, const char *userName,
CSSM_ALGORITHMS sigAlg, const CSSM_OID *sigOid, CSSM_DATA_PTR certData);
static char *secCopyCString(CFStringRef theString);
OSStatus makeMasterPassword(const char *fvmkcName, const char *masterPasswordPassword, uint32 keySizeInBits, SecKeychainRef *keychainRef)
{
SecAccessRef initialAccess = NULL;
if (!masterPasswordPassword)
{
sec_error("You must supply a non-empty password");
return -2;
}
OSStatus status = SecKeychainCreate(fvmkcName, (UInt32) strlen(masterPasswordPassword), masterPasswordPassword, false, initialAccess, keychainRef);
if (status!=noErr)
{
if (status==errSecDuplicateKeychain || status==CSSMERR_DL_DATASTORE_ALREADY_EXISTS)
sec_error("The keychain file %s already exists", fvmkcName);
return status;
}
char host[PATH_MAX];
int rx = gethostname(host, sizeof(host));
if (rx)
strcpy(host, "localhost");
CFStringRef hostName = CFSTR("FileVault Recovery Key"); CFStringRef userName = CFStringCreateWithCString(kCFAllocatorDefault, host, kCFStringEncodingUTF8);
CFDataRef certData = NULL;
printf("Generating a %d bit key pair; this may take several minutes\n", keySizeInBits);
status = createPair(hostName,userName,*keychainRef,keySizeInBits, &certData);
CFReleaseNull(userName);
if (status)
sec_error("Error in createPair: %s", sec_errstr(status));
if (certData)
CFRelease(certData);
return status;
}
OSStatus createPair(CFStringRef hostName,CFStringRef userName,SecKeychainRef keychainRef, uint32 keySizeInBits, CFDataRef *cert)
{
SecCertificateRef certRef = NULL;
CSSM_DL_DB_HANDLE dlDbHand = {0, 0};
CSSM_CSP_HANDLE cspHand = 0;
CSSM_TP_HANDLE tpHand = 0;
CSSM_CL_HANDLE clHand = 0;
CSSM_KEY_PTR pubKey = NULL;
CSSM_KEY_PTR privKey = NULL;
CSSM_DATA certData = {0, NULL};
char *hostStr = NULL;
char *userStr = NULL;
OSStatus ortn;
CSSM_OID algOid = SR_CERT_SIGNATURE_ALG_OID;
hostStr = secCopyCString(hostName);
userStr = secCopyCString(userName);
if (!hostStr || !userStr) {
ortn = paramErr;
goto xit;
}
ortn = SecKeychainGetCSPHandle(keychainRef, &cspHand);
if (ortn)
goto xit;
ortn = SecKeychainGetDLDBHandle(keychainRef, &dlDbHand);
if (ortn)
goto xit;
tpHand = srTpStartup();
if (tpHand == 0)
{
ortn = ioErr;
goto xit;
}
clHand = srClStartup();
if (clHand == 0)
{
ortn = ioErr;
goto xit;
}
ortn = generateKeyPair(cspHand, dlDbHand, SR_KEY_ALGORITHM, keySizeInBits,
"FileVault Master Password Key", &pubKey, &privKey);
if (ortn)
goto xit;
ortn = createRootCert(tpHand,clHand,cspHand,pubKey,privKey,hostStr,userStr,
SR_CERT_SIGNATURE_ALGORITHM,&algOid,&certData);
if (ortn)
goto xit;
ortn = SecCertificateCreateFromData(&certData, CSSM_CERT_X_509v3, CSSM_CERT_ENCODING_DER, &certRef);
if (ortn)
goto xit;
ortn = SecCertificateAddToKeychain(certRef, keychainRef);
if (ortn)
goto xit;
*cert = CFDataCreate(NULL, certData.Data, certData.Length);
xit:
if (certRef)
CFRelease(certRef);
if (certData.Data)
free(certData.Data);
if (hostStr)
free(hostStr);
if (userStr)
free(userStr);
if (tpHand)
CSSM_ModuleDetach(tpHand);
if (clHand)
CSSM_ModuleDetach(clHand);
if (pubKey)
{
CSSM_FreeKey(cspHand,
NULL, pubKey,
CSSM_FALSE); APP_FREE(pubKey);
}
if (privKey)
{
CSSM_FreeKey(cspHand,
NULL, privKey,
CSSM_FALSE); APP_FREE(privKey);
}
return ortn;
}
char *secCopyCString(CFStringRef theString)
{
CFIndex maxLength = CFStringGetMaximumSizeForEncoding(CFStringGetLength(theString), kCFStringEncodingUTF8) + 1;
char* buffer = (char*) malloc(maxLength);
Boolean converted = CFStringGetCString(theString, buffer, maxLength, kCFStringEncodingUTF8);
if (!converted) {
free(buffer);
buffer = NULL;
}
return buffer;
}
#pragma mark -------------------- SecFileVaultCert private implementation --------------------
OSStatus createRootCert(
CSSM_TP_HANDLE tpHand,
CSSM_CL_HANDLE clHand,
CSSM_CSP_HANDLE cspHand,
CSSM_KEY_PTR subjPubKey,
CSSM_KEY_PTR signerPrivKey,
const char *hostName, const char *userName, CSSM_ALGORITHMS sigAlg,
const CSSM_OID *sigOid,
CSSM_DATA_PTR certData) {
CE_DataAndType exts[2];
CE_DataAndType *extp = exts;
unsigned numExts;
CSSM_DATA refId; CSSM_APPLE_TP_CERT_REQUEST certReq;
CSSM_TP_REQUEST_SET reqSet;
sint32 estTime;
CSSM_BOOL confirmRequired;
CSSM_TP_RESULT_SET_PTR resultSet=NULL;
CSSM_ENCODED_CERT *encCert=NULL;
CSSM_APPLE_TP_NAME_OID subjectNames[2];
CSSM_TP_CALLERAUTH_CONTEXT CallerAuthContext;
CSSM_FIELD policyId;
numExts = 0;
certReq.challengeString = NULL;
extp->type = DT_KeyUsage;
extp->critical = CSSM_FALSE;
extp->extension.keyUsage = CE_KU_DigitalSignature |
CE_KU_KeyCertSign |
CE_KU_KeyEncipherment |
CE_KU_DataEncipherment;
extp++;
numExts++;
extp->type = DT_BasicConstraints;
extp->critical = CSSM_TRUE;
extp->extension.basicConstraints.cA = CSSM_FALSE;
extp->extension.basicConstraints.pathLenConstraintPresent = CSSM_FALSE;
extp++;
numExts++;
subjectNames[0].string = hostName;
subjectNames[0].oid = &CSSMOID_CommonName;
subjectNames[1].string = userName;
subjectNames[1].oid = &CSSMOID_Description;
certReq.cspHand = cspHand;
certReq.clHand = clHand;
certReq.serialNumber = arc4random();
certReq.numSubjectNames = 2;
certReq.subjectNames = subjectNames;
certReq.numIssuerNames = 0; certReq.issuerNames = NULL;
certReq.issuerNameX509 = NULL;
certReq.certPublicKey = subjPubKey;
certReq.issuerPrivateKey = signerPrivKey;
certReq.signatureAlg = sigAlg;
certReq.signatureOid = *sigOid;
certReq.notBefore = 0;
certReq.notAfter = 60 * 60 * 24 * 365; certReq.numExtensions = numExts;
certReq.extensions = exts;
reqSet.NumberOfRequests = 1;
reqSet.Requests = &certReq;
memset(&CallerAuthContext, 0, sizeof(CSSM_TP_CALLERAUTH_CONTEXT));
memset(&policyId, 0, sizeof(CSSM_FIELD));
policyId.FieldOid = CSSMOID_APPLE_TP_LOCAL_CERT_GEN;
CallerAuthContext.Policy.NumberOfPolicyIds = 1;
CallerAuthContext.Policy.PolicyIds = &policyId;
CSSM_RETURN crtn = CSSM_TP_SubmitCredRequest(tpHand,
NULL, CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
&reqSet,
&CallerAuthContext,
&estTime,
&refId);
if(crtn) {
sec_error("CSSM_TP_SubmitCredRequest: %s", sec_errstr(crtn));
goto xit;
}
crtn = CSSM_TP_RetrieveCredResult(tpHand,
&refId,
NULL, &estTime,
&confirmRequired,
&resultSet);
if(crtn) {
sec_error("CSSM_TP_RetrieveCredResult: %s", sec_errstr(crtn));
goto xit;
}
if(resultSet == NULL) {
sec_error("CSSM_TP_RetrieveCredResult: returned NULL result set");
crtn = ioErr;
goto xit;
}
encCert = (CSSM_ENCODED_CERT *)resultSet->Results;
certData->Length = encCert->CertBlob.Length;
certData->Data = malloc(encCert->CertBlob.Length);
if (certData->Data)
memcpy(certData->Data, encCert->CertBlob.Data, encCert->CertBlob.Length);
crtn = noErr;
xit:
APP_FREE(refId.Data);
if (encCert)
{
if (encCert->CertBlob.Data)
{
APP_FREE(encCert->CertBlob.Data);
}
APP_FREE(encCert);
}
APP_FREE(resultSet);
return crtn;
}
static CSSM_RETURN refKeyToRaw(
CSSM_CSP_HANDLE cspHand,
const CSSM_KEY *refKey,
CSSM_KEY_PTR rawKey) {
CSSM_CC_HANDLE ccHand;
CSSM_RETURN crtn;
CSSM_ACCESS_CREDENTIALS creds;
memset(rawKey, 0, sizeof(CSSM_KEY));
memset(&creds, 0, sizeof(CSSM_ACCESS_CREDENTIALS));
crtn = CSSM_CSP_CreateSymmetricContext(cspHand,
CSSM_ALGID_NONE,
CSSM_ALGMODE_NONE,
&creds, NULL, NULL, CSSM_PADDING_NONE, 0, &ccHand);
if(crtn) {
sec_error("CSSM_CSP_CreateSymmetricContext: refKeyToRaw context err: %s", sec_errstr(crtn));
return crtn;
}
crtn = CSSM_WrapKey(ccHand,
&creds,
refKey,
NULL, rawKey);
if(crtn != CSSM_OK) {
sec_error("CSSM_WrapKey: refKeyToRaw wrap err: %s", sec_errstr(crtn));
return crtn;
}
CSSM_DeleteContext(ccHand);
return CSSM_OK;
}
static CSSM_RETURN setPubKeyHash(
CSSM_CSP_HANDLE cspHand,
CSSM_DL_DB_HANDLE dlDbHand,
const CSSM_KEY *pubOrPrivKey, const char *keyLabel) {
CSSM_QUERY query;
CSSM_SELECTION_PREDICATE predicate;
CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
CSSM_RETURN crtn;
CSSM_DATA labelData;
CSSM_HANDLE resultHand;
labelData.Data = (uint8 *)keyLabel;
labelData.Length = strlen(keyLabel) + 1; query.RecordType = CSSM_DL_DB_RECORD_PRIVATE_KEY;
query.Conjunctive = CSSM_DB_NONE;
query.NumSelectionPredicates = 1;
predicate.DbOperator = CSSM_DB_EQUAL;
predicate.Attribute.Info.AttributeNameFormat =
CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
predicate.Attribute.Info.Label.AttributeName = "Label";
predicate.Attribute.Info.AttributeFormat =
CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
predicate.Attribute.Value = &labelData;
query.SelectionPredicate = &predicate;
query.QueryLimits.TimeLimit = 0;
query.QueryLimits.SizeLimit = 1;
query.QueryFlags = 0;
CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
CSSM_DB_ATTRIBUTE_DATA attr;
attr.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
attr.Info.Label.AttributeName = "Label";
attr.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_PRIVATE_KEY;
recordAttrs.NumberOfAttributes = 1;
recordAttrs.AttributeData = &attr;
crtn = CSSM_DL_DataGetFirst(dlDbHand,
&query,
&resultHand,
&recordAttrs,
NULL, &record);
if(crtn != CSSM_OK) {
sec_error("CSSM_DL_DataGetFirst: setPubKeyHash: can't find private key: %s", sec_errstr(crtn));
return crtn;
}
CSSM_KEY rawKeyToDigest;
if(pubOrPrivKey->KeyHeader.BlobType == CSSM_KEYBLOB_REFERENCE) {
crtn = refKeyToRaw(cspHand, pubOrPrivKey, &rawKeyToDigest);
if(crtn) {
sec_error("setPubKeyHash: Error converting public key to raw format: %s", sec_errstr(crtn));
return crtn;
}
}
else {
rawKeyToDigest = *pubOrPrivKey;
}
CSSM_CSP_HANDLE rawCspHand = srCspStartup(CSSM_TRUE);
if(rawCspHand == 0) {
printf("***Error connecting to raw CSP; aborting.\n");
return -1;
}
CSSM_DATA_PTR keyDigest = NULL;
CSSM_CC_HANDLE ccHand;
crtn = CSSM_CSP_CreatePassThroughContext(rawCspHand,
&rawKeyToDigest,
&ccHand);
if(ccHand == 0) {
sec_error("CSSM_CSP_CreatePassThroughContext: Error calculating public key hash. Aborting: %s", sec_errstr(crtn));
return -1;
}
crtn = CSSM_CSP_PassThrough(ccHand,
CSSM_APPLECSP_KEYDIGEST,
NULL,
(void **)&keyDigest);
if(crtn) {
sec_error("CSSM_CSP_PassThrough(PUBKEYHASH): Error calculating public key hash. Aborting: %s", sec_errstr(crtn));
return crtn;
}
if(pubOrPrivKey->KeyHeader.BlobType == CSSM_KEYBLOB_REFERENCE) {
CSSM_FreeKey(cspHand, NULL, &rawKeyToDigest, CSSM_FALSE);
}
CSSM_DeleteContext(ccHand);
CSSM_ModuleDetach(rawCspHand);
CSSM_API_MEMORY_FUNCS memFuncs;
crtn = CSSM_GetAPIMemoryFunctions(dlDbHand.DLHandle, &memFuncs);
if(crtn) {
sec_error("CSSM_GetAPIMemoryFunctions(DLHandle): Error: %s", sec_errstr(crtn));
}
else {
memFuncs.free_func(attr.Value->Data, memFuncs.AllocRef);
memFuncs.free_func(attr.Value, memFuncs.AllocRef);
}
attr.Value = keyDigest;
crtn = CSSM_DL_DataModify(dlDbHand,
CSSM_DL_DB_RECORD_PRIVATE_KEY,
record,
&recordAttrs,
NULL, CSSM_DB_MODIFY_ATTRIBUTE_REPLACE);
if(crtn) {
sec_error("CSSM_DL_DataModify(PUBKEYHASH): Error setting public key hash. Aborting: %s", sec_errstr(crtn));
return crtn;
}
crtn = CSSM_DL_DataAbortQuery(dlDbHand, resultHand);
if(crtn) {
sec_error("CSSM_DL_DataAbortQuery: Error while stopping query: %s", sec_errstr(crtn));
}
crtn = CSSM_DL_FreeUniqueRecord(dlDbHand, record);
if(crtn) {
sec_error("CSSM_DL_FreeUniqueRecord: Error while freeing record: %s", sec_errstr(crtn));
crtn = CSSM_OK;
}
if (keyDigest)
{
srAppFree(keyDigest->Data, NULL);
srAppFree(keyDigest, NULL);
}
return CSSM_OK;
}
OSStatus generateKeyPair(
CSSM_CSP_HANDLE cspHand,
CSSM_DL_DB_HANDLE dlDbHand,
CSSM_ALGORITHMS keyAlg, uint32 keySizeInBits,
const char *keyLabel, CSSM_KEY_PTR *pubKeyPtr, CSSM_KEY_PTR *privKeyPtr) {
CSSM_KEY_PTR pubKey = (CSSM_KEY_PTR)(APP_MALLOC(sizeof(CSSM_KEY)));
CSSM_KEY_PTR privKey = (CSSM_KEY_PTR)(APP_MALLOC(sizeof(CSSM_KEY)));
if((pubKey == NULL) || (privKey == NULL)) {
return memFullErr;
}
CSSM_RETURN crtn;
CSSM_KEYUSE pubKeyUse;
CSSM_KEYUSE privKeyUse;
pubKeyUse = CSSM_KEYUSE_VERIFY | CSSM_KEYUSE_ENCRYPT |
CSSM_KEYUSE_WRAP;
privKeyUse = CSSM_KEYUSE_SIGN | CSSM_KEYUSE_DECRYPT |
CSSM_KEYUSE_UNWRAP;
crtn = srCspGenKeyPair(cspHand,
&dlDbHand,
keyAlg,
keyLabel,
(int) strlen(keyLabel) + 1,
keySizeInBits,
pubKey,
pubKeyUse,
CSSM_KEYATTR_EXTRACTABLE | CSSM_KEYATTR_RETURN_REF,
privKey,
privKeyUse,
CSSM_KEYATTR_SENSITIVE | CSSM_KEYATTR_RETURN_REF |
CSSM_KEYATTR_PERMANENT | CSSM_KEYATTR_EXTRACTABLE);
if(crtn) {
APP_FREE(pubKey);
APP_FREE(privKey);
return paramErr;
}
crtn = setPubKeyHash(cspHand,
dlDbHand,
pubKey,
keyLabel);
if(crtn) {
sec_error("setPubKeyHash: Error setting public key hash. Continuing at peril: %s", sec_errstr(crtn));
}
*pubKeyPtr = pubKey;
*privKeyPtr = privKey;
return noErr;
}
int
keychain_createMFV(int argc, char * const *argv)
{
int zero_password = 0;
char *password = NULL;
const char *keychainName = NULL;
int result = 0, ch = 0;
Boolean do_prompt = FALSE;
SecKeychainRef keychainRef = NULL;
uint32 keySizeInBits = SR2_KEY_SIZE_IN_BITS;
while ((ch = getopt(argc, argv, "hp:s:P")) != -1)
{
switch (ch)
{
case 'p':
password = optarg;
break;
case 'P':
do_prompt = TRUE;
break;
case 's':
keySizeInBits = atoi(optarg);
if (!(keySizeInBits == SR_KEY_SIZE_IN_BITS || keySizeInBits == SR2_KEY_SIZE_IN_BITS || keySizeInBits == 4096))
return 2;
break;
case '?':
default:
return 2;
}
}
argc -= optind;
argv += optind;
if (argc > 1)
return 2;
keychainName = (argc == 1)?*argv:_masterKeychainName;
if (!keychainName || *keychainName == '\0')
return -1;
if (!password && !do_prompt)
{
int compare = 1;
int tries;
for (tries = 3; tries-- > 0;)
{
char *firstpass;
password = getpass("password for new keychain: ");
if (!password)
{
result = -1;
goto loser;
}
firstpass = malloc(strlen(password) + 1);
strcpy(firstpass, password);
password = getpass("retype password for new keychain: ");
compare = password ? strcmp(password, firstpass) : 1;
memset(firstpass, 0, strlen(firstpass));
free(firstpass);
if (!password)
{
result = -1;
goto loser;
}
if (compare)
{
fprintf(stderr, "passwords don't match\n");
memset(password, 0, strlen(password));
}
else
{
zero_password = 1;
break;
}
}
if (compare)
{
result = 1;
goto loser;
}
}
do
{
result = makeMasterPassword(keychainName, password, keySizeInBits, &keychainRef);
if (keychainRef)
CFRelease(keychainRef);
if (zero_password)
memset(password, 0, strlen(password));
if (result)
goto loser;
argc--;
argv++;
keychainName = *argv;
} while (argc > 0);
loser:
return result;
}