2  * Copyright (c) 2002-2009,2011-2012,2014 Apple Inc. All Rights Reserved. 
   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 
  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. 
  20  * TPDatabase.cpp - TP's DL/DB access functions. 
  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" 
  40  * Given a DL/DB, look up cert by subject name. Subsequent 
  41  * certs can be found using the returned result handle. 
  43 static 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 
  50         CSSM_SELECTION_PREDICATE                predicate
; 
  51         CSSM_DB_UNIQUE_RECORD_PTR               record 
= NULL
; 
  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; 
  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? 
  73         CSSM_DL_DataGetFirst(dlDb
, 
  76                 NULL
,                           // don't fetch attributes 
  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. 
  92 TPCertInfo 
*tpDbFindIssuerCert( 
  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 
 102         StLock
<Mutex
> _(SecTrustKeychainsGetMutex()); 
 105         CSSM_HANDLE                                     resultHand
; 
 107         CSSM_DL_DB_HANDLE                       dlDb
; 
 108         CSSM_DB_UNIQUE_RECORD_PTR       record
; 
 109         TPCertInfo                                      
*issuerCert 
= NULL
; 
 111         TPCertInfo                                      
*expiredIssuer 
= NULL
; 
 112         TPCertInfo                                      
*nonRootIssuer 
= NULL
; 
 114         partialIssuerKey 
= false; 
 118         for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) { 
 119                 dlDb 
= dbList
->DLDBHandle
[dbDex
]; 
 122                 resultHand 
= CSSM_INVALID_HANDLE
; 
 123                 record 
= tpCertLookup(dlDb
, 
 124                         subjectItem
->issuerName(), 
 127                 /* remember we have to: 
 128                  * -- abort this query regardless, and 
 129                  * -- free the CSSM_DATA cert regardless, and 
 130                  * -- free the unique record if we don't use it 
 131                  *    (by placing it in issuerCert)... 
 135                         assert(cert
.Data 
!= NULL
); 
 136                         tpDbDebug("tpDbFindIssuerCert: found cert record (1) %p", record
); 
 138                         CSSM_RETURN crtn 
= CSSM_OK
; 
 140                                 issuerCert 
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
, verifyTime
); 
 143                                 crtn 
= CSSMERR_TP_INVALID_CERTIFICATE
; 
 146                         /* we're done with raw cert data */ 
 147                         tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
); 
 151                         /* Does it verify the subject cert? */ 
 152                         if(crtn 
== CSSM_OK
) { 
 153                                 crtn 
= subjectItem
->verifyWithIssuer(issuerCert
); 
 157                          * Handle temporal invalidity - if so and this is the first one 
 158                          * we've seen, hold on to it while we search for better one. 
 160                         if(crtn 
== CSSM_OK
) { 
 161                                 if(issuerCert
->isExpired() || issuerCert
->isNotValidYet()) { 
 163                                          * Exact value not important here, this just uniquely identifies 
 164                                          * this situation in the switch below. 
 166                                         tpDbDebug("tpDbFindIssuerCert: holding expired cert (1)"); 
 167                                         crtn 
= CSSM_CERT_STATUS_EXPIRED
; 
 168                                         /* Delete old stashed expired issuer */ 
 170                                                 expiredIssuer
->freeUniqueRecord(); 
 171                                                 delete expiredIssuer
; 
 173                                         expiredIssuer 
= issuerCert
; 
 174                                         expiredIssuer
->dlDbHandle(dlDb
); 
 175                                         expiredIssuer
->uniqueRecord(record
); 
 179                          * Prefer a root over an intermediate issuer if we can get one 
 180                          * (in case a cross-signed intermediate and root are both available) 
 182                         if(crtn 
== CSSM_OK 
&& !issuerCert
->isSelfSigned()) { 
 184                                  * Exact value not important here, this just uniquely identifies 
 185                                  * this situation in the switch below. 
 187                                 tpDbDebug("tpDbFindIssuerCert: holding non-root cert (1)"); 
 188                                 crtn 
= CSSM_CERT_STATUS_IS_ROOT
; 
 190                                  * If the old intermediate was temporally invalid, replace it. 
 191                                  * (Regardless of temporal validity of new one we found, because 
 192                                  * as far as this code is concerned they're equivalent.) 
 195                                    (nonRootIssuer 
&& (nonRootIssuer
->isExpired() || nonRootIssuer
->isNotValidYet()))) { 
 197                                                 nonRootIssuer
->freeUniqueRecord(); 
 198                                                 delete nonRootIssuer
; 
 200                                         nonRootIssuer 
= issuerCert
; 
 201                                         nonRootIssuer
->dlDbHandle(dlDb
); 
 202                                         nonRootIssuer
->uniqueRecord(record
); 
 206                                         CSSM_DL_FreeUniqueRecord(dlDb
, record
); 
 211                                 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
: 
 212                                         partialIssuerKey 
= true; 
 215                     if((oldRoot 
== NULL
) || 
 216                        !tp_CompareCerts(issuerCert
->itemData(), oldRoot
->itemData())) { 
 217                         /* We found a new root cert which does not match the old one */ 
 220                     /* else fall through to search for a different one */ 
 222                                         if(issuerCert 
!= NULL
) { 
 223                                                 /* either holding onto this cert, or done with it. */ 
 224                                                 if(crtn 
!= CSSM_CERT_STATUS_EXPIRED 
&& 
 225                                                    crtn 
!= CSSM_CERT_STATUS_IS_ROOT
) { 
 227                                                         CSSM_DL_FreeUniqueRecord(dlDb
, record
); 
 233                                          * Continue searching this DB. Break on finding the holy 
 234                                          * grail or no more records found. 
 240                                                 CSSM_RETURN crtn 
= CSSM_DL_DataGetNext(dlDb
, 
 246                                                         /* no more, done with this DB */ 
 247                                                         assert(cert
.Data 
== NULL
); 
 250                                                 assert(cert
.Data 
!= NULL
); 
 251                                                 tpDbDebug("tpDbFindIssuerCert: found cert record (2) %p", record
); 
 253                                                 /* found one - does it verify subject? */ 
 255                                                         issuerCert 
= new TPCertInfo(clHand
, cspHand
, &cert
, TIC_CopyData
, 
 259                                                         crtn 
= CSSMERR_TP_INVALID_CERTIFICATE
; 
 261                                                 /* we're done with raw cert data */ 
 262                                                 tpFreePluginMemory(dlDb
.DLHandle
, cert
.Data
); 
 266                                                 if(crtn 
== CSSM_OK
) { 
 267                                                         crtn 
= subjectItem
->verifyWithIssuer(issuerCert
); 
 270                                                 /* temporal validity check, again */ 
 271                                                 if(crtn 
== CSSM_OK
) { 
 272                                                         if(issuerCert
->isExpired() || issuerCert
->isNotValidYet()) { 
 273                                                                 tpDbDebug("tpDbFindIssuerCert: holding expired cert (2)"); 
 274                                                                 crtn 
= CSSM_CERT_STATUS_EXPIRED
; 
 275                                                                 /* Delete old stashed expired issuer */ 
 277                                                                         expiredIssuer
->freeUniqueRecord(); 
 278                                                                         delete expiredIssuer
; 
 280                                                                 expiredIssuer 
= issuerCert
; 
 281                                                                 expiredIssuer
->dlDbHandle(dlDb
); 
 282                                                                 expiredIssuer
->uniqueRecord(record
); 
 285                                                 /* self-signed check, again */ 
 286                                                 if(crtn 
== CSSM_OK 
&& !issuerCert
->isSelfSigned()) { 
 287                                                         tpDbDebug("tpDbFindIssuerCert: holding non-root cert (2)"); 
 288                                                         crtn 
= CSSM_CERT_STATUS_IS_ROOT
; 
 290                                                          * If the old intermediate was temporally invalid, replace it. 
 291                                                          * (Regardless of temporal validity of new one we found, because 
 292                                                          * as far as this code is concerned they're equivalent.) 
 295                                                            (nonRootIssuer 
&& (nonRootIssuer
->isExpired() || nonRootIssuer
->isNotValidYet()))) { 
 297                                                                         nonRootIssuer
->freeUniqueRecord(); 
 298                                                                         delete nonRootIssuer
; 
 300                                                                 nonRootIssuer 
= issuerCert
; 
 301                                                                 nonRootIssuer
->dlDbHandle(dlDb
); 
 302                                                                 nonRootIssuer
->uniqueRecord(record
); 
 306                                                                 CSSM_DL_FreeUniqueRecord(dlDb
, record
); 
 314                                 /* duplicate check, again */ 
 315                                 if((oldRoot 
== NULL
) || 
 316                                    !tp_CompareCerts(issuerCert
->itemData(), oldRoot
->itemData())) { 
 320                                                         case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE
: 
 321                                                                 partialIssuerKey 
= true; 
 331                                                 if(issuerCert 
!= NULL
) { 
 332                                                         /* either holding onto this cert, or done with it. */ 
 333                                                         if(crtn 
!= CSSM_CERT_STATUS_EXPIRED 
&& 
 334                                                            crtn 
!= CSSM_CERT_STATUS_IS_ROOT
) { 
 336                                                                 CSSM_DL_FreeUniqueRecord(dlDb
, record
); 
 340                                         } /* searching subsequent records */ 
 341                         }       /* switch verify */ 
 344                                 /* NULL record --> end of search --> DB auto-aborted */ 
 345                                 crtn 
= CSSM_DL_DataAbortQuery(dlDb
, resultHand
); 
 346                                 assert(crtn 
== CSSM_OK
); 
 348                         if(issuerCert 
!= NULL
) { 
 349                                 /* successful return */ 
 350                                 tpDbDebug("tpDbFindIssuer: returning record %p", record
); 
 351                                 issuerCert
->dlDbHandle(dlDb
); 
 352                                 issuerCert
->uniqueRecord(record
); 
 353                                 if(expiredIssuer 
!= NULL
) { 
 354                                         /* We found a replacement */ 
 355                                         tpDbDebug("tpDbFindIssuer: discarding expired cert"); 
 356                                         expiredIssuer
->freeUniqueRecord(); 
 357                                         delete expiredIssuer
; 
 359                                 /* Avoid deleting the non-root cert if same as expired cert */ 
 360                                 if(nonRootIssuer 
!= NULL 
&& nonRootIssuer 
!= expiredIssuer
) { 
 361                                         /* We found a replacement */ 
 362                                         tpDbDebug("tpDbFindIssuer: discarding non-root cert"); 
 363                                         nonRootIssuer
->freeUniqueRecord(); 
 364                                         delete nonRootIssuer
; 
 368                 }       /* tpCertLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ 
 370                         assert(cert
.Data 
== NULL
); 
 371                         assert(resultHand 
== 0); 
 373         }       /* main loop searching dbList */ 
 375         if(nonRootIssuer 
!= NULL
) { 
 376                 /* didn't find root issuer, so use this one */ 
 377                 tpDbDebug("tpDbFindIssuer: taking non-root issuer cert, record %p", 
 378                         nonRootIssuer
->uniqueRecord()); 
 379                 if(expiredIssuer 
!= NULL 
&& expiredIssuer 
!= nonRootIssuer
) { 
 380                         expiredIssuer
->freeUniqueRecord(); 
 381                         delete expiredIssuer
; 
 383                 return nonRootIssuer
; 
 386         if(expiredIssuer 
!= NULL
) { 
 387                 /* OK, we'll take this one */ 
 388                 tpDbDebug("tpDbFindIssuer: taking expired cert after all, record %p", 
 389                         expiredIssuer
->uniqueRecord()); 
 390                 return expiredIssuer
; 
 392         /* issuer not found */ 
 397  * Given a DL/DB, look up CRL by issuer name and validity time. 
 398  * Subsequent CRLs can be found using the returned result handle. 
 400 #define SEARCH_BY_DATE          1 
 402 static CSSM_DB_UNIQUE_RECORD_PTR 
tpCrlLookup( 
 403         CSSM_DL_DB_HANDLE       dlDb
, 
 404         const CSSM_DATA         
*issuerName
,    // DER-encoded 
 405         CSSM_TIMESTRING         verifyTime
,             // may be NULL, implies "now" 
 406         CSSM_HANDLE_PTR         resultHand
,             // RETURNED 
 407         CSSM_DATA_PTR           crl
)                    // RETURNED 
 410         CSSM_SELECTION_PREDICATE                pred
[3]; 
 411         CSSM_DB_UNIQUE_RECORD_PTR               record 
= NULL
; 
 412         char                                                    timeStr
[CSSM_TIME_STRLEN 
+ 1]; 
 417         /* Three predicates...first, the issuer name */ 
 418         pred
[0].DbOperator 
= CSSM_DB_EQUAL
; 
 419         pred
[0].Attribute
.Info
.AttributeNameFormat 
= 
 420                 CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
 421         pred
[0].Attribute
.Info
.Label
.AttributeName 
= (char*) "Issuer"; 
 422         pred
[0].Attribute
.Info
.AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_BLOB
; 
 423         pred
[0].Attribute
.Value 
= const_cast<CSSM_DATA_PTR
>(issuerName
); 
 424         pred
[0].Attribute
.NumberOfValues 
= 1; 
 426         /* now before/after. Cook up an appropriate time string. */ 
 427         if(verifyTime 
!= NULL
) { 
 428                 /* Caller spec'd tolerate any format */ 
 429                 int rtn 
= tpTimeToCssmTimestring(verifyTime
, (unsigned)strlen(verifyTime
), timeStr
); 
 431                         tpErrorLog("tpCrlLookup: Invalid VerifyTime string\n"); 
 437                 StLock
<Mutex
> _(tpTimeLock()); 
 438                 timeAtNowPlus(0, TIME_CSSM
, timeStr
); 
 441         timeData
.Data 
= (uint8 
*)timeStr
; 
 442         timeData
.Length 
= CSSM_TIME_STRLEN
; 
 445         pred
[1].DbOperator 
= CSSM_DB_LESS_THAN
; 
 446         pred
[1].Attribute
.Info
.AttributeNameFormat 
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
 447         pred
[1].Attribute
.Info
.Label
.AttributeName 
= (char*) "NextUpdate"; 
 448         pred
[1].Attribute
.Info
.AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_BLOB
; 
 449         pred
[1].Attribute
.Value 
= &timeData
; 
 450         pred
[1].Attribute
.NumberOfValues 
= 1; 
 452         pred
[2].DbOperator 
= CSSM_DB_GREATER_THAN
; 
 453         pred
[2].Attribute
.Info
.AttributeNameFormat 
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
 454         pred
[2].Attribute
.Info
.Label
.AttributeName 
= (char*) "ThisUpdate"; 
 455         pred
[2].Attribute
.Info
.AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_BLOB
; 
 456         pred
[2].Attribute
.Value 
= &timeData
; 
 457         pred
[2].Attribute
.NumberOfValues 
= 1; 
 460         query
.RecordType 
= CSSM_DL_DB_RECORD_X509_CRL
; 
 461         query
.Conjunctive 
= CSSM_DB_AND
; 
 463         query
.NumSelectionPredicates 
= 3; 
 465         query
.NumSelectionPredicates 
= 1; 
 467         query
.SelectionPredicate 
= pred
; 
 468         query
.QueryLimits
.TimeLimit 
= 0;        // FIXME - meaningful? 
 469         query
.QueryLimits
.SizeLimit 
= 1;        // FIXME - meaningful? 
 470         query
.QueryFlags 
= 0;                           // FIXME - used? 
 472         CSSM_DL_DataGetFirst(dlDb
, 
 475                 NULL
,                           // don't fetch attributes 
 482  * Search a list of DBs for a CRL from the specified issuer and (optional) 
 483  * TPVerifyContext.verifyTime. 
 484  * Just a boolean return - we found it, or not. If we did, we return a 
 485  * TPCrlInfo which has been verified with the specified TPVerifyContext. 
 487 TPCrlInfo 
*tpDbFindIssuerCrl( 
 488         TPVerifyContext         
&vfyCtx
, 
 489         const CSSM_DATA         
&issuer
, 
 492         StLock
<Mutex
> _(SecTrustKeychainsGetMutex()); 
 495         CSSM_HANDLE                                     resultHand
; 
 497         CSSM_DL_DB_HANDLE                       dlDb
; 
 498         CSSM_DB_UNIQUE_RECORD_PTR       record
; 
 499         TPCrlInfo                                       
*issuerCrl 
= NULL
; 
 500         CSSM_DL_DB_LIST_PTR             dbList 
= vfyCtx
.dbList
; 
 506         for(dbDex
=0; dbDex
<dbList
->NumHandles
; dbDex
++) { 
 507                 dlDb 
= dbList
->DLDBHandle
[dbDex
]; 
 510                 resultHand 
= CSSM_INVALID_HANDLE
; 
 511                 record 
= tpCrlLookup(dlDb
, 
 516                 /* remember we have to: 
 517                  * -- abort this query regardless, and 
 518                  * -- free the CSSM_DATA crl regardless, and 
 519                  * -- free the unique record if we don't use it 
 520                  *    (by placing it in issuerCert)... 
 524                         assert(crl
.Data 
!= NULL
); 
 525                         issuerCrl 
= new TPCrlInfo(vfyCtx
.clHand
, 
 530                         /* we're done with raw CRL data */ 
 531                         /* FIXME this assumes that vfyCtx.alloc is the same as the 
 532                          * allocator associated with DlDB...OK? */ 
 533                         tpFreeCssmData(vfyCtx
.alloc
, &crl
, CSSM_FALSE
); 
 537                         /* and we're done with the record */ 
 538                         CSSM_DL_FreeUniqueRecord(dlDb
, record
); 
 540                         /* Does it verify with specified context? */ 
 541                         crtn 
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
); 
 548                                  * Verify fail. Continue searching this DB. Break on 
 549                                  * finding the holy grail or no more records found. 
 554                                         crtn 
= CSSM_DL_DataGetNext(dlDb
, 
 560                                                 /* no more, done with this DB */ 
 561                                                 assert(crl
.Data 
== NULL
); 
 564                                         assert(crl
.Data 
!= NULL
); 
 566                                         /* found one - is it any good? */ 
 567                                         issuerCrl 
= new TPCrlInfo(vfyCtx
.clHand
, 
 572                                         /* we're done with raw CRL data */ 
 573                                         /* FIXME this assumes that vfyCtx.alloc is the same as the 
 574                                         * allocator associated with DlDB...OK? */ 
 575                                         tpFreeCssmData(vfyCtx
.alloc
, &crl
, CSSM_FALSE
); 
 579                                         CSSM_DL_FreeUniqueRecord(dlDb
, record
); 
 581                                         crtn 
= issuerCrl
->verifyWithContextNow(vfyCtx
, &forCert
); 
 582                                         if(crtn 
== CSSM_OK
) { 
 588                                 } /* searching subsequent records */ 
 592                         if(issuerCrl 
!= NULL
) { 
 593                                 /* successful return */ 
 594                                 CSSM_DL_DataAbortQuery(dlDb
, resultHand
); 
 595                                 tpDebug("tpDbFindIssuerCrl: found CRL record %p", record
); 
 598                 }       /* tpCrlLookup, i.e., CSSM_DL_DataGetFirst, succeeded */ 
 600                         assert(crl
.Data 
== NULL
); 
 602                 /* in any case, abort the query for this db */ 
 603                 CSSM_DL_DataAbortQuery(dlDb
, resultHand
); 
 605         }       /* main loop searching dbList */ 
 607         /* issuer not found */