File: System\ServiceModel\Security\Tokens\DerivedKeySecurityToken.cs
Web Access
Project: src\src\System.ServiceModel.Primitives\src\System.ServiceModel.Primitives.csproj (System.ServiceModel.Primitives)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
 
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.IdentityModel.Tokens;
using System.Security.Cryptography;
using System.Text;
using System.Xml;
using System.Runtime;
 
namespace System.ServiceModel.Security.Tokens
{
    internal sealed class DerivedKeySecurityToken : SecurityToken
    {
        //        public const string DefaultLabel = "WS-SecureConversationWS-SecureConversation";
        private static readonly byte[] s_DefaultLabel = new byte[]
            {
                (byte)'W', (byte)'S', (byte)'-', (byte)'S', (byte)'e', (byte)'c', (byte)'u', (byte)'r', (byte)'e',
                (byte)'C', (byte)'o', (byte)'n', (byte)'v', (byte)'e', (byte)'r', (byte)'s', (byte)'a', (byte)'t', (byte)'i', (byte)'o', (byte)'n',
                (byte)'W', (byte)'S', (byte)'-', (byte)'S', (byte)'e', (byte)'c', (byte)'u', (byte)'r', (byte)'e',
                (byte)'C', (byte)'o', (byte)'n', (byte)'v', (byte)'e', (byte)'r', (byte)'s', (byte)'a', (byte)'t', (byte)'i', (byte)'o', (byte)'n'
            };
 
        public const int DefaultNonceLength = 16;
        public const int DefaultDerivedKeyLength = 32;
 
        // fields are read from in this class, but lack of implemenation means we never assign them yet. 
        private string _id;
        private byte[] _key;
        private string _label;
        private byte[] _nonce;
        private ReadOnlyCollection<SecurityKey> _securityKeys;
 
        internal DerivedKeySecurityToken(int generation, int offset, int length,
            string label, int minNonceLength, SecurityToken tokenToDerive,
            SecurityKeyIdentifierClause tokenToDeriveIdentifier,
            string derivationAlgorithm, string id)
        {
            byte[] nonce = RandomNumberGenerator.GetBytes(minNonceLength);
            Fx.Assert(nonce.Length == minNonceLength, "Returned random bytes for nonce is not the expected length");
            Initialize(id, generation, offset, length, label, nonce, tokenToDerive, tokenToDeriveIdentifier, derivationAlgorithm);
        }
 
        // create from xml
        internal DerivedKeySecurityToken(int generation, int offset, int length,
            string label, byte[] nonce, SecurityToken tokenToDerive,
            SecurityKeyIdentifierClause tokenToDeriveIdentifier, string derivationAlgorithm, string id)
        {
            Initialize(id, generation, offset, length, label, nonce, tokenToDerive, tokenToDeriveIdentifier, derivationAlgorithm, false);
        }
 
        public override string Id
        {
            get { return _id; }
        }
 
        public override DateTime ValidFrom
        {
            get { return TokenToDerive.ValidFrom; }
        }
 
        public override DateTime ValidTo
        {
            get { return TokenToDerive.ValidTo; }
        }
 
        public string KeyDerivationAlgorithm { get; private set; }
 
        public int Generation { get; private set; } = -1;
 
        public string Label
        {
            get { return _label; }
        }
 
        public int Length { get; private set; } = -1;
 
        internal byte[] Nonce
        {
            get { return _nonce; }
        }
 
        public int Offset { get; private set; } = -1;
 
        internal SecurityToken TokenToDerive { get; private set; }
 
        internal SecurityKeyIdentifierClause TokenToDeriveIdentifier { get; private set; }
 
        public override ReadOnlyCollection<SecurityKey> SecurityKeys
        {
            get
            {
                if (_securityKeys == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(SRP.DerivedKeyNotInitialized));
                }
                return _securityKeys;
            }
        }
 
        public byte[] GetKeyBytes()
        {
            return SecurityUtils.CloneBuffer(_key);
        }
 
        public byte[] GetNonce()
        {
            return SecurityUtils.CloneBuffer(_nonce);
        }
 
        internal bool TryGetSecurityKeys(out ReadOnlyCollection<SecurityKey> keys)
        {
            keys = _securityKeys;
            return (keys != null);
        }
 
        public override string ToString()
        {
            StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
            writer.WriteLine("DerivedKeySecurityToken:");
            writer.WriteLine("   Generation: {0}", Generation);
            writer.WriteLine("   Offset: {0}", Offset);
            writer.WriteLine("   Length: {0}", Length);
            writer.WriteLine("   Label: {0}", Label);
            writer.WriteLine("   Nonce: {0}", Convert.ToBase64String(Nonce));
            writer.WriteLine("   TokenToDeriveFrom:");
            using (XmlTextWriter xmlWriter = new XmlTextWriter(writer))
            {
                xmlWriter.Formatting = Formatting.Indented;
                SecurityStandardsManager.DefaultInstance.SecurityTokenSerializer.WriteKeyIdentifierClause(XmlDictionaryWriter.CreateDictionaryWriter(xmlWriter), TokenToDeriveIdentifier);
            }
            return writer.ToString();
        }
 
        private void Initialize(string id, int generation, int offset, int length, string label, byte[] nonce,
    SecurityToken tokenToDerive, SecurityKeyIdentifierClause tokenToDeriveIdentifier, string derivationAlgorithm)
        {
            Initialize(id, generation, offset, length, label, nonce, tokenToDerive, tokenToDeriveIdentifier, derivationAlgorithm, true);
        }
 
        private void Initialize(string id, int generation, int offset, int length, string label, byte[] nonce,
    SecurityToken tokenToDerive, SecurityKeyIdentifierClause tokenToDeriveIdentifier, string derivationAlgorithm,
    bool initializeDerivedKey)
        {
            if (tokenToDerive == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(tokenToDerive));
            }
 
            if (!SecurityUtils.IsSupportedAlgorithm(derivationAlgorithm, tokenToDerive))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentException(SRP.DerivedKeyCannotDeriveFromSecret));
            }
 
            if (length == -1)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(length)));
            }
            if (offset == -1 && generation == -1)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.DerivedKeyPosAndGenNotSpecified);
            }
            if (offset >= 0 && generation >= 0)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.DerivedKeyPosAndGenBothSpecified);
            }
 
            _id = id ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(id));
            _label = label;
            _nonce = nonce ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(nonce));
            Length = length;
            Offset = offset;
            Generation = generation;
            TokenToDerive = tokenToDerive;
            TokenToDeriveIdentifier = tokenToDeriveIdentifier ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(tokenToDeriveIdentifier));
            KeyDerivationAlgorithm = derivationAlgorithm;
 
            if (initializeDerivedKey)
            {
                InitializeDerivedKey(Length);
            }
        }
 
        internal void InitializeDerivedKey(int maxKeyLength)
        {
            if (_key != null)
            {
                return;
            }
            if (Length > maxKeyLength)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.Format(SRP.DerivedKeyLengthTooLong, Length, maxKeyLength));
            }
 
            _key = SecurityUtils.GenerateDerivedKey(TokenToDerive, KeyDerivationAlgorithm,
                (_label != null ? Encoding.UTF8.GetBytes(_label) : s_DefaultLabel), _nonce, Length * 8,
                ((Offset >= 0) ? Offset : Generation * Length));
            if ((_key == null) || (_key.Length == 0))
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SRP.DerivedKeyCannotDeriveFromSecret);
            }
            List<SecurityKey> temp = new List<SecurityKey>(1);
            temp.Add(new InMemorySymmetricSecurityKey(_key, false));
            _securityKeys = temp.AsReadOnly();
        }
 
        internal static void EnsureAcceptableOffset(int offset, int generation, int length, int maxOffset)
        {
            if (offset != -1)
            {
                if (offset > maxOffset)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new MessageSecurityException(SRP.Format(SRP.DerivedKeyTokenOffsetTooHigh, offset, maxOffset)));
                }
            }
            else
            {
                int effectiveOffset = generation * length;
                if ((effectiveOffset < generation && effectiveOffset < length) || effectiveOffset > maxOffset)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperWarning(new MessageSecurityException(SRP.Format(SRP.DerivedKeyTokenGenerationAndLengthTooHigh, generation, length, maxOffset)));
                }
            }
        }
    }
}