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