1 /** 2 * X.509 Cert Path Validation 3 * 4 * Copyright: 5 * (C) 2010-2011 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.cert.x509.x509path; 12 13 import botan.constants; 14 static if (BOTAN_HAS_X509_CERTIFICATES): 15 16 public import botan.cert.x509.ocsp; 17 public import botan.cert.x509.x509_crl; 18 import botan.cert.x509.key_constraint; 19 import botan.utils.http_util.http_util; 20 import botan.utils.parsing; 21 import botan.pubkey.pubkey; 22 import botan.asn1.oids; 23 import botan.asn1.asn1_time; 24 import std.algorithm; 25 import std.datetime; 26 import botan.utils.types; 27 import memutils.rbtree : RBTreeRef, RBTree; 28 //version(Have_vibe_d) { 29 // import vibe.core.concurrency; 30 //} 31 //else { 32 import core.thread; 33 //} 34 import botan.cert.x509.cert_status; 35 import botan.cert.x509.x509cert; 36 import core.sync.mutex; 37 38 /** 39 * Specifies restrictions on the PKIX path validation 40 */ 41 struct PathValidationRestrictions 42 { 43 public: 44 /** 45 * Params: 46 * require_rev = if true, revocation information is required 47 * key_strength = is the minimum strength (in terms of 48 * operations, eg 80 means 2^80) of a signature. Signatures 49 * weaker than this are rejected. If more than 80, SHA-1 50 * signatures are also rejected. 51 * ocsp_all = where to use all intermediates 52 */ 53 this(bool require_rev, size_t key_strength = 80, bool ocsp_all = false, int max_cert_chain_length = 9) 54 { 55 m_require_revocation_information = require_rev; 56 m_ocsp_all_intermediates = ocsp_all; 57 m_minimum_key_strength = key_strength; 58 if (key_strength <= 80) 59 m_trusted_hashes.insert("SHA-160"); 60 61 m_trusted_hashes.insert("SHA-224"); 62 m_trusted_hashes.insert("SHA-256"); 63 m_trusted_hashes.insert("SHA-384"); 64 m_trusted_hashes.insert("SHA-512"); 65 } 66 67 /** 68 * Params: 69 * require_rev = if true, revocation information is required 70 * minimum_key_strength = is the minimum strength (in terms of 71 * operations, eg 80 means 2^80) of a signature. Signatures 72 * weaker than this are rejected. 73 * ocsp_all_intermediates = where to use all intermediates 74 * trusted_hashes = a set of trusted hashes. Any signatures 75 * created using a hash other than one of these will be 76 * rejected. 77 */ 78 this(bool require_rev, 79 size_t minimum_key_strength, 80 bool ocsp_all_intermediates, 81 RBTreeRef!string trusted_hashes, 82 int max_cert_chain_length = 9) 83 { 84 m_require_revocation_information = require_rev; 85 m_ocsp_all_intermediates = ocsp_all_intermediates; 86 m_trusted_hashes.insert((*trusted_hashes)[]); 87 m_minimum_key_strength = minimum_key_strength; 88 } 89 90 @property int maxCertChainLength() const { return m_max_cert_chain_length; } 91 @property void maxCertChainLength(int sz) { m_max_cert_chain_length = sz; } 92 93 bool requireRevocationInformation() const 94 { return m_require_revocation_information; } 95 96 bool ocspAllIntermediates() const 97 { return m_ocsp_all_intermediates; } 98 99 ref const(RBTree!string) trustedHashes() const 100 { 101 if (m_trusted_hashes.length > 0) 102 return m_trusted_hashes; 103 return m_def_trusted_hashes; 104 } 105 106 size_t minimumKeyStrength() const 107 { return m_minimum_key_strength; } 108 109 private: 110 bool m_require_revocation_information = false; 111 bool m_ocsp_all_intermediates = false; 112 int m_max_cert_chain_length = 9; 113 RBTree!string m_trusted_hashes; 114 size_t m_minimum_key_strength = 80; 115 116 static this() { 117 m_def_trusted_hashes.insert("SHA-160"); 118 m_def_trusted_hashes.insert("SHA-224"); 119 m_def_trusted_hashes.insert("SHA-256"); 120 m_def_trusted_hashes.insert("SHA-384"); 121 m_def_trusted_hashes.insert("SHA-512"); 122 } 123 static ~this() { 124 m_def_trusted_hashes.clear(); 125 m_def_trusted_hashes.destroy(); 126 } 127 static RBTree!string m_def_trusted_hashes; 128 } 129 130 /** 131 * Represents the result of a PKIX path validation 132 */ 133 struct PathValidationResult 134 { 135 public: 136 alias Code = CertificateStatusCode; 137 138 /** 139 * Returns: the set of hash functions you are implicitly 140 * trusting by trusting this result. 141 */ 142 RBTreeRef!string trustedHashes() const 143 { 144 RBTreeRef!string hashes; 145 foreach (cert_path; m_cert_path[]) 146 hashes.insert(cert_path.hashUsedForSignature()); 147 return hashes; 148 } 149 150 /** 151 * Returns: the trust root of the validation 152 */ 153 X509Certificate trustRoot() const 154 { 155 import std.range : back; 156 if (m_cert_path.length == 0) return X509Certificate.init; 157 return m_cert_path[].back; 158 } 159 160 /** 161 * Returns: the full path from subject to trust root 162 */ 163 ref const(Vector!X509Certificate) certPath() const { return m_cert_path; } 164 165 /** 166 * Returns: true iff the validation was succesful 167 */ 168 bool successfulValidation() const 169 { 170 if (result() == CertificateStatusCode.VERIFIED || 171 result() == CertificateStatusCode.OCSP_RESPONSE_GOOD) 172 return true; 173 return false; 174 } 175 176 /** 177 * Returns: overall validation result code 178 */ 179 CertificateStatusCode result() const { return m_overall; } 180 181 /** 182 * Return a set of status codes for each certificate in the chain 183 */ 184 ref const(Vector!(RBTreeRef!CertificateStatusCode)) allStatuses() const 185 { return m_all_status; } 186 187 /** 188 * Returns: string representation of the validation result 189 */ 190 string resultString() const 191 { 192 return statusString(result()); 193 } 194 195 196 static string statusString(CertificateStatusCode code) 197 { 198 switch(code) 199 { 200 case CertificateStatusCode.VERIFIED: 201 return "Verified"; 202 case CertificateStatusCode.OCSP_RESPONSE_GOOD: 203 return "OCSP response good"; 204 case CertificateStatusCode.NO_REVOCATION_DATA: 205 return "No revocation data"; 206 case CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK: 207 return "Signature method too weak"; 208 case CertificateStatusCode.UNTRUSTED_HASH: 209 return "Untrusted hash"; 210 211 case CertificateStatusCode.CERT_NOT_YET_VALID: 212 return "Certificate is not yet valid"; 213 case CertificateStatusCode.CERT_HAS_EXPIRED: 214 return "Certificate has expired"; 215 case CertificateStatusCode.OCSP_NOT_YET_VALID: 216 return "OCSP is not yet valid"; 217 case CertificateStatusCode.OCSP_HAS_EXPIRED: 218 return "OCSP has expired"; 219 case CertificateStatusCode.CRL_NOT_YET_VALID: 220 return "CRL is not yet valid"; 221 case CertificateStatusCode.CRL_HAS_EXPIRED: 222 return "CRL has expired"; 223 224 case CertificateStatusCode.CERT_ISSUER_NOT_FOUND: 225 return "Certificate issuer not found"; 226 case CertificateStatusCode.CANNOT_ESTABLISH_TRUST: 227 return "Cannot establish trust"; 228 229 case CertificateStatusCode.POLICY_ERROR: 230 return "TLSPolicy error"; 231 case CertificateStatusCode.INVALID_USAGE: 232 return "Invalid usage"; 233 case CertificateStatusCode.CERT_CHAIN_TOO_LONG: 234 return "Certificate chain too long"; 235 case CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER: 236 return "CA certificate not allowed to issue certs"; 237 case CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER: 238 return "CA certificate not allowed to issue CRLs"; 239 case CertificateStatusCode.OCSP_CERT_NOT_LISTED: 240 return "OCSP cert not listed"; 241 case CertificateStatusCode.OCSP_BAD_STATUS: 242 return "OCSP bad status"; 243 244 case CertificateStatusCode.CERT_IS_REVOKED: 245 return "Certificate is revoked"; 246 case CertificateStatusCode.CRL_BAD_SIGNATURE: 247 return "CRL bad signature"; 248 case CertificateStatusCode.SIGNATURE_ERROR: 249 return "Signature error"; 250 default: 251 return "Unknown error"; 252 } 253 } 254 255 this()(auto ref Vector!(RBTreeRef!CertificateStatusCode ) status, 256 auto ref Vector!X509Certificate cert_chain) 257 { 258 int i = 1; 259 m_overall = CertificateStatusCode.VERIFIED; 260 // take the "worst" error as overall 261 foreach (ref s; status[]) 262 { 263 if (!s.empty) 264 { 265 auto worst = s.back; 266 // Leave OCSP confirmations on cert-level status only 267 if (worst != CertificateStatusCode.OCSP_RESPONSE_GOOD) 268 m_overall = worst; 269 } 270 } 271 m_all_status = status.move(); 272 m_cert_path = cert_chain.move(); 273 } 274 275 276 this(CertificateStatusCode status) { m_overall = status; } 277 278 private: 279 CertificateStatusCode m_overall; 280 Vector!( RBTreeRef!CertificateStatusCode ) m_all_status; 281 Vector!X509Certificate m_cert_path; 282 } 283 284 /** 285 * PKIX Path Validation 286 */ 287 PathValidationResult 288 x509PathValidate()(const ref Vector!X509Certificate end_certs, 289 auto const ref PathValidationRestrictions restrictions, 290 const ref Vector!CertificateStore certstores) 291 { 292 const size_t max_iterations = restrictions.maxCertChainLength(); 293 if (end_certs.empty) 294 throw new InvalidArgument("x509PathValidate called with no subjects"); 295 Vector!X509Certificate cert_path = Vector!X509Certificate(); 296 cert_path.pushBack(end_certs[0]); 297 298 Unique!CertificateStoreOverlay extra = new CertificateStoreOverlay(end_certs); 299 CertificateStore cert_store = cast(CertificateStore)*extra; 300 size_t i; 301 // iterate until we reach a root or cannot find the issuer 302 while (!cert_path.back().isSelfSigned() && ++i < max_iterations) 303 { 304 X509Certificate cert = findIssuingCert(cert_path.back(), cert_store, certstores); 305 if (!cert) { 306 return PathValidationResult(CertificateStatusCode.CERT_ISSUER_NOT_FOUND); 307 } 308 cert_path.pushBack(cert); 309 } 310 if (i >= max_iterations) 311 throw new PKCS8Exception("Max iterations reached when attempting to find root certificate"); 312 auto chain = checkChain(cert_path, restrictions, certstores); 313 314 return PathValidationResult(chain, cert_path); 315 } 316 317 318 /** 319 * PKIX Path Validation 320 */ 321 PathValidationResult x509PathValidate()(in X509Certificate end_cert, 322 auto const ref PathValidationRestrictions restrictions, 323 const ref Vector!CertificateStore certstores) 324 { 325 Vector!X509Certificate certs; 326 certs.pushBack(cast(X509Certificate)end_cert); 327 return x509PathValidate(certs, restrictions, certstores); 328 } 329 330 /** 331 * PKIX Path Validation 332 */ 333 PathValidationResult x509PathValidate()(in X509Certificate end_cert, 334 auto const ref PathValidationRestrictions restrictions, 335 in CertificateStore store) 336 { 337 Vector!X509Certificate certs; 338 certs.pushBack(cast(X509Certificate)end_cert); 339 340 Vector!CertificateStore certstores; 341 certstores.pushBack(cast(CertificateStore) store); 342 343 return x509PathValidate(certs, restrictions, certstores); 344 } 345 346 /** 347 * PKIX Path Validation 348 */ 349 PathValidationResult x509PathValidate()(const ref Vector!X509Certificate end_certs, 350 auto const ref PathValidationRestrictions restrictions, 351 in CertificateStore store) 352 { 353 Vector!CertificateStore certstores; 354 certstores.pushBack(cast(CertificateStore)store); 355 356 return x509PathValidate(end_certs, restrictions, certstores); 357 } 358 359 X509Certificate findIssuingCert(in X509Certificate cert_, 360 ref CertificateStore end_certs, 361 const ref Vector!CertificateStore certstores) 362 { 363 364 const X509DN issuer_dn = cert_.issuerDn(); 365 366 const Vector!ubyte auth_key_id = cert_.authorityKeyId(); 367 368 if (X509Certificate cert = end_certs.findCert(issuer_dn, auth_key_id)) { 369 //logTrace("Found certificate: ", cert.toString()); 370 return cert; 371 } 372 373 foreach (certstore; certstores[]) 374 { 375 376 if (X509Certificate cert = certstore.findCert(issuer_dn, auth_key_id)) 377 return cert; 378 } 379 380 return X509Certificate.init; 381 } 382 383 const(X509CRL) findCrlsFor(in X509Certificate cert, 384 const ref Vector!CertificateStore certstores) 385 { 386 foreach (certstore; certstores[]) 387 { 388 if (const X509CRL crl = certstore.findCrlFor(cert)) 389 return crl; 390 } 391 392 /// todo: use crl distribution point and download the CRL 393 version(none) { 394 /* 395 const string crl_url = cert.crlDistributionPoint(); 396 if (crl_url != "") 397 { 398 std::cout << "Downloading CRL " << crl_url << ""; 399 auto http = HTTP::GET_sync(crl_url); 400 401 std::cout << http.status_message() << ""; 402 403 http.throw_unless_ok(); 404 // check the mime type 405 406 auto crl = X509CRL(http.body()); 407 408 return crl; 409 }*/ 410 } 411 412 return X509CRL.init; 413 } 414 415 Vector!( RBTreeRef!CertificateStatusCode ) 416 checkChain(const ref Vector!X509Certificate cert_path, 417 const ref PathValidationRestrictions restrictions, 418 const ref Vector!CertificateStore certstores) 419 { 420 //import core.memory : GC; GC.disable(); scope(exit) GC.enable(); 421 const RBTree!string* trusted_hashes = &restrictions.trustedHashes(); 422 423 const bool self_signed_ee_cert = (cert_path.length == 1); 424 425 X509Time current_time = X509Time(Clock.currTime(UTC())); 426 427 Vector!( Thread ) ocsp_responses; 428 429 scope(exit) foreach (Thread thr; ocsp_responses[]) { 430 ThreadMem.free(thr); 431 } 432 433 Vector!(OCSPResponse) ocsp_data = Vector!OCSPResponse(8); 434 435 Vector!( RBTreeRef!CertificateStatusCode ) cert_status = Vector!( RBTreeRef!CertificateStatusCode )( cert_path.length ); 436 437 foreach (ref e; cert_status) { 438 e.clear(); // touch 439 } 440 441 //logTrace("Cert path size: ", cert_path.length); 442 443 foreach (size_t i; 0 .. cert_path.length) 444 { 445 auto status = &*(cert_status[i]); 446 447 const bool at_self_signed_root = (i == cert_path.length - 1); 448 449 const X509Certificate subject = cert_path[i]; 450 451 const X509Certificate issuer = cert_path[at_self_signed_root ? (i) : (i + 1)]; 452 453 const CertificateStore* trusted = certstores.ptr; 454 455 Mutex mtx = new Mutex; 456 457 if (i == 0 || restrictions.ocspAllIntermediates()) { 458 459 if (certstores.length > 1) { 460 461 //version(Have_vibe_d) 462 // Tid id_ = runTask(&onlineCheck, cast(shared)Tid.getThis(), cast(shared)i, cast(shared)&ocsp_data[i], cast(shared)&issuer, cast(shared)&subject, cast(shared)trusted); 463 //else 464 synchronized(mtx) { 465 ocsp_data.length = i + 1; 466 OnlineCheck oc = OnlineCheck( cast(shared)mtx, cast(shared)i, cast(shared)&ocsp_data[i], cast(shared)&issuer, cast(shared)&subject, cast(shared)trusted ); 467 Thread thr = ThreadMem.alloc!Thread(&oc.run); 468 thr.start(); 469 ocsp_responses ~= thr; 470 } 471 } 472 } 473 // Check all certs for valid time range 474 if (current_time < X509Time(subject.startTime())) 475 status.insert(CertificateStatusCode.CERT_NOT_YET_VALID); 476 477 if (current_time > X509Time(subject.endTime())) 478 status.insert(CertificateStatusCode.CERT_HAS_EXPIRED); 479 480 // Check issuer constraints 481 logTrace("Check issuer constraints"); 482 // Don't require CA bit set on self-signed end entity cert 483 if (!issuer.isCACert() && !self_signed_ee_cert) 484 status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER); 485 486 if (issuer.pathLimit() < i) 487 status.insert(CertificateStatusCode.CERT_CHAIN_TOO_LONG); 488 const PublicKey issuer_key = issuer.subjectPublicKey(); 489 logTrace("Got issuer key"); 490 if (subject.checkSignature(issuer_key) == false) 491 status.insert(CertificateStatusCode.SIGNATURE_ERROR); 492 logTrace("Get estimated strength"); 493 if (issuer_key.estimatedStrength() < restrictions.minimumKeyStrength()) 494 status.insert(CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK); 495 496 logTrace("Scan untrusted hashes"); 497 // Allow untrusted hashes on self-signed roots 498 if (!trusted_hashes.empty && !at_self_signed_root) 499 { 500 if (subject.hashUsedForSignature() !in *trusted_hashes) 501 status.insert(CertificateStatusCode.UNTRUSTED_HASH); 502 } 503 } 504 logTrace("Certificates to check: ", cert_path.length); 505 foreach (size_t i; 0 .. cert_path.length - 1) 506 { 507 logTrace("Checking status ", i); 508 509 auto status = &*(cert_status[i]); 510 511 const X509Certificate subject = cert_path[i]; 512 const X509Certificate ca = cert_path[i+1]; 513 514 logTrace("Checking response ", i+1, " of ", ocsp_responses.length); 515 if (i < ocsp_responses.length) 516 { 517 try 518 { 519 ocsp_responses[i].join(); 520 if (ocsp_data.length <= i) continue; 521 OCSPResponse ocsp = ocsp_data[i]; 522 logTrace("Got response for ID#", i.to!string); 523 if (!ocsp || ocsp.empty) 524 throw new Exception("OSCP.responder is undefined"); 525 auto ocsp_status = ocsp.statusFor(ca, subject); 526 527 status.insert(ocsp_status); 528 529 logTrace("OCSP status: ", ocsp_status.to!string); 530 //std::cout << "OCSP status: " << statusString(ocsp_status) << "\n"; 531 532 // Either way we have a definitive answer, no need to check CRLs 533 if (ocsp_status == CertificateStatusCode.CERT_IS_REVOKED) 534 return cert_status.move(); 535 else if (ocsp_status == CertificateStatusCode.OCSP_RESPONSE_GOOD) 536 continue; 537 } 538 catch(Exception e) 539 { 540 logTrace("OCSP error: " ~ e.msg ~ ""); 541 } 542 } 543 544 const X509CRL crl = findCrlsFor(subject, certstores); 545 546 if (!crl) 547 { 548 if (restrictions.requireRevocationInformation()) 549 status.insert(CertificateStatusCode.NO_REVOCATION_DATA); 550 continue; 551 } 552 553 if (!ca.allowedUsage(KeyConstraints.CRL_SIGN)) 554 status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER); 555 556 if (current_time < crl.thisUpdate()) 557 status.insert(CertificateStatusCode.CRL_NOT_YET_VALID); 558 559 if (current_time > crl.nextUpdate()) 560 status.insert(CertificateStatusCode.CRL_HAS_EXPIRED); 561 Unique!PublicKey pubkey = ca.subjectPublicKey(); 562 if (crl.checkSignature(*pubkey) == false) 563 status.insert(CertificateStatusCode.CRL_BAD_SIGNATURE); 564 565 if (crl.isRevoked(subject)) 566 status.insert(CertificateStatusCode.CERT_IS_REVOKED); 567 } 568 569 if (self_signed_ee_cert) 570 cert_status.back().insert(CertificateStatusCode.CANNOT_ESTABLISH_TRUST); 571 572 return cert_status.move(); 573 }