File: System\ServiceModel\Security\ReceiveSecurityHeaderElementManager.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//----------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace System.ServiceModel.Security
{
    using System.IdentityModel.Tokens;
    using System.Runtime;
    using System.ServiceModel;
    using System.ServiceModel.Diagnostics;
    using System.Xml;
    using ISignatureReaderProvider = System.IdentityModel.ISignatureReaderProvider;
    using ISignatureValueSecurityElement = System.IdentityModel.ISignatureValueSecurityElement;
    using SignedXml = System.IdentityModel.SignedXml;
    using System.Collections.Generic;
 
    sealed class ReceiveSecurityHeaderElementManager : ISignatureReaderProvider
    {
        const int InitialCapacity = 8;
        readonly ReceiveSecurityHeader securityHeader;
        ReceiveSecurityHeaderEntry[] elements;
        int count;
        readonly string[] headerIds;
        string[] predecryptionHeaderIds;
        string bodyId;
        string bodyContentId;
        bool isPrimaryTokenSigned = false;
 
        public ReceiveSecurityHeaderElementManager(ReceiveSecurityHeader securityHeader)
        {
            this.securityHeader = securityHeader;
            this.elements = new ReceiveSecurityHeaderEntry[InitialCapacity];
            if (securityHeader.RequireMessageProtection)
            {
                this.headerIds = new string[securityHeader.ProcessedMessage.Headers.Count];
            }
        }
 
        public int Count
        {
            get { return this.count; }
        }
 
        public bool IsPrimaryTokenSigned
        {
            get { return this.isPrimaryTokenSigned; }
            set { this.isPrimaryTokenSigned = value; }
        }
 
        public void AppendElement(
            ReceiveSecurityHeaderElementCategory elementCategory, object element,
            ReceiveSecurityHeaderBindingModes bindingMode, string id, TokenTracker supportingTokenTracker)
        {
            if (id != null)
            {
                VerifyIdUniquenessInSecurityHeader(id);
            }
            EnsureCapacityToAdd();
            this.elements[this.count++].SetElement(elementCategory, element, bindingMode, id, false, null, supportingTokenTracker);
        }
 
        public void AppendSignature(SignedXml signedXml)
        {
            AppendElement(ReceiveSecurityHeaderElementCategory.Signature, signedXml,
                ReceiveSecurityHeaderBindingModes.Unknown, signedXml.Id, null);
        }
 
        public void AppendReferenceList(ReferenceList referenceList)
        {
            AppendElement(ReceiveSecurityHeaderElementCategory.ReferenceList, referenceList,
                ReceiveSecurityHeaderBindingModes.Unknown, null, null);
        }
 
        public void AppendEncryptedData(EncryptedData encryptedData)
        {
            AppendElement(ReceiveSecurityHeaderElementCategory.EncryptedData, encryptedData,
                ReceiveSecurityHeaderBindingModes.Unknown, encryptedData.Id, null);
        }
 
        public void AppendSignatureConfirmation(ISignatureValueSecurityElement signatureConfirmationElement)
        {
            AppendElement(ReceiveSecurityHeaderElementCategory.SignatureConfirmation, signatureConfirmationElement,
                ReceiveSecurityHeaderBindingModes.Unknown, signatureConfirmationElement.Id, null);
        }
 
        public void AppendTimestamp(SecurityTimestamp timestamp)
        {
            AppendElement(ReceiveSecurityHeaderElementCategory.Timestamp, timestamp,
                ReceiveSecurityHeaderBindingModes.Unknown, timestamp.Id, null);
        }
 
        public void AppendSecurityTokenReference(SecurityKeyIdentifierClause strClause, string strId)
        {
            if (!String.IsNullOrEmpty(strId))
            {
                VerifyIdUniquenessInSecurityHeader(strId);
                AppendElement(ReceiveSecurityHeaderElementCategory.SecurityTokenReference, strClause, ReceiveSecurityHeaderBindingModes.Unknown, strId, null);
            }
        }
 
        public void AppendToken(SecurityToken token, ReceiveSecurityHeaderBindingModes mode, TokenTracker supportingTokenTracker)
        {
            AppendElement(ReceiveSecurityHeaderElementCategory.Token, token,
                mode, token.Id, supportingTokenTracker);
        }
 
        public void EnsureAllRequiredSecurityHeaderTargetsWereProtected()
        {
            Fx.Assert(this.securityHeader.RequireMessageProtection, "security header protection checks should only be done for message security");
            ReceiveSecurityHeaderEntry entry;
            for (int i = 0; i < this.count; i++)
            {
                GetElementEntry(i, out entry);
                if (!entry.signed)
                {
                    switch (entry.elementCategory)
                    {
                        case ReceiveSecurityHeaderElementCategory.Timestamp:
                        case ReceiveSecurityHeaderElementCategory.SignatureConfirmation:
                            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                                new MessageSecurityException(SR.GetString(SR.RequiredSecurityHeaderElementNotSigned, entry.elementCategory, entry.id)));
                        case ReceiveSecurityHeaderElementCategory.Token:
                            switch (entry.bindingMode)
                            {
                                case ReceiveSecurityHeaderBindingModes.Signed:
                                case ReceiveSecurityHeaderBindingModes.SignedEndorsing:
                                case ReceiveSecurityHeaderBindingModes.Basic:
                                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                                        new MessageSecurityException(SR.GetString(SR.RequiredSecurityTokenNotSigned, entry.element, entry.bindingMode)));
                            }
                            break;
                    }
                }
                
                if (!entry.encrypted)
                {
                    if (entry.elementCategory == ReceiveSecurityHeaderElementCategory.Token &&
                        entry.bindingMode == ReceiveSecurityHeaderBindingModes.Basic)
                    {
                        throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(
                            new MessageSecurityException(SR.GetString(SR.RequiredSecurityTokenNotEncrypted, entry.element, entry.bindingMode)));
                    }
                }
            }
        }
 
        void EnsureCapacityToAdd()
        {
            if (this.count == this.elements.Length)
            {
                ReceiveSecurityHeaderEntry[] newElements = new ReceiveSecurityHeaderEntry[this.elements.Length * 2];
                Array.Copy(this.elements, 0, newElements, 0, this.count);
                this.elements = newElements;
            }
        }
 
        public object GetElement(int index)
        {
            Fx.Assert(0 <= index && index < this.count, "");
            return this.elements[index].element;
        }
 
        public T GetElement<T>(int index) where T : class
        {
            Fx.Assert(0 <= index && index < this.count, "");
            return (T) this.elements[index].element;
        }
 
        public void GetElementEntry(int index, out ReceiveSecurityHeaderEntry element)
        {
            Fx.Assert(0 <= index && index < this.count, "index out of range");
            element = this.elements[index];
        }
 
        public ReceiveSecurityHeaderElementCategory GetElementCategory(int index)
        {
            Fx.Assert(0 <= index && index < this.count, "index out of range");
            return this.elements[index].elementCategory;
        }
 
        public void GetPrimarySignature(out XmlDictionaryReader reader, out string id)
        {
            ReceiveSecurityHeaderEntry entry;
            for (int i = 0; i < this.count; i++)
            {
                GetElementEntry(i, out entry);
                if (entry.elementCategory == ReceiveSecurityHeaderElementCategory.Signature &&
                    entry.bindingMode == ReceiveSecurityHeaderBindingModes.Primary)
                {
                    reader = GetReader(i, false);
                    id = entry.id;
                    return;
                }
            }
            reader = null;
            id = null;
            return;
        }
 
        internal XmlDictionaryReader GetReader(int index, bool requiresEncryptedFormReader)
        {
            Fx.Assert(0 <= index && index < this.count, "index out of range");
            if (!requiresEncryptedFormReader)
            {
                byte[] decryptedBuffer = this.elements[index].decryptedBuffer;
                if (decryptedBuffer != null)
                {
                    return this.securityHeader.CreateDecryptedReader(decryptedBuffer);
                }
            }
            XmlDictionaryReader securityHeaderReader = this.securityHeader.CreateSecurityHeaderReader();
            securityHeaderReader.ReadStartElement();
            for (int i = 0; securityHeaderReader.IsStartElement() && i < index; i++)
            {
                securityHeaderReader.Skip();
            }
            return securityHeaderReader;
        }
 
        public XmlDictionaryReader GetSignatureVerificationReader(string id, bool requiresEncryptedFormReaderIfDecrypted)
        {
            ReceiveSecurityHeaderEntry entry;
            for (int i = 0; i < this.count; i++)
            {
                GetElementEntry(i, out entry);
                bool encryptedForm = entry.encrypted && requiresEncryptedFormReaderIfDecrypted;
                bool isSignedToken = (entry.bindingMode == ReceiveSecurityHeaderBindingModes.Signed) || (entry.bindingMode == ReceiveSecurityHeaderBindingModes.SignedEndorsing);
                if (entry.MatchesId(id, encryptedForm))
                {
                    SetSigned(i);
                    if (!this.IsPrimaryTokenSigned)
                    {
                        this.IsPrimaryTokenSigned = entry.bindingMode == ReceiveSecurityHeaderBindingModes.Primary && entry.elementCategory == ReceiveSecurityHeaderElementCategory.Token;
                    }
                    return GetReader(i, encryptedForm);
                }                
                else if (entry.MatchesId(id, isSignedToken))
                {
                    SetSigned(i);
                    if (!this.IsPrimaryTokenSigned)
                    {
                        this.IsPrimaryTokenSigned = entry.bindingMode == ReceiveSecurityHeaderBindingModes.Primary && entry.elementCategory == ReceiveSecurityHeaderElementCategory.Token;
                    }
                    return GetReader(i, isSignedToken);
                }
            }
            return null;
        }
 
        void OnDuplicateId(string id)
        {
            throw TraceUtility.ThrowHelperError(
                new MessageSecurityException(SR.GetString(SR.DuplicateIdInMessageToBeVerified, id)), this.securityHeader.SecurityVerifiedMessage);
        }
 
        public void SetBindingMode(int index, ReceiveSecurityHeaderBindingModes bindingMode)
        {
            Fx.Assert(0 <= index && index < this.count, "index out of range");
            this.elements[index].bindingMode = bindingMode;
        }
 
        public void SetElement(int index, object element)
        {
            Fx.Assert(0 <= index && index < this.count, "");
            this.elements[index].element = element;
        }
 
        public void ReplaceHeaderEntry(int index, ReceiveSecurityHeaderEntry element)
        {
            Fx.Assert(0 <= index && index < this.count, "");
            this.elements[index] = element;
        }
 
        public void SetElementAfterDecryption(
            int index,
            ReceiveSecurityHeaderElementCategory elementCategory, object element,
            ReceiveSecurityHeaderBindingModes bindingMode, string id, byte[] decryptedBuffer, TokenTracker supportingTokenTracker)
        {
            Fx.Assert(0 <= index && index < this.count, "index out of range");
            Fx.Assert(this.elements[index].elementCategory == ReceiveSecurityHeaderElementCategory.EncryptedData, "Replaced item must be EncryptedData");
            if (id != null)
            {
                VerifyIdUniquenessInSecurityHeader(id);
            }
            this.elements[index].PreserveIdBeforeDecryption();
            this.elements[index].SetElement(elementCategory, element, bindingMode, id, true, decryptedBuffer, supportingTokenTracker);
        }
 
        public void SetSignatureAfterDecryption(int index, SignedXml signedXml, byte[] decryptedBuffer)
        {
            SetElementAfterDecryption(index, ReceiveSecurityHeaderElementCategory.Signature,
                                      signedXml, ReceiveSecurityHeaderBindingModes.Unknown, signedXml.Id, decryptedBuffer, null);
        }
 
        public void SetSignatureConfirmationAfterDecryption(int index, ISignatureValueSecurityElement signatureConfirmationElement, byte[] decryptedBuffer)
        {
            SetElementAfterDecryption(index, ReceiveSecurityHeaderElementCategory.SignatureConfirmation,
                                      signatureConfirmationElement, ReceiveSecurityHeaderBindingModes.Unknown, signatureConfirmationElement.Id, decryptedBuffer, null);
        }
 
        internal void SetSigned(int index)
        {
            Fx.Assert(0 <= index && index < this.count, "");
            this.elements[index].signed = true;
            if (this.elements[index].supportingTokenTracker != null)
            {
                this.elements[index].supportingTokenTracker.IsSigned = true;
            }
        }
 
        public void SetTimestampSigned(string id)
        {
            for (int i = 0; i < this.count; i++)
            {
                if (this.elements[i].elementCategory == ReceiveSecurityHeaderElementCategory.Timestamp &&
                    this.elements[i].id == id)
                {
                    SetSigned(i);
                }
            }
        }
 
        public void SetTokenAfterDecryption(int index, SecurityToken token, ReceiveSecurityHeaderBindingModes mode, byte[] decryptedBuffer, TokenTracker supportingTokenTracker)
        {
            SetElementAfterDecryption(index, ReceiveSecurityHeaderElementCategory.Token, token, mode, token.Id, decryptedBuffer, supportingTokenTracker);
        }
 
        internal bool TryGetTokenElementIndexFromStrId(string strId, out int index)
        {
            index = -1;
            SecurityKeyIdentifierClause strClause = null;
            for (int position = 0; position < this.Count; position++)
            {
                if (this.GetElementCategory(position) == ReceiveSecurityHeaderElementCategory.SecurityTokenReference)
                {
                    strClause = this.GetElement(position) as SecurityKeyIdentifierClause;
                    if (strClause.Id == strId)
                        break;
                }
            }
 
            if (strClause == null)
                return false;
 
            for (int position = 0; position < this.Count; position++)
            {
                if (this.GetElementCategory(position) == ReceiveSecurityHeaderElementCategory.Token)
                {
                    SecurityToken token = this.GetElement(position) as SecurityToken;
                    if (token.MatchesKeyIdentifierClause(strClause))
                    {
                        index = position;
                        return true;
                    }
                }
            }
 
            return false;
        }
 
        public void VerifyUniquenessAndSetBodyId(string id)
        {
            if (id != null)
            {
                VerifyIdUniquenessInSecurityHeader(id);
                VerifyIdUniquenessInMessageHeadersAndBody(id, this.headerIds.Length);
                this.bodyId = id;
            }
        }
 
        public void VerifyUniquenessAndSetBodyContentId(string id)
        {
            if (id != null)
            {
                VerifyIdUniquenessInSecurityHeader(id);
                VerifyIdUniquenessInMessageHeadersAndBody(id, this.headerIds.Length);
                this.bodyContentId = id;
            }
        }
 
        public void VerifyUniquenessAndSetDecryptedHeaderId(string id, int headerIndex)
        {
            if (id != null)
            {
                VerifyIdUniquenessInSecurityHeader(id);
                VerifyIdUniquenessInMessageHeadersAndBody(id, headerIndex);
                if (this.predecryptionHeaderIds == null)
                {
                    this.predecryptionHeaderIds = new string[headerIds.Length];
                }
                this.predecryptionHeaderIds[headerIndex] = this.headerIds[headerIndex];
                this.headerIds[headerIndex] = id;
            }
        }
 
        public void VerifyUniquenessAndSetHeaderId(string id, int headerIndex)
        {
            if (id != null)
            {
                VerifyIdUniquenessInSecurityHeader(id);
                VerifyIdUniquenessInMessageHeadersAndBody(id, headerIndex);
                this.headerIds[headerIndex] = id;
            }
        }
 
        void VerifyIdUniquenessInHeaderIdTable(string id, int headerCount, string[] headerIdTable)
        {
            for (int i = 0; i < headerCount; i++)
            {
                if (headerIdTable[i] == id)
                {
                    OnDuplicateId(id);
                }
            }
        }
 
        void VerifyIdUniquenessInSecurityHeader(string id)
        {
            Fx.Assert(id != null, "Uniqueness should only be tested for non-empty ids");
            for (int i = 0; i < this.count; i++)
            {
                if (this.elements[i].id == id || this.elements[i].encryptedFormId == id)
                {
                    OnDuplicateId(id);
                }
            }
        }
 
        void VerifyIdUniquenessInMessageHeadersAndBody(string id, int headerCount)
        {
            Fx.Assert(id != null, "Uniqueness should only be tested for non-empty ids");
            VerifyIdUniquenessInHeaderIdTable(id, headerCount, this.headerIds);
            if (this.predecryptionHeaderIds != null)
            {
                VerifyIdUniquenessInHeaderIdTable(id, headerCount, this.predecryptionHeaderIds);
            }
            if (this.bodyId == id || this.bodyContentId == id)
            {
                OnDuplicateId(id);
            }
        }
 
        XmlDictionaryReader ISignatureReaderProvider.GetReader(object callbackContext)
        {
            int index = (int)callbackContext;
            Fx.Assert(index < this.Count, "Invalid Context provided.");
            return GetReader(index, false);
        }
 
        public void VerifySignatureConfirmationWasFound()
        {
            ReceiveSecurityHeaderEntry entry;
            for (int i = 0; i < this.count; i++)
            {
                GetElementEntry(i, out entry);
                if (entry.elementCategory == ReceiveSecurityHeaderElementCategory.SignatureConfirmation)
                {
                    return;
                }
            }
 
            throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new MessageSecurityException(SR.GetString(SR.SignatureConfirmationWasExpected)));
        }
 
    }
}