|
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------
namespace System.IdentityModel.Selectors
{
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.IdentityModel.Configuration;
using System.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Xml;
// This is to allow easy rollback to CLR implementation by commenting out the below.
using X509Chain = System.IdentityModel.Selectors.X509CertificateChain;
public abstract class X509CertificateValidator : ICustomIdentityConfiguration
{
static X509CertificateValidator peerTrust;
static X509CertificateValidator chainTrust;
static X509CertificateValidator ntAuthChainTrust;
static X509CertificateValidator peerOrChainTrust;
static X509CertificateValidator none;
public static X509CertificateValidator None
{
get
{
if (none == null)
none = new NoneX509CertificateValidator();
return none;
}
}
public static X509CertificateValidator PeerTrust
{
get
{
if (peerTrust == null)
peerTrust = new PeerTrustValidator();
return peerTrust;
}
}
public static X509CertificateValidator ChainTrust
{
get
{
if (chainTrust == null)
chainTrust = new ChainTrustValidator();
return chainTrust;
}
}
internal static X509CertificateValidator NTAuthChainTrust
{
get
{
if (ntAuthChainTrust == null)
ntAuthChainTrust = new ChainTrustValidator(false, null, CAPI.CERT_CHAIN_POLICY_NT_AUTH);
return ntAuthChainTrust;
}
}
public static X509CertificateValidator PeerOrChainTrust
{
get
{
if (peerOrChainTrust == null)
peerOrChainTrust = new PeerOrChainTrustValidator();
return peerOrChainTrust;
}
}
public static X509CertificateValidator CreateChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
{
if (chainPolicy == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("chainPolicy");
return new ChainTrustValidator(useMachineContext, chainPolicy, X509CertificateChain.DefaultChainPolicyOID);
}
public static X509CertificateValidator CreatePeerOrChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
{
if (chainPolicy == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("chainPolicy");
return new PeerOrChainTrustValidator(useMachineContext, chainPolicy);
}
public abstract void Validate(X509Certificate2 certificate);
/// <summary>
/// Load custom configuration from Xml
/// </summary>
/// <param name="nodelist">Custom configuration elements</param>
public virtual void LoadCustomConfiguration(XmlNodeList nodelist)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new NotImplementedException(SR.GetString(SR.ID0023, this.GetType().AssemblyQualifiedName)));
}
class NoneX509CertificateValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
}
}
class PeerTrustValidator : X509CertificateValidator
{
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
Exception exception;
if (!TryValidate(certificate, out exception))
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(exception);
}
static bool StoreContainsCertificate(StoreName storeName, X509Certificate2 certificate)
{
X509CertificateStore store = new X509CertificateStore(storeName, StoreLocation.CurrentUser);
X509Certificate2Collection certificates = null;
try
{
store.Open(OpenFlags.ReadOnly);
certificates = store.Find(X509FindType.FindByThumbprint, certificate.GetCertHash(), false);
// store.Find(X509FindType.FindByThumbprint, certificate.GetCertHash(), false) gets a cert
// from a store only by comparing SHA-1 certificate hash.
// This is vulnerable to known SHA1 collision attacks where an attacker can produce different certificates
// with the same thumbprint and get a service to trust one of the certificates and later use another.
// As a precaution, we will check if the certificate collection contains the given certificate by comparing certificate's raw data byte-by-byte.
return SecurityUtils.CollectionContainsCertificate(certificates, certificate);
}
finally
{
SecurityUtils.ResetAllCertificates(certificates);
store.Close();
}
}
internal bool TryValidate(X509Certificate2 certificate, out Exception exception)
{
// Checklist
// 1) time validity of cert
// 2) in trusted people store
// 3) not in disallowed store
// The following code could be written as:
// DateTime now = DateTime.UtcNow;
// if (now > certificate.NotAfter.ToUniversalTime() || now < certificate.NotBefore.ToUniversalTime())
//
// this is because X509Certificate2.xxx doesn't return UT. However this would be a SMALL perf hit.
// I put a DebugAssert so that this will ensure that the we are compatible with the CLR we shipped with
DateTime now = DateTime.Now;
DiagnosticUtility.DebugAssert(now.Kind == certificate.NotAfter.Kind && now.Kind == certificate.NotBefore.Kind, "");
if (now > certificate.NotAfter || now < certificate.NotBefore)
{
exception = new SecurityTokenValidationException(SR.GetString(SR.X509InvalidUsageTime,
SecurityUtils.GetCertificateId(certificate), now, certificate.NotBefore, certificate.NotAfter));
return false;
}
if (!StoreContainsCertificate(StoreName.TrustedPeople, certificate))
{
exception = new SecurityTokenValidationException(SR.GetString(SR.X509IsNotInTrustedStore,
SecurityUtils.GetCertificateId(certificate)));
return false;
}
if (StoreContainsCertificate(StoreName.Disallowed, certificate))
{
exception = new SecurityTokenValidationException(SR.GetString(SR.X509IsInUntrustedStore,
SecurityUtils.GetCertificateId(certificate)));
return false;
}
exception = null;
return true;
}
}
class ChainTrustValidator : X509CertificateValidator
{
bool useMachineContext;
X509ChainPolicy chainPolicy;
uint chainPolicyOID = X509CertificateChain.DefaultChainPolicyOID;
public ChainTrustValidator()
{
this.chainPolicy = null;
}
public ChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy, uint chainPolicyOID)
{
this.useMachineContext = useMachineContext;
this.chainPolicy = chainPolicy;
this.chainPolicyOID = chainPolicyOID;
}
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
X509Chain chain = new X509Chain(this.useMachineContext, this.chainPolicyOID);
if (this.chainPolicy != null)
{
chain.ChainPolicy = this.chainPolicy;
}
if (!chain.Build(certificate))
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(SR.GetString(SR.X509ChainBuildFail,
SecurityUtils.GetCertificateId(certificate), GetChainStatusInformation(chain.ChainStatus))));
}
}
static string GetChainStatusInformation(X509ChainStatus[] chainStatus)
{
if (chainStatus != null)
{
StringBuilder error = new StringBuilder(128);
for (int i = 0; i < chainStatus.Length; ++i)
{
error.Append(chainStatus[i].StatusInformation);
error.Append(" ");
}
return error.ToString();
}
return String.Empty;
}
}
class PeerOrChainTrustValidator : X509CertificateValidator
{
X509CertificateValidator chain;
PeerTrustValidator peer;
public PeerOrChainTrustValidator()
{
this.chain = X509CertificateValidator.ChainTrust;
this.peer = (PeerTrustValidator)X509CertificateValidator.PeerTrust;
}
public PeerOrChainTrustValidator(bool useMachineContext, X509ChainPolicy chainPolicy)
{
this.chain = X509CertificateValidator.CreateChainTrustValidator(useMachineContext, chainPolicy);
this.peer = (PeerTrustValidator)X509CertificateValidator.PeerTrust;
}
public override void Validate(X509Certificate2 certificate)
{
if (certificate == null)
throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("certificate");
Exception exception;
if (this.peer.TryValidate(certificate, out exception))
return;
try
{
this.chain.Validate(certificate);
}
catch (SecurityTokenValidationException ex)
{
throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityTokenValidationException(exception.Message + " " + ex.Message));
}
}
}
}
}
|