|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO.Packaging;
using System.Security.Cryptography.X509Certificates;
using System.Windows.TrustUI;
using System.Windows.Xps.Packaging;
namespace MS.Internal.Documents
{
/// <summary>
/// DigitalSignatureProvider is used to connect DRP to Xps dig sig
/// </summary>
internal class DigitalSignatureProvider : IDigitalSignatureProvider
{
#region Constructors
//------------------------------------------------------
//
// Constructors
//
//------------------------------------------------------
/// <summary>
/// The constructor
/// </summary>
/// <param name="package">The package whose signatures this provider
/// will manipulate</param>
public DigitalSignatureProvider(Package package)
{
ArgumentNullException.ThrowIfNull(package);
XpsDocument = new XpsDocument(package);
FixedDocumentSequence = XpsDocument.FixedDocumentSequenceReader;
if (FixedDocumentSequence == null)
{
throw new ArgumentException(SR.DigitalSignatureNoFixedDocumentSequence);
}
// We only want to save the first fixed document since all
// XPS Viewer signature definitions will be added to the first
// fixed document.
FixedDocument =
FixedDocumentSequence.FixedDocuments[0];
}
#endregion Constructors
#region IDigitalSignatureProvider
//------------------------------------------------------
//
// IDigitalSignatureProvider
//
//------------------------------------------------------
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
bool IDigitalSignatureProvider.IsSigned
{
get
{
return XpsDocument.Signatures.Count > 0;
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
bool IDigitalSignatureProvider.IsSignable
{
get
{
if (!_isSignableCacheValid)
{
_isSignableCache = XpsDocument.IsSignable;
_isSignableCacheValid = true;
}
return _isSignableCache;
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
bool IDigitalSignatureProvider.HasRequests
{
get
{
bool rtn = false;
foreach (DigitalSignature digitalSignature in ((IDigitalSignatureProvider)this).Signatures)
{
if (digitalSignature.SignatureState == SignatureStatus.NotSigned)
{
rtn = true;
break;
}
}
return rtn;
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
void IDigitalSignatureProvider.SignDocument(DigitalSignature digitalSignature)
{
AssertIsSignable();
XpsDigSigPartAlteringRestrictions reachRestrictions = XpsDigSigPartAlteringRestrictions.None;
if (digitalSignature.IsDocumentPropertiesRestricted)
{
reachRestrictions |= XpsDigSigPartAlteringRestrictions.CoreMetadata;
}
// If additional signatures should invalidate this signature, we
// need to sign the signature origin part
if (digitalSignature.IsAddingSignaturesRestricted)
{
reachRestrictions |= XpsDigSigPartAlteringRestrictions.SignatureOrigin;
}
// a null guid means there was no associated spot, so create a guid
if (digitalSignature.GuidID == null)
{
digitalSignature.GuidID = Guid.NewGuid();
}
XpsDigitalSignature xpsDigitalSignature =
XpsDocument.SignDigitally(
digitalSignature.Certificate,
true,
reachRestrictions,
(Guid)digitalSignature.GuidID,
false /* don't re-verify IsSignable, we've already done it */
);
if (xpsDigitalSignature != null)
{
// Fill in relevant fields from the XPS signature
digitalSignature.XpsDigitalSignature = xpsDigitalSignature;
digitalSignature.SignatureState = SignatureStatus.Valid;
digitalSignature.SignedOn = xpsDigitalSignature.SigningTime;
// Save the simple name from the certificate as the subject name
// in the signature
digitalSignature.SubjectName =
digitalSignature.Certificate.GetNameInfo(
X509NameType.SimpleName,
false /* don't include issuer name */);
// Add the new signature to the list (if it isn't already there).
// That is a possibility since the first signature in a document
// is always added as a signature definition and a signature.
if (!DigitalSignatureList.Contains(digitalSignature))
{
DigitalSignatureList.Add(digitalSignature);
}
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
Guid IDigitalSignatureProvider.AddRequestSignature(DigitalSignature digitalSignature)
{
AssertIsSignable();
// Create guid used for signature ID
Guid guidID = Guid.NewGuid();
// Create a new SignatureDefinition
XpsSignatureDefinition xpsSignatureDefinition = new XpsSignatureDefinition();
// Use the digSig to setup the SignatureDefinition.
xpsSignatureDefinition.RequestedSigner = digitalSignature.SubjectName;
xpsSignatureDefinition.Intent = digitalSignature.Reason;
xpsSignatureDefinition.SigningLocale = digitalSignature.Location;
xpsSignatureDefinition.SignBy = digitalSignature.SignedOn;
// Use our new guid to setup the ID
xpsSignatureDefinition.SpotId = guidID;
// Add the signature definition to the document
FixedDocument.AddSignatureDefinition(xpsSignatureDefinition);
FixedDocument.CommitSignatureDefinition();
// Set the signature's status to Not Signed before adding to our list
digitalSignature.SignatureState = SignatureStatus.NotSigned;
// Add the new signature to our list of signatures and definitions
DigitalSignatureList.Add(digitalSignature);
return guidID;
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
void IDigitalSignatureProvider.RemoveRequestSignature(Guid spotId)
{
AssertIsSignable();
XpsSignatureDefinition definition = FindSignatureDefinition(spotId);
if (definition != null)
{
FixedDocument.RemoveSignatureDefinition(definition);
FixedDocument.CommitSignatureDefinition();
}
// Loop through the signature list and remove the entry for the
// requested signature
foreach (DigitalSignature signature in DigitalSignatureList)
{
if (signature.GuidID == spotId)
{
// We only want to remove unsigned signature definitions
// (requested signatures) and not actual signatures.
if (signature.SignatureState == SignatureStatus.NotSigned)
{
DigitalSignatureList.Remove(signature);
}
// It is safe to remove an element from the list that we're
// currently enumerating because we stop enumerating as soon
// as we find the element we're looking for.
break;
}
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
void IDigitalSignatureProvider.UnsignDocument(Guid id)
{
AssertIsSignable();
foreach (DigitalSignature signature in DigitalSignatureList)
{
if (signature.GuidID == id)
{
// Remove the associated XpsDigitalSignature from the
// document
if (signature.XpsDigitalSignature != null)
{
XpsDocument.RemoveSignature(signature.XpsDigitalSignature);
signature.XpsDigitalSignature = null;
}
// Check if the document contains a signature definition
// corresponding to this signature
bool matchesDefinition = (FindSignatureDefinition(id) != null);
// If the signature matches a signature definition in the
// document, mark the signature as NotSigned (i.e. an
// unsigned request) but leave it in the list
if (matchesDefinition)
{
signature.SignatureState = SignatureStatus.NotSigned;
}
else
{
// Remove the signature from the list
DigitalSignatureList.Remove(signature);
}
// It is safe to remove an element from the list that we're
// currently enumerating because we stop enumerating as soon
// as we find the element we're looking for.
break;
}
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
void IDigitalSignatureProvider.VerifySignatures()
{
// If we haven't yet retrieved signatures from the package, do so
if (DigitalSignatureList == null)
{
DigitalSignatureList = GetSignaturesFromPackage();
}
// Verify each XPS digital signature from the map
// This will update the status of the signatures in the list also
foreach (DigitalSignature signature in DigitalSignatureList)
{
if (signature.XpsDigitalSignature != null)
{
signature.SignatureState =
VerifyXpsDigitalSignature(signature.XpsDigitalSignature);
}
}
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
IList<X509Certificate2> IDigitalSignatureProvider.GetAllCertificates()
{
List<X509Certificate2> certificateList = new List<X509Certificate2>();
foreach (DigitalSignature signature in ((IDigitalSignatureProvider)this).Signatures)
{
X509Certificate2 certificate = signature.Certificate;
if (certificate != null && !certificateList.Contains(certificate))
{
certificateList.Add(certificate);
}
}
return certificateList;
}
/// <summary>
/// See IDigitalSignatureProvider
/// </summary>
IDictionary<X509Certificate2, CertificatePriorityStatus> IDigitalSignatureProvider.GetCertificateStatus(
IList<X509Certificate2> certificates)
{
Dictionary<X509Certificate2, CertificatePriorityStatus> certificateStatusTable =
new Dictionary<X509Certificate2, CertificatePriorityStatus>();
foreach (X509Certificate2 certificate in certificates)
{
certificateStatusTable.Add(
certificate,
GetCertificateStatus(certificate));
}
return certificateStatusTable;
}
/// <summary>
/// Returns a Read Only collection of our Digital Signatures.
/// </summary>
ReadOnlyCollection<DigitalSignature> IDigitalSignatureProvider.Signatures
{
get
{
// If we have not yet read signatures from the package, load them
if (DigitalSignatureList == null)
{
DigitalSignatureList = GetSignaturesFromPackage();
}
if (_readOnlySignatureList == null)
{
_readOnlySignatureList =
new ReadOnlyCollection<DigitalSignature>(DigitalSignatureList);
}
return _readOnlySignatureList;
}
}
#endregion IDigitalSignatureProvider
#region Private Methods
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
/// <summary>
/// Returns a list of all the DigitalSignature objects from the package.
/// </summary>
/// <returns>A list of DigitalSignature objects</returns>
private IList<DigitalSignature> GetSignaturesFromPackage()
{
IList<DigitalSignature> signatureList = new List<DigitalSignature>();
// This will contain a mapping of GUIDs to signature definitions so
// that we can easily look up the signature definition (if any) that
// corresponds with a signature
IDictionary<Guid, XpsSignatureDefinition> signatureDefinitionMap =
new Dictionary<Guid, XpsSignatureDefinition>();
// This will contain a list of all the signature definitions that do
// not have an associated GUID, which means that they have to be
// requested signatures
IList<XpsSignatureDefinition> requestedSignatureList =
new List<XpsSignatureDefinition>();
// Enumerate all the signature definitions in all of the fixed
// documents in the XPS document to generate the map of GUIDs to
// signature definitions
foreach (IXpsFixedDocumentReader fixedDocument in FixedDocumentSequence.FixedDocuments)
{
ICollection<XpsSignatureDefinition> documentSignatureDefinitionList =
fixedDocument.SignatureDefinitions;
if (documentSignatureDefinitionList != null)
{
// Add each signature definition to either the GUID map or
// the list of requested signatures
foreach (XpsSignatureDefinition signatureDefinition in documentSignatureDefinitionList)
{
// If the signature definition has a GUID, add it to the map
if (signatureDefinition.SpotId != null)
{
signatureDefinitionMap.Add(signatureDefinition.SpotId.Value, signatureDefinition);
}
// If it does not have a GUID it cannot match a signature yet,
// so add it to the list of requested signatures
else
{
requestedSignatureList.Add(signatureDefinition);
}
}
}
}
// Now loop through all the XpsDigitalSignatures, matching them with
// signature definitions by GUID to get the signature fields that
// are only found in signature definitions.
foreach (XpsDigitalSignature xpsDigitalSignature in XpsDocument.Signatures)
{
// Convert the XPS signature into our format
DigitalSignature digitalSignature =
ConvertXpsDigitalSignature(xpsDigitalSignature);
// Check if the signature corresponds to a definition by seeing
// if the GUID is in the signature definition map
bool definitionFound =
xpsDigitalSignature.Id.HasValue &&
signatureDefinitionMap.ContainsKey(xpsDigitalSignature.Id.Value);
// If the signature corresponds to a signature definition, copy
// fields from the corresponding definition
if (definitionFound)
{
XpsSignatureDefinition signatureDefinition =
signatureDefinitionMap[xpsDigitalSignature.Id.Value];
// Copy SignatureDefinition fields
digitalSignature.Reason = signatureDefinition.Intent;
digitalSignature.Location = signatureDefinition.SigningLocale;
// Now that we have found a signature that matches this
// signature definition, it can no longer match any other
// signatures by GUID and we can remove it from the map.
signatureDefinitionMap.Remove(xpsDigitalSignature.Id.Value);
}
signatureList.Add(digitalSignature);
}
// What is left over in the signatureDefinitionMap are definitions
// that don't have matching XpsDigSigs. Add these as requested
// signatures.
foreach (XpsSignatureDefinition signatureDefinition in signatureDefinitionMap.Values)
{
//Add this request signature to our list.
signatureList.Add(ConvertXpsSignatureDefinition(signatureDefinition));
}
// Add all the definitions we already knew were requested signatures
foreach (XpsSignatureDefinition definition in requestedSignatureList)
{
//Add this request signature to our list.
signatureList.Add(ConvertXpsSignatureDefinition(definition));
}
return signatureList;
}
/// <summary>
/// Maps an XpsDigitalSignature to our DigitalSignature.
/// </summary>
/// <param name="xpsDigitalSignature">The signature to convert</param>
/// <returns>A DigitalSignature that corresponds to the signature
/// passed in as a parameter</returns>
private static DigitalSignature ConvertXpsDigitalSignature(XpsDigitalSignature xpsDigitalSignature)
{
DigitalSignature digitalSignature = new DigitalSignature();
digitalSignature.XpsDigitalSignature = xpsDigitalSignature;
X509Certificate2 x509Certificate2 =
xpsDigitalSignature.SignerCertificate as X509Certificate2;
digitalSignature.SignatureState = SignatureStatus.Unknown;
// Copy simple fields if cert isn't null. If it is null then the
// cert wasn't embedded into container so don't copy cert related
// fields.
if (x509Certificate2 != null)
{
digitalSignature.Certificate = x509Certificate2;
digitalSignature.SignedOn = xpsDigitalSignature.SigningTime;
// save the simple name from the certificate as the subject name
// in the signature
digitalSignature.SubjectName =
x509Certificate2.GetNameInfo(
X509NameType.SimpleName,
false /* don't include issuer name */);
}
digitalSignature.IsDocumentPropertiesRestricted =
xpsDigitalSignature.DocumentPropertiesRestricted;
// If the signature origin part is signed, adding new signatures
// will invalidate this signature
digitalSignature.IsAddingSignaturesRestricted =
xpsDigitalSignature.SignatureOriginRestricted;
//These fields come from a Signature Definition.
digitalSignature.Reason = string.Empty;
digitalSignature.Location = string.Empty;
return digitalSignature;
}
/// <summary>
/// Maps an XpsSignatureDefinition to our DigitalSignature.
/// </summary>
/// <param name="signatureDefinition">The signature definition to
/// convert</param>
/// <returns>A DigitalSignature representing a requested signature with
/// signature status NotSigned</returns>
private static DigitalSignature ConvertXpsSignatureDefinition(XpsSignatureDefinition signatureDefinition)
{
//Create new DigSig. This is a request and will have the status NotSigned.
DigitalSignature digitalSignature = new DigitalSignature();
digitalSignature.SignatureState = SignatureStatus.NotSigned;
//set fields using the definition.
digitalSignature.SubjectName = signatureDefinition.RequestedSigner;
digitalSignature.Reason = signatureDefinition.Intent;
digitalSignature.SignedOn = signatureDefinition.SignBy;
digitalSignature.Location = signatureDefinition.SigningLocale;
digitalSignature.GuidID = signatureDefinition.SpotId;
return digitalSignature;
}
/// <summary>
/// Verifies a given XPS digital signature.
/// </summary>
/// <remarks>This computes and checks the hashes of all the package
/// parts, so it may take a long time to complete.</remarks>
/// <param name="xpsSignature">The XPS signature to verify</param>
/// <returns>The status of the signature</returns>
private static SignatureStatus VerifyXpsDigitalSignature(XpsDigitalSignature xpsDigitalSignature)
{
SignatureStatus status = SignatureStatus.Unknown;
//Verify signature and map to DRPs SignatureStatus enum.
switch (xpsDigitalSignature.Verify())
{
case VerifyResult.Success:
{
status = SignatureStatus.Valid;
break;
}
case VerifyResult.NotSigned:
{
status = SignatureStatus.NotSigned;
break;
}
default:
{
status = SignatureStatus.Invalid;
break;
}
}
return status;
}
/// <summary>
/// Gets the CertificateStatus for a specific certificate.
/// </summary>
private static CertificatePriorityStatus GetCertificateStatus(X509Certificate2 certificate)
{
//Default status is the most severe error: Corrupted
CertificatePriorityStatus certificatePriorityStatus = CertificatePriorityStatus.Corrupted;
X509ChainStatusFlags x509ChainStatusFlags;
// Use the static VerifyCertificate method on XpsDigitalSignature
// to verify the certificate
x509ChainStatusFlags = XpsDigitalSignature.VerifyCertificate(certificate);
//Strip out all known flags (minus Cyclic and NotSignatureValid). What is left are any unknown flags
//and flags that convert to Corrupted.
X509ChainStatusFlags x509RemainingFlags = (x509ChainStatusFlags ^ _x509NonCorruptedFlags) &
~(_x509NonCorruptedFlags);
//x509ChainStatusFlags is a flag we want to convert to a CertificatePriorityStatus.
//First we need to make sure there are no unknown flags. If there is an unknown
//flag we assume that it is the worst possible error and leave CertificateStatus
//set to Corrupted. Leaving out Cyclic and NotSignatureValid since they also convert
//to Corrupted.
if (x509RemainingFlags == X509ChainStatusFlags.NoError)
{
//The following flags convert to CannotBeVerified
if ((x509ChainStatusFlags & _x509CannotBeVerifiedFlags) != X509ChainStatusFlags.NoError)
{
certificatePriorityStatus = CertificatePriorityStatus.CannotBeVerified;
}
//The following flags convert to IssuerNotTrusted
else if ((x509ChainStatusFlags & _x509IssuerNotTrustedFlags) != X509ChainStatusFlags.NoError)
{
certificatePriorityStatus = CertificatePriorityStatus.IssuerNotTrusted;
}
//The following flags convert to Revoked
else if ((x509ChainStatusFlags & _x509RevokedFlags) != X509ChainStatusFlags.NoError)
{
certificatePriorityStatus = CertificatePriorityStatus.Revoked;
}
//The following flags convert to Expired
else if ((x509ChainStatusFlags & _x509ExpiredFlags) != X509ChainStatusFlags.NoError)
{
certificatePriorityStatus = CertificatePriorityStatus.Expired;
}
//The following flags are all considered Igorable
else
{
certificatePriorityStatus = CertificatePriorityStatus.Ok;
}
}
return certificatePriorityStatus;
}
/// <summary>
/// Returns a signature definition in the document that matches a GUID.
/// </summary>
/// <param name="id">The GUID to match</param>
/// <returns>The corresponding signature definition, if one exists.
/// </returns>
private XpsSignatureDefinition FindSignatureDefinition(Guid id)
{
XpsSignatureDefinition definition = null;
// Loop through our collection of definitions and find the matching
// GUID. We only need to look at the fixed document we have saved
// (and not the other documents in the sequence) since we always
// save signature definitions to that fixed document.
foreach (XpsSignatureDefinition signatureDefinition in FixedDocument.SignatureDefinitions)
{
if (signatureDefinition.SpotId == id)
{
definition = signatureDefinition;
break;
}
}
return definition;
}
/// <summary>
/// Sanity check that the content is signable, and that the check for this
/// has been done prior to calling Signing-related methods.
/// </summary>
private void AssertIsSignable()
{
// We assert that _isSignableCacheValid is true here --
// we don't want to block on calling XpsDocument.IsSignable so we
// require calling code do that work prior to invoking SignDocument.
Invariant.Assert(_isSignableCacheValid);
// Assert that the document is actually signable. We should never
// get here if it's not.
Invariant.Assert(_isSignableCache);
}
#endregion Private Methods
#region Private Properties
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
/// <summary>
/// Gets or sets the XPS document from which to read signatures.
/// </summary>
private XpsDocument XpsDocument
{
get
{
return _xpsDocument;
}
set
{
_xpsDocument = value;
}
}
/// <summary>
/// Gets or sets the fixed document to which to write signature
/// definitions.
/// </summary>
private IXpsFixedDocumentReader FixedDocument
{
get
{
return _fixedDocument;
}
set
{
_fixedDocument = value;
}
}
/// <summary>
/// Gets or sets the fixed document sequence
/// </summary>
private IXpsFixedDocumentSequenceReader FixedDocumentSequence
{
get
{
return _fixedDocumentSequence;
}
set
{
_fixedDocumentSequence = value;
}
}
/// <summary>
/// Gets or sets the list of all the signatures in the package.
/// </summary>
private IList<DigitalSignature> DigitalSignatureList
{
get
{
return _digitalSignatureList;
}
set
{
_digitalSignatureList = value;
}
}
#endregion Private Properties
#region Private Fields
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
/// <summary>
/// The XPS document from which to read signatures.
/// </summary>
XpsDocument _xpsDocument;
/// <summary>
/// The fixed document sequence to which to write signature definitions.
/// </summary>
IXpsFixedDocumentSequenceReader _fixedDocumentSequence;
/// <summary>
/// The fixed document to which to write signature definitions.
/// </summary>
IXpsFixedDocumentReader _fixedDocument;
/// <summary>
/// A list of all the signatures in the package.
/// </summary>
IList<DigitalSignature> _digitalSignatureList;
/// <summary>
/// A cached read-only version of the signature list. This is a wrapper
/// around _digitalSignatureList that is intended to be passed out by
/// the Signatures property.
/// </summary>
ReadOnlyCollection<DigitalSignature> _readOnlySignatureList;
//Contains all known flags that don't convert to Corrupted.
//(All flags except Cyclic and NotSignatureValid). We will be looking for unknown flags using this
//and if any exist then status will be set to Corrupted.
private const X509ChainStatusFlags _x509NonCorruptedFlags =
X509ChainStatusFlags.HasExcludedNameConstraint |
X509ChainStatusFlags.HasNotDefinedNameConstraint |
X509ChainStatusFlags.HasNotPermittedNameConstraint |
X509ChainStatusFlags.HasNotSupportedNameConstraint |
X509ChainStatusFlags.InvalidBasicConstraints |
X509ChainStatusFlags.InvalidExtension |
X509ChainStatusFlags.InvalidNameConstraints |
X509ChainStatusFlags.InvalidPolicyConstraints |
X509ChainStatusFlags.NoIssuanceChainPolicy |
X509ChainStatusFlags.PartialChain |
X509ChainStatusFlags.UntrustedRoot |
X509ChainStatusFlags.Revoked |
X509ChainStatusFlags.NotTimeValid |
X509ChainStatusFlags.NoError |
X509ChainStatusFlags.CtlNotSignatureValid |
X509ChainStatusFlags.CtlNotTimeValid |
X509ChainStatusFlags.CtlNotValidForUsage |
X509ChainStatusFlags.NotTimeNested |
X509ChainStatusFlags.NotValidForUsage |
X509ChainStatusFlags.OfflineRevocation |
X509ChainStatusFlags.RevocationStatusUnknown;
//Create variable that contains all known flags that convert to CannotBeVerified.
private const X509ChainStatusFlags _x509CannotBeVerifiedFlags =
X509ChainStatusFlags.HasExcludedNameConstraint |
X509ChainStatusFlags.HasNotDefinedNameConstraint |
X509ChainStatusFlags.HasNotPermittedNameConstraint |
X509ChainStatusFlags.HasNotSupportedNameConstraint |
X509ChainStatusFlags.InvalidBasicConstraints |
X509ChainStatusFlags.InvalidExtension |
X509ChainStatusFlags.InvalidNameConstraints |
X509ChainStatusFlags.InvalidPolicyConstraints |
X509ChainStatusFlags.NoIssuanceChainPolicy;
//Create variable that contains all known flags that convert to IssuerNotTrusted.
private const X509ChainStatusFlags _x509IssuerNotTrustedFlags =
X509ChainStatusFlags.PartialChain |
X509ChainStatusFlags.UntrustedRoot;
//Create variable that contains all known flags that convert to Revoked.
private const X509ChainStatusFlags _x509RevokedFlags =
X509ChainStatusFlags.Revoked;
//Create variable that contains all known flags that convert to Expired.
private const X509ChainStatusFlags _x509ExpiredFlags =
X509ChainStatusFlags.NotTimeValid;
/// <summary>
/// Cached value for the IsSignable property
/// </summary>
private bool _isSignableCache;
private bool _isSignableCacheValid;
#endregion Private Fields
}
}
|