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