File: System\IdentityModel\Psha1DerivedKeyGenerator.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.Runtime;
using System.Security.Cryptography;
using System.ServiceModel;
 
namespace System.IdentityModel
{
    internal sealed class Psha1DerivedKeyGenerator
    {
        private byte[] _key;
 
        public Psha1DerivedKeyGenerator(byte[] key)
        {
            _key = key ?? throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(key));
        }
 
        public byte[] GenerateDerivedKey(byte[] label, byte[] nonce, int derivedKeySize, int position)
        {
            if (label == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(label));
            }
            if (nonce == null)
            {
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull(nameof(nonce));
            }
            ManagedPsha1 dkcp = new ManagedPsha1(_key, label, nonce);
            return dkcp.GetDerivedKey(derivedKeySize, position);
        }
 
        // private class to do the real work
        // Note: Though named ManagedPsha1, this works for both fips and non-fips compliance
        private sealed class ManagedPsha1
        {
            private byte[] _aValue;
            private byte[] _buffer;
            private byte[] _chunk;
            private KeyedHashAlgorithm _hmac;
            private int _index;
            private int _position;
            private byte[] _secret;
            private byte[] _seed;
 
            // assume arguments are already validated
            public ManagedPsha1(byte[] secret, byte[] label, byte[] seed)
            {
                _secret = secret;
                _seed = Fx.AllocateByteArray(checked(label.Length + seed.Length));
                label.CopyTo(_seed, 0);
                seed.CopyTo(_seed, label.Length);
 
                _aValue = _seed;
                _chunk = new byte[0];
                _index = 0;
                _position = 0;
                _hmac = CryptoHelper.NewHmacSha1KeyedHashAlgorithm(secret);
 
                _buffer = Fx.AllocateByteArray(checked(_hmac.HashSize / 8 + _seed.Length));
            }
 
            public byte[] GetDerivedKey(int derivedKeySize, int position)
            {
                if (derivedKeySize < 0)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(derivedKeySize), SRP.ValueMustBeNonNegative));
                }
                if (_position > position)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new ArgumentOutOfRangeException(nameof(position), SRP.Format(SRP.ValueMustBeInRange, 0, _position)));
                }
 
                // Seek to the desired position in the pseudo-random stream.
                while (_position < position)
                {
                    GetByte();
                }
                int sizeInBytes = derivedKeySize / 8;
                byte[] derivedKey = new byte[sizeInBytes];
                for (int i = 0; i < sizeInBytes; i++)
                {
                    derivedKey[i] = GetByte();
                }
                return derivedKey;
            }
 
            private byte GetByte()
            {
                if (_index >= _chunk.Length)
                {
                    // Calculate A(i) = HMAC_SHA1(secret, A(i-1)).
                    _hmac.Initialize();
                    _aValue = _hmac.ComputeHash(_aValue);
                    // Calculate P_SHA1(secret, seed)[j] = HMAC_SHA1(secret, A(j+1) || seed).
                    _aValue.CopyTo(_buffer, 0);
                    _seed.CopyTo(_buffer, _aValue.Length);
                    _hmac.Initialize();
                    _chunk = _hmac.ComputeHash(_buffer);
                    _index = 0;
                }
                _position++;
                return _chunk[_index++];
            }
        }
    }
}