1/* 2 * Copyright (c) 2002-2009,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. 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 */ 23 24#include <Security/cssmtype.h> 25#include <Security/cssmapi.h> 26#include <security_cdsa_utilities/Schema.h> /* private API */ 27#include <security_keychain/TrustKeychains.h> /* private SecTrustKeychainsGetMutex() */ 28#include <Security/SecCertificatePriv.h> /* private SecInferLabelFromX509Name() */ 29#include <Security/oidscert.h> 30#include "TPDatabase.h" 31#include "tpdebugging.h" 32#include "certGroupUtils.h" 33#include "TPCertInfo.h" 34#include "TPCrlInfo.h" 35#include "tpCrlVerify.h" 36#include "tpTime.h" 37 38 39/* 40 * Given a DL/DB, look up cert by subject name. Subsequent 41 * certs can be found using the returned result handle. 42 */ 43static CSSM_DB_UNIQUE_RECORD_PTR tpCertLookup( 44 CSSM_DL_DB_HANDLE dlDb, 45 const CSSM_DATA *subjectName, // DER-encoded 46 CSSM_HANDLE_PTR resultHand, // RETURNED 47 CSSM_DATA_PTR cert) // RETURNED 48{ 49 CSSM_QUERY query; 50 CSSM_SELECTION_PREDICATE predicate; 51 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 52 53 cert->Data = NULL; 54 cert->Length = 0; 55 56 /* SWAG until cert schema nailed down */ 57 predicate.DbOperator = CSSM_DB_EQUAL; 58 predicate.Attribute.Info.AttributeNameFormat = 59 CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 60 predicate.Attribute.Info.Label.AttributeName = (char*) "Subject"; 61 predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 62 predicate.Attribute.Value = const_cast<CSSM_DATA_PTR>(subjectName); 63 predicate.Attribute.NumberOfValues = 1; 64 65 query.RecordType = CSSM_DL_DB_RECORD_X509_CERTIFICATE; 66 query.Conjunctive = CSSM_DB_NONE; 67 query.NumSelectionPredicates = 1; 68 query.SelectionPredicate = &predicate; 69 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful? 70 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? 71 query.QueryFlags = 0; // FIXME - used? 72 73 CSSM_DL_DataGetFirst(dlDb, 74 &query, 75 resultHand, 76 NULL, // don't fetch attributes 77 cert, 78 &record); 79 80 return record; 81} 82 83/* 84 * Search a list of DBs for a cert which verifies specified subject item. 85 * Just a boolean return - we found it, or not. If we did, we return 86 * TPCertInfo associated with the raw cert. 87 * A true partialIssuerKey on return indicates that caller must deal 88 * with partial public key processing later. 89 * If verifyCurrent is true, we will not return a cert which is not 90 * temporally valid; else we may well do so. 91 */ 92TPCertInfo *tpDbFindIssuerCert( 93 Allocator &alloc, 94 CSSM_CL_HANDLE clHand, 95 CSSM_CSP_HANDLE cspHand, 96 const TPClItemInfo *subjectItem, 97 const CSSM_DL_DB_LIST *dbList, 98 const char *verifyTime, // may be NULL 99 bool &partialIssuerKey) // RETURNED 100{ 101 StLock<Mutex> _(SecTrustKeychainsGetMutex()); 102 103 uint32 dbDex; 104 CSSM_HANDLE resultHand; 105 CSSM_DATA cert; 106 CSSM_DL_DB_HANDLE dlDb; 107 CSSM_DB_UNIQUE_RECORD_PTR record; 108 TPCertInfo *issuerCert = NULL; 109 bool foundIt; 110 TPCertInfo *expiredIssuer = NULL; 111 TPCertInfo *nonRootIssuer = 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 (1) %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 /* 173 * Prefer a root over an intermediate issuer if we can get one 174 * (in case a cross-signed intermediate and root are both available) 175 */ 176 if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) { 177 if(!issuerCert->isSelfSigned()) { 178 /* 179 * Exact value not important here, this just uniquely identifies 180 * this situation in the switch below. 181 */ 182 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)"); 183 crtn = CSSM_CERT_STATUS_IS_ROOT; 184 nonRootIssuer = issuerCert; 185 nonRootIssuer->dlDbHandle(dlDb); 186 nonRootIssuer->uniqueRecord(record); 187 } 188 } 189 switch(crtn) { 190 case CSSM_OK: 191 break; 192 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: 193 partialIssuerKey = true; 194 break; 195 default: 196 if(issuerCert != NULL) { 197 /* either holding onto this cert, or done with it. */ 198 if(crtn != CSSM_CERT_STATUS_EXPIRED && 199 crtn != CSSM_CERT_STATUS_IS_ROOT) { 200 delete issuerCert; 201 CSSM_DL_FreeUniqueRecord(dlDb, record); 202 } 203 issuerCert = NULL; 204 } 205 206 /* 207 * Continue searching this DB. Break on finding the holy 208 * grail or no more records found. 209 */ 210 for(;;) { 211 cert.Data = NULL; 212 cert.Length = 0; 213 record = NULL; 214 CSSM_RETURN crtn = CSSM_DL_DataGetNext(dlDb, 215 resultHand, 216 NULL, // no attrs 217 &cert, 218 &record); 219 if(crtn) { 220 /* no more, done with this DB */ 221 assert(cert.Data == NULL); 222 break; 223 } 224 assert(cert.Data != NULL); 225 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record); 226 227 /* found one - does it verify subject? */ 228 try { 229 issuerCert = new TPCertInfo(clHand, cspHand, &cert, TIC_CopyData, 230 verifyTime); 231 } 232 catch(...) { 233 crtn = CSSMERR_TP_INVALID_CERTIFICATE; 234 } 235 /* we're done with raw cert data */ 236 tpFreePluginMemory(dlDb.DLHandle, cert.Data); 237 cert.Data = NULL; 238 cert.Length = 0; 239 240 if(crtn == CSSM_OK) { 241 crtn = subjectItem->verifyWithIssuer(issuerCert); 242 } 243 244 /* temporal validity check, again */ 245 if((crtn == CSSM_OK) && (expiredIssuer == NULL)) { 246 if(issuerCert->isExpired() || issuerCert->isNotValidYet()) { 247 tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)"); 248 crtn = CSSM_CERT_STATUS_EXPIRED; 249 expiredIssuer = issuerCert; 250 expiredIssuer->dlDbHandle(dlDb); 251 expiredIssuer->uniqueRecord(record); 252 } 253 } 254 /* self-signed check, again */ 255 if((crtn == CSSM_OK) && (nonRootIssuer == NULL)) { 256 if(!issuerCert->isSelfSigned()) { 257 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)"); 258 crtn = CSSM_CERT_STATUS_IS_ROOT; 259 nonRootIssuer = issuerCert; 260 nonRootIssuer->dlDbHandle(dlDb); 261 nonRootIssuer->uniqueRecord(record); 262 } 263 } 264 265 foundIt = false; 266 switch(crtn) { 267 case CSSM_OK: 268 foundIt = true; 269 break; 270 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: 271 partialIssuerKey = true; 272 foundIt = true; 273 break; 274 default: 275 break; 276 } 277 if(foundIt) { 278 /* yes! */ 279 break; 280 } 281 if(issuerCert != NULL) { 282 /* either holding onto this cert, or done with it. */ 283 if(crtn != CSSM_CERT_STATUS_EXPIRED && 284 crtn != CSSM_CERT_STATUS_IS_ROOT) { 285 delete issuerCert; 286 CSSM_DL_FreeUniqueRecord(dlDb, record); 287 } 288 issuerCert = NULL; 289 } 290 } /* searching subsequent records */ 291 } /* switch verify */ 292 293 if(record != NULL) { 294 /* NULL record --> end of search --> DB auto-aborted */ 295 crtn = CSSM_DL_DataAbortQuery(dlDb, resultHand); 296 assert(crtn == CSSM_OK); 297 } 298 if(issuerCert != NULL) { 299 /* successful return */ 300 tpDbDebug("tpDbFindIssuer: returning record %p", record); 301 issuerCert->dlDbHandle(dlDb); 302 issuerCert->uniqueRecord(record); 303 if(expiredIssuer != NULL) { 304 /* We found a replacement */ 305 tpDbDebug("tpDbFindIssuer: discarding expired cert"); 306 expiredIssuer->freeUniqueRecord(); 307 delete expiredIssuer; 308 } 309 /* Avoid deleting the non-root cert if same as expired cert */ 310 if(nonRootIssuer != NULL && nonRootIssuer != expiredIssuer) { 311 /* We found a replacement */ 312 tpDbDebug("tpDbFindIssuer: discarding non-root cert"); 313 nonRootIssuer->freeUniqueRecord(); 314 delete nonRootIssuer; 315 } 316 return issuerCert; 317 } 318 } /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ 319 else { 320 assert(cert.Data == NULL); 321 assert(resultHand == 0); 322 } 323 } /* main loop searching dbList */ 324 325 if(nonRootIssuer != NULL) { 326 /* didn't find root issuer, so use this one */ 327 tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p", 328 nonRootIssuer->uniqueRecord()); 329 if(expiredIssuer != NULL && expiredIssuer != nonRootIssuer) { 330 expiredIssuer->freeUniqueRecord(); 331 delete expiredIssuer; 332 } 333 return nonRootIssuer; 334 } 335 336 if(expiredIssuer != NULL) { 337 /* OK, we'll take this one */ 338 tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p", 339 expiredIssuer->uniqueRecord()); 340 return expiredIssuer; 341 } 342 /* issuer not found */ 343 return NULL; 344} 345 346/* 347 * Given a DL/DB, look up CRL by issuer name and validity time. 348 * Subsequent CRLs can be found using the returned result handle. 349 */ 350#define SEARCH_BY_DATE 1 351 352static CSSM_DB_UNIQUE_RECORD_PTR tpCrlLookup( 353 CSSM_DL_DB_HANDLE dlDb, 354 const CSSM_DATA *issuerName, // DER-encoded 355 CSSM_TIMESTRING verifyTime, // may be NULL, implies "now" 356 CSSM_HANDLE_PTR resultHand, // RETURNED 357 CSSM_DATA_PTR crl) // RETURNED 358{ 359 CSSM_QUERY query; 360 CSSM_SELECTION_PREDICATE pred[3]; 361 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 362 char timeStr[CSSM_TIME_STRLEN + 1]; 363 364 crl->Data = NULL; 365 crl->Length = 0; 366 367 /* Three predicates...first, the issuer name */ 368 pred[0].DbOperator = CSSM_DB_EQUAL; 369 pred[0].Attribute.Info.AttributeNameFormat = 370 CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 371 pred[0].Attribute.Info.Label.AttributeName = (char*) "Issuer"; 372 pred[0].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 373 pred[0].Attribute.Value = const_cast<CSSM_DATA_PTR>(issuerName); 374 pred[0].Attribute.NumberOfValues = 1; 375 376 /* now before/after. Cook up an appropriate time string. */ 377 if(verifyTime != NULL) { 378 /* Caller spec'd tolerate any format */ 379 int rtn = tpTimeToCssmTimestring(verifyTime, (unsigned)strlen(verifyTime), timeStr); 380 if(rtn) { 381 tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n"); 382 return NULL; 383 } 384 } 385 else { 386 /* right now */ 387 StLock<Mutex> _(tpTimeLock()); 388 timeAtNowPlus(0, TIME_CSSM, timeStr); 389 } 390 CSSM_DATA timeData; 391 timeData.Data = (uint8 *)timeStr; 392 timeData.Length = CSSM_TIME_STRLEN; 393 394 #if SEARCH_BY_DATE 395 pred[1].DbOperator = CSSM_DB_LESS_THAN; 396 pred[1].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 397 pred[1].Attribute.Info.Label.AttributeName = (char*) "NextUpdate"; 398 pred[1].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 399 pred[1].Attribute.Value = &timeData; 400 pred[1].Attribute.NumberOfValues = 1; 401 402 pred[2].DbOperator = CSSM_DB_GREATER_THAN; 403 pred[2].Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 404 pred[2].Attribute.Info.Label.AttributeName = (char*) "ThisUpdate"; 405 pred[2].Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_BLOB; 406 pred[2].Attribute.Value = &timeData; 407 pred[2].Attribute.NumberOfValues = 1; 408 #endif 409 410 query.RecordType = CSSM_DL_DB_RECORD_X509_CRL; 411 query.Conjunctive = CSSM_DB_AND; 412 #if SEARCH_BY_DATE 413 query.NumSelectionPredicates = 3; 414 #else 415 query.NumSelectionPredicates = 1; 416 #endif 417 query.SelectionPredicate = pred; 418 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful? 419 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? 420 query.QueryFlags = 0; // FIXME - used? 421 422 CSSM_DL_DataGetFirst(dlDb, 423 &query, 424 resultHand, 425 NULL, // don't fetch attributes 426 crl, 427 &record); 428 return record; 429} 430 431/* 432 * Search a list of DBs for a CRL from the specified issuer and (optional) 433 * TPVerifyContext.verifyTime. 434 * Just a boolean return - we found it, or not. If we did, we return a 435 * TPCrlInfo which has been verified with the specified TPVerifyContext. 436 */ 437TPCrlInfo *tpDbFindIssuerCrl( 438 TPVerifyContext &vfyCtx, 439 const CSSM_DATA &issuer, 440 TPCertInfo &forCert) 441{ 442 StLock<Mutex> _(SecTrustKeychainsGetMutex()); 443 444 uint32 dbDex; 445 CSSM_HANDLE resultHand; 446 CSSM_DATA crl; 447 CSSM_DL_DB_HANDLE dlDb; 448 CSSM_DB_UNIQUE_RECORD_PTR record; 449 TPCrlInfo *issuerCrl = NULL; 450 CSSM_DL_DB_LIST_PTR dbList = vfyCtx.dbList; 451 CSSM_RETURN crtn; 452 453 if(dbList == NULL) { 454 return NULL; 455 } 456 for(dbDex=0; dbDex<dbList->NumHandles; dbDex++) { 457 dlDb = dbList->DLDBHandle[dbDex]; 458 crl.Data = NULL; 459 crl.Length = 0; 460 record = tpCrlLookup(dlDb, 461 &issuer, 462 vfyCtx.verifyTime, 463 &resultHand, 464 &crl); 465 /* remember we have to: 466 * -- abort this query regardless, and 467 * -- free the CSSM_DATA crl regardless, and 468 * -- free the unique record if we don't use it 469 * (by placing it in issuerCert)... 470 */ 471 if(record != NULL) { 472 /* Found one */ 473 assert(crl.Data != NULL); 474 issuerCrl = new TPCrlInfo(vfyCtx.clHand, 475 vfyCtx.cspHand, 476 &crl, 477 TIC_CopyData, 478 vfyCtx.verifyTime); 479 /* we're done with raw CRL data */ 480 /* FIXME this assumes that vfyCtx.alloc is the same as the 481 * allocator associated with DlDB...OK? */ 482 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE); 483 crl.Data = NULL; 484 crl.Length = 0; 485 486 /* and we're done with the record */ 487 CSSM_DL_FreeUniqueRecord(dlDb, record); 488 489 /* Does it verify with specified context? */ 490 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert); 491 if(crtn) { 492 493 delete issuerCrl; 494 issuerCrl = NULL; 495 496 /* 497 * Verify fail. Continue searching this DB. Break on 498 * finding the holy grail or no more records found. 499 */ 500 for(;;) { 501 crl.Data = NULL; 502 crl.Length = 0; 503 crtn = CSSM_DL_DataGetNext(dlDb, 504 resultHand, 505 NULL, // no attrs 506 &crl, 507 &record); 508 if(crtn) { 509 /* no more, done with this DB */ 510 assert(crl.Data == NULL); 511 break; 512 } 513 assert(crl.Data != NULL); 514 515 /* found one - is it any good? */ 516 issuerCrl = new TPCrlInfo(vfyCtx.clHand, 517 vfyCtx.cspHand, 518 &crl, 519 TIC_CopyData, 520 vfyCtx.verifyTime); 521 /* we're done with raw CRL data */ 522 /* FIXME this assumes that vfyCtx.alloc is the same as the 523 * allocator associated with DlDB...OK? */ 524 tpFreeCssmData(vfyCtx.alloc, &crl, CSSM_FALSE); 525 crl.Data = NULL; 526 crl.Length = 0; 527 528 CSSM_DL_FreeUniqueRecord(dlDb, record); 529 530 crtn = issuerCrl->verifyWithContextNow(vfyCtx, &forCert); 531 if(crtn == CSSM_OK) { 532 /* yes! */ 533 break; 534 } 535 delete issuerCrl; 536 issuerCrl = NULL; 537 } /* searching subsequent records */ 538 } /* verify fail */ 539 /* else success! */ 540 541 if(issuerCrl != NULL) { 542 /* successful return */ 543 CSSM_DL_DataAbortQuery(dlDb, resultHand); 544 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record); 545 return issuerCrl; 546 } 547 } /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ 548 else { 549 assert(crl.Data == NULL); 550 } 551 /* in any case, abort the query for this db */ 552 CSSM_DL_DataAbortQuery(dlDb, resultHand); 553 554 } /* main loop searching dbList */ 555 556 /* issuer not found */ 557 return NULL; 558} 559 560