]> git.saurik.com Git - apple/security.git/blob - CrlRefresh/crlRefresh.cpp
5ddf4af0bc119766ebde305f317b3782c07cd2a2
[apple/security.git] / CrlRefresh / crlRefresh.cpp
1 /*
2 * Copyright (c) 2003 Apple Computer, 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
7 * obtain a copy of the License at http://www.apple.com/publicsource and
8 * 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 and
16 * limitations under the License.
17 */
18
19 /*
20 * Examine the CRLs in the system CRL cache, looking for expired CRLs
21 * for which we don't have current valid entries. Perform net fetch for
22 * all such entries to get up-to-date entries. Purge entries older
23 * than specified date (i.e., "stale" CRLs).
24 *
25 * Terminology used here:
26 *
27 * 'nowTime' is the absolute current time.
28 * 'updateTime' is the time at which we evaluate a CRL's NextUpdate
29 * attribute to determine whether a CRL has expired. This is
30 * generally subsequent to nowTime.
31 * 'expired' means that a CRL's NextUpdate time has passed, relative
32 * to updateTime, and that we need to fetch a new CRL to replace
33 * the expired CRL.
34 * 'expireOverlap' is (nowTime - updateTime) in seconds. It's the
35 * distance into the future at which we evaluate a CRL's expiration
36 * status.
37 * 'stale' means that a CRL is so old that it should be deleted from
38 * the cache.
39 * 'staleTime' is maximum age (relative to nowTime) that a CRL can
40 * achieve in cache before being deemed stale. StaleTime is always
41 * greater than expireOverlap (i.e., if a CRL is stale, it MUST be
42 * expired, but a CRL can be expired without being stale).
43 *
44 * CRLs are only deleted from cache if they are stale; multiple
45 * CRLs from one CA may exist in cache at a given time but (generally)
46 * only one of them is not expired.
47 *
48 * expireOverlap and staleTime have defaults which can be overridden
49 * via command line arguments.
50 */
51
52 #include <stdlib.h>
53 #include <stdio.h>
54 #include <sys/types.h>
55 #include <sys/stat.h>
56 #include <CdsaUtils/cuCdsaUtils.h>
57 #include <CdsaUtils/cuTimeStr.h>
58 #include <CdsaUtils/cuDbUtils.h>
59 #include <CdsaUtils/cuFileIo.h>
60 #include <strings.h>
61 #include "ldapFetch.h"
62 #include <Security/keychainacl.h>
63 #include <Security/cssmacl.h>
64 #include <Security/aclclient.h>
65 #include <Security/cssmdata.h>
66 #include <Security/SecTrust.h>
67
68 #define DEFAULT_STALE_DAYS 10
69 #define DEFAULT_EXPIRE_OVERLAP_SECONDS 3600
70
71 #define SECONDS_PER_DAY (60 * 60 * 24)
72
73 #define CRL_CACHE_DB "/var/db/crls/crlcache.db"
74 #define X509_CERT_DB "/System/Library/Keychains/X509Certificates"
75
76 #ifdef NDEBUG
77 #define DEBUG_PRINT 0
78 #else
79 #define DEBUG_PRINT 1
80 #endif
81
82 #if DEBUG_PRINT
83 #define dprintf(args...) fprintf(stderr, args)
84 #else
85 #define dprintf(args...)
86 #endif
87
88 static void usage(char **argv)
89 {
90 printf("Usage\n");
91 printf("Refresh : %s r [options]\n", argv[0]);
92 printf("Fetch CRL : %s f URI [options]\n", argv[0]);
93 printf("Fetch cert : %s F URI [options]\n", argv[0]);
94 printf("Refresh options:\n");
95 printf(" s=stale_period in DAYS; default=%d\n", DEFAULT_STALE_DAYS);
96 printf(" o=expire_overlap in SECONDS; default=%d\n",
97 DEFAULT_EXPIRE_OVERLAP_SECONDS);
98 printf(" p (Purge all entries, ensuring refresh with fresh CRLs)\n");
99 printf(" f (Full crypto CRL verification)\n");
100 printf(" k=keychainName (default=%s\n", CRL_CACHE_DB);
101 printf(" v(erbose)\n");
102 printf("Fetch options:\n");
103 printf(" F=outFileName (default is stdout)\n");
104 printf(" n (no write to cache after fetch)\n");
105 exit(1);
106 }
107
108 /*
109 * Print string. Null terminator is not assumed.
110 */
111 static void printString(
112 const CSSM_DATA *str)
113 {
114 unsigned i;
115 char *cp = (char *)str->Data;
116 for(i=0; i<str->Length; i++) {
117 printf("%c", *cp++);
118 }
119 }
120
121 /* declare a CSSM_DB_ATTRIBUTE_INFO with NAME_AS_STRING */
122 #define DB_ATTRIBUTE(name, type) \
123 { CSSM_DB_ATTRIBUTE_NAME_AS_STRING, \
124 {#name}, \
125 CSSM_DB_ATTRIBUTE_FORMAT_ ## type \
126 }
127
128 /* The CRL DB attributes we care about*/
129 /* Keep these positions in sync with ATTR_DEX_xxx, below */
130 static const CSSM_DB_ATTRIBUTE_INFO x509CrlRecordAttrs[] = {
131 DB_ATTRIBUTE(CrlType, UINT32), // 0
132 DB_ATTRIBUTE(CrlEncoding, UINT32), // 1
133 DB_ATTRIBUTE(PrintName, BLOB), // 2
134 DB_ATTRIBUTE(Issuer, BLOB), // 3
135 DB_ATTRIBUTE(NextUpdate, BLOB), // 4
136 DB_ATTRIBUTE(URI, BLOB), // 5
137
138 /* we don't use these */
139 // DB_ATTRIBUTE(ThisUpdate, BLOB), // 4
140 // DB_ATTRIBUTE(DeltaCrlNumber, UINT32)
141 // DB_ATTRIBUTE(Alias, BLOB),
142 // DB_ATTRIBUTE(CrlNumber, UINT32),
143 };
144
145 #define NUM_CRL_ATTRS \
146 (sizeof(x509CrlRecordAttrs) / sizeof(x509CrlRecordAttrs[0]))
147
148 #define ATTR_DEX_CRL_TYPE 0
149 #define ATTR_DEX_CRL_ENC 1
150 #define ATTR_DEX_PRINT_NAME 2
151 #define ATTR_DEX_ISSUER 3
152 #define ATTR_DEX_NEXT_UPDATE 4
153 #define ATTR_DEX_URI 5
154
155 /* free attribute(s) allocated by DL */
156 static void freeAttrs(
157 CSSM_DB_ATTRIBUTE_DATA *attrs,
158 unsigned numAttrs)
159 {
160 unsigned i;
161
162 for(i=0; i<numAttrs; i++) {
163 CSSM_DB_ATTRIBUTE_DATA_PTR attrData = &attrs[i];
164 unsigned j;
165 for(j=0; j<attrData->NumberOfValues; j++) {
166 CSSM_DATA_PTR data = &attrData->Value[j];
167 if(data == NULL) {
168 /* fault of DL, who said there was a value here */
169 printf("***freeAttrs screwup: NULL data\n");
170 return;
171 }
172 APP_FREE(data->Data);
173 data->Data = NULL;
174 data->Length = 0;
175 }
176 APP_FREE(attrData->Value);
177 attrData->Value = NULL;
178 }
179 }
180
181 /*
182 * Compare two CSSM_TIMESTRINGs. Returns:
183 * -1 if t1 < t2
184 * 0 if t1 == t2
185 * 1 if t1 > t2
186 */
187 int compareTimes(
188 const char *t1,
189 const char *t2)
190 {
191 for(unsigned dex=0; dex<CSSM_TIME_STRLEN; dex++, t1++, t2++) {
192 if(*t1 > *t2) {
193 return 1;
194 }
195 if(*t1 < *t2) {
196 return -1;
197 }
198 /* else same, on to next byte */
199 }
200 /* equal */
201 return 0;
202 }
203
204 /*
205 * everything we know or care about a CRL.
206 */
207 class CrlInfo
208 {
209 public:
210 CrlInfo(
211 CSSM_DL_DB_HANDLE dlDbHand,
212 CSSM_DB_ATTRIBUTE_DATA *attrData, // [NUM_CRL_ATTRS]
213 CSSM_DB_UNIQUE_RECORD_PTR record,
214 CSSM_DATA_PTR crlBlob); // optional
215 ~CrlInfo();
216
217 CSSM_DATA_PTR fetchValidAttr(
218 unsigned attrDex);
219 int fetchIntAttr(
220 unsigned dex,
221 uint32 &rtn);
222
223 bool isSameIssuer(
224 CrlInfo *other);
225
226 /* print the printable name + '\n' to stdout */
227 void printName();
228
229 void validateTimes(
230 const char *updateTime,
231 const char *staleTime,
232 unsigned dex);
233
234 /* state inferred from attributes, and maintained by
235 * owner (not by us) */
236 bool mIsBadlyFormed; // general parse error
237 bool mIsExpired; // compare to 'now'
238 bool mIsStale; // compared to "staleTime'
239 bool mRefreshed; // already refreshed
240
241 /*
242 * Actual CRL, optionally fetched from DB if doing a full crypto verify
243 */
244 CSSM_DATA mCrlBlob;
245
246
247 /* accessors for read-only member vars */
248 CSSM_DL_DB_HANDLE dlDbHand() { return mDlDbHand; }
249 CSSM_DB_ATTRIBUTE_DATA_PTR attrData() { return &mAttrData[0]; }
250 CSSM_DB_UNIQUE_RECORD_PTR record() { return mRecord; };
251
252 private:
253 /* member variables which are read-only subsequent to construction */
254 CSSM_DL_DB_HANDLE mDlDbHand;
255
256 /*
257 * array of attr data
258 * contents APP_MALLOCd by DL
259 * contents APP_FREEd by our destructor
260 */
261 CSSM_DB_ATTRIBUTE_DATA mAttrData[NUM_CRL_ATTRS];
262
263 /*
264 * For possible use in CSSM_DL_DataDelete
265 * Our destructor does CSSM_DL_FreeUniqueRecord
266 */
267 CSSM_DB_UNIQUE_RECORD_PTR mRecord;
268 };
269
270 CrlInfo::CrlInfo(
271 CSSM_DL_DB_HANDLE dlDbHand,
272 CSSM_DB_ATTRIBUTE_DATA *attrData, // [NUM_CRL_ATTRS]
273 CSSM_DB_UNIQUE_RECORD_PTR record,
274 CSSM_DATA_PTR crlBlob) // optional
275 : mIsBadlyFormed(false),
276 mIsExpired(false),
277 mIsStale(false),
278 mRefreshed(false),
279 mDlDbHand(dlDbHand),
280 mRecord(record)
281 {
282 if(crlBlob) {
283 mCrlBlob = *crlBlob;
284 }
285 else {
286 mCrlBlob.Data = NULL;
287 mCrlBlob.Length = 0;
288 }
289 memmove(mAttrData, attrData,
290 sizeof(CSSM_DB_ATTRIBUTE_DATA) * NUM_CRL_ATTRS);
291 }
292
293 CrlInfo::~CrlInfo()
294 {
295 freeAttrs(&mAttrData[0], NUM_CRL_ATTRS);
296 CSSM_DL_FreeUniqueRecord(mDlDbHand, mRecord);
297 if(mCrlBlob.Data) {
298 APP_FREE(mCrlBlob.Data);
299 }
300 }
301
302 /*
303 * Is attribute at specified index present with one value? Returns the
304 * value if so, else returns NULL.
305 */
306 CSSM_DATA_PTR CrlInfo::fetchValidAttr(
307 unsigned attrDex)
308 {
309 if(mAttrData[attrDex].NumberOfValues != 1) {
310 return NULL;
311 }
312 return mAttrData[attrDex].Value;
313 }
314
315 /*
316 * Fetch uint32 attr if it's there at specified attr index.
317 * Returns non zero if it's not there and flags the CRL as bad.
318 */
319 int CrlInfo::fetchIntAttr(
320 unsigned dex,
321 uint32 &rtn)
322 {
323 CSSM_DATA *val = fetchValidAttr(dex);
324 if((val == NULL) || (val->Length != sizeof(uint32))) {
325 dprintf("***Badly formed uint32 attr at dex %u\n", dex);
326 mIsBadlyFormed = true;
327 return 1;
328 }
329 rtn = cuDER_ToInt(val);
330 return 0;
331 }
332
333
334 /*
335 * See if two CRLs have same issuer. Requires (and verifies) that both
336 * issuer attrs are well formed.
337 */
338 bool CrlInfo::isSameIssuer(
339 CrlInfo *other)
340 {
341 CSSM_DATA_PTR thisIssuer = fetchValidAttr(ATTR_DEX_ISSUER);
342 if(thisIssuer == NULL) {
343 return false;
344 }
345 CSSM_DATA_PTR otherIssuer = other->fetchValidAttr(ATTR_DEX_ISSUER);
346 if(otherIssuer == NULL) {
347 return false;
348 }
349 return cuCompareCssmData(thisIssuer, otherIssuer) ? true : false;
350 }
351
352 /* Print a CRL's PrintName attr */
353 void CrlInfo::printName()
354 {
355 CSSM_DATA_PTR val = fetchValidAttr(ATTR_DEX_PRINT_NAME);
356 if(val == NULL) {
357 printf("X509 CRL\n");
358 }
359 else {
360 printString(val);
361 printf("\n");
362 }
363 }
364
365 /*
366 * Given time strings representing 'update time' and 'stale time',
367 * calculate mIsExpired and mIsStale.
368 */
369 void CrlInfo::validateTimes(
370 const char *updateTime, // now - expireOverlap
371 const char *staleTime, // now - staleTime
372 unsigned dex) // for debug info
373 {
374 CSSM_DATA *nextUpdateData = fetchValidAttr(ATTR_DEX_NEXT_UPDATE);
375 if((nextUpdateData == NULL) ||
376 (nextUpdateData->Length != CSSM_TIME_STRLEN)) {
377 printf("***Badly formed NextUpdate attr on CRL %u\n", dex);
378 mIsBadlyFormed = true;
379 return;
380 }
381 #if DEBUG_PRINT
382 printf("Crl %u NextUpdate : ", dex); printString(nextUpdateData);
383 printf("\n");
384 #endif
385 char *nextUpdate = (char *)nextUpdateData->Data;
386 if(compareTimes(nextUpdate, updateTime) < 0) {
387 dprintf("...CRL %u is expired\n", dex);
388 mIsExpired = true;
389 if(compareTimes(nextUpdate, staleTime) < 0) {
390 dprintf("...CRL %u is stale\n", dex);
391 mIsStale = true;
392 }
393 /* note it can't be stale and not expired */
394 }
395 }
396
397 /*
398 * Fetch attrs for all CRLs from DB. CRL blobs themselves are not fetched
399 * unless the fetchBlobs argument is asserted.
400 */
401 static CSSM_RETURN fetchAllCrls(
402 CSSM_DL_DB_HANDLE dlDbHand,
403 bool fetchBlobs, // fetch actual CRL data
404 CrlInfo **&rtnCrlInfo, // RETURNED
405 unsigned &numCrls) // RETURNED
406 {
407 CSSM_QUERY query;
408 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
409 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
410 CSSM_RETURN crtn;
411 CSSM_HANDLE resultHand;
412 unsigned attrDex;
413 CSSM_DB_ATTRIBUTE_DATA attrData[NUM_CRL_ATTRS];
414 CSSM_DATA_PTR crlDataPtr = NULL;
415 CSSM_DATA crlData;
416
417 numCrls = 0;
418 rtnCrlInfo = NULL;
419
420 /* build an ATTRIBUTE_DATA array from list attrs */
421 memset(attrData, 0, sizeof(CSSM_DB_ATTRIBUTE_DATA) * NUM_CRL_ATTRS);
422 for(attrDex=0; attrDex<NUM_CRL_ATTRS; attrDex++) {
423 attrData[attrDex].Info = x509CrlRecordAttrs[attrDex];
424 }
425
426 recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_X509_CRL;
427 recordAttrs.NumberOfAttributes = NUM_CRL_ATTRS;
428 recordAttrs.AttributeData = &attrData[0];
429
430 /* just search by recordType, no predicates */
431 query.RecordType = CSSM_DL_DB_RECORD_X509_CRL;
432 query.Conjunctive = CSSM_DB_NONE;
433 query.NumSelectionPredicates = 0;
434 query.SelectionPredicate = NULL;
435 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
436 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
437 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
438
439 if(fetchBlobs) {
440 crlDataPtr = &crlData;
441 }
442
443 crtn = CSSM_DL_DataGetFirst(dlDbHand,
444 &query,
445 &resultHand,
446 &recordAttrs,
447 crlDataPtr,
448 &record);
449 switch(crtn) {
450 case CSSM_OK:
451 break; // proceed
452 case CSSMERR_DL_ENDOFDATA:
453 /* we're done here */
454 return CSSM_OK;
455 case CSSMERR_DL_INVALID_RECORDTYPE:
456 /* this means that this keychain hasn't been initialized
457 * for CRL schema; treat it as empty. */
458 return CSSM_OK;
459 default:
460 cuPrintError("DataGetFirst", crtn);
461 return crtn;
462 }
463
464 /* Cook up a CrlInfo, add it to outgoing array */
465 CrlInfo *crlInfo = new CrlInfo(dlDbHand, &attrData[0], record, crlDataPtr);
466 rtnCrlInfo = (CrlInfo **)malloc(sizeof(CrlInfo*));
467 rtnCrlInfo[0] = crlInfo;
468 numCrls++;
469
470 /* now the rest of them */
471 for(;;) {
472 crtn = CSSM_DL_DataGetNext(dlDbHand,
473 resultHand,
474 &recordAttrs,
475 crlDataPtr,
476 &record);
477 switch(crtn) {
478 case CSSM_OK:
479 rtnCrlInfo = (CrlInfo **)realloc(rtnCrlInfo,
480 sizeof(CrlInfo *) * (numCrls + 1));
481 rtnCrlInfo[numCrls] = new CrlInfo(dlDbHand, &attrData[0], record,
482 crlDataPtr);
483 numCrls++;
484 break; // and go again
485 case CSSMERR_DL_ENDOFDATA:
486 /* normal termination */
487 return CSSM_OK;
488 default:
489 cuPrintError("DataGetNext", crtn);
490 return crtn;
491 }
492 }
493 /* not reached */
494 }
495
496 /*
497 * Validate each CRL's integrity (Not including expiration or stale time).
498 */
499 static void validateCrls(
500 CrlInfo **crlInfo,
501 unsigned numCrls,
502 bool verbose)
503 {
504 CrlInfo *crl;
505
506 for(unsigned dex=0; dex<numCrls; dex++) {
507 crl = crlInfo[dex];
508
509 /* get CrlType, make sure it's acceptable */
510 uint32 i;
511
512 if(crl->fetchIntAttr(ATTR_DEX_CRL_TYPE, i)) {
513 continue;
514 }
515 switch(i) {
516 case CSSM_CRL_TYPE_X_509v1:
517 case CSSM_CRL_TYPE_X_509v2:
518 /* OK */
519 break;
520 default:
521 printf("***bad CRL type (%u) on CRL %u\n", (unsigned)i, dex);
522 crl->mIsBadlyFormed = true;
523 continue;
524 }
525
526 /* ditto for encoding */
527 if(crl->fetchIntAttr(ATTR_DEX_CRL_ENC, i)) {
528 continue;
529 }
530 switch(i) {
531 case CSSM_CRL_ENCODING_BER:
532 case CSSM_CRL_ENCODING_DER:
533 /* OK */
534 break;
535 default:
536 printf("***bad CRL encoding (%u) on CRL %u\n",
537 (unsigned)i, dex);
538 crl->mIsBadlyFormed = true;
539 continue;
540 }
541 /* any other grounds for deletion? */
542 }
543 }
544
545 /*
546 * Perform full crypto CRL validation.
547 * We use the system-wide intermediate cert keychain here, but do
548 * NOT use the CRL cache we're working on (or any other), since
549 * we dont' really want to trust anything at this point.
550 */
551 static void cryptoValidateCrls(
552 CrlInfo **crlInfo,
553 unsigned numCrls,
554 bool verbose,
555 CSSM_TP_HANDLE tpHand,
556 CSSM_CSP_HANDLE cspHand,
557 CSSM_CL_HANDLE clHand,
558 CSSM_DL_HANDLE dlHand)
559 {
560 CrlInfo *crl;
561 const CSSM_DATA *anchors;
562 uint32 anchorCount;
563 OSStatus ortn;
564
565 /* just snag these once */
566 ortn = SecTrustGetCSSMAnchorCertificates(&anchors, &anchorCount);
567 if(ortn) {
568 printf("SecTrustGetCSSMAnchorCertificates returned %u\n", (int)ortn);
569 return;
570 }
571
572 /* and the system-wide intermediate certs */
573 CSSM_DL_DB_HANDLE certDb;
574 CSSM_DL_DB_HANDLE_PTR certDbPtr = NULL;
575 CSSM_RETURN crtn = CSSM_DL_DbOpen(dlHand,
576 X509_CERT_DB,
577 NULL, // DbLocation
578 CSSM_DB_ACCESS_READ,
579 NULL, // CSSM_ACCESS_CREDENTIALS *AccessCred
580 NULL, // void *OpenParameters
581 &certDb.DBHandle);
582 if(crtn) {
583 cuPrintError("CSSM_DL_DbOpen", crtn);
584 printf("***Error opening intermediate cert file %s.\n", X509_CERT_DB);
585 /* Oh well, keep trying */
586 }
587 else {
588 certDb.DLHandle = dlHand;
589 certDbPtr = &certDb;
590 }
591
592 for(unsigned dex=0; dex<numCrls; dex++) {
593 crl = crlInfo[dex];
594 crtn = cuCrlVerify(tpHand, clHand, cspHand,
595 &crl->mCrlBlob,
596 certDbPtr,
597 anchors,
598 anchorCount);
599 switch(crtn) {
600 case CSSMERR_APPLETP_CRL_EXPIRED:
601 /* special case, we'll handle this via its attrs */
602 case CSSM_OK:
603 break;
604 default:
605 if(verbose) {
606 printf("...CRL %u FAILED crypto verify\n", dex);
607 }
608 crl->mIsBadlyFormed = true;
609 break;
610 }
611 }
612 CSSM_DL_DbClose(certDb);
613 }
614
615 /*
616 * Calculate expired/stale state for all CRLs.
617 */
618 int calcCurrent(
619 CrlInfo **crlInfo,
620 unsigned numCrls,
621 int expireOverlapSeconds,
622 int staleTimeSeconds)
623 {
624 if(expireOverlapSeconds > staleTimeSeconds) {
625 printf("***ExpireOverlap greater than StaleTime; aborting.\n");
626 return 1;
627 }
628 char *updateTime = cuTimeAtNowPlus(expireOverlapSeconds, TIME_CSSM);
629 char *staleTime = cuTimeAtNowPlus(-staleTimeSeconds, TIME_CSSM);
630
631 dprintf("updateTime : %s\n", updateTime);
632 dprintf("staleTime : %s\n", staleTime);
633
634 for(unsigned dex=0; dex<numCrls; dex++) {
635 crlInfo[dex]->validateTimes(updateTime, staleTime, dex);
636 }
637 APP_FREE(updateTime);
638 APP_FREE(staleTime);
639 return 0;
640 }
641
642 /*
643 * Mark all CRLs as stale (i.e., force them to be deleted later).
644 */
645 static void purgeAllCrls(
646 CrlInfo **crlInfo,
647 unsigned numCrls,
648 bool verbose)
649 {
650 for(unsigned dex=0; dex<numCrls; dex++) {
651 CrlInfo *crl = crlInfo[dex];
652 crl->mIsExpired = true;
653 crl->mIsStale = true;
654 }
655 }
656
657 /*
658 * Delete all stale and badly formed CRLs from cache.
659 */
660 static void deleteBadCrls(
661 CrlInfo **crlInfo,
662 unsigned numCrls,
663 bool verbose)
664 {
665 CrlInfo *crl;
666
667 for(unsigned dex=0; dex<numCrls; dex++) {
668 crl = crlInfo[dex];
669
670 /*
671 * expired is not grounds for deletion; mIsStale is.
672 */
673 if(crl->mIsBadlyFormed || crl->mIsStale) {
674 if(verbose || DEBUG_PRINT) {
675 printf("...deleting CRL %u from ", dex);
676 crl->printName();
677 }
678 CSSM_RETURN crtn = CSSM_DL_DataDelete(crl->dlDbHand(),
679 crl->record());
680 if(crtn) {
681 cuPrintError("CSSM_DL_DataDelete", crtn);
682 }
683 }
684 }
685 }
686
687 /*
688 * For each expired CRL, fetch a new one if we don't have a current
689 * CRL from the same place.
690 */
691 static void refreshExpiredCrls(
692 CrlInfo **crlInfo,
693 unsigned numCrls,
694 CSSM_CL_HANDLE clHand,
695 bool verbose)
696 {
697 CrlInfo *crl;
698 bool haveCurrent;
699 CSSM_DATA newCrl;
700
701 for(unsigned dex=0; dex<numCrls; dex++) {
702 crl = crlInfo[dex];
703
704 if(!crl->mIsExpired || crl->mRefreshed) {
705 continue;
706 }
707
708 /* do we have one for the same issuer that's current? */
709 haveCurrent = false;
710 for(unsigned i=0; i<numCrls; i++) {
711 if(i == dex) {
712 /* skip identity */
713 continue;
714 }
715 CrlInfo *checkCrl = crlInfo[i];
716 if(checkCrl->mIsBadlyFormed) {
717 /* forget this one */
718 continue;
719 }
720 if(checkCrl->mIsExpired && !checkCrl->mRefreshed) {
721 continue;
722 }
723 if(crl->isSameIssuer(checkCrl)) {
724 /* have a match; this one's OK */
725 dprintf("up-to-date CRL at dex %u matching expired CRL %u\n",
726 i, dex);
727 haveCurrent = true;
728 break;
729 }
730 }
731 if(haveCurrent) {
732 continue;
733 }
734
735 /*
736 * Not all CRLs have a URI attribute, which is required for
737 * refresh
738 */
739 CSSM_DATA_PTR uri = crl->fetchValidAttr(ATTR_DEX_URI);
740 if(uri == NULL) {
741 dprintf("Expired CRL with no URI at dex %u\n", dex);
742 continue;
743 }
744
745 /* fetch a new one */
746 if(verbose || DEBUG_PRINT) {
747 printf("...fetching new CRL from net to update CRL %u from ",
748 dex);
749 crl->printName();
750 }
751 CSSM_RETURN crtn = netFetch(*uri, LT_Crl, newCrl);
752 if(crtn) {
753 cuPrintError("netFetch", crtn);
754 continue;
755 }
756
757 /* store it in the DB */
758 crtn = cuAddCrlToDb(crl->dlDbHand(), clHand, &newCrl, uri);
759
760 /*
761 * One special error case - UNIQUE_INDEX_DATA indicates that
762 * the CRL we just fetched is already in the cache. This
763 * can occur when expireOverlap is sufficiently large that
764 * we decide to fetch before a CRL is actually expired. In
765 * this case process as usual, avoiding any further updates
766 * from this CA/URI.
767 */
768 switch(crtn) {
769 case CSSM_OK:
770 dprintf("...refreshed CRL added to DB to account "
771 "for expired CRL %u\n", dex);
772 break;
773 case CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA:
774 dprintf("...refreshed CRL is a dup of CRL %u; skipping\n",
775 dex);
776 break;
777 default:
778 continue;
779 }
780
781
782 /*
783 * In case there are other CRLs still to be discovered
784 * in our list which are a) expired, and b) from this same issuer,
785 * we flag the current (expired) CRL as refreshed to ensure that
786 * we don't do this fetch again. A lot easier than cooking up
787 * a new CrlInfo object for the CRL we just fetched.
788 */
789 crl->mRefreshed = true;
790 }
791 }
792
793 /*
794 * Open an existing keychain or create a new one.
795 * This is a known "insecure" keychain/DB, since you don't need
796 * to unlock it to add or remove CRLs to/from it. Thus if
797 * we create it we use the filename as password.
798 */
799 CSSM_RETURN openDatabase(
800 CSSM_DL_HANDLE dlHand,
801 const char *dbFileName,
802 bool verbose,
803 CSSM_DB_HANDLE &dbHand, // RETURNED
804 bool &didCreate) // RETURNED
805 {
806 didCreate = false;
807
808 /* try to open existing DB */
809 CSSM_RETURN crtn = CSSM_DL_DbOpen(dlHand,
810 dbFileName,
811 NULL, // DbLocation
812 CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
813 NULL, // CSSM_ACCESS_CREDENTIALS *AccessCred
814 NULL, // void *OpenParameters
815 &dbHand);
816 switch(crtn) {
817 case CSSM_OK:
818 return CSSM_OK;
819 case CSSMERR_DL_DATASTORE_DOESNOT_EXIST:
820 /* proceed to create it */
821 break;
822 default:
823 cuPrintError("CSSM_DL_DbOpen", crtn);
824 return crtn;
825 }
826
827 /* create new one */
828 if(verbose) {
829 printf("...creating database %s\n", dbFileName);
830 }
831 CSSM_DBINFO dbInfo;
832 memset(&dbInfo, 0, sizeof(CSSM_DBINFO));
833
834 CssmAllocator &alloc = CssmAllocator::standard();
835 CssmClient::AclFactory::PasswordChangeCredentials pCreds((StringData(dbFileName)), alloc);
836 const AccessCredentials* aa = pCreds;
837
838 // @@@ Create a nice wrapper for building the default AclEntryPrototype.
839 TypedList subject(alloc, CSSM_ACL_SUBJECT_TYPE_ANY);
840 AclEntryPrototype protoType(subject);
841 AuthorizationGroup &authGroup = protoType.authorization();
842 CSSM_ACL_AUTHORIZATION_TAG tag = CSSM_ACL_AUTHORIZATION_ANY;
843 authGroup.NumberOfAuthTags = 1;
844 authGroup.AuthTags = &tag;
845
846 const ResourceControlContext rcc(protoType, const_cast<AccessCredentials *>(aa));
847
848 crtn = CSSM_DL_DbCreate(dlHand,
849 dbFileName,
850 NULL, // DbLocation
851 &dbInfo,
852 CSSM_DB_ACCESS_PRIVILEGED,
853 &rcc, // CredAndAclEntry
854 NULL, // OpenParameters
855 &dbHand);
856 if(crtn) {
857 cuPrintError("CSSM_DL_DbCreate", crtn);
858 return crtn;
859 }
860 else {
861 /* one more thing: make it world writable by convention */
862 if(chmod(dbFileName, 0666)) {
863 perror(dbFileName);
864 crtn = CSSMERR_DL_DB_LOCKED;
865 }
866 didCreate = true;
867 }
868 return crtn;
869 }
870
871 /*
872 * Add CRL fetched from net to local cache, used only by fetchItemFromNet.
873 * Note we're not dealing with fetched certs here; they are not
874 * stored on the fly.
875 */
876 static int writeFetchedItem(
877 LF_Type lfType,
878 const CSSM_DATA *itemData,
879 const CSSM_DATA *uriData)
880 {
881 if(lfType == LT_Cert) {
882 return 0;
883 }
884
885 /*
886 * The awkward part of this operation is that we have to open a DLDB
887 * (whose filename can only be hard coded at this point) and attach
888 * to the CL.
889 */
890 CSSM_DL_DB_HANDLE dlDbHand = {0, 0};
891 CSSM_CL_HANDLE clHand = 0;
892 CSSM_RETURN crtn;
893 bool didCreate;
894 int ourRtn = 0;
895
896 clHand = cuClStartup();
897 if(clHand == 0) {
898 return 1;
899 }
900 /* subsequent errors to done: */
901 dlDbHand.DLHandle = cuDlStartup();
902 if(dlDbHand.DLHandle == 0) {
903 ourRtn = 1;
904 goto done;
905 }
906 crtn = openDatabase(dlDbHand.DLHandle,
907 CRL_CACHE_DB,
908 false, // verbose
909 dlDbHand.DBHandle,
910 didCreate);
911 if(crtn) {
912 dprintf("***Error opening keychain %s. Aborting.\n", CRL_CACHE_DB);
913 ourRtn = 1;
914 goto done;
915 }
916
917 /* store it in the DB */
918 crtn = cuAddCrlToDb(dlDbHand, clHand, itemData, uriData);
919
920 /*
921 * One special error case - UNIQUE_INDEX_DATA indicates that
922 * the CRL we just fetched is already in the cache. This
923 * can occur as a result of a race condition between searching
924 * for a CRL in the cache (currently done by the TP, who execs us)
925 * and the fetch we just completed, if multiple tasks or threads are
926 * searching for the same CRL.
927 * Eventually this will be handled more robustly by all of the searching
928 * and fetching being done in a daemon.
929 */
930 switch(crtn) {
931 case CSSM_OK:
932 dprintf("...fetched CRL added to DB\n");
933 break;
934 case CSSMERR_DL_INVALID_UNIQUE_INDEX_DATA:
935 dprintf("...fetched CRL is a dup; skipping\n");
936 break;
937 default:
938 /* specific error logged by cuAddCrlToDb() */
939 dprintf("Error writing CRL to cache\n");
940 ourRtn = 1;
941 break;
942 }
943 done:
944 if(dlDbHand.DBHandle) {
945 CSSM_DL_DbClose(dlDbHand);
946 }
947 if(dlDbHand.DLHandle) {
948 CSSM_ModuleDetach(dlDbHand.DLHandle);
949 }
950 if(clHand) {
951 CSSM_ModuleDetach(clHand);
952 }
953 return ourRtn;
954 }
955 /*
956 * Fetch a CRL or Cert from net; write it to a file.
957 */
958 int fetchItemFromNet(
959 LF_Type lfType,
960 const char *URI,
961 char *outFileName, // NULL indicates write to stdout
962 bool writeToCache)
963 {
964 const CSSM_DATA uriData = {strlen(URI) + 1, (uint8 *)URI};
965 CSSM_DATA item;
966 CSSM_RETURN crtn;
967 int irtn;
968
969 dprintf("fetchItemFromNet %s outFile %s\n",
970 URI, outFileName ? outFileName : "stdout");
971
972 /* netFetch deals with NULL-terminated string */
973 uriData.Data[uriData.Length - 1] = 0;
974 crtn = netFetch(uriData, lfType, item);
975 if(crtn) {
976 cuPrintError("netFetch", crtn);
977 return 1;
978 }
979 dprintf("fetchItemFromNet netFetch complete, %u bytes read\n",
980 (unsigned)item.Length);
981 if(outFileName == NULL) {
982 irtn = write(STDOUT_FILENO, item.Data, item.Length);
983 if(irtn != (int)item.Length) {
984 irtn = errno;
985 perror("write");
986 }
987 else {
988 irtn = 0;
989 }
990 }
991 else {
992 irtn = writeFile(outFileName, item.Data, item.Length);
993 if(irtn) {
994 perror(outFileName);
995 }
996 }
997 if((irtn == 0) && writeToCache) {
998 irtn = writeFetchedItem(lfType, &item, &uriData);
999 }
1000 free(item.Data);
1001 dprintf("fetchItemFromNet returning %d\n", irtn);
1002 return irtn;
1003 }
1004
1005 int main(int argc, char **argv)
1006 {
1007 CSSM_RETURN crtn;
1008 CSSM_DL_DB_HANDLE dlDbHand;
1009 CSSM_CL_HANDLE clHand;
1010 CSSM_CSP_HANDLE cspHand = 0;
1011 CSSM_TP_HANDLE tpHand = 0;
1012 int arg;
1013 char *argp;
1014 bool didCreate = false;
1015 int optArg;
1016
1017 /* user-specified variables */
1018 bool verbose = false;
1019 bool purgeAll = false;
1020 bool fullCryptoValidation = false;
1021 int staleDays = DEFAULT_STALE_DAYS;
1022 int expireOverlapSeconds =
1023 DEFAULT_EXPIRE_OVERLAP_SECONDS;
1024 char *dbFileName = CRL_CACHE_DB;
1025 /* fetch options */
1026 LF_Type lfType = LT_Crl;
1027 char *outFileName = NULL;
1028 bool writeToCache = true;
1029 char *uri = NULL;
1030
1031 if(argc < 2) {
1032 usage(argv);
1033 }
1034 switch(argv[1][0]) {
1035 case 'F':
1036 lfType = LT_Cert;
1037 /* and drop thru */
1038 case 'f':
1039 if(argc < 3) {
1040 usage(argv);
1041 }
1042 uri = argv[2];
1043 optArg = 3;
1044 break;
1045 case 'r':
1046 optArg = 2;
1047 break;
1048 default:
1049 usage(argv);
1050 }
1051 /* refresh options */
1052 for(arg=optArg; arg<argc; arg++) {
1053 argp = argv[arg];
1054 switch(argp[0]) {
1055 case 's':
1056 if(argp[1] != '=') {
1057 usage(argv);
1058 }
1059 staleDays = atoi(&argp[2]);
1060 break;
1061 case 'o':
1062 if(argp[1] != '=') {
1063 usage(argv);
1064 }
1065 expireOverlapSeconds = atoi(&argp[2]);
1066 break;
1067 case 'p':
1068 purgeAll = true;
1069 break;
1070 case 'f':
1071 fullCryptoValidation = true;
1072 break;
1073 case 'k':
1074 if(argp[1] != '=') {
1075 usage(argv);
1076 }
1077 dbFileName = &argp[2];
1078 break;
1079 case 'n':
1080 writeToCache = false;
1081 break;
1082 case 'F':
1083 if(argp[1] != '=') {
1084 usage(argv);
1085 }
1086 outFileName = &argp[2];
1087 break;
1088 case 'v':
1089 verbose = true;
1090 break;
1091 default:
1092 usage(argv);
1093 }
1094 }
1095 if(argv[1][0] != 'r') {
1096 return fetchItemFromNet(lfType, uri, outFileName, writeToCache);
1097 }
1098
1099 dprintf("...staleDays %d expireOverlapSeconds %d\n",
1100 staleDays, expireOverlapSeconds);
1101
1102 /*
1103 * Open the keychain.
1104 * Note that since we're doing a lot of CDSA_level DB ops, we
1105 * just acces the keychain as a DLDB direwctly - that way we
1106 * have control over the app-level memory callbacks.
1107 */
1108 dlDbHand.DLHandle = cuDlStartup();
1109 if(dlDbHand.DLHandle == 0) {
1110 exit(1);
1111 }
1112 crtn = openDatabase(dlDbHand.DLHandle,
1113 dbFileName,
1114 verbose,
1115 dlDbHand.DBHandle,
1116 didCreate);
1117 if(crtn) {
1118 printf("***Error opening keychain %s. Aborting.\n", dbFileName);
1119 exit(1);
1120 }
1121
1122 if(didCreate) {
1123 /* New, empty keychain. I guarantee you we're done. */
1124 CSSM_DL_DbClose(dlDbHand);
1125 CSSM_ModuleDetach(dlDbHand.DLHandle);
1126 return 0;
1127 }
1128
1129 clHand = cuClStartup();
1130 if(clHand == 0) {
1131 exit(1);
1132 }
1133 if(fullCryptoValidation) {
1134 /* also need TP, CSP */
1135 cspHand = cuCspStartup(CSSM_TRUE);
1136 if(cspHand == 0) {
1137 exit(1);
1138 }
1139 tpHand = cuTpStartup();
1140 if(tpHand == 0) {
1141 exit(1);
1142 }
1143 }
1144
1145 /* fetch all CRLs from the keychain */
1146 CrlInfo **crlInfo;
1147 unsigned numCrls;
1148 crtn = fetchAllCrls(dlDbHand, fullCryptoValidation, crlInfo, numCrls);
1149 if(crtn) {
1150 printf("***Error reading CRLs from %s. Aborting.\n", dbFileName);
1151 exit(1);
1152 }
1153 dprintf("...%u CRLs found\n", numCrls);
1154
1155 /* basic validation */
1156 validateCrls(crlInfo, numCrls, verbose);
1157
1158 /* Optional full crypto validation */
1159 if(fullCryptoValidation) {
1160 cryptoValidateCrls(crlInfo, numCrls, verbose,
1161 tpHand, cspHand, clHand, dlDbHand.DLHandle);
1162 }
1163
1164 /* update the validity time flags on the CRLs */
1165 if(calcCurrent(crlInfo, numCrls, expireOverlapSeconds,
1166 staleDays * SECONDS_PER_DAY)) {
1167 printf("***Error calculating CRL times. Aborting\n");
1168 exit(1);
1169 }
1170
1171 if(purgeAll) {
1172 /* mark all of them stale */
1173 purgeAllCrls(crlInfo, numCrls, verbose);
1174 }
1175
1176 /*
1177 * Delete all bad CRLs from DB. We do this before the refresh in
1178 * case of the purgeAll option, in which case we really want to
1179 * insert newly fetched CRLs in the DB even if they appear to
1180 * be trhe same as the ones they're replacing.
1181 */
1182 deleteBadCrls(crlInfo, numCrls, verbose);
1183
1184 /* refresh the out-of-date CRLs */
1185 refreshExpiredCrls(crlInfo, numCrls, clHand, verbose);
1186
1187 /* clean up */
1188 for(unsigned dex=0; dex<numCrls; dex++) {
1189 delete crlInfo[dex];
1190 }
1191 free(crlInfo);
1192 CSSM_DL_DbClose(dlDbHand);
1193 CSSM_ModuleDetach(dlDbHand.DLHandle);
1194 CSSM_ModuleDetach(clHand);
1195 if(tpHand) {
1196 CSSM_ModuleDetach(tpHand);
1197 }
1198 if(cspHand) {
1199 CSSM_ModuleDetach(cspHand);
1200 }
1201 return 0;
1202 }