File: Base\System\IO\Packaging\PackageDigitalSignature.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//
// <copyright file="PackageDigitalSignature.cs" company="Microsoft">
//    Copyright (C) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//  This class represents a PackageDigitalSignature.  It is immutable. 
//
// History:
//  03/22/2004: BruceMac: Initial Implementation
//  01/21/2005: BruceMac: Update for DigSig Security Mitigation DCR
//
//-----------------------------------------------------------------------------
 
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
using System.Security.Cryptography.Xml;     // for Xml Signature classes
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using MS.Internal.IO.Packaging;            // helper classes Certificate, HashStream
using System.Collections.ObjectModel;       // for ReadOnlyCollection<>
using MS.Internal.WindowsBase;
 
namespace System.IO.Packaging
{
    /// <summary>
    /// VerifyResult
    /// </summary>
    public enum VerifyResult : int
    {
        /// <summary>
        /// Verification succeeded
        /// </summary>
        Success,               // signature valid
    
        /// <summary>
        /// Signature was invalid (tampering detected)
        /// </summary>
        InvalidSignature,      // hash incorrect
    
        /// <summary>
        /// Certificate was not embedded in container and caller did not supply one
        /// </summary>
        CertificateRequired,   // no certificate is embedded in container - caller must provide one
    
        /// <summary>
        /// Certificate was invalid (perhaps expired?)
        /// </summary>
        InvalidCertificate,    // certificate problem - verify does not fully verify cert
    
        /// <summary>
        /// PackagePart was missing - signature invalid
        /// </summary>
        ReferenceNotFound,     // signature failed because a part is missing
    
        /// <summary>
        /// Package not signed
        /// </summary>
        NotSigned               // no signatures were found
    }
 
    /// <summary>
    /// PackageDigitalSignature
    /// </summary>
    public class PackageDigitalSignature
    {
        #region Public Members
        //------------------------------------------------------
        //
        //  Public Properties
        //
        //------------------------------------------------------
        /// <summary>
        /// Parts that are covered by this signature
        /// </summary>
        /// <value>read only list</value>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public ReadOnlyCollection<Uri> SignedParts
        {
            get 
            {
                ThrowIfInvalidated();
 
                // wrap in read-only collection to protect against alteration
                if (_signedParts == null)
                    _signedParts = new ReadOnlyCollection<Uri>(_processor.PartManifest);
 
                return _signedParts;
            }
        }
 
        /// <summary>
        /// Relationships that are covered by this signature
        /// </summary>
        /// <value>read only list</value>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public ReadOnlyCollection<PackageRelationshipSelector> SignedRelationshipSelectors
        {
            get
            {
                ThrowIfInvalidated();
 
                // wrap in read-only collection to protect against alteration
                if (_signedRelationshipSelectors == null)
                    _signedRelationshipSelectors = new ReadOnlyCollection<PackageRelationshipSelector>(_processor.RelationshipManifest);
 
                return _signedRelationshipSelectors;
            }
        }
 
        /// <summary>
        /// The part that contains the actual signature - useful for counter-signing scenarios
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public PackagePart SignaturePart
        {
            get
            {
                ThrowIfInvalidated();
 
                return _processor.SignaturePart;
            }
        }
 
        /// <summary>
        /// Certificate of signer embedded in container
        /// </summary>
        /// <value>null if certificate was not embedded</value>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public X509Certificate Signer 
        {
            get
            {
                ThrowIfInvalidated();
 
                return _processor.Signer;
            }
        }
 
        /// <summary>
        /// Time signature was created - not a trusted TimeStamp
        /// </summary>
        /// <value></value>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public DateTime SigningTime
        {
            get
            {
                ThrowIfInvalidated();
 
                return _processor.SigningTime;
            }
        }
 
        /// <summary>
        /// Format of time returned by SigningTime (see PackageDigitalSignatureManager.TimeFormat for details)
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public String TimeFormat
        {
            get
            {
                ThrowIfInvalidated();
 
                return _processor.TimeFormat;
            }
        }
 
        /// <summary>
        /// encrypted hash value
        /// </summary>
        /// <value></value>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public byte[] SignatureValue
        {
            get
            {
                ThrowIfInvalidated();
 
                return _processor.SignatureValue;
            }
        }
 
        /// <summary>
        /// Content Type of signature
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public String SignatureType
        {
            get
            {
                ThrowIfInvalidated();
 
                return XmlDigitalSignatureProcessor.ContentType.ToString();
            }
        }
 
        /// <summary>
        /// Type-specific signature object
        /// </summary>
        /// <remarks>
        /// Provides access to the underlying class that performs the signature type-specific cryptographic
        /// functions and serialization to/from the package part that houses the signature.
        /// </remarks>
        /// <returns>
        /// Returns an object of type System.Security.Cryptography.Xml.Signature.
        /// Future signature types will return objects of different classes.
        /// </returns>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public Signature Signature
        {
            get
            {
                ThrowIfInvalidated();
                return _processor.Signature;
            }
            set
            {
                ThrowIfInvalidated();
                if (value == null)
                    throw new ArgumentNullException("value");
 
                _processor.Signature = value;
            }
        }
 
        /// <summary>
        /// Where is the certificate?
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public CertificateEmbeddingOption CertificateEmbeddingOption
        {
            get
            {
                ThrowIfInvalidated();
 
                if (GetCertificatePart() == null)
                {
                    if (Signer == null)
                    {
                        return CertificateEmbeddingOption.NotEmbedded;
                    }
                    else
                    {
                        return CertificateEmbeddingOption.InSignaturePart;
                    }
                }
                else
                {
                    return CertificateEmbeddingOption.InCertificatePart;
                }
            }
        }
        
        //------------------------------------------------------
        //
        //  Public Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// Returns ordered list of transforms applied to the given part
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public List<String> GetPartTransformList(Uri partName)
        {
            ThrowIfInvalidated();
 
            // no need to clone this for return as it's already a single-use collection
            return _processor.GetPartTransformList(partName);
        }
        
        /// <summary>
        /// Verify
        /// </summary>
        /// <remarks>cannot use this overload with signatures created without embedding their certs</remarks>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        public VerifyResult Verify()
        {
            ThrowIfInvalidated();
 
            if (Signer == null)
                return VerifyResult.CertificateRequired;
 
            return Verify(Signer);
        }
 
        /// <summary>
        /// Verify
        /// </summary>
        /// <param name="signingCertificate">certificate used to create the signature</param>
        /// <returns></returns>
        /// <remarks>Use this overload when the certificate is not embedded in the container at signing time</remarks>
        /// <exception cref="InvalidOperationException">Thrown if associated digital signature has been deleted.</exception>
        ///<SecurityNote>
        ///     Critical: calls X509Certificate2 ctor which LinkDemands
        ///     TreatAsSafe: X509Certificate2 is only used internally and not returned
        ///</SecurityNote> 
        [SecurityCritical, SecurityTreatAsSafe]
        public VerifyResult Verify(X509Certificate signingCertificate)
        {
            ThrowIfInvalidated();
 
            VerifyResult result = VerifyResult.NotSigned;
 
            if (signingCertificate == null)
                throw new ArgumentNullException("signingCertificate");
 
            // Check for part existence
            foreach (Uri partUri in SignedParts)
            {
                // check if they exist
                if (!_manager.Package.PartExists(partUri))
                {
                    return VerifyResult.ReferenceNotFound;
                }
            }
 
            // convert to Ex variant that has more functionality
            X509Certificate2 certificate = signingCertificate as X509Certificate2;
            if (certificate == null)
                certificate = new X509Certificate2(signingCertificate.Handle);
 
            // verify
            if (_processor.Verify(certificate))
                result = VerifyResult.Success;
            else
                result = VerifyResult.InvalidSignature;
 
            return result;
        }
 
        #endregion
 
        #region Internal Members
        //------------------------------------------------------
        //
        //  Internal Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// Constructor for creating a new signature
        /// </summary>
        /// <param name="manager">digital signature manager - to consult for hash, embedding and other options</param>
        /// <param name="processor">digital signature manager - to consult for hash, embedding and other options</param>
        internal PackageDigitalSignature(
            PackageDigitalSignatureManager manager, 
            XmlDigitalSignatureProcessor processor)
        {
            Debug.Assert(processor.PackageSignature == null, "Logic Error: one processor per-signature");
            _manager = manager;
            _processor = processor;
//            _processor.PackageSignature = this;
        }
 
        /// <summary>
        /// Constructor for use when opening an existing signature
        /// </summary>
        /// <param name="manager">digital signature manager - to consult for hash, embedding and other options</param>
        /// <param name="signaturePart">part that houses the signature</param>
        internal PackageDigitalSignature(
            PackageDigitalSignatureManager manager,
            PackagePart signaturePart)
        {
            _manager = manager;
            _processor = new XmlDigitalSignatureProcessor(manager, signaturePart, this);
        }
 
        /// <summary>
        /// This is called when the underlying signature is deleted - it prevents usage of the object
        /// </summary>
        internal void Invalidate()
        {
            _invalid = true;
        }
 
        //------------------------------------------------------
        //
        //  Internal Properties
        //
        //------------------------------------------------------
        /// <summary>
        /// Get certificate part - null if none
        /// </summary>
        internal CertificatePart GetCertificatePart()
        {
            // lazy init
            if (_certificatePart == null && !_alreadyLookedForCertPart)
            {
                PackageRelationshipCollection relationships = SignaturePart.GetRelationshipsByType(
                    CertificatePart.RelationshipType);
                foreach (PackageRelationship relationship in relationships)
                {
                    // don't resolve if external
                    if (relationship.TargetMode != TargetMode.Internal)
                        throw new FileFormatException(SR.Get(SRID.PackageSignatureCorruption));
 
                    Uri resolvedUri = PackUriHelper.ResolvePartUri(SignaturePart.Uri, relationship.TargetUri);
 
                    // don't create if it doesn't exist
                    if (!_manager.Package.PartExists(resolvedUri))
                    {
                        continue;
                    }
 
                    // find the cert
                    _certificatePart = new CertificatePart(_manager.Package, resolvedUri);
                    break;
                }
                _alreadyLookedForCertPart = true;
            }
            
            return _certificatePart;
        }
        
        internal void SetCertificatePart(CertificatePart certificatePart)
        {
            Debug.Assert(certificatePart != null, "Logic Error: Not expecting setting certificate part to null on digital signature");
            _certificatePart = certificatePart;
        }
 
        #endregion
 
        #region Private Methods
 
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// ThrowIfInvalidated - check on each access
        /// </summary>
        private void ThrowIfInvalidated()
        {
            if (_invalid)
                throw new InvalidOperationException(SR.Get(SRID.SignatureDeleted));
        }
 
        #endregion Private Methods
 
        //------------------------------------------------------
        //
        //  Private Fields
        //
        //------------------------------------------------------
        private PackageDigitalSignatureManager                     _manager;
        private XmlDigitalSignatureProcessor                       _processor;
        private CertificatePart                                    _certificatePart;
        private ReadOnlyCollection<Uri>                            _signedParts;
        private ReadOnlyCollection<PackageRelationshipSelector>    _signedRelationshipSelectors;
        private bool                                               _alreadyLookedForCertPart;
        private bool                                               _invalid;   // have we been invalidated?
    }
}