diff options
Diffstat (limited to 'security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_aiamgr.c')
-rw-r--r-- | security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_aiamgr.c | 699 |
1 files changed, 699 insertions, 0 deletions
diff --git a/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_aiamgr.c b/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_aiamgr.c new file mode 100644 index 000000000..d9f566230 --- /dev/null +++ b/security/nss/lib/libpkix/pkix_pl_nss/module/pkix_pl_aiamgr.c @@ -0,0 +1,699 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* + * pkix_pl_aiamgr.c + * + * AIAMgr Object Definitions + * + */ + +#include "pkix_pl_aiamgr.h" +extern PKIX_PL_HashTable *aiaConnectionCache; + +#ifndef NSS_PKIX_NO_LDAP +/* --Virtual-LdapClient-Functions------------------------------------ */ + +PKIX_Error * +PKIX_PL_LdapClient_InitiateRequest( + PKIX_PL_LdapClient *client, + LDAPRequestParams *requestParams, + void **pNBIO, + PKIX_List **pResponse, + void *plContext) +{ + PKIX_ENTER(LDAPCLIENT, "PKIX_PL_LdapClient_InitiateRequest"); + PKIX_NULLCHECK_TWO(client, client->initiateFcn); + + PKIX_CHECK(client->initiateFcn + (client, requestParams, pNBIO, pResponse, plContext), + PKIX_LDAPCLIENTINITIATEREQUESTFAILED); +cleanup: + + PKIX_RETURN(LDAPCLIENT); + +} + +PKIX_Error * +PKIX_PL_LdapClient_ResumeRequest( + PKIX_PL_LdapClient *client, + void **pNBIO, + PKIX_List **pResponse, + void *plContext) +{ + PKIX_ENTER(LDAPCLIENT, "PKIX_PL_LdapClient_ResumeRequest"); + PKIX_NULLCHECK_TWO(client, client->resumeFcn); + + PKIX_CHECK(client->resumeFcn + (client, pNBIO, pResponse, plContext), + PKIX_LDAPCLIENTRESUMEREQUESTFAILED); +cleanup: + + PKIX_RETURN(LDAPCLIENT); + +} +#endif /* !NSS_PKIX_NO_LDAP */ + +/* --Private-AIAMgr-Functions----------------------------------*/ + +/* + * FUNCTION: pkix_pl_AIAMgr_Destroy + * (see comments for PKIX_PL_DestructorCallback in pkix_pl_pki.h) + */ +static PKIX_Error * +pkix_pl_AIAMgr_Destroy( + PKIX_PL_Object *object, + void *plContext) +{ + PKIX_PL_AIAMgr *aiaMgr = NULL; + + PKIX_ENTER(AIAMGR, "pkix_pl_AIAMgr_Destroy"); + PKIX_NULLCHECK_ONE(object); + + PKIX_CHECK(pkix_CheckType(object, PKIX_AIAMGR_TYPE, plContext), + PKIX_OBJECTNOTAIAMGR); + + aiaMgr = (PKIX_PL_AIAMgr *)object; + + /* pointer to cert cache */ + /* pointer to crl cache */ + aiaMgr->method = 0; + aiaMgr->aiaIndex = 0; + aiaMgr->numAias = 0; + PKIX_DECREF(aiaMgr->aia); + PKIX_DECREF(aiaMgr->location); + PKIX_DECREF(aiaMgr->results); +#ifndef NSS_PKIX_NO_LDAP + PKIX_DECREF(aiaMgr->client.ldapClient); +#endif + +cleanup: + + PKIX_RETURN(AIAMGR); +} + +/* + * FUNCTION: pkix_pl_AIAMgr_RegisterSelf + * DESCRIPTION: + * Registers PKIX_AIAMGR_TYPE and its related functions with systemClasses[] + * THREAD SAFETY: + * Not Thread Safe - for performance and complexity reasons + * + * Since this function is only called by PKIX_PL_Initialize, which should + * only be called once, it is acceptable that this function is not + * thread-safe. + */ +PKIX_Error * +pkix_pl_AIAMgr_RegisterSelf(void *plContext) +{ + extern pkix_ClassTable_Entry systemClasses[PKIX_NUMTYPES]; + pkix_ClassTable_Entry *entry = &systemClasses[PKIX_AIAMGR_TYPE]; + + PKIX_ENTER(AIAMGR, "pkix_pl_AIAMgr_RegisterSelf"); + + entry->description = "AIAMgr"; + entry->typeObjectSize = sizeof(PKIX_PL_AIAMgr); + entry->destructor = pkix_pl_AIAMgr_Destroy; + + PKIX_RETURN(AIAMGR); +} + +#ifndef NSS_PKIX_NO_LDAP +/* + * FUNCTION: pkix_pl_AiaMgr_FindLDAPClient + * DESCRIPTION: + * + * This function checks the collection of LDAPClient connections held by the + * AIAMgr pointed to by "aiaMgr" for one matching the domain name given by + * "domainName". The string may include a port number: e.g., "betty.nist.gov" + * or "nss.red.iplanet.com:1389". If a match is found, that LDAPClient is + * stored at "pClient". Otherwise, an LDAPClient is created and added to the + * collection, and then stored at "pClient". + * + * PARAMETERS: + * "aiaMgr" + * The AIAMgr whose LDAPClient connected are to be managed. Must be + * non-NULL. + * "domainName" + * Address of a string pointing to a server name. Must be non-NULL. + * An empty string (which means no <host> is given in the LDAP URL) is + * not supported. + * "pClient" + * Address at which the returned LDAPClient is stored. Must be non-NULL. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns an AIAMgr Error if the function fails in a non-fatal way + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +static PKIX_Error * +pkix_pl_AiaMgr_FindLDAPClient( + PKIX_PL_AIAMgr *aiaMgr, + char *domainName, + PKIX_PL_LdapClient **pClient, + void *plContext) +{ + PKIX_PL_String *domainString = NULL; + PKIX_PL_LdapDefaultClient *client = NULL; + + PKIX_ENTER(AIAMGR, "pkix_pl_AiaMgr_FindLDAPClient"); + PKIX_NULLCHECK_THREE(aiaMgr, domainName, pClient); + + /* + * An LDAP URL may not have a <host> part, for example, + * ldap:///o=University%20of%20Michigan,c=US + * PKIX_PL_LdapDefaultClient doesn't know how to discover the default + * LDAP server, so we don't support this kind of LDAP URL. + */ + if (*domainName == '\0') { + /* Simulate a PKIX_PL_LdapDefaultClient_CreateByName failure. */ + PKIX_ERROR(PKIX_LDAPDEFAULTCLIENTCREATEBYNAMEFAILED); + } + + /* create PKIX_PL_String from domain name */ + PKIX_CHECK(PKIX_PL_String_Create + (PKIX_ESCASCII, domainName, 0, &domainString, plContext), + PKIX_STRINGCREATEFAILED); + + /* Is this domainName already in cache? */ + PKIX_CHECK(PKIX_PL_HashTable_Lookup + (aiaConnectionCache, + (PKIX_PL_Object *)domainString, + (PKIX_PL_Object **)&client, + plContext), + PKIX_HASHTABLELOOKUPFAILED); + + if (client == NULL) { + + /* No, create a connection (and cache it) */ + PKIX_CHECK(PKIX_PL_LdapDefaultClient_CreateByName + (domainName, + /* Do not use NBIO until we verify, that + * it is working. For now use 1 min timeout. */ + PR_SecondsToInterval( + ((PKIX_PL_NssContext*)plContext)->timeoutSeconds), + NULL, + &client, + plContext), + PKIX_LDAPDEFAULTCLIENTCREATEBYNAMEFAILED); + + PKIX_CHECK(PKIX_PL_HashTable_Add + (aiaConnectionCache, + (PKIX_PL_Object *)domainString, + (PKIX_PL_Object *)client, + plContext), + PKIX_HASHTABLEADDFAILED); + + } + + *pClient = (PKIX_PL_LdapClient *)client; + +cleanup: + + PKIX_DECREF(domainString); + + PKIX_RETURN(AIAMGR); +} +#endif /* !NSS_PKIX_NO_LDAP */ + +PKIX_Error * +pkix_pl_AIAMgr_GetHTTPCerts( + PKIX_PL_AIAMgr *aiaMgr, + PKIX_PL_InfoAccess *ia, + void **pNBIOContext, + PKIX_List **pCerts, + void *plContext) +{ + PKIX_PL_GeneralName *location = NULL; + PKIX_PL_String *locationString = NULL; + PKIX_UInt32 len = 0; + PRUint16 port = 0; + const SEC_HttpClientFcn *httpClient = NULL; + const SEC_HttpClientFcnV1 *hcv1 = NULL; + SECStatus rv = SECFailure; + SEC_HTTP_SERVER_SESSION serverSession = NULL; + SEC_HTTP_REQUEST_SESSION requestSession = NULL; + char *path = NULL; + char *hostname = NULL; + char *locationAscii = NULL; + void *nbio = NULL; + PRUint16 responseCode = 0; + const char *responseContentType = NULL; + const char *responseData = NULL; + + PKIX_ENTER(AIAMGR, "pkix_pl_AIAMgr_GetHTTPCerts"); + PKIX_NULLCHECK_FOUR(aiaMgr, ia, pNBIOContext, pCerts); + + nbio = *pNBIOContext; + *pNBIOContext = NULL; + *pCerts = NULL; + + if (nbio == NULL) { /* a new request */ + + PKIX_CHECK(PKIX_PL_InfoAccess_GetLocation + (ia, &location, plContext), + PKIX_INFOACCESSGETLOCATIONFAILED); + + /* find or create httpClient = default client */ + httpClient = SEC_GetRegisteredHttpClient(); + aiaMgr->client.hdata.httpClient = httpClient; + if (!httpClient) + PKIX_ERROR(PKIX_OUTOFMEMORY); + + if (httpClient->version == 1) { + + PKIX_UInt32 timeout = + ((PKIX_PL_NssContext*)plContext)->timeoutSeconds; + + hcv1 = &(httpClient->fcnTable.ftable1); + + /* create server session */ + PKIX_TOSTRING(location, &locationString, plContext, + PKIX_GENERALNAMETOSTRINGFAILED); + + PKIX_CHECK(PKIX_PL_String_GetEncoded + (locationString, + PKIX_ESCASCII, + (void **)&locationAscii, + &len, + plContext), + PKIX_STRINGGETENCODEDFAILED); + + rv = CERT_ParseURL(locationAscii, &hostname, &port, + &path); + if ((rv != SECSuccess) || + (hostname == NULL) || + (path == NULL)) { + PKIX_ERROR(PKIX_URLPARSINGFAILED); + } + + rv = (*hcv1->createSessionFcn)(hostname, port, + &serverSession); + if (rv != SECSuccess) { + PKIX_ERROR(PKIX_HTTPCLIENTCREATESESSIONFAILED); + } + + aiaMgr->client.hdata.serverSession = serverSession; + + /* create request session */ + rv = (*hcv1->createFcn)(serverSession, "http", path, + "GET", PR_SecondsToInterval(timeout), + &requestSession); + if (rv != SECSuccess) { + PKIX_ERROR(PKIX_HTTPSERVERERROR); + } + + aiaMgr->client.hdata.requestSession = requestSession; + } else { + PKIX_ERROR(PKIX_UNSUPPORTEDVERSIONOFHTTPCLIENT); + } + } + + httpClient = aiaMgr->client.hdata.httpClient; + + if (httpClient->version == 1) { + PRUint32 responseDataLen = + ((PKIX_PL_NssContext*)plContext)->maxResponseLength; + + hcv1 = &(httpClient->fcnTable.ftable1); + requestSession = aiaMgr->client.hdata.requestSession; + + /* trySendAndReceive */ + rv = (*hcv1->trySendAndReceiveFcn)(requestSession, + (PRPollDesc **)&nbio, + &responseCode, + (const char **)&responseContentType, + NULL, /* &responseHeaders */ + (const char **)&responseData, + &responseDataLen); + + if (rv != SECSuccess) { + PKIX_ERROR(PKIX_HTTPSERVERERROR); + } + + if (nbio != 0) { + *pNBIOContext = nbio; + goto cleanup; + } + + PKIX_CHECK(pkix_pl_HttpCertStore_ProcessCertResponse + (responseCode, + responseContentType, + responseData, + responseDataLen, + pCerts, + plContext), + PKIX_HTTPCERTSTOREPROCESSCERTRESPONSEFAILED); + + /* Session and request cleanup in case of success */ + if (aiaMgr->client.hdata.requestSession != NULL) { + (*hcv1->freeFcn)(aiaMgr->client.hdata.requestSession); + aiaMgr->client.hdata.requestSession = NULL; + } + if (aiaMgr->client.hdata.serverSession != NULL) { + (*hcv1->freeSessionFcn)(aiaMgr->client.hdata.serverSession); + aiaMgr->client.hdata.serverSession = NULL; + } + aiaMgr->client.hdata.httpClient = 0; /* callback fn */ + + } else { + PKIX_ERROR(PKIX_UNSUPPORTEDVERSIONOFHTTPCLIENT); + } + +cleanup: + /* Session and request cleanup in case of error. Passing through without cleanup + * if interrupted by blocked IO. */ + if (PKIX_ERROR_RECEIVED) { + if (aiaMgr->client.hdata.requestSession != NULL) { + (*hcv1->freeFcn)(aiaMgr->client.hdata.requestSession); + aiaMgr->client.hdata.requestSession = NULL; + } + if (aiaMgr->client.hdata.serverSession != NULL) { + (*hcv1->freeSessionFcn)(aiaMgr->client.hdata.serverSession); + aiaMgr->client.hdata.serverSession = NULL; + } + aiaMgr->client.hdata.httpClient = 0; /* callback fn */ + } + + PKIX_DECREF(location); + PKIX_DECREF(locationString); + + if (locationAscii) { + PORT_Free(locationAscii); + } + if (hostname) { + PORT_Free(hostname); + } + if (path) { + PORT_Free(path); + } + + PKIX_RETURN(AIAMGR); +} + +#ifndef NSS_PKIX_NO_LDAP +PKIX_Error * +pkix_pl_AIAMgr_GetLDAPCerts( + PKIX_PL_AIAMgr *aiaMgr, + PKIX_PL_InfoAccess *ia, + void **pNBIOContext, + PKIX_List **pCerts, + void *plContext) +{ + PKIX_List *result = NULL; + PKIX_PL_GeneralName *location = NULL; + PKIX_PL_LdapClient *client = NULL; + LDAPRequestParams request; + PLArenaPool *arena = NULL; + char *domainName = NULL; + void *nbio = NULL; + + PKIX_ENTER(AIAMGR, "pkix_pl_AIAMgr_GetLDAPCerts"); + PKIX_NULLCHECK_FOUR(aiaMgr, ia, pNBIOContext, pCerts); + + nbio = *pNBIOContext; + *pNBIOContext = NULL; + *pCerts = NULL; + + if (nbio == NULL) { /* a new request */ + + /* Initiate an LDAP request */ + + request.scope = WHOLE_SUBTREE; + request.derefAliases = NEVER_DEREF; + request.sizeLimit = 0; + request.timeLimit = 0; + + PKIX_CHECK(PKIX_PL_InfoAccess_GetLocation + (ia, &location, plContext), + PKIX_INFOACCESSGETLOCATIONFAILED); + + /* + * Get a short-lived arena. We'll be done with + * this space once the request is encoded. + */ + arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + if (!arena) { + PKIX_ERROR_FATAL(PKIX_OUTOFMEMORY); + } + + PKIX_CHECK(pkix_pl_InfoAccess_ParseLocation + (location, arena, &request, &domainName, plContext), + PKIX_INFOACCESSPARSELOCATIONFAILED); + + PKIX_DECREF(location); + + /* Find or create a connection to LDAP server */ + PKIX_CHECK(pkix_pl_AiaMgr_FindLDAPClient + (aiaMgr, domainName, &client, plContext), + PKIX_AIAMGRFINDLDAPCLIENTFAILED); + + aiaMgr->client.ldapClient = client; + + PKIX_CHECK(PKIX_PL_LdapClient_InitiateRequest + (aiaMgr->client.ldapClient, + &request, + &nbio, + &result, + plContext), + PKIX_LDAPCLIENTINITIATEREQUESTFAILED); + + PKIX_PL_NSSCALL(AIAMGR, PORT_FreeArena, (arena, PR_FALSE)); + + } else { + + PKIX_CHECK(PKIX_PL_LdapClient_ResumeRequest + (aiaMgr->client.ldapClient, &nbio, &result, plContext), + PKIX_LDAPCLIENTRESUMEREQUESTFAILED); + + } + + if (nbio != NULL) { /* WOULDBLOCK */ + *pNBIOContext = nbio; + *pCerts = NULL; + goto cleanup; + } + + PKIX_DECREF(aiaMgr->client.ldapClient); + + if (result == NULL) { + *pCerts = NULL; + } else { + PKIX_CHECK(pkix_pl_LdapCertStore_BuildCertList + (result, pCerts, plContext), + PKIX_LDAPCERTSTOREBUILDCERTLISTFAILED); + } + + *pNBIOContext = nbio; + +cleanup: + + if (arena && (PKIX_ERROR_RECEIVED)) { + PKIX_PL_NSSCALL(AIAMGR, PORT_FreeArena, (arena, PR_FALSE)); + } + + if (PKIX_ERROR_RECEIVED) { + PKIX_DECREF(aiaMgr->client.ldapClient); + } + + PKIX_DECREF(location); + + PKIX_RETURN(AIAMGR); +} +#endif /* !NSS_PKIX_NO_LDAP */ + +/* + * FUNCTION: PKIX_PL_AIAMgr_Create + * DESCRIPTION: + * + * This function creates an AIAMgr, storing the result at "pAIAMgr". + * + * PARAMETERS: + * "pAIAMGR" + * Address at which the returned AIAMgr is stored. Must be non-NULL. + * "plContext" + * Platform-specific context pointer. + * THREAD SAFETY: + * Thread Safe (see Thread Safety Definitions in Programmer's Guide) + * RETURNS: + * Returns NULL if the function succeeds. + * Returns an AIAMgr Error if the function fails in a non-fatal way + * Returns a Fatal Error if the function fails in an unrecoverable way. + */ +PKIX_Error * +PKIX_PL_AIAMgr_Create( + PKIX_PL_AIAMgr **pAIAMgr, + void *plContext) +{ + PKIX_PL_AIAMgr *aiaMgr = NULL; + + PKIX_ENTER(AIAMGR, "PKIX_PL_AIAMgr_Create"); + PKIX_NULLCHECK_ONE(pAIAMgr); + + PKIX_CHECK(PKIX_PL_Object_Alloc + (PKIX_AIAMGR_TYPE, + sizeof(PKIX_PL_AIAMgr), + (PKIX_PL_Object **)&aiaMgr, + plContext), + PKIX_COULDNOTCREATEAIAMGROBJECT); + /* pointer to cert cache */ + /* pointer to crl cache */ + aiaMgr->method = 0; + aiaMgr->aiaIndex = 0; + aiaMgr->numAias = 0; + aiaMgr->aia = NULL; + aiaMgr->location = NULL; + aiaMgr->results = NULL; + aiaMgr->client.hdata.httpClient = NULL; + aiaMgr->client.hdata.serverSession = NULL; + aiaMgr->client.hdata.requestSession = NULL; + + *pAIAMgr = aiaMgr; + +cleanup: + + PKIX_RETURN(AIAMGR); +} + +/* --Public-Functions------------------------------------------------------- */ + +/* + * FUNCTION: PKIX_PL_AIAMgr_GetAIACerts (see description in pkix_pl_pki.h) + */ +PKIX_Error * +PKIX_PL_AIAMgr_GetAIACerts( + PKIX_PL_AIAMgr *aiaMgr, + PKIX_PL_Cert *prevCert, + void **pNBIOContext, + PKIX_List **pCerts, + void *plContext) +{ + PKIX_UInt32 numAias = 0; + PKIX_UInt32 aiaIndex = 0; + PKIX_UInt32 iaType = PKIX_INFOACCESS_LOCATION_UNKNOWN; + PKIX_List *certs = NULL; + PKIX_PL_InfoAccess *ia = NULL; + void *nbio = NULL; + + PKIX_ENTER(AIAMGR, "PKIX_PL_AIAMgr_GetAIACerts"); + PKIX_NULLCHECK_FOUR(aiaMgr, prevCert, pNBIOContext, pCerts); + + nbio = *pNBIOContext; + *pCerts = NULL; + *pNBIOContext = NULL; + + if (nbio == NULL) { /* a new request */ + + /* Does this Cert have an AIA extension? */ + PKIX_CHECK(PKIX_PL_Cert_GetAuthorityInfoAccess + (prevCert, &aiaMgr->aia, plContext), + PKIX_CERTGETAUTHORITYINFOACCESSFAILED); + + if (aiaMgr->aia != NULL) { + PKIX_CHECK(PKIX_List_GetLength + (aiaMgr->aia, &numAias, plContext), + PKIX_LISTGETLENGTHFAILED); + } + + /* And if so, does it have any entries? */ + if ((aiaMgr->aia == NULL) || (numAias == 0)) { + *pCerts = NULL; + goto cleanup; + } + + aiaMgr->aiaIndex = 0; + aiaMgr->numAias = numAias; + aiaMgr->results = NULL; + + } + + for (aiaIndex = aiaMgr->aiaIndex; + aiaIndex < aiaMgr->numAias; + aiaIndex ++) { + PKIX_UInt32 method = 0; + + PKIX_CHECK(PKIX_List_GetItem + (aiaMgr->aia, + aiaIndex, + (PKIX_PL_Object **)&ia, + plContext), + PKIX_LISTGETITEMFAILED); + + PKIX_CHECK(PKIX_PL_InfoAccess_GetMethod + (ia, &method, plContext), + PKIX_INFOACCESSGETMETHODFAILED); + + if (method != PKIX_INFOACCESS_CA_ISSUERS && + method != PKIX_INFOACCESS_CA_REPOSITORY) { + PKIX_DECREF(ia); + continue; + } + + PKIX_CHECK(PKIX_PL_InfoAccess_GetLocationType + (ia, &iaType, plContext), + PKIX_INFOACCESSGETLOCATIONTYPEFAILED); + + if (iaType == PKIX_INFOACCESS_LOCATION_HTTP) { + PKIX_CHECK(pkix_pl_AIAMgr_GetHTTPCerts + (aiaMgr, ia, &nbio, &certs, plContext), + PKIX_AIAMGRGETHTTPCERTSFAILED); +#ifndef NSS_PKIX_NO_LDAP + } else if (iaType == PKIX_INFOACCESS_LOCATION_LDAP) { + PKIX_CHECK(pkix_pl_AIAMgr_GetLDAPCerts + (aiaMgr, ia, &nbio, &certs, plContext), + PKIX_AIAMGRGETLDAPCERTSFAILED); +#endif + } else { + /* We only support http and ldap requests. */ + PKIX_DECREF(ia); + continue; + } + + if (nbio != NULL) { /* WOULDBLOCK */ + aiaMgr->aiaIndex = aiaIndex; + *pNBIOContext = nbio; + *pCerts = NULL; + goto cleanup; + } + + /* + * We can't just use and modify the List we received. + * Because it's cached, it's set immutable. + */ + if (aiaMgr->results == NULL) { + PKIX_CHECK(PKIX_List_Create + (&(aiaMgr->results), plContext), + PKIX_LISTCREATEFAILED); + } + PKIX_CHECK(pkix_List_AppendList + (aiaMgr->results, certs, plContext), + PKIX_APPENDLISTFAILED); + PKIX_DECREF(certs); + + PKIX_DECREF(ia); + } + + PKIX_DECREF(aiaMgr->aia); + + *pNBIOContext = NULL; + *pCerts = aiaMgr->results; + aiaMgr->results = NULL; + +cleanup: + + if (PKIX_ERROR_RECEIVED) { + PKIX_DECREF(aiaMgr->aia); + PKIX_DECREF(aiaMgr->results); +#ifndef NSS_PKIX_NO_LDAP + PKIX_DECREF(aiaMgr->client.ldapClient); +#endif + } + + PKIX_DECREF(certs); + PKIX_DECREF(ia); + + PKIX_RETURN(AIAMGR); +} |