darling-security_dotmac_tp/lib/dotMacCredRequest.cpp
2016-12-03 18:42:06 -08:00

599 lines
20 KiB
C++

/*
* Copyright (c) 2004 Apple Computer, Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
/*
* DotMacCredRequest.cpp - public session functions for Submit/Retrieve cred result
*/
#include "AppleDotMacTPSession.h"
#include "dotMacTpDebug.h"
#include "dotMacTpUtils.h"
#include "dotMacTpRpcGlue.h"
#include "dotMacTp.h"
#include <security_asn1/nssUtils.h>
#include <security_asn1/SecNssCoder.h>
#include <Security/oidsalg.h>
#include <security_cdsa_utils/cuPem.h>
#include <MacErrors.h>
void AppleDotMacTPSession::RetrieveCredResult(
const CssmData &ReferenceIdentifier,
const CSSM_TP_CALLERAUTH_CONTEXT *CallerAuthCredentials,
sint32 &EstimatedTime,
CSSM_BOOL &ConfirmationRequired,
CSSM_TP_RESULT_SET_PTR &RetrieveOutput)
{
/*
* The only input we use is the RefId, which we created in SubmitCredRequest()
* in the case of a CSSMERR_APPLE_DOTMAC_REQ_QUEUED status.
*/
if((ReferenceIdentifier.Data == NULL) || (ReferenceIdentifier.Length == 0)) {
dotMacErrorLog("RetrieveCredResult: NULL ReferenceIdentifier\n");
CssmError::throwMe(CSSMERR_TP_INVALID_IDENTIFIER);
}
if(CallerAuthCredentials != NULL) {
dotMacErrorLog("RetrieveCredResult: CallerAuthCredentials not used\n");
CssmError::throwMe(CSSMERR_TP_INVALID_CALLERAUTH_CONTEXT_POINTER);
}
/*
* Decode the RefId to get the fields we need to fetch the cert.
*/
SecNssCoder coder;
CSSM_DATA userName;
CSSM_DATA domain;
DotMacCertTypeTag certType;
OSStatus ortn = dotMacDecodeRefId(coder, ReferenceIdentifier, userName, domain, &certType);
if(ortn) {
dotMacErrorLog("RetrieveCredResult: Invalid RefID\n");
CssmError::throwMe(CSSMERR_TP_INVALID_IDENTIFIER);
}
/* Fetch the cert. */
CSSM_DATA certData = {0, NULL};
CSSM_RETURN crtn;
crtn = dotMacTpCertFetch(userName, domain, certType, *this, certData);
if(crtn) {
/* FIXME error handling here, including no data */
CssmError::throwMe(crtn);
}
ConfirmationRequired = CSSM_FALSE;
EstimatedTime = 0;
/* Return cert data as the single Results in CSSM_TP_RESULT_SET. */
if(certData.Length == 0) {
/* The spec says this is OK, it just means that no cert is available yet */
dotMacErrorLog("RetrieveCredResult: no data after successful GET\n");
CssmError::throwMe(CSSMERR_TP_CERT_NOT_VALID_YET);
}
RetrieveOutput = (CSSM_TP_RESULT_SET_PTR)malloc(sizeof(CSSM_TP_RESULT_SET));
CSSM_DATA *resultData = (CSSM_DATA *)malloc(sizeof(CSSM_DATA));
*resultData = certData;
RetrieveOutput->NumberOfResults = 1;
RetrieveOutput->Results = resultData;
}
/*
* All archive requests (store, fetch, list, remove) go through here.
* All are synchronous (i.e., no RetrieveCredResult is needed).
*/
void AppleDotMacTPSession::SubmitArchiveRequest(
DotMacArchiveType archiveType, // OID preparsed
const CSSM_DATA &hostName, // required
CSSM_TP_AUTHORITY_REQUEST_TYPE RequestType,
const CSSM_TP_REQUEST_SET &RequestInput,
const CSSM_TP_CALLERAUTH_CONTEXT *CallerAuthContext,
sint32 &EstimatedTime,
CssmData &ReferenceIdentifier)
{
CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST *archReq =
(CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST *)RequestInput.Requests;
const CSSM_DATA *pfxIn = NULL;
CSSM_DATA *pfxOut = NULL;
const CSSM_DATA *archiveName = NULL;
const CSSM_DATA *timeString = NULL;
uint32 version;
if((archReq == NULL) ||
(archReq->userName.Data == NULL) ||
(archReq->password.Data == NULL)) {
dotMacErrorLog("SubmitArchiveRequest: bad username/pwd\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
/* Version and cert type - infer certType if v. 1 */
version = archReq->version;
const CSSM_DATA *serialNumber = NULL;
DotMacArchive_v2 **archives_v2 = NULL;
DotMacArchive **archives_v1 = NULL;
DotMacCertTypeTag certTypeTag;
if(version >= CSSM_DOT_MAC_TP_ARCHIVE_REQ_VERSION_v2) {
certTypeTag = archReq->certTypeTag;
archives_v2 = &archReq->archives_v2;
serialNumber = &archReq->serialNumber;
}
else {
/* backwards compatibility */
certTypeTag = CSSM_DOT_MAC_TYPE_ICHAT;
archives_v1 = &archReq->archives;
}
/* further verification per request type */
bool badInputs = false;
switch(archiveType) {
case DMAT_List:
/* nothing further needed */
break;
case DMAT_Store:
if((archReq->archiveName.Data == NULL) ||
(archReq->timeString.Data == NULL) ||
(archReq->pfx.Data == NULL)) {
badInputs = true;
}
pfxIn = &archReq->pfx;
archiveName = &archReq->archiveName;
timeString = &archReq->timeString;
break;
case DMAT_Fetch:
pfxOut = &archReq->pfx;
/* and drop thru */
case DMAT_Remove:
if(archReq->archiveName.Data == NULL) {
badInputs = true;
}
archiveName = &archReq->archiveName;
break;
default:
assert(0);
CssmError::throwMe(internalComponentErr);
}
if(badInputs) {
dotMacErrorLog("SubmitArchiveRequest: bad per-method inputs\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
CSSM_RETURN crtn;
crtn = dotMacPostArchiveReq(version, certTypeTag,
archiveType,
archReq->userName, archReq->password, hostName, // all required
archiveName, pfxIn, timeString, serialNumber, pfxOut,
&archReq->numArchives, archives_v1, archives_v2,
*this);
if(crtn) {
CssmError::throwMe(crtn);
}
}
typedef enum {
CRO_Sign,
CRO_Archive,
CRO_Lookup
} CredReqOp;
/*
* This is the primary entry point to initiate all operations performed
* by this module.
*/
void AppleDotMacTPSession::SubmitCredRequest(
const CSSM_TP_AUTHORITY_ID *PreferredAuthority, // optional
CSSM_TP_AUTHORITY_REQUEST_TYPE RequestType,
const CSSM_TP_REQUEST_SET &RequestInput,
const CSSM_TP_CALLERAUTH_CONTEXT *CallerAuthContext,
sint32 &EstimatedTime,
CssmData &ReferenceIdentifier)
{
CSSM_DATA hostName = { strlen(DOT_MAC_SIGN_HOST_NAME), (uint8 *)DOT_MAC_SIGN_HOST_NAME };
CSSM_DATA domainName = { strlen(DOT_MAC_DOMAIN), (uint8 *)DOT_MAC_DOMAIN };
CSSM_DATA fullName = { 0, NULL };
CSSM_DATA *altHost = NULL;
CredReqOp op = CRO_Sign;
switch(RequestType) {
case CSSM_TP_AUTHORITY_REQUEST_CERTISSUE:
break;
case CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP:
op = CRO_Lookup;
hostName.Length = strlen(DOT_MAC_LOOKUP_HOST_NAME);
hostName.Data = (uint8 *)DOT_MAC_LOOKUP_HOST_NAME;
break;
default:
CssmError::throwMe(CSSMERR_TP_UNSUPPORTED_SERVICE);
}
/* default host, overridable via PreferredAuthority or username specification */
CssmAutoData fullHostName(Allocator::standard());
fullHostName.malloc(hostName.Length + 1 + domainName.Length);
fullName = fullHostName.get();
memmove(fullName.Data, hostName.Data, hostName.Length);
memmove(fullName.Data + hostName.Length, ".", 1);
memmove(fullName.Data + hostName.Length + 1, domainName.Data, domainName.Length);
/* qualify inputs */
if(PreferredAuthority) {
/* only valid option: host name */
if(PreferredAuthority->AuthorityCert != NULL) {
dotMacErrorLog("SubmitCredRequest: AuthorityCert illegal\n");
CssmError::throwMe(CSSMERR_TP_INVALID_AUTHORITY);
}
if(PreferredAuthority->AuthorityLocation == NULL) {
dotMacErrorLog("SubmitCredRequest: AuthorityLocation invalid\n");
CssmError::throwMe(CSSMERR_TP_INVALID_AUTHORITY);
}
if(PreferredAuthority->AuthorityLocation->AddressType != CSSM_ADDR_NAME) {
dotMacErrorLog("SubmitCredRequest: AddressType invalid\n");
CssmError::throwMe(CSSMERR_TP_INVALID_AUTHORITY);
}
fullName = PreferredAuthority->AuthorityLocation->Address;
if(fullName.Data == NULL) {
dotMacErrorLog("SubmitCredRequest: Address invalid\n");
CssmError::throwMe(CSSMERR_TP_INVALID_AUTHORITY);
}
/* for archive only (it has a different default) */
altHost = &fullName;
dotMacTokenizeHostName(fullName, hostName, domainName);
}
if(CallerAuthContext == NULL) {
CssmError::throwMe(CSSMERR_TP_INVALID_CALLERAUTH_CONTEXT_POINTER);
}
if((RequestInput.NumberOfRequests != 1) ||
(RequestInput.Requests == NULL)) {
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
const CSSM_TP_POLICYINFO *tpPolicy = &CallerAuthContext->Policy;
if((tpPolicy->NumberOfPolicyIds != 1) || (tpPolicy->PolicyIds == NULL)) {
CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
}
DotMacCertTypeTag certType = CSSM_DOT_MAC_TYPE_UNSPECIFIED;
DotMacArchiveType archiveType = DMAT_List;
CSSM_DATA csr = {0, NULL};
/*
* Map policy to op and (if op is CRO_Sign) cert type.
* For CRO_Archive ops, certType is in CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST.
*/
const CSSM_OID *oid = &tpPolicy->PolicyIds->FieldOid;
if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_IDENTITY)) {
certType = CSSM_DOT_MAC_TYPE_ICHAT;
}
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_EMAIL_SIGN)) {
certType = CSSM_DOT_MAC_TYPE_EMAIL_SIGNING;
}
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_EMAIL_ENCRYPT)) {
certType = CSSM_DOT_MAC_TYPE_EMAIL_ENCRYPT;
}
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_SHARED_SERVICES)) {
certType = CSSM_DOT_MAC_TYPE_SHARED_SERVICES;
}
/* Archive ops: certType is in CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST */
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_LIST)) {
op = CRO_Archive;
archiveType = DMAT_List;
}
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_STORE)) {
op = CRO_Archive;
archiveType = DMAT_Store;
}
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_FETCH)) {
op = CRO_Archive;
archiveType = DMAT_Fetch;
}
else if(nssCompareCssmData(oid, &CSSMOID_DOTMAC_CERT_REQ_ARCHIVE_REMOVE)) {
op = CRO_Archive;
archiveType = DMAT_Remove;
}
else {
CssmError::throwMe(CSSMERR_TP_INVALID_POLICY_IDENTIFIERS);
}
switch(op) {
case CRO_Archive:
{
CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST *archReq =
(CSSM_APPLE_DOTMAC_TP_ARCHIVE_REQUEST *)RequestInput.Requests;
if(!archReq || !archReq->userName.Data || !archReq->userName.Length) {
dotMacErrorLog("SubmitCredRequest(Archive): bad username\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
if (!altHost) {
CSSM_DATA archReqDomain = domainName;
dotMacTokenizeUserName(archReq->userName, archReq->userName, archReqDomain);
if (archReqDomain.Length && archReqDomain.Data) {
domainName = archReqDomain;
fullHostName.reset();
fullHostName.malloc(hostName.Length + 1 + domainName.Length);
fullName = fullHostName.get();
memmove(fullName.Data, hostName.Data, hostName.Length);
memmove(fullName.Data + hostName.Length, ".", 1);
memmove(fullName.Data + hostName.Length + 1, domainName.Data, domainName.Length);
}
}
SubmitArchiveRequest(archiveType, fullName, RequestType,
RequestInput, CallerAuthContext, EstimatedTime, ReferenceIdentifier);
return;
}
case CRO_Lookup:
{
CSSM_APPLE_DOTMAC_TP_CERT_REQUEST *certReq =
(CSSM_APPLE_DOTMAC_TP_CERT_REQUEST *)RequestInput.Requests;
if(!certReq || !certReq->userName.Data || !certReq->userName.Length) {
dotMacErrorLog("SubmitCredRequest(Lookup): bad username\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
if (!altHost) {
CSSM_DATA certReqDomain = domainName;
dotMacTokenizeUserName(certReq->userName, certReq->userName, certReqDomain);
if (certReqDomain.Length && certReqDomain.Data) {
domainName = certReqDomain;
fullHostName.reset();
fullHostName.malloc(hostName.Length + 1 + domainName.Length);
fullName = fullHostName.get();
memmove(fullName.Data, hostName.Data, hostName.Length);
memmove(fullName.Data + hostName.Length, ".", 1);
memmove(fullName.Data + hostName.Length + 1, domainName.Data, domainName.Length);
}
}
if(certReq->flags & CSSM_DOTMAC_TP_IS_REQ_PENDING) {
/*
* We're just asking the server if there is a request pending
* for this user
*/
if(certReq->password.Data == NULL) {
dotMacErrorLog("TP_IS_REQ_PENDING: no passphrase\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
CSSM_RETURN crtn = dotMacPostReqPendingPing(certType,
certReq->userName,
certReq->password,
fullName);
/* this RPC does not have a "success" return */
assert(crtn != CSSM_OK);
if(crtn) {
CssmError::throwMe(crtn);
}
}
else {
/*
* Note this is a path which apps could use to have us do a standard
* cert fetch via HTTP, without any "pending request" state, but
* currently (9/20/06) no code in the system uses this. The only
* time CertificateRequest (in libsecurity_keychain) does a
* CSSM_TP_AUTHORITY_REQUEST_CERTLOOKUP op is when it's doing
* a CSSM_DOTMAC_TP_IS_REQ_PENDING poll op, which is handled just
* above this block.
*/
CSSM_RETURN crtn = dotMacTpCertFetch(certReq->userName, domainName,
certType, *this, ReferenceIdentifier);
if(crtn) {
dotMacErrorLog("SubmitCredRequest(Lookup): error on fetch\n");
CssmError::throwMe(crtn);
}
}
return;
}
case CRO_Sign:
/* proceed to main body */
break;
}
/* op = CRO_Sign */
CSSM_APPLE_DOTMAC_TP_CERT_REQUEST *certReq =
(CSSM_APPLE_DOTMAC_TP_CERT_REQUEST *)RequestInput.Requests;
if((certReq->userName.Data == NULL) ||
(certReq->password.Data == NULL)) {
dotMacErrorLog("SubmitCredRequest: bad username/pwd\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
if(!(certReq->flags & CSSM_DOTMAC_TP_EXIST_CSR)) {
/* Generating a CSR requires even more... */
if((certReq->cspHand == 0) ||
(certReq->clHand == 0) ||
(certReq->numTypeValuePairs == 0) ||
(certReq->typeValuePairs == NULL) ||
(certReq->publicKey == NULL) ||
(certReq->privateKey == NULL)) {
dotMacErrorLog("SubmitCredRequest: bad CSR generating params\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
}
else if(certReq->csr.Data == NULL) {
/* using existing CSR */
dotMacErrorLog("SubmitCredRequest: bad incoming CSR\n");
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
/* local vars prior to goto */
SecNssCoder coder;
CSSM_X509_NAME x509Name;
const CSSM_KEY *pubKey = certReq->publicKey;
const CSSM_KEY *actPubKey = NULL;
CSSM_BOOL freeRawKey = CSSM_FALSE;
CSSM_KEY rawPubKey;
unsigned char *pemCsr = NULL;
unsigned pemCsrLen;
CSSM_RETURN crtn = CSSM_OK;
CSSM_CC_HANDLE sigHand = 0;
CSSM_DATA_PTR csrPtr = NULL;
if(certReq->flags & CSSM_DOTMAC_TP_EXIST_CSR) {
/* Skip the CSR gen */
csr = certReq->csr;
goto doPost;
}
/***
*** Create a PEM-encoded PKCS12 CSR using the CL handle provided.
***/
/* Build an X509Name for the CL */
dotMacTpbuildX509Name(coder, certReq->numTypeValuePairs, certReq->typeValuePairs,
x509Name);
/* convert possible ref key to raw; CL requires this */
switch(pubKey->KeyHeader.BlobType) {
case CSSM_KEYBLOB_RAW:
actPubKey = pubKey;
break;
case CSSM_KEYBLOB_REFERENCE:
dotMacRefKeyToRaw(certReq->cspHand, pubKey, &rawPubKey);
actPubKey = &rawPubKey;
freeRawKey = CSSM_TRUE;
break;
default:
dotMacErrorLog("SubmitCredRequest: bad key blob type (%u)",
(unsigned)pubKey->KeyHeader.BlobType);
CssmError::throwMe(CSSMERR_TP_INVALID_REQUEST_INPUTS);
}
/* subsequent errors to errOut: */
/* cook up a CL-passthrough-specific request */
CSSM_APPLE_CL_CSR_REQUEST clReq;
memset(&clReq, 0, sizeof(clReq));
clReq.subjectNameX509 = &x509Name;
clReq.signatureAlg = DOT_MAC_CSR_SIGNATURE_ALGID;
clReq.signatureOid = DOT_MAC_CSR_SIGNATURE_ALGOID;
clReq.cspHand = certReq->cspHand;
clReq.subjectPublicKey = actPubKey;
clReq.subjectPrivateKey = certReq->privateKey;
/* A crypto handle to pass to the CL */
crtn = CSSM_CSP_CreateSignatureContext(certReq->cspHand,
clReq.signatureAlg,
(CallerAuthContext ? CallerAuthContext->CallerCredentials : NULL),
certReq->privateKey,
&sigHand);
if(crtn) {
dotMacErrorLog("CSSM_CSP_CreateSignatureContext returned %ld", (long)crtn);
goto errOut;
}
/* down to the CL to do the actual work */
crtn = CSSM_CL_PassThrough(certReq->clHand,
sigHand,
CSSM_APPLEX509CL_OBTAIN_CSR,
&clReq,
(void **)&csrPtr);
if(crtn) {
dotMacErrorLog("CSSM_CL_PassThrough returned %ld", (long)crtn);
goto errOut;
}
if(csrPtr == NULL) {
dotMacErrorLog("SubmitCredRequest: CL returned NULL CSR\n");
crtn = internalComponentErr;
goto errOut;
}
/* base64 encode */
if(pemEncode(csrPtr->Data, csrPtr->Length, &pemCsr, &pemCsrLen,
"CERTIFICATE REQUEST")) {
dotMacErrorLog("***Error on PEM encode of CSR\n");
crtn = memFullErr;
goto errOut;
}
if(certReq->flags & CSSM_DOTMAC_TP_RETURN_CSR) {
/* caller wants a copy of the CSR */
certReq->csr.Data = (uint8 *)malloc(pemCsrLen);
memmove(certReq->csr.Data, pemCsr, pemCsrLen);
certReq->csr.Length = pemCsrLen;
}
csr.Data = pemCsr;
csr.Length = pemCsrLen;
doPost:
if(!(certReq->flags & CSSM_DOTMAC_TP_DO_NOT_POST)) {
/* do the net request */
CSSM_DATA resultBody;
if (!altHost) {
CSSM_DATA certReqDomain = domainName;
dotMacTokenizeUserName(certReq->userName, certReq->userName, certReqDomain);
if (certReqDomain.Length && certReqDomain.Data) {
domainName = certReqDomain;
fullHostName.reset();
fullHostName.malloc(hostName.Length + 1 + domainName.Length);
fullName = fullHostName.get();
memmove(fullName.Data, hostName.Data, hostName.Length);
memmove(fullName.Data + hostName.Length, ".", 1);
memmove(fullName.Data + hostName.Length + 1, domainName.Data, domainName.Length);
}
}
crtn = dotMacPostCertReq(certType,
certReq->userName,
certReq->password,
fullName,
certReq->flags & CSSM_DOTMAC_TP_SIGN_RENEW ? true : false,
csr,
coder,
EstimatedTime,
resultBody);
switch(crtn) {
/* Some cases return data to caller */
case noErr: /* resultBody = PEM-encoded cert */
case CSSMERR_APPLE_DOTMAC_REQ_REDIRECT: /* resultBody = URL */
case CSSMERR_APPLE_DOTMAC_REQ_QUEUED: /* resultBody = opaque data we'll use later */
if(resultBody.Data != NULL) {
ReferenceIdentifier.Data = (uint8 *)malloc(resultBody.Length);
ReferenceIdentifier.Length = resultBody.Length;
memmove(ReferenceIdentifier.Data, resultBody.Data, resultBody.Length);
/* skip trailing NULL - it's just ASCII data */
if(resultBody.Data[resultBody.Length-1] == '\0') {
ReferenceIdentifier.Length--;
}
}
else {
dotMacErrorLog("***SubmitCredReq: expected RefId.Data, got none\n");
}
break;
default:
break;
}
}
errOut:
/* free local resources */
if(sigHand) {
CSSM_DeleteContext(sigHand);
}
if(freeRawKey) {
CSSM_FreeKey(certReq->cspHand, NULL, &rawPubKey, CSSM_FALSE);
}
if(pemCsr) {
free(pemCsr);
}
if(crtn) {
CssmError::throwMe(crtn);
}
}