1 // 2 // System.Security.Cryptography.X509Certificates.X509ChainImplMono 3 // 4 // Author: 5 // Sebastien Pouliot <sebastien@ximian.com> 6 // 7 // (C) 2003 Motus Technologies Inc. (http://www.motus.com) 8 // Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com) 9 // Copyright (C) 2011 Xamarin Inc. (http://www.xamarin.com) 10 // 11 // Permission is hereby granted, free of charge, to any person obtaining 12 // a copy of this software and associated documentation files (the 13 // "Software"), to deal in the Software without restriction, including 14 // without limitation the rights to use, copy, modify, merge, publish, 15 // distribute, sublicense, and/or sell copies of the Software, and to 16 // permit persons to whom the Software is furnished to do so, subject to 17 // the following conditions: 18 // 19 // The above copyright notice and this permission notice shall be 20 // included in all copies or substantial portions of the Software. 21 // 22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 // 30 31 #if SECURITY_DEP 32 33 #if MONO_SECURITY_ALIAS 34 extern alias MonoSecurity; 35 using MX = MonoSecurity::Mono.Security.X509; 36 #else 37 using MX = Mono.Security.X509; 38 #endif 39 40 using System.Collections; 41 using System.Text; 42 43 namespace System.Security.Cryptography.X509Certificates { 44 45 internal class X509ChainImplMono : X509ChainImpl 46 { 47 private StoreLocation location; 48 private X509ChainElementCollection elements; 49 private X509ChainPolicy policy; 50 private X509ChainStatus[] status; 51 52 static X509ChainStatus[] Empty = new X509ChainStatus [0]; 53 54 // RFC3280 variables 55 private int max_path_length; 56 private X500DistinguishedName working_issuer_name; 57 // private string working_public_key_algorithm; 58 private AsymmetricAlgorithm working_public_key; 59 60 // other flags 61 private X509ChainElement bce_restriction; 62 63 // constructors 64 X509ChainImplMono()65 public X509ChainImplMono () 66 : this (false) 67 { 68 } 69 X509ChainImplMono(bool useMachineContext)70 public X509ChainImplMono (bool useMachineContext) 71 { 72 location = useMachineContext ? StoreLocation.LocalMachine : StoreLocation.CurrentUser; 73 elements = new X509ChainElementCollection (); 74 policy = new X509ChainPolicy (); 75 } 76 77 [MonoTODO ("Mono's X509Chain is fully managed. All handles are invalid.")] X509ChainImplMono(IntPtr chainContext)78 public X509ChainImplMono (IntPtr chainContext) 79 { 80 // CryptoAPI compatibility (unmanaged handle) 81 throw new NotSupportedException (); 82 } 83 84 public override bool IsValid { 85 get { return true; } 86 } 87 88 public override IntPtr Handle { 89 get { return IntPtr.Zero; } 90 } 91 92 // properties 93 94 public override X509ChainElementCollection ChainElements { 95 get { return elements; } 96 } 97 98 public override X509ChainPolicy ChainPolicy { 99 get { return policy; } 100 set { policy = value; } 101 } 102 103 public override X509ChainStatus[] ChainStatus { 104 get { 105 if (status == null) 106 return Empty; 107 return status; 108 } 109 } 110 111 // methods 112 113 [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")] Build(X509Certificate2 certificate)114 public override bool Build (X509Certificate2 certificate) 115 { 116 if (certificate == null) 117 throw new ArgumentException ("certificate"); 118 119 Reset (); 120 X509ChainStatusFlags flag; 121 try { 122 flag = BuildChainFrom (certificate); 123 ValidateChain (flag); 124 } 125 catch (CryptographicException ce) { 126 throw new ArgumentException ("certificate", ce); 127 } 128 129 X509ChainStatusFlags total = X509ChainStatusFlags.NoError; 130 ArrayList list = new ArrayList (); 131 // build "global" ChainStatus from the ChainStatus of every ChainElements 132 foreach (X509ChainElement ce in elements) { 133 foreach (X509ChainStatus cs in ce.ChainElementStatus) { 134 // we MUST avoid duplicates in the "global" list 135 if ((total & cs.Status) != cs.Status) { 136 list.Add (cs); 137 total |= cs.Status; 138 } 139 } 140 } 141 // and if required add some 142 if (flag != X509ChainStatusFlags.NoError) { 143 list.Insert (0, new X509ChainStatus (flag)); 144 } 145 status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus)); 146 147 // (fast path) this ignore everything we have checked 148 if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags)) 149 return true; 150 151 bool result = true; 152 // now check if exclude some verification for the "end result" (boolean) 153 foreach (X509ChainStatus cs in status) { 154 switch (cs.Status) { 155 case X509ChainStatusFlags.UntrustedRoot: 156 case X509ChainStatusFlags.PartialChain: 157 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.AllowUnknownCertificateAuthority) != 0); 158 break; 159 case X509ChainStatusFlags.NotTimeValid: 160 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeValid) != 0); 161 break; 162 // FIXME - from here we needs new test cases for all cases 163 case X509ChainStatusFlags.NotTimeNested: 164 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreNotTimeNested) != 0); 165 break; 166 case X509ChainStatusFlags.InvalidBasicConstraints: 167 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidBasicConstraints) != 0); 168 break; 169 case X509ChainStatusFlags.InvalidPolicyConstraints: 170 case X509ChainStatusFlags.NoIssuanceChainPolicy: 171 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidPolicy) != 0); 172 break; 173 case X509ChainStatusFlags.InvalidNameConstraints: 174 case X509ChainStatusFlags.HasNotSupportedNameConstraint: 175 case X509ChainStatusFlags.HasNotPermittedNameConstraint: 176 case X509ChainStatusFlags.HasExcludedNameConstraint: 177 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreInvalidName) != 0); 178 break; 179 case X509ChainStatusFlags.InvalidExtension: 180 // not sure ?!? 181 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0); 182 break; 183 // 184 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreRootRevocationUnknown) != 0) 185 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreEndRevocationUnknown) != 0) 186 case X509ChainStatusFlags.CtlNotTimeValid: 187 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlNotTimeValid) != 0); 188 break; 189 case X509ChainStatusFlags.CtlNotSignatureValid: 190 // ? 191 break; 192 // ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreCtlSignerRevocationUnknown) != 0); 193 case X509ChainStatusFlags.CtlNotValidForUsage: 194 // FIXME - does IgnoreWrongUsage apply to CTL (it doesn't have Ctl in it's name like the others) 195 result &= ((ChainPolicy.VerificationFlags & X509VerificationFlags.IgnoreWrongUsage) != 0); 196 break; 197 default: 198 result = false; 199 break; 200 } 201 // once we have one failure there's no need to check further 202 if (!result) 203 return false; 204 } 205 206 // every "problem" was excluded 207 return true; 208 } 209 Reset()210 public override void Reset () 211 { 212 // note: this call doesn't Reset the X509ChainPolicy 213 if ((status != null) && (status.Length != 0)) 214 status = null; 215 if (elements.Count > 0) 216 elements.Clear (); 217 if (user_root_store != null) { 218 user_root_store.Close (); 219 user_root_store = null; 220 } 221 if (root_store != null) { 222 root_store.Close (); 223 root_store = null; 224 } 225 if (user_ca_store != null) { 226 user_ca_store.Close (); 227 user_ca_store = null; 228 } 229 if (ca_store != null) { 230 ca_store.Close (); 231 ca_store = null; 232 } 233 roots = null; 234 cas = null; 235 collection = null; 236 bce_restriction = null; 237 working_public_key = null; 238 } 239 240 // private stuff 241 242 private X509Certificate2Collection roots; 243 private X509Certificate2Collection cas; 244 private X509Store root_store; 245 private X509Store ca_store; 246 private X509Store user_root_store; 247 private X509Store user_ca_store; 248 249 private X509Certificate2Collection Roots { 250 get { 251 if (roots == null) { 252 X509Certificate2Collection c = new X509Certificate2Collection (); 253 X509Store store = LMRootStore; 254 if (location == StoreLocation.CurrentUser) 255 c.AddRange (UserRootStore.Certificates); 256 c.AddRange (store.Certificates); 257 roots = c; 258 } 259 return roots; 260 } 261 } 262 263 private X509Certificate2Collection CertificateAuthorities { 264 get { 265 if (cas == null) { 266 X509Certificate2Collection c = new X509Certificate2Collection (); 267 X509Store store = LMCAStore; 268 if (location == StoreLocation.CurrentUser) 269 c.AddRange (UserCAStore.Certificates); 270 c.AddRange (store.Certificates); 271 cas = c; 272 } 273 return cas; 274 } 275 } 276 277 private X509Store LMRootStore { 278 get { 279 if (root_store == null) { 280 root_store = new X509Store (StoreName.Root, StoreLocation.LocalMachine); 281 try { 282 root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); 283 } catch { 284 } 285 } 286 return root_store; 287 } 288 } 289 290 private X509Store UserRootStore { 291 get { 292 if (user_root_store == null) { 293 user_root_store = new X509Store (StoreName.Root, StoreLocation.CurrentUser); 294 try { 295 user_root_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); 296 } catch { 297 } 298 } 299 return user_root_store; 300 } 301 } 302 303 private X509Store LMCAStore { 304 get { 305 if (ca_store == null) { 306 ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.LocalMachine); 307 try { 308 ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); 309 } catch { 310 } 311 } 312 return ca_store; 313 } 314 } 315 316 private X509Store UserCAStore { 317 get { 318 if (user_ca_store == null) { 319 user_ca_store = new X509Store (StoreName.CertificateAuthority, StoreLocation.CurrentUser); 320 try { 321 user_ca_store.Open (OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly); 322 } catch { 323 } 324 } 325 return user_ca_store; 326 } 327 } 328 // *** certificate chain/path building stuff *** 329 330 private X509Certificate2Collection collection; 331 332 // we search local user (default) or machine certificate store 333 // and in the extra certificate supplied in ChainPolicy.ExtraStore 334 private X509Certificate2Collection CertificateCollection { 335 get { 336 if (collection == null) { 337 collection = new X509Certificate2Collection (ChainPolicy.ExtraStore); 338 collection.AddRange (Roots); 339 collection.AddRange (CertificateAuthorities); 340 } 341 return collection; 342 } 343 } 344 345 // This is a non-recursive chain/path building algorithm. 346 // 347 // At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they 348 // affect the path building (other errors are verification errors). 349 // 350 // Note that the order match the one we need to match MS and not the one defined in RFC3280, 351 // we also include the trusted root certificate (trust anchor in RFC3280) in the list. 352 // (this isn't an issue, just keep that in mind if you look at the source and the RFC) BuildChainFrom(X509Certificate2 certificate)353 private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate) 354 { 355 elements.Add (certificate); 356 357 while (!IsChainComplete (certificate)) { 358 certificate = FindParent (certificate); 359 360 if (certificate == null) 361 return X509ChainStatusFlags.PartialChain; 362 363 if (elements.Contains (certificate)) 364 return X509ChainStatusFlags.Cyclic; 365 366 elements.Add (certificate); 367 } 368 369 // roots may be supplied (e.g. in the ExtraStore) so we need to confirm their 370 // trustiness (what a cute word) in the trusted root collection 371 if (!Roots.Contains (certificate)) 372 elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot; 373 374 return X509ChainStatusFlags.NoError; 375 } 376 377 SelectBestFromCollection(X509Certificate2 child, X509Certificate2Collection c)378 private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c) 379 { 380 switch (c.Count) { 381 case 0: 382 return null; 383 case 1: 384 return c [0]; 385 default: 386 // multiple candidate, keep only the ones that are still valid 387 X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false); 388 switch (time_valid.Count) { 389 case 0: 390 // that's too restrictive, let's revert and try another thing... 391 time_valid = c; 392 break; 393 case 1: 394 return time_valid [0]; 395 default: 396 break; 397 } 398 399 // again multiple candidates, let's find the AKI that match the SKI (if we have one) 400 string aki = GetAuthorityKeyIdentifier (child); 401 if (String.IsNullOrEmpty (aki)) { 402 return time_valid [0]; // FIXME: out of luck, you get the first one 403 } 404 foreach (X509Certificate2 parent in time_valid) { 405 string ski = GetSubjectKeyIdentifier (parent); 406 // if both id are available then they must match 407 if (aki == ski) 408 return parent; 409 } 410 return time_valid [0]; // FIXME: out of luck, you get the first one 411 } 412 } 413 FindParent(X509Certificate2 certificate)414 private X509Certificate2 FindParent (X509Certificate2 certificate) 415 { 416 X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false); 417 string aki = GetAuthorityKeyIdentifier (certificate); 418 if ((aki != null) && (aki.Length > 0)) { 419 subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false)); 420 } 421 X509Certificate2 parent = SelectBestFromCollection (certificate, subset); 422 // if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs) 423 return certificate.Equals (parent) ? null : parent; 424 } 425 IsChainComplete(X509Certificate2 certificate)426 private bool IsChainComplete (X509Certificate2 certificate) 427 { 428 // the chain is complete if we have a self-signed certificate 429 if (!IsSelfIssued (certificate)) 430 return false; 431 432 // we're very limited to what we can do without certificate extensions 433 if (certificate.Version < 3) 434 return true; 435 436 // check that Authority Key Identifier == Subject Key Identifier 437 // e.g. it will be different if a self-signed certificate is part (not the end) of the chain 438 string ski = GetSubjectKeyIdentifier (certificate); 439 if (String.IsNullOrEmpty (ski)) 440 return true; 441 string aki = GetAuthorityKeyIdentifier (certificate); 442 if (String.IsNullOrEmpty (aki)) 443 return true; 444 // if both id are available then they must match 445 return (aki == ski); 446 } 447 448 // check for "self-issued" certificate - without verifying the signature 449 // note that self-issued doesn't always mean it's a root certificate! IsSelfIssued(X509Certificate2 certificate)450 private bool IsSelfIssued (X509Certificate2 certificate) 451 { 452 return (certificate.Issuer == certificate.Subject); 453 } 454 455 456 // *** certificate chain/path validation stuff *** 457 458 // Currently a subset of RFC3280 (hopefully a full implementation someday) ValidateChain(X509ChainStatusFlags flag)459 private void ValidateChain (X509ChainStatusFlags flag) 460 { 461 // 'n' should be the root certificate... 462 int n = elements.Count - 1; 463 X509Certificate2 certificate = elements [n].Certificate; 464 465 // ... and, if so, must be treated outside the chain... 466 if (((flag & X509ChainStatusFlags.PartialChain) == 0)) { 467 Process (n); 468 // deal with the case where the chain == the root certificate 469 // (which isn't for RFC3280) part of the chain 470 if (n == 0) { 471 elements [0].UncompressFlags (); 472 return; 473 } 474 // skip the root certificate when processing the chain (in 6.1.3) 475 n--; 476 } 477 // ... unless the chain is a partial one (then we start with that one) 478 479 // 6.1.1 - Inputs 480 // 6.1.1.a - a prospective certificate path of length n (i.e. elements) 481 // 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime) 482 // 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy) 483 // 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain) 484 // 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API) 485 // 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API) 486 // 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API) 487 488 // 6.1.2 - Initialization (incomplete) 489 // 6.1.2.a-f - policy stuff, some TODO, some not supported 490 // 6.1.2.g - working public key algorithm 491 // working_public_key_algorithm = certificate.PublicKey.Oid.Value; 492 // 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data 493 working_public_key = certificate.PublicKey.Key; 494 // 6.1.2.j - working issuer name 495 working_issuer_name = certificate.IssuerName; 496 // 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and 497 // may be reduced to the value in the path length constraint field 498 max_path_length = n; 499 500 // 6.1.3 - Basic Certificate Processing 501 // note: loop looks reversed (the list is) but we process this part just like RFC3280 does 502 for (int i = n; i > 0; i--) { 503 Process (i); 504 // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop) 505 PrepareForNextCertificate (i); 506 } 507 Process (0); 508 509 // 6.1.3.a.3 - revocation checks 510 CheckRevocationOnChain (flag); 511 512 // 6.1.5 - Wrap-up procedure 513 WrapUp (); 514 } 515 Process(int n)516 private void Process (int n) 517 { 518 X509ChainElement element = elements [n]; 519 X509Certificate2 certificate = element.Certificate; 520 521 // pre-step: DSA certificates may inherit the parameters of their CA 522 if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) { 523 if (certificate.MonoCertificate.KeyAlgorithmParameters == null) { 524 X509Certificate2 parent = elements [n+1].Certificate; 525 certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters; 526 } 527 } 528 529 bool root = (working_public_key == null); 530 // 6.1.3.a.1 - check signature (with special case to deal with root certificates) 531 if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) { 532 // another special case where only an end-entity is available and can't be verified. 533 // In this case we do not report an invalid signature (since this is unknown) 534 if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) { 535 element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid; 536 } 537 } 538 539 // 6.1.3.a.2 - check validity period 540 if ((ChainPolicy.VerificationTime < certificate.NotBefore) || 541 (ChainPolicy.VerificationTime > certificate.NotAfter)) { 542 element.StatusFlags |= X509ChainStatusFlags.NotTimeValid; 543 } 544 // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure) 545 546 // note: most of them don't apply to the root certificate 547 if (root) { 548 return; 549 } 550 551 // 6.1.3.a.3 - revocation check (we're doing at the last stage) 552 // note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job) 553 554 // 6.1.3.a.4 - check certificate issuer name 555 if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) { 556 // NOTE: this is not the "right" error flag, but it's the closest one defined 557 element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints; 558 } 559 560 if (!IsSelfIssued (certificate) && (n != 0)) { 561 // TODO 6.1.3.b - subject name in the permitted_subtrees ... 562 // TODO 6.1.3.c - subject name not within excluded_subtrees... 563 564 // TODO - check for X509ChainStatusFlags.InvalidNameConstraint 565 // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint 566 // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint 567 // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint 568 } 569 570 // TODO 6.1.3.d - check if certificate policies extension is present 571 //if (false) { 572 // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints 573 // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy 574 575 // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy 576 577 //} else { 578 // TODO 6.1.3.e - set valid_policy_tree to NULL 579 //} 580 581 // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL 582 } 583 584 // CTL == Certificate Trust List / NOT SUPPORTED 585 // TODO - check for X509ChainStatusFlags.CtlNotTimeValid 586 // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid 587 // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage 588 PrepareForNextCertificate(int n)589 private void PrepareForNextCertificate (int n) 590 { 591 X509ChainElement element = elements [n]; 592 X509Certificate2 certificate = element.Certificate; 593 594 // TODO 6.1.4.a-b 595 596 // 6.1.4.c 597 working_issuer_name = certificate.SubjectName; 598 // 6.1.4.d-e - our key includes both the public key and it's parameters 599 working_public_key = certificate.PublicKey.Key; 600 // 6.1.4.f 601 // working_public_key_algorithm = certificate.PublicKey.Oid.Value; 602 603 // TODO 6.1.4.g-j 604 605 // 6.1.4.k - Verify that the certificate is a CA certificate 606 X509BasicConstraintsExtension bce = (certificate.Extensions["2.5.29.19"] as X509BasicConstraintsExtension); 607 if (bce != null) { 608 if (!bce.CertificateAuthority) { 609 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints; 610 } 611 } else if (certificate.Version >= 3) { 612 // recent (v3+) CA certificates must include BCE 613 element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints; 614 } 615 616 // 6.1.4.l - if the certificate isn't self-issued... 617 if (!IsSelfIssued (certificate)) { 618 // ... verify that max_path_length > 0 619 if (max_path_length > 0) { 620 max_path_length--; 621 } else { 622 // to match MS the reported status must be against the certificate 623 // with the BCE and not where the path is too long. It also means 624 // that this condition has to be reported only once 625 if (bce_restriction != null) { 626 bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints; 627 } 628 } 629 } 630 631 // 6.1.4.m - if pathLengthConstraint is present... 632 if ((bce != null) && (bce.HasPathLengthConstraint)) { 633 // ... and is less that max_path_length, set max_path_length to it's value 634 if (bce.PathLengthConstraint < max_path_length) { 635 max_path_length = bce.PathLengthConstraint; 636 bce_restriction = element; 637 } 638 } 639 640 // 6.1.4.n - if key usage extension is present... 641 X509KeyUsageExtension kue = (certificate.Extensions["2.5.29.15"] as X509KeyUsageExtension); 642 if (kue != null) { 643 // ... verify keyCertSign is set 644 X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign; 645 if ((kue.KeyUsages & success) != success) 646 element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage; 647 } 648 649 // 6.1.4.o - recognize and process other critical extension present in the certificate 650 ProcessCertificateExtensions (element); 651 } 652 WrapUp()653 private void WrapUp () 654 { 655 X509ChainElement element = elements [0]; 656 X509Certificate2 certificate = element.Certificate; 657 658 // 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0 659 if (IsSelfIssued (certificate)) { 660 // TODO... decrement explicit_policy by 1 661 } 662 663 // 6.1.5.b - TODO 664 665 // 6.1.5.c,d,e - not required by the X509Chain implementation 666 667 // 6.1.5.f - recognize and process other critical extension present in the certificate 668 ProcessCertificateExtensions (element); 669 670 // 6.1.5.g - TODO 671 672 // uncompressed the flags into several elements 673 for (int i = elements.Count - 1; i >= 0; i--) { 674 elements [i].UncompressFlags (); 675 } 676 } 677 ProcessCertificateExtensions(X509ChainElement element)678 private void ProcessCertificateExtensions (X509ChainElement element) 679 { 680 foreach (X509Extension ext in element.Certificate.Extensions) { 681 if (ext.Critical) { 682 switch (ext.Oid.Value) { 683 case "2.5.29.15": // X509KeyUsageExtension 684 case "2.5.29.19": // X509BasicConstraintsExtension 685 // we processed this extension 686 break; 687 default: 688 // note: Under Windows XP MS implementation seems to ignore 689 // certificate with unknown critical extensions. 690 element.StatusFlags |= X509ChainStatusFlags.InvalidExtension; 691 break; 692 } 693 } 694 } 695 } 696 IsSignedWith(X509Certificate2 signed, AsymmetricAlgorithm pubkey)697 private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey) 698 { 699 if (pubkey == null) 700 return false; 701 // Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure 702 var mx = signed.MonoCertificate; 703 return (mx.VerifySignature (pubkey)); 704 } 705 GetSubjectKeyIdentifier(X509Certificate2 certificate)706 private string GetSubjectKeyIdentifier (X509Certificate2 certificate) 707 { 708 X509SubjectKeyIdentifierExtension ski = (certificate.Extensions["2.5.29.14"] as X509SubjectKeyIdentifierExtension); 709 return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier; 710 } 711 712 // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension GetAuthorityKeyIdentifier(X509Certificate2 certificate)713 static string GetAuthorityKeyIdentifier (X509Certificate2 certificate) 714 { 715 return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]); 716 } 717 718 // but anyway System.dll v2 doesn't expose CRL in any way so... GetAuthorityKeyIdentifier(MX.X509Crl crl)719 static string GetAuthorityKeyIdentifier (MX.X509Crl crl) 720 { 721 return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]); 722 } 723 GetAuthorityKeyIdentifier(MX.X509Extension ext)724 static string GetAuthorityKeyIdentifier (MX.X509Extension ext) 725 { 726 if (ext == null) 727 return String.Empty; 728 var aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext); 729 byte[] id = aki.Identifier; 730 if (id == null) 731 return String.Empty; 732 StringBuilder sb = new StringBuilder (); 733 foreach (byte b in id) 734 sb.Append (b.ToString ("X02")); 735 return sb.ToString (); 736 } 737 738 // we check the revocation only once we have built the complete chain CheckRevocationOnChain(X509ChainStatusFlags flag)739 private void CheckRevocationOnChain (X509ChainStatusFlags flag) 740 { 741 bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0); 742 bool online; 743 744 switch (ChainPolicy.RevocationMode) { 745 case X509RevocationMode.Online: 746 // default 747 online = true; 748 break; 749 case X509RevocationMode.Offline: 750 online = false; 751 break; 752 case X509RevocationMode.NoCheck: 753 return; 754 default: 755 throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode.")); 756 } 757 758 bool unknown = partial; 759 // from the root down to the end-entity 760 for (int i = elements.Count - 1; i >= 0; i--) { 761 bool check = true; 762 763 switch (ChainPolicy.RevocationFlag) { 764 case X509RevocationFlag.EndCertificateOnly: 765 check = (i == 0); 766 break; 767 case X509RevocationFlag.EntireChain: 768 check = true; 769 break; 770 case X509RevocationFlag.ExcludeRoot: 771 // default 772 check = (i != (elements.Count - 1)); 773 // anyway, who's gonna sign that the root is invalid ? 774 break; 775 } 776 777 X509ChainElement element = elements [i]; 778 779 // we can't assume the revocation status if the certificate is bad (e.g. invalid signature) 780 if (!unknown) 781 unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0); 782 783 if (unknown) { 784 // we can skip the revocation checks as we can't be sure of them anyway 785 element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown; 786 element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation; 787 } else if (check && !partial && !IsSelfIssued (element.Certificate)) { 788 // check for revocation (except for the trusted root and self-issued certs) 789 element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online); 790 // if revoked, then all others following in the chain are unknown... 791 unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0); 792 } 793 } 794 } 795 796 // This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas... CheckRevocation(X509Certificate2 certificate, int ca, bool online)797 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online) 798 { 799 X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown; 800 X509ChainElement element = elements [ca]; 801 X509Certificate2 ca_cert = element.Certificate; 802 803 // find the CRL from the "right" CA 804 while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) { 805 // try with this self-issued 806 result = CheckRevocation (certificate, ca_cert, online); 807 if (result != X509ChainStatusFlags.RevocationStatusUnknown) 808 break; 809 ca++; 810 element = elements [ca]; 811 ca_cert = element.Certificate; 812 } 813 if (result == X509ChainStatusFlags.RevocationStatusUnknown) 814 result = CheckRevocation (certificate, ca_cert, online); 815 return result; 816 } 817 CheckRevocation(X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)818 private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online) 819 { 820 // change this if/when we support OCSP 821 X509KeyUsageExtension kue = (ca_cert.Extensions["2.5.29.15"] as X509KeyUsageExtension); 822 if (kue != null) { 823 // ... verify CrlSign is set 824 X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign; 825 if ((kue.KeyUsages & success) != success) { 826 // FIXME - we should try to find an alternative CA that has the CrlSign bit 827 return X509ChainStatusFlags.RevocationStatusUnknown; 828 } 829 } 830 831 MX.X509Crl crl = FindCrl (ca_cert); 832 833 if ((crl == null) && online) { 834 // FIXME - download and install new CRL 835 // then you get a second chance 836 // crl = FindCrl (ca_cert, ref valid, ref out_of_date); 837 838 // We need to get the subjectAltName and an URI from there (or use OCSP) 839 // X509KeyUsageExtension subjectAltName = (ca_cert.Extensions["2.5.29.17"] as X509KeyUsageExtension); 840 } 841 842 if (crl != null) { 843 // validate the digital signature on the CRL using the CA public key 844 // note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates 845 // checks and we loose the "why" of the failure 846 // note #2: we do this before other tests as an invalid signature could be a hacked CRL 847 // (so anything within can't be trusted) 848 if (!crl.VerifySignature (ca_cert.PublicKey.Key)) { 849 return X509ChainStatusFlags.RevocationStatusUnknown; 850 } 851 852 MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate); 853 if (entry != null) { 854 // We have an entry for this CRL that includes an unknown CRITICAL extension 855 // See [X.509 7.3] NOTE 4 856 if (!ProcessCrlEntryExtensions (entry)) 857 return X509ChainStatusFlags.Revoked; 858 859 // FIXME - a little more is involved 860 if (entry.RevocationDate <= ChainPolicy.VerificationTime) 861 return X509ChainStatusFlags.Revoked; 862 } 863 864 // are we overdue for a CRL update ? if so we can't be sure of any certificate status 865 if (crl.NextUpdate < ChainPolicy.VerificationTime) 866 return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation; 867 868 // we have a CRL that includes an unknown CRITICAL extension 869 // we put this check at the end so we do not "hide" any Revoked flags 870 if (!ProcessCrlExtensions (crl)) { 871 return X509ChainStatusFlags.RevocationStatusUnknown; 872 } 873 } else { 874 return X509ChainStatusFlags.RevocationStatusUnknown; 875 } 876 877 return X509ChainStatusFlags.NoError; 878 } 879 CheckCrls(string subject, string ski, MX.X509Store store)880 static MX.X509Crl CheckCrls (string subject, string ski, MX.X509Store store) 881 { 882 if (store == null) 883 return null; 884 885 var crls = store.Crls; 886 foreach (MX.X509Crl crl in crls) { 887 if (crl.IssuerName == subject && (ski.Length == 0 || ski == GetAuthorityKeyIdentifier (crl))) 888 return crl; 889 } 890 return null; // No CRL found 891 } 892 FindCrl(X509Certificate2 caCertificate)893 private MX.X509Crl FindCrl (X509Certificate2 caCertificate) 894 { 895 string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None); 896 string ski = GetSubjectKeyIdentifier (caCertificate); 897 898 // consider that the LocalMachine directories could not exists... and cannot be created by the user 899 MX.X509Crl result = CheckCrls (subject, ski, LMCAStore.Store); 900 if (result != null) 901 return result; 902 if (location == StoreLocation.CurrentUser) { 903 result = CheckCrls (subject, ski, UserCAStore.Store); 904 if (result != null) 905 return result; 906 } 907 908 // consider that the LocalMachine directories could not exists... and cannot be created by the user 909 result = CheckCrls (subject, ski, LMRootStore.Store); 910 if (result != null) 911 return result; 912 if (location == StoreLocation.CurrentUser) { 913 result = CheckCrls (subject, ski, UserRootStore.Store); 914 if (result != null) 915 return result; 916 } 917 return null; 918 } 919 ProcessCrlExtensions(MX.X509Crl crl)920 private bool ProcessCrlExtensions (MX.X509Crl crl) 921 { 922 foreach (MX.X509Extension ext in crl.Extensions) { 923 if (ext.Critical) { 924 switch (ext.Oid) { 925 case "2.5.29.20": // cRLNumber 926 case "2.5.29.35": // authorityKeyIdentifier 927 // we processed/know about this extension 928 break; 929 default: 930 return false; 931 } 932 } 933 } 934 return true; 935 } 936 ProcessCrlEntryExtensions(MX.X509Crl.X509CrlEntry entry)937 private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry) 938 { 939 foreach (MX.X509Extension ext in entry.Extensions) { 940 if (ext.Critical) { 941 switch (ext.Oid) { 942 case "2.5.29.21": // cRLReason 943 // we processed/know about this extension 944 break; 945 default: 946 return false; 947 } 948 } 949 } 950 return true; 951 } 952 } 953 } 954 #endif 955