diff options
author | Sebastien Pouliot <sebastien@ximian.com> | 2006-12-08 00:13:23 +0300 |
---|---|---|
committer | Sebastien Pouliot <sebastien@ximian.com> | 2006-12-08 00:13:23 +0300 |
commit | 777237d9f4d64cb3f36b2316c2ac0ace760b3dd4 (patch) | |
tree | 6af8c2de7b2e84e7d6bb7896d99d02b3550d30ef /mcs/class/System/System.Security.Cryptography.X509Certificates | |
parent | 2d3ee79bc135800845409fbc340491ad89f69da9 (diff) |
2006-12-07 Sebastien Pouliot <sebastien@ximian.com>
* X500DistinguishedName.cs: Add an internal method to compare
(canonized) DN so the class can be used in X509Chain.
* X509Certificate2.cs: Expose the internal certificate (from Mono.
Security.dll) as X509Certificate2 isn't complete enough to implement
chaining.
* X509Chain.cs: A (working) *subset( of RFC3280 path building and
validation.
* X509ChainElementCollection.cs: Add help method Contains and change
Add not to require a flag parameter.
* X509ChainElement.cs: Keeps flags compressed (as flags!) and add
a method to uncompress them when validation is complete.
* X509Store.cs: Expose the internal store (from Mono.Security.dll) as
internal. Map Trust and Root as the same store (for compatibility).
svn path=/trunk/mcs/; revision=69204
Diffstat (limited to 'mcs/class/System/System.Security.Cryptography.X509Certificates')
7 files changed, 760 insertions, 153 deletions
diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/ChangeLog b/mcs/class/System/System.Security.Cryptography.X509Certificates/ChangeLog index e9ff6713f7b..3f9b05e5c38 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/ChangeLog +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/ChangeLog @@ -1,3 +1,19 @@ +2006-12-07 Sebastien Pouliot <sebastien@ximian.com> + + * X500DistinguishedName.cs: Add an internal method to compare + (canonized) DN so the class can be used in X509Chain. + * X509Certificate2.cs: Expose the internal certificate (from Mono. + Security.dll) as X509Certificate2 isn't complete enough to implement + chaining. + * X509Chain.cs: A (working) *subset( of RFC3280 path building and + validation. + * X509ChainElementCollection.cs: Add help method Contains and change + Add not to require a flag parameter. + * X509ChainElement.cs: Keeps flags compressed (as flags!) and add + a method to uncompress them when validation is complete. + * X509Store.cs: Expose the internal store (from Mono.Security.dll) as + internal. Map Trust and Root as the same store (for compatibility). + 2006-11-24 Sebastien Pouliot <sebastien@ximian.com> * X509Certificate2.cs: Modified Verify to use CryptoConfig to create diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/X500DistinguishedName.cs b/mcs/class/System/System.Security.Cryptography.X509Certificates/X500DistinguishedName.cs index 5d104eff3a4..3c1b217b1bf 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/X500DistinguishedName.cs +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/X500DistinguishedName.cs @@ -176,6 +176,51 @@ namespace System.Security.Cryptography.X509Certificates { ASN1 sequence = new ASN1 (RawData); name = MX.X501.ToString (sequence, true, ", ", true); } + + private static string Canonize (string s)
+ {
+ int i = s.IndexOf ('=');
+ StringBuilder r = new StringBuilder (s.Substring (0, i + 1));
+ // skip any white space starting the value
+ while (Char.IsWhiteSpace (s, ++i));
+ // ensure we skip white spaces at the end of the value
+ s = s.TrimEnd ();
+ // keep track of internal multiple spaces
+ bool space = false;
+ for (; i < s.Length; i++) {
+ if (space) {
+ space = Char.IsWhiteSpace (s, i);
+ if (space)
+ continue;
+ }
+ if (Char.IsWhiteSpace (s, i))
+ space = true;
+ r.Append (Char.ToUpperInvariant (s[i]));
+ }
+ return r.ToString ();
+ }
+
+ // of all X500DistinguishedNameFlags flags nothing can do a "correct" comparison :|
+ internal static bool AreEqual (X500DistinguishedName name1, X500DistinguishedName name2)
+ {
+ if (name1 == null)
+ return (name2 == null);
+ if (name2 == null)
+ return false;
+
+ X500DistinguishedNameFlags flags = X500DistinguishedNameFlags.UseNewLines | X500DistinguishedNameFlags.DoNotUseQuotes;
+ string[] split = new string[] { Environment.NewLine };
+ string[] parts1 = name1.Decode (flags).Split (split, StringSplitOptions.RemoveEmptyEntries);
+ string[] parts2 = name2.Decode (flags).Split (split, StringSplitOptions.RemoveEmptyEntries);
+ if (parts1.Length != parts2.Length)
+ return false;
+
+ for (int i = 0; i < parts1.Length; i++) {
+ if (Canonize (parts1[i]) != Canonize (parts2[i]))
+ return false;
+ }
+ return true;
+ }
} } diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Certificate2.cs b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Certificate2.cs index ebc1423f3c4..9f63de87287 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Certificate2.cs +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Certificate2.cs @@ -632,6 +632,13 @@ namespace System.Security.Cryptography.X509Certificates { byte[] data = Load (fileName); return GetCertContentType (data); } + + // internal stuff because X509Certificate2 isn't complete enough + // (maybe X509Certificate3 will be better?) + + internal MX.X509Certificate MonoCertificate { + get { return _cert; } + } } } diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Chain.cs b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Chain.cs index 4676e927278..e7358f099f5 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Chain.cs +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Chain.cs @@ -30,12 +30,14 @@ #if NET_2_0 && SECURITY_DEP using System.Collections; +using System.Text; + +using MX = Mono.Security.X509; namespace System.Security.Cryptography.X509Certificates { public class X509Chain { - // Set to internal to remove a warning private StoreLocation location; private X509ChainElementCollection elements; private X509ChainPolicy policy; @@ -43,6 +45,15 @@ namespace System.Security.Cryptography.X509Certificates { static X509ChainStatus[] Empty = new X509ChainStatus [0]; + // RFC3280 variables + private int max_path_length; + private X500DistinguishedName working_issuer_name; + private string working_public_key_algorithm; + private AsymmetricAlgorithm working_public_key; + + // other flags + private X509ChainElement bce_restriction; + // constructors public X509Chain () @@ -90,28 +101,32 @@ namespace System.Security.Cryptography.X509Certificates { // methods - [MonoTODO ("Work in progress")] + [MonoTODO ("Not totally RFC3280 compliant, but neither is MS implementation...")] public bool Build (X509Certificate2 certificate) { if (certificate == null) throw new ArgumentException ("certificate"); Reset (); - X509ChainStatusFlags flag; try { - flag = BuildFrom (certificate); + flag = BuildChainFrom (certificate); + ValidateChain (flag); } catch (CryptographicException ce) { throw new ArgumentException ("certificate", ce); } + X509ChainStatusFlags total = X509ChainStatusFlags.NoError; ArrayList list = new ArrayList (); // build "global" ChainStatus from the ChainStatus of every ChainElements
- foreach (X509ChainElement ce in elements) {
- foreach (X509ChainStatus cs in ce.ChainElementStatus) {
- // FIXME - avoid duplicates ?
- list.Add (cs);
+ foreach (X509ChainElement ce in elements) { + foreach (X509ChainStatus cs in ce.ChainElementStatus) { + // we MUST avoid duplicates in the "global" list
+ if ((total & cs.Status) != cs.Status) {
+ list.Add (cs); + total |= cs.Status; + }
}
} // and if required add some @@ -121,7 +136,7 @@ namespace System.Security.Cryptography.X509Certificates { status = (X509ChainStatus[]) list.ToArray (typeof (X509ChainStatus)); // (fast path) this ignore everything we have checked
- if (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags)
+ if ((status.Length == 0) || (ChainPolicy.VerificationFlags == X509VerificationFlags.AllFlags))
return true;
bool result = true; @@ -185,11 +200,22 @@ namespace System.Security.Cryptography.X509Certificates { public void Reset () { + // note: this call doesn't Reset the X509ChainPolicy if ((status != null) && (status.Length != 0)) status = null; if (elements.Count > 0) - elements.Clear (); - // note: this call doesn't Reset the X509ChainPolicy + elements.Clear ();
+ if (roots != null) {
+ roots.Close ();
+ roots = null;
+ }
+ if (cas != null) {
+ cas.Close ();
+ cas = null;
+ } + collection = null; + bce_restriction = null; + working_public_key = null; } // static methods @@ -201,154 +227,636 @@ namespace System.Security.Cryptography.X509Certificates { // private stuff - private X509ChainStatusFlags BuildFrom (X509Certificate2 certificate) - {
- X509ChainStatusFlags result = X509ChainStatusFlags.NoError; - X509ChainStatusFlags flags = X509ChainStatusFlags.NoError; - - // check certificate - Process (certificate, ref flags); - - // check if certificate is self-signed - if (IsSelfSigned (certificate)) { - // FIXME - add support for cross-certificate, bridges - ProcessRoot (certificate, ref flags); - } else {
- CheckRevocation (certificate, ref flags);
- - X509Certificate2 parent = FindParent (certificate, ref flags); - if (parent != null) { - // recurse - result = BuildFrom (parent); - if (result != X509ChainStatusFlags.NoError) - return result; - } else { - // we didn't end with a root, nor could we find one (stores) - result = X509ChainStatusFlags.PartialChain; + private X509Store roots; + private X509Store cas;
+
+ private X509Store Roots {
+ get {
+ if (roots == null) {
+ roots = new X509Store (StoreName.Root, location);
+ roots.Open (OpenFlags.ReadOnly);
+ }
+ return roots;
+ }
+ }
+ + private X509Store CertificateAuthorities {
+ get {
+ if (cas == null) {
+ cas = new X509Store (StoreName.CertificateAuthority, location);
+ cas.Open (OpenFlags.ReadOnly);
+ }
+ return cas;
+ }
+ }
+ + // *** certificate chain/path building stuff *** + + private X509Certificate2Collection collection; +
+ // we search local user (default) or machine certificate store + // and in the extra certificate supplied in ChainPolicy.ExtraStore + private X509Certificate2Collection CertificateCollection {
+ get { + if (collection == null) { + collection = new X509Certificate2Collection (ChainPolicy.ExtraStore); + if (Roots.Certificates.Count > 0) + collection.AddRange (Roots.Certificates); + if (CertificateAuthorities.Certificates.Count > 0) + collection.AddRange (CertificateAuthorities.Certificates); } - } - elements.Add (certificate, flags); - return result; + return collection;
+ }
} - private void Process (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + // This is a non-recursive chain/path building algorithm. + // + // At this stage we only checks for PartialChain, Cyclic and UntrustedRoot errors are they + // affect the path building (other errors are verification errors). + // + // Note that the order match the one we need to match MS and not the one defined in RFC3280, + // we also include the trusted root certificate (trust anchor in RFC3280) in the list. + // (this isn't an issue, just keep that in mind if you look at the source and the RFC) + private X509ChainStatusFlags BuildChainFrom (X509Certificate2 certificate) { - // is it the end-entity ? - if (elements.Count == 0) { - }
-
- if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
- (ChainPolicy.VerificationTime > certificate.NotAfter)) {
- flags |= X509ChainStatusFlags.NotTimeValid;
+ elements.Add (certificate); + + while (!IsChainComplete (certificate)) { + certificate = FindParent (certificate); + + if (certificate == null) + return X509ChainStatusFlags.PartialChain; + + if (elements.Contains (certificate)) + return X509ChainStatusFlags.Cyclic; + + elements.Add (certificate); } - // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
-
- // TODO - for X509ChainStatusFlags.InvalidExtension + // roots may be supplied (e.g. in the ExtraStore) so we need to confirm their + // trustiness (what a cute word) in the trusted root collection + if (!Roots.Certificates.Contains (certificate)) + elements [elements.Count - 1].StatusFlags |= X509ChainStatusFlags.UntrustedRoot; - // TODO - check for X509ChainStatusFlags.InvalidBasicConstraint + return X509ChainStatusFlags.NoError; + } - // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints - // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy - // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy + private X509Certificate2 SelectBestFromCollection (X509Certificate2 child, X509Certificate2Collection c) + { + switch (c.Count) { + case 0: + return null; + case 1: + return c [0]; + default: + // multiple candidate, keep only the ones that are still valid + X509Certificate2Collection time_valid = c.Find (X509FindType.FindByTimeValid, ChainPolicy.VerificationTime, false); + switch (time_valid.Count) { + case 0: + // that's too restrictive, let's revert and try another thing... + time_valid = c; + break; + case 1: + return time_valid [0]; + default: + break; + } + + // again multiple candidates, let's find the AKI that match the SKI (if we have one) + string aki = GetAuthorityKeyIdentifier (child); + if ((aki == null) || (aki.Length == 0)) { + return time_valid [0]; // FIXME: out of luck, you get the first one + } + foreach (X509Certificate2 parent in time_valid) { + string ski = GetSubjectKeyIdentifier (parent); + // if both id are available then they must match + if (aki == ski) + return parent; + } + return time_valid [0]; // FIXME: out of luck, you get the first one + } + }
- // TODO - check for X509ChainStatusFlags.InvalidNameConstraint - // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint - // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint - // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint + private X509Certificate2 FindParent (X509Certificate2 certificate) + {
+ X509Certificate2Collection subset = CertificateCollection.Find (X509FindType.FindBySubjectDistinguishedName, certificate.Issuer, false); + string aki = GetAuthorityKeyIdentifier (certificate); + if ((aki != null) && (aki.Length > 0)) { + subset.AddRange (CertificateCollection.Find (X509FindType.FindBySubjectKeyIdentifier, aki, false)); + } + X509Certificate2 parent = SelectBestFromCollection (certificate, subset);
+ // if parent==certificate we're looping but it's not (probably) a bug and not a true cyclic (over n certs) + return certificate.Equals (parent) ? null : parent; } - private void ProcessEndEntity (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + private bool IsChainComplete (X509Certificate2 certificate) { + // the chain is complete if we have a self-signed certificate + if (!IsSelfIssued (certificate)) + return false; + + // we're very limited to what we can do without certificate extensions + if (certificate.Version < 3) + return true; + + // check that Authority Key Identifier == Subject Key Identifier + // e.g. it will be different if a self-signed certificate is part (not the end) of the chain + string ski = GetSubjectKeyIdentifier (certificate); + if ((ski == null) || (ski.Length == 0)) + return true; + string aki = GetAuthorityKeyIdentifier (certificate); + if ((aki == null) || (aki.Length == 0)) + return true; + // if both id are available then they must match + return (aki == ski); + } + + // check for "self-issued" certificate - without verifying the signature + // note that self-issued doesn't always mean it's a root certificate!
+ private bool IsSelfIssued (X509Certificate2 certificate) + {
+ return (certificate.Issuer == certificate.Subject); } - private void ProcessCertificateAuthority (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + + // *** certificate chain/path validation stuff *** + + // Currently a subset of RFC3280 (hopefully a full implementation someday) + private void ValidateChain (X509ChainStatusFlags flag) { + // 'n' should be the root certificate... + int n = elements.Count - 1; + X509Certificate2 certificate = elements [n].Certificate; + + // ... and, if so, must be treated outside the chain... + if (((flag & X509ChainStatusFlags.PartialChain) == 0)) { + Process (n); + // deal with the case where the chain == the root certificate + // (which isn't for RFC3280) part of the chain + if (n == 0) { + elements [0].UncompressFlags (); + return; + } + // skip the root certificate when processing the chain (in 6.1.3) + n--; + } + // ... unless the chain is a partial one (then we start with that one) + + // 6.1.1 - Inputs + // 6.1.1.a - a prospective certificate path of length n (i.e. elements) + // 6.1.1.b - the current date/time (i.e. ChainPolicy.VerificationTime) + // 6.1.1.c - user-initial-policy-set (i.e. ChainPolicy.CertificatePolicy) + // 6.1.1.d - the trust anchor information (i.e. certificate, unless it's a partial chain) + // 6.1.1.e - initial-policy-mapping-inhibit (NOT SUPPORTED BY THE API) + // 6.1.1.f - initial-explicit-policy (NOT SUPPORTED BY THE API) + // 6.1.1.g - initial-any-policy-inhibit (NOT SUPPORTED BY THE API) + + // 6.1.2 - Initialization (incomplete) + // 6.1.2.a-f - policy stuff, some TODO, some not supported + // 6.1.2.g - working public key algorithm + working_public_key_algorithm = certificate.PublicKey.Oid.Value; + // 6.1.2.h-i - our key contains both the "working public key" and "working public key parameters" data + working_public_key = certificate.PublicKey.Key; + // 6.1.2.j - working issuer name + working_issuer_name = certificate.IssuerName; + // 6.1.2.k - this integer is initialized to n, is decremented for each non-self-issued, certificate and + // may be reduced to the value in the path length constraint field + max_path_length = n; + + // 6.1.3 - Basic Certificate Processing + // note: loop looks reversed (the list is) but we process this part just like RFC3280 does + for (int i = n; i > 0; i--) { + Process (i); + // 6.1.4 - preparation for certificate i+1 (for not with i+1, or i-1 in our loop) + PrepareForNextCertificate (i); + } + Process (0); + + // 6.1.3.a.3 - revocation checks + CheckRevocationOnChain (flag); + + // 6.1.5 - Wrap-up procedure + WrapUp (); } - // CTL == Certificate Trust List / not sure how/if they apply here - private void ProcessCTL (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + private void Process (int n) { - // TODO - check for X509ChainStatusFlags.CtlNotTimeValid - // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid - // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage + X509ChainElement element = elements [n]; + X509Certificate2 certificate = element.Certificate; + + // pre-step: DSA certificates may inherit the parameters of their CA + if ((n != elements.Count - 1) && (certificate.MonoCertificate.KeyAlgorithm == "1.2.840.10040.4.1")) { + if (certificate.MonoCertificate.KeyAlgorithmParameters == null) { + X509Certificate2 parent = elements [n+1].Certificate; + certificate.MonoCertificate.KeyAlgorithmParameters = parent.MonoCertificate.KeyAlgorithmParameters; + } + } + + bool root = (working_public_key == null); + // 6.1.3.a.1 - check signature (with special case to deal with root certificates) + if (!IsSignedWith (certificate, root ? certificate.PublicKey.Key : working_public_key)) { + // another special case where only an end-entity is available and can't be verified. + // In this case we do not report an invalid signature (since this is unknown) + if (root || (n != elements.Count - 1) || IsSelfIssued (certificate)) { + element.StatusFlags |= X509ChainStatusFlags.NotSignatureValid; + } + } + + // 6.1.3.a.2 - check validity period + if ((ChainPolicy.VerificationTime < certificate.NotBefore) ||
+ (ChainPolicy.VerificationTime > certificate.NotAfter)) {
+ element.StatusFlags |= X509ChainStatusFlags.NotTimeValid;
+ } + // TODO - for X509ChainStatusFlags.NotTimeNested (needs global structure)
+ + // note: most of them don't apply to the root certificate + if (root) { + return; + } + + // 6.1.3.a.3 - revocation check (we're doing at the last stage) + // note: you revoke a trusted root by removing it from your trusted store (i.e. no CRL can do this job) + + // 6.1.3.a.4 - check certificate issuer name + if (!X500DistinguishedName.AreEqual (certificate.IssuerName, working_issuer_name)) { + // NOTE: this is not the "right" error flag, but it's the closest one defined + element.StatusFlags |= X509ChainStatusFlags.InvalidNameConstraints;
+ } + + if (!IsSelfIssued (certificate) && (n != 0)) { + // TODO 6.1.3.b - subject name in the permitted_subtrees ... + // TODO 6.1.3.c - subject name not within excluded_subtrees... + + // TODO - check for X509ChainStatusFlags.InvalidNameConstraint + // TODO - check for X509ChainStatusFlags.HasNotSupportedNameConstraint + // TODO - check for X509ChainStatusFlags.HasNotPermittedNameConstraint + // TODO - check for X509ChainStatusFlags.HasExcludedNameConstraint + } + + // TODO 6.1.3.d - check if certificate policies extension is present + if (false) { + // TODO - for X509ChainStatusFlags.InvalidPolicyConstraints + // using X509ChainPolicy.ApplicationPolicy and X509ChainPolicy.CertificatePolicy + + // TODO - check for X509ChainStatusFlags.NoIssuanceChainPolicy + + } else { + // TODO 6.1.3.e - set valid_policy_tree to NULL + } + + // TODO 6.1.3.f - verify explict_policy > 0 if valid_policy_tree != NULL } - private void ProcessRoot (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + // CTL == Certificate Trust List / NOT SUPPORTED + // TODO - check for X509ChainStatusFlags.CtlNotTimeValid + // TODO - check for X509ChainStatusFlags.CtlNotSignatureValid + // TODO - check for X509ChainStatusFlags.CtlNotValidForUsage + + private void PrepareForNextCertificate (int n) { - X509Store trust = new X509Store (StoreName.Root, location); - trust.Open (OpenFlags.ReadOnly); - if (!trust.Certificates.Contains (certificate)) { - flags |= X509ChainStatusFlags.UntrustedRoot; + X509ChainElement element = elements [n]; + X509Certificate2 certificate = element.Certificate; + + // TODO 6.1.4.a-b + + // 6.1.4.c + working_issuer_name = certificate.SubjectName; + // 6.1.4.d-e - our key includes both the public key and it's parameters + working_public_key = certificate.PublicKey.Key; + // 6.1.4.f + working_public_key_algorithm = certificate.PublicKey.Oid.Value; + + // TODO 6.1.4.g-j + + // 6.1.4.k - Verify that the certificate is a CA certificate
+ X509BasicConstraintsExtension bce = (X509BasicConstraintsExtension) certificate.Extensions["2.5.29.19"];
+ if (bce != null) { + if (!bce.CertificateAuthority) {
+ element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints; + }
+ } else if (certificate.Version >= 3) { + // recent (v3+) CA certificates must include BCE
+ element.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints; + } + + // 6.1.4.l - if the certificate isn't self-issued... + if (!IsSelfIssued (certificate)) { + // ... verify that max_path_length > 0 + if (max_path_length > 0) { + max_path_length--; + } else { + // to match MS the reported status must be against the certificate + // with the BCE and not where the path is too long. It also means + // that this condition has to be reported only once + if (bce_restriction != null) { + bce_restriction.StatusFlags |= X509ChainStatusFlags.InvalidBasicConstraints; + } + }
} - trust.Close (); - if (!IsSignedBy (certificate, certificate)) { - flags |= X509ChainStatusFlags.NotSignatureValid; + // 6.1.4.m - if pathLengthConstraint is present...
+ if ((bce != null) && (bce.HasPathLengthConstraint)) { + // ... and is less that max_path_length, set max_path_length to it's value + if (bce.PathLengthConstraint < max_path_length) { + max_path_length = bce.PathLengthConstraint; + bce_restriction = element; + } } + + // 6.1.4.n - if key usage extension is present... + X509KeyUsageExtension kue = (X509KeyUsageExtension) certificate.Extensions["2.5.29.15"];
+ if (kue != null) { + // ... verify keyCertSign is set
+ X509KeyUsageFlags success = X509KeyUsageFlags.KeyCertSign;
+ if ((kue.KeyUsages & success) != success)
+ element.StatusFlags |= X509ChainStatusFlags.NotValidForUsage;
+ }
+ + // 6.1.4.o - recognize and process other critical extension present in the certificate + ProcessCertificateExtensions (element); } - // we search local user (default) or machine certificate store - // and in the extra certificate supplied in ChainPolicy.ExtraStore - private X509Certificate2 FindParent (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + private void WrapUp () { - X509Certificate2 parent = null; + X509ChainElement element = elements [0]; + X509Certificate2 certificate = element.Certificate; + + // 6.1.5.a - TODO if certificate n (our 0) wasn't self issued and explicit_policy != 0 + if (IsSelfIssued (certificate)) { + // TODO... decrement explicit_policy by 1 + } + + // 6.1.5.b - TODO + + // 6.1.5.c,d,e - not required by the X509Chain implementation - // TODO - check for X509ChainStatusFlags.Cyclic + // 6.1.5.f - recognize and process other critical extension present in the certificate + ProcessCertificateExtensions (element); - if ((parent != null) && !IsSignedBy (certificate, parent)) { - flags |= X509ChainStatusFlags.NotSignatureValid; + // 6.1.5.g - TODO + + // uncompressed the flags into several elements + for (int i = elements.Count - 1; i >= 0; i--) { + elements [i].UncompressFlags (); } - return null; } - // check for "self-signed" certificate - without verifying the signature - private bool IsSelfSigned (X509Certificate2 certificate) - {
- // FIXME - very incomplete
- return (certificate.Issuer == certificate.Subject);
+ private void ProcessCertificateExtensions (X509ChainElement element) + { + foreach (X509Extension ext in element.Certificate.Extensions) { + if (ext.Critical) { + switch (ext.Oid.Value) { + case "2.5.29.15": // X509KeyUsageExtension + case "2.5.29.19": // X509BasicConstraintsExtension + // we processed this extension + break; + default: + // note: Under Windows XP MS implementation seems to ignore + // certificate with unknown critical extensions. + element.StatusFlags |= X509ChainStatusFlags.InvalidExtension; + break; + } + } + } } - // this method verify the signature - private bool IsSignedBy (X509Certificate2 signed, X509Certificate2 signer) - {
- // FIXME - return true; + private bool IsSignedWith (X509Certificate2 signed, AsymmetricAlgorithm pubkey) + { + if (pubkey == null) + return false;
+ // Sadly X509Certificate2 doesn't expose the signature nor the tbs (to be signed) structure + MX.X509Certificate mx = signed.MonoCertificate; + return (mx.VerifySignature (pubkey)); + } + + private string GetSubjectKeyIdentifier (X509Certificate2 certificate) + { + X509SubjectKeyIdentifierExtension ski = (X509SubjectKeyIdentifierExtension) certificate.Extensions["2.5.29.14"];
+ return (ski == null) ? String.Empty : ski.SubjectKeyIdentifier;
+ } + + // System.dll v2 doesn't have a class to deal with the AuthorityKeyIdentifier extension + private string GetAuthorityKeyIdentifier (X509Certificate2 certificate) + { + return GetAuthorityKeyIdentifier (certificate.MonoCertificate.Extensions ["2.5.29.35"]); }
-
- private void CheckRevocation (X509Certificate2 certificate, ref X509ChainStatusFlags flags)
- {
+ + // but anyway System.dll v2 doesn't expose CRL in any way so... + private string GetAuthorityKeyIdentifier (MX.X509Crl crl) + { + return GetAuthorityKeyIdentifier (crl.Extensions ["2.5.29.35"]); + } + + private string GetAuthorityKeyIdentifier (MX.X509Extension ext) + { + if (ext == null) + return String.Empty; + MX.Extensions.AuthorityKeyIdentifierExtension aki = new MX.Extensions.AuthorityKeyIdentifierExtension (ext); + byte[] id = aki.Identifier; + if (id == null) + return String.Empty; + StringBuilder sb = new StringBuilder (); + foreach (byte b in id) + sb.Append (b.ToString ("X02")); + return sb.ToString (); + } + + // we check the revocation only once we have built the complete chain + private void CheckRevocationOnChain (X509ChainStatusFlags flag) + { + bool partial = ((flag & X509ChainStatusFlags.PartialChain) != 0); + bool online; + switch (ChainPolicy.RevocationMode) {
case X509RevocationMode.Online:
- // local?/download CRL and OCSP
- CheckOnlineRevocation (certificate, ref flags);
- break;
- case X509RevocationMode.Offline:
- // only local CRL ?
- CheckOfflineRevocation (certificate, ref flags);
+ // default
+ online = true; break;
- case X509RevocationMode.NoCheck:
+ case X509RevocationMode.Offline: + online = false; break;
+ case X509RevocationMode.NoCheck: + return;
default:
- throw new InvalidOperationException ();
+ throw new InvalidOperationException (Locale.GetText ("Invalid revocation mode."));
}
+ + bool unknown = partial; + // from the root down to the end-entity + for (int i = elements.Count - 1; i >= 0; i--) { + bool check = true; + + switch (ChainPolicy.RevocationFlag) { + case X509RevocationFlag.EndCertificateOnly: + check = (i == 0); + break; + case X509RevocationFlag.EntireChain: + check = true; + break; + case X509RevocationFlag.ExcludeRoot: + // default + check = (i != (elements.Count - 1)); + // anyway, who's gonna sign that the root is invalid ? + break; + } + + X509ChainElement element = elements [i]; + + // we can't assume the revocation status if the certificate is bad (e.g. invalid signature) + if (!unknown) + unknown |= ((element.StatusFlags & X509ChainStatusFlags.NotSignatureValid) != 0); + + if (unknown) { + // we can skip the revocation checks as we can't be sure of them anyway + element.StatusFlags |= X509ChainStatusFlags.RevocationStatusUnknown; + element.StatusFlags |= X509ChainStatusFlags.OfflineRevocation; + } else if (check && !partial && !IsSelfIssued (element.Certificate)) { + // check for revocation (except for the trusted root and self-issued certs) + element.StatusFlags |= CheckRevocation (element.Certificate, i+1, online); + // if revoked, then all others following in the chain are unknown...
+ unknown |= ((element.StatusFlags & X509ChainStatusFlags.Revoked) != 0); + } + } }
- private void CheckOfflineRevocation (X509Certificate2 certificate, ref X509ChainStatusFlags flags)
- {
- // TODO - check for X509ChainStatusFlags.Revoked
- // TODO - check for X509ChainStatusFlags.RevocationStatusUnknown
- // TODO - check for X509ChainStatusFlags.OfflineRevocation
- // (using X509ChainPolicy.RevocationFlag and X509ChainPolicy.RevocationMode)
- }
+ // This isn't how RFC3280 (section 6.3) deals with CRL, but then we don't (yet) support DP, deltas... + private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, int ca, bool online)
+ { + X509ChainStatusFlags result = X509ChainStatusFlags.RevocationStatusUnknown; + X509ChainElement element = elements [ca]; + X509Certificate2 ca_cert = element.Certificate; + + // find the CRL from the "right" CA + while (IsSelfIssued (ca_cert) && (ca < elements.Count - 1)) { + // try with this self-issued + result = CheckRevocation (certificate, ca_cert, online); + if (result != X509ChainStatusFlags.RevocationStatusUnknown) + break; + ca++; + element = elements [ca]; + ca_cert = element.Certificate; + } + if (result == X509ChainStatusFlags.RevocationStatusUnknown) + result = CheckRevocation (certificate, ca_cert, online); + return result; + } + + private X509ChainStatusFlags CheckRevocation (X509Certificate2 certificate, X509Certificate2 ca_cert, bool online)
+ { + // change this if/when we support OCSP + X509KeyUsageExtension kue = (X509KeyUsageExtension) ca_cert.Extensions["2.5.29.15"];
+ if (kue != null) { + // ... verify CrlSign is set
+ X509KeyUsageFlags success = X509KeyUsageFlags.CrlSign;
+ if ((kue.KeyUsages & success) != success) { + // FIXME - we should try to find an alternative CA that has the CrlSign bit
+ return X509ChainStatusFlags.RevocationStatusUnknown; + } + }
+ + MX.X509Crl crl = FindCrl (ca_cert); + + if ((crl == null) && online) { + // FIXME - download and install new CRL + // then you get a second chance + // crl = FindCrl (ca_cert, ref valid, ref out_of_date); + } + + if (crl != null) { + // validate the digital signature on the CRL using the CA public key + // note #1: we can't use X509Crl.VerifySignature(X509Certificate) because it duplicates + // checks and we loose the "why" of the failure + // note #2: we do this before other tests as an invalid signature could be a hacked CRL + // (so anything within can't be trusted) + if (!crl.VerifySignature (ca_cert.PublicKey.Key)) { + return X509ChainStatusFlags.RevocationStatusUnknown; + } + + MX.X509Crl.X509CrlEntry entry = crl.GetCrlEntry (certificate.MonoCertificate); + if (entry != null) { + // We have an entry for this CRL that includes an unknown CRITICAL extension + // See [X.509 7.3] NOTE 4 + if (!ProcessCrlEntryExtensions (entry)) + return X509ChainStatusFlags.Revoked; + + // FIXME - a little more is involved + if (entry.RevocationDate <= ChainPolicy.VerificationTime) + return X509ChainStatusFlags.Revoked; + } + + // are we overdue for a CRL update ? if so we can't be sure of any certificate status + if (crl.NextUpdate < ChainPolicy.VerificationTime) + return X509ChainStatusFlags.RevocationStatusUnknown | X509ChainStatusFlags.OfflineRevocation; + + // we have a CRL that includes an unknown CRITICAL extension + // we put this check at the end so we do not "hide" any Revoked flags + if (!ProcessCrlExtensions (crl)) { + return X509ChainStatusFlags.RevocationStatusUnknown; + } + } else { + return X509ChainStatusFlags.RevocationStatusUnknown;
+ } - private void CheckOnlineRevocation (X509Certificate2 certificate, ref X509ChainStatusFlags flags) + return X509ChainStatusFlags.NoError;
+ } + + private MX.X509Crl OnlineCheck (X509Certificate2 certificate) + { + // CRL doesn't exists or couldn't be downloaded + return null; + } + + private MX.X509Crl FindCrl (X509Certificate2 caCertificate) + { + string subject = caCertificate.SubjectName.Decode (X500DistinguishedNameFlags.None); + string ski = GetSubjectKeyIdentifier (caCertificate); + foreach (MX.X509Crl crl in CertificateAuthorities.Store.Crls) { + if (crl.IssuerName == subject) { + if ((ski.Length == 0) || (ski == GetAuthorityKeyIdentifier (crl))) + return crl; + } + } + foreach (MX.X509Crl crl in Roots.Store.Crls) { + if (crl.IssuerName == subject) { + if ((ski.Length == 0) || (ski == GetAuthorityKeyIdentifier (crl))) + return crl; + } + } + return null; + }
+ + private bool ProcessCrlExtensions (MX.X509Crl crl) { - // TODO - check for X509ChainStatusFlags.Revoked - // TODO - check for X509ChainStatusFlags.RevocationStatusUnknown - // TODO - check for X509ChainStatusFlags.OfflineRevocation - // (using X509ChainPolicy.RevocationFlag and X509ChainPolicy.RevocationMode) + foreach (MX.X509Extension ext in crl.Extensions) { + if (ext.Critical) { + switch (ext.Oid) { + case "2.5.29.20": // cRLNumber + case "2.5.29.35": // authorityKeyIdentifier + // we processed/know about this extension + break; + default: + return false; + } + } + } + return true; + } + + private bool ProcessCrlEntryExtensions (MX.X509Crl.X509CrlEntry entry) + { + foreach (MX.X509Extension ext in entry.Extensions) { + if (ext.Critical) { + switch (ext.Oid) { + case "2.5.29.21": // cRLReason + // we processed/know about this extension + break; + default: + return false; + } + } + } + return true; } } } diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElement.cs b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElement.cs index 103f2a11992..ef48154f045 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElement.cs +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElement.cs @@ -36,46 +36,14 @@ namespace System.Security.Cryptography.X509Certificates { private X509Certificate2 certificate; private X509ChainStatus[] status; private string info; + private X509ChainStatusFlags compressed_status_flags; // constructors // only accessible from X509Chain.ChainElements - internal X509ChainElement (X509Certificate2 certificate, X509ChainStatusFlags flags) + internal X509ChainElement (X509Certificate2 certificate) { this.certificate = certificate; - - if (flags == X509ChainStatusFlags.NoError) { - status = new X509ChainStatus [0]; - } else { - int size = Count (flags); - status = new X509ChainStatus [size]; - - int n = 0; - // process every possible error
- Set (status, ref n, flags, X509ChainStatusFlags.UntrustedRoot);
- Set (status, ref n, flags, X509ChainStatusFlags.NotTimeValid); - // not yet sorted after this... - Set (status, ref n, flags, X509ChainStatusFlags.NotTimeNested); - Set (status, ref n, flags, X509ChainStatusFlags.Revoked); - Set (status, ref n, flags, X509ChainStatusFlags.NotSignatureValid); - Set (status, ref n, flags, X509ChainStatusFlags.NotValidForUsage); - Set (status, ref n, flags, X509ChainStatusFlags.RevocationStatusUnknown); - Set (status, ref n, flags, X509ChainStatusFlags.Cyclic); - Set (status, ref n, flags, X509ChainStatusFlags.InvalidExtension); - Set (status, ref n, flags, X509ChainStatusFlags.InvalidPolicyConstraints); - Set (status, ref n, flags, X509ChainStatusFlags.InvalidBasicConstraints); - Set (status, ref n, flags, X509ChainStatusFlags.InvalidNameConstraints); - Set (status, ref n, flags, X509ChainStatusFlags.HasNotSupportedNameConstraint); - Set (status, ref n, flags, X509ChainStatusFlags.HasNotDefinedNameConstraint); - Set (status, ref n, flags, X509ChainStatusFlags.HasNotPermittedNameConstraint); - Set (status, ref n, flags, X509ChainStatusFlags.HasExcludedNameConstraint); - Set (status, ref n, flags, X509ChainStatusFlags.PartialChain); - Set (status, ref n, flags, X509ChainStatusFlags.CtlNotTimeValid); - Set (status, ref n, flags, X509ChainStatusFlags.CtlNotSignatureValid); - Set (status, ref n, flags, X509ChainStatusFlags.CtlNotValidForUsage); - Set (status, ref n, flags, X509ChainStatusFlags.OfflineRevocation); - Set (status, ref n, flags, X509ChainStatusFlags.NoIssuanceChainPolicy); - } // so far String.Empty is the only thing I've seen. // The interesting stuff is inside X509ChainStatus.Information info = String.Empty; @@ -97,6 +65,11 @@ namespace System.Security.Cryptography.X509Certificates { // private stuff + internal X509ChainStatusFlags StatusFlags { + get { return compressed_status_flags; } + set { compressed_status_flags = value; } + } + private int Count (X509ChainStatusFlags flags) { int size = 0; @@ -119,6 +92,41 @@ namespace System.Security.Cryptography.X509Certificates { position++; } } + + internal void UncompressFlags () + { + if (compressed_status_flags == X509ChainStatusFlags.NoError) { + status = new X509ChainStatus [0]; + } else { + int size = Count (compressed_status_flags); + status = new X509ChainStatus [size]; + + int n = 0; + // process every possible error + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.UntrustedRoot); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.NotTimeValid); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.NotTimeNested); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.Revoked); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.NotSignatureValid); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.NotValidForUsage); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.RevocationStatusUnknown); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.Cyclic); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.InvalidExtension); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.InvalidPolicyConstraints); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.InvalidBasicConstraints); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.InvalidNameConstraints); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.HasNotSupportedNameConstraint); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.HasNotDefinedNameConstraint); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.HasNotPermittedNameConstraint); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.HasExcludedNameConstraint); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.PartialChain); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.CtlNotTimeValid); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.CtlNotSignatureValid); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.CtlNotValidForUsage); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.OfflineRevocation); + Set (status, ref n, compressed_status_flags, X509ChainStatusFlags.NoIssuanceChainPolicy); + } + } } } diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElementCollection.cs b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElementCollection.cs index f47e4c859e9..b9b1d925bbf 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElementCollection.cs +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509ChainElementCollection.cs @@ -2,11 +2,10 @@ // X509ChainElementCollection.cs - System.Security.Cryptography.X509Certificates.X509ChainElementCollection // // Author: -// Sebastien Pouliot (spouliot@motus.com) +// Sebastien Pouliot <sebastien@ximian.com> // // (C) 2003 Motus Technologies Inc. (http://www.motus.com) -// - +// Copyright (C) 2006 Novell Inc. (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -88,15 +87,24 @@ namespace System.Security.Cryptography.X509Certificates { // private stuff - internal void Add (X509Certificate2 certificate, X509ChainStatusFlags flags) + internal void Add (X509Certificate2 certificate) { - _list.Add (new X509ChainElement (certificate, flags)); + _list.Add (new X509ChainElement (certificate)); } internal void Clear () { _list.Clear (); } + + internal bool Contains (X509Certificate2 certificate) + { + for (int i=0; i < _list.Count; i++) { + if (certificate.Equals (( _list [i] as X509ChainElement).Certificate)) + return true; + } + return false; + } } } diff --git a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Store.cs b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Store.cs index 7625c87c54d..f75e1388f29 100644 --- a/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Store.cs +++ b/mcs/class/System/System.Security.Cryptography.X509Certificates/X509Store.cs @@ -139,6 +139,10 @@ namespace System.Security.Cryptography.X509Certificates { get { return ((_flags & OpenFlags.ReadWrite) == OpenFlags.ReadOnly); } } + internal MX.X509Store Store { + get { return store; } + } + [MonoTODO ("Mono's stores are fully managed. Always returns IntPtr.Zero.")] public IntPtr StoreHandle { get { return IntPtr.Zero; } @@ -203,8 +207,19 @@ namespace System.Security.Cryptography.X509Certificates { if (String.IsNullOrEmpty (_name)) throw new CryptographicException (Locale.GetText ("Invalid store name (null or empty).")); + /* keep existing Mono installations (pre 2.0) compatible with new stuff */ + string name; + switch (_name) { + case "Root": + name = "Trust"; + break; + default: + name = _name; + break; + } + bool create = ((flags & OpenFlags.OpenExistingOnly) != OpenFlags.OpenExistingOnly); - store = Factory.Open (_name, create); + store = Factory.Open (name, create); if (store == null) throw new CryptographicException (Locale.GetText ("Store {0} doesn't exists.", _name)); _flags = flags; |