CertificateRequest.cpp [plain text]
#include <security_keychain/CertificateRequest.h>
#include <Security/oidsalg.h>
#include <Security/SecKey.h>
#include <Security/SecKeyPriv.h>
#include <Security/cssmapi.h>
#include <string.h>
#include <dotMacTp.h>
#include <Security/oidsattr.h>
#include <security_utilities/simpleprefs.h>
#include <SecBase.h>
#define DOT_MAC_REQ_PREFS "com.apple.security.certreq"
#define DOT_MAC_REF_ID_KEY "refId"
#define DOT_MAC_CERT_KEY "certificate"
#define DOT_MAC_DOMAIN_KEY "domain"
#define DOT_MAC_DOMAIN "mac.com"
#define DOT_MAC_MGMT_HOST "certmgmt"
#define DOT_MAC_INFO_HOST "certinfo"
static
bool nssCompareCssmData(
const CSSM_DATA *data1,
const CSSM_DATA *data2)
{
if((data1 == NULL) || (data1->Data == NULL) ||
(data2 == NULL) || (data2->Data == NULL) ||
(data1->Length != data2->Length)) {
return false;
}
if(data1->Length != data2->Length) {
return false;
}
if(memcmp(data1->Data, data2->Data, data1->Length) == 0) {
return true;
}
else {
return false;
}
}
static bool attrBoolValue(
const SecCertificateRequestAttribute *attr)
{
if((attr->value.Data != NULL) &&
(attr->value.Length != 0) &&
(attr->value.Data[0] != 0)) {
return true;
}
else {
return false;
}
}
static void tokenizeName(
const CSSM_DATA *inName,
CSSM_DATA *outName,
CSSM_DATA *outDomain)
{
if (!inName || !outName) return;
CSSM_SIZE idx = 0;
CSSM_SIZE stopIdx = inName->Length;
uint8 *p = inName->Data;
*outName = *inName;
if (outDomain) {
outDomain->Length = idx;
outDomain->Data = p;
}
if (!p) return;
while (idx < stopIdx) {
if (*p++ == '@') {
outName->Length = idx;
if (outDomain) {
outDomain->Length = inName->Length - (idx + 1);
outDomain->Data = p;
}
break;
}
idx++;
}
}
using namespace KeychainCore;
CertificateRequest::CertificateRequest(const CSSM_OID &policy,
CSSM_CERT_TYPE certificateType,
CSSM_TP_AUTHORITY_REQUEST_TYPE requestType,
SecKeyRef privateKeyItemRef,
SecKeyRef publicKeyItemRef,
const SecCertificateRequestAttributeList *attributeList,
bool isNew )
: mAlloc(Allocator::standard()),
mTP(gGuidAppleDotMacTP),
mCL(gGuidAppleX509CL),
mPolicy(mAlloc, policy.Data, policy.Length),
mCertType(certificateType),
mReqType(requestType),
mPrivKey(NULL),
mPubKey(NULL),
mEstTime(0),
mRefId(mAlloc),
mCertState(isNew ? CRS_New : CRS_Reconstructed),
mCertData(mAlloc),
mUserName(mAlloc),
mPassword(mAlloc),
mHostName(mAlloc),
mDomain(mAlloc),
mDoRenew(false),
mIsAsync(false),
mMutex(Mutex::recursive)
{
StLock<Mutex>_(mMutex);
certReqDbg("CertificateRequest construct");
if(!(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy))) {
certReqDbg("CertificateRequest(): unknown policy oid");
MacOSError::throwMe(errSecParam);
}
if(privateKeyItemRef) {
mPrivKey = privateKeyItemRef;
CFRetain(mPrivKey);
}
if(publicKeyItemRef) {
mPubKey = publicKeyItemRef;
CFRetain(mPubKey);
}
if(attributeList == NULL) {
return;
}
bool doPendingRequest = false;
for(unsigned dex=0; dex<attributeList->count; dex++) {
const SecCertificateRequestAttribute *attr = &attributeList->attr[dex];
if((attr->oid.Data == NULL) || (attr->value.Data == NULL)) {
MacOSError::throwMe(errSecParam);
}
if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_USERNAME, &attr->oid)) {
CSSM_DATA userName = { 0, NULL };
CSSM_DATA domainName = { 0, NULL };
tokenizeName(&attr->value, &userName, &domainName);
if (!domainName.Length || !domainName.Data) {
domainName.Length = strlen(DOT_MAC_DOMAIN);
domainName.Data = (uint8*) DOT_MAC_DOMAIN;
}
mUserName.copy(userName);
mDomain.copy(domainName);
}
else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_PASSWORD, &attr->oid)) {
mPassword.copy(attr->value);
}
else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_HOSTNAME, &attr->oid)) {
mHostName.copy(attr->value);
}
else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_RENEW, &attr->oid)) {
mDoRenew = attrBoolValue(attr);
}
else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_ASYNC, &attr->oid)) {
mIsAsync = attrBoolValue(attr);
}
else if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_VALUE_IS_PENDING, &attr->oid)) {
doPendingRequest = attrBoolValue(attr);
}
else {
certReqDbg("CertificateRequest(): unknown name/value oid");
MacOSError::throwMe(errSecParam);
}
}
if(mCertState == CRS_Reconstructed) {
retrieveResults();
if(mCertData.data() != NULL) {
mCertState = CRS_HaveCert;
}
else if(mRefId.data() != NULL) {
mCertState = CRS_HaveRefId;
}
else if(doPendingRequest) {
postPendingRequest();
}
else {
certReqDbg("CertificateRequest(): nothing in prefs");
MacOSError::throwMe(errSecItemNotFound);
}
}
}
CertificateRequest::~CertificateRequest() throw()
{
StLock<Mutex>_(mMutex);
certReqDbg("CertificateRequest destruct");
if(mPrivKey) {
CFRelease(mPrivKey);
}
if(mPubKey) {
CFRelease(mPubKey);
}
}
#pragma mark ----- cert request submit -----
void CertificateRequest::submit(
sint32 *estimatedTime)
{
StLock<Mutex>_(mMutex);
CSSM_DATA &policy = mPolicy.get();
if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) {
return submitDotMac(estimatedTime);
}
else {
assert(0);
certReqDbg("CertificateRequest::submit(): bad policy");
MacOSError::throwMe(errSecParam);
}
}
void CertificateRequest::submitDotMac(
sint32 *estimatedTime)
{
StLock<Mutex>_(mMutex);
CSSM_RETURN crtn;
CSSM_TP_AUTHORITY_ID tpAuthority;
CSSM_TP_AUTHORITY_ID *tpAuthPtr = NULL;
CSSM_NET_ADDRESS tpNetAddrs;
CSSM_APPLE_DOTMAC_TP_CERT_REQUEST certReq;
CSSM_TP_REQUEST_SET reqSet;
CSSM_CSP_HANDLE cspHand = 0;
CSSM_X509_TYPE_VALUE_PAIR tvp;
CSSM_TP_CALLERAUTH_CONTEXT callerAuth;
CSSM_FIELD policyField;
CSSM_DATA refId = {0, NULL};
const CSSM_KEY *privKey;
const CSSM_KEY *pubKey;
OSStatus ortn;
if(mCertState != CRS_New) {
certReqDbg("CertificateRequest: can only submit a new request");
MacOSError::throwMe(errSecParam);
}
if((mUserName.data() == NULL) || (mPassword.data() == NULL)) {
certReqDbg("CertificateRequest: user name and password required");
MacOSError::throwMe(errSecParam);
}
if((mPrivKey == NULL) || (mPubKey == NULL)) {
certReqDbg("CertificateRequest: pub and priv keys required");
MacOSError::throwMe(errSecParam);
}
ortn = SecKeyGetCSSMKey(mPrivKey, &privKey);
if(ortn) {
MacOSError::throwMe(ortn);
}
ortn = SecKeyGetCSSMKey(mPubKey, &pubKey);
if(ortn) {
MacOSError::throwMe(ortn);
}
ortn = SecKeyGetCSPHandle(mPrivKey, &cspHand);
if(ortn) {
MacOSError::throwMe(ortn);
}
tvp.type = CSSMOID_CommonName;
tvp.valueType = BER_TAG_PKIX_UTF8_STRING;
CssmAutoData fullUserName(mAlloc);
size_t nameLen = mUserName.length();
size_t domainLen = mDomain.length();
fullUserName.malloc(nameLen + 1 + domainLen);
tvp.value = fullUserName.get();
memmove(tvp.value.Data, mUserName.data(), nameLen);
memmove(tvp.value.Data + nameLen, "@", 1);
memmove(tvp.value.Data + nameLen + 1, mDomain.data(), domainLen);
memset(&certReq, 0, sizeof(certReq));
certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION;
certReq.cspHand = cspHand;
certReq.clHand = mCL->handle();
certReq.numTypeValuePairs = 1;
certReq.typeValuePairs = &tvp;
certReq.publicKey = const_cast<CSSM_KEY_PTR>(pubKey);
certReq.privateKey = const_cast<CSSM_KEY_PTR>(privKey);
certReq.userName = mUserName.get();
certReq.password = mPassword.get();
if(mDoRenew) {
certReq.flags |= CSSM_DOTMAC_TP_SIGN_RENEW;
}
reqSet.Requests = &certReq;
reqSet.NumberOfRequests = 1;
policyField.FieldOid = mPolicy;
policyField.FieldValue.Data = NULL;
policyField.FieldValue.Length = 0;
memset(&callerAuth, 0, sizeof(callerAuth));
callerAuth.Policy.NumberOfPolicyIds = 1;
callerAuth.Policy.PolicyIds = &policyField;
ortn = SecKeyGetCredentials(mPrivKey,
CSSM_ACL_AUTHORIZATION_SIGN,
kSecCredentialTypeDefault,
const_cast<const CSSM_ACCESS_CREDENTIALS **>(&callerAuth.CallerCredentials));
if(ortn) {
certReqDbg("CertificateRequest: SecKeyGetCredentials error");
MacOSError::throwMe(ortn);
}
CssmAutoData hostName(mAlloc);
tpAuthority.AuthorityCert = NULL;
tpAuthority.AuthorityLocation = &tpNetAddrs;
tpNetAddrs.AddressType = CSSM_ADDR_NAME;
if(mHostName.data() != NULL) {
tpNetAddrs.Address = mHostName.get();
} else {
unsigned hostLen = strlen(DOT_MAC_MGMT_HOST);
hostName.malloc(hostLen + 1 + domainLen);
tpNetAddrs.Address = hostName.get();
memmove(tpNetAddrs.Address.Data, DOT_MAC_MGMT_HOST, hostLen);
memmove(tpNetAddrs.Address.Data + hostLen, ".", 1);
memmove(tpNetAddrs.Address.Data + hostLen + 1, mDomain.data(), domainLen);
}
tpAuthPtr = &tpAuthority;
crtn = CSSM_TP_SubmitCredRequest(mTP->handle(),
tpAuthPtr,
CSSM_TP_AUTHORITY_REQUEST_CERTISSUE,
&reqSet,
&callerAuth,
&mEstTime,
&refId);
switch(crtn) {
case CSSM_OK:
certReqDbg("submitDotMac: full success, storing cert");
if(!mIsAsync) {
ortn = storeResults(NULL, &refId);
if(ortn) {
crtn = ortn;
}
}
mCertData.copy(refId);
mCertState = CRS_HaveCert;
if(estimatedTime) {
*estimatedTime = 0;
}
break;
case CSSMERR_APPLE_DOTMAC_REQ_QUEUED:
certReqDbg("submitDotMac: queued, storing refId");
mRefId.copy(refId);
crtn = CSSM_OK;
if(!mIsAsync) {
ortn = storeResults(&refId, NULL);
if(ortn) {
crtn = ortn;
}
}
mCertState = CRS_HaveRefId;
if(estimatedTime) {
*estimatedTime = mEstTime;
}
break;
case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT:
certReqDbg("submitDotMac: redirect");
mRefId.copy(refId);
mCertState = CRS_HaveOtherData;
break;
default:
break;
}
if(refId.Data) {
free(refId.Data);
}
if(crtn) {
CssmError::throwMe(crtn);
}
}
#pragma mark ----- cert request get result -----
void CertificateRequest::getResult(
sint32 *estimatedTime, CssmData &certData)
{
StLock<Mutex>_(mMutex);
CSSM_DATA &policy = mPolicy.get();
if(nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_IDENTITY, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT, &policy) ||
nssCompareCssmData(&CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES, &policy)) {
return getResultDotMac(estimatedTime, certData);
}
else {
assert(0);
certReqDbg("CertificateRequest::getResult(): bad policy");
MacOSError::throwMe(errSecParam);
}
}
void CertificateRequest::getResultDotMac(
sint32 *estimatedTime, CssmData &certData)
{
StLock<Mutex>_(mMutex);
switch(mCertState) {
case CRS_HaveCert:
certReqDbg("getResultDotMac: have the cert right now");
assert(mCertData.data() != NULL);
certData = mCertData.get();
if(estimatedTime) {
*estimatedTime = 0;
}
break;
case CRS_HaveRefId:
{
certReqDbg("getResultDotMac: CRS_HaveRefId; polling server");
assert(mRefId.data() != NULL);
CSSM_BOOL ConfirmationRequired;
CSSM_TP_RESULT_SET_PTR resultSet = NULL;
CSSM_RETURN crtn;
crtn = CSSM_TP_RetrieveCredResult(mTP->handle(),
&mRefId.get(),
NULL, &mEstTime,
&ConfirmationRequired,
&resultSet);
switch(crtn) {
case CSSM_OK:
break;
case CSSMERR_TP_CERT_NOT_VALID_YET:
certReqDbg("getResultDotMac: polled server, not ready yet");
if(estimatedTime) {
*estimatedTime = (mEstTime) ? mEstTime : 1;
}
MacOSError::throwMe(CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING);
default:
certReqDbg("CSSM_TP_RetrieveCredResult error");
CssmError::throwMe(crtn);
}
if(resultSet == NULL) {
certReqDbg("***CSSM_TP_RetrieveCredResult OK, but no result set");
MacOSError::throwMe(errSecInternalComponent);
}
if(resultSet->NumberOfResults != 1) {
certReqDbg("***CSSM_TP_RetrieveCredResult OK, NumberOfResults (%lu)",
(unsigned long)resultSet->NumberOfResults);
MacOSError::throwMe(errSecInternalComponent);
}
if(resultSet->Results == NULL) {
certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result set");
MacOSError::throwMe(errSecInternalComponent);
}
certReqDbg("getResultDotMac: polled server, SUCCESS");
CSSM_DATA_PTR result = (CSSM_DATA_PTR)resultSet->Results;
if(result->Data == NULL) {
certReqDbg("***CSSM_TP_RetrieveCredResult OK, but empty result");
MacOSError::throwMe(errSecInternalComponent);
}
mCertData.copy(*result);
certData = mCertData.get();
mCertState = CRS_HaveCert;
if(estimatedTime) {
*estimatedTime = 0;
}
free(result->Data);
free(result);
free(resultSet);
break;
}
default:
certReqDbg("CertificateRequest::getResultDotMac(): bad state");
MacOSError::throwMe(errSecInternalComponent);
}
assert(mCertData.data() != NULL);
assert(mCertData.data() == certData.Data);
removeResults();
}
void CertificateRequest::getReturnData(
CssmData &rtnData)
{
StLock<Mutex>_(mMutex);
rtnData = mRefId.get();
}
#pragma mark ----- preferences support -----
CFStringRef CertificateRequest::createUserKey()
{
StLock<Mutex>_(mMutex);
return CFStringCreateWithBytes(NULL, (UInt8 *)mUserName.data(), mUserName.length(),
kCFStringEncodingUTF8, false);
}
#define MAX_OID_LEN 2048 // way big... */
CFStringRef CertificateRequest::createPolicyKey()
{
StLock<Mutex>_(mMutex);
char oidstr[MAX_OID_LEN];
unsigned char *inp = (unsigned char *)mPolicy.data();
char *outp = oidstr;
CFIndex len = mPolicy.length();
for(CFIndex dex=0; dex<len; dex++) {
sprintf(outp, "%02X ", *inp++);
outp += 3;
}
return CFStringCreateWithBytes(NULL, (UInt8 *)oidstr, len * 3,
kCFStringEncodingUTF8, false);
}
OSStatus CertificateRequest::storeResults(
const CSSM_DATA *refId, const CSSM_DATA *certData) {
StLock<Mutex>_(mMutex);
assert(mPolicy.data() != NULL);
assert(mUserName.data() != NULL);
assert(mDomain.data() != NULL);
bool deleteEntry = ((refId == NULL) && (certData == NULL));
MutableDictionary *prefsDict = MutableDictionary::CreateMutableDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User);
if (prefsDict == NULL)
{
prefsDict = new MutableDictionary();
}
CFStringRef policyKey = createPolicyKey();
MutableDictionary *policyDict = prefsDict->copyMutableDictValue(policyKey);
CFStringRef userKey = createUserKey();
if(deleteEntry) {
policyDict->removeValue(userKey);
}
else {
MutableDictionary *userDict = policyDict->copyMutableDictValue(userKey);
CFStringRef domainKey = CFStringCreateWithBytes(NULL, (UInt8 *)mDomain.data(), mDomain.length(), kCFStringEncodingUTF8, false);
userDict->setValue(CFSTR(DOT_MAC_DOMAIN_KEY), domainKey);
CFRelease(domainKey);
if(refId) {
userDict->setDataValue(CFSTR(DOT_MAC_REF_ID_KEY), refId->Data, refId->Length);
}
if(certData) {
userDict->setDataValue(CFSTR(DOT_MAC_CERT_KEY), certData->Data, certData->Length);
}
policyDict->setValue(userKey, userDict->dict());
delete userDict;
}
CFRelease(userKey);
if(policyDict->count() == 0) {
prefsDict->removeValue(policyKey);
}
else {
prefsDict->setValue(policyKey, policyDict->dict());
}
CFRelease(policyKey);
delete policyDict;
OSStatus ortn = errSecSuccess;
if(!prefsDict->writePlistToPrefs(DOT_MAC_REQ_PREFS, Dictionary::US_User)) {
certReqDbg("storeResults: error writing prefs to disk");
ortn = errSecIO;
}
delete prefsDict;
return ortn;
}
void CertificateRequest::retrieveResults()
{
StLock<Mutex>_(mMutex);
assert(mPolicy.data() != NULL);
assert(mUserName.data() != NULL);
Dictionary *pd = Dictionary::CreateDictionary(DOT_MAC_REQ_PREFS, Dictionary::US_User);
if (pd == NULL)
{
certReqDbg("retrieveResults: no prefs found");
return;
}
auto_ptr<Dictionary> prefsDict(pd);
CFStringRef policyKey = createPolicyKey();
Dictionary *policyDict = prefsDict->copyDictValue(policyKey);
CFRelease(policyKey);
if(policyDict != NULL) {
CFStringRef userKey = createUserKey();
Dictionary *userDict = policyDict->copyDictValue(userKey);
if(userDict != NULL) {
CFDataRef val = userDict->getDataValue(CFSTR(DOT_MAC_CERT_KEY));
if(val) {
mCertData.copy(CFDataGetBytePtr(val), CFDataGetLength(val));
}
val = userDict->getDataValue(CFSTR(DOT_MAC_REF_ID_KEY));
if(val) {
mRefId.copy(CFDataGetBytePtr(val), CFDataGetLength(val));
}
delete userDict;
}
CFRelease(userKey);
delete policyDict;
}
}
void CertificateRequest::removeResults()
{
StLock<Mutex>_(mMutex);
assert(mPolicy.data() != NULL);
assert(mUserName.data() != NULL);
storeResults(NULL, NULL);
}
void CertificateRequest::postPendingRequest()
{
StLock<Mutex>_(mMutex);
CSSM_RETURN crtn;
CSSM_TP_AUTHORITY_ID tpAuthority;
CSSM_TP_AUTHORITY_ID *tpAuthPtr = NULL;
CSSM_NET_ADDRESS tpNetAddrs;
CSSM_APPLE_DOTMAC_TP_CERT_REQUEST certReq;
CSSM_TP_REQUEST_SET reqSet;
CSSM_TP_CALLERAUTH_CONTEXT callerAuth;
CSSM_FIELD policyField;
CSSM_DATA refId = {0, NULL};
assert(mCertState == CRS_Reconstructed);
if((mUserName.data() == NULL) || (mPassword.data() == NULL)) {
certReqDbg("postPendingRequest: user name and password required");
MacOSError::throwMe(errSecParam);
}
memset(&certReq, 0, sizeof(certReq));
certReq.version = CSSM_DOT_MAC_TP_REQ_VERSION;
certReq.userName = mUserName.get();
certReq.password = mPassword.get();
certReq.flags = CSSM_DOTMAC_TP_IS_REQ_PENDING;
reqSet.Requests = &certReq;
reqSet.NumberOfRequests = 1;
policyField.FieldOid = mPolicy;
policyField.FieldValue.Data = NULL;
policyField.FieldValue.Length = 0;
memset(&callerAuth, 0, sizeof(callerAuth));
callerAuth.Policy.NumberOfPolicyIds = 1;
callerAuth.Policy.PolicyIds = &policyField;
if(mHostName.data() != NULL) {
tpAuthority.AuthorityCert = NULL;
tpAuthority.AuthorityLocation = &tpNetAddrs;
tpNetAddrs.AddressType = CSSM_ADDR_NAME;
tpNetAddrs.Address = mHostName.get();
tpAuthPtr = &tpAuthority;
}
crtn = CSSM_TP_SubmitCredRequest(mTP->handle(),
tpAuthPtr,
CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP,
&reqSet,
&callerAuth,
&mEstTime,
&refId);
if(refId.Data) {
free(refId.Data);
}
switch(crtn) {
case CSSMERR_APPLE_DOTMAC_REQ_IS_PENDING:
certReqDbg("postPendingRequest: REQ_IS_PENDING");
break;
case CSSMERR_APPLE_DOTMAC_NO_REQ_PENDING:
certReqDbg("postPendingRequest: NO_REQ_PENDING");
break;
case CSSM_OK:
certReqDbg("postPendingRequest: unexpected success!");
crtn = errSecInternalComponent;
break;
default:
certReqDbg("postPendingRequest: unexpected rtn %lu", (unsigned long)crtn);
break;
}
CssmError::throwMe(crtn);
}