1/* 2 * Copyright (c) 2002-2009 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. Please obtain 7 * a copy of the License at http://www.apple.com/publicsource and read it before 8 * 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 EXPRESS 12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT 13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the 15 * specific language governing rights and limitations under the License. 16 */ 17 18 19/* 20 * TPDatabase.cpp - TP's DL/DB access functions. 21 * 22 * Created 10/9/2002 by Doug Mitchell. 23 */ 24 25#include <Security/cssmtype.h> 26#include <Security/cssmapi.h> 27#include <security_cdsa_utilities/Schema.h> /* private API */ 28#include <security_keychain/TrustKeychains.h> /* private SecTrustKeychainsGetMutex() */ 29#include <Security/SecCertificatePriv.h> /* private SecInferLabelFromX509Name() */ 30#include <Security/oidscert.h> 31#include "TPDatabase.h" 32#include "tpdebugging.h" 33#include "certGroupUtils.h" 34#include "TPCertInfo.h" 35#include "TPCrlInfo.h" 36#include "tpCrlVerify.h" 37#include "tpTime.h" 38 39 40/* 41 * Given a DL/DB, look up cert by subject name. Subsequent 42 * certs can be found using the returned result handle. 43 */ 44static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup( 45 CSSM_DL_DB_HANDLE dlDb, 46 const CSSM_DATA *subjectName, // DER-encoded 47 CSSM_HANDLE_PTR resultHand, // RETURNED 48 CSSM_DATA_PTR cert) // RETURNED 49{ 50 CSSM_QUERY query; 51 CSSM_SELECTION_PREDICATE predicate; 52 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 53 54 cert->Data = NULL; 55 cert->Length = 0; 56 57 /* SWAG until cert schema nailed down */ 58 predicate.DbOperator = CSSM_DB_EQUAL; 59 predicate.Attribute.Info.AttributeNameFormat = 60 CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 61 predicate.Attribute.Info.Label.AttributeName = (char*) "Subject"; 62 predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 63 predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName); 64 predicate.Attribute.NumberOfValues = 1; 65 66 query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE; 67 query.Conjunctive = CSSM_DB_NONE; 68 query.NumSelectionPredicates = 1; 69 query.SelectionPredicate = &predicate; 70 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful? 71 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? 72 query.QueryFlags = 0; // FIXME - used? 73 74 CSSM_DL_DataGetFirst(dlDb, 75 &query, 76 resultHand, 77 NULL, // don't fetch attributes 78 cert, 79 &record); 80 81 return record; 82} 83 84/* 85 * Search a list of DBs for a cert which verifies specified subject item. 86 * Just a boolean return - we found it, or not. If we did, we return 87 * TPCertInfo associated with the raw cert. 88 * A true partialIssuerKey on return indicates that caller must deal 89 * with partial public key processing later. 90 * If verifyCurrent is true, we will not return a cert which is not 91 * temporally valid; else we may well do so. 92 */ 93TPCertInfo *tpDbFindIssuerCert( 94 Allocator &alloc, 95 CSSM_CL_HANDLE clHand, 96 CSSM_CSP_HANDLE cspHand, 97 const TPClItemInfo *subjectItem, 98 const CSSM_DL_DB_LIST *dbList, 99 const char *verifyTime, // may be NULL 100 bool &partialIssuerKey) // RETURNED 101{ 102 StLock<Mutex> _(SecTrustKeychainsGetMutex()); 103 104 uint32 dbDex; 105 CSSM_HANDLE resultHand; 106 CSSM_DATA cert; 107 CSSM_DL_DB_HANDLE dlDb; 108 CSSM_DB_UNIQUE_RECORD_PTR record; 109 TPCertInfo *issuerCert = NULL; 110 bool foundIt; 111 TPCertInfo *expiredIssuer = NULL; 112 113 partialIssuerKey = false; 114 if(dbList == NULL) { 115 return NULL; 116 } 117 for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) { 118 dlDb = dbList->DLDBHandle[dbDex]; 119 cert.Data = NULL; 120 cert.Length = 0; 121 resultHand = 0; 122 record = tpCertLookup(dlDb, 123 subjectItem->issuerName(), 124 &resultHand, 125 &cert); 126 /* remember we have to: 127 * -- abort this query regardless, and 128 * -- free the CSSM_DATA cert regardless, and 129 * -- free the unique record if we don't use it 130 * (by placing it in issuerCert)... 131 */ 132 if(record != NULL) { 133 /* Found one */ 134 assert(cert.Data != NULL); 135 tpDbDebug("tpDbFindIssuerCert: found cert record %p", record); 136 issuerCert = NULL; 137 CSSM_RETURN crtn = CSSM_OK; 138 try { 139 issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, verifyTime); 140 } 141 catch(...) { 142 crtn = CSSMERR_TP_INVALID_CERTIFICATE; 143 } 144 145 /* we're done with raw cert data */ 146 tpFreePluginMemory(dlDb.DLHandle, cert.Data); 147 cert.Data = NULL; 148 cert.Length = 0; 149 150 /* Does it verify the subject cert? */ 151 if(crtn == CSSM_OK) { 152 crtn = subjectItem->verifyWithIssuer(issuerCert); 153 } 154 155 /* 156 * Handle temporal invalidity - if so and this is the first one 157 * we've seen, hold on to it while we search for better one. 158 */ 159 if((crtn == CSSM_OK) && (expiredIssuer == NULL)) { 160 if(issuerCert->isExpired() || issuerCert->isNotValidYet()) { 161 /* 162 * Exact value not important here, this just uniquely identifies 163 * this situation in the switch below. 164 */ 165 tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)"); 166 crtn = CSSM_CERT_STATUS_EXPIRED; 167 expiredIssuer = issuerCert; 168 expiredIssuer->dlDbHandle(dlDb); 169 expiredIssuer->uniqueRecord(record); 170 } 171 } 172 switch(crtn) { 173 case CSSM_OK: 174 break; 175 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: 176 partialIssuerKey = true; 177 break; 178 default: 179 issuerCert = NULL; 180 if(crtn != CSSM_CERT_STATUS_EXPIRED) { 181 delete issuerCert; 182 CSSM_DL_FreeUniqueRecord(dlDb, record); 183 } 184 185 /* 186 * Continue searching this DB. Break on finding the holy 187 * grail or no more records found. 188 */ 189 for(;;) { 190 cert.Data = NULL; 191 cert.Length = 0; 192 record = NULL; 193 CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb, 194 resultHand, 195 NULL, // no attrs 196 &cert, 197 &record); 198 if(crtn) { 199 /* no more, done with this DB */ 200 assert(cert.Data == NULL); 201 break; 202 } 203 assert(cert.Data != NULL); 204 tpDbDebug("tpDbFindIssuerCert: found cert record %p", record); 205 206 /* found one - does it verify subject? */ 207 try { 208 issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, 209 verifyTime); 210 } 211 catch(...) { 212 crtn = CSSMERR_TP_INVALID_CERTIFICATE; 213 } 214 /* we're done with raw cert data */ 215 tpFreePluginMemory(dlDb.DLHandle, cert.Data); 216 cert.Data = NULL; 217 cert.Length = 0; 218 219 if(crtn == CSSM_OK) { 220 crtn = subjectItem->verifyWithIssuer(issuerCert); 221 } 222 223 /* temporal validity check, again */ 224 if((crtn == CSSM_OK) && (expiredIssuer == NULL)) { 225 if(issuerCert->isExpired() || issuerCert->isNotValidYet()) { 226 tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)"); 227 crtn = CSSM_CERT_STATUS_EXPIRED; 228 expiredIssuer = issuerCert; 229 expiredIssuer->dlDbHandle(dlDb); 230 expiredIssuer->uniqueRecord(record); 231 } 232 } 233 234 foundIt = false; 235 switch(crtn) { 236 case CSSM_OK: 237 foundIt = true; 238 break; 239 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: 240 partialIssuerKey = true; 241 foundIt = true; 242 break; 243 default: 244 break; 245 } 246 if(foundIt) { 247 /* yes! */ 248 break; 249 } 250 if(crtn != CSSM_CERT_STATUS_EXPIRED) { 251 delete issuerCert; 252 CSSM_DL_FreeUniqueRecord(dlDb, record); 253 } 254 issuerCert = NULL; 255 } /* searching subsequent records */ 256 } /* switch verify */ 257 258 if(record != NULL) { 259 /* NULL record --> end of search --> DB auto-aborted */ 260 crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand); 261 assert(crtn == CSSM_OK); 262 } 263 if(issuerCert != NULL) { 264 /* successful return */ 265 tpDbDebug("tpDbFindIssuer: returning record %p", record); 266 issuerCert->dlDbHandle(dlDb); 267 issuerCert->uniqueRecord(record); 268 if(expiredIssuer != NULL) { 269 /* We found a replacement */ 270 tpDbDebug("tpDbFindIssuer: discarding expired cert"); 271 expiredIssuer->freeUniqueRecord(); 272 delete expiredIssuer; 273 } 274 return issuerCert; 275 } 276 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ 277 else { 278 assert(cert.Data == NULL); 279 assert(resultHand == 0); 280 } 281 } /* main loop searching dbList */ 282 283 if(expiredIssuer != NULL) { 284 /* OK, we'll take this one */ 285 tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p", 286 expiredIssuer->uniqueRecord()); 287 return expiredIssuer; 288 } 289 /* issuer not found */ 290 return NULL; 291} 292 293/* 294 * Given a DL/DB, look up CRL by issuer name and validity time. 295 * Subsequent CRLs can be found using the returned result handle. 296 */ 297#define SEARCH_BY_DATE 1 298 299static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup( 300 CSSM_DL_DB_HANDLE dlDb, 301 const CSSM_DATA *issuerName, // DER-encoded 302 CSSM_TIMESTRING verifyTime, // may be NULL, implies "now" 303 CSSM_HANDLE_PTR resultHand, // RETURNED 304 CSSM_DATA_PTR crl) // RETURNED 305{ 306 CSSM_QUERY query; 307 CSSM_SELECTION_PREDICATE pred[3]; 308 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 309 char timeStr[CSSM_TIME_STRLEN + 1]; 310 311 crl->Data = NULL; 312 crl->Length = 0; 313 314 /* Three predicates...first, the issuer name */ 315 pred[0].DbOperator = CSSM_DB_EQUAL; 316 pred[0].Attribute.Info.AttributeNameFormat = 317 CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 318 pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer"; 319 pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 320 pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName); 321 pred[0].Attribute.NumberOfValues = 1; 322 323 /* now before/after. Cook up an appropriate time string. */ 324 if(verifyTime != NULL) { 325 /* Caller spec'd tolerate any format */ 326 int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr); 327 if(rtn) { 328 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n"); 329 return NULL; 330 } 331 } 332 else { 333 /* right now */ 334 StLock<Mutex> _(tpTimeLock()); 335 timeAtNowPlus(0, TIME_CSSM, timeStr); 336 } 337 CSSM_DATA timeData; 338 timeData.Data = (uint8 *)timeStr; 339 timeData.Length = CSSM_TIME_STRLEN; 340 341 #if SEARCH_BY_DATE 342 pred[1].DbOperator = CSSM_DB_LESS_THAN; 343 pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 344 pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate"; 345 pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 346 pred[1].Attribute.Value = &timeData; 347 pred[1].Attribute.NumberOfValues = 1; 348 349 pred[2].DbOperator = CSSM_DB_GREATER_THAN; 350 pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 351 pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate"; 352 pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 353 pred[2].Attribute.Value = &timeData; 354 pred[2].Attribute.NumberOfValues = 1; 355 #endif 356 357 query.RecordType = CSSM_DL_DB_RECORD_X509_CRL; 358 query.Conjunctive = CSSM_DB_AND; 359 #if SEARCH_BY_DATE 360 query.NumSelectionPredicates = 3; 361 #else 362 query.NumSelectionPredicates = 1; 363 #endif 364 query.SelectionPredicate = pred; 365 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful? 366 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? 367 query.QueryFlags = 0; // FIXME - used? 368 369 CSSM_DL_DataGetFirst(dlDb, 370 &query, 371 resultHand, 372 NULL, // don't fetch attributes 373 crl, 374 &record); 375 return record; 376} 377 378/* 379 * Search a list of DBs for a CRL from the specified issuer and (optional) 380 * TPVerifyContext.verifyTime. 381 * Just a boolean return - we found it, or not. If we did, we return a 382 * TPCrlInfo which has been verified with the specified TPVerifyContext. 383 */ 384TPCrlInfo *tpDbFindIssuerCrl( 385 TPVerifyContext &vfyCtx, 386 const CSSM_DATA &issuer, 387 TPCertInfo &forCert) 388{ 389 StLock<Mutex> _(SecTrustKeychainsGetMutex()); 390 391 uint32 dbDex; 392 CSSM_HANDLE resultHand; 393 CSSM_DATA crl; 394 CSSM_DL_DB_HANDLE dlDb; 395 CSSM_DB_UNIQUE_RECORD_PTR record; 396 TPCrlInfo *issuerCrl = NULL; 397 CSSM_DL_DB_LIST_PTR dbList = vfyCtx.dbList; 398 CSSM_RETURN crtn; 399 400 if(dbList == NULL) { 401 return NULL; 402 } 403 for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) { 404 dlDb = dbList->DLDBHandle[dbDex]; 405 crl.Data = NULL; 406 crl.Length = 0; 407 record = tpCrlLookup(dlDb, 408 &issuer, 409 vfyCtx.verifyTime, 410 &resultHand, 411 &crl); 412 /* remember we have to: 413 * -- abort this query regardless, and 414 * -- free the CSSM_DATA crl regardless, and 415 * -- free the unique record if we don't use it 416 * (by placing it in issuerCert)... 417 */ 418 if(record != NULL) { 419 /* Found one */ 420 assert(crl.Data != NULL); 421 issuerCrl = new TPCrlInfo(vfyCtx.clHand, 422 vfyCtx.cspHand, 423 &crl, 424 TIC_CopyData, 425 vfyCtx.verifyTime); 426 /* we're done with raw CRL data */ 427 /* FIXME this assumes that vfyCtx.alloc is the same as the 428 * allocator associated with DlDB...OK? */ 429 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE); 430 crl.Data = NULL; 431 crl.Length = 0; 432 433 /* and we're done with the record */ 434 CSSM_DL_FreeUniqueRecord(dlDb, record); 435 436 /* Does it verify with specified context? */ 437 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert); 438 if(crtn) { 439 440 delete issuerCrl; 441 issuerCrl = NULL; 442 443 /* 444 * Verify fail. Continue searching this DB. Break on 445 * finding the holy grail or no more records found. 446 */ 447 for(;;) { 448 crl.Data = NULL; 449 crl.Length = 0; 450 crtn = CSSM_DL_DataGetNext(dlDb, 451 resultHand, 452 NULL, // no attrs 453 &crl, 454 &record); 455 if(crtn) { 456 /* no more, done with this DB */ 457 assert(crl.Data == NULL); 458 break; 459 } 460 assert(crl.Data != NULL); 461 462 /* found one - is it any good? */ 463 issuerCrl = new TPCrlInfo(vfyCtx.clHand, 464 vfyCtx.cspHand, 465 &crl, 466 TIC_CopyData, 467 vfyCtx.verifyTime); 468 /* we're done with raw CRL data */ 469 /* FIXME this assumes that vfyCtx.alloc is the same as the 470 * allocator associated with DlDB...OK? */ 471 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE); 472 crl.Data = NULL; 473 crl.Length = 0; 474 475 CSSM_DL_FreeUniqueRecord(dlDb, record); 476 477 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert); 478 if(crtn == CSSM_OK) { 479 /* yes! */ 480 break; 481 } 482 delete issuerCrl; 483 issuerCrl = NULL; 484 } /* searching subsequent records */ 485 } /* verify fail */ 486 /* else success! */ 487 488 if(issuerCrl != NULL) { 489 /* successful return */ 490 CSSM_DL_DataAbortQuery(dlDb, resultHand); 491 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record); 492 return issuerCrl; 493 } 494 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ 495 else { 496 assert(crl.Data == NULL); 497 } 498 /* in any case, abort the query for this db */ 499 CSSM_DL_DataAbortQuery(dlDb, resultHand); 500 501 } /* main loop searching dbList */ 502 503 /* issuer not found */ 504 return NULL; 505} 506 507