]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_cdsa_utils/lib/cuDbUtils.cpp
Security-57740.1.18.tar.gz
[apple/security.git] / OSX / libsecurity_cdsa_utils / lib / cuDbUtils.cpp
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 */
51 CSSM_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
140 static CSSM_RETURN cuAddCrlSchema(
141 CSSM_DL_DB_HANDLE dlDbHand);
142
143 static 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 */
163 static 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
188 CSSM_RETURN cuAddCrlToDb(
189 CSSM_DL_DB_HANDLE dlDbHand,
190 CSSM_CL_HANDLE clHand,
191 const CSSM_DATA *crl,
192 const CSSM_DATA *URI)
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 /* ensure URI string does not contain NULL */
381 attrUri = *URI;
382 if((attrUri.Length != 0) &&
383 (attrUri.Data[attrUri.Length - 1] == 0)) {
384 attrUri.Length--;
385 }
386 attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
387 attr->Info.Label.AttributeName = (char*) "URI";
388 attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB;
389 attr->NumberOfValues = 1;
390 attr->Value = &attrUri;
391 attr++;
392
393 /* now the optional attributes */
394 if(crlNumberPresent) {
395 attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
396 attr->Info.Label.AttributeName = (char*) "CrlNumber";
397 attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
398 attr->NumberOfValues = 1;
399 attr->Value = &crlNumberData;
400 attr++;
401 }
402 if(deltaCrlPresent) {
403 attr->Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
404 attr->Info.Label.AttributeName = (char*) "DeltaCrlNumber";
405 attr->Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_UINT32;
406 attr->NumberOfValues = 1;
407 attr->Value = &deltaCrlNumberData;
408 attr++;
409 }
410 recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CRL;
411 recordAttrs.SemanticInformation = 0;
412 recordAttrs.NumberOfAttributes = (uint32)(attr - attrs);
413 recordAttrs.AttributeData = attrs;
414
415 crtn = CSSM_DL_DataInsert(dlDbHand,
416 CSSM_DL_DB_RECORD_X509_CRL,
417 &recordAttrs,
418 crl,
419 &recordPtr);
420 if(crtn == CSSMERR_DL_INVALID_RECORDTYPE) {
421 /* gross hack of inserting this "new" schema that Keychain didn't specify */
422 crtn = cuAddCrlSchema(dlDbHand);
423 if(crtn == CSSM_OK) {
424 /* Retry with a fully capable DLDB */
425 crtn = CSSM_DL_DataInsert(dlDbHand,
426 CSSM_DL_DB_RECORD_X509_CRL,
427 &recordAttrs,
428 crl,
429 &recordPtr);
430 }
431 }
432 if(crtn == CSSM_OK) {
433 CSSM_DL_FreeUniqueRecord(dlDbHand, recordPtr);
434 }
435
436 errOut:
437 /* free all the stuff we allocated to get here */
438 if(issuer) {
439 CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V1IssuerName, issuer);
440 }
441 if(crlValue) {
442 CSSM_CL_FreeFieldValue(clHand, &CSSMOID_X509V2CRLSignedCrlCStruct, crlValue);
443 }
444 if(thisUpdate) {
445 free(thisUpdate);
446 }
447 if(nextUpdate) {
448 free(nextUpdate);
449 }
450 return crtn;
451 }
452
453
454 /*
455 * Update an existing DLDB to be CRL-capable.
456 */
457 static CSSM_RETURN cuAddCrlSchema(
458 CSSM_DL_DB_HANDLE dlDbHand)
459 {
460 return CSSM_DL_CreateRelation(dlDbHand,
461 CSSM_DL_DB_RECORD_X509_CRL,
462 "CSSM_DL_DB_RECORD_X509_CRL",
463 Security::KeychainCore::Schema::X509CrlSchemaAttributeCount,
464 Security::KeychainCore::Schema::X509CrlSchemaAttributeList,
465 Security::KeychainCore::Schema::X509CrlSchemaIndexCount,
466 Security::KeychainCore::Schema::X509CrlSchemaIndexList);
467 }
468
469 /*
470 * Search DB for all records of type CRL or cert, calling appropriate
471 * parse/print routine for each record.
472 */
473 CSSM_RETURN cuDumpCrlsCerts(
474 CSSM_DL_DB_HANDLE dlDbHand,
475 CSSM_CL_HANDLE clHand,
476 CSSM_BOOL isCert,
477 unsigned &numItems, // returned
478 CSSM_BOOL verbose)
479 {
480 CSSM_QUERY query;
481 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
482 CSSM_HANDLE resultHand;
483 CSSM_RETURN crtn;
484 CSSM_DATA certCrl;
485 const char *itemStr;
486
487 numItems = 0;
488 itemStr = isCert ? "Certificate" : "CRL";
489
490 /* just search by recordType, no predicates, no attributes */
491 if(isCert) {
492 query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE;
493 }
494 else {
495 query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
496 }
497 query.Conjunctive = CSSM_DB_NONE;
498 query.NumSelectionPredicates = 0;
499 query.SelectionPredicate = NULL;
500 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
501 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
502 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
503
504 certCrl.Data = NULL;
505 certCrl.Length = 0;
506 crtn = CSSM_DL_DataGetFirst(dlDbHand,
507 &query,
508 &resultHand,
509 NULL, // no attrs
510 &certCrl,
511 &record);
512 switch(crtn) {
513 case CSSM_OK:
514 break; // proceed
515 case CSSMERR_DL_ENDOFDATA:
516 /* no data, otherwise OK */
517 return CSSM_OK;
518 case CSSMERR_DL_INVALID_RECORDTYPE:
519 /* invalid record type just means "this hasn't been set up
520 * for certs yet". */
521 return crtn;
522 default:
523 cuPrintError("DataGetFirst", crtn);
524 return crtn;
525 }
526
527 /* got one; print it */
528 dprintf("%s %u:\n", itemStr, numItems);
529 if(isCert) {
530 printCert(certCrl.Data, (unsigned)certCrl.Length, verbose);
531 }
532 else {
533 printCrl(certCrl.Data, (unsigned)certCrl.Length, verbose);
534 }
535 CSSM_DL_FreeUniqueRecord(dlDbHand, record);
536 APP_FREE(certCrl.Data);
537 certCrl.Data = NULL;
538 certCrl.Length = 0;
539 numItems++;
540
541 /* get the rest */
542 for(;;) {
543 crtn = CSSM_DL_DataGetNext(dlDbHand,
544 resultHand,
545 NULL,
546 &certCrl,
547 &record);
548 switch(crtn) {
549 case CSSM_OK:
550 dprintf("%s %u:\n", itemStr, numItems);
551 if(isCert) {
552 printCert(certCrl.Data, (unsigned)certCrl.Length, verbose);
553 }
554 else {
555 printCrl(certCrl.Data, (unsigned)certCrl.Length, verbose);
556 }
557 CSSM_DL_FreeUniqueRecord(dlDbHand, record);
558 APP_FREE(certCrl.Data);
559 certCrl.Data = NULL;
560 certCrl.Length = 0;
561 numItems++;
562 break; // and go again
563 case CSSMERR_DL_ENDOFDATA:
564 /* normal termination */
565 return CSSM_OK;
566 default:
567 cuPrintError("DataGetNext", crtn);
568 return crtn;
569 }
570 }
571 /* NOT REACHED */
572 }
573