File: System\IdentityModel\EnvelopedSignatureReader.cs
Project: ndp\cdf\src\WCF\IdentityModel\System.IdentityModel.csproj (System.IdentityModel)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
namespace System.IdentityModel
{
    using System;
    using System.IdentityModel.Selectors;
    using System.IdentityModel.Tokens;
    using System.Security.Cryptography;
    using System.Xml;
 
    /// <summary>
    /// Wraps a reader pointing to a enveloped signed XML and provides
    /// a reader that can be used to read the content without having to
    /// process the signature. The Signature is automatically validated
    /// when the last element of the envelope is read.
    /// </summary>
    public sealed class EnvelopedSignatureReader : DelegatingXmlDictionaryReader
    {
        bool _automaticallyReadSignature;
        DictionaryManager _dictionaryManager;
        int _elementCount;
        bool _resolveIntrinsicSigningKeys;
        bool _requireSignature;
        SigningCredentials _signingCredentials;
        SecurityTokenResolver _signingTokenResolver;
        SignedXml _signedXml;
        SecurityTokenSerializer _tokenSerializer;
        WrappedReader _wrappedReader;
        bool _disposed;
 
        /// <summary>
        /// Initializes an instance of <see cref="EnvelopedSignatureReader"/>
        /// </summary>
        /// <param name="reader">Reader pointing to the enveloped signed XML.</param>
        /// <param name="securityTokenSerializer">Token Serializer to resolve the signing token.</param>
        public EnvelopedSignatureReader(XmlReader reader, SecurityTokenSerializer securityTokenSerializer)
            : this(reader, securityTokenSerializer, null)
        {
        }
 
        /// <summary>
        /// Initializes an instance of <see cref="EnvelopedSignatureReader"/>
        /// </summary>
        /// <param name="reader">Reader pointing to the enveloped signed XML.</param>
        /// <param name="securityTokenSerializer">Token Serializer to deserialize the KeyInfo of the Signature.</param>
        /// <param name="signingTokenResolver">Token Resolver to resolve the signing token.</param>
        /// <exception cref="ArgumentNullException">One of the input parameter is null.</exception>
        public EnvelopedSignatureReader(XmlReader reader, SecurityTokenSerializer securityTokenSerializer, SecurityTokenResolver signingTokenResolver)
            : this(reader, securityTokenSerializer, signingTokenResolver, true, true, true)
        {
        }
 
        /// <summary>
        /// Initializes an instance of <see cref="EnvelopedSignatureReader"/>
        /// </summary>
        /// <param name="reader">Reader pointing to the enveloped signed XML.</param>
        /// <param name="securityTokenSerializer">Token Serializer to deserialize the KeyInfo of the Signature.</param>
        /// <param name="signingTokenResolver">Token Resolver to resolve the signing token.</param>
        /// <param name="requireSignature">The value indicates whether the signature is optional.</param>
        /// <param name="automaticallyReadSignature">This value indicates if the Signature should be read 
        /// when the Signature element is encountered or allow the caller to read the Signature manually.</param>
        /// <param name="resolveIntrinsicSigningKeys">A value indicating if intrinsic signing keys should be resolved.</param>
        public EnvelopedSignatureReader(XmlReader reader, SecurityTokenSerializer securityTokenSerializer, SecurityTokenResolver signingTokenResolver, bool requireSignature, bool automaticallyReadSignature, bool resolveIntrinsicSigningKeys)
        {
            if (reader == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("reader");
            }
            if (securityTokenSerializer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("securityTokenSerializer");
            }
 
            _automaticallyReadSignature = automaticallyReadSignature;
            _dictionaryManager = new DictionaryManager();
            _tokenSerializer = securityTokenSerializer;
            _requireSignature = requireSignature;
            _signingTokenResolver = signingTokenResolver ?? EmptySecurityTokenResolver.Instance;
            _resolveIntrinsicSigningKeys = resolveIntrinsicSigningKeys;
 
            XmlDictionaryReader dictionaryReader = XmlDictionaryReader.CreateDictionaryReader(reader);
            _wrappedReader = new WrappedReader(dictionaryReader);
 
            base.InitializeInnerReader(_wrappedReader);
        }
 
        void OnEndOfRootElement()
        {
            if (null == _signedXml)
            {
                if (_requireSignature)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                        new CryptographicException(SR.GetString(SR.ID3089)));
                }
            }
            else
            {
                ResolveSigningCredentials();
                _signedXml.StartSignatureVerification(_signingCredentials.SigningKey);
                _wrappedReader.XmlTokens.SetElementExclusion(XD.XmlSignatureDictionary.Signature.Value, XD.XmlSignatureDictionary.Namespace.Value);
                WifSignedInfo signedInfo = _signedXml.Signature.SignedInfo as WifSignedInfo;
                _signedXml.EnsureDigestValidity(signedInfo[0].ExtractReferredId(), _wrappedReader);
                _signedXml.CompleteSignatureVerification();                
            }
        }
 
        /// <summary>
        /// Returns the SigningCredentials used in the signature after the 
        /// envelope is consumed and when the signature is validated.
        /// </summary>
        public SigningCredentials SigningCredentials
        {
            get
            {
                return _signingCredentials;
            }
        }
 
        /// <summary>
        /// Gets a XmlBuffer of the envelope that was enveloped signed.
        /// The buffer is available after the XML has been read and
        /// signature validated.
        /// </summary>
        internal XmlTokenStream XmlTokens
        {
            get
            {
                return _wrappedReader.XmlTokens.Trim();
            }
        }
 
        /// <summary>
        /// Overrides the base Read method. Checks if the end of the envelope is reached and 
        /// validates the signature if requireSignature is enabled. If the reader gets
        /// positioned on a Signature element the whole signature is read in if automaticallyReadSignature
        /// is enabled.
        /// </summary>
        /// <returns>true if the next node was read successfully; false if there are no more nodes</returns>
        public override bool Read()
        {
            if ((base.NodeType == XmlNodeType.Element) && (!base.IsEmptyElement))
            {
                _elementCount++;
            }
 
            if (base.NodeType == XmlNodeType.EndElement)
            {
                _elementCount--;
                if (_elementCount == 0)
                {
                    OnEndOfRootElement();
                }
            }
 
            bool result = base.Read();
            if (_automaticallyReadSignature
                && (_signedXml == null)
                && result
                && base.InnerReader.IsLocalName(XD.XmlSignatureDictionary.Signature)
                && base.InnerReader.IsNamespaceUri(XD.XmlSignatureDictionary.Namespace))
            {
                ReadSignature();
            }
 
            return result;
        }
 
        void ReadSignature()
        {
            _signedXml = new SignedXml(new WifSignedInfo(_dictionaryManager), _dictionaryManager, _tokenSerializer);
            _signedXml.TransformFactory = ExtendedTransformFactory.Instance;
 
            _signedXml.ReadFrom(_wrappedReader);
 
            if (_signedXml.Signature.SignedInfo.ReferenceCount != 1)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new CryptographicException(SR.GetString(SR.ID3057)));
            }
        }
 
        void ResolveSigningCredentials()
        {
            if (_signedXml.Signature == null || _signedXml.Signature.KeyIdentifier == null || _signedXml.Signature.KeyIdentifier.Count == 0)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.ID3276)));
            }
 
            SecurityKey signingKey = null;
            WifSignedInfo signedInfo = _signedXml.Signature.SignedInfo as WifSignedInfo;
            if (!_signingTokenResolver.TryResolveSecurityKey(_signedXml.Signature.KeyIdentifier[0], out signingKey))
            {
                if (_resolveIntrinsicSigningKeys && _signedXml.Signature.KeyIdentifier.CanCreateKey)
                {
                    if (_signedXml.Signature.KeyIdentifier.Count < 2 || LocalAppContextSwitches.ReturnMultipleSecurityKeyIdentifierClauses)
                    {
                        signingKey = _signedXml.Signature.KeyIdentifier.CreateKey();
                        _signingCredentials = new SigningCredentials(signingKey, _signedXml.Signature.SignedInfo.SignatureMethod, signedInfo[0].DigestMethod, _signedXml.Signature.KeyIdentifier);
                        return;
                    }
                    else
                    {
                        foreach (var clause in _signedXml.Signature.KeyIdentifier)
                        {
                            if (clause.CanCreateKey)
                            {
                                _signingCredentials = new SigningCredentials(clause.CreateKey(), _signedXml.Signature.SignedInfo.SignatureMethod, signedInfo[0].DigestMethod, new SecurityKeyIdentifier(clause));
                                return;
                            }
                        }
 
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SR.GetString(SR.KeyIdentifierCannotCreateKey)));
                    }
                }
                else
                {
                    //
                    // we cannot find the signing key to verify the signature
                    //
                    EncryptedKeyIdentifierClause encryptedKeyClause;
                    if (_signedXml.Signature.KeyIdentifier.TryFind<EncryptedKeyIdentifierClause>(out encryptedKeyClause))
                    {
                        //
                        // System.IdentityModel.Tokens.EncryptedKeyIdentifierClause.ToString() does not print out 
                        // very good information except the cipher data in this case. We have worked around that
                        // by using the token serializer to serialize the key identifier clause again.
                        //
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                                new SignatureVerificationFailedException(
                                SR.GetString(SR.ID4036, XmlUtil.SerializeSecurityKeyIdentifier(_signedXml.Signature.KeyIdentifier, _tokenSerializer))));
                    }
                    else
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                                new SignatureVerificationFailedException(SR.GetString(SR.ID4037, _signedXml.Signature.KeyIdentifier.ToString())));
                    }
 
                }
            }
 
            if (_signedXml.Signature.KeyIdentifier.Count < 2 || LocalAppContextSwitches.ReturnMultipleSecurityKeyIdentifierClauses)
                _signingCredentials = new SigningCredentials(signingKey, _signedXml.Signature.SignedInfo.SignatureMethod, signedInfo[0].DigestMethod, _signedXml.Signature.KeyIdentifier);
            else
                _signingCredentials = new SigningCredentials(signingKey, _signedXml.Signature.SignedInfo.SignatureMethod, signedInfo[0].DigestMethod, new SecurityKeyIdentifier(_signedXml.Signature.KeyIdentifier[0]));
        }
 
        /// <summary>
        /// Reads the signature if the reader is currently positioned at a Signature element.
        /// </summary>
        /// <returns>true if the signature was successfully read else false.</returns>
        /// <remarks>Does not move the reader when returning false.</remarks>
        public bool TryReadSignature()
        {
            if (IsStartElement(XD.XmlSignatureDictionary.Signature, XD.XmlSignatureDictionary.Namespace))
            {
                ReadSignature();
                return true;
            }
            return false;
        }
 
        #region IDisposable Members
 
        /// <summary>
        /// Releases the unmanaged resources used by the System.IdentityModel.Protocols.XmlSignature.EnvelopedSignatureReader and optionally
        /// releases the managed resources.
        /// </summary>
        /// <param name="disposing">
        /// True to release both managed and unmanaged resources; false to release only unmanaged resources.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
 
            if (_disposed)
            {
                return;
            }
 
            if (disposing)
            {
                //
                // Free all of our managed resources
                //
 
                if (_wrappedReader != null)
                {
                    _wrappedReader.Close();
                    _wrappedReader = null;
                }
            }
 
            // Free native resources, if any.
 
            _disposed = true;
        }
 
        #endregion
    }
}