1/*
2 * Copyright (c) 2002-2003,2011-2012,2014 Apple Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License.
7 * Please obtain a copy of the License at http://www.apple.com/publicsource
8 * and read it before using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
12 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
13 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
14 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
15 * Please see the License for the specific language governing rights
16 * and limitations under the License.
17 */
18
19/*
20	File:		 cuDbUtils.cpp
21
22	Description: CDSA DB access utilities
23
24	Author:		 dmitch
25*/
26
27#include "cuCdsaUtils.h"
28#include "cuTimeStr.h"
29#include "cuDbUtils.h"
30#include "cuPrintCert.h"
31#include <stdlib.h>
32#include <stdio.h>
33#include <Security/SecCertificate.h>
34#include <Security/SecCertificatePriv.h>	/* private SecInferLabelFromX509Name() */
35#include <Security/cssmapple.h>				/* for cssmPerror() */
36#include <Security/oidscert.h>
37#include <Security/oidscrl.h>
38#include <Security/oidsattr.h>
39#include <strings.h>
40#include <security_cdsa_utilities/Schema.h>			/* private API */
41
42#ifndef	NDEBUG
43#define dprintf(args...) printf(args)
44#else
45#define dprintf(args...)
46#endif
47
48/*
49 * Add a certificate to an open DLDB.
50 */
51CSSM_RETURN cuAddCertToDb(
52	CSSM_DL_DB_HANDLE	dlDbHand,
53	const CSSM_DATA		*cert,
54	CSSM_CERT_TYPE		certType,
55	CSSM_CERT_ENCODING	certEncoding,
56	const char			*printName,		// C string
57	const CSSM_DATA		*publicKeyHash)
58{
59	CSSM_DB_ATTRIBUTE_DATA			attrs[6];
60	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
61	CSSM_DB_ATTRIBUTE_DATA_PTR		attr = &attrs[0];
62	CSSM_DATA						certTypeData;
63	CSSM_DATA						certEncData;
64	CSSM_DATA						printNameData;
65	CSSM_RETURN						crtn;
66	CSSM_DB_UNIQUE_RECORD_PTR		recordPtr;
67
68	/* issuer and serial number required, fake 'em */
69	CSSM_DATA						issuer = {6, (uint8 *)"issuer"};
70	CSSM_DATA						serial = {6, (uint8 *)"serial"};
71
72	/* we spec six attributes, skipping alias */
73	certTypeData.Data = (uint8 *)&certType;
74	certTypeData.Length = sizeof(CSSM_CERT_TYPE);
75	certEncData.Data = (uint8 *)&certEncoding;
76	certEncData.Length = sizeof(CSSM_CERT_ENCODING);
77	printNameData.Data = (uint8 *)printName;
78	printNameData.Length = strlen(printName) + 1;
79
80	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
81	attr->Info.Label.AttributeName = (char*) "CertType";
82	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
83	attr->NumberOfValues = 1;
84	attr->Value = &certTypeData;
85
86	attr++;
87	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
88	attr->Info.Label.AttributeName = (char*) "CertEncoding";
89	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
90	attr->NumberOfValues = 1;
91	attr->Value = &certEncData;
92
93	attr++;
94	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
95	attr->Info.Label.AttributeName = (char*) "PrintName";
96	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
97	attr->NumberOfValues = 1;
98	attr->Value = &printNameData;
99
100	attr++;
101	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
102	attr->Info.Label.AttributeName = (char*) "PublicKeyHash";
103	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
104	attr->NumberOfValues = 1;
105	attr->Value = (CSSM_DATA_PTR)publicKeyHash;
106
107	attr++;
108	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
109	attr->Info.Label.AttributeName = (char*) "Issuer";
110	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
111	attr->NumberOfValues = 1;
112	attr->Value = &issuer;
113
114	attr++;
115	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
116	attr->Info.Label.AttributeName = (char*) "SerialNumber";
117	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
118	attr->NumberOfValues = 1;
119	attr->Value = &serial;
120
121	recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
122	recordAttrs.SemanticInformation = 0;
123	recordAttrs.NumberOfAttributes = 6;
124	recordAttrs.AttributeData = attrs;
125
126	crtn = CSSM_DL_DataInsert(dlDbHand,
127		CSSM_DL_DB_RECORD_X509_CERTIFICATE,
128		&recordAttrs,
129		cert,
130		&recordPtr);
131	if(crtn) {
132		cuPrintError("CSSM_DL_DataInsert", crtn);
133	}
134	else {
135		CSSM_DL_FreeUniqueRecord(dlDbHand, recordPtr);
136	}
137	return crtn;
138}
139
140static CSSM_RETURN cuAddCrlSchema(
141	CSSM_DL_DB_HANDLE	dlDbHand);
142
143static void cuInferCrlLabel(
144	const CSSM_X509_NAME 	*x509Name,
145	CSSM_DATA				*label)	 // not mallocd; contents are from the x509Name
146{
147	/* use private API for common "infer label" logic */
148	const CSSM_DATA *printValue = SecInferLabelFromX509Name(x509Name);
149	if(printValue == NULL) {
150		/* punt! */
151		label->Data = (uint8 *)"X509 CRL";
152		label->Length = 8;
153	}
154	else {
155		*label = *printValue;
156	}
157}
158
159/*
160 * Search extensions for specified OID, assumed to have underlying
161 * value type of uint32; returns the value and true if found.
162 */
163static bool cuSearchNumericExtension(
164	const CSSM_X509_EXTENSIONS	*extens,
165	const CSSM_OID				*oid,
166	uint32						*val)
167{
168	for(uint32 dex=0; dex<extens->numberOfExtensions; dex++) {
169		const CSSM_X509_EXTENSION *exten = &extens->extensions[dex];
170		if(!cuCompareOid(&exten->extnId, oid)) {
171			continue;
172		}
173		if(exten->format != CSSM_X509_DATAFORMAT_PARSED) {
174			dprintf("***Malformed extension\n");
175			continue;
176		}
177		*val = *((uint32 *)exten->value.parsedValue);
178		return true;
179	}
180	return false;
181}
182
183/*
184 * Add a CRL to an existing DL/DB.
185 */
186#define MAX_CRL_ATTRS			9
187
188CSSM_RETURN cuAddCrlToDb(
189	CSSM_DL_DB_HANDLE	dlDbHand,
190	CSSM_CL_HANDLE		clHand,
191	const CSSM_DATA		*crl,
192	const CSSM_DATA		*URI)		// optional
193{
194	CSSM_DB_ATTRIBUTE_DATA			attrs[MAX_CRL_ATTRS];
195	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
196	CSSM_DB_ATTRIBUTE_DATA_PTR		attr = &attrs[0];
197	CSSM_DATA						crlTypeData;
198	CSSM_DATA						crlEncData;
199	CSSM_DATA						printNameData;
200	CSSM_RETURN						crtn;
201	CSSM_DB_UNIQUE_RECORD_PTR		recordPtr;
202	CSSM_DATA_PTR					issuer = NULL;		// mallocd by CL
203	CSSM_DATA_PTR					crlValue = NULL;	// ditto
204	uint32							numFields;
205	CSSM_HANDLE						result;
206	CSSM_CRL_ENCODING 				crlEnc = CSSM_CRL_ENCODING_DER;
207	const CSSM_X509_SIGNED_CRL 		*signedCrl;
208	const CSSM_X509_TBS_CERTLIST 	*tbsCrl;
209	CSSM_CRL_TYPE 					crlType;
210	CSSM_DATA 						thisUpdateData = {0, NULL};
211	CSSM_DATA 						nextUpdateData = {0, NULL};
212	char							*thisUpdate = NULL;
213	char							*nextUpdate = NULL;
214	unsigned						timeLen;
215	uint32							crlNumber;
216	uint32							deltaCrlNumber;
217	CSSM_DATA						crlNumberData;
218	CSSM_DATA						deltaCrlNumberData;
219	bool							crlNumberPresent = false;
220	bool							deltaCrlPresent = false;
221	CSSM_DATA						attrUri;
222
223	/* get normalized issuer name as Issuer attr */
224	crtn = CSSM_CL_CrlGetFirstFieldValue(clHand,
225		crl,
226		&CSSMOID_X509V1IssuerName,
227		&result,
228		&numFields,
229		&issuer);
230	if(crtn) {
231		cuPrintError("CSSM_CL_CrlGetFirstFieldValue(Issuer)", crtn);
232		return crtn;
233	}
234	CSSM_CL_CrlAbortQuery(clHand, result);
235
236	/* get parsed CRL from the CL */
237	crtn = CSSM_CL_CrlGetFirstFieldValue(clHand,
238		crl,
239		&CSSMOID_X509V2CRLSignedCrlCStruct,
240		&result,
241		&numFields,
242		&crlValue);
243	if(crtn) {
244		cuPrintError("CSSM_CL_CrlGetFirstFieldValue(Issuer)", crtn);
245		goto errOut;
246	}
247	CSSM_CL_CrlAbortQuery(clHand, result);
248	if(crlValue == NULL) {
249		dprintf("***CSSM_CL_CrlGetFirstFieldValue: value error (1)\n");
250		crtn = CSSMERR_CL_INVALID_CRL_POINTER;
251		goto errOut;
252	}
253	if((crlValue->Data == NULL) ||
254	   (crlValue->Length != sizeof(CSSM_X509_SIGNED_CRL))) {
255		dprintf("***CSSM_CL_CrlGetFirstFieldValue: value error (2)\n");
256		crtn = CSSMERR_CL_INVALID_CRL_POINTER;
257		goto errOut;
258	}
259	signedCrl = (const CSSM_X509_SIGNED_CRL *)crlValue->Data;
260	tbsCrl = &signedCrl->tbsCertList;
261
262	/* CrlType inferred from version */
263	if(tbsCrl->version.Length == 0) {
264		/* should never happen... */
265		crlType = CSSM_CRL_TYPE_X_509v1;
266	}
267	else {
268		uint8 vers = tbsCrl->version.Data[tbsCrl->version.Length - 1];
269		switch(vers) {
270			case 0:
271				crlType = CSSM_CRL_TYPE_X_509v1;
272				break;
273			case 1:
274				crlType = CSSM_CRL_TYPE_X_509v2;
275				break;
276			default:
277				dprintf("***Unknown version in CRL (%u)\n", vers);
278				crlType = CSSM_CRL_TYPE_X_509v1;
279				break;
280		}
281	}
282	crlTypeData.Data = (uint8 *)&crlType;
283	crlTypeData.Length = sizeof(CSSM_CRL_TYPE);
284	/* encoding more-or-less assumed here */
285	crlEncData.Data = (uint8 *)&crlEnc;
286	crlEncData.Length = sizeof(CSSM_CRL_ENCODING);
287
288	/* printName inferred from issuer */
289	cuInferCrlLabel(&tbsCrl->issuer, &printNameData);
290
291	/* cook up CSSM_TIMESTRING versions of this/next update */
292	thisUpdate = cuX509TimeToCssmTimestring(&tbsCrl->thisUpdate, &timeLen);
293	if(thisUpdate == NULL) {
294		dprintf("***Badly formatted thisUpdate\n");
295	}
296	else {
297		thisUpdateData.Data = (uint8 *)thisUpdate;
298		thisUpdateData.Length = timeLen;
299	}
300	if(tbsCrl->nextUpdate.time.Data != NULL) {
301		nextUpdate = cuX509TimeToCssmTimestring(&tbsCrl->nextUpdate, &timeLen);
302		if(nextUpdate == NULL) {
303			dprintf("***Badly formatted nextUpdate\n");
304		}
305		else {
306			nextUpdateData.Data = (uint8 *)nextUpdate;
307			nextUpdateData.Length = timeLen;
308		}
309	}
310	else {
311		/*
312		 * NextUpdate not present; fake it by using "virtual end of time"
313		 */
314		CSSM_X509_TIME tempTime = {	0,		// timeType, not used
315			{ strlen(CSSM_APPLE_CRL_END_OF_TIME),
316			  (uint8 *)CSSM_APPLE_CRL_END_OF_TIME} };
317		nextUpdate = cuX509TimeToCssmTimestring(&tempTime, &timeLen);
318		nextUpdateData.Data = (uint8 *)nextUpdate;
319		nextUpdateData.Length = CSSM_TIME_STRLEN;
320	}
321
322	/* optional CrlNumber and DeltaCrlNumber */
323	if(cuSearchNumericExtension(&tbsCrl->extensions,
324			&CSSMOID_CrlNumber,
325			&crlNumber)) {
326		crlNumberData.Data = (uint8 *)&crlNumber;
327		crlNumberData.Length = sizeof(uint32);
328		crlNumberPresent = true;
329	}
330	if(cuSearchNumericExtension(&tbsCrl->extensions,
331			&CSSMOID_DeltaCrlIndicator,
332			&deltaCrlNumber)) {
333		deltaCrlNumberData.Data = (uint8 *)&deltaCrlNumber;
334		deltaCrlNumberData.Length = sizeof(uint32);
335		deltaCrlPresent = true;
336	}
337
338	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
339	attr->Info.Label.AttributeName = (char*) "CrlType";
340	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
341	attr->NumberOfValues = 1;
342	attr->Value = &crlTypeData;
343	attr++;
344
345	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
346	attr->Info.Label.AttributeName = (char*) "CrlEncoding";
347	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
348	attr->NumberOfValues = 1;
349	attr->Value = &crlEncData;
350	attr++;
351
352	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
353	attr->Info.Label.AttributeName = (char*) "PrintName";
354	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
355	attr->NumberOfValues = 1;
356	attr->Value = &printNameData;
357	attr++;
358
359	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
360	attr->Info.Label.AttributeName = (char*) "Issuer";
361	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
362	attr->NumberOfValues = 1;
363	attr->Value = issuer;
364	attr++;
365
366	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
367	attr->Info.Label.AttributeName = (char*) "ThisUpdate";
368	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
369	attr->NumberOfValues = 1;
370	attr->Value = &thisUpdateData;
371	attr++;
372
373	attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
374	attr->Info.Label.AttributeName = (char*) "NextUpdate";
375	attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
376	attr->NumberOfValues = 1;
377	attr->Value = &nextUpdateData;
378	attr++;
379
380	/* now the optional attributes */
381	if(crlNumberPresent) {
382		attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
383		attr->Info.Label.AttributeName = (char*) "CrlNumber";
384		attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
385		attr->NumberOfValues = 1;
386		attr->Value = &crlNumberData;
387		attr++;
388	}
389	if(deltaCrlPresent) {
390		attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
391		attr->Info.Label.AttributeName = (char*) "DeltaCrlNumber";
392		attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
393		attr->NumberOfValues = 1;
394		attr->Value = &deltaCrlNumberData;
395		attr++;
396	}
397	if(URI) {
398		/* ensure URI string does not contain NULL */
399		attrUri = *URI;
400		if((attrUri.Length != 0) &&
401		   (attrUri.Data[attrUri.Length - 1] == 0)) {
402			attrUri.Length--;
403		}
404		attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
405		attr->Info.Label.AttributeName = (char*) "URI";
406		attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
407		attr->NumberOfValues = 1;
408		attr->Value = &attrUri;
409		attr++;
410	}
411	recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CRL;
412	recordAttrs.SemanticInformation = 0;
413	recordAttrs.NumberOfAttributes = (uint32)(attr - attrs);
414	recordAttrs.AttributeData = attrs;
415
416	crtn = CSSM_DL_DataInsert(dlDbHand,
417		CSSM_DL_DB_RECORD_X509_CRL,
418		&recordAttrs,
419		crl,
420		&recordPtr);
421	if(crtn == CSSMERR_DL_INVALID_RECORDTYPE) {
422		/* gross hack of inserting this "new" schema that Keychain didn't specify */
423		crtn = cuAddCrlSchema(dlDbHand);
424		if(crtn == CSSM_OK) {
425			/* Retry with a fully capable DLDB */
426			crtn = CSSM_DL_DataInsert(dlDbHand,
427				CSSM_DL_DB_RECORD_X509_CRL,
428				&recordAttrs,
429				crl,
430				&recordPtr);
431		}
432	}
433	if(crtn == CSSM_OK) {
434		CSSM_DL_FreeUniqueRecord(dlDbHand, recordPtr);
435	}
436
437errOut:
438	/* free all the stuff we allocated to get here */
439	if(issuer) {
440		CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1IssuerName, issuer);
441	}
442	if(crlValue) {
443		CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V2CRLSignedCrlCStruct, crlValue);
444	}
445	if(thisUpdate) {
446		free(thisUpdate);
447	}
448	if(nextUpdate) {
449		free(nextUpdate);
450	}
451	return crtn;
452}
453
454
455/*
456 * Update an existing DLDB to be CRL-capable.
457 */
458static CSSM_RETURN cuAddCrlSchema(
459	CSSM_DL_DB_HANDLE	dlDbHand)
460{
461	return CSSM_DL_CreateRelation(dlDbHand,
462		CSSM_DL_DB_RECORD_X509_CRL,
463		"CSSM_DL_DB_RECORD_X509_CRL",
464		Security::KeychainCore::Schema::X509CrlSchemaAttributeCount,
465		Security::KeychainCore::Schema::X509CrlSchemaAttributeList,
466		Security::KeychainCore::Schema::X509CrlSchemaIndexCount,
467		Security::KeychainCore::Schema::X509CrlSchemaIndexList);
468}
469
470/*
471 * Search DB for all records of type CRL or cert, calling appropriate
472 * parse/print routine for each record.
473 */
474CSSM_RETURN cuDumpCrlsCerts(
475	CSSM_DL_DB_HANDLE	dlDbHand,
476	CSSM_CL_HANDLE		clHand,
477	CSSM_BOOL			isCert,
478	unsigned			&numItems,		// returned
479	CSSM_BOOL			verbose)
480{
481	CSSM_QUERY					query;
482	CSSM_DB_UNIQUE_RECORD_PTR	record = NULL;
483	CSSM_HANDLE					resultHand;
484	CSSM_RETURN					crtn;
485	CSSM_DATA					certCrl;
486	const char					*itemStr;
487
488	numItems = 0;
489	itemStr = isCert ? "Certificate" : "CRL";
490
491	/* just search by recordType, no predicates, no attributes */
492	if(isCert) {
493		query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
494	}
495	else {
496		query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
497	}
498	query.Conjunctive = CSSM_DB_NONE;
499	query.NumSelectionPredicates = 0;
500	query.SelectionPredicate = NULL;
501	query.QueryLimits.TimeLimit = 0;			// FIXME - meaningful?
502	query.QueryLimits.SizeLimit = 1;			// FIXME - meaningful?
503	query.QueryFlags = 0;		// CSSM_QUERY_RETURN_DATA...FIXME - used?
504
505	certCrl.Data = NULL;
506	certCrl.Length = 0;
507	crtn = CSSM_DL_DataGetFirst(dlDbHand,
508		&query,
509		&resultHand,
510		NULL,			// no attrs
511		&certCrl,
512		&record);
513	switch(crtn) {
514		case CSSM_OK:
515			break;		// proceed
516		case CSSMERR_DL_ENDOFDATA:
517			/* no data, otherwise OK */
518			return CSSM_OK;
519		case CSSMERR_DL_INVALID_RECORDTYPE:
520			/* invalid record type just means "this hasn't been set up
521			* for certs yet". */
522			return crtn;
523		default:
524			cuPrintError("DataGetFirst", crtn);
525			return crtn;
526	}
527
528	/* got one; print it */
529	dprintf("%s %u:\n", itemStr, numItems);
530	if(isCert) {
531		printCert(certCrl.Data, (unsigned)certCrl.Length, verbose);
532	}
533	else {
534		printCrl(certCrl.Data, (unsigned)certCrl.Length, verbose);
535	}
536	CSSM_DL_FreeUniqueRecord(dlDbHand, record);
537	APP_FREE(certCrl.Data);
538	certCrl.Data = NULL;
539	certCrl.Length = 0;
540	numItems++;
541
542	/* get the rest */
543	for(;;) {
544		crtn = CSSM_DL_DataGetNext(dlDbHand,
545			resultHand,
546			NULL,
547			&certCrl,
548			&record);
549		switch(crtn) {
550			case CSSM_OK:
551				dprintf("%s %u:\n", itemStr, numItems);
552				if(isCert) {
553					printCert(certCrl.Data, (unsigned)certCrl.Length, verbose);
554				}
555				else {
556					printCrl(certCrl.Data, (unsigned)certCrl.Length, verbose);
557				}
558				CSSM_DL_FreeUniqueRecord(dlDbHand, record);
559				APP_FREE(certCrl.Data);
560				certCrl.Data = NULL;
561				certCrl.Length = 0;
562				numItems++;
563				break;		// and go again
564			case CSSMERR_DL_ENDOFDATA:
565				/* normal termination */
566				return CSSM_OK;
567			default:
568				cuPrintError("DataGetNext", crtn);
569				return crtn;
570		}
571	}
572	/* NOT REACHED */
573}
574
575