|
//-----------------------------------------------------------------------------
//
// <copyright file="PackageDigitalSignatureManager.cs" company="Microsoft">
// Copyright (C) Microsoft Corporation. All rights reserved.
// </copyright>
//
// Description:
// This class provides api's to add/remove/verify signatures on an MMCF container.
//
// History:
// 03/22/2004: BruceMac: Initial Implementation
//
//-----------------------------------------------------------------------------
// Allow use of presharp warning numbers [6506] unknown to the compiler
#pragma warning disable 1634, 1691
using System;
using System.Collections.Generic;
using System.Windows; // For Exception strings - SRID
using System.Text; // for StringBuilder
using System.Diagnostics; // for Assert
using System.Security; // for SecurityCritical tag
using System.Security.Permissions; // for LinkDemand
using System.Security.Cryptography.Xml; // for SignedXml
using System.Security.Cryptography.X509Certificates; // for X509Certificate
using MS.Internal.IO.Packaging; // for internal helpers
using System.Collections.ObjectModel; // for ReadOnlyCollection<>
using MS.Internal; // for ContentType
using MS.Internal.WindowsBase;
namespace System.IO.Packaging
{
/// <summary>
/// Options for storing the signing Certificate
/// </summary>
public enum CertificateEmbeddingOption : int
{
/// <summary>
/// Embed certificate in its own PackagePart (or share if same cert already exists)
/// </summary>
InCertificatePart = 0, // embed the certificate in its own, possibly-shared part
/// <summary>
/// Embed certificate within the signature PackagePart
/// </summary>
InSignaturePart = 1, // embed the certificate within the signature
/// <summary>
/// Do not embed
/// </summary>
NotEmbedded = 2, // do not embed the certificate at all
}
/// <summary>
/// Type of the handler that is invoked if signature validation is non-success.
/// </summary>
/// <param name="sender">signature</param>
/// <param name="e">event arguments - containing the result</param>
/// <returns>true to continue verifying other signatures, false to abandon effort</returns>
public delegate void InvalidSignatureEventHandler(object sender, SignatureVerificationEventArgs e);
/// <summary>
/// Signature Verification Event Args - information about a verification event
/// </summary>
public class SignatureVerificationEventArgs : EventArgs
{
//------------------------------------------------------
//
// Public Members
//
//------------------------------------------------------
/// <summary>
/// Signature being processed
/// </summary>
public PackageDigitalSignature Signature
{
get
{
return _signature;
}
}
/// <summary>
/// Result of Verification
/// </summary>
public VerifyResult VerifyResult
{
get
{
return _result;
}
}
//------------------------------------------------------
//
// Internal Members
//
//------------------------------------------------------
internal SignatureVerificationEventArgs(PackageDigitalSignature signature,
VerifyResult result)
{
// verify arguments
if (signature == null)
throw new ArgumentNullException("signature");
if (result < VerifyResult.Success || result > VerifyResult.NotSigned)
throw new System.ArgumentOutOfRangeException("result");
_signature = signature;
_result = result;
}
//------------------------------------------------------
//
// Private Members
//
//------------------------------------------------------
private PackageDigitalSignature _signature;
private VerifyResult _result;
}
/// <summary>
/// PackageDigitalSignatureManager
/// </summary>
public sealed class PackageDigitalSignatureManager
{
#region Public Members
//------------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
/// <summary>
/// Event to subscribe to for signature validation activities
/// </summary>
public event InvalidSignatureEventHandler InvalidSignatureEvent;
//------------------------------------------------------
//
// Public Properties
//
//------------------------------------------------------
/// <summary>
/// Does this container hold digital signatures?
/// </summary>
/// <value>true if signatures exist</value>
/// <remarks>this does not evaluate the signatures - they may be invalid even if this returns true</remarks>
public bool IsSigned
{
get
{
EnsureSignatures();
return (_signatures.Count > 0);
}
}
/// <summary>
/// Signatures in container
/// </summary>
/// <value>read only list of immutable signatures found in the container</value>
public ReadOnlyCollection<PackageDigitalSignature> Signatures
{
get
{
// ensure signatures are loaded from origin
EnsureSignatures();
// Return a read-only collection referring to them.
// This list will be automatically updated when the underlying collection is changed.
if (_signatureList == null)
_signatureList = new ReadOnlyCollection<PackageDigitalSignature>(_signatures);
return _signatureList;
}
}
/// <summary>
/// ContentType - Transform mapping dictionary
/// </summary>
/// <remarks>Dictionary of transform Uri's indexed by ContentType.
/// Contains a single transform to be applied
/// before hashing any Part encountered with that ContentType</remarks>
public Dictionary<String, String> TransformMapping
{
get
{
return _transformDictionary;
}
}
/// <summary>
/// Handle of parent window to use when displaying certificate selection dialog
/// </summary>
/// <value></value>
/// <remarks>not necessary if certificates are provided in calls to sign</remarks>
public IntPtr ParentWindow
{
get
{
return _parentWindow;
}
set
{
_parentWindow = value;
}
}
/// <summary>
/// Hashalgorithm to use when creating/verifying signatures
/// </summary>
/// <value></value>
/// <remarks>defaults to SHA1</remarks>
public String HashAlgorithm
{
get
{
return _hashAlgorithmString;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (value == String.Empty)
throw new ArgumentException(SR.Get(SRID.UnsupportedHashAlgorithm), "value");
_hashAlgorithmString = value;
}
}
/// <summary>
/// How to embed certificates when Signing
/// </summary>
/// <value></value>
public CertificateEmbeddingOption CertificateOption
{
get
{
return _certificateEmbeddingOption;
}
set
{
if ((value < CertificateEmbeddingOption.InCertificatePart) || (value > CertificateEmbeddingOption.NotEmbedded))
throw new ArgumentOutOfRangeException("value");
_certificateEmbeddingOption = value;
}
}
/// <summary>
/// How to format the SignatureTime in new signatures
/// </summary>
/// <remarks>Legal formats specified in Opc book and reproduced here:
/// YYYY-MM-DDThh:mm:ss.sTZD
/// YYYY-MM-DDThh:mm:ssTZD
/// YYYY-MM-DDThh:mmTZD
/// YYYY-MM-DD
/// YYYY-MM
/// YYYY
///
/// where:
/// Y = year, M = month integer (leading zero), D = day integer (leading zero),
/// hh = 24hr clock hour
/// mm = minutes (leading zero)
/// ss = seconds (leading zero)
/// .s = tenths of a second
/// </remarks>
public String TimeFormat
{
get
{
return _signatureTimeFormat;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (XmlSignatureProperties.LegalFormat(value))
_signatureTimeFormat = value;
else
throw new FormatException(SR.Get(SRID.BadSignatureTimeFormatString));
}
}
//------------------------------------------------------
//
// Public Fields
//
//------------------------------------------------------
/// <summary>
/// Name of signature origin part
/// </summary>
/// <value></value>
/// <remarks>This value may vary by Package because the name is not formally defined. While this
/// implementation will generally use the same default value, the value returned by this property will reflect
/// whatever origin is already present in the current Package (if any) which may vary between implementations.
/// </remarks>
public Uri SignatureOrigin
{
get
{
OriginPartExists(); // force search for OriginPart in case it is different from default
return _originPartName;
}
}
/// <summary>
/// Type of default signature origin relationship
/// </summary>
/// <value></value>
static public String SignatureOriginRelationshipType
{
get
{
return _originRelationshipType;
}
}
/// <summary>
/// Default hash algorithm
/// </summary>
/// <value></value>
static public String DefaultHashAlgorithm
{
get
{
// DDVSO:395149
// If we set the compatibility flag, return the legacy default (SHA1).
return (BaseAppContextSwitches.UseSha1AsDefaultHashAlgorithmForDigitalSignatures) ? SignedXml.XmlDsigSHA1Url : _defaultHashAlgorithm;
}
}
//------------------------------------------------------
//
// Public Methods
//
//------------------------------------------------------
/// <summary>
/// Create a new PackageDigitalSignature manager
/// </summary>
/// <param name="package">container to work with</param>
/// <remarks>based on the default origin</remarks>
/// <exception cref="ArgumentNullException">package is null</exception>
public PackageDigitalSignatureManager(Package package)
{
if (package == null)
throw new ArgumentNullException("package");
_parentWindow = IntPtr.Zero;
_container = package;
// initialize the transform dictionary with defaults
_transformDictionary = new Dictionary<String, String>(4);
_transformDictionary[PackagingUtilities.RelationshipPartContentType.ToString()] = SignedXml.XmlDsigC14NTransformUrl; // relationship parts
_transformDictionary[XmlDigitalSignatureProcessor.ContentType.ToString()] = SignedXml.XmlDsigC14NTransformUrl; // xml signature
}
#region Sign
/// <summary>
/// Sign - prompts for certificate and embeds it
/// </summary>
/// <param name="parts">list of parts to sign</param>
/// <remarks>Set ParentWindow before this call if you want to make the certificate
/// selection dialog modal to a particular window. Does not prompt for certificates if none could be located in the default certificate store.</remarks>
/// <returns>null if no certificate could be located, or if the user cancels from the certificate selection dialog.</returns>
public PackageDigitalSignature Sign(IEnumerable<Uri> parts)
{
X509Certificate certificate = PromptForSigningCertificate(ParentWindow);
if (certificate == null)
return null;
else
return Sign(parts, certificate);
}
/// <summary>
/// Sign - certificate provided by caller
/// </summary>
/// <param name="parts">list of parts to sign</param>
/// <param name="certificate">signer's certificate</param>
public PackageDigitalSignature Sign(IEnumerable<Uri> parts, X509Certificate certificate)
{
// create unique signature name
return Sign(parts, certificate, null);
}
/// <summary>
/// Sign - certificate provided by caller
/// </summary>
/// <param name="parts">list of parts to sign - may be empty or null</param>
/// <param name="certificate">signer's certificate</param>
/// <param name="relationshipSelectors">relationshipSelectors that hold information about
/// the relationships to be signed - may be empty or null</param>
/// <remarks>one of parts or relationships must be non-null and contain at least a single entry</remarks>
public PackageDigitalSignature Sign(IEnumerable<Uri> parts, X509Certificate certificate, IEnumerable<PackageRelationshipSelector> relationshipSelectors)
{
// use default signature Id
return Sign(parts, certificate, relationshipSelectors, XTable.Get(XTable.ID.OpcSignatureAttrValue));
}
/// <summary>
/// Sign - certificate provided by caller
/// </summary>
/// <param name="parts">list of parts to sign - may be empty or null</param>
/// <param name="certificate">signer's certificate</param>
/// <param name="relationshipSelectors">relationshipSelectors that hold information about
/// the relationships to be signed - may be empty or null</param>
/// <param name="signatureId">id for the new Signature - may be empty or null</param>
/// <remarks>one of parts or relationships must be non-null and contain at least a single entry</remarks>
public PackageDigitalSignature Sign(
IEnumerable<Uri> parts,
X509Certificate certificate,
IEnumerable<PackageRelationshipSelector> relationshipSelectors,
String signatureId)
{
// Cannot both be null - need to check here because the similar check in the super-overload cannot
// distinguish to this level.
if (parts == null && relationshipSelectors == null)
{
throw new ArgumentException(SR.Get(SRID.NothingToSign));
}
return Sign(parts, certificate, relationshipSelectors, signatureId, null, null);
}
/// <summary>
/// Sign - caller specifies custom "Object" and/or SignedInfo "Reference" tags
/// </summary>
/// <param name="parts">list of parts to sign - may be empty or null</param>
/// <param name="certificate">signer's certificate</param>
/// <param name="relationshipSelectors">relationshipSelectors that hold information about
/// the relationships to be signed - may be empty or null</param>
/// <param name="signatureId">id for the new Signature - may be empty or null</param>
/// <param name="objectReferences">references to custom object tags. The DigestMethod on each
/// Reference will be ignored. The signature will use the globally defined HashAlgorithm
/// obtained from the current value of the HashAlgorithm property.</param>
/// <param name="signatureObjects">objects (signed or not)</param>
/// <exception cref="InvalidOperationException">Thrown if any TransformMapping
/// defines an empty or null transform for the ContentType of any Part being signed or if an unknown
/// transform is encountered.</exception>
/// <exception cref="System.Xml.XmlException">Thrown if signatureId is non-null and violates the
/// Xml Id schema (essentially - no leading digit is allowed).</exception>
/// <remarks>One of parts, relationships, signatureObjects and objectReferences must be
/// non-null and contain at least a single entry.
/// This and every other Sign overload makes use of the current state of the TransformMapping
/// dictionary which defines a Transform to apply based on ContentType. The Opc specification
/// only currently allows for two legal Transform algorithms: C14 and C14N.
/// Note that the w3c Xml Signature standard does not allow for empty Manifest tags.
/// Because the Opc specification requires the existence of a Package-specific Object
/// tag and further specifies that this Object tag contain a Manifest and SignatureProperties
/// tags, it follows that this Manifest tag must include at least one Reference tag.
/// This means that every signature include at least one of a Part to sign (non-empty parts tag)
/// or a Relationship to sign (non-empty relationshipSelectors) even if such a signature
/// is only destined to sign signatureObjects and/or objectReferences.
/// This overload provides support for generation of Xml signatures that require custom
/// Object tags. For any provided Object tag to be signed, a corresponding Reference
/// tag must be provided with a Uri that targets the Object tag using local fragment
/// syntax. If the object had an ID of "myObject" the Uri on the Reference would
/// be "#myObject". For unsigned objects, no reference is required.</remarks>
///<SecurityNote>
/// Critical: calls X509Certificate.Handle which LinkDemands
/// PublicOK: we don't store or return the handle
///</SecurityNote>
[SecurityCritical]
public PackageDigitalSignature Sign(
IEnumerable<Uri> parts,
X509Certificate certificate,
IEnumerable<PackageRelationshipSelector> relationshipSelectors,
String signatureId,
IEnumerable<System.Security.Cryptography.Xml.DataObject> signatureObjects,
IEnumerable<System.Security.Cryptography.Xml.Reference> objectReferences)
{
if (ReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotSignReadOnlyFile));
VerifySignArguments(parts, certificate, relationshipSelectors, signatureId, signatureObjects, objectReferences);
// substitute default id if none given
if ((signatureId == null) || (signatureId == String.Empty))
{
signatureId = "packageSignature"; // default
}
// Make sure the list reflects what's in the package.
// Do this before adding the new signature part because we don't want it included until it
// is fully formed (and delaying the add saves us having to remove it in case there is an
// error during the Sign call).
EnsureSignatures();
Uri newSignaturePartName = GenerateSignaturePartName();
if (_container.PartExists(newSignaturePartName))
throw new ArgumentException(SR.Get(SRID.DuplicateSignature));
// Pre-create origin part if it does not already exist.
// Do this before signing to allow for signing the package relationship part (because a Relationship
// is added from the Package to the Origin part by this call) and the Origin Relationship part in case this is
// a Publishing signature and the caller wants the addition of more signatures to break this signature.
PackageRelationship relationshipToNewSignature = OriginPart.CreateRelationship(newSignaturePartName, TargetMode.Internal,
_originToSignatureRelationshipType);
_container.Flush(); // ensure the origin relationship part is persisted so that any signature will include this newest relationship
VerifyPartsExist(parts);
// sign the data and optionally embed the certificate
bool embedCertificateInSignaturePart = (_certificateEmbeddingOption == CertificateEmbeddingOption.InSignaturePart);
// convert cert to version2 - more functionality
X509Certificate2 exSigner = certificate as X509Certificate2;
if (exSigner == null)
exSigner = new X509Certificate2(certificate.Handle);
//PRESHARP: Parameter to this public method must be validated: A null-dereference can occur here.
// Parameter 'exSigner' to this public method must be validated: A null-dereference can occur here.
//This is a false positive as the checks above can gurantee no null dereference will occur
#pragma warning disable 6506
PackageDigitalSignature signature = null;
PackagePart newSignaturePart = null;
try
{
// create the new part
newSignaturePart = _container.CreatePart(newSignaturePartName, XmlDigitalSignatureProcessor.ContentType.ToString());
// do the actual signing - only Xml signatures currently supported
signature = XmlDigitalSignatureProcessor.Sign(this, newSignaturePart, parts, relationshipSelectors, exSigner, signatureId, embedCertificateInSignaturePart,
signatureObjects, objectReferences);
}
catch (InvalidOperationException)
{
// bad hash algorithm - revert changes
// guarantees proper cleanup including removal of Origin if appropriate
// Note: _signatures.Count reflects the number of signatures that were
// existing before this sign method was called. So we want to leave those
// untouched and clean up what we added in this method prior to the
// exception. If the count is zero, we will also delete the origin part.
InternalRemoveSignature(newSignaturePartName, _signatures.Count);
_container.Flush(); // actually persist the revert
throw;
}
catch (System.IO.IOException)
{
// failure to open part - revert changes
// guarantees proper cleanup including removal of Origin if appropriate
// Note: _signatures.Count reflects the number of signatures that were
// existing before this sign method was called. So we want to leave those
// untouched and clean up what we added in this method prior to the
// exception. If the count is zero, we will also delete the origin part.
InternalRemoveSignature(newSignaturePartName, _signatures.Count);
_container.Flush(); // actually persist the revert
throw;
}
catch (System.Security.Cryptography.CryptographicException)
{
// failure to sign - revert changes
// guarantees proper cleanup including removal of Origin if appropriate
// Note: _signatures.Count reflects the number of signatures that were
// existing before this sign method was called. So we want to leave those
// untouched and clean up what we added in this method prior to the
// exception. If the count is zero, we will also delete the origin part.
InternalRemoveSignature(newSignaturePartName, _signatures.Count);
_container.Flush(); // actually persist the revert
throw;
}
// add to the list
_signatures.Add(signature);
// embed certificate if called for
if (_certificateEmbeddingOption == CertificateEmbeddingOption.InCertificatePart)
{
// create the cert part
// auto-generate a certificate name - will be the same for the same certificate
Uri certificatePartName = PackUriHelper.CreatePartUri(new Uri(
CertificatePart.PartNamePrefix + exSigner.SerialNumber + CertificatePart.PartNameExtension, UriKind.Relative));
// create the serialization helper class (side-effect of creating or opening the part)
CertificatePart certPart = new CertificatePart(_container, certificatePartName);
certPart.SetCertificate(exSigner);
// establish a relationship
newSignaturePart.CreateRelationship(certificatePartName, TargetMode.Internal, CertificatePart.RelationshipType);
signature.SetCertificatePart(certPart);
}
#pragma warning restore 6506
_container.Flush();
// return to caller in case they need it
return signature;
}
#endregion
#region CounterSign
/// <summary>
/// CounterSign - prompts for certificate and embeds it based on current CertificateEmbeddingOption
/// </summary>
/// <remarks>Set ParentWindow before this call if you want to make the certificate
/// selection dialog modal to a particular window. Does not present the dialog if no suitable certificate
/// could be found in the default certificate store.
/// Signs all existing signature parts so that any change to these part(s) will invalidate the
/// returned signature.</remarks>
/// <exception cref="InvalidOperationException">Cannot CounterSign an unsigned package.</exception>
/// <returns>null if no certificate could be located, or if the user cancels from the certificate selection dialog.</returns>
public PackageDigitalSignature Countersign()
{
// Counter-sign makes no sense if we are not already signed
// Check before asking for certificate
if (!IsSigned)
throw new InvalidOperationException(SR.Get(SRID.NoCounterSignUnsignedContainer));
// prompt for certificate
X509Certificate certificate = PromptForSigningCertificate(ParentWindow);
if (certificate == null)
return null;
else
return Countersign(certificate);
}
/// <summary>
/// CounterSign - certificate provided
/// </summary>
/// <param name="certificate">signer's certificate</param>
/// <exception cref="InvalidOperationException">Cannot CounterSign an unsigned package.</exception>
/// <exception cref="ArgumentNullException">certificate must be non-null.</exception>
/// <remarks>Signs all existing signature parts so that any change to these part(s) will invalidate the
/// returned signature.</remarks>
public PackageDigitalSignature Countersign(X509Certificate certificate)
{
if (certificate == null)
throw new ArgumentNullException("certificate");
// Counter-sign makes no sense if we are not already signed
// Check before asking for certificate
if (!IsSigned)
throw new InvalidOperationException(SR.Get(SRID.NoCounterSignUnsignedContainer));
// sign all existing signatures
List<Uri> signatures = new List<Uri>(_signatures.Count);
for (int i = 0; i < _signatures.Count; i++)
{
signatures.Add(_signatures[i].SignaturePart.Uri);
}
// sign
return Sign(signatures, certificate);
}
/// <summary>
/// CounterSign - signature part name(s) specified by caller
/// </summary>
/// <param name="certificate">signer's certificate</param>
/// <param name="signatures">signature parts to sign</param>
/// <remarks>Signs the given signature parts so that any change to these part(s) will invalidate the
/// returned signature.</remarks>
/// <exception cref="InvalidOperationException">Cannot CounterSign an unsigned package.</exception>
/// <exception cref="ArgumentException">signatures must be non-empty and cannot refer to parts other than signature parts.</exception>
/// <exception cref="ArgumentNullException">Both arguments must be non-null.</exception>
public PackageDigitalSignature Countersign(X509Certificate certificate, IEnumerable<Uri> signatures)
{
if (certificate == null)
throw new ArgumentNullException("certificate");
if (signatures == null)
throw new ArgumentNullException("signatures");
// Counter-sign makes no sense if we are not already signed
if (!IsSigned)
throw new InvalidOperationException(SR.Get(SRID.NoCounterSignUnsignedContainer));
// Restrict signatures to be actual signature part references
foreach (Uri uri in signatures)
{
PackagePart part = _container.GetPart(uri);
if (!part.ValidatedContentType.AreTypeAndSubTypeEqual(XmlDigitalSignatureProcessor.ContentType))
throw new ArgumentException(SR.Get(SRID.CanOnlyCounterSignSignatureParts, signatures));
}
return Sign(signatures, certificate);
}
#endregion
/// <summary>
/// verify all signatures - calls verify on each signature
/// </summary>
/// <param name="exitOnFailure">true to exit on first failure - false to continue</param>
/// <remarks>register for invalid signature events</remarks>
public VerifyResult VerifySignatures(bool exitOnFailure)
{
VerifyResult result;
EnsureSignatures();
// signed?
if (_signatures.Count == 0)
result = VerifyResult.NotSigned;
else
{
// contract is to return a failure value, even if there are subsequent successes
// defaulting to success here simplifies the logic for this
result = VerifyResult.Success; // default
for (int i = 0; i < _signatures.Count; i++)
{
VerifyResult temp = _signatures[i].Verify();
if (temp != VerifyResult.Success)
{
result = temp; // note failure
if (InvalidSignatureEvent != null)
InvalidSignatureEvent(this, new SignatureVerificationEventArgs(_signatures[i], temp));
if (exitOnFailure)
break;
}
}
}
return result;
}
/// <summary>
/// Remove a signature
/// </summary>
/// <param name="signatureUri">signature to remove</param>
/// <remarks>Caller should call Package.Flush() in order to persist changes.</remarks>
public void RemoveSignature(Uri signatureUri)
{
if (ReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotRemoveSignatureFromReadOnlyFile));
if (signatureUri == null)
throw new ArgumentNullException("signatureUri");
// empty?
if (!IsSigned) // calls EnsureSignatures for us
return;
// find the signature
int index = GetSignatureIndex(signatureUri);
if (index < 0)
return;
try
{
Debug.Assert(index < _signatures.Count);
//After this signature is removed the total number of signatures remaining will
//be _signatures.Count - 1. If this count is zero, then additional clean up needs
//to be done, like removing the Origin part.
InternalRemoveSignature(signatureUri, _signatures.Count - 1 /*since we are deleting one*/);
// invalidate the signature itself
_signatures[index].Invalidate();
}
finally
{
_signatures.RemoveAt(index); // ensure it is actually removed from the list
}
}
/// <summary>
/// Remove all signatures based on this origin
/// </summary>
/// <remarks>also removes all certificate parts and the signature origin. Caller must call Flush() to persist changes.</remarks>
public void RemoveAllSignatures()
{
if (ReadOnly)
throw new InvalidOperationException(SR.Get(SRID.CannotRemoveSignatureFromReadOnlyFile));
EnsureSignatures();
try
{
// Remove via known traversal - required to find all signatures (we may not know all signature content-types).
for (int i = 0; i < _signatures.Count; i++)
{
PackagePart p = _signatures[i].SignaturePart;
// Delete any Certificate part(s) targeted by this signature. We know that all of the
// reference counts will reach zero because we are removing all signatures.
foreach (PackageRelationship r in p.GetRelationshipsByType(CertificatePart.RelationshipType))
{
// don't resolve if external
if (r.TargetMode != TargetMode.Internal)
continue; // fail silently
_container.DeletePart(PackUriHelper.ResolvePartUri(r.SourceUri, r.TargetUri)); // will not throw if part not found
}
// delete signature part
_container.DeletePart(p.Uri);
// invalidate the signature itself
_signatures[i].Invalidate();
}
DeleteOriginPart();
}
finally
{
// update internal variables
_signatures.Clear();
}
}
/// <summary>
/// Obtain the PackageDigitalSignature referred to by the given Uri
/// </summary>
/// <param name="signatureUri">ID obtained from a PackageDigitalSignature object</param>
/// <returns>null if signature not found</returns>
public PackageDigitalSignature GetSignature(Uri signatureUri)
{
if (signatureUri == null)
throw new ArgumentNullException("signatureUri");
int index = GetSignatureIndex(signatureUri);
if (index < 0)
return null;
else
{
Debug.Assert(index < _signatures.Count);
return _signatures[index];
}
}
/// <summary>
/// Verify Certificate
/// </summary>
/// <param name="certificate">certificate to inspect</param>
/// <exception cref="System.Security.Cryptography.CryptographicException">certificate is invalid but the error code is not recognized</exception>
/// <returns>the first error encountered when inspecting the certificate chain or NoError if the certificate is valid</returns>
///<SecurityNote>
/// Critical - The X509Chain.Build method has a LinkDemand for Unrestricted.
/// PublicOK – VerifyCertificate has LinkDemand.
///</SecurityNote>
[SecurityCritical]
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Unrestricted = true)]
public static X509ChainStatusFlags VerifyCertificate(X509Certificate certificate)
{
if (certificate == null)
throw new ArgumentNullException("certificate");
X509ChainStatusFlags status = X509ChainStatusFlags.NoError;
// build the certificate chain
X509Chain chain = new X509Chain();
bool valid = chain.Build(new X509Certificate2(certificate.Handle));
// inspect the results
if (!valid)
{
X509ChainStatus[] chainStatus = chain.ChainStatus;
for (int i = 0; i < chainStatus.Length; i++)
{
status |= chainStatus[i].Status;
}
}
return status;
}
#endregion
//------------------------------------------------------
//
// Internal Properties
//
//------------------------------------------------------
/// <summary>
/// Get package - used by DigitalSignatureProcessors
/// </summary>
internal Package Package
{
get
{
return _container;
}
}
//------------------------------------------------------
//
// Internal Methods
//
//------------------------------------------------------
/// <summary>
/// PromptForSigningCertificate - invoked from Sign overloads if certificate is not provided by caller
/// </summary>
/// <param name="hwndParent"></param>
/// <returns>null if user cancels or no certificate could be located</returns>
///<SecurityNote>
/// Critical: calls X509Certificate2UI.SelectFromCollection which LinkDemands
/// TreatAsSafe: UI can only display existing certificates, no spoofing
///</SecurityNote>
[SecurityCritical, SecurityTreatAsSafe]
static internal X509Certificate PromptForSigningCertificate(IntPtr hwndParent)
{
X509Certificate2 X509cert = null;
// look for appropriate certificates
X509Store store = new X509Store(StoreLocation.CurrentUser);
store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
X509Certificate2Collection collection = (X509Certificate2Collection)store.Certificates;
// narrow down the choices
// timevalid
collection = collection.Find(X509FindType.FindByTimeValid, DateTime.Now, true);
// intended for signing (or no intent specified)
collection = collection.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.DigitalSignature, false);
// remove certs that don't have private key
// work backward so we don't disturb the enumeration
for (int i = collection.Count - 1; i >= 0; i--)
{
if (!collection[i].HasPrivateKey)
{
collection.RemoveAt(i);
}
}
// any suitable certificates available?
if (collection.Count > 0)
{
// ask user to select
collection = X509Certificate2UI.SelectFromCollection(collection, SR.Get(SRID.CertSelectionDialogTitle), SR.Get(SRID.CertSelectionDialogMessage), X509SelectionFlag.SingleSelection, hwndParent);
if (collection.Count > 0)
{
X509cert = collection[0]; // return the first one
}
}
return X509cert;
}
#region Private Members
//------------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
/// <summary>
/// Predicate for use with List.Exists()
/// </summary>
private class StringMatchPredicate
{
public StringMatchPredicate(String id)
{
_id = id;
}
public bool Match(String id)
{
return (String.CompareOrdinal(_id, id) == 0);
}
private string _id;
}
/// <summary>
/// Verify Parts Exist before signing
/// </summary>
/// <param name="parts"></param>
/// <remarks>This call must be done after the signature Origin has been created to allow for
/// callers to sign an Origin (or it's relationship part) for the first signature in the package.</remarks>
private void VerifyPartsExist(IEnumerable<Uri> parts)
{
// check for missing parts
if (parts != null)
{
foreach (Uri partUri in parts)
{
if (!_container.PartExists(partUri))
{
// delete origin part if it was created and this is the first signature
if (_signatures.Count == 0)
DeleteOriginPart();
throw new ArgumentException(SR.Get(SRID.PartToSignMissing), "parts");
}
}
}
}
/// <summary>
/// Verifies arguments to Sign() method - sub-function to reduce complexity in Sign() logic
/// </summary>
/// <param name="parts"></param>
/// <param name="certificate"></param>
/// <param name="relationshipSelectors"></param>
/// <param name="signatureId"></param>
/// <param name="signatureObjects"></param>
/// <param name="objectReferences"></param>
private void VerifySignArguments(IEnumerable<Uri> parts,
X509Certificate certificate,
IEnumerable<PackageRelationshipSelector> relationshipSelectors,
String signatureId,
IEnumerable<System.Security.Cryptography.Xml.DataObject> signatureObjects,
IEnumerable<System.Security.Cryptography.Xml.Reference> objectReferences)
{
if (certificate == null)
throw new ArgumentNullException("certificate");
// Check for empty collections in order to provide negative feedback as soon as possible.
if (EnumeratorEmptyCheck(parts) && EnumeratorEmptyCheck(relationshipSelectors)
&& EnumeratorEmptyCheck(signatureObjects) && EnumeratorEmptyCheck(objectReferences))
throw new ArgumentException(SR.Get(SRID.NothingToSign));
// check for illegal and/or duplicate id's in signatureObjects
if (signatureObjects != null)
{
List<String> ids = new List<String>();
foreach (DataObject obj in signatureObjects)
{
// ensure they don't duplicate the reserved one
if (String.CompareOrdinal(obj.Id, XTable.Get(XTable.ID.OpcAttrValue)) == 0)
throw new ArgumentException(SR.Get(SRID.SignaturePackageObjectTagMustBeUnique), "signatureObjects");
// check for duplicates
//if (ids.Contains(obj.Id))
if (ids.Exists(new StringMatchPredicate(obj.Id).Match))
throw new ArgumentException(SR.Get(SRID.SignatureObjectIdMustBeUnique), "signatureObjects");
else
ids.Add(obj.Id);
}
}
// ensure id is legal Xml id
if ((signatureId != null) && (signatureId != String.Empty))
{
try
{
// An XSD ID is an NCName that is unique.
System.Xml.XmlConvert.VerifyNCName(signatureId);
}
catch (System.Xml.XmlException xmlException)
{
throw new ArgumentException(SR.Get(SRID.NotAValidXmlIdString, signatureId), "signatureId", xmlException);
}
}
}
/// <summary>
/// Returns true if the given enumerator is null or empty
/// </summary>
/// <param name="enumerable">may be null</param>
/// <returns>true if enumerator is empty or null</returns>
private bool EnumeratorEmptyCheck(System.Collections.IEnumerable enumerable)
{
if (enumerable == null)
return true; // null means empty
// see if it's really a collection as this is more efficient than enumerating
System.Collections.ICollection collection = enumerable as System.Collections.ICollection;
if (collection != null)
{
return (collection.Count == 0);
}
else
{
// not a collection - do things the hard way
foreach (Object o in enumerable)
{
return false; // if we get here - we're not empty
}
return true; // empty
}
}
/// <summary>
/// Remove a signature - helper method
/// </summary>
/// <param name="signatureUri">signature to remove</param>
/// <param name="countOfSignaturesRemaining">number of signatures that will remain
/// after the remove operation. If this count becomes zero, then we can remove the
/// origin part also from the package as there will be no remaining signatures
/// in the package.</param>
/// <remarks>Caller should call Package.Flush() in order to persist changes.</remarks>
private void InternalRemoveSignature(Uri signatureUri, int countOfSignaturesRemaining)
{
Debug.Assert(signatureUri != null);
Debug.Assert(countOfSignaturesRemaining >= 0);
// Remove origin if this operation will have removed the last signature in order to conform with Metro specification.
// This will remove all relationships too so the code in the "else" clause becomes redundant and we can skip it.
if (countOfSignaturesRemaining == 0)
{
DeleteOriginPart();
}
else // there will be at least a single signature left after this remove, so we need to be more delicate in our surgery
{
SafeVisitRelationships(
OriginPart.GetRelationshipsByType(_originToSignatureRelationshipType),
DeleteRelationshipToSignature, signatureUri);
}
// delete the cert (if any) if it's reference count will become zero
SafeVisitRelationships(_container.GetPart(signatureUri).GetRelationshipsByType(CertificatePart.RelationshipType),
DeleteCertificateIfReferenceCountBecomesZeroVisitor);
// delete the signature part
_container.DeletePart(signatureUri);
}
// return true to continue
private delegate bool RelationshipOperation(PackageRelationship r, Object context);
/// <summary>
/// Visit relationships without disturbing the PackageRelationshipCollection iterator
/// </summary>
/// <param name="relationships">collection of relationships to visit</param>
/// <param name="visit">function to call with each relationship in the list</param>
private void SafeVisitRelationships(PackageRelationshipCollection relationships, RelationshipOperation visit)
{
SafeVisitRelationships(relationships, visit, null);
}
/// <summary>
/// Visit relationships without disturbing the PackageRelationshipCollection iterator
/// </summary>
/// <param name="relationships">collection of relationships to visit</param>
/// <param name="visit">function to call with each relationship in the list</param>
/// <param name="context">context object - may be null</param>
private void SafeVisitRelationships(PackageRelationshipCollection relationships, RelationshipOperation visit, Object context)
{
// make a local copy that will not be invalidated by any activity of the visitor function
List<PackageRelationship> relationshipsToVisit = new List<PackageRelationship>(relationships);
// now invoke the delegate for each member
for (int i = 0; i < relationshipsToVisit.Count; i++)
{
// exit if visitor wants us to
if (!visit(relationshipsToVisit[i], context))
break;
}
}
/// <summary>
/// Removes the certificate associated with the given signature if removing the signature would leave the
/// certificate part orphaned.
/// </summary>
private bool DeleteCertificateIfReferenceCountBecomesZeroVisitor(PackageRelationship r, Object context)
{
// don't resolve if external
if (r.TargetMode != TargetMode.Internal)
throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
Uri certificatePartName = PackUriHelper.ResolvePartUri(r.SourceUri, r.TargetUri);
if (CertificatePartReferenceCount(certificatePartName) == 1) // we are part of the calculation so one is the magic number
_container.DeletePart(certificatePartName); // will not throw if part not found
return true;
}
/// <summary>
/// Deletes any relationship that is of the type that relates a Package to the Digital Signature Origin
/// </summary>
/// <param name="r"></param>
/// <param name="context"></param>
/// <returns></returns>
private bool DeleteRelationshipOfTypePackageToOriginVisitor(PackageRelationship r, Object context)
{
Debug.Assert(Uri.Compare(r.SourceUri,
PackUriHelper.PackageRootUri,
UriComponents.SerializationInfoString,
UriFormat.UriEscaped,
StringComparison.Ordinal) == 0,
"Logic Error: This visitor should only be called with relationships from the Package itself");
// don't resolve if external
if (r.TargetMode != TargetMode.Internal)
throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
Uri targetUri = PackUriHelper.ResolvePartUri(r.SourceUri, r.TargetUri);
if (PackUriHelper.ComparePartUri(targetUri, _originPartName) == 0)
_container.DeleteRelationship(r.Id);
return true;
}
/// <summary>
/// Deletes any relationship to the given signature from the signature origin
/// </summary>
/// <param name="r">relationship from origin</param>
/// <param name="signatureUri">signatureUri</param>
/// <returns>true</returns>
private bool DeleteRelationshipToSignature(PackageRelationship r, Object signatureUri)
{
Uri uri = signatureUri as Uri;
Debug.Assert(uri != null, "Improper use of delegate - context must be Uri");
// don't resolve if external
if (r.TargetMode != TargetMode.Internal)
throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
if (PackUriHelper.ComparePartUri(PackUriHelper.ResolvePartUri(r.SourceUri, r.TargetUri), uri) == 0)
{
OriginPart.DeleteRelationship(r.Id); // don't break early in case there are redundant relationships
}
return true;
}
private void DeleteOriginPart()
{
try
{
// remove all relationships of the type "package-to-signature-origin"
SafeVisitRelationships(_container.GetRelationshipsByType(_originRelationshipType),
DeleteRelationshipOfTypePackageToOriginVisitor);
_container.DeletePart(_originPartName);
}
finally
{
// reset state variables
_originPartExists = false;
_originSearchConducted = true;
_originPart = null;
}
}
/// <summary>
/// Lookup the index of the signature object in the _signatures array by the name of the part
/// </summary>
/// <param name="uri">name of the signature part</param>
/// <returns>zero-based index or -1 if not found</returns>
private int GetSignatureIndex(Uri uri)
{
EnsureSignatures();
for (int i = 0; i < _signatures.Count; i++)
{
if (PackUriHelper.ComparePartUri(uri, _signatures[i].SignaturePart.Uri) == 0)
return i;
}
return -1; // not found
}
/// <summary>
/// Counts the number of signatures using the given certificate
/// </summary>
/// <param name="certificatePartUri">certificate to inspect</param>
private int CertificatePartReferenceCount(Uri certificatePartUri)
{
// count the number of signatures that reference this certificate part
int count = 0;
for (int i = 0; i < _signatures.Count; i++)
{
// for each signature, follow it's certificate link (if there) and compare the Uri
if (_signatures[i].GetCertificatePart() != null)
{
// same Uri?
if (PackUriHelper.ComparePartUri(certificatePartUri, _signatures[i].GetCertificatePart().Uri) == 0)
++count;
}
}
return count;
}
/// <summary>
/// Generate guid-based signature name to reduce chances of conflict in merging scenarios
/// </summary>
/// <returns></returns>
private Uri GenerateSignaturePartName()
{
return PackUriHelper.CreatePartUri(new Uri(_defaultSignaturePartNamePrefix +
Guid.NewGuid().ToString(_guidStorageFormatString, (IFormatProvider)null) + _defaultSignaturePartNameExtension, UriKind.Relative));
}
// load signatures from container
private void EnsureSignatures()
{
if (_signatures == null)
{
_signatures = new List<PackageDigitalSignature>();
// no signatures if origin not found
if (OriginPartExists())
{
// find all signatures from this origin (if any)
PackageRelationshipCollection relationships = _originPart.GetRelationshipsByType(
_originToSignatureRelationshipType);
foreach (PackageRelationship r in relationships)
{
// don't resolve if external
if (r.TargetMode != TargetMode.Internal)
throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
Uri signaturePartName = PackUriHelper.ResolvePartUri(_originPart.Uri, r.TargetUri);
// throw if part does not exist
if (!_container.PartExists(signaturePartName))
throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
PackagePart signaturePart = _container.GetPart(signaturePartName);
// ignore future signature types that we do not recognize
if (signaturePart.ValidatedContentType.AreTypeAndSubTypeEqual
(XmlDigitalSignatureProcessor.ContentType))
{
// parse it
PackageDigitalSignature signature = new PackageDigitalSignature(this, signaturePart);
// add to the list
_signatures.Add(signature);
}
}
}
}
}
/// <summary>
/// Looks for part name of Origin by searching from the container root and following the metro origin part relationship
/// </summary>
/// <remarks>side effect of assigning the _originPartName and _originPart if found</remarks>
/// <returns>true if found</returns>
private bool OriginPartExists()
{
// only search once
if (!_originSearchConducted)
{
try
{
Debug.Assert(!_originPartExists, "Logic Error: If OriginPartExists, OriginSearchConducted should be true.");
PackageRelationshipCollection containerRelationships = _container.GetRelationshipsByType(_originRelationshipType);
foreach (PackageRelationship r in containerRelationships)
{
// don't resolve if external
if (r.TargetMode != TargetMode.Internal)
throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
// resolve target (may be relative)
Uri targetUri = PackUriHelper.ResolvePartUri(r.SourceUri, r.TargetUri);
// if part does not exist - we throw
if (!_container.PartExists(targetUri))
throw new FileFormatException(SR.Get(SRID.SignatureOriginNotFound));
PackagePart p = _container.GetPart(targetUri);
// inspect content type - ignore things we don't understand
if (p.ValidatedContentType.AreTypeAndSubTypeEqual(_originPartContentType))
{
// throw if more than one relationship to an origin part that we recognize
if (_originPartExists)
throw new FileFormatException(SR.Get(SRID.MultipleSignatureOrigins));
// overwrite default if some container is using some other name
_originPartName = targetUri;
_originPart = p;
_originPartExists = true;
}
}
}
finally
{
_originSearchConducted = true;
}
}
return _originPartExists;
}
//------------------------------------------------------
//
// Private Properties
//
//------------------------------------------------------
private bool ReadOnly
{
get
{
return (_container.FileOpenAccess == FileAccess.Read);
}
}
private PackagePart OriginPart
{
get
{
if (_originPart == null)
{
if (!OriginPartExists())
{
// add if not found
_originPart = _container.CreatePart(_originPartName, _originPartContentType.ToString());
_container.CreateRelationship(_originPartName, TargetMode.Internal, _originRelationshipType);
}
}
return _originPart;
}
}
//------------------------------------------------------
//
// Private Fields
//
//------------------------------------------------------
private CertificateEmbeddingOption _certificateEmbeddingOption;
private Package _container;
private IntPtr _parentWindow;
private static Uri _defaultOriginPartName = PackUriHelper.CreatePartUri(new Uri("/package/services/digital-signature/origin.psdsor", UriKind.Relative));
private Uri _originPartName = _defaultOriginPartName;
private PackagePart _originPart;
private String _hashAlgorithmString = DefaultHashAlgorithm;
private String _signatureTimeFormat = XmlSignatureProperties.DefaultDateTimeFormat;
private List<PackageDigitalSignature> _signatures;
private Dictionary<String, String> _transformDictionary;
private bool _originSearchConducted; // don't look more than once for Origin part
private bool _originPartExists; // was the part found?
private ReadOnlyCollection<PackageDigitalSignature> _signatureList; // lazy-init cached return value for Signatures property
private static readonly ContentType _originPartContentType = new ContentType("application/vnd.openxmlformats-package.digital-signature-origin");
private static readonly String _guidStorageFormatString = @"N"; // N - simple format without adornments
private static readonly String _defaultHashAlgorithm = SignedXml.XmlDsigSHA256Url;
private static readonly String _originRelationshipType = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/origin";
private static readonly String _originToSignatureRelationshipType = "http://schemas.openxmlformats.org/package/2006/relationships/digital-signature/signature";
private static readonly String _defaultSignaturePartNamePrefix = "/package/services/digital-signature/xml-signature/";
private static readonly String _defaultSignaturePartNameExtension = ".psdsxs";
#endregion Private Members
}
}
|