diff options
Diffstat (limited to 'crypto/src/pkix')
23 files changed, 4848 insertions, 18 deletions
diff --git a/crypto/src/pkix/CertStatus.cs b/crypto/src/pkix/CertStatus.cs new file mode 100644 index 000000000..4f40b7bc6 --- /dev/null +++ b/crypto/src/pkix/CertStatus.cs @@ -0,0 +1,35 @@ +using System; + +using Org.BouncyCastle.Utilities.Date; + +namespace Org.BouncyCastle.Pkix +{ + public class CertStatus + { + public const int Unrevoked = 11; + + public const int Undetermined = 12; + + private int status = Unrevoked; + + DateTimeObject revocationDate = null; + + /// <summary> + /// Returns the revocationDate. + /// </summary> + public DateTimeObject RevocationDate + { + get { return revocationDate; } + set { this.revocationDate = value; } + } + + /// <summary> + /// Returns the certStatus. + /// </summary> + public int Status + { + get { return status; } + set { this.status = value; } + } + } +} diff --git a/crypto/src/pkix/PkixAttrCertChecker.cs b/crypto/src/pkix/PkixAttrCertChecker.cs new file mode 100644 index 000000000..a6eab8480 --- /dev/null +++ b/crypto/src/pkix/PkixAttrCertChecker.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + public abstract class PkixAttrCertChecker + { + /** + * Returns an immutable <code>Set</code> of X.509 attribute certificate + * extensions that this <code>PkixAttrCertChecker</code> supports or + * <code>null</code> if no extensions are supported. + * <p> + * Each element of the set is a <code>String</code> representing the + * Object Identifier (OID) of the X.509 extension that is supported. + * </p> + * <p> + * All X.509 attribute certificate extensions that a + * <code>PkixAttrCertChecker</code> might possibly be able to process + * should be included in the set. + * </p> + * + * @return an immutable <code>Set</code> of X.509 extension OIDs (in + * <code>String</code> format) supported by this + * <code>PkixAttrCertChecker</code>, or <code>null</code> if no + * extensions are supported + */ + public abstract ISet GetSupportedExtensions(); + + /** + * Performs checks on the specified attribute certificate. Every handled + * extension is rmeoved from the <code>unresolvedCritExts</code> + * collection. + * + * @param attrCert The attribute certificate to be checked. + * @param certPath The certificate path which belongs to the attribute + * certificate issuer public key certificate. + * @param holderCertPath The certificate path which belongs to the holder + * certificate. + * @param unresolvedCritExts a <code>Collection</code> of OID strings + * representing the current set of unresolved critical extensions + * @throws CertPathValidatorException if the specified attribute certificate + * does not pass the check. + */ + public abstract void Check(IX509AttributeCertificate attrCert, PkixCertPath certPath, + PkixCertPath holderCertPath, ICollection unresolvedCritExts); + + /** + * Returns a clone of this object. + * + * @return a copy of this <code>PkixAttrCertChecker</code> + */ + public abstract PkixAttrCertChecker Clone(); + } +} diff --git a/crypto/src/pkix/PkixAttrCertPathBuilder.cs b/crypto/src/pkix/PkixAttrCertPathBuilder.cs new file mode 100644 index 000000000..646cc5db5 --- /dev/null +++ b/crypto/src/pkix/PkixAttrCertPathBuilder.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixAttrCertPathBuilder + { + /** + * Build and validate a CertPath using the given parameter. + * + * @param params PKIXBuilderParameters object containing all information to + * build the CertPath + */ + public virtual PkixCertPathBuilderResult Build( + PkixBuilderParameters pkixParams) + { + // search target certificates + + IX509Selector certSelect = pkixParams.GetTargetConstraints(); + if (!(certSelect is X509AttrCertStoreSelector)) + { + throw new PkixCertPathBuilderException( + "TargetConstraints must be an instance of " + + typeof(X509AttrCertStoreSelector).FullName + + " for " + + typeof(PkixAttrCertPathBuilder).FullName + " class."); + } + + ICollection targets; + try + { + targets = PkixCertPathValidatorUtilities.FindCertificates( + (X509AttrCertStoreSelector)certSelect, pkixParams.GetStores()); + } + catch (Exception e) + { + throw new PkixCertPathBuilderException("Error finding target attribute certificate.", e); + } + + if (targets.Count == 0) + { + throw new PkixCertPathBuilderException( + "No attribute certificate found matching targetContraints."); + } + + PkixCertPathBuilderResult result = null; + + // check all potential target certificates + foreach (IX509AttributeCertificate cert in targets) + { + X509CertStoreSelector selector = new X509CertStoreSelector(); + X509Name[] principals = cert.Issuer.GetPrincipals(); + ISet issuers = new HashSet(); + for (int i = 0; i < principals.Length; i++) + { + try + { + selector.Subject = principals[i]; + + issuers.AddAll(PkixCertPathValidatorUtilities.FindCertificates(selector, pkixParams.GetStores())); + } + catch (Exception e) + { + throw new PkixCertPathBuilderException( + "Public key certificate for attribute certificate cannot be searched.", + e); + } + } + + if (issuers.IsEmpty) + throw new PkixCertPathBuilderException("Public key certificate for attribute certificate cannot be found."); + + IList certPathList = Platform.CreateArrayList(); + + foreach (X509Certificate issuer in issuers) + { + result = Build(cert, issuer, pkixParams, certPathList); + + if (result != null) + break; + } + + if (result != null) + break; + } + + if (result == null && certPathException != null) + { + throw new PkixCertPathBuilderException( + "Possible certificate chain could not be validated.", + certPathException); + } + + if (result == null && certPathException == null) + { + throw new PkixCertPathBuilderException( + "Unable to find certificate chain."); + } + + return result; + } + + private Exception certPathException; + + private PkixCertPathBuilderResult Build( + IX509AttributeCertificate attrCert, + X509Certificate tbvCert, + PkixBuilderParameters pkixParams, + IList tbvPath) + { + // If tbvCert is readily present in tbvPath, it indicates having run + // into a cycle in the + // PKI graph. + if (tbvPath.Contains(tbvCert)) + return null; + + // step out, the certificate is not allowed to appear in a certification + // chain + if (pkixParams.GetExcludedCerts().Contains(tbvCert)) + return null; + + // test if certificate path exceeds maximum length + if (pkixParams.MaxPathLength != -1) + { + if (tbvPath.Count - 1 > pkixParams.MaxPathLength) + return null; + } + + tbvPath.Add(tbvCert); + + PkixCertPathBuilderResult builderResult = null; + +// X509CertificateParser certParser = new X509CertificateParser(); + PkixAttrCertPathValidator validator = new PkixAttrCertPathValidator(); + + try + { + // check whether the issuer of <tbvCert> is a TrustAnchor + if (PkixCertPathValidatorUtilities.FindTrustAnchor(tbvCert, pkixParams.GetTrustAnchors()) != null) + { + PkixCertPath certPath = new PkixCertPath(tbvPath); + PkixCertPathValidatorResult result; + + try + { + result = validator.Validate(certPath, pkixParams); + } + catch (Exception e) + { + throw new Exception("Certification path could not be validated.", e); + } + + return new PkixCertPathBuilderResult(certPath, result.TrustAnchor, + result.PolicyTree, result.SubjectPublicKey); + } + else + { + // add additional X.509 stores from locations in certificate + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromAltNames(tbvCert, pkixParams); + } + catch (CertificateParsingException e) + { + throw new Exception("No additional X.509 stores can be added from certificate locations.", e); + } + + // try to get the issuer certificate from one of the stores + ISet issuers = new HashSet(); + try + { + issuers.AddAll(PkixCertPathValidatorUtilities.FindIssuerCerts(tbvCert, pkixParams)); + } + catch (Exception e) + { + throw new Exception("Cannot find issuer certificate for certificate in certification path.", e); + } + + if (issuers.IsEmpty) + throw new Exception("No issuer certificate for certificate in certification path found."); + + foreach (X509Certificate issuer in issuers) + { + // if untrusted self signed certificate continue + if (PkixCertPathValidatorUtilities.IsSelfIssued(issuer)) + continue; + + builderResult = Build(attrCert, issuer, pkixParams, tbvPath); + + if (builderResult != null) + break; + } + } + } + catch (Exception e) + { + certPathException = new Exception("No valid certification path could be build.", e); + } + + if (builderResult == null) + { + tbvPath.Remove(tbvCert); + } + + return builderResult; + } + } +} diff --git a/crypto/src/pkix/PkixAttrCertPathValidator.cs b/crypto/src/pkix/PkixAttrCertPathValidator.cs new file mode 100644 index 000000000..5f53bcde6 --- /dev/null +++ b/crypto/src/pkix/PkixAttrCertPathValidator.cs @@ -0,0 +1,76 @@ +using System; + +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * CertPathValidatorSpi implementation for X.509 Attribute Certificates la RFC 3281. + * + * @see org.bouncycastle.x509.ExtendedPkixParameters + */ + public class PkixAttrCertPathValidator + // extends CertPathValidatorSpi + { + /** + * Validates an attribute certificate with the given certificate path. + * + * <p> + * <code>params</code> must be an instance of + * <code>ExtendedPkixParameters</code>. + * </p><p> + * The target constraints in the <code>params</code> must be an + * <code>X509AttrCertStoreSelector</code> with at least the attribute + * certificate criterion set. Obey that also target informations may be + * necessary to correctly validate this attribute certificate. + * </p><p> + * The attribute certificate issuer must be added to the trusted attribute + * issuers with {@link ExtendedPkixParameters#setTrustedACIssuers(Set)}. + * </p> + * @param certPath The certificate path which belongs to the attribute + * certificate issuer public key certificate. + * @param params The PKIX parameters. + * @return A <code>PKIXCertPathValidatorResult</code> of the result of + * validating the <code>certPath</code>. + * @throws InvalidAlgorithmParameterException if <code>params</code> is + * inappropriate for this validator. + * @throws CertPathValidatorException if the verification fails. + */ + public virtual PkixCertPathValidatorResult Validate( + PkixCertPath certPath, + PkixParameters pkixParams) + { + IX509Selector certSelect = pkixParams.GetTargetConstraints(); + if (!(certSelect is X509AttrCertStoreSelector)) + { + throw new ArgumentException( + "TargetConstraints must be an instance of " + typeof(X509AttrCertStoreSelector).FullName, + "pkixParams"); + } + IX509AttributeCertificate attrCert = ((X509AttrCertStoreSelector) certSelect).AttributeCert; + + PkixCertPath holderCertPath = Rfc3281CertPathUtilities.ProcessAttrCert1(attrCert, pkixParams); + PkixCertPathValidatorResult result = Rfc3281CertPathUtilities.ProcessAttrCert2(certPath, pkixParams); + X509Certificate issuerCert = (X509Certificate)certPath.Certificates[0]; + Rfc3281CertPathUtilities.ProcessAttrCert3(issuerCert, pkixParams); + Rfc3281CertPathUtilities.ProcessAttrCert4(issuerCert, pkixParams); + Rfc3281CertPathUtilities.ProcessAttrCert5(attrCert, pkixParams); + // 6 already done in X509AttrCertStoreSelector + Rfc3281CertPathUtilities.ProcessAttrCert7(attrCert, certPath, holderCertPath, pkixParams); + Rfc3281CertPathUtilities.AdditionalChecks(attrCert, pkixParams); + DateTime date; + try + { + date = PkixCertPathValidatorUtilities.GetValidCertDateFromValidityModel(pkixParams, null, -1); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException( + "Could not get validity date from attribute certificate.", e); + } + Rfc3281CertPathUtilities.CheckCrls(attrCert, pkixParams, issuerCert, date, certPath.Certificates); + return result; + } + } +} diff --git a/crypto/src/pkix/PkixBuilderParameters.cs b/crypto/src/pkix/PkixBuilderParameters.cs new file mode 100644 index 000000000..32fc04360 --- /dev/null +++ b/crypto/src/pkix/PkixBuilderParameters.cs @@ -0,0 +1,140 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509.Store; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// Summary description for PkixBuilderParameters. + /// </summary> + public class PkixBuilderParameters + : PkixParameters + { + private int maxPathLength = 5; + + private ISet excludedCerts = new HashSet(); + + /** + * Returns an instance of <code>PkixBuilderParameters</code>. + * <p> + * This method can be used to get a copy from other + * <code>PKIXBuilderParameters</code>, <code>PKIXParameters</code>, + * and <code>ExtendedPKIXParameters</code> instances. + * </p> + * + * @param pkixParams The PKIX parameters to create a copy of. + * @return An <code>PkixBuilderParameters</code> instance. + */ + public static PkixBuilderParameters GetInstance( + PkixParameters pkixParams) + { + PkixBuilderParameters parameters = new PkixBuilderParameters( + pkixParams.GetTrustAnchors(), + new X509CertStoreSelector(pkixParams.GetTargetCertConstraints())); + parameters.SetParams(pkixParams); + return parameters; + } + + public PkixBuilderParameters( + ISet trustAnchors, + IX509Selector targetConstraints) + : base(trustAnchors) + { + SetTargetCertConstraints(targetConstraints); + } + + public virtual int MaxPathLength + { + get { return maxPathLength; } + set + { + if (value < -1) + { + throw new InvalidParameterException( + "The maximum path length parameter can not be less than -1."); + } + this.maxPathLength = value; + } + } + + /// <summary> + /// Excluded certificates are not used for building a certification path. + /// </summary> + /// <returns>the excluded certificates.</returns> + public virtual ISet GetExcludedCerts() + { + return new HashSet(excludedCerts); + } + + /// <summary> + /// Sets the excluded certificates which are not used for building a + /// certification path. If the <code>ISet</code> is <code>null</code> an + /// empty set is assumed. + /// </summary> + /// <remarks> + /// The given set is cloned to protect it against subsequent modifications. + /// </remarks> + /// <param name="excludedCerts">The excluded certificates to set.</param> + public virtual void SetExcludedCerts( + ISet excludedCerts) + { + if (excludedCerts == null) + { + excludedCerts = new HashSet(); + } + else + { + this.excludedCerts = new HashSet(excludedCerts); + } + } + + /** + * Can alse handle <code>ExtendedPKIXBuilderParameters</code> and + * <code>PKIXBuilderParameters</code>. + * + * @param params Parameters to set. + * @see org.bouncycastle.x509.ExtendedPKIXParameters#setParams(java.security.cert.PKIXParameters) + */ + protected override void SetParams( + PkixParameters parameters) + { + base.SetParams(parameters); + if (parameters is PkixBuilderParameters) + { + PkixBuilderParameters _params = (PkixBuilderParameters) parameters; + maxPathLength = _params.maxPathLength; + excludedCerts = new HashSet(_params.excludedCerts); + } + } + + /** + * Makes a copy of this <code>PKIXParameters</code> object. Changes to the + * copy will not affect the original and vice versa. + * + * @return a copy of this <code>PKIXParameters</code> object + */ + public override object Clone() + { + PkixBuilderParameters parameters = new PkixBuilderParameters( + GetTrustAnchors(), GetTargetCertConstraints()); + parameters.SetParams(this); + return parameters; + } + + public override string ToString() + { + string nl = Platform.NewLine; + StringBuilder s = new StringBuilder(); + s.Append("PkixBuilderParameters [" + nl); + s.Append(base.ToString()); + s.Append(" Maximum Path Length: "); + s.Append(MaxPathLength); + s.Append(nl + "]" + nl); + return s.ToString(); + } + } +} diff --git a/crypto/src/pkix/PkixCertPath.cs b/crypto/src/pkix/PkixCertPath.cs index e13e50995..e3d3ea7fe 100644 --- a/crypto/src/pkix/PkixCertPath.cs +++ b/crypto/src/pkix/PkixCertPath.cs @@ -401,7 +401,7 @@ namespace Org.BouncyCastle.Pkix pWrt.WriteObject(certificates[i]); } - pWrt.Writer.Dispose(); + pWrt.Writer.Close(); } catch (Exception) { diff --git a/crypto/src/pkix/PkixCertPathBuilder.cs b/crypto/src/pkix/PkixCertPathBuilder.cs new file mode 100644 index 000000000..7082fe409 --- /dev/null +++ b/crypto/src/pkix/PkixCertPathBuilder.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Asn1.IsisMtt; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Asn1.X500; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Math; +using Org.BouncyCastle.Security.Certificates; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * Implements the PKIX CertPathBuilding algorithm for BouncyCastle. + * + * @see CertPathBuilderSpi + */ + public class PkixCertPathBuilder + // : CertPathBuilderSpi + { + /** + * Build and validate a CertPath using the given parameter. + * + * @param params PKIXBuilderParameters object containing all information to + * build the CertPath + */ + public virtual PkixCertPathBuilderResult Build( + PkixBuilderParameters pkixParams) + { + // search target certificates + + IX509Selector certSelect = pkixParams.GetTargetCertConstraints(); + if (!(certSelect is X509CertStoreSelector)) + { + throw new PkixCertPathBuilderException( + "TargetConstraints must be an instance of " + + typeof(X509CertStoreSelector).FullName + " for " + + this.GetType() + " class."); + } + + ISet targets = new HashSet(); + try + { + targets.AddAll(PkixCertPathValidatorUtilities.FindCertificates((X509CertStoreSelector)certSelect, pkixParams.GetStores())); + // TODO Should this include an entry for pkixParams.GetAdditionalStores() too? + } + catch (Exception e) + { + throw new PkixCertPathBuilderException( + "Error finding target certificate.", e); + } + + if (targets.IsEmpty) + throw new PkixCertPathBuilderException("No certificate found matching targetContraints."); + + PkixCertPathBuilderResult result = null; + IList certPathList = Platform.CreateArrayList(); + + // check all potential target certificates + foreach (X509Certificate cert in targets) + { + result = Build(cert, pkixParams, certPathList); + + if (result != null) + break; + } + + if (result == null && certPathException != null) + { + throw new PkixCertPathBuilderException(certPathException.Message, certPathException.InnerException); + } + + if (result == null && certPathException == null) + { + throw new PkixCertPathBuilderException("Unable to find certificate chain."); + } + + return result; + } + + private Exception certPathException; + + protected virtual PkixCertPathBuilderResult Build( + X509Certificate tbvCert, + PkixBuilderParameters pkixParams, + IList tbvPath) + { + // If tbvCert is readily present in tbvPath, it indicates having run + // into a cycle in the PKI graph. + if (tbvPath.Contains(tbvCert)) + return null; + + // step out, the certificate is not allowed to appear in a certification + // chain. + if (pkixParams.GetExcludedCerts().Contains(tbvCert)) + return null; + + // test if certificate path exceeds maximum length + if (pkixParams.MaxPathLength != -1) + { + if (tbvPath.Count - 1 > pkixParams.MaxPathLength) + return null; + } + + tbvPath.Add(tbvCert); + +// X509CertificateParser certParser = new X509CertificateParser(); + PkixCertPathBuilderResult builderResult = null; + PkixCertPathValidator validator = new PkixCertPathValidator(); + + try + { + // check whether the issuer of <tbvCert> is a TrustAnchor + if (PkixCertPathValidatorUtilities.FindTrustAnchor(tbvCert, pkixParams.GetTrustAnchors()) != null) + { + // exception message from possibly later tried certification + // chains + PkixCertPath certPath = null; + try + { + certPath = new PkixCertPath(tbvPath); + } + catch (Exception e) + { + throw new Exception( + "Certification path could not be constructed from certificate list.", + e); + } + + PkixCertPathValidatorResult result = null; + try + { + result = (PkixCertPathValidatorResult)validator.Validate( + certPath, pkixParams); + } + catch (Exception e) + { + throw new Exception( + "Certification path could not be validated.", e); + } + + return new PkixCertPathBuilderResult(certPath, result.TrustAnchor, + result.PolicyTree, result.SubjectPublicKey); + } + else + { + // add additional X.509 stores from locations in certificate + try + { + PkixCertPathValidatorUtilities.AddAdditionalStoresFromAltNames( + tbvCert, pkixParams); + } + catch (CertificateParsingException e) + { + throw new Exception( + "No additiontal X.509 stores can be added from certificate locations.", + e); + } + + // try to get the issuer certificate from one of the stores + HashSet issuers = new HashSet(); + try + { + issuers.AddAll(PkixCertPathValidatorUtilities.FindIssuerCerts(tbvCert, pkixParams)); + } + catch (Exception e) + { + throw new Exception( + "Cannot find issuer certificate for certificate in certification path.", + e); + } + + if (issuers.IsEmpty) + throw new Exception("No issuer certificate for certificate in certification path found."); + + foreach (X509Certificate issuer in issuers) + { + builderResult = Build(issuer, pkixParams, tbvPath); + + if (builderResult != null) + break; + } + } + } + catch (Exception e) + { + certPathException = e; + } + + if (builderResult == null) + { + tbvPath.Remove(tbvCert); + } + + return builderResult; + } + } +} diff --git a/crypto/src/pkix/PkixCertPathBuilderException.cs b/crypto/src/pkix/PkixCertPathBuilderException.cs index 106075a63..5a4944dd8 100644 --- a/crypto/src/pkix/PkixCertPathBuilderException.cs +++ b/crypto/src/pkix/PkixCertPathBuilderException.cs @@ -7,7 +7,10 @@ namespace Org.BouncyCastle.Pkix /// <summary> /// Summary description for PkixCertPathBuilderException. /// </summary> - public class PkixCertPathBuilderException : GeneralSecurityException +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class PkixCertPathBuilderException : GeneralSecurityException { public PkixCertPathBuilderException() : base() { } diff --git a/crypto/src/pkix/PkixCertPathBuilderResult.cs b/crypto/src/pkix/PkixCertPathBuilderResult.cs new file mode 100644 index 000000000..f8003032f --- /dev/null +++ b/crypto/src/pkix/PkixCertPathBuilderResult.cs @@ -0,0 +1,45 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Pkix; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// Summary description for PkixCertPathBuilderResult. + /// </summary> + public class PkixCertPathBuilderResult + : PkixCertPathValidatorResult//, ICertPathBuilderResult + { + private PkixCertPath certPath; + + public PkixCertPathBuilderResult( + PkixCertPath certPath, + TrustAnchor trustAnchor, + PkixPolicyNode policyTree, + AsymmetricKeyParameter subjectPublicKey) + : base(trustAnchor, policyTree, subjectPublicKey) + { + if (certPath == null) + throw new ArgumentNullException("certPath"); + + this.certPath = certPath; + } + + public PkixCertPath CertPath + { + get { return certPath; } + } + + public override string ToString() + { + StringBuilder s = new StringBuilder(); + s.Append("SimplePKIXCertPathBuilderResult: [\n"); + s.Append(" Certification Path: ").Append(CertPath).Append('\n'); + s.Append(" Trust Anchor: ").Append(this.TrustAnchor.TrustedCert.IssuerDN.ToString()).Append('\n'); + s.Append(" Subject Public Key: ").Append(this.SubjectPublicKey).Append("\n]"); + return s.ToString(); + } + } +} diff --git a/crypto/src/pkix/PkixCertPathChecker.cs b/crypto/src/pkix/PkixCertPathChecker.cs new file mode 100644 index 000000000..f22738d89 --- /dev/null +++ b/crypto/src/pkix/PkixCertPathChecker.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + public abstract class PkixCertPathChecker + { + protected PkixCertPathChecker() + { + } + + /** + * Initializes the internal state of this <code>PKIXCertPathChecker</code>. + * <p> + * The <code>forward</code> flag specifies the order that certificates + * will be passed to the {@link #check check} method (forward or reverse). A + * <code>PKIXCertPathChecker</code> <b>must</b> support reverse checking + * and <b>may</b> support forward checking. + * </p> + * + * @param forward + * the order that certificates are presented to the + * <code>check</code> method. If <code>true</code>, + * certificates are presented from target to most-trusted CA + * (forward); if <code>false</code>, from most-trusted CA to + * target (reverse). + * @exception CertPathValidatorException + * if this <code>PKIXCertPathChecker</code> is unable to + * check certificates in the specified order; it should never + * be thrown if the forward flag is false since reverse + * checking must be supported + */ + public abstract void Init(bool forward); + //throws CertPathValidatorException; + + /** + * Indicates if forward checking is supported. Forward checking refers to + * the ability of the <code>PKIXCertPathChecker</code> to perform its + * checks when certificates are presented to the <code>check</code> method + * in the forward direction (from target to most-trusted CA). + * + * @return <code>true</code> if forward checking is supported, + * <code>false</code> otherwise + */ + public abstract bool IsForwardCheckingSupported(); + + /** + * Returns an immutable <code>Set</code> of X.509 certificate extensions + * that this <code>PKIXCertPathChecker</code> supports (i.e. recognizes, + * is able to process), or <code>null</code> if no extensions are + * supported. + * <p> + * Each element of the set is a <code>String</code> representing the + * Object Identifier (OID) of the X.509 extension that is supported. The OID + * is represented by a set of nonnegative integers separated by periods. + * </p><p> + * All X.509 certificate extensions that a <code>PKIXCertPathChecker</code> + * might possibly be able to process should be included in the set. + * </p> + * + * @return an immutable <code>Set</code> of X.509 extension OIDs (in + * <code>String</code> format) supported by this + * <code>PKIXCertPathChecker</code>, or <code>null</code> if no + * extensions are supported + */ + public abstract ISet GetSupportedExtensions(); + + /** + * Performs the check(s) on the specified certificate using its internal + * state and removes any critical extensions that it processes from the + * specified collection of OID strings that represent the unresolved + * critical extensions. The certificates are presented in the order + * specified by the <code>init</code> method. + * + * @param cert + * the <code>Certificate</code> to be checked + * @param unresolvedCritExts + * a <code>Collection</code> of OID strings representing the + * current set of unresolved critical extensions + * @exception CertPathValidatorException + * if the specified certificate does not pass the check + */ + public abstract void Check(X509Certificate cert, ICollection unresolvedCritExts); + //throws CertPathValidatorException; + + /** + * Returns a clone of this object. Calls the <code>Object.clone()</code> + * method. All subclasses which maintain state must support and override + * this method, if necessary. + * + * @return a copy of this <code>PKIXCertPathChecker</code> + */ + public virtual object Clone() + { + // TODO Check this + return base.MemberwiseClone(); + } + } +} diff --git a/crypto/src/pkix/PkixCertPathValidator.cs b/crypto/src/pkix/PkixCertPathValidator.cs new file mode 100644 index 000000000..7eb838886 --- /dev/null +++ b/crypto/src/pkix/PkixCertPathValidator.cs @@ -0,0 +1,420 @@ +using System; +using System.Collections; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /** + * The <i>Service Provider Interface</i> (<b>SPI</b>) + * for the {@link CertPathValidator CertPathValidator} class. All + * <code>CertPathValidator</code> implementations must include a class (the + * SPI class) that extends this class (<code>CertPathValidatorSpi</code>) + * and implements all of its methods. In general, instances of this class + * should only be accessed through the <code>CertPathValidator</code> class. + * For details, see the Java Cryptography Architecture.<br /> + * <br /> + * <b>Concurrent Access</b><br /> + * <br /> + * Instances of this class need not be protected against concurrent + * access from multiple threads. Threads that need to access a single + * <code>CertPathValidatorSpi</code> instance concurrently should synchronize + * amongst themselves and provide the necessary locking before calling the + * wrapping <code>CertPathValidator</code> object.<br /> + * <br /> + * However, implementations of <code>CertPathValidatorSpi</code> may still + * encounter concurrency issues, since multiple threads each + * manipulating a different <code>CertPathValidatorSpi</code> instance need not + * synchronize. + */ + /// <summary> + /// CertPathValidatorSpi implementation for X.509 Certificate validation a la RFC + /// 3280. + /// </summary> + public class PkixCertPathValidator + { + public virtual PkixCertPathValidatorResult Validate( + PkixCertPath certPath, + PkixParameters paramsPkix) + { + if (paramsPkix.GetTrustAnchors() == null) + { + throw new ArgumentException( + "trustAnchors is null, this is not allowed for certification path validation.", + "parameters"); + } + + // + // 6.1.1 - inputs + // + + // + // (a) + // + IList certs = certPath.Certificates; + int n = certs.Count; + + if (certs.Count == 0) + throw new PkixCertPathValidatorException("Certification path is empty.", null, certPath, 0); + + // + // (b) + // + // DateTime validDate = PkixCertPathValidatorUtilities.GetValidDate(paramsPkix); + + // + // (c) + // + ISet userInitialPolicySet = paramsPkix.GetInitialPolicies(); + + // + // (d) + // + TrustAnchor trust; + try + { + trust = PkixCertPathValidatorUtilities.FindTrustAnchor( + (X509Certificate)certs[certs.Count - 1], + paramsPkix.GetTrustAnchors()); + } + catch (Exception e) + { + throw new PkixCertPathValidatorException(e.Message, e, certPath, certs.Count - 1); + } + + if (trust == null) + throw new PkixCertPathValidatorException("Trust anchor for certification path not found.", null, certPath, -1); + + // + // (e), (f), (g) are part of the paramsPkix object. + // + IEnumerator certIter; + int index = 0; + int i; + // Certificate for each interation of the validation loop + // Signature information for each iteration of the validation loop + // + // 6.1.2 - setup + // + + // + // (a) + // + IList[] policyNodes = new IList[n + 1]; + for (int j = 0; j < policyNodes.Length; j++) + { + policyNodes[j] = Platform.CreateArrayList(); + } + + ISet policySet = new HashSet(); + + policySet.Add(Rfc3280CertPathUtilities.ANY_POLICY); + + PkixPolicyNode validPolicyTree = new PkixPolicyNode(Platform.CreateArrayList(), 0, policySet, null, new HashSet(), + Rfc3280CertPathUtilities.ANY_POLICY, false); + + policyNodes[0].Add(validPolicyTree); + + // + // (b) and (c) + // + PkixNameConstraintValidator nameConstraintValidator = new PkixNameConstraintValidator(); + + // (d) + // + int explicitPolicy; + ISet acceptablePolicies = new HashSet(); + + if (paramsPkix.IsExplicitPolicyRequired) + { + explicitPolicy = 0; + } + else + { + explicitPolicy = n + 1; + } + + // + // (e) + // + int inhibitAnyPolicy; + + if (paramsPkix.IsAnyPolicyInhibited) + { + inhibitAnyPolicy = 0; + } + else + { + inhibitAnyPolicy = n + 1; + } + + // + // (f) + // + int policyMapping; + + if (paramsPkix.IsPolicyMappingInhibited) + { + policyMapping = 0; + } + else + { + policyMapping = n + 1; + } + + // + // (g), (h), (i), (j) + // + AsymmetricKeyParameter workingPublicKey; + X509Name workingIssuerName; + + X509Certificate sign = trust.TrustedCert; + try + { + if (sign != null) + { + workingIssuerName = sign.SubjectDN; + workingPublicKey = sign.GetPublicKey(); + } + else + { + workingIssuerName = new X509Name(trust.CAName); + workingPublicKey = trust.CAPublicKey; + } + } + catch (ArgumentException ex) + { + throw new PkixCertPathValidatorException("Subject of trust anchor could not be (re)encoded.", ex, certPath, + -1); + } + + AlgorithmIdentifier workingAlgId = null; + try + { + workingAlgId = PkixCertPathValidatorUtilities.GetAlgorithmIdentifier(workingPublicKey); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException( + "Algorithm identifier of public key of trust anchor could not be read.", e, certPath, -1); + } + +// DerObjectIdentifier workingPublicKeyAlgorithm = workingAlgId.ObjectID; +// Asn1Encodable workingPublicKeyParameters = workingAlgId.Parameters; + + // + // (k) + // + int maxPathLength = n; + + // + // 6.1.3 + // + + X509CertStoreSelector certConstraints = paramsPkix.GetTargetCertConstraints(); + if (certConstraints != null && !certConstraints.Match((X509Certificate)certs[0])) + { + throw new PkixCertPathValidatorException( + "Target certificate in certification path does not match targetConstraints.", null, certPath, 0); + } + + // + // initialize CertPathChecker's + // + IList pathCheckers = paramsPkix.GetCertPathCheckers(); + certIter = pathCheckers.GetEnumerator(); + + while (certIter.MoveNext()) + { + ((PkixCertPathChecker)certIter.Current).Init(false); + } + + X509Certificate cert = null; + + for (index = certs.Count - 1; index >= 0; index--) + { + // try + // { + // + // i as defined in the algorithm description + // + i = n - index; + + // + // set certificate to be checked in this round + // sign and workingPublicKey and workingIssuerName are set + // at the end of the for loop and initialized the + // first time from the TrustAnchor + // + cert = (X509Certificate)certs[index]; + + // + // 6.1.3 + // + + Rfc3280CertPathUtilities.ProcessCertA(certPath, paramsPkix, index, workingPublicKey, + workingIssuerName, sign); + + Rfc3280CertPathUtilities.ProcessCertBC(certPath, index, nameConstraintValidator); + + validPolicyTree = Rfc3280CertPathUtilities.ProcessCertD(certPath, index, + acceptablePolicies, validPolicyTree, policyNodes, inhibitAnyPolicy); + + validPolicyTree = Rfc3280CertPathUtilities.ProcessCertE(certPath, index, validPolicyTree); + + Rfc3280CertPathUtilities.ProcessCertF(certPath, index, validPolicyTree, explicitPolicy); + + // + // 6.1.4 + // + + if (i != n) + { + if (cert != null && cert.Version == 1) + { + throw new PkixCertPathValidatorException( + "Version 1 certificates can't be used as CA ones.", null, certPath, index); + } + + Rfc3280CertPathUtilities.PrepareNextCertA(certPath, index); + + validPolicyTree = Rfc3280CertPathUtilities.PrepareCertB(certPath, index, policyNodes, + validPolicyTree, policyMapping); + + Rfc3280CertPathUtilities.PrepareNextCertG(certPath, index, nameConstraintValidator); + + // (h) + explicitPolicy = Rfc3280CertPathUtilities.PrepareNextCertH1(certPath, index, explicitPolicy); + policyMapping = Rfc3280CertPathUtilities.PrepareNextCertH2(certPath, index, policyMapping); + inhibitAnyPolicy = Rfc3280CertPathUtilities.PrepareNextCertH3(certPath, index, inhibitAnyPolicy); + + // + // (i) + // + explicitPolicy = Rfc3280CertPathUtilities.PrepareNextCertI1(certPath, index, explicitPolicy); + policyMapping = Rfc3280CertPathUtilities.PrepareNextCertI2(certPath, index, policyMapping); + + // (j) + inhibitAnyPolicy = Rfc3280CertPathUtilities.PrepareNextCertJ(certPath, index, inhibitAnyPolicy); + + // (k) + Rfc3280CertPathUtilities.PrepareNextCertK(certPath, index); + + // (l) + maxPathLength = Rfc3280CertPathUtilities.PrepareNextCertL(certPath, index, maxPathLength); + + // (m) + maxPathLength = Rfc3280CertPathUtilities.PrepareNextCertM(certPath, index, maxPathLength); + + // (n) + Rfc3280CertPathUtilities.PrepareNextCertN(certPath, index); + + ISet criticalExtensions1 = cert.GetCriticalExtensionOids(); + + if (criticalExtensions1 != null) + { + criticalExtensions1 = new HashSet(criticalExtensions1); + + // these extensions are handled by the algorithm + criticalExtensions1.Remove(X509Extensions.KeyUsage.Id); + criticalExtensions1.Remove(X509Extensions.CertificatePolicies.Id); + criticalExtensions1.Remove(X509Extensions.PolicyMappings.Id); + criticalExtensions1.Remove(X509Extensions.InhibitAnyPolicy.Id); + criticalExtensions1.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions1.Remove(X509Extensions.DeltaCrlIndicator.Id); + criticalExtensions1.Remove(X509Extensions.PolicyConstraints.Id); + criticalExtensions1.Remove(X509Extensions.BasicConstraints.Id); + criticalExtensions1.Remove(X509Extensions.SubjectAlternativeName.Id); + criticalExtensions1.Remove(X509Extensions.NameConstraints.Id); + } + else + { + criticalExtensions1 = new HashSet(); + } + + // (o) + Rfc3280CertPathUtilities.PrepareNextCertO(certPath, index, criticalExtensions1, pathCheckers); + + // set signing certificate for next round + sign = cert; + + // (c) + workingIssuerName = sign.SubjectDN; + + // (d) + try + { + workingPublicKey = PkixCertPathValidatorUtilities.GetNextWorkingKey(certPath.Certificates, index); + } + catch (PkixCertPathValidatorException e) + { + throw new PkixCertPathValidatorException("Next working key could not be retrieved.", e, certPath, index); + } + + workingAlgId = PkixCertPathValidatorUtilities.GetAlgorithmIdentifier(workingPublicKey); + // (f) +// workingPublicKeyAlgorithm = workingAlgId.ObjectID; + // (e) +// workingPublicKeyParameters = workingAlgId.Parameters; + } + } + + // + // 6.1.5 Wrap-up procedure + // + + explicitPolicy = Rfc3280CertPathUtilities.WrapupCertA(explicitPolicy, cert); + + explicitPolicy = Rfc3280CertPathUtilities.WrapupCertB(certPath, index + 1, explicitPolicy); + + // + // (c) (d) and (e) are already done + // + + // + // (f) + // + ISet criticalExtensions = cert.GetCriticalExtensionOids(); + + if (criticalExtensions != null) + { + criticalExtensions = new HashSet(criticalExtensions); + + // Requires .Id + // these extensions are handled by the algorithm + criticalExtensions.Remove(X509Extensions.KeyUsage.Id); + criticalExtensions.Remove(X509Extensions.CertificatePolicies.Id); + criticalExtensions.Remove(X509Extensions.PolicyMappings.Id); + criticalExtensions.Remove(X509Extensions.InhibitAnyPolicy.Id); + criticalExtensions.Remove(X509Extensions.IssuingDistributionPoint.Id); + criticalExtensions.Remove(X509Extensions.DeltaCrlIndicator.Id); + criticalExtensions.Remove(X509Extensions.PolicyConstraints.Id); + criticalExtensions.Remove(X509Extensions.BasicConstraints.Id); + criticalExtensions.Remove(X509Extensions.SubjectAlternativeName.Id); + criticalExtensions.Remove(X509Extensions.NameConstraints.Id); + criticalExtensions.Remove(X509Extensions.CrlDistributionPoints.Id); + } + else + { + criticalExtensions = new HashSet(); + } + + Rfc3280CertPathUtilities.WrapupCertF(certPath, index + 1, pathCheckers, criticalExtensions); + + PkixPolicyNode intersection = Rfc3280CertPathUtilities.WrapupCertG(certPath, paramsPkix, userInitialPolicySet, + index + 1, policyNodes, validPolicyTree, acceptablePolicies); + + if ((explicitPolicy > 0) || (intersection != null)) + { + return new PkixCertPathValidatorResult(trust, intersection, cert.GetPublicKey()); + } + + throw new PkixCertPathValidatorException("Path processing failed on policy.", null, certPath, index); + } + } +} diff --git a/crypto/src/pkix/PkixCertPathValidatorException.cs b/crypto/src/pkix/PkixCertPathValidatorException.cs index 504a3c646..35522c6f8 100644 --- a/crypto/src/pkix/PkixCertPathValidatorException.cs +++ b/crypto/src/pkix/PkixCertPathValidatorException.cs @@ -27,8 +27,11 @@ namespace Org.BouncyCastle.Pkix * * @see CertPathValidator **/ - - public class PkixCertPathValidatorException : GeneralSecurityException +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class PkixCertPathValidatorException + : GeneralSecurityException { private Exception cause; private PkixCertPath certPath; diff --git a/crypto/src/pkix/PkixCertPathValidatorResult.cs b/crypto/src/pkix/PkixCertPathValidatorResult.cs new file mode 100644 index 000000000..c7d81c7f5 --- /dev/null +++ b/crypto/src/pkix/PkixCertPathValidatorResult.cs @@ -0,0 +1,69 @@ +using System; +using System.Text; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// Summary description for PkixCertPathValidatorResult. + /// </summary> + public class PkixCertPathValidatorResult + //: ICertPathValidatorResult + { + private TrustAnchor trustAnchor; + private PkixPolicyNode policyTree; + private AsymmetricKeyParameter subjectPublicKey; + + public PkixPolicyNode PolicyTree + { + get { return this.policyTree; } + } + + public TrustAnchor TrustAnchor + { + get { return this.trustAnchor; } + } + + public AsymmetricKeyParameter SubjectPublicKey + { + get { return this.subjectPublicKey; } + } + + public PkixCertPathValidatorResult( + TrustAnchor trustAnchor, + PkixPolicyNode policyTree, + AsymmetricKeyParameter subjectPublicKey) + { + if (subjectPublicKey == null) + { + throw new NullReferenceException("subjectPublicKey must be non-null"); + } + if (trustAnchor == null) + { + throw new NullReferenceException("trustAnchor must be non-null"); + } + + this.trustAnchor = trustAnchor; + this.policyTree = policyTree; + this.subjectPublicKey = subjectPublicKey; + } + + public object Clone() + { + return new PkixCertPathValidatorResult(this.TrustAnchor, this.PolicyTree, this.SubjectPublicKey); + } + + public override String ToString() + { + StringBuilder sB = new StringBuilder(); + sB.Append("PKIXCertPathValidatorResult: [ \n"); + sB.Append(" Trust Anchor: ").Append(this.TrustAnchor).Append('\n'); + sB.Append(" Policy Tree: ").Append(this.PolicyTree).Append('\n'); + sB.Append(" Subject Public Key: ").Append(this.SubjectPublicKey).Append("\n]"); + return sB.ToString(); + } + + } +} diff --git a/crypto/src/pkix/PkixCertPathValidatorUtilities.cs b/crypto/src/pkix/PkixCertPathValidatorUtilities.cs index 305b2de35..acea77856 100644 --- a/crypto/src/pkix/PkixCertPathValidatorUtilities.cs +++ b/crypto/src/pkix/PkixCertPathValidatorUtilities.cs @@ -528,7 +528,7 @@ namespace Org.BouncyCastle.Pkix } catch (Exception e) { - new Exception( + throw new Exception( "Reason code CRL entry extension could not be decoded.", e); } @@ -683,8 +683,8 @@ namespace Org.BouncyCastle.Pkix /// the certificates</param> /// <param name="certStores">a List containing only X509Store objects. These /// are used to search for certificates.</param> - /// <returns>a Collection of all found <see cref="X509Certificate "/> or - /// org.bouncycastle.x509.X509AttributeCertificate objects. + /// <returns>a Collection of all found <see cref="X509Certificate"/> or + /// <see cref="Org.BouncyCastle.X509.IX509AttributeCertificate"/> objects. /// May be empty but never <code>null</code>.</returns> /// <exception cref="Exception"></exception> internal static ICollection FindCertificates( @@ -864,7 +864,7 @@ namespace Org.BouncyCastle.Pkix } catch (Exception e) { - new Exception("Could not get issuer information from distribution point.", e); + throw new Exception("Could not get issuer information from distribution point.", e); } if (cert is X509Certificate) @@ -924,7 +924,7 @@ namespace Org.BouncyCastle.Pkix } catch (IOException e) { - new Exception("Cannot extract issuer from CRL.", e); + throw new Exception("Cannot extract issuer from CRL.", e); } BigInteger completeCRLNumber = null; diff --git a/crypto/src/pkix/PkixCrlUtilities.cs b/crypto/src/pkix/PkixCrlUtilities.cs new file mode 100644 index 000000000..c386b8a05 --- /dev/null +++ b/crypto/src/pkix/PkixCrlUtilities.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.X509; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixCrlUtilities + { + public virtual ISet FindCrls(X509CrlStoreSelector crlselect, PkixParameters paramsPkix, DateTime currentDate) + { + ISet initialSet = new HashSet(); + + // get complete CRL(s) + try + { + initialSet.AddAll(FindCrls(crlselect, paramsPkix.GetAdditionalStores())); + initialSet.AddAll(FindCrls(crlselect, paramsPkix.GetStores())); + } + catch (Exception e) + { + throw new Exception("Exception obtaining complete CRLs.", e); + } + + ISet finalSet = new HashSet(); + DateTime validityDate = currentDate; + + if (paramsPkix.Date != null) + { + validityDate = paramsPkix.Date.Value; + } + + // based on RFC 5280 6.3.3 + foreach (X509Crl crl in initialSet) + { + if (crl.NextUpdate.Value.CompareTo(validityDate) > 0) + { + X509Certificate cert = crlselect.CertificateChecking; + + if (cert != null) + { + if (crl.ThisUpdate.CompareTo(cert.NotAfter) < 0) + { + finalSet.Add(crl); + } + } + else + { + finalSet.Add(crl); + } + } + } + + return finalSet; + } + + public virtual ISet FindCrls(X509CrlStoreSelector crlselect, PkixParameters paramsPkix) + { + ISet completeSet = new HashSet(); + + // get complete CRL(s) + try + { + completeSet.AddAll(FindCrls(crlselect, paramsPkix.GetStores())); + } + catch (Exception e) + { + throw new Exception("Exception obtaining complete CRLs.", e); + } + + return completeSet; + } + + /// <summary> + /// crl checking + /// Return a Collection of all CRLs found in the X509Store's that are + /// matching the crlSelect criteriums. + /// </summary> + /// <param name="crlSelect">a {@link X509CRLStoreSelector} object that will be used + /// to select the CRLs</param> + /// <param name="crlStores">a List containing only {@link org.bouncycastle.x509.X509Store + /// X509Store} objects. These are used to search for CRLs</param> + /// <returns>a Collection of all found {@link X509CRL X509CRL} objects. May be + /// empty but never <code>null</code>. + /// </returns> + private ICollection FindCrls(X509CrlStoreSelector crlSelect, IList crlStores) + { + ISet crls = new HashSet(); + + Exception lastException = null; + bool foundValidStore = false; + + foreach (IX509Store store in crlStores) + { + try + { + crls.AddAll(store.GetMatches(crlSelect)); + foundValidStore = true; + } + catch (X509StoreException e) + { + lastException = new Exception("Exception searching in X.509 CRL store.", e); + } + } + + if (!foundValidStore && lastException != null) + throw lastException; + + return crls; + } + } +} diff --git a/crypto/src/pkix/PkixNameConstraintValidator.cs b/crypto/src/pkix/PkixNameConstraintValidator.cs new file mode 100644 index 000000000..535f95174 --- /dev/null +++ b/crypto/src/pkix/PkixNameConstraintValidator.cs @@ -0,0 +1,1937 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + public class PkixNameConstraintValidator + { + private ISet excludedSubtreesDN = new HashSet(); + + private ISet excludedSubtreesDNS = new HashSet(); + + private ISet excludedSubtreesEmail = new HashSet(); + + private ISet excludedSubtreesURI = new HashSet(); + + private ISet excludedSubtreesIP = new HashSet(); + + private ISet permittedSubtreesDN; + + private ISet permittedSubtreesDNS; + + private ISet permittedSubtreesEmail; + + private ISet permittedSubtreesURI; + + private ISet permittedSubtreesIP; + + public PkixNameConstraintValidator() + { + } + + private static bool WithinDNSubtree( + Asn1Sequence dns, + Asn1Sequence subtree) + { + if (subtree.Count < 1) + { + return false; + } + + if (subtree.Count > dns.Count) + { + return false; + } + + for (int j = subtree.Count - 1; j >= 0; j--) + { + if (!(subtree[j].Equals(dns[j]))) + { + return false; + } + } + + return true; + } + + public void CheckPermittedDN(Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + CheckPermittedDN(permittedSubtreesDN, dns); + } + + public void CheckExcludedDN(Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + CheckExcludedDN(excludedSubtreesDN, dns); + } + + private void CheckPermittedDN(ISet permitted, Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + if ((permitted.Count == 0) && dns.Count == 0) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dns, subtree)) + { + return; + } + } + + throw new PkixNameConstraintValidatorException( + "Subject distinguished name is not from a permitted subtree"); + } + + private void CheckExcludedDN(ISet excluded, Asn1Sequence dns) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dns, subtree)) + { + throw new PkixNameConstraintValidatorException( + "Subject distinguished name is from an excluded subtree"); + } + } + } + + private ISet IntersectDN(ISet permitted, ISet dns) + { + ISet intersect = new HashSet(); + for (IEnumerator it = dns.GetEnumerator(); it.MoveNext(); ) + { + Asn1Sequence dn = Asn1Sequence.GetInstance(((GeneralSubtree)it + .Current).Base.Name.ToAsn1Object()); + if (permitted == null) + { + if (dn != null) + { + intersect.Add(dn); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)_iter.Current; + + if (WithinDNSubtree(dn, subtree)) + { + intersect.Add(dn); + } + else if (WithinDNSubtree(subtree, dn)) + { + intersect.Add(subtree); + } + } + } + } + return intersect; + } + + private ISet UnionDN(ISet excluded, Asn1Sequence dn) + { + if (excluded.IsEmpty) + { + if (dn == null) + { + return excluded; + } + excluded.Add(dn); + + return excluded; + } + else + { + ISet intersect = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + Asn1Sequence subtree = (Asn1Sequence)it.Current; + + if (WithinDNSubtree(dn, subtree)) + { + intersect.Add(subtree); + } + else if (WithinDNSubtree(subtree, dn)) + { + intersect.Add(dn); + } + else + { + intersect.Add(subtree); + intersect.Add(dn); + } + } + + return intersect; + } + } + + private ISet IntersectEmail(ISet permitted, ISet emails) + { + ISet intersect = new HashSet(); + for (IEnumerator it = emails.GetEnumerator(); it.MoveNext(); ) + { + String email = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + + if (permitted == null) + { + if (email != null) + { + intersect.Add(email); + } + } + else + { + IEnumerator it2 = permitted.GetEnumerator(); + while (it2.MoveNext()) + { + String _permitted = (String)it2.Current; + + intersectEmail(email, _permitted, intersect); + } + } + } + return intersect; + } + + private ISet UnionEmail(ISet excluded, String email) + { + if (excluded.IsEmpty) + { + if (email == null) + { + return excluded; + } + excluded.Add(email); + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + String _excluded = (String)it.Current; + + unionEmail(_excluded, email, union); + } + + return union; + } + } + + /** + * Returns the intersection of the permitted IP ranges in + * <code>permitted</code> with <code>ip</code>. + * + * @param permitted A <code>Set</code> of permitted IP addresses with + * their subnet mask as byte arrays. + * @param ips The IP address with its subnet mask. + * @return The <code>Set</code> of permitted IP ranges intersected with + * <code>ip</code>. + */ + private ISet IntersectIP(ISet permitted, ISet ips) + { + ISet intersect = new HashSet(); + for (IEnumerator it = ips.GetEnumerator(); it.MoveNext(); ) + { + byte[] ip = Asn1OctetString.GetInstance( + ((GeneralSubtree)it.Current).Base.Name).GetOctets(); + if (permitted == null) + { + if (ip != null) + { + intersect.Add(ip); + } + } + else + { + IEnumerator it2 = permitted.GetEnumerator(); + while (it2.MoveNext()) + { + byte[] _permitted = (byte[])it2.Current; + intersect.AddAll(IntersectIPRange(_permitted, ip)); + } + } + } + return intersect; + } + + /** + * Returns the union of the excluded IP ranges in <code>excluded</code> + * with <code>ip</code>. + * + * @param excluded A <code>Set</code> of excluded IP addresses with their + * subnet mask as byte arrays. + * @param ip The IP address with its subnet mask. + * @return The <code>Set</code> of excluded IP ranges unified with + * <code>ip</code> as byte arrays. + */ + private ISet UnionIP(ISet excluded, byte[] ip) + { + if (excluded.IsEmpty) + { + if (ip == null) + { + return excluded; + } + excluded.Add(ip); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator it = excluded.GetEnumerator(); + while (it.MoveNext()) + { + byte[] _excluded = (byte[])it.Current; + union.AddAll(UnionIPRange(_excluded, ip)); + } + + return union; + } + } + + /** + * Calculates the union if two IP ranges. + * + * @param ipWithSubmask1 The first IP address with its subnet mask. + * @param ipWithSubmask2 The second IP address with its subnet mask. + * @return A <code>Set</code> with the union of both addresses. + */ + private ISet UnionIPRange(byte[] ipWithSubmask1, byte[] ipWithSubmask2) + { + ISet set = new HashSet(); + + // difficult, adding always all IPs is not wrong + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(ipWithSubmask1, ipWithSubmask2)) + { + set.Add(ipWithSubmask1); + } + else + { + set.Add(ipWithSubmask1); + set.Add(ipWithSubmask2); + } + return set; + } + + /** + * Calculates the interesction if two IP ranges. + * + * @param ipWithSubmask1 The first IP address with its subnet mask. + * @param ipWithSubmask2 The second IP address with its subnet mask. + * @return A <code>Set</code> with the single IP address with its subnet + * mask as a byte array or an empty <code>Set</code>. + */ + private ISet IntersectIPRange(byte[] ipWithSubmask1, byte[] ipWithSubmask2) + { + if (ipWithSubmask1.Length != ipWithSubmask2.Length) + { + //Collections.EMPTY_SET; + return new HashSet(); + } + + byte[][] temp = ExtractIPsAndSubnetMasks(ipWithSubmask1, ipWithSubmask2); + byte[] ip1 = temp[0]; + byte[] subnetmask1 = temp[1]; + byte[] ip2 = temp[2]; + byte[] subnetmask2 = temp[3]; + + byte[][] minMax = MinMaxIPs(ip1, subnetmask1, ip2, subnetmask2); + byte[] min; + byte[] max; + max = Min(minMax[1], minMax[3]); + min = Max(minMax[0], minMax[2]); + + // minimum IP address must be bigger than max + if (CompareTo(min, max) == 1) + { + //return Collections.EMPTY_SET; + return new HashSet(); + } + // OR keeps all significant bits + byte[] ip = Or(minMax[0], minMax[2]); + byte[] subnetmask = Or(subnetmask1, subnetmask2); + + //return new HashSet( ICollectionsingleton(IpWithSubnetMask(ip, subnetmask)); + ISet hs = new HashSet(); + hs.Add(IpWithSubnetMask(ip, subnetmask)); + + return hs; + } + + /** + * Concatenates the IP address with its subnet mask. + * + * @param ip The IP address. + * @param subnetMask Its subnet mask. + * @return The concatenated IP address with its subnet mask. + */ + private byte[] IpWithSubnetMask(byte[] ip, byte[] subnetMask) + { + int ipLength = ip.Length; + byte[] temp = new byte[ipLength * 2]; + Array.Copy(ip, 0, temp, 0, ipLength); + Array.Copy(subnetMask, 0, temp, ipLength, ipLength); + return temp; + } + + /** + * Splits the IP addresses and their subnet mask. + * + * @param ipWithSubmask1 The first IP address with the subnet mask. + * @param ipWithSubmask2 The second IP address with the subnet mask. + * @return An array with two elements. Each element contains the IP address + * and the subnet mask in this order. + */ + private byte[][] ExtractIPsAndSubnetMasks( + byte[] ipWithSubmask1, + byte[] ipWithSubmask2) + { + int ipLength = ipWithSubmask1.Length / 2; + byte[] ip1 = new byte[ipLength]; + byte[] subnetmask1 = new byte[ipLength]; + Array.Copy(ipWithSubmask1, 0, ip1, 0, ipLength); + Array.Copy(ipWithSubmask1, ipLength, subnetmask1, 0, ipLength); + + byte[] ip2 = new byte[ipLength]; + byte[] subnetmask2 = new byte[ipLength]; + Array.Copy(ipWithSubmask2, 0, ip2, 0, ipLength); + Array.Copy(ipWithSubmask2, ipLength, subnetmask2, 0, ipLength); + return new byte[][] + {ip1, subnetmask1, ip2, subnetmask2}; + } + + /** + * Based on the two IP addresses and their subnet masks the IP range is + * computed for each IP address - subnet mask pair and returned as the + * minimum IP address and the maximum address of the range. + * + * @param ip1 The first IP address. + * @param subnetmask1 The subnet mask of the first IP address. + * @param ip2 The second IP address. + * @param subnetmask2 The subnet mask of the second IP address. + * @return A array with two elements. The first/second element contains the + * min and max IP address of the first/second IP address and its + * subnet mask. + */ + private byte[][] MinMaxIPs( + byte[] ip1, + byte[] subnetmask1, + byte[] ip2, + byte[] subnetmask2) + { + int ipLength = ip1.Length; + byte[] min1 = new byte[ipLength]; + byte[] max1 = new byte[ipLength]; + + byte[] min2 = new byte[ipLength]; + byte[] max2 = new byte[ipLength]; + + for (int i = 0; i < ipLength; i++) + { + min1[i] = (byte)(ip1[i] & subnetmask1[i]); + max1[i] = (byte)(ip1[i] & subnetmask1[i] | ~subnetmask1[i]); + + min2[i] = (byte)(ip2[i] & subnetmask2[i]); + max2[i] = (byte)(ip2[i] & subnetmask2[i] | ~subnetmask2[i]); + } + + return new byte[][] { min1, max1, min2, max2 }; + } + + private void CheckPermittedEmail(ISet permitted, String email) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (EmailIsConstrained(email, str)) + { + return; + } + } + + if (email.Length == 0 && permitted.Count == 0) + { + return; + } + + throw new PkixNameConstraintValidatorException( + "Subject email address is not from a permitted subtree."); + } + + private void CheckExcludedEmail(ISet excluded, String email) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = (String)it.Current; + + if (EmailIsConstrained(email, str)) + { + throw new PkixNameConstraintValidatorException( + "Email address is from an excluded subtree."); + } + } + } + + /** + * Checks if the IP <code>ip</code> is included in the permitted ISet + * <code>permitted</code>. + * + * @param permitted A <code>Set</code> of permitted IP addresses with + * their subnet mask as byte arrays. + * @param ip The IP address. + * @throws PkixNameConstraintValidatorException + * if the IP is not permitted. + */ + private void CheckPermittedIP(ISet permitted, byte[] ip) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + byte[] ipWithSubnet = (byte[])it.Current; + + if (IsIPConstrained(ip, ipWithSubnet)) + { + return; + } + } + if (ip.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "IP is not from a permitted subtree."); + } + + /** + * Checks if the IP <code>ip</code> is included in the excluded ISet + * <code>excluded</code>. + * + * @param excluded A <code>Set</code> of excluded IP addresses with their + * subnet mask as byte arrays. + * @param ip The IP address. + * @throws PkixNameConstraintValidatorException + * if the IP is excluded. + */ + private void checkExcludedIP(ISet excluded, byte[] ip) + //throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + byte[] ipWithSubnet = (byte[])it.Current; + + if (IsIPConstrained(ip, ipWithSubnet)) + { + throw new PkixNameConstraintValidatorException( + "IP is from an excluded subtree."); + } + } + } + + /** + * Checks if the IP address <code>ip</code> is constrained by + * <code>constraint</code>. + * + * @param ip The IP address. + * @param constraint The constraint. This is an IP address concatenated with + * its subnetmask. + * @return <code>true</code> if constrained, <code>false</code> + * otherwise. + */ + private bool IsIPConstrained(byte[] ip, byte[] constraint) + { + int ipLength = ip.Length; + + if (ipLength != (constraint.Length / 2)) + { + return false; + } + + byte[] subnetMask = new byte[ipLength]; + Array.Copy(constraint, ipLength, subnetMask, 0, ipLength); + + byte[] permittedSubnetAddress = new byte[ipLength]; + + byte[] ipSubnetAddress = new byte[ipLength]; + + // the resulting IP address by applying the subnet mask + for (int i = 0; i < ipLength; i++) + { + permittedSubnetAddress[i] = (byte)(constraint[i] & subnetMask[i]); + ipSubnetAddress[i] = (byte)(ip[i] & subnetMask[i]); + } + + return Org.BouncyCastle.Utilities.Arrays.AreEqual(permittedSubnetAddress, ipSubnetAddress); + } + + private bool EmailIsConstrained(String email, String constraint) + { + String sub = email.Substring(email.IndexOf('@') + 1); + // a particular mailbox + if (constraint.IndexOf('@') != -1) + { + if (email.ToUpper().Equals(constraint.ToUpper())) + { + return true; + } + } + // on particular host + else if (!(constraint[0].Equals('.'))) + { + if (sub.ToUpper().Equals(constraint.ToUpper())) + { + return true; + } + } + // address in sub domain + else if (WithinDomain(sub, constraint)) + { + return true; + } + return false; + } + + private bool WithinDomain(String testDomain, String domain) + { + String tempDomain = domain; + if (tempDomain.StartsWith(".")) + { + tempDomain = tempDomain.Substring(1); + } + String[] domainParts = tempDomain.Split('.'); // Strings.split(tempDomain, '.'); + String[] testDomainParts = testDomain.Split('.'); // Strings.split(testDomain, '.'); + + // must have at least one subdomain + if (testDomainParts.Length <= domainParts.Length) + { + return false; + } + + int d = testDomainParts.Length - domainParts.Length; + for (int i = -1; i < domainParts.Length; i++) + { + if (i == -1) + { + if (testDomainParts[i + d].Equals("")) + { + return false; + } + } + else if (!(Platform.CompareIgnoreCase(testDomainParts[i + d], domainParts[i]) == 0)) + { + return false; + } + } + return true; + } + + private void CheckPermittedDNS(ISet permitted, String dns) + //throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + // is sub domain + if (WithinDomain(dns, str) || dns.ToUpper().Equals(str.ToUpper())) + { + return; + } + } + if (dns.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "DNS is not from a permitted subtree."); + } + + private void checkExcludedDNS(ISet excluded, String dns) + // throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + // is sub domain or the same + if (WithinDomain(dns, str) || (Platform.CompareIgnoreCase(dns, str) == 0)) + { + throw new PkixNameConstraintValidatorException( + "DNS is from an excluded subtree."); + } + } + } + + /** + * The common part of <code>email1</code> and <code>email2</code> is + * added to the union <code>union</code>. If <code>email1</code> and + * <code>email2</code> have nothing in common they are added both. + * + * @param email1 Email address constraint 1. + * @param email2 Email address constraint 2. + * @param union The union. + */ + private void unionEmail(String email1, String email2, ISet union) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(_sub, email2) == 0) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email1 specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) || Platform.CompareIgnoreCase(email1, email2) == 0) + { + union.Add(email2); + } + else if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (Platform.CompareIgnoreCase(_sub, email1) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + } + + private void unionURI(String email1, String email2, ISet union) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(_sub, email2) == 0) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + + } + } + } + // email1 specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) || Platform.CompareIgnoreCase(email1, email2) == 0) + { + union.Add(email2); + } + else if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + // email specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (Platform.CompareIgnoreCase(_sub, email1) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + union.Add(email2); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + union.Add(email1); + } + else + { + union.Add(email1); + union.Add(email2); + } + } + } + } + + private ISet intersectDNS(ISet permitted, ISet dnss) + { + ISet intersect = new HashSet(); + for (IEnumerator it = dnss.GetEnumerator(); it.MoveNext(); ) + { + String dns = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + if (permitted == null) + { + if (dns != null) + { + intersect.Add(dns); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + + if (WithinDomain(_permitted, dns)) + { + intersect.Add(_permitted); + } + else if (WithinDomain(dns, _permitted)) + { + intersect.Add(dns); + } + } + } + } + + return intersect; + } + + protected ISet unionDNS(ISet excluded, String dns) + { + if (excluded.IsEmpty) + { + if (dns == null) + { + return excluded; + } + excluded.Add(dns); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator _iter = excluded.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + + if (WithinDomain(_permitted, dns)) + { + union.Add(dns); + } + else if (WithinDomain(dns, _permitted)) + { + union.Add(_permitted); + } + else + { + union.Add(_permitted); + union.Add(dns); + } + } + + return union; + } + } + + /** + * The most restricting part from <code>email1</code> and + * <code>email2</code> is added to the intersection <code>intersect</code>. + * + * @param email1 Email address constraint 1. + * @param email2 Email address constraint 2. + * @param intersect The intersection. + */ + private void intersectEmail(String email1, String email2, ISet intersect) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + intersect.Add(email1); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(_sub, email2) == 0) + { + intersect.Add(email1); + } + } + } + // email specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) || (Platform.CompareIgnoreCase(email1, email2) == 0)) + { + intersect.Add(email1); + } + else if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + } + // email1 specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email2.IndexOf('@') + 1); + if (Platform.CompareIgnoreCase(_sub, email1) == 0) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + intersect.Add(email1); + } + } + } + } + + private void checkExcludedURI(ISet excluded, String uri) + // throws PkixNameConstraintValidatorException + { + if (excluded.IsEmpty) + { + return; + } + + IEnumerator it = excluded.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (IsUriConstrained(uri, str)) + { + throw new PkixNameConstraintValidatorException( + "URI is from an excluded subtree."); + } + } + } + + private ISet intersectURI(ISet permitted, ISet uris) + { + ISet intersect = new HashSet(); + for (IEnumerator it = uris.GetEnumerator(); it.MoveNext(); ) + { + String uri = ExtractNameAsString(((GeneralSubtree)it.Current) + .Base); + if (permitted == null) + { + if (uri != null) + { + intersect.Add(uri); + } + } + else + { + IEnumerator _iter = permitted.GetEnumerator(); + while (_iter.MoveNext()) + { + String _permitted = (String)_iter.Current; + intersectURI(_permitted, uri, intersect); + } + } + } + return intersect; + } + + private ISet unionURI(ISet excluded, String uri) + { + if (excluded.IsEmpty) + { + if (uri == null) + { + return excluded; + } + excluded.Add(uri); + + return excluded; + } + else + { + ISet union = new HashSet(); + + IEnumerator _iter = excluded.GetEnumerator(); + while (_iter.MoveNext()) + { + String _excluded = (String)_iter.Current; + + unionURI(_excluded, uri, union); + } + + return union; + } + } + + private void intersectURI(String email1, String email2, ISet intersect) + { + // email1 is a particular address + if (email1.IndexOf('@') != -1) + { + String _sub = email1.Substring(email1.IndexOf('@') + 1); + // both are a particular mailbox + if (email2.IndexOf('@') != -1) + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + intersect.Add(email1); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(_sub, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(_sub, email2) == 0) + { + intersect.Add(email1); + } + } + } + // email specifies a domain + else if (email1.StartsWith(".")) + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email1.IndexOf('@') + 1); + if (WithinDomain(_sub, email1)) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2) || (Platform.CompareIgnoreCase(email1, email2) == 0)) + { + intersect.Add(email1); + } + else if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + else + { + if (WithinDomain(email2, email1)) + { + intersect.Add(email2); + } + } + } + // email1 specifies a host + else + { + if (email2.IndexOf('@') != -1) + { + String _sub = email2.Substring(email2.IndexOf('@') + 1); + if (Platform.CompareIgnoreCase(_sub, email1) == 0) + { + intersect.Add(email2); + } + } + // email2 specifies a domain + else if (email2.StartsWith(".")) + { + if (WithinDomain(email1, email2)) + { + intersect.Add(email1); + } + } + // email2 specifies a particular host + else + { + if (Platform.CompareIgnoreCase(email1, email2) == 0) + { + intersect.Add(email1); + } + } + } + } + + private void CheckPermittedURI(ISet permitted, String uri) + // throws PkixNameConstraintValidatorException + { + if (permitted == null) + { + return; + } + + IEnumerator it = permitted.GetEnumerator(); + + while (it.MoveNext()) + { + String str = ((String)it.Current); + + if (IsUriConstrained(uri, str)) + { + return; + } + } + if (uri.Length == 0 && permitted.Count == 0) + { + return; + } + throw new PkixNameConstraintValidatorException( + "URI is not from a permitted subtree."); + } + + private bool IsUriConstrained(String uri, String constraint) + { + String host = ExtractHostFromURL(uri); + // a host + if (!constraint.StartsWith(".")) + { + if (Platform.CompareIgnoreCase(host, constraint) == 0) + { + return true; + } + } + + // in sub domain or domain + else if (WithinDomain(host, constraint)) + { + return true; + } + + return false; + } + + private static String ExtractHostFromURL(String url) + { + // see RFC 1738 + // remove ':' after protocol, e.g. http: + String sub = url.Substring(url.IndexOf(':') + 1); + // extract host from Common Internet Scheme Syntax, e.g. http:// + if (sub.IndexOf("//") != -1) + { + sub = sub.Substring(sub.IndexOf("//") + 2); + } + // first remove port, e.g. http://test.com:21 + if (sub.LastIndexOf(':') != -1) + { + sub = sub.Substring(0, sub.LastIndexOf(':')); + } + // remove user and password, e.g. http://john:password@test.com + sub = sub.Substring(sub.IndexOf(':') + 1); + sub = sub.Substring(sub.IndexOf('@') + 1); + // remove local parts, e.g. http://test.com/bla + if (sub.IndexOf('/') != -1) + { + sub = sub.Substring(0, sub.IndexOf('/')); + } + return sub; + } + + /** + * Checks if the given GeneralName is in the permitted ISet. + * + * @param name The GeneralName + * @throws PkixNameConstraintValidatorException + * If the <code>name</code> + */ + public void checkPermitted(GeneralName name) + // throws PkixNameConstraintValidatorException + { + switch (name.TagNo) + { + case 1: + CheckPermittedEmail(permittedSubtreesEmail, + ExtractNameAsString(name)); + break; + case 2: + CheckPermittedDNS(permittedSubtreesDNS, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 4: + CheckPermittedDN(Asn1Sequence.GetInstance(name.Name.ToAsn1Object())); + break; + case 6: + CheckPermittedURI(permittedSubtreesURI, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 7: + byte[] ip = Asn1OctetString.GetInstance(name.Name).GetOctets(); + + CheckPermittedIP(permittedSubtreesIP, ip); + break; + } + } + + /** + * Check if the given GeneralName is contained in the excluded ISet. + * + * @param name The GeneralName. + * @throws PkixNameConstraintValidatorException + * If the <code>name</code> is + * excluded. + */ + public void checkExcluded(GeneralName name) + // throws PkixNameConstraintValidatorException + { + switch (name.TagNo) + { + case 1: + CheckExcludedEmail(excludedSubtreesEmail, ExtractNameAsString(name)); + break; + case 2: + checkExcludedDNS(excludedSubtreesDNS, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 4: + CheckExcludedDN(Asn1Sequence.GetInstance(name.Name.ToAsn1Object())); + break; + case 6: + checkExcludedURI(excludedSubtreesURI, DerIA5String.GetInstance( + name.Name).GetString()); + break; + case 7: + byte[] ip = Asn1OctetString.GetInstance(name.Name).GetOctets(); + + checkExcludedIP(excludedSubtreesIP, ip); + break; + } + } + + /** + * Updates the permitted ISet of these name constraints with the intersection + * with the given subtree. + * + * @param permitted The permitted subtrees + */ + + public void IntersectPermittedSubtree(Asn1Sequence permitted) + { + IDictionary subtreesMap = Platform.CreateHashtable(); + + // group in ISets in a map ordered by tag no. + for (IEnumerator e = permitted.GetEnumerator(); e.MoveNext(); ) + { + GeneralSubtree subtree = GeneralSubtree.GetInstance(e.Current); + + int tagNo = subtree.Base.TagNo; + if (subtreesMap[tagNo] == null) + { + subtreesMap[tagNo] = new HashSet(); + } + + ((ISet)subtreesMap[tagNo]).Add(subtree); + } + + for (IEnumerator it = subtreesMap.GetEnumerator(); it.MoveNext(); ) + { + DictionaryEntry entry = (DictionaryEntry)it.Current; + + // go through all subtree groups + switch ((int)entry.Key ) + { + case 1: + permittedSubtreesEmail = IntersectEmail(permittedSubtreesEmail, + (ISet)entry.Value); + break; + case 2: + permittedSubtreesDNS = intersectDNS(permittedSubtreesDNS, + (ISet)entry.Value); + break; + case 4: + permittedSubtreesDN = IntersectDN(permittedSubtreesDN, + (ISet)entry.Value); + break; + case 6: + permittedSubtreesURI = intersectURI(permittedSubtreesURI, + (ISet)entry.Value); + break; + case 7: + permittedSubtreesIP = IntersectIP(permittedSubtreesIP, + (ISet)entry.Value); + break; + } + } + } + + private String ExtractNameAsString(GeneralName name) + { + return DerIA5String.GetInstance(name.Name).GetString(); + } + + public void IntersectEmptyPermittedSubtree(int nameType) + { + switch (nameType) + { + case 1: + permittedSubtreesEmail = new HashSet(); + break; + case 2: + permittedSubtreesDNS = new HashSet(); + break; + case 4: + permittedSubtreesDN = new HashSet(); + break; + case 6: + permittedSubtreesURI = new HashSet(); + break; + case 7: + permittedSubtreesIP = new HashSet(); + break; + } + } + + /** + * Adds a subtree to the excluded ISet of these name constraints. + * + * @param subtree A subtree with an excluded GeneralName. + */ + public void AddExcludedSubtree(GeneralSubtree subtree) + { + GeneralName subTreeBase = subtree.Base; + + switch (subTreeBase.TagNo) + { + case 1: + excludedSubtreesEmail = UnionEmail(excludedSubtreesEmail, + ExtractNameAsString(subTreeBase)); + break; + case 2: + excludedSubtreesDNS = unionDNS(excludedSubtreesDNS, + ExtractNameAsString(subTreeBase)); + break; + case 4: + excludedSubtreesDN = UnionDN(excludedSubtreesDN, + (Asn1Sequence)subTreeBase.Name.ToAsn1Object()); + break; + case 6: + excludedSubtreesURI = unionURI(excludedSubtreesURI, + ExtractNameAsString(subTreeBase)); + break; + case 7: + excludedSubtreesIP = UnionIP(excludedSubtreesIP, Asn1OctetString + .GetInstance(subTreeBase.Name).GetOctets()); + break; + } + } + + /** + * Returns the maximum IP address. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The maximum IP address. + */ + private static byte[] Max(byte[] ip1, byte[] ip2) + { + for (int i = 0; i < ip1.Length; i++) + { + if ((ip1[i] & 0xFFFF) > (ip2[i] & 0xFFFF)) + { + return ip1; + } + } + return ip2; + } + + /** + * Returns the minimum IP address. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The minimum IP address. + */ + private static byte[] Min(byte[] ip1, byte[] ip2) + { + for (int i = 0; i < ip1.Length; i++) + { + if ((ip1[i] & 0xFFFF) < (ip2[i] & 0xFFFF)) + { + return ip1; + } + } + return ip2; + } + + /** + * Compares IP address <code>ip1</code> with <code>ip2</code>. If ip1 + * is equal to ip2 0 is returned. If ip1 is bigger 1 is returned, -1 + * otherwise. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return 0 if ip1 is equal to ip2, 1 if ip1 is bigger, -1 otherwise. + */ + private static int CompareTo(byte[] ip1, byte[] ip2) + { + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(ip1, ip2)) + { + return 0; + } + if (Org.BouncyCastle.Utilities.Arrays.AreEqual(Max(ip1, ip2), ip1)) + { + return 1; + } + return -1; + } + + /** + * Returns the logical OR of the IP addresses <code>ip1</code> and + * <code>ip2</code>. + * + * @param ip1 The first IP address. + * @param ip2 The second IP address. + * @return The OR of <code>ip1</code> and <code>ip2</code>. + */ + private static byte[] Or(byte[] ip1, byte[] ip2) + { + byte[] temp = new byte[ip1.Length]; + for (int i = 0; i < ip1.Length; i++) + { + temp[i] = (byte)(ip1[i] | ip2[i]); + } + return temp; + } + + [Obsolete("Use GetHashCode instead")] + public int HashCode() + { + return GetHashCode(); + } + + public override int GetHashCode() + { + return HashCollection(excludedSubtreesDN) + + HashCollection(excludedSubtreesDNS) + + HashCollection(excludedSubtreesEmail) + + HashCollection(excludedSubtreesIP) + + HashCollection(excludedSubtreesURI) + + HashCollection(permittedSubtreesDN) + + HashCollection(permittedSubtreesDNS) + + HashCollection(permittedSubtreesEmail) + + HashCollection(permittedSubtreesIP) + + HashCollection(permittedSubtreesURI); + } + + private int HashCollection(ICollection coll) + { + if (coll == null) + { + return 0; + } + int hash = 0; + IEnumerator it1 = coll.GetEnumerator(); + while (it1.MoveNext()) + { + Object o = it1.Current; + if (o is byte[]) + { + hash += Org.BouncyCastle.Utilities.Arrays.GetHashCode((byte[])o); + } + else + { + hash += o.GetHashCode(); + } + } + return hash; + } + + public override bool Equals(Object o) + { + if (!(o is PkixNameConstraintValidator)) + return false; + + PkixNameConstraintValidator constraintValidator = (PkixNameConstraintValidator)o; + + return CollectionsAreEqual(constraintValidator.excludedSubtreesDN, excludedSubtreesDN) + && CollectionsAreEqual(constraintValidator.excludedSubtreesDNS, excludedSubtreesDNS) + && CollectionsAreEqual(constraintValidator.excludedSubtreesEmail, excludedSubtreesEmail) + && CollectionsAreEqual(constraintValidator.excludedSubtreesIP, excludedSubtreesIP) + && CollectionsAreEqual(constraintValidator.excludedSubtreesURI, excludedSubtreesURI) + && CollectionsAreEqual(constraintValidator.permittedSubtreesDN, permittedSubtreesDN) + && CollectionsAreEqual(constraintValidator.permittedSubtreesDNS, permittedSubtreesDNS) + && CollectionsAreEqual(constraintValidator.permittedSubtreesEmail, permittedSubtreesEmail) + && CollectionsAreEqual(constraintValidator.permittedSubtreesIP, permittedSubtreesIP) + && CollectionsAreEqual(constraintValidator.permittedSubtreesURI, permittedSubtreesURI); + } + + private bool CollectionsAreEqual(ICollection coll1, ICollection coll2) + { + if (coll1 == coll2) + { + return true; + } + if (coll1 == null || coll2 == null) + { + return false; + } + if (coll1.Count != coll2.Count) + { + return false; + } + IEnumerator it1 = coll1.GetEnumerator(); + + while (it1.MoveNext()) + { + Object a = it1.Current; + IEnumerator it2 = coll2.GetEnumerator(); + bool found = false; + while (it2.MoveNext()) + { + Object b = it2.Current; + if (SpecialEquals(a, b)) + { + found = true; + break; + } + } + if (!found) + { + return false; + } + } + return true; + } + + private bool SpecialEquals(Object o1, Object o2) + { + if (o1 == o2) + { + return true; + } + if (o1 == null || o2 == null) + { + return false; + } + if ((o1 is byte[]) && (o2 is byte[])) + { + return Org.BouncyCastle.Utilities.Arrays.AreEqual((byte[])o1, (byte[])o2); + } + else + { + return o1.Equals(o2); + } + } + + /** + * Stringifies an IPv4 or v6 address with subnet mask. + * + * @param ip The IP with subnet mask. + * @return The stringified IP address. + */ + private String StringifyIP(byte[] ip) + { + String temp = ""; + for (int i = 0; i < ip.Length / 2; i++) + { + //temp += Integer.toString(ip[i] & 0x00FF) + "."; + temp += (ip[i] & 0x00FF) + "."; + } + temp = temp.Substring(0, temp.Length - 1); + temp += "/"; + for (int i = ip.Length / 2; i < ip.Length; i++) + { + //temp += Integer.toString(ip[i] & 0x00FF) + "."; + temp += (ip[i] & 0x00FF) + "."; + } + temp = temp.Substring(0, temp.Length - 1); + return temp; + } + + private String StringifyIPCollection(ISet ips) + { + String temp = ""; + temp += "["; + for (IEnumerator it = ips.GetEnumerator(); it.MoveNext(); ) + { + temp += StringifyIP((byte[])it.Current) + ","; + } + if (temp.Length > 1) + { + temp = temp.Substring(0, temp.Length - 1); + } + temp += "]"; + + return temp; + } + + public override String ToString() + { + String temp = ""; + + temp += "permitted:\n"; + if (permittedSubtreesDN != null) + { + temp += "DN:\n"; + temp += permittedSubtreesDN.ToString() + "\n"; + } + if (permittedSubtreesDNS != null) + { + temp += "DNS:\n"; + temp += permittedSubtreesDNS.ToString() + "\n"; + } + if (permittedSubtreesEmail != null) + { + temp += "Email:\n"; + temp += permittedSubtreesEmail.ToString() + "\n"; + } + if (permittedSubtreesURI != null) + { + temp += "URI:\n"; + temp += permittedSubtreesURI.ToString() + "\n"; + } + if (permittedSubtreesIP != null) + { + temp += "IP:\n"; + temp += StringifyIPCollection(permittedSubtreesIP) + "\n"; + } + temp += "excluded:\n"; + if (!(excludedSubtreesDN.IsEmpty)) + { + temp += "DN:\n"; + temp += excludedSubtreesDN.ToString() + "\n"; + } + if (!excludedSubtreesDNS.IsEmpty) + { + temp += "DNS:\n"; + temp += excludedSubtreesDNS.ToString() + "\n"; + } + if (!excludedSubtreesEmail.IsEmpty) + { + temp += "Email:\n"; + temp += excludedSubtreesEmail.ToString() + "\n"; + } + if (!excludedSubtreesURI.IsEmpty) + { + temp += "URI:\n"; + temp += excludedSubtreesURI.ToString() + "\n"; + } + if (!excludedSubtreesIP.IsEmpty) + { + temp += "IP:\n"; + temp += StringifyIPCollection(excludedSubtreesIP) + "\n"; + } + return temp; + } + + } +} diff --git a/crypto/src/pkix/PkixNameConstraintValidatorException.cs b/crypto/src/pkix/PkixNameConstraintValidatorException.cs index 9f01e8765..432d7bd6b 100644 --- a/crypto/src/pkix/PkixNameConstraintValidatorException.cs +++ b/crypto/src/pkix/PkixNameConstraintValidatorException.cs @@ -2,7 +2,11 @@ using System; namespace Org.BouncyCastle.Pkix { - public class PkixNameConstraintValidatorException : Exception +#if !(NETCF_1_0 || NETCF_2_0 || SILVERLIGHT) + [Serializable] +#endif + public class PkixNameConstraintValidatorException + : Exception { public PkixNameConstraintValidatorException(String msg) : base(msg) diff --git a/crypto/src/pkix/PkixParameters.cs b/crypto/src/pkix/PkixParameters.cs new file mode 100644 index 000000000..6df1b646f --- /dev/null +++ b/crypto/src/pkix/PkixParameters.cs @@ -0,0 +1,893 @@ +using System; +using System.Collections; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; +using Org.BouncyCastle.Utilities.Date; +using Org.BouncyCastle.X509.Store; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// Summary description for PkixParameters. + /// </summary> + public class PkixParameters +// : ICertPathParameters + { + /** + * This is the default PKIX validity model. Actually there are two variants + * of this: The PKIX model and the modified PKIX model. The PKIX model + * verifies that all involved certificates must have been valid at the + * current time. The modified PKIX model verifies that all involved + * certificates were valid at the signing time. Both are indirectly choosen + * with the {@link PKIXParameters#setDate(java.util.Date)} method, so this + * methods sets the Date when <em>all</em> certificates must have been + * valid. + */ + public const int PkixValidityModel = 0; + + /** + * This model uses the following validity model. Each certificate must have + * been valid at the moment where is was used. That means the end + * certificate must have been valid at the time the signature was done. The + * CA certificate which signed the end certificate must have been valid, + * when the end certificate was signed. The CA (or Root CA) certificate must + * have been valid, when the CA certificate was signed and so on. So the + * {@link PKIXParameters#setDate(java.util.Date)} method sets the time, when + * the <em>end certificate</em> must have been valid. <p/> It is used e.g. + * in the German signature law. + */ + public const int ChainValidityModel = 1; + + private ISet trustAnchors; + private DateTimeObject date; + private IList certPathCheckers; + private bool revocationEnabled = true; + private ISet initialPolicies; + //private bool checkOnlyEECertificateCrl = false; + private bool explicitPolicyRequired = false; + private bool anyPolicyInhibited = false; + private bool policyMappingInhibited = false; + private bool policyQualifiersRejected = true; + private IX509Selector certSelector; + private IList stores; + private IX509Selector selector; + private bool additionalLocationsEnabled; + private IList additionalStores; + private ISet trustedACIssuers; + private ISet necessaryACAttributes; + private ISet prohibitedACAttributes; + private ISet attrCertCheckers; + private int validityModel = PkixValidityModel; + private bool useDeltas = false; + + /** + * Creates an instance of PKIXParameters with the specified Set of + * most-trusted CAs. Each element of the set is a TrustAnchor.<br /> + * <br /> + * Note that the Set is copied to protect against subsequent modifications. + * + * @param trustAnchors + * a Set of TrustAnchors + * + * @exception InvalidAlgorithmParameterException + * if the specified Set is empty + * <code>(trustAnchors.isEmpty() == true)</code> + * @exception NullPointerException + * if the specified Set is <code>null</code> + * @exception ClassCastException + * if any of the elements in the Set are not of type + * <code>java.security.cert.TrustAnchor</code> + */ + public PkixParameters( + ISet trustAnchors) + { + SetTrustAnchors(trustAnchors); + + this.initialPolicies = new HashSet(); + this.certPathCheckers = Platform.CreateArrayList(); + this.stores = Platform.CreateArrayList(); + this.additionalStores = Platform.CreateArrayList(); + this.trustedACIssuers = new HashSet(); + this.necessaryACAttributes = new HashSet(); + this.prohibitedACAttributes = new HashSet(); + this.attrCertCheckers = new HashSet(); + } + +// // TODO implement for other keystores (see Java build)? +// /** +// * Creates an instance of <code>PKIXParameters</code> that +// * populates the set of most-trusted CAs from the trusted +// * certificate entries contained in the specified <code>KeyStore</code>. +// * Only keystore entries that contain trusted <code>X509Certificates</code> +// * are considered; all other certificate types are ignored. +// * +// * @param keystore a <code>KeyStore</code> from which the set of +// * most-trusted CAs will be populated +// * @throws KeyStoreException if the keystore has not been initialized +// * @throws InvalidAlgorithmParameterException if the keystore does +// * not contain at least one trusted certificate entry +// * @throws NullPointerException if the keystore is <code>null</code> +// */ +// public PkixParameters( +// Pkcs12Store keystore) +//// throws KeyStoreException, InvalidAlgorithmParameterException +// { +// if (keystore == null) +// throw new ArgumentNullException("keystore"); +// ISet trustAnchors = new HashSet(); +// foreach (string alias in keystore.Aliases) +// { +// if (keystore.IsCertificateEntry(alias)) +// { +// X509CertificateEntry x509Entry = keystore.GetCertificate(alias); +// trustAnchors.Add(new TrustAnchor(x509Entry.Certificate, null)); +// } +// } +// SetTrustAnchors(trustAnchors); +// +// this.initialPolicies = new HashSet(); +// this.certPathCheckers = new ArrayList(); +// this.stores = new ArrayList(); +// this.additionalStores = new ArrayList(); +// this.trustedACIssuers = new HashSet(); +// this.necessaryACAttributes = new HashSet(); +// this.prohibitedACAttributes = new HashSet(); +// this.attrCertCheckers = new HashSet(); +// } + + public virtual bool IsRevocationEnabled + { + get { return revocationEnabled; } + set { revocationEnabled = value; } + } + + public virtual bool IsExplicitPolicyRequired + { + get { return explicitPolicyRequired; } + set { this.explicitPolicyRequired = value; } + } + + public virtual bool IsAnyPolicyInhibited + { + get { return anyPolicyInhibited; } + set { this.anyPolicyInhibited = value; } + } + + public virtual bool IsPolicyMappingInhibited + { + get { return policyMappingInhibited; } + set { this.policyMappingInhibited = value; } + } + + public virtual bool IsPolicyQualifiersRejected + { + get { return policyQualifiersRejected; } + set { this.policyQualifiersRejected = value; } + } + + //public bool IsCheckOnlyEECertificateCrl + //{ + // get { return this.checkOnlyEECertificateCrl; } + // set { this.checkOnlyEECertificateCrl = value; } + //} + + public virtual DateTimeObject Date + { + get { return this.date; } + set { this.date = value; } + } + + // Returns a Set of the most-trusted CAs. + public virtual ISet GetTrustAnchors() + { + return new HashSet(this.trustAnchors); + } + + // Sets the set of most-trusted CAs. + // Set is copied to protect against subsequent modifications. + public virtual void SetTrustAnchors( + ISet tas) + { + if (tas == null) + throw new ArgumentNullException("value"); + if (tas.IsEmpty) + throw new ArgumentException("non-empty set required", "value"); + + // Explicit copy to enforce type-safety + this.trustAnchors = new HashSet(); + foreach (TrustAnchor ta in tas) + { + if (ta != null) + { + trustAnchors.Add(ta); + } + } + } + + /** + * Returns the required constraints on the target certificate. The + * constraints are returned as an instance of CertSelector. If + * <code>null</code>, no constraints are defined.<br /> + * <br /> + * Note that the CertSelector returned is cloned to protect against + * subsequent modifications. + * + * @return a CertSelector specifying the constraints on the target + * certificate (or <code>null</code>) + * + * @see #setTargetCertConstraints(CertSelector) + */ + public virtual X509CertStoreSelector GetTargetCertConstraints() + { + if (certSelector == null) + { + return null; + } + + return (X509CertStoreSelector)certSelector.Clone(); + } + + /** + * Sets the required constraints on the target certificate. The constraints + * are specified as an instance of CertSelector. If null, no constraints are + * defined.<br /> + * <br /> + * Note that the CertSelector specified is cloned to protect against + * subsequent modifications. + * + * @param selector + * a CertSelector specifying the constraints on the target + * certificate (or <code>null</code>) + * + * @see #getTargetCertConstraints() + */ + public virtual void SetTargetCertConstraints( + IX509Selector selector) + { + if (selector == null) + { + certSelector = null; + } + else + { + certSelector = (IX509Selector)selector.Clone(); + } + } + + /** + * Returns an immutable Set of initial policy identifiers (OID strings), + * indicating that any one of these policies would be acceptable to the + * certificate user for the purposes of certification path processing. The + * default return value is an empty <code>Set</code>, which is + * interpreted as meaning that any policy would be acceptable. + * + * @return an immutable <code>Set</code> of initial policy OIDs in String + * format, or an empty <code>Set</code> (implying any policy is + * acceptable). Never returns <code>null</code>. + * + * @see #setInitialPolicies(java.util.Set) + */ + public virtual ISet GetInitialPolicies() + { + ISet returnSet = initialPolicies; + + // TODO Can it really be null? + if (initialPolicies == null) + { + returnSet = new HashSet(); + } + + return new HashSet(returnSet); + } + + /** + * Sets the <code>Set</code> of initial policy identifiers (OID strings), + * indicating that any one of these policies would be acceptable to the + * certificate user for the purposes of certification path processing. By + * default, any policy is acceptable (i.e. all policies), so a user that + * wants to allow any policy as acceptable does not need to call this + * method, or can call it with an empty <code>Set</code> (or + * <code>null</code>).<br /> + * <br /> + * Note that the Set is copied to protect against subsequent modifications.<br /> + * <br /> + * + * @param initialPolicies + * a Set of initial policy OIDs in String format (or + * <code>null</code>) + * + * @exception ClassCastException + * if any of the elements in the set are not of type String + * + * @see #getInitialPolicies() + */ + public virtual void SetInitialPolicies( + ISet initialPolicies) + { + this.initialPolicies = new HashSet(); + if (initialPolicies != null) + { + foreach (string obj in initialPolicies) + { + if (obj != null) + { + this.initialPolicies.Add(obj); + } + } + } + } + + /** + * Sets a <code>List</code> of additional certification path checkers. If + * the specified List contains an object that is not a PKIXCertPathChecker, + * it is ignored.<br /> + * <br /> + * Each <code>PKIXCertPathChecker</code> specified implements additional + * checks on a certificate. Typically, these are checks to process and + * verify private extensions contained in certificates. Each + * <code>PKIXCertPathChecker</code> should be instantiated with any + * initialization parameters needed to execute the check.<br /> + * <br /> + * This method allows sophisticated applications to extend a PKIX + * <code>CertPathValidator</code> or <code>CertPathBuilder</code>. Each + * of the specified PKIXCertPathCheckers will be called, in turn, by a PKIX + * <code>CertPathValidator</code> or <code>CertPathBuilder</code> for + * each certificate processed or validated.<br /> + * <br /> + * Regardless of whether these additional PKIXCertPathCheckers are set, a + * PKIX <code>CertPathValidator</code> or <code>CertPathBuilder</code> + * must perform all of the required PKIX checks on each certificate. The one + * exception to this rule is if the RevocationEnabled flag is set to false + * (see the {@link #setRevocationEnabled(boolean) setRevocationEnabled} + * method).<br /> + * <br /> + * Note that the List supplied here is copied and each PKIXCertPathChecker + * in the list is cloned to protect against subsequent modifications. + * + * @param checkers + * a List of PKIXCertPathCheckers. May be null, in which case no + * additional checkers will be used. + * @exception ClassCastException + * if any of the elements in the list are not of type + * <code>java.security.cert.PKIXCertPathChecker</code> + * @see #getCertPathCheckers() + */ + public virtual void SetCertPathCheckers(IList checkers) + { + certPathCheckers = Platform.CreateArrayList(); + if (checkers != null) + { + foreach (PkixCertPathChecker obj in checkers) + { + certPathCheckers.Add(obj.Clone()); + } + } + } + + /** + * Returns the List of certification path checkers. Each PKIXCertPathChecker + * in the returned IList is cloned to protect against subsequent modifications. + * + * @return an immutable List of PKIXCertPathCheckers (may be empty, but not + * <code>null</code>) + * + * @see #setCertPathCheckers(java.util.List) + */ + public virtual IList GetCertPathCheckers() + { + IList checkers = Platform.CreateArrayList(); + foreach (PkixCertPathChecker obj in certPathCheckers) + { + checkers.Add(obj.Clone()); + } + return checkers; + } + + /** + * Adds a <code>PKIXCertPathChecker</code> to the list of certification + * path checkers. See the {@link #setCertPathCheckers setCertPathCheckers} + * method for more details. + * <p> + * Note that the <code>PKIXCertPathChecker</code> is cloned to protect + * against subsequent modifications.</p> + * + * @param checker a <code>PKIXCertPathChecker</code> to add to the list of + * checks. If <code>null</code>, the checker is ignored (not added to list). + */ + public virtual void AddCertPathChecker( + PkixCertPathChecker checker) + { + if (checker != null) + { + certPathCheckers.Add(checker.Clone()); + } + } + + public virtual object Clone() + { + // FIXME Check this whole method against the Java implementation! + + PkixParameters parameters = new PkixParameters(GetTrustAnchors()); + parameters.SetParams(this); + return parameters; + + +// PkixParameters obj = new PkixParameters(new HashSet()); +//// (PkixParameters) this.MemberwiseClone(); +// obj.x509Stores = new ArrayList(x509Stores); +// obj.certPathCheckers = new ArrayList(certPathCheckers); +// +// //Iterator iter = certPathCheckers.iterator(); +// //obj.certPathCheckers = new ArrayList(); +// //while (iter.hasNext()) +// //{ +// // obj.certPathCheckers.add(((PKIXCertPathChecker)iter.next()) +// // .clone()); +// //} +// //if (initialPolicies != null) +// //{ +// // obj.initialPolicies = new HashSet(initialPolicies); +// //} +//// if (trustAnchors != null) +//// { +//// obj.trustAnchors = new HashSet(trustAnchors); +//// } +//// if (certSelector != null) +//// { +//// obj.certSelector = (X509CertStoreSelector) certSelector.Clone(); +//// } +// return obj; + } + + /** + * Method to support <code>Clone()</code> under J2ME. + * <code>super.Clone()</code> does not exist and fields are not copied. + * + * @param params Parameters to set. If this are + * <code>ExtendedPkixParameters</code> they are copied to. + */ + protected virtual void SetParams( + PkixParameters parameters) + { + Date = parameters.Date; + SetCertPathCheckers(parameters.GetCertPathCheckers()); + IsAnyPolicyInhibited = parameters.IsAnyPolicyInhibited; + IsExplicitPolicyRequired = parameters.IsExplicitPolicyRequired; + IsPolicyMappingInhibited = parameters.IsPolicyMappingInhibited; + IsRevocationEnabled = parameters.IsRevocationEnabled; + SetInitialPolicies(parameters.GetInitialPolicies()); + IsPolicyQualifiersRejected = parameters.IsPolicyQualifiersRejected; + SetTargetCertConstraints(parameters.GetTargetCertConstraints()); + SetTrustAnchors(parameters.GetTrustAnchors()); + + validityModel = parameters.validityModel; + useDeltas = parameters.useDeltas; + additionalLocationsEnabled = parameters.additionalLocationsEnabled; + selector = parameters.selector == null ? null + : (IX509Selector) parameters.selector.Clone(); + stores = Platform.CreateArrayList(parameters.stores); + additionalStores = Platform.CreateArrayList(parameters.additionalStores); + trustedACIssuers = new HashSet(parameters.trustedACIssuers); + prohibitedACAttributes = new HashSet(parameters.prohibitedACAttributes); + necessaryACAttributes = new HashSet(parameters.necessaryACAttributes); + attrCertCheckers = new HashSet(parameters.attrCertCheckers); + } + + /** + * Whether delta CRLs should be used for checking the revocation status. + * Defaults to <code>false</code>. + */ + public virtual bool IsUseDeltasEnabled + { + get { return useDeltas; } + set { useDeltas = value; } + } + + /** + * The validity model. + * @see #CHAIN_VALIDITY_MODEL + * @see #PKIX_VALIDITY_MODEL + */ + public virtual int ValidityModel + { + get { return validityModel; } + set { validityModel = value; } + } + + /** + * Sets the Bouncy Castle Stores for finding CRLs, certificates, attribute + * certificates or cross certificates. + * <p> + * The <code>IList</code> is cloned. + * </p> + * + * @param stores A list of stores to use. + * @see #getStores + * @throws ClassCastException if an element of <code>stores</code> is not + * a {@link Store}. + */ + public virtual void SetStores( + IList stores) + { + if (stores == null) + { + this.stores = Platform.CreateArrayList(); + } + else + { + foreach (object obj in stores) + { + if (!(obj is IX509Store)) + { + throw new InvalidCastException( + "All elements of list must be of type " + typeof(IX509Store).FullName); + } + } + this.stores = Platform.CreateArrayList(stores); + } + } + + /** + * Adds a Bouncy Castle {@link Store} to find CRLs, certificates, attribute + * certificates or cross certificates. + * <p> + * This method should be used to add local stores, like collection based + * X.509 stores, if available. Local stores should be considered first, + * before trying to use additional (remote) locations, because they do not + * need possible additional network traffic. + * </p><p> + * If <code>store</code> is <code>null</code> it is ignored. + * </p> + * + * @param store The store to add. + * @see #getStores + */ + public virtual void AddStore( + IX509Store store) + { + if (store != null) + { + stores.Add(store); + } + } + + /** + * Adds an additional Bouncy Castle {@link Store} to find CRLs, certificates, + * attribute certificates or cross certificates. + * <p> + * You should not use this method. This method is used for adding additional + * X.509 stores, which are used to add (remote) locations, e.g. LDAP, found + * during X.509 object processing, e.g. in certificates or CRLs. This method + * is used in PKIX certification path processing. + * </p><p> + * If <code>store</code> is <code>null</code> it is ignored. + * </p> + * + * @param store The store to add. + * @see #getStores() + */ + public virtual void AddAdditionalStore( + IX509Store store) + { + if (store != null) + { + additionalStores.Add(store); + } + } + + /** + * Returns an <code>IList</code> of additional Bouncy Castle + * <code>Store</code>s used for finding CRLs, certificates, attribute + * certificates or cross certificates. + * + * @return an immutable <code>IList</code> of additional Bouncy Castle + * <code>Store</code>s. Never <code>null</code>. + * + * @see #addAddionalStore(Store) + */ + public virtual IList GetAdditionalStores() + { + return Platform.CreateArrayList(additionalStores); + } + + /** + * Returns an <code>IList</code> of Bouncy Castle + * <code>Store</code>s used for finding CRLs, certificates, attribute + * certificates or cross certificates. + * + * @return an immutable <code>IList</code> of Bouncy Castle + * <code>Store</code>s. Never <code>null</code>. + * + * @see #setStores(IList) + */ + public virtual IList GetStores() + { + return Platform.CreateArrayList(stores); + } + + /** + * Returns if additional {@link X509Store}s for locations like LDAP found + * in certificates or CRLs should be used. + * + * @return Returns <code>true</code> if additional stores are used. + */ + public virtual bool IsAdditionalLocationsEnabled + { + get { return additionalLocationsEnabled; } + } + + /** + * Sets if additional {@link X509Store}s for locations like LDAP found in + * certificates or CRLs should be used. + * + * @param enabled <code>true</code> if additional stores are used. + */ + public virtual void SetAdditionalLocationsEnabled( + bool enabled) + { + additionalLocationsEnabled = enabled; + } + + /** + * Returns the required constraints on the target certificate or attribute + * certificate. The constraints are returned as an instance of + * <code>IX509Selector</code>. If <code>null</code>, no constraints are + * defined. + * + * <p> + * The target certificate in a PKIX path may be a certificate or an + * attribute certificate. + * </p><p> + * Note that the <code>IX509Selector</code> returned is cloned to protect + * against subsequent modifications. + * </p> + * @return a <code>IX509Selector</code> specifying the constraints on the + * target certificate or attribute certificate (or <code>null</code>) + * @see #setTargetConstraints + * @see X509CertStoreSelector + * @see X509AttributeCertStoreSelector + */ + public virtual IX509Selector GetTargetConstraints() + { + if (selector != null) + { + return (IX509Selector) selector.Clone(); + } + else + { + return null; + } + } + + /** + * Sets the required constraints on the target certificate or attribute + * certificate. The constraints are specified as an instance of + * <code>IX509Selector</code>. If <code>null</code>, no constraints are + * defined. + * <p> + * The target certificate in a PKIX path may be a certificate or an + * attribute certificate. + * </p><p> + * Note that the <code>IX509Selector</code> specified is cloned to protect + * against subsequent modifications. + * </p> + * + * @param selector a <code>IX509Selector</code> specifying the constraints on + * the target certificate or attribute certificate (or + * <code>null</code>) + * @see #getTargetConstraints + * @see X509CertStoreSelector + * @see X509AttributeCertStoreSelector + */ + public virtual void SetTargetConstraints(IX509Selector selector) + { + if (selector != null) + { + this.selector = (IX509Selector) selector.Clone(); + } + else + { + this.selector = null; + } + } + + /** + * Returns the trusted attribute certificate issuers. If attribute + * certificates is verified the trusted AC issuers must be set. + * <p> + * The returned <code>ISet</code> consists of <code>TrustAnchor</code>s. + * </p><p> + * The returned <code>ISet</code> is immutable. Never <code>null</code> + * </p> + * + * @return Returns an immutable set of the trusted AC issuers. + */ + public virtual ISet GetTrustedACIssuers() + { + return new HashSet(trustedACIssuers); + } + + /** + * Sets the trusted attribute certificate issuers. If attribute certificates + * is verified the trusted AC issuers must be set. + * <p> + * The <code>trustedACIssuers</code> must be a <code>ISet</code> of + * <code>TrustAnchor</code> + * </p><p> + * The given set is cloned. + * </p> + * + * @param trustedACIssuers The trusted AC issuers to set. Is never + * <code>null</code>. + * @throws ClassCastException if an element of <code>stores</code> is not + * a <code>TrustAnchor</code>. + */ + public virtual void SetTrustedACIssuers( + ISet trustedACIssuers) + { + if (trustedACIssuers == null) + { + this.trustedACIssuers = new HashSet(); + } + else + { + foreach (object obj in trustedACIssuers) + { + if (!(obj is TrustAnchor)) + { + throw new InvalidCastException("All elements of set must be " + + "of type " + typeof(TrustAnchor).Name + "."); + } + } + this.trustedACIssuers = new HashSet(trustedACIssuers); + } + } + + /** + * Returns the neccessary attributes which must be contained in an attribute + * certificate. + * <p> + * The returned <code>ISet</code> is immutable and contains + * <code>String</code>s with the OIDs. + * </p> + * + * @return Returns the necessary AC attributes. + */ + public virtual ISet GetNecessaryACAttributes() + { + return new HashSet(necessaryACAttributes); + } + + /** + * Sets the neccessary which must be contained in an attribute certificate. + * <p> + * The <code>ISet</code> must contain <code>String</code>s with the + * OIDs. + * </p><p> + * The set is cloned. + * </p> + * + * @param necessaryACAttributes The necessary AC attributes to set. + * @throws ClassCastException if an element of + * <code>necessaryACAttributes</code> is not a + * <code>String</code>. + */ + public virtual void SetNecessaryACAttributes( + ISet necessaryACAttributes) + { + if (necessaryACAttributes == null) + { + this.necessaryACAttributes = new HashSet(); + } + else + { + foreach (object obj in necessaryACAttributes) + { + if (!(obj is string)) + { + throw new InvalidCastException("All elements of set must be " + + "of type string."); + } + } + this.necessaryACAttributes = new HashSet(necessaryACAttributes); + } + } + + /** + * Returns the attribute certificates which are not allowed. + * <p> + * The returned <code>ISet</code> is immutable and contains + * <code>String</code>s with the OIDs. + * </p> + * + * @return Returns the prohibited AC attributes. Is never <code>null</code>. + */ + public virtual ISet GetProhibitedACAttributes() + { + return new HashSet(prohibitedACAttributes); + } + + /** + * Sets the attribute certificates which are not allowed. + * <p> + * The <code>ISet</code> must contain <code>String</code>s with the + * OIDs. + * </p><p> + * The set is cloned. + * </p> + * + * @param prohibitedACAttributes The prohibited AC attributes to set. + * @throws ClassCastException if an element of + * <code>prohibitedACAttributes</code> is not a + * <code>String</code>. + */ + public virtual void SetProhibitedACAttributes( + ISet prohibitedACAttributes) + { + if (prohibitedACAttributes == null) + { + this.prohibitedACAttributes = new HashSet(); + } + else + { + foreach (object obj in prohibitedACAttributes) + { + if (!(obj is String)) + { + throw new InvalidCastException("All elements of set must be " + + "of type string."); + } + } + this.prohibitedACAttributes = new HashSet(prohibitedACAttributes); + } + } + + /** + * Returns the attribute certificate checker. The returned set contains + * {@link PKIXAttrCertChecker}s and is immutable. + * + * @return Returns the attribute certificate checker. Is never + * <code>null</code>. + */ + public virtual ISet GetAttrCertCheckers() + { + return new HashSet(attrCertCheckers); + } + + /** + * Sets the attribute certificate checkers. + * <p> + * All elements in the <code>ISet</code> must a {@link PKIXAttrCertChecker}. + * </p> + * <p> + * The given set is cloned. + * </p> + * + * @param attrCertCheckers The attribute certificate checkers to set. Is + * never <code>null</code>. + * @throws ClassCastException if an element of <code>attrCertCheckers</code> + * is not a <code>PKIXAttrCertChecker</code>. + */ + public virtual void SetAttrCertCheckers( + ISet attrCertCheckers) + { + if (attrCertCheckers == null) + { + this.attrCertCheckers = new HashSet(); + } + else + { + foreach (object obj in attrCertCheckers) + { + if (!(obj is PkixAttrCertChecker)) + { + throw new InvalidCastException("All elements of set must be " + + "of type " + typeof(PkixAttrCertChecker).FullName + "."); + } + } + this.attrCertCheckers = new HashSet(attrCertCheckers); + } + } + } +} diff --git a/crypto/src/pkix/PkixPolicyNode.cs b/crypto/src/pkix/PkixPolicyNode.cs new file mode 100644 index 000000000..fc5b82f6f --- /dev/null +++ b/crypto/src/pkix/PkixPolicyNode.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Text; + +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Collections; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// Summary description for PkixPolicyNode. + /// </summary> + public class PkixPolicyNode +// : IPolicyNode + { + protected IList mChildren; + protected int mDepth; + protected ISet mExpectedPolicies; + protected PkixPolicyNode mParent; + protected ISet mPolicyQualifiers; + protected string mValidPolicy; + protected bool mCritical; + + public virtual int Depth + { + get { return this.mDepth; } + } + + public virtual IEnumerable Children + { + get { return new EnumerableProxy(mChildren); } + } + + public virtual bool IsCritical + { + get { return this.mCritical; } + set { this.mCritical = value; } + } + + public virtual ISet PolicyQualifiers + { + get { return new HashSet(this.mPolicyQualifiers); } + } + + public virtual string ValidPolicy + { + get { return this.mValidPolicy; } + } + + public virtual bool HasChildren + { + get { return mChildren.Count != 0; } + } + + public virtual ISet ExpectedPolicies + { + get { return new HashSet(this.mExpectedPolicies); } + set { this.mExpectedPolicies = new HashSet(value); } + } + + public virtual PkixPolicyNode Parent + { + get { return this.mParent; } + set { this.mParent = value; } + } + + /// Constructors + public PkixPolicyNode( + IList children, + int depth, + ISet expectedPolicies, + PkixPolicyNode parent, + ISet policyQualifiers, + string validPolicy, + bool critical) + { + if (children == null) + { + this.mChildren = Platform.CreateArrayList(); + } + else + { + this.mChildren = Platform.CreateArrayList(children); + } + + this.mDepth = depth; + this.mExpectedPolicies = expectedPolicies; + this.mParent = parent; + this.mPolicyQualifiers = policyQualifiers; + this.mValidPolicy = validPolicy; + this.mCritical = critical; + } + + public virtual void AddChild( + PkixPolicyNode child) + { + child.Parent = this; + mChildren.Add(child); + } + + public virtual void RemoveChild( + PkixPolicyNode child) + { + mChildren.Remove(child); + } + + public override string ToString() + { + return ToString(""); + } + + public virtual string ToString( + string indent) + { + StringBuilder buf = new StringBuilder(); + buf.Append(indent); + buf.Append(mValidPolicy); + buf.Append(" {"); + buf.Append(Platform.NewLine); + + foreach (PkixPolicyNode child in mChildren) + { + buf.Append(child.ToString(indent + " ")); + } + + buf.Append(indent); + buf.Append("}"); + buf.Append(Platform.NewLine); + return buf.ToString(); + } + + public virtual object Clone() + { + return Copy(); + } + + public virtual PkixPolicyNode Copy() + { + PkixPolicyNode node = new PkixPolicyNode( + Platform.CreateArrayList(), + mDepth, + new HashSet(mExpectedPolicies), + null, + new HashSet(mPolicyQualifiers), + mValidPolicy, + mCritical); + + foreach (PkixPolicyNode child in mChildren) + { + PkixPolicyNode copy = child.Copy(); + copy.Parent = node; + node.AddChild(copy); + } + + return node; + } + } +} diff --git a/crypto/src/pkix/ReasonsMask.cs b/crypto/src/pkix/ReasonsMask.cs new file mode 100644 index 000000000..e389bfe11 --- /dev/null +++ b/crypto/src/pkix/ReasonsMask.cs @@ -0,0 +1,96 @@ +using System; + +using Org.BouncyCastle.Asn1.X509; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// This class helps to handle CRL revocation reasons mask. Each CRL handles a + /// certain set of revocation reasons. + /// </summary> + internal class ReasonsMask + { + private int _reasons; + + /// <summary> + /// Constructs are reason mask with the reasons. + /// </summary> + /// <param name="reasons">The reasons.</param> + internal ReasonsMask( + int reasons) + { + _reasons = reasons; + } + + /// <summary> + /// A reason mask with no reason. + /// </summary> + internal ReasonsMask() + : this(0) + { + } + + /// <summary> + /// A mask with all revocation reasons. + /// </summary> + internal static readonly ReasonsMask AllReasons = new ReasonsMask( + ReasonFlags.AACompromise | ReasonFlags.AffiliationChanged | ReasonFlags.CACompromise + | ReasonFlags.CertificateHold | ReasonFlags.CessationOfOperation + | ReasonFlags.KeyCompromise | ReasonFlags.PrivilegeWithdrawn | ReasonFlags.Unused + | ReasonFlags.Superseded); + + /** + * Adds all reasons from the reasons mask to this mask. + * + * @param mask The reasons mask to add. + */ + internal void AddReasons( + ReasonsMask mask) + { + _reasons = _reasons | mask.Reasons.IntValue; + } + + /// <summary> + /// Returns <code>true</code> if this reasons mask contains all possible + /// reasons. + /// </summary> + /// <returns>true if this reasons mask contains all possible reasons. + /// </returns> + internal bool IsAllReasons + { + get { return _reasons == AllReasons._reasons; } + } + + /// <summary> + /// Intersects this mask with the given reasons mask. + /// </summary> + /// <param name="mask">mask The mask to intersect with.</param> + /// <returns>The intersection of this and teh given mask.</returns> + internal ReasonsMask Intersect( + ReasonsMask mask) + { + ReasonsMask _mask = new ReasonsMask(); + _mask.AddReasons(new ReasonsMask(_reasons & mask.Reasons.IntValue)); + return _mask; + } + + /// <summary> + /// Returns <c>true</c> if the passed reasons mask has new reasons. + /// </summary> + /// <param name="mask">The reasons mask which should be tested for new reasons.</param> + /// <returns><c>true</c> if the passed reasons mask has new reasons.</returns> + internal bool HasNewReasons( + ReasonsMask mask) + { + return ((_reasons | mask.Reasons.IntValue ^ _reasons) != 0); + } + + /// <summary> + /// Returns the reasons in this mask. + /// </summary> + public ReasonFlags Reasons + { + get { return new ReasonFlags(_reasons); } + } + } +} diff --git a/crypto/src/pkix/Rfc3280CertPathUtilities.cs b/crypto/src/pkix/Rfc3280CertPathUtilities.cs index bae657d90..c6f3fbff9 100644 --- a/crypto/src/pkix/Rfc3280CertPathUtilities.cs +++ b/crypto/src/pkix/Rfc3280CertPathUtilities.cs @@ -1197,10 +1197,10 @@ namespace Org.BouncyCastle.Pkix } if (certStatus.Status != CertStatus.Unrevoked) { - // TODO This format is forced by the NistCertPath tests - string formattedDate = certStatus.RevocationDate.Value.ToString( - "G", new CultureInfo("en-us")); - string message = "Certificate revocation after " + formattedDate; + // This format is enforced by the NistCertPath tests + string formattedDate = certStatus.RevocationDate.Value.ToString( + "ddd MMM dd HH:mm:ss K yyyy"); + string message = "Certificate revocation after " + formattedDate; message += ", reason: " + CrlReasons[certStatus.Status]; throw new Exception(message); } diff --git a/crypto/src/pkix/Rfc3281CertPathUtilities.cs b/crypto/src/pkix/Rfc3281CertPathUtilities.cs index bda2aa737..101ef5e11 100644 --- a/crypto/src/pkix/Rfc3281CertPathUtilities.cs +++ b/crypto/src/pkix/Rfc3281CertPathUtilities.cs @@ -195,10 +195,10 @@ namespace Org.BouncyCastle.Pkix } if (certStatus.Status != CertStatus.Unrevoked) { - // TODO This format is forced by the NistCertPath tests - string formattedDate = certStatus.RevocationDate.Value.ToString( - "G", new CultureInfo("en-us")); - string message = "Attribute certificate revocation after " + // This format is enforced by the NistCertPath tests + string formattedDate = certStatus.RevocationDate.Value.ToString( + "ddd MMM dd HH:mm:ss K yyyy"); + string message = "Attribute certificate revocation after " + formattedDate; message += ", reason: " + Rfc3280CertPathUtilities.CrlReasons[certStatus.Status]; diff --git a/crypto/src/pkix/TrustAnchor.cs b/crypto/src/pkix/TrustAnchor.cs new file mode 100644 index 000000000..22078baf2 --- /dev/null +++ b/crypto/src/pkix/TrustAnchor.cs @@ -0,0 +1,259 @@ +using System; +using System.IO; +using System.Text; + +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.X509; + +namespace Org.BouncyCastle.Pkix +{ + /// <summary> + /// A trust anchor or most-trusted Certification Authority (CA). + /// + /// This class represents a "most-trusted CA", which is used as a trust anchor + /// for validating X.509 certification paths. A most-trusted CA includes the + /// public key of the CA, the CA's name, and any constraints upon the set of + /// paths which may be validated using this key. These parameters can be + /// specified in the form of a trusted X509Certificate or as individual + /// parameters. + /// </summary> + public class TrustAnchor + { + private readonly AsymmetricKeyParameter pubKey; + private readonly string caName; + private readonly X509Name caPrincipal; + private readonly X509Certificate trustedCert; + private byte[] ncBytes; + private NameConstraints nc; + + /// <summary> + /// Creates an instance of TrustAnchor with the specified X509Certificate and + /// optional name constraints, which are intended to be used as additional + /// constraints when validating an X.509 certification path. + /// The name constraints are specified as a byte array. This byte array + /// should contain the DER encoded form of the name constraints, as they + /// would appear in the NameConstraints structure defined in RFC 2459 and + /// X.509. The ASN.1 definition of this structure appears below. + /// + /// <pre> + /// NameConstraints ::= SEQUENCE { + /// permittedSubtrees [0] GeneralSubtrees OPTIONAL, + /// excludedSubtrees [1] GeneralSubtrees OPTIONAL } + /// + /// GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree + /// + /// GeneralSubtree ::= SEQUENCE { + /// base GeneralName, + /// minimum [0] BaseDistance DEFAULT 0, + /// maximum [1] BaseDistance OPTIONAL } + /// + /// BaseDistance ::= INTEGER (0..MAX) + /// + /// GeneralName ::= CHOICE { + /// otherName [0] OtherName, + /// rfc822Name [1] IA5String, + /// dNSName [2] IA5String, + /// x400Address [3] ORAddress, + /// directoryName [4] Name, + /// ediPartyName [5] EDIPartyName, + /// uniformResourceIdentifier [6] IA5String, + /// iPAddress [7] OCTET STRING, + /// registeredID [8] OBJECT IDENTIFIER} + /// </pre> + /// + /// Note that the name constraints byte array supplied is cloned to protect + /// against subsequent modifications. + /// </summary> + /// <param name="trustedCert">a trusted X509Certificate</param> + /// <param name="nameConstraints">a byte array containing the ASN.1 DER encoding of a + /// NameConstraints extension to be used for checking name + /// constraints. Only the value of the extension is included, not + /// the OID or criticality flag. Specify null to omit the + /// parameter.</param> + /// <exception cref="ArgumentNullException">if the specified X509Certificate is null</exception> + public TrustAnchor( + X509Certificate trustedCert, + byte[] nameConstraints) + { + if (trustedCert == null) + throw new ArgumentNullException("trustedCert"); + + this.trustedCert = trustedCert; + this.pubKey = null; + this.caName = null; + this.caPrincipal = null; + setNameConstraints(nameConstraints); + } + + /// <summary> + /// Creates an instance of <c>TrustAnchor</c> where the + /// most-trusted CA is specified as an X500Principal and public key. + /// </summary> + /// <remarks> + /// <p> + /// Name constraints are an optional parameter, and are intended to be used + /// as additional constraints when validating an X.509 certification path. + /// </p><p> + /// The name constraints are specified as a byte array. This byte array + /// contains the DER encoded form of the name constraints, as they + /// would appear in the NameConstraints structure defined in RFC 2459 + /// and X.509. The ASN.1 notation for this structure is supplied in the + /// documentation for the other constructors. + /// </p><p> + /// Note that the name constraints byte array supplied here is cloned to + /// protect against subsequent modifications. + /// </p> + /// </remarks> + /// <param name="caPrincipal">the name of the most-trusted CA as X509Name</param> + /// <param name="pubKey">the public key of the most-trusted CA</param> + /// <param name="nameConstraints"> + /// a byte array containing the ASN.1 DER encoding of a NameConstraints extension to + /// be used for checking name constraints. Only the value of the extension is included, + /// not the OID or criticality flag. Specify <c>null</c> to omit the parameter. + /// </param> + /// <exception cref="ArgumentNullException"> + /// if <c>caPrincipal</c> or <c>pubKey</c> is null + /// </exception> + public TrustAnchor( + X509Name caPrincipal, + AsymmetricKeyParameter pubKey, + byte[] nameConstraints) + { + if (caPrincipal == null) + throw new ArgumentNullException("caPrincipal"); + if (pubKey == null) + throw new ArgumentNullException("pubKey"); + + this.trustedCert = null; + this.caPrincipal = caPrincipal; + this.caName = caPrincipal.ToString(); + this.pubKey = pubKey; + setNameConstraints(nameConstraints); + } + + /// <summary> + /// Creates an instance of <code>TrustAnchor</code> where the most-trusted + /// CA is specified as a distinguished name and public key. Name constraints + /// are an optional parameter, and are intended to be used as additional + /// constraints when validating an X.509 certification path. + /// <br/> + /// The name constraints are specified as a byte array. This byte array + /// contains the DER encoded form of the name constraints, as they would + /// appear in the NameConstraints structure defined in RFC 2459 and X.509. + /// </summary> + /// <param name="caName">the X.500 distinguished name of the most-trusted CA in RFC + /// 2253 string format</param> + /// <param name="pubKey">the public key of the most-trusted CA</param> + /// <param name="nameConstraints">a byte array containing the ASN.1 DER encoding of a + /// NameConstraints extension to be used for checking name + /// constraints. Only the value of the extension is included, not + /// the OID or criticality flag. Specify null to omit the + /// parameter.</param> + /// throws NullPointerException, IllegalArgumentException + public TrustAnchor( + string caName, + AsymmetricKeyParameter pubKey, + byte[] nameConstraints) + { + if (caName == null) + throw new ArgumentNullException("caName"); + if (pubKey == null) + throw new ArgumentNullException("pubKey"); + if (caName.Length == 0) + throw new ArgumentException("caName can not be an empty string"); + + this.caPrincipal = new X509Name(caName); + this.pubKey = pubKey; + this.caName = caName; + this.trustedCert = null; + setNameConstraints(nameConstraints); + } + + /// <summary> + /// Returns the most-trusted CA certificate. + /// </summary> + public X509Certificate TrustedCert + { + get { return this.trustedCert; } + } + + /// <summary> + /// Returns the name of the most-trusted CA as an X509Name. + /// </summary> + public X509Name CA + { + get { return this.caPrincipal; } + } + + /// <summary> + /// Returns the name of the most-trusted CA in RFC 2253 string format. + /// </summary> + public string CAName + { + get { return this.caName; } + } + + /// <summary> + /// Returns the public key of the most-trusted CA. + /// </summary> + public AsymmetricKeyParameter CAPublicKey + { + get { return this.pubKey; } + } + + /// <summary> + /// Decode the name constraints and clone them if not null. + /// </summary> + private void setNameConstraints( + byte[] bytes) + { + if (bytes == null) + { + ncBytes = null; + nc = null; + } + else + { + ncBytes = (byte[]) bytes.Clone(); + // validate DER encoding + //nc = new NameConstraintsExtension(Boolean.FALSE, bytes); + nc = NameConstraints.GetInstance(Asn1Object.FromByteArray(bytes)); + } + } + + public byte[] GetNameConstraints + { + get { return Arrays.Clone(ncBytes); } + } + + /// <summary> + /// Returns a formatted string describing the <code>TrustAnchor</code>. + /// </summary> + /// <returns>a formatted string describing the <code>TrustAnchor</code></returns> + public override string ToString() + { + // TODO Some of the sub-objects might not implement ToString() properly + string nl = Platform.NewLine; + StringBuilder sb = new StringBuilder(); + sb.Append("["); + sb.Append(nl); + if (this.pubKey != null) + { + sb.Append(" Trusted CA Public Key: ").Append(this.pubKey).Append(nl); + sb.Append(" Trusted CA Issuer Name: ").Append(this.caName).Append(nl); + } + else + { + sb.Append(" Trusted CA cert: ").Append(this.TrustedCert).Append(nl); + } + if (nc != null) + { + sb.Append(" Name Constraints: ").Append(nc).Append(nl); + } + return sb.ToString(); + } + } +} \ No newline at end of file |