< Summary

Class:Microsoft.Azure.KeyVault.EcKey
Assembly:Microsoft.Azure.KeyVault.Cryptography
File(s):C:\Git\azure-sdk-for-net\sdk\keyvault\Microsoft.Azure.KeyVault.Cryptography\src\EcKey.cs
Covered lines:141
Uncovered lines:45
Coverable lines:186
Total lines:649
Line coverage:75.8% (141 of 186)
Covered branches:53
Total branches:84
Branch coverage:63% (53 of 84)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-73.53%62.2%
get_Kid()-100%100%
.ctor()-0%100%
.ctor(...)-0%100%
.ctor(...)-91.67%80%
.ctor(...)-80%68.75%
ImportNistKey(...)-100%100%
ImportNistPublicKey(...)-100%100%
ImportNistPrivateKey(...)-100%100%
ImportGenericKey(...)-100%100%
ImportGenericPublicKey(...)-100%100%
ImportGenericPrivateKey(...)-100%100%
.ctor(...)-0%0%
Dispose()-100%100%
Dispose(...)-100%100%
get_DefaultEncryptionAlgorithm()-0%100%
get_DefaultKeyWrapAlgorithm()-0%100%
get_DefaultSignatureAlgorithm()-100%100%
DecryptAsync(...)-0%100%
EncryptAsync(...)-0%100%
WrapKeyAsync(...)-0%100%
UnwrapKeyAsync(...)-0%100%
MethodNotSupported(...)-0%100%
SignAsync(...)-71.43%58.33%
VerifyAsync(...)-62.5%50%
IsZero(...)-66.67%66.67%
AlignAndWrite(...)-30%25%
get_Secp256k1Parameters()-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\keyvault\Microsoft.Azure.KeyVault.Cryptography\src\EcKey.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License. See License.txt in the project root for
 3// license information.
 4
 5using System;
 6using System.IO;
 7using System.Security.Cryptography;
 8using System.Text;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using Microsoft.Azure.KeyVault.Core;
 12using Microsoft.Azure.KeyVault.Cryptography;
 13using Microsoft.Azure.KeyVault.Cryptography.Algorithms;
 14
 15#if NETSTANDARD
 16using TaskException = System.Threading.Tasks.Task;
 17#endif
 18
 19namespace Microsoft.Azure.KeyVault
 20{
 21    /// <summary>
 22    /// An elliptic curve key.
 23    /// </summary>
 24    public class EcKey : IKey
 25    {
 26        public const string P256 = "P-256";
 27        public const string P384 = "P-384";
 28        public const string P521 = "P-521";
 29        public const string P256K = "P-256K";
 30
 231        private static readonly string DefaultCurve = P256;
 32
 33        private ECDsa _ecdsa;
 34
 35        /// <summary>
 36        /// Key Identifier
 37        /// </summary>
 2838        public string Kid { get; }
 39
 40        /// <summary>
 41        /// Constructor, creates a P-256 key with a GUID identifier.
 42        /// </summary>
 043        public EcKey() : this( Guid.NewGuid().ToString( "D" ), DefaultCurve )
 44        {
 045        }
 46
 47        /// <summary>
 48        /// Constructor, creates a P-256 key.
 49        /// </summary>
 50        /// <param name="kid">The key identifier to use</param>
 051        public EcKey( string kid ) : this( kid, DefaultCurve )
 52        {
 053        }
 54
 55        /// <summary>
 56        /// Constructor.
 57        /// </summary>
 58        /// <param name="kid">The key identifier to use</param>
 59        /// <param name="curve">The name of elliptic curve to use</param>
 1260        public EcKey( string kid, string curve )
 61        {
 1262            if ( string.IsNullOrWhiteSpace( kid ) )
 063                throw new ArgumentNullException( nameof( kid ) );
 64
 1265            Kid = kid;
 66
 1267            var kcp = new CngKeyCreationParameters();
 1268            kcp.ExportPolicy = CngExportPolicies.AllowPlaintextExport;
 1269            kcp.KeyUsage = CngKeyUsages.Signing;
 70
 71            CngAlgorithm cngAlgo;
 72            switch ( curve )
 73            {
 74                case P256:
 675                    cngAlgo = CngAlgorithm.ECDsaP256;
 676                    DefaultSignatureAlgorithm = Es256.AlgorithmName;
 677                    break;
 78
 79                case P384:
 280                    cngAlgo = CngAlgorithm.ECDsaP384;
 281                    DefaultSignatureAlgorithm = Es384.AlgorithmName;
 282                    break;
 83
 84                case P521:
 285                    cngAlgo = CngAlgorithm.ECDsaP521;
 286                    DefaultSignatureAlgorithm = Es512.AlgorithmName;
 287                    break;
 88
 89                case P256K:
 290                    cngAlgo = CngAlgorithm_Generic_ECDSA;
 291                    kcp.Parameters.Add( new CngProperty( NativeMethods.BCRYPT_ECC_PARAMETERS, Secp256k1Parameters.CurveB
 292                    DefaultSignatureAlgorithm = ES256K.AlgorithmName;
 293                    break;
 94
 95                default:
 096                    throw new ArgumentException( $"Unsupported curve: \"{curve}\"." );
 97            }
 98
 1299            var key = CngKey.Create( cngAlgo, null, kcp );
 12100            _ecdsa = new ECDsaCng( key );
 12101        }
 102
 103        /// <summary>
 104        /// Constructor
 105        /// </summary>
 106        /// <param name="kid">The key identifier to use</param>
 107        /// <param name="curve">The name of elliptic curve to use; supported values are constants described in this clas
 108        /// <param name="x">The value of public point field X.</param>
 109        /// <param name="y">The value of public point field Y.</param>
 110        /// <param name="d">The value of private key D. If null, only the public key operations will be allowed.</param>
 16111        public EcKey( string kid, string curve, byte[] x, byte[] y, byte[] d = null )
 112        {
 16113            if ( string.IsNullOrWhiteSpace( kid ) )
 0114                throw new ArgumentNullException( nameof( kid ) );
 115
 16116            if ( string.IsNullOrWhiteSpace( curve ) )
 0117                throw new ArgumentNullException( nameof( curve ) );
 118
 16119            if ( x == null )
 0120                throw new ArgumentNullException( nameof( x ) );
 121
 16122            if ( y == null )
 0123                throw new ArgumentNullException( nameof( y ) );
 124
 16125            Kid = kid;
 126
 127            CngKey key;
 128
 129            switch ( curve )
 130            {
 131                case P256:
 4132                    key = ImportNistKey( NativeMethods.BCRYPT_ECDSA_PRIVATE_P256_MAGIC, NativeMethods.BCRYPT_ECDSA_PUBLI
 4133                    DefaultSignatureAlgorithm = Es256.AlgorithmName;
 4134                    break;
 135
 136                case P384:
 4137                    key = ImportNistKey( NativeMethods.BCRYPT_ECDSA_PRIVATE_P384_MAGIC, NativeMethods.BCRYPT_ECDSA_PUBLI
 4138                    DefaultSignatureAlgorithm = Es384.AlgorithmName;
 4139                    break;
 140
 141                case P521:
 4142                    key = ImportNistKey( NativeMethods.BCRYPT_ECDSA_PRIVATE_P521_MAGIC, NativeMethods.BCRYPT_ECDSA_PUBLI
 4143                    DefaultSignatureAlgorithm = Es512.AlgorithmName;
 4144                    break;
 145
 146                case P256K:
 4147                    key = ImportGenericKey( Secp256k1Parameters, x, y, d );
 4148                    DefaultSignatureAlgorithm = ES256K.AlgorithmName;
 4149                    break;
 150
 151                default:
 0152                    throw new ArgumentException( $"Invalid curve name: \"{curve}\"", nameof( curve ) );
 153            }
 154
 16155            _ecdsa = new ECDsaCng( key );
 16156        }
 157
 158        private CngKey ImportNistKey( int privateMagic, int publicMagic, int size, byte[] x, byte[] y, byte[] d )
 159        {
 12160            if ( d == null || IsZero( d ) )
 6161                return ImportNistPublicKey( publicMagic, size, x, y );
 162
 6163            return ImportNistPrivateKey( privateMagic, size, x, y, d );
 164        }
 165
 166        private CngKey ImportNistPublicKey( int magic, int sizeInBytes, byte[] x, byte[] y )
 167        {
 168            // Format described by BCRYPT_ECCKEY_BLOB (BCRYPT_ECCPUBLIC_BLOB).
 169            // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375520(v=vs.85).aspx
 170
 6171            var keyBlob = new byte[4 + 4 + sizeInBytes + sizeInBytes];
 6172            using ( var writer = new BinaryWriter( new MemoryStream( keyBlob ) ) )
 173            {
 6174                writer.Write( magic );
 6175                writer.Write( sizeInBytes );
 6176                AlignAndWrite( writer, x, sizeInBytes, nameof( x ) );
 6177                AlignAndWrite( writer, y, sizeInBytes, nameof( y ) );
 6178            }
 179
 6180            return CngKey.Import( keyBlob, CngKeyBlobFormat.EccPublicBlob );
 181        }
 182
 183        private CngKey ImportNistPrivateKey( int magic, int sizeInBytes, byte[] x, byte[] y, byte[] d )
 184        {
 185            // Format described by BCRYPT_ECCKEY_BLOB (BCRYPT_ECCPRIVATE_BLOB).
 186            // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375520(v=vs.85).aspx
 187
 6188            var keyBlob = new byte[4 + 4 + sizeInBytes + sizeInBytes + sizeInBytes];
 6189            using ( var writer = new BinaryWriter( new MemoryStream( keyBlob ) ) )
 190            {
 6191                writer.Write( magic );
 6192                writer.Write( sizeInBytes );
 6193                AlignAndWrite( writer, x, sizeInBytes, nameof( x ) );
 6194                AlignAndWrite( writer, y, sizeInBytes, nameof( y ) );
 6195                AlignAndWrite( writer, d, sizeInBytes, nameof( d ) );
 6196            }
 197
 6198            return CngKey.Import( keyBlob, CngKeyBlobFormat.EccPrivateBlob );
 199        }
 200
 201        private CngKey ImportGenericKey( EcKeyCurveParameters curveParameters, byte[] x, byte[] y, byte[] d )
 202        {
 4203            if ( d == null || IsZero( d ) )
 2204                return ImportGenericPublicKey( curveParameters, x, y );
 205
 2206            return ImportGenericPrivateKey( curveParameters, x, y, d );
 207        }
 208
 209        private CngKey ImportGenericPublicKey( EcKeyCurveParameters curveParameters, byte[] x, byte[] y )
 210        {
 211            /*  struct BCRYPT_ECCFULLKEY_BLOB
 212                {
 213                    ULONG dwMagic;              // BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC or BCRYPT_ECDSA_PRIVATE_GENERIC_MAG
 214                    BCRYPT_ECC_PARAMETER_HEADER curveParameters;
 215                    BYTE Qx[cbFieldLength]      // X-coordinate of the public point.
 216                    BYTE Qy[cbFieldLength]      // Y-coordinate of the public point.
 217                    BYTE d[cbSubgroupOrder]     // Private key. Zero if only public key is required.
 218                }
 219            */
 220
 2221            var keyBlob = new byte[curveParameters.KeyBlobSize];
 2222            using ( var writer = new BinaryWriter( new MemoryStream( keyBlob ) ) )
 223            {
 2224                writer.Write( NativeMethods.BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC );
 2225                curveParameters.WriteTo( writer );
 226
 2227                var size = curveParameters.KeySizeInBytes;
 2228                AlignAndWrite( writer, x, size, nameof( x ) );
 2229                AlignAndWrite( writer, y, size, nameof( y ) );
 2230            }
 231
 2232            return CngKey.Import( keyBlob, CngKeyBlobFormat_Generic_Public );
 233        }
 234
 235        private CngKey ImportGenericPrivateKey( EcKeyCurveParameters curveParameters, byte[] x, byte[] y, byte[] d )
 236        {
 237            /*  struct BCRYPT_ECCFULLKEY_BLOB
 238                {
 239                    ULONG dwMagic;              // BCRYPT_ECDSA_PUBLIC_GENERIC_MAGIC or BCRYPT_ECDSA_PRIVATE_GENERIC_MAG
 240                    BCRYPT_ECC_PARAMETER_HEADER curveParameters;
 241                    BYTE Qx[cbFieldLength]      // X-coordinate of the public point.
 242                    BYTE Qy[cbFieldLength]      // Y-coordinate of the public point.
 243                    BYTE d[cbSubgroupOrder]     // Private key. Zero if only public key is required.
 244                }
 245            */
 246
 2247            var keyBlob = new byte[curveParameters.KeyBlobSize];
 2248            using ( var writer = new BinaryWriter( new MemoryStream( keyBlob ) ) )
 249            {
 2250                writer.Write( NativeMethods.BCRYPT_ECDSA_PRIVATE_GENERIC_MAGIC );
 2251                curveParameters.WriteTo( writer );
 252
 2253                var size = curveParameters.KeySizeInBytes;
 2254                AlignAndWrite( writer, x, size, nameof( x ) );
 2255                AlignAndWrite( writer, y, size, nameof( y ) );
 2256                AlignAndWrite( writer, d, size, nameof( d ) );
 2257            }
 258
 2259            return CngKey.Import( keyBlob, CngKeyBlobFormat_Generic_Private );
 260        }
 261
 262        /// <summary>
 263        /// Constructor.
 264        /// </summary>
 265        /// <param name="kid">The key identifier to use</param>
 266        /// <param name="ecdsa">The ECDsa object for the key</param>
 267        /// <remarks>The ECDsa object is IDisposable, this class will hold a
 268        /// reference to the ECDsa object but will not dispose it, the caller
 269        /// of this constructor is responsible for the lifetime of this
 270        /// parameter.</remarks>
 0271        public EcKey( string kid, ECDsa ecdsa )
 272        {
 0273            if ( string.IsNullOrWhiteSpace( kid ) )
 0274                throw new ArgumentNullException( nameof( kid ) );
 275
 0276            if ( ecdsa == null )
 0277                throw new ArgumentNullException( nameof( ecdsa ) );
 278
 0279            Kid = kid;
 280
 281            // NOTE: ECDsa is disposable and that may lead to runtime errors later.
 0282            _ecdsa = ecdsa;
 0283        }
 284
 285        // Intentionally excluded.
 286        //~EcKey()
 287        //{
 288        //    Dispose( false );
 289        //}
 290
 291        public void Dispose()
 292        {
 4293            Dispose( true );
 4294            GC.SuppressFinalize( this );
 4295        }
 296
 297        protected virtual void Dispose( bool disposing )
 298        {
 299            // Clean up managed resources if Dispose was called
 4300            if ( disposing )
 301            {
 4302                if ( _ecdsa != null )
 303                {
 4304                    _ecdsa.Dispose();
 4305                    _ecdsa = null;
 306                }
 307            }
 4308        }
 309
 310        #region IKey implementation
 311
 0312        public string DefaultEncryptionAlgorithm => null;
 313
 0314        public string DefaultKeyWrapAlgorithm => null;
 315
 88316        public string DefaultSignatureAlgorithm { get; set; }
 317
 318        public Task<byte[]> DecryptAsync( byte[] ciphertext, byte[] iv, byte[] authenticationData = null, byte[] authent
 319        {
 0320            throw MethodNotSupported( nameof( DecryptAsync ) );
 321        }
 322
 323        public Task<Tuple<byte[], byte[], string>> EncryptAsync( byte[] plaintext, byte[] iv = null, byte[] authenticati
 324        {
 0325            throw MethodNotSupported( nameof( EncryptAsync ) );
 326        }
 327
 328        public Task<Tuple<byte[], string>> WrapKeyAsync( byte[] key, string algorithm = RsaOaep.AlgorithmName, Cancellat
 329        {
 0330            throw MethodNotSupported( nameof( WrapKeyAsync ) );
 331        }
 332
 333        public Task<byte[]> UnwrapKeyAsync( byte[] encryptedKey, string algorithm = RsaOaep.AlgorithmName, CancellationT
 334        {
 0335            throw MethodNotSupported( nameof( UnwrapKeyAsync ) );
 336        }
 337
 338        private Exception MethodNotSupported( string methodName )
 339        {
 0340            throw new NotSupportedException( $"Method {methodName} cannot is not supported by EcKey {Kid}." );
 341        }
 342
 343        public Task<Tuple<byte[], string>> SignAsync( byte[] digest, string algorithm, CancellationToken token = default
 344        {
 16345            if ( _ecdsa == null )
 0346                throw new ObjectDisposedException( $"EcKey {Kid} is disposed" );
 347
 16348            if ( algorithm == null )
 16349                algorithm = DefaultSignatureAlgorithm;
 350
 16351            if ( digest == null )
 0352                throw new ArgumentNullException( nameof( digest ) );
 353
 16354            var algo = AlgorithmResolver.Default[algorithm] as AsymmetricSignatureAlgorithm;
 16355            var transform = algo?.CreateSignatureTransform( _ecdsa );
 356
 16357            if ( algo == null || transform == null )
 0358                throw new NotSupportedException( $"algorithm {algorithm} is not supported by EcKey {Kid}" );
 359
 360            try
 361            {
 16362                var result = new Tuple<byte[], string>( transform.Sign( digest ), algorithm );
 363
 16364                return Task.FromResult( result );
 365            }
 366            catch ( Exception ex )
 367            {
 0368                return TaskException.FromException<Tuple<byte[], string>>( ex );
 369            }
 16370        }
 371
 372        public Task<bool> VerifyAsync( byte[] digest, byte[] signature, string algorithm, CancellationToken token = defa
 373        {
 48374            if ( _ecdsa == null )
 0375                throw new ObjectDisposedException( $"EcKey {Kid} is disposed" );
 376
 48377            if ( digest == null )
 0378                throw new ArgumentNullException( nameof( digest ) );
 379
 48380            if ( signature == null )
 0381                throw new ArgumentNullException( nameof( signature ) );
 382
 48383            if ( algorithm == null )
 0384                algorithm = DefaultSignatureAlgorithm;
 385
 48386            var algo = AlgorithmResolver.Default[algorithm] as AsymmetricSignatureAlgorithm;
 48387            var transform = algo?.CreateSignatureTransform( _ecdsa );
 388
 48389            if ( algo == null || transform == null )
 0390                throw new NotSupportedException( $"algorithm {algorithm} is not supported by EcKey {Kid}" );
 391
 392            try
 393            {
 48394                var result = transform.Verify( digest, signature );
 395
 48396                return Task.FromResult( result );
 397            }
 398            catch ( Exception ex )
 399            {
 0400                return TaskException.FromException<bool>( ex );
 401            }
 48402        }
 403
 404        #endregion
 405
 406        #region Utilities
 407
 408        private static bool IsZero( byte[] v )
 409        {
 8410            if ( v == null )
 0411                return true;
 412
 20413            for ( var i = 0; i < v.Length; ++i )
 10414                if ( v[i] != 0 )
 8415                    return false;
 416
 0417            return true;
 418        }
 419
 420        private static void AlignAndWrite( BinaryWriter writer, byte[] bytes, int size, string paramName )
 421        {
 40422            if ( bytes.Length >= size )
 423            {
 0424                for ( var i = 0; i < bytes.Length - size; ++i )
 0425                    if ( bytes[i] != 0 )
 0426                        throw new ArgumentException( $"Value of {paramName} is bigger than allowed for this key." );
 427
 40428                writer.Write( bytes, bytes.Length - size, size );
 40429                return;
 430            }
 431
 0432            for ( var i = bytes.Length; i < size; ++i )
 0433                writer.Write( (byte) 0 );
 0434            writer.Write( bytes );
 0435        }
 436
 2437        private static readonly CngKeyBlobFormat CngKeyBlobFormat_Generic_Public = new CngKeyBlobFormat( NativeMethods.B
 2438        private static readonly CngKeyBlobFormat CngKeyBlobFormat_Generic_Private = new CngKeyBlobFormat( NativeMethods.
 439
 2440        private static readonly CngAlgorithm CngAlgorithm_Generic_ECDSA = new CngAlgorithm( "ECDSA" );
 441
 442        private static EcKeyCurveParameters _secp256k1Parameters;
 443
 444        private static EcKeyCurveParameters Secp256k1Parameters
 445        {
 446            get
 447            {
 6448                if ( _secp256k1Parameters != null )
 4449                    return _secp256k1Parameters;
 450
 2451                return _secp256k1Parameters = new EcKeyCurveParameters
 2452                {
 2453                    // Copied from http://www.secg.org/sec2-v2.pdf and verified to match https://en.bitcoin.it/wiki/Secp
 2454                    CurveType = EcKeyCurveType.PrimeShortWeierstrass,
 2455                    Hash = null,
 2456                    Prime = Convert.FromBase64String( "/////////////////////////////////////v///C8=" ),
 2457                    A = Convert.FromBase64String( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" ),
 2458                    B = Convert.FromBase64String( "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc=" ),
 2459                    Gx = Convert.FromBase64String( "eb5mfvncu6xVoGKVzocLBwKb/NstzijZWfKBWxb4F5g=" ),
 2460                    Gy = Convert.FromBase64String( "SDradyajxGVdpPv8DhEIqP0XtEimhVQZnEfQj/sQ1Lg=" ),
 2461                    Order = Convert.FromBase64String( "/////////////////////rqu3OavSKA7v9JejNA2QUE=" ),
 2462                    Cofactor = Convert.FromBase64String( "AQ==" ),
 2463                    Seed = Convert.FromBase64String( "" ),
 2464                };
 465            }
 466        }
 467
 468        #endregion
 469    }
 470
 471    internal sealed class EcKeyCurveParameters
 472    {
 473        public EcKeyCurveType CurveType { get; set; }
 474        public string Hash { get; set; }
 475        public byte[] Prime { get; set; }
 476        public byte[] A { get; set; }
 477        public byte[] B { get; set; }
 478        public byte[] Gx { get; set; }
 479        public byte[] Gy { get; set; }
 480        public byte[] Order { get; set; }
 481        public byte[] Cofactor { get; set; }
 482        public byte[] Seed { get; set; }
 483
 484        public int KeySizeInBytes => Prime.Length;
 485
 486        public int CurveBlobSize
 487        {
 488            get
 489            {
 490                var cbFieldLength = Prime.Length;
 491                var cbSubgroupOrder = Order.Length;
 492                var cbCofactor = Cofactor.Length;
 493                var cbSeed = Seed.Length;
 494
 495                var size = 7 * sizeof( uint ) + 5 * cbFieldLength + cbSubgroupOrder + cbCofactor + cbSeed;
 496
 497                return size;
 498            }
 499        }
 500
 501        public byte[] CurveBlob
 502        {
 503            get
 504            {
 505                var result = new byte[CurveBlobSize];
 506                using ( var writer = new BinaryWriter( new MemoryStream( result ) ) )
 507                    WriteTo( writer );
 508                return result;
 509            }
 510        }
 511
 512        public int KeyBlobSize
 513        {
 514            get
 515            {
 516                var cbFieldLength = Prime.Length;
 517                var size = sizeof( uint ) + CurveBlobSize + 3 * cbFieldLength;
 518                return size;
 519            }
 520        }
 521
 522        public override string ToString()
 523        {
 524            var sb = new StringBuilder();
 525            sb.AppendLine( $"new {nameof( EcKeyCurveParameters )}" );
 526            sb.AppendLine( "{" );
 527            sb.AppendLine( $"    {nameof( CurveType )} = {nameof( EcKeyCurveType )}.{CurveType}," );
 528            var hashDesc = Hash == null ? "null" : $"\"{Hash}\"";
 529            sb.AppendLine( $"    {nameof( Hash )} = {hashDesc}," );
 530            sb.AppendLine( $"    {nameof( Prime )} = {GetBytesDesc( Prime )}," );
 531            sb.AppendLine( $"    {nameof( A )} = {GetBytesDesc( A )}," );
 532            sb.AppendLine( $"    {nameof( B )} = {GetBytesDesc( B )}," );
 533            sb.AppendLine( $"    {nameof( Gx )} = {GetBytesDesc( Gx )}," );
 534            sb.AppendLine( $"    {nameof( Gy )} = {GetBytesDesc( Gy )}," );
 535            sb.AppendLine( $"    {nameof( Order )} = {GetBytesDesc( Order )}," );
 536            sb.AppendLine( $"    {nameof( Cofactor )} = {GetBytesDesc( Cofactor )}," );
 537            sb.AppendLine( $"    {nameof( Seed )} = {GetBytesDesc( Seed )}," );
 538            sb.AppendLine( "}" );
 539            return sb.ToString();
 540        }
 541
 542        private static string GetBytesDesc( byte[] bytes )
 543        {
 544            if ( bytes == null )
 545                return "null";
 546            var base64 = Convert.ToBase64String( bytes );
 547            return $"Convert.FromBase64String(\"{base64}\")";
 548        }
 549
 550        public void WriteTo( BinaryWriter writer )
 551        {
 552            /*
 553                struct BCRYPT_ECC_PARAMETER_HEADER
 554                {
 555                    ULONG                   dwVersion;              //Version of the structure
 556                    ECC_CURVE_TYPE_ENUM     dwCurveType;            //Supported curve types.
 557                    ECC_CURVE_ALG_ID_ENUM   dwCurveGenerationAlgId; //For X.592 verification purposes, if we include See
 558                    ULONG                   cbFieldLength;          //Byte length of the fields P, A, B, X, Y.
 559                    ULONG                   cbSubgroupOrder;        //Byte length of the subgroup.
 560                    ULONG                   cbCofactor;             //Byte length of cofactor of G in E.
 561                    ULONG                   cbSeed;                 //Byte length of the seed used to generate the curve
 562                    //P[cbFieldLength]              Prime specifying the base field.
 563                    //A[cbFieldLength]              Coefficient A of the equation y^2 = x^3 + A*x + B mod p
 564                    //B[cbFieldLength]              Coefficient B of the equation y^2 = x^3 + A*x + B mod p
 565                    //Gx[cbFieldLength]             X-coordinate of the base point.
 566                    //Gy[cbFieldLength]             Y-coordinate of the base point.
 567                    //n[cbSubgroupOrder]            Order of the group generated by G = (x,y)
 568                    //h[cbCofactor]                 Cofactor of G in E.
 569                    //S[cbSeed]                     Seed of the curve.
 570                }
 571             */
 572
 573            var cbFieldLength = Prime.Length;
 574            var cbSubgroupOrder = Order.Length;
 575            var cbCofactor = Cofactor.Length;
 576            var cbSeed = Seed.Length;
 577
 578            writer.Write( 1 ); // version
 579            writer.Write( ToCurveType( CurveType ) );
 580            writer.Write( ToCurveGenerationAlgId( Hash ) );
 581            writer.Write( cbFieldLength );
 582            writer.Write( cbSubgroupOrder );
 583            writer.Write( cbCofactor );
 584            writer.Write( cbSeed );
 585
 586            AlignAndWrite( writer, Prime, cbFieldLength, nameof( Prime ) );
 587            AlignAndWrite( writer, A, cbFieldLength, nameof( A ) );
 588            AlignAndWrite( writer, B, cbFieldLength, nameof( B ) );
 589            AlignAndWrite( writer, Gx, cbFieldLength, nameof( Gx ) );
 590            AlignAndWrite( writer, Gy, cbFieldLength, nameof( Gy ) );
 591            AlignAndWrite( writer, Order, cbSubgroupOrder, nameof( Order ) );
 592            AlignAndWrite( writer, Cofactor, cbCofactor, nameof( Cofactor ) );
 593            AlignAndWrite( writer, Seed, cbSeed, nameof( Seed ) );
 594        }
 595
 596        private static int ToCurveType( EcKeyCurveType type )
 597        {
 598            switch ( type )
 599            {
 600                case EcKeyCurveType.PrimeMontgomery:
 601                    return NativeMethods.BCRYPT_ECC_PRIME_MONTGOMERY_CURVE;
 602
 603                case EcKeyCurveType.PrimeShortWeierstrass:
 604                    return NativeMethods.BCRYPT_ECC_PRIME_SHORT_WEIERSTRASS_CURVE;
 605
 606                case EcKeyCurveType.PrimeTwistedEdwards:
 607                    return NativeMethods.BCRYPT_ECC_PRIME_TWISTED_EDWARDS_CURVE;
 608
 609                default:
 610                    throw new InvalidOperationException( $"Unsupported curve type: {type}" );
 611            }
 612        }
 613
 614        private static int ToCurveGenerationAlgId( string hash )
 615        {
 616            if ( !string.IsNullOrWhiteSpace( hash ) )
 617                throw new InvalidOperationException( $"Unsupported curve generation hash algorithm: {hash}" );
 618            return NativeMethods.BCRYPT_NO_CURVE_GENERATION_ALG_ID;
 619        }
 620
 621        private static void AlignAndWrite( BinaryWriter writer, byte[] bytes, int size, string paramName )
 622        {
 623            if ( bytes.Length >= size )
 624            {
 625                for ( var i = 0; i < bytes.Length - size; ++i )
 626                    if ( bytes[i] != 0 )
 627                        throw new ArgumentException( $"Value of {paramName} is bigger than allowed for this curve." );
 628
 629                writer.Write( bytes, bytes.Length - size, size );
 630                return;
 631            }
 632
 633            for ( var i = bytes.Length; i < size; ++i )
 634                writer.Write( (byte) 0 );
 635
 636            writer.Write( bytes );
 637        }
 638    }
 639
 640    internal enum EcKeyCurveType
 641    {
 642        Characteristic2,
 643        Implicit,
 644        Named,
 645        PrimeMontgomery,
 646        PrimeShortWeierstrass,
 647        PrimeTwistedEdwards
 648    }
 649}