1/* 2 * Copyright (c) 2002-2011 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 * TPNetwork.h - LDAP, HTTP and (eventually) other network tools 21 */ 22 23#include "TPNetwork.h" 24#include "tpdebugging.h" 25#include "tpTime.h" 26#include "cuEnc64.h" 27#include <Security/cssmtype.h> 28#include <Security/cssmapple.h> 29#include <Security/oidscert.h> 30#include <security_utilities/logging.h> 31#include <security_ocspd/ocspdClient.h> 32 33#define CA_ISSUERS_OID OID_PKIX, 0x30, 0x02 34#define CA_ISSUERS_OID_LEN OID_PKIX_LENGTH + 2 35 36static const uint8 OID_CA_ISSUERS[] = {CA_ISSUERS_OID}; 37const CSSM_OID CSSMOID_CA_ISSUERS = {CA_ISSUERS_OID_LEN, (uint8 *)OID_CA_ISSUERS}; 38 39typedef enum { 40 LT_Crl = 1, 41 LT_Cert 42} LF_Type; 43 44static CSSM_RETURN tpDecodeCert( 45 Allocator &alloc, 46 CSSM_DATA &rtnBlob) // will be reallocated if needed 47{ 48 const unsigned char *inbuf = (const unsigned char *)rtnBlob.Data; 49 unsigned inlen = (unsigned)rtnBlob.Length; 50 unsigned char *outbuf = NULL; 51 unsigned outlen = 0; 52 CSSM_RETURN ortn = cuConvertPem(inbuf, inlen, &outbuf, &outlen); 53 54 if(ortn == 0 && outbuf != NULL) { 55 /* Decoded result needs to be malloc'd via input allocator */ 56 unsigned char *rtnP = (unsigned char *) alloc.malloc(outlen); 57 if(rtnP != NULL) { 58 memcpy(rtnP, outbuf, outlen); 59 rtnBlob.Data = rtnP; 60 rtnBlob.Length = outlen; 61 } 62 free(outbuf); 63 alloc.free((void *)inbuf); 64 } 65 return ortn; 66} 67 68static CSSM_RETURN tpFetchViaNet( 69 const CSSM_DATA &url, 70 const CSSM_DATA *issuer, // optional 71 LF_Type lfType, 72 CSSM_TIMESTRING verifyTime, // CRL only 73 Allocator &alloc, 74 CSSM_DATA &rtnBlob) // mallocd and RETURNED 75{ 76 if(lfType == LT_Crl) { 77 return ocspdCRLFetch(alloc, url, issuer, 78 true, true, // cache r/w both enable 79 verifyTime, rtnBlob); 80 } 81 else { 82 CSSM_RETURN result = ocspdCertFetch(alloc, url, rtnBlob); 83 if(result == CSSM_OK) { 84 /* The data might be in PEM format; if so, convert it here */ 85 (void)tpDecodeCert(alloc, rtnBlob); 86 } 87 return result; 88 } 89} 90 91static CSSM_RETURN tpCrlViaNet( 92 const CSSM_DATA &url, 93 const CSSM_DATA *issuer, // optional, only if cert and CRL have same issuer 94 TPVerifyContext &vfyCtx, 95 TPCertInfo &forCert, // for verifyWithContext 96 TPCrlInfo *&rtnCrl) 97{ 98 TPCrlInfo *crl = NULL; 99 CSSM_DATA crlData; 100 CSSM_RETURN crtn; 101 Allocator &alloc = Allocator::standard(); 102 char cssmTime[CSSM_TIME_STRLEN+1]; 103 104 rtnCrl = NULL; 105 106 /* verifyTime: we want a CRL that's valid right now. */ 107 { 108 StLock<Mutex> _(tpTimeLock()); 109 timeAtNowPlus(0, TIME_CSSM, cssmTime); 110 } 111 112 crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData); 113 if(crtn) { 114 return crtn; 115 } 116 try { 117 crl = new TPCrlInfo(vfyCtx.clHand, 118 vfyCtx.cspHand, 119 &crlData, 120 TIC_CopyData, 121 NULL); // verifyTime = Now 122 } 123 catch(...) { 124 alloc.free(crlData.Data); 125 126 /* 127 * There is a slight possibility of recovering from this error. In case 128 * the CRL came from disk cache, flush the cache and try to get the CRL 129 * from the net. 130 */ 131 tpDebug(" bad CRL; flushing from cache and retrying"); 132 ocspdCRLFlush(url); 133 crtn = tpFetchViaNet(url, issuer, LT_Crl, cssmTime, alloc, crlData); 134 if(crtn == CSSM_OK) { 135 try { 136 crl = new TPCrlInfo(vfyCtx.clHand, 137 vfyCtx.cspHand, 138 &crlData, 139 TIC_CopyData, 140 NULL); 141 tpDebug(" RECOVERY: good CRL obtained from net"); 142 } 143 catch(...) { 144 alloc.free(crlData.Data); 145 tpDebug(" bad CRL; recovery FAILED (1)"); 146 return CSSMERR_APPLETP_CRL_NOT_FOUND; 147 } 148 } 149 else { 150 /* it was in cache but we can't find it on the net */ 151 tpDebug(" bad CRL; recovery FAILED (2)"); 152 return CSSMERR_APPLETP_CRL_NOT_FOUND; 153 } 154 } 155 alloc.free(crlData.Data); 156 157 /* 158 * Full CRL verify. 159 * The verify time in the TPVerifyContext is the time at which various 160 * entities (CRL and its own cert chain) are to be verified; that's 161 * NULL for "right now". The current vfyCtx.verifyTime is the time at 162 * which the cert's revocation status to be determined; this call to 163 * verifyWithContextNow() doesn't do that. 164 */ 165 crtn = crl->verifyWithContextNow(vfyCtx, &forCert); 166 if(crtn == CSSM_OK) { 167 crl->uri(url); 168 } 169 else { 170 delete crl; 171 crl = NULL; 172 } 173 rtnCrl = crl; 174 return crtn; 175} 176 177static CSSM_RETURN tpIssuerCertViaNet( 178 const CSSM_DATA &url, 179 CSSM_CL_HANDLE clHand, 180 CSSM_CSP_HANDLE cspHand, 181 const char *verifyTime, 182 TPCertInfo &subject, 183 TPCertInfo *&rtnCert) 184{ 185 TPCertInfo *issuer = NULL; 186 CSSM_DATA certData; 187 CSSM_RETURN crtn; 188 Allocator &alloc = Allocator::standard(); 189 190 crtn = tpFetchViaNet(url, NULL, LT_Cert, NULL, alloc, certData); 191 if(crtn) { 192 tpErrorLog("tpIssuerCertViaNet: net fetch failed\n"); 193 return CSSMERR_APPLETP_CERT_NOT_FOUND_FROM_ISSUER; 194 } 195 try { 196 issuer = new TPCertInfo(clHand, 197 cspHand, 198 &certData, 199 TIC_CopyData, 200 verifyTime); 201 } 202 catch(...) { 203 tpErrorLog("tpIssuerCertViaNet: bad cert via net fetch\n"); 204 alloc.free(certData.Data); 205 rtnCert = NULL; 206 return CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER; 207 } 208 alloc.free(certData.Data); 209 210 /* subject/issuer match? */ 211 if(!issuer->isIssuerOf(subject)) { 212 tpErrorLog("tpIssuerCertViaNet: wrong issuer cert via net fetch\n"); 213 crtn = CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER; 214 } 215 else { 216 /* yep, do a sig verify */ 217 crtn = subject.verifyWithIssuer(issuer); 218 if(crtn) { 219 tpErrorLog("tpIssuerCertViaNet: sig verify fail for cert via net " 220 "fetch\n"); 221 crtn = CSSMERR_APPLETP_BAD_CERT_FROM_ISSUER; 222 } 223 } 224 if(crtn) { 225 assert(issuer != NULL); 226 delete issuer; 227 issuer = NULL; 228 } 229 rtnCert = issuer; 230 return crtn; 231} 232 233/* 234 * Fetch a CRL or a cert via a GeneralNames. 235 * Shared by cert and CRL code to avoid duplicating GeneralNames traversal 236 * code, despite the awkward interface for this function. 237 */ 238static CSSM_RETURN tpFetchViaGeneralNames( 239 const CE_GeneralNames *names, 240 TPCertInfo &forCert, 241 const CSSM_DATA *issuer, // optional, and only for CRLs 242 TPVerifyContext *verifyContext, // only for CRLs 243 CSSM_CL_HANDLE clHand, // only for certs 244 CSSM_CSP_HANDLE cspHand, // only for certs 245 const char *verifyTime, // optional 246 /* exactly one must be non-NULL, that one is returned */ 247 TPCertInfo **certInfo, 248 TPCrlInfo **crlInfo) 249{ 250 assert(certInfo || crlInfo); 251 assert(!certInfo || !crlInfo); 252 CSSM_RETURN crtn; 253 254 for(unsigned nameDex=0; nameDex<names->numNames; nameDex++) { 255 CE_GeneralName *name = &names->generalName[nameDex]; 256 switch(name->nameType) { 257 case GNT_URI: 258 if(name->name.Length < 5) { 259 continue; 260 } 261 if(strncmp((char *)name->name.Data, "ldap:", 5) && 262 strncmp((char *)name->name.Data, "http:", 5) && 263 strncmp((char *)name->name.Data, "https:", 6)) { 264 /* eventually handle other schemes here */ 265 continue; 266 } 267 if(certInfo) { 268 tpDebug(" fetching cert via net"); 269 crtn = tpIssuerCertViaNet(name->name, 270 clHand, 271 cspHand, 272 verifyTime, 273 forCert, 274 *certInfo); 275 } 276 else { 277 tpDebug(" fetching CRL via net"); 278 assert(verifyContext != NULL); 279 crtn = tpCrlViaNet(name->name, 280 issuer, 281 *verifyContext, 282 forCert, 283 *crlInfo); 284 } 285 switch(crtn) { 286 case CSSM_OK: 287 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: // caller handles 288 return crtn; 289 default: 290 break; 291 } 292 /* not found/no good; try again */ 293 break; 294 default: 295 tpCrlDebug(" tpFetchCrlFromNet: unknown" 296 "nameType (%u)", (unsigned)name->nameType); 297 break; 298 } /* switch nameType */ 299 } /* for each name */ 300 if(certInfo) { 301 return CSSMERR_TP_CERTGROUP_INCOMPLETE; 302 } 303 else { 304 return CSSMERR_APPLETP_CRL_NOT_FOUND; 305 } 306} 307 308/* 309 * Fetch CRL(s) from specified cert if the cert has a cRlDistributionPoint 310 * extension. 311 * 312 * Return values: 313 * CSSM_OK - found and returned fully verified CRL 314 * CSSMERR_APPLETP_CRL_NOT_FOUND - no CRL in cRlDistributionPoint 315 * Anything else - gross error, typically from last LDAP/HTTP attempt 316 * 317 * FIXME - this whole mechanism sort of falls apart if verifyContext.verifyTime 318 * is non-NULL. How are we supposed to get the CRL which was valid at 319 * a specified time in the past? 320 */ 321CSSM_RETURN tpFetchCrlFromNet( 322 TPCertInfo &cert, 323 TPVerifyContext &vfyCtx, 324 TPCrlInfo *&crl) // RETURNED 325{ 326 /* does the cert have a cRlDistributionPoint? */ 327 CSSM_DATA_PTR fieldValue; // mallocd by CL 328 329 CSSM_RETURN crtn = cert.fetchField(&CSSMOID_CrlDistributionPoints, 330 &fieldValue); 331 switch(crtn) { 332 case CSSM_OK: 333 break; 334 case CSSMERR_CL_NO_FIELD_VALUES: 335 /* field not present */ 336 return CSSMERR_APPLETP_CRL_NOT_FOUND; 337 default: 338 /* gross error */ 339 return crtn; 340 } 341 if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) { 342 tpErrorLog("tpFetchCrlFromNet: malformed CSSM_FIELD"); 343 return CSSMERR_TP_UNKNOWN_FORMAT; 344 } 345 CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data; 346 CE_CRLDistPointsSyntax *dps = 347 (CE_CRLDistPointsSyntax *)cssmExt->value.parsedValue; 348 TPCrlInfo *rtnCrl = NULL; 349 350 /* default return if we don't find anything */ 351 crtn = CSSMERR_APPLETP_CRL_NOT_FOUND; 352 for(unsigned dex=0; dex<dps->numDistPoints; dex++) { 353 CE_CRLDistributionPoint *dp = &dps->distPoints[dex]; 354 if(dp->distPointName == NULL) { 355 continue; 356 } 357 /* 358 * FIXME if this uses an indirect CRL, we need to follow the 359 * crlIssuer field... TBD. 360 */ 361 switch(dp->distPointName->nameType) { 362 case CE_CDNT_NameRelativeToCrlIssuer: 363 /* not yet */ 364 tpErrorLog("tpFetchCrlFromNet: " 365 "CE_CDNT_NameRelativeToCrlIssuer not implemented\n"); 366 break; 367 368 case CE_CDNT_FullName: 369 { 370 /* 371 * Since we don't support indirect CRLs (yet), we always pass 372 * the cert-to-be-verified's issuer as the CRL issuer for 373 * cache lookup. 374 */ 375 CE_GeneralNames *names = dp->distPointName->dpn.fullName; 376 crtn = tpFetchViaGeneralNames(names, 377 cert, 378 cert.issuerName(), 379 &vfyCtx, 380 0, // clHand, use the one in vfyCtx 381 0, // cspHand, ditto 382 vfyCtx.verifyTime, 383 NULL, 384 &rtnCrl); 385 break; 386 } /* CE_CDNT_FullName */ 387 388 default: 389 /* not yet */ 390 tpErrorLog("tpFetchCrlFromNet: " 391 "unknown distPointName->nameType (%u)\n", 392 (unsigned)dp->distPointName->nameType); 393 break; 394 } /* switch distPointName->nameType */ 395 if(crtn == CSSM_OK) { 396 /* i.e., tpFetchViaGeneralNames SUCCEEDED */ 397 break; 398 } 399 } /* for each distPoints */ 400 401 cert.freeField(&CSSMOID_CrlDistributionPoints, fieldValue); 402 if(crtn == CSSM_OK) { 403 assert(rtnCrl != NULL); 404 crl = rtnCrl; 405 } 406 return crtn; 407} 408 409/* 410 * Fetch issuer cert of specified cert if the cert has an issuerAltName 411 * with a URI. If non-NULL cert is returned, it has passed subject/issuer 412 * name comparison and signature verification with target cert. 413 * 414 * Return values: 415 * CSSM_OK - found and returned issuer cert 416 * CSSMERR_TP_CERTGROUP_INCOMPLETE - no URL in issuerAltName 417 * CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE - found and returned issuer 418 * cert, but signature verification needs subsequent retry. 419 * Anything else - gross error, typically from last LDAP/HTTP attempt 420 */ 421CSSM_RETURN tpFetchIssuerFromNet( 422 TPCertInfo &subject, 423 CSSM_CL_HANDLE clHand, 424 CSSM_CSP_HANDLE cspHand, 425 const char *verifyTime, 426 TPCertInfo *&issuer) // RETURNED 427{ 428 CSSM_OID_PTR fieldOid = NULL; 429 CSSM_DATA_PTR fieldValue = NULL; // mallocd by CL 430 CSSM_RETURN crtn; 431 bool hasAIA = false; 432 433 /* look for the Authority Info Access extension first */ 434 fieldOid = (CSSM_OID_PTR)&CSSMOID_AuthorityInfoAccess; 435 crtn = subject.fetchField(fieldOid, 436 &fieldValue); 437 hasAIA = (crtn == CSSM_OK); 438 if (!hasAIA) { 439 /* fall back to Issuer Alternative Name extension */ 440 fieldOid = (CSSM_OID_PTR)&CSSMOID_IssuerAltName; 441 crtn = subject.fetchField(fieldOid, 442 &fieldValue); 443 } 444 switch(crtn) { 445 case CSSM_OK: 446 break; 447 case CSSMERR_CL_NO_FIELD_VALUES: 448 /* field not present */ 449 return CSSMERR_TP_CERTGROUP_INCOMPLETE; 450 default: 451 /* gross error */ 452 return crtn; 453 } 454 if(fieldValue->Length != sizeof(CSSM_X509_EXTENSION)) { 455 tpPolicyError("tpFetchIssuerFromNet: malformed CSSM_FIELD"); 456 return CSSMERR_TP_UNKNOWN_FORMAT; 457 } 458 CSSM_X509_EXTENSION *cssmExt = (CSSM_X509_EXTENSION *)fieldValue->Data; 459 CE_GeneralNames *names = (CE_GeneralNames *)cssmExt->value.parsedValue; 460 TPCertInfo *rtnCert = NULL; 461 if (hasAIA) { /* authority info access */ 462 CE_AuthorityInfoAccess *access = (CE_AuthorityInfoAccess *)cssmExt->value.parsedValue; 463 for (uint32 index = 0; access && index < access->numAccessDescriptions; index++) { 464 CE_AccessDescription *accessDesc = &access->accessDescriptions[index]; 465 CSSM_OID_PTR methodOid = (CSSM_OID_PTR)&accessDesc->accessMethod; 466 /* look for the CA Issuers method */ 467 if(methodOid->Data != NULL && methodOid->Length == CSSMOID_CA_ISSUERS.Length && 468 !memcmp(methodOid->Data, CSSMOID_CA_ISSUERS.Data, methodOid->Length)) { 469 CE_GeneralNames aiaNames = { 1, &accessDesc->accessLocation }; 470 /* attempt to fetch cert from named location */ 471 crtn = tpFetchViaGeneralNames(&aiaNames, 472 subject, 473 NULL, // issuer - not used 474 NULL, // verifyContext 475 clHand, 476 cspHand, 477 verifyTime, 478 &rtnCert, 479 NULL); 480 if (crtn == CSSM_OK || 481 crtn == CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE) { 482 break; // got one 483 } 484 } 485 } 486 subject.freeField(fieldOid, fieldValue); 487 } 488 else { /* issuer alt name */ 489 crtn = tpFetchViaGeneralNames(names, 490 subject, 491 NULL, // issuer - not used 492 NULL, // verifyContext 493 clHand, 494 cspHand, 495 verifyTime, 496 &rtnCert, 497 NULL); 498 subject.freeField(fieldOid, fieldValue); 499 } 500 switch(crtn) { 501 case CSSM_OK: 502 case CSSMERR_CSP_APPLE_PUBLIC_KEY_INCOMPLETE: 503 issuer = rtnCert; 504 break; 505 default: 506 break; 507 } 508 return crtn; 509} 510 511 512