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 return m_cert_path[].back; 157 } 158 159 /** 160 * Returns: the full path from subject to trust root 161 */ 162 ref const(Vector!X509Certificate) certPath() const { return m_cert_path; } 163 164 /** 165 * Returns: true iff the validation was succesful 166 */ 167 bool successfulValidation() const 168 { 169 if (result() == CertificateStatusCode.VERIFIED || 170 result() == CertificateStatusCode.OCSP_RESPONSE_GOOD) 171 return true; 172 return false; 173 } 174 175 /** 176 * Returns: overall validation result code 177 */ 178 CertificateStatusCode result() const { return m_overall; } 179 180 /** 181 * Return a set of status codes for each certificate in the chain 182 */ 183 ref const(Vector!(RBTreeRef!CertificateStatusCode)) allStatuses() const 184 { return m_all_status; } 185 186 /** 187 * Returns: string representation of the validation result 188 */ 189 string resultString() const 190 { 191 return statusString(result()); 192 } 193 194 195 static string statusString(CertificateStatusCode code) 196 { 197 switch(code) 198 { 199 case CertificateStatusCode.VERIFIED: 200 return "Verified"; 201 case CertificateStatusCode.OCSP_RESPONSE_GOOD: 202 return "OCSP response good"; 203 case CertificateStatusCode.NO_REVOCATION_DATA: 204 return "No revocation data"; 205 case CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK: 206 return "Signature method too weak"; 207 case CertificateStatusCode.UNTRUSTED_HASH: 208 return "Untrusted hash"; 209 210 case CertificateStatusCode.CERT_NOT_YET_VALID: 211 return "Certificate is not yet valid"; 212 case CertificateStatusCode.CERT_HAS_EXPIRED: 213 return "Certificate has expired"; 214 case CertificateStatusCode.OCSP_NOT_YET_VALID: 215 return "OCSP is not yet valid"; 216 case CertificateStatusCode.OCSP_HAS_EXPIRED: 217 return "OCSP has expired"; 218 case CertificateStatusCode.CRL_NOT_YET_VALID: 219 return "CRL is not yet valid"; 220 case CertificateStatusCode.CRL_HAS_EXPIRED: 221 return "CRL has expired"; 222 223 case CertificateStatusCode.CERT_ISSUER_NOT_FOUND: 224 return "Certificate issuer not found"; 225 case CertificateStatusCode.CANNOT_ESTABLISH_TRUST: 226 return "Cannot establish trust"; 227 228 case CertificateStatusCode.POLICY_ERROR: 229 return "TLSPolicy error"; 230 case CertificateStatusCode.INVALID_USAGE: 231 return "Invalid usage"; 232 case CertificateStatusCode.CERT_CHAIN_TOO_LONG: 233 return "Certificate chain too long"; 234 case CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER: 235 return "CA certificate not allowed to issue certs"; 236 case CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER: 237 return "CA certificate not allowed to issue CRLs"; 238 case CertificateStatusCode.OCSP_CERT_NOT_LISTED: 239 return "OCSP cert not listed"; 240 case CertificateStatusCode.OCSP_BAD_STATUS: 241 return "OCSP bad status"; 242 243 case CertificateStatusCode.CERT_IS_REVOKED: 244 return "Certificate is revoked"; 245 case CertificateStatusCode.CRL_BAD_SIGNATURE: 246 return "CRL bad signature"; 247 case CertificateStatusCode.SIGNATURE_ERROR: 248 return "Signature error"; 249 default: 250 return "Unknown error"; 251 } 252 } 253 254 this()(auto ref Vector!(RBTreeRef!CertificateStatusCode ) status, 255 auto ref Vector!X509Certificate cert_chain) 256 { 257 int i = 1; 258 m_overall = CertificateStatusCode.VERIFIED; 259 // take the "worst" error as overall 260 foreach (ref s; status[]) 261 { 262 if (!s.empty) 263 { 264 auto worst = s.back; 265 // Leave OCSP confirmations on cert-level status only 266 if (worst != CertificateStatusCode.OCSP_RESPONSE_GOOD) 267 m_overall = worst; 268 } 269 } 270 m_all_status = status.move(); 271 m_cert_path = cert_chain.move(); 272 } 273 274 275 this(CertificateStatusCode status) { m_overall = status; } 276 277 private: 278 CertificateStatusCode m_overall; 279 Vector!( RBTreeRef!CertificateStatusCode ) m_all_status; 280 Vector!X509Certificate m_cert_path; 281 } 282 283 /** 284 * PKIX Path Validation 285 */ 286 PathValidationResult 287 x509PathValidate()(const ref Vector!X509Certificate end_certs, 288 auto const ref PathValidationRestrictions restrictions, 289 const ref Vector!CertificateStore certstores) 290 { 291 const size_t max_iterations = restrictions.maxCertChainLength(); 292 if (end_certs.empty) 293 throw new InvalidArgument("x509PathValidate called with no subjects"); 294 Vector!X509Certificate cert_path = Vector!X509Certificate(); 295 cert_path.pushBack(end_certs[0]); 296 297 Unique!CertificateStoreOverlay extra = new CertificateStoreOverlay(end_certs); 298 CertificateStore cert_store = cast(CertificateStore)*extra; 299 size_t i; 300 // iterate until we reach a root or cannot find the issuer 301 while (!cert_path.back().isSelfSigned() && ++i < max_iterations) 302 { 303 X509Certificate cert = findIssuingCert(cert_path.back(), cert_store, certstores); 304 if (!cert) { 305 return PathValidationResult(CertificateStatusCode.CERT_ISSUER_NOT_FOUND); 306 } 307 cert_path.pushBack(cert); 308 } 309 if (i >= max_iterations) 310 throw new PKCS8Exception("Max iterations reached when attempting to find root certificate"); 311 auto chain = checkChain(cert_path, restrictions, certstores); 312 313 return PathValidationResult(chain, cert_path); 314 } 315 316 317 /** 318 * PKIX Path Validation 319 */ 320 PathValidationResult x509PathValidate()(in X509Certificate end_cert, 321 auto const ref PathValidationRestrictions restrictions, 322 const ref Vector!CertificateStore certstores) 323 { 324 Vector!X509Certificate certs; 325 certs.pushBack(cast(X509Certificate)end_cert); 326 return x509PathValidate(certs, restrictions, certstores); 327 } 328 329 /** 330 * PKIX Path Validation 331 */ 332 PathValidationResult x509PathValidate()(in X509Certificate end_cert, 333 auto const ref PathValidationRestrictions restrictions, 334 in CertificateStore store) 335 { 336 Vector!X509Certificate certs; 337 certs.pushBack(cast(X509Certificate)end_cert); 338 339 Vector!CertificateStore certstores; 340 certstores.pushBack(cast(CertificateStore) store); 341 342 return x509PathValidate(certs, restrictions, certstores); 343 } 344 345 /** 346 * PKIX Path Validation 347 */ 348 PathValidationResult x509PathValidate()(const ref Vector!X509Certificate end_certs, 349 auto const ref PathValidationRestrictions restrictions, 350 in CertificateStore store) 351 { 352 Vector!CertificateStore certstores; 353 certstores.pushBack(cast(CertificateStore)store); 354 355 return x509PathValidate(end_certs, restrictions, certstores); 356 } 357 358 X509Certificate findIssuingCert(in X509Certificate cert_, 359 ref CertificateStore end_certs, 360 const ref Vector!CertificateStore certstores) 361 { 362 363 const X509DN issuer_dn = cert_.issuerDn(); 364 365 const Vector!ubyte auth_key_id = cert_.authorityKeyId(); 366 367 if (X509Certificate cert = end_certs.findCert(issuer_dn, auth_key_id)) { 368 //logTrace("Found certificate: ", cert.toString()); 369 return cert; 370 } 371 372 foreach (certstore; certstores[]) 373 { 374 if (X509Certificate cert = certstore.findCert(issuer_dn, auth_key_id)) 375 return cert; 376 } 377 378 return X509Certificate.init; 379 } 380 381 const(X509CRL) findCrlsFor(in X509Certificate cert, 382 const ref Vector!CertificateStore certstores) 383 { 384 foreach (certstore; certstores[]) 385 { 386 if (const X509CRL crl = certstore.findCrlFor(cert)) 387 return crl; 388 } 389 390 /// todo: use crl distribution point and download the CRL 391 version(none) { 392 /* 393 const string crl_url = cert.crlDistributionPoint(); 394 if (crl_url != "") 395 { 396 std::cout << "Downloading CRL " << crl_url << ""; 397 auto http = HTTP::GET_sync(crl_url); 398 399 std::cout << http.status_message() << ""; 400 401 http.throw_unless_ok(); 402 // check the mime type 403 404 auto crl = X509CRL(http.body()); 405 406 return crl; 407 }*/ 408 } 409 410 return X509CRL.init; 411 } 412 413 Vector!( RBTreeRef!CertificateStatusCode ) 414 checkChain(const ref Vector!X509Certificate cert_path, 415 const ref PathValidationRestrictions restrictions, 416 const ref Vector!CertificateStore certstores) 417 { 418 //import core.memory : GC; GC.disable(); scope(exit) GC.enable(); 419 const RBTree!string* trusted_hashes = &restrictions.trustedHashes(); 420 421 const bool self_signed_ee_cert = (cert_path.length == 1); 422 423 X509Time current_time = X509Time(Clock.currTime(UTC())); 424 425 Vector!( Thread ) ocsp_responses; 426 427 scope(exit) foreach (Thread thr; ocsp_responses[]) { 428 ThreadMem.free(thr); 429 } 430 431 Vector!(OCSPResponse) ocsp_data = Vector!OCSPResponse(8); 432 433 Vector!( RBTreeRef!CertificateStatusCode ) cert_status = Vector!( RBTreeRef!CertificateStatusCode )( cert_path.length ); 434 435 foreach (ref e; cert_status) { 436 e.clear(); // touch 437 } 438 439 //logTrace("Cert path size: ", cert_path.length); 440 441 foreach (size_t i; 0 .. cert_path.length) 442 { 443 auto status = &*(cert_status[i]); 444 445 const bool at_self_signed_root = (i == cert_path.length - 1); 446 447 const X509Certificate subject = cert_path[i]; 448 449 const X509Certificate issuer = cert_path[at_self_signed_root ? (i) : (i + 1)]; 450 451 const CertificateStore* trusted = certstores.ptr; 452 453 Mutex mtx = new Mutex; 454 455 if (i == 0 || restrictions.ocspAllIntermediates()) { 456 457 if (certstores.length > 1) { 458 459 //version(Have_vibe_d) 460 // 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); 461 //else 462 synchronized(mtx) { 463 ocsp_data.length = i + 1; 464 OnlineCheck oc = OnlineCheck( cast(shared)mtx, cast(shared)i, cast(shared)&ocsp_data[i], cast(shared)&issuer, cast(shared)&subject, cast(shared)trusted ); 465 Thread thr = ThreadMem.alloc!Thread(&oc.run); 466 thr.start(); 467 ocsp_responses ~= thr; 468 } 469 } 470 } 471 // Check all certs for valid time range 472 if (current_time < X509Time(subject.startTime())) 473 status.insert(CertificateStatusCode.CERT_NOT_YET_VALID); 474 475 if (current_time > X509Time(subject.endTime())) 476 status.insert(CertificateStatusCode.CERT_HAS_EXPIRED); 477 478 // Check issuer constraints 479 logTrace("Check issuer constraints"); 480 // Don't require CA bit set on self-signed end entity cert 481 if (!issuer.isCACert() && !self_signed_ee_cert) 482 status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CERT_ISSUER); 483 484 if (issuer.pathLimit() < i) 485 status.insert(CertificateStatusCode.CERT_CHAIN_TOO_LONG); 486 const PublicKey issuer_key = issuer.subjectPublicKey(); 487 logTrace("Got issuer key"); 488 if (subject.checkSignature(issuer_key) == false) 489 status.insert(CertificateStatusCode.SIGNATURE_ERROR); 490 logTrace("Get estimated strength"); 491 if (issuer_key.estimatedStrength() < restrictions.minimumKeyStrength()) 492 status.insert(CertificateStatusCode.SIGNATURE_METHOD_TOO_WEAK); 493 494 logTrace("Scan untrusted hashes"); 495 // Allow untrusted hashes on self-signed roots 496 if (!trusted_hashes.empty && !at_self_signed_root) 497 { 498 if (subject.hashUsedForSignature() !in *trusted_hashes) 499 status.insert(CertificateStatusCode.UNTRUSTED_HASH); 500 } 501 } 502 logTrace("Certificates to check: ", cert_path.length); 503 foreach (size_t i; 0 .. cert_path.length - 1) 504 { 505 logTrace("Checking status ", i); 506 507 auto status = &*(cert_status[i]); 508 509 const X509Certificate subject = cert_path[i]; 510 const X509Certificate ca = cert_path[i+1]; 511 512 logTrace("Checking response ", i+1, " of ", ocsp_responses.length); 513 if (i < ocsp_responses.length) 514 { 515 try 516 { 517 ocsp_responses[i].join(); 518 if (ocsp_data.length <= i) continue; 519 OCSPResponse ocsp = ocsp_data[i]; 520 logTrace("Got response for ID#", i.to!string); 521 if (!ocsp || ocsp.empty) 522 throw new Exception("OSCP.responder is undefined"); 523 auto ocsp_status = ocsp.statusFor(ca, subject); 524 525 status.insert(ocsp_status); 526 527 logTrace("OCSP status: ", ocsp_status.to!string); 528 //std::cout << "OCSP status: " << statusString(ocsp_status) << "\n"; 529 530 // Either way we have a definitive answer, no need to check CRLs 531 if (ocsp_status == CertificateStatusCode.CERT_IS_REVOKED) 532 return cert_status.move(); 533 else if (ocsp_status == CertificateStatusCode.OCSP_RESPONSE_GOOD) 534 continue; 535 } 536 catch(Exception e) 537 { 538 logTrace("OCSP error: " ~ e.msg ~ ""); 539 } 540 } 541 542 const X509CRL crl = findCrlsFor(subject, certstores); 543 544 if (!crl) 545 { 546 if (restrictions.requireRevocationInformation()) 547 status.insert(CertificateStatusCode.NO_REVOCATION_DATA); 548 continue; 549 } 550 551 if (!ca.allowedUsage(KeyConstraints.CRL_SIGN)) 552 status.insert(CertificateStatusCode.CA_CERT_NOT_FOR_CRL_ISSUER); 553 554 if (current_time < crl.thisUpdate()) 555 status.insert(CertificateStatusCode.CRL_NOT_YET_VALID); 556 557 if (current_time > crl.nextUpdate()) 558 status.insert(CertificateStatusCode.CRL_HAS_EXPIRED); 559 Unique!PublicKey pubkey = ca.subjectPublicKey(); 560 if (crl.checkSignature(*pubkey) == false) 561 status.insert(CertificateStatusCode.CRL_BAD_SIGNATURE); 562 563 if (crl.isRevoked(subject)) 564 status.insert(CertificateStatusCode.CERT_IS_REVOKED); 565 } 566 567 if (self_signed_ee_cert) 568 cert_status.back().insert(CertificateStatusCode.CANNOT_ESTABLISH_TRUST); 569 570 return cert_status.move(); 571 }