File: System\ServiceModel\Security\EncryptedType.cs
Project: ndp\cdf\src\WCF\IdentityModel\System.IdentityModel.csproj (System.IdentityModel)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace System.ServiceModel.Security
{
    using System.IdentityModel;
    using System.IdentityModel.Selectors;
    using System.IdentityModel.Tokens;
    using System.Runtime;
    using System.Runtime.CompilerServices;
    using System.Xml;
    using DictionaryManager = System.IdentityModel.DictionaryManager;
    using ISecurityElement = System.IdentityModel.ISecurityElement;
 
    [TypeForwardedFrom("System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")]
    abstract class EncryptedType : ISecurityElement
    {
        internal static readonly XmlDictionaryString NamespaceUri = XD.XmlEncryptionDictionary.Namespace;
        internal static readonly XmlDictionaryString EncodingAttribute = XD.XmlEncryptionDictionary.Encoding;
        internal static readonly XmlDictionaryString MimeTypeAttribute = XD.XmlEncryptionDictionary.MimeType;
        internal static readonly XmlDictionaryString TypeAttribute = XD.XmlEncryptionDictionary.Type;
        internal static readonly XmlDictionaryString CipherDataElementName = XD.XmlEncryptionDictionary.CipherData;
        internal static readonly XmlDictionaryString CipherValueElementName = XD.XmlEncryptionDictionary.CipherValue;
 
        string encoding;
        EncryptionMethodElement encryptionMethod;
        string id;
        string wsuId;
        SecurityKeyIdentifier keyIdentifier;
        string mimeType;
        EncryptionState state;
        string type;
        SecurityTokenSerializer tokenSerializer;
        bool shouldReadXmlReferenceKeyInfoClause;
 
        protected EncryptedType()
        {
            this.encryptionMethod.Init();
            this.state = EncryptionState.New;
            this.tokenSerializer = new KeyInfoSerializer(false);
        }
 
        public string Encoding
        {
            get
            {
                return this.encoding;
            }
            set
            {
                this.encoding = value;
            }
        }
 
        public string EncryptionMethod
        {
            get
            {
                return this.encryptionMethod.algorithm;
            }
            set
            {
                this.encryptionMethod.algorithm = value;
            }
        }
 
        public XmlDictionaryString EncryptionMethodDictionaryString
        {
            get
            {
                return this.encryptionMethod.algorithmDictionaryString;
            }
            set
            {
                this.encryptionMethod.algorithmDictionaryString = value;
            }
        }
 
        public bool HasId
        {
            get
            {
                return true;
            }
        }
 
        public string Id
        {
            get
            {
                return this.id;
            }
            set
            {
                this.id = value;
            }
        }
 
        // This is set to true on the client side. And this means that when this knob is set to true and the default serializers on the client side fail 
        // to read the KeyInfo clause from the incoming response message from a service; then the ckient should 
        // try to read the keyInfo clause as GenericXmlSecurityKeyIdentifierClause before throwing.
        public bool ShouldReadXmlReferenceKeyInfoClause
        {
            get
            {
                return this.shouldReadXmlReferenceKeyInfoClause;
            }
            set
            {
                this.shouldReadXmlReferenceKeyInfoClause = value;
            }
        }
 
        public string WsuId
        {
            get
            {
                return this.wsuId;
            }
            set
            {
                this.wsuId = value;
            }
        }
 
        public SecurityKeyIdentifier KeyIdentifier
        {
            get
            {
                return this.keyIdentifier;
            }
            set
            {
                this.keyIdentifier = value;
            }
        }
 
        public string MimeType
        {
            get
            {
                return this.mimeType;
            }
            set
            {
                this.mimeType = value;
            }
        }
 
        public string Type
        {
            get
            {
                return this.type;
            }
            set
            {
                this.type = value;
            }
        }
 
        protected abstract XmlDictionaryString OpeningElementName
        {
            get;
        }
 
        protected EncryptionState State
        {
            get
            {
                return this.state;
            }
            set
            {
                this.state = value;
            }
        }
 
        public SecurityTokenSerializer SecurityTokenSerializer
        {
            get
            {
                return this.tokenSerializer;
            }
            set
            {
                this.tokenSerializer = value ?? new KeyInfoSerializer(false);
            }
        }
 
        protected abstract void ForceEncryption();
 
        protected virtual void ReadAdditionalAttributes(XmlDictionaryReader reader)
        {
        }
 
        protected virtual void ReadAdditionalElements(XmlDictionaryReader reader)
        {
        }
 
        protected abstract void ReadCipherData(XmlDictionaryReader reader);
        protected abstract void ReadCipherData(XmlDictionaryReader reader, long maxBufferSize);
 
        public void ReadFrom(XmlReader reader)
        {
            ReadFrom(reader, 0);
        }
 
        public void ReadFrom(XmlDictionaryReader reader)
        {
            ReadFrom(reader, 0);
        }
 
        public void ReadFrom(XmlReader reader, long maxBufferSize)
        {
            ReadFrom(XmlDictionaryReader.CreateDictionaryReader(reader), maxBufferSize);
        }
 
        public void ReadFrom(XmlDictionaryReader reader, long maxBufferSize)
        {
            ValidateReadState();
            reader.MoveToStartElement(OpeningElementName, NamespaceUri);
            this.encoding = reader.GetAttribute(EncodingAttribute, null);
            this.id = reader.GetAttribute(XD.XmlEncryptionDictionary.Id, null) ?? SecurityUniqueId.Create().Value;
            this.wsuId = reader.GetAttribute(XD.XmlEncryptionDictionary.Id, XD.UtilityDictionary.Namespace) ?? SecurityUniqueId.Create().Value;
            this.mimeType = reader.GetAttribute(MimeTypeAttribute, null);
            this.type = reader.GetAttribute(TypeAttribute, null);
            ReadAdditionalAttributes(reader);
            reader.Read();
 
            if (reader.IsStartElement(EncryptionMethodElement.ElementName, NamespaceUri))
            {
                this.encryptionMethod.ReadFrom(reader);
            }
 
            if (this.tokenSerializer.CanReadKeyIdentifier(reader))
            {
                XmlElement xml = null;
                XmlDictionaryReader localReader;
 
                if (this.ShouldReadXmlReferenceKeyInfoClause)
                {
                    // We create the dom only when needed to not affect perf.
                    XmlDocument doc = new XmlDocument();
                    xml = (doc.ReadNode(reader) as XmlElement);
                    localReader = XmlDictionaryReader.CreateDictionaryReader(new XmlNodeReader(xml));
                }
                else
                {
                    localReader = reader;
                }
 
                try
                {
                    this.KeyIdentifier = this.tokenSerializer.ReadKeyIdentifier(localReader);
                }
                catch (Exception e)
                {
                    // In case when the issued token ( custom token) is used as an initiator token; we will fail 
                    // to read the keyIdentifierClause using the plugged in default serializer. So We need to try to read it as an XmlReferencekeyIdentifierClause 
                    // if it is the client side.
 
                    if (Fx.IsFatal(e) || !this.ShouldReadXmlReferenceKeyInfoClause)
                    {
                        throw;
                    }
 
                    this.keyIdentifier = ReadGenericXmlSecurityKeyIdentifier( XmlDictionaryReader.CreateDictionaryReader( new XmlNodeReader(xml)), e);
                }
            }
 
            reader.ReadStartElement(CipherDataElementName, EncryptedType.NamespaceUri);
            reader.ReadStartElement(CipherValueElementName, EncryptedType.NamespaceUri);
            if (maxBufferSize == 0)
                ReadCipherData(reader);
            else
                ReadCipherData(reader, maxBufferSize);
            reader.ReadEndElement(); // CipherValue
            reader.ReadEndElement(); // CipherData
 
            ReadAdditionalElements(reader);
            reader.ReadEndElement(); // OpeningElementName
            this.State = EncryptionState.Read;
        }
 
        private SecurityKeyIdentifier ReadGenericXmlSecurityKeyIdentifier(XmlDictionaryReader localReader, Exception previousException)
        {
            if (!localReader.IsStartElement(XD.XmlSignatureDictionary.KeyInfo, XD.XmlSignatureDictionary.Namespace))
            {
                return null;
            }
 
            localReader.ReadStartElement(XD.XmlSignatureDictionary.KeyInfo, XD.XmlSignatureDictionary.Namespace);
            SecurityKeyIdentifier keyIdentifier = new SecurityKeyIdentifier();
          
            if (localReader.IsStartElement())
            {
                SecurityKeyIdentifierClause clause = null;
                string strId = localReader.GetAttribute(XD.UtilityDictionary.IdAttribute, XD.UtilityDictionary.Namespace);
                XmlDocument doc = new XmlDocument();
                XmlElement keyIdentifierReferenceXml = (doc.ReadNode(localReader) as XmlElement);
                clause = new GenericXmlSecurityKeyIdentifierClause(keyIdentifierReferenceXml);
                if (!string.IsNullOrEmpty(strId))
                    clause.Id = strId;
                keyIdentifier.Add(clause);
            }
 
            if (keyIdentifier.Count == 0)
                throw previousException;
 
            localReader.ReadEndElement();
            return keyIdentifier;
        }
 
        protected virtual void WriteAdditionalAttributes(XmlDictionaryWriter writer, DictionaryManager dictionaryManager)
        {
        }
 
        protected virtual void WriteAdditionalElements(XmlDictionaryWriter writer, DictionaryManager dictionaryManager)
        {
        }
 
        protected abstract void WriteCipherData(XmlDictionaryWriter writer);
 
        public void WriteTo(XmlDictionaryWriter writer, DictionaryManager dictionaryManager)
        {
            if (writer == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("writer");
            }
            ValidateWriteState();
            writer.WriteStartElement(XmlEncryptionStrings.Prefix, this.OpeningElementName, NamespaceUri);
            if (this.id != null && this.id.Length != 0)
            {
                writer.WriteAttributeString(XD.XmlEncryptionDictionary.Id, null, this.Id);
            }
            if (this.type != null)
            {
                writer.WriteAttributeString(TypeAttribute, null, this.Type);
            }
            if (this.mimeType != null)
            {
                writer.WriteAttributeString(MimeTypeAttribute, null, this.MimeType);
            }
            if (this.encoding != null)
            {
                writer.WriteAttributeString(EncodingAttribute, null, this.Encoding);
            }
            WriteAdditionalAttributes(writer, dictionaryManager);
            if (this.encryptionMethod.algorithm != null)
            {
                this.encryptionMethod.WriteTo(writer);
            }
            if (this.KeyIdentifier != null)
            {
                this.tokenSerializer.WriteKeyIdentifier(writer, this.KeyIdentifier);
            }
 
            writer.WriteStartElement(CipherDataElementName, NamespaceUri);
            writer.WriteStartElement(CipherValueElementName, NamespaceUri);
            WriteCipherData(writer);
            writer.WriteEndElement(); // CipherValue
            writer.WriteEndElement(); // CipherData
 
            WriteAdditionalElements(writer, dictionaryManager);
            writer.WriteEndElement(); // OpeningElementName
        }
 
        void ValidateReadState()
        {
            if (this.State != EncryptionState.New)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityMessageSerializationException(SR.GetString(SR.BadEncryptionState)));
            }
        }
 
        void ValidateWriteState()
        {
            if (this.State == EncryptionState.EncryptionSetup)
            {
                ForceEncryption();
            }
            else if (this.State == EncryptionState.New)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityMessageSerializationException(SR.GetString(SR.BadEncryptionState)));
            }
        }
 
        protected enum EncryptionState
        {
            New,
            Read,
            DecryptionSetup,
            Decrypted,
            EncryptionSetup,
            Encrypted
        }
        
        struct EncryptionMethodElement
        {
            internal string algorithm;
            internal XmlDictionaryString algorithmDictionaryString;
            internal static readonly XmlDictionaryString ElementName = XD.XmlEncryptionDictionary.EncryptionMethod;
 
            public void Init()
            {
                this.algorithm = null;
            }
 
            public void ReadFrom(XmlDictionaryReader reader)
            {
                reader.MoveToStartElement(ElementName, XD.XmlEncryptionDictionary.Namespace);
                bool isEmptyElement = reader.IsEmptyElement;
                this.algorithm = reader.GetAttribute(XD.XmlSignatureDictionary.Algorithm, null);
                if (this.algorithm == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new SecurityMessageSerializationException(
                        SR.GetString(SR.RequiredAttributeMissing, XD.XmlSignatureDictionary.Algorithm.Value, ElementName.Value)));
                }
                reader.Read();
                if (!isEmptyElement)
                {
                    while (reader.IsStartElement())
                    {
                        reader.Skip();
                    }
                    reader.ReadEndElement();
                }
            }
 
            public void WriteTo(XmlDictionaryWriter writer)
            {
                writer.WriteStartElement(XmlEncryptionStrings.Prefix, ElementName, XD.XmlEncryptionDictionary.Namespace);
                if (this.algorithmDictionaryString != null)
                {
                    writer.WriteStartAttribute(XD.XmlSignatureDictionary.Algorithm, null);
                    writer.WriteString(this.algorithmDictionaryString);
                    writer.WriteEndAttribute();
                }
                else
                {
                    writer.WriteAttributeString(XD.XmlSignatureDictionary.Algorithm, null, this.algorithm);
                }
                if (this.algorithm == XD.SecurityAlgorithmDictionary.RsaOaepKeyWrap.Value)
                {
                    writer.WriteStartElement(XmlSignatureStrings.Prefix, XD.XmlSignatureDictionary.DigestMethod, XD.XmlSignatureDictionary.Namespace);
                    writer.WriteStartAttribute(XD.XmlSignatureDictionary.Algorithm, null);
                    writer.WriteString(XD.SecurityAlgorithmDictionary.Sha1Digest);
                    writer.WriteEndAttribute();
                    writer.WriteEndElement();
                }
                writer.WriteEndElement(); // EncryptionMethod
            }
        }
    }
}