File: System\Security\Cryptography\CapiHelper.DSA.Shared.cs
Web Access
Project: src\src\libraries\System.Security.Cryptography\src\System.Security.Cryptography.csproj (System.Security.Cryptography)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Diagnostics;
using System.IO;
 
namespace System.Security.Cryptography
{
    internal static partial class CapiHelper
    {
        private const int DSS_Q_LEN = 20;
 
        internal const int DSS_MAGIC = 0x31535344;           // Encoding of "DSS1"
        internal const int DSS_PRIVATE_MAGIC = 0x32535344;   // Encoding of "DSS2"
        internal const int DSS_PUB_MAGIC_VER3 = 0x33535344;  // Encoding of "DSS3"
        internal const int DSS_PRIV_MAGIC_VER3 = 0x34535344; // Encoding of "DSS4"
 
        /// <summary>
        /// Helper for DsaCryptoServiceProvider.ImportParameters()
        /// </summary>
        internal static byte[] ToKeyBlob(this DSAParameters dsaParameters)
        {
            // Validate the DSA structure first
            // P and Q are required. Q is a 160 bit divisor of P-1.
            if (dsaParameters.P == null || dsaParameters.P.Length == 0 || dsaParameters.Q == null || dsaParameters.Q.Length != DSS_Q_LEN)
                throw GetBadDataException();
 
            // G is required. G is an element of Z_p
            if (dsaParameters.G == null || dsaParameters.G.Length != dsaParameters.P.Length)
                throw GetBadDataException();
 
            // If J is present, it should be less than the size of P: J = (P-1) / Q
            // This is only a sanity check. Not doing it here is not really an issue as CAPI will fail.
            if (dsaParameters.J != null && dsaParameters.J.Length >= dsaParameters.P.Length)
                throw GetBadDataException();
 
            // Y is present for V3 DSA key blobs, Y = g^j mod P
            if (dsaParameters.Y != null && dsaParameters.Y.Length != dsaParameters.P.Length)
                throw GetBadDataException();
 
            // The seed is always a 20 byte array
            if (dsaParameters.Seed != null && dsaParameters.Seed.Length != 20)
                throw GetBadDataException();
 
            bool isPrivate = (dsaParameters.X != null && dsaParameters.X.Length > 0);
 
            // The private key should be the same length as Q
            if (isPrivate && dsaParameters.X!.Length != DSS_Q_LEN)
                throw GetBadDataException();
 
            uint bitLenP = (uint)dsaParameters.P.Length * 8;
            uint bitLenJ = dsaParameters.J == null ? 0 : (uint)dsaParameters.J.Length * 8;
 
            using (var ms = new MemoryStream())
            using (var bw = new BinaryWriter(ms))
            {
                // Write out the BLOBHEADER
                bool isV3;
                WriteKeyBlobHeader(dsaParameters, bw, isPrivate, out isV3);
 
                // Write out the DSA key
                if (isV3)
                {
                    // We need to build a key blob (DSSPUBKEY_VER3 or DSSPRIVKEY_VER3) as follows:
                    //  DWORD           magic
                    //  DWORD           bitlenP
                    //  DWORD           bitlenQ
                    //  DWORD           bitlenJ
                    //  DWORD           bitlenX (if private)
                    //  DWORD           counter (DSSSEED)
                    //  BYTE[20]        seed (DSSSEED)
                    //  BYTE[lenP]      P
                    //  BYTE[lenQ]      Q
                    //  BYTE[lenP]      G
                    //  BYTE[lenJ]      J (optional)
                    //  BYTE[lenP]      Y
                    //  BYTE[lenX]      X (if private)
 
                    bw.Write((int)(isPrivate ? DSS_PRIV_MAGIC_VER3 : DSS_PUB_MAGIC_VER3));
                    bw.Write((uint)(bitLenP));
                    bw.Write((uint)(dsaParameters.Q.Length * 8));
                    bw.Write((uint)(bitLenJ));
 
                    if (isPrivate)
                    {
                        bw.Write((uint)dsaParameters.X!.Length * 8);
                    }
 
                    WriteDSSSeed(dsaParameters, bw);
 
                    bw.WriteReversed(dsaParameters.P);
                    bw.WriteReversed(dsaParameters.Q);
                    bw.WriteReversed(dsaParameters.G);
 
                    if (bitLenJ != 0)
                    {
                        bw.WriteReversed(dsaParameters.J!);
                    }
 
                    bw.WriteReversed(dsaParameters.Y!);
 
                    if (isPrivate)
                    {
                        bw.WriteReversed(dsaParameters.X!);
                    }
                }
                else
                {
                    // We need to build a key blob as follows:
                    //  DWORD           magic (DSSPUBKEY)
                    //  DWORD           bitlen (DSSPUBKEY)
                    //  BYTE[len]       P
                    //  BYTE[DSS_Q_LEN] Q
                    //  BYTE[len]       G
                    //  BYTE[20]        X (if private)
                    //  BYTE[len]       Y (if public)
                    //  DWORD           counter (DSSSEED)
                    //  BYTE[20]        seed (DSSSEED)
 
                    bw.Write((int)(isPrivate ? DSS_PRIVATE_MAGIC : DSS_MAGIC));
                    bw.Write((uint)(bitLenP));
                    bw.WriteReversed(dsaParameters.P);
                    bw.WriteReversed(dsaParameters.Q);
                    bw.WriteReversed(dsaParameters.G);
 
                    if (isPrivate)
                    {
                        bw.WriteReversed(dsaParameters.X!);
                    }
                    else
                    {
                        Debug.Assert(dsaParameters.Y != null);
                        bw.WriteReversed(dsaParameters.Y);
                    }
 
                    WriteDSSSeed(dsaParameters, bw);
                }
 
                bw.Flush();
                byte[] key = ms.ToArray();
                return key;
            }
        }
 
        /// <summary>
        /// Helper for DSACryptoServiceProvider.ExportParameters()
        /// </summary>
        internal static DSAParameters ToDSAParameters(this byte[] cspBlob, bool includePrivateParameters, byte[]? cspPublicBlob)
        {
            try
            {
                using (var ms = new MemoryStream(cspBlob))
                using (var br = new BinaryReader(ms))
                {
                    byte bVersion;
                    ReadKeyBlobHeader(br, out bVersion);
 
                    DSAParameters dsaParameters = default;
 
                    if (bVersion > 2)
                    {
                        Debug.Assert(cspPublicBlob == null);
 
                        // We need to read a key blob (DSSPUBKEY_VER3 or DSSPRIVKEY_VER3) as follows:
                        //  DWORD           magic
                        //  DWORD           bitlenP
                        //  DWORD           bitlenQ
                        //  DWORD           bitlenJ
                        //  DWORD           bitlenX (if private)
                        //  DWORD           counter (DSSSEED)
                        //  BYTE[20]        seed (DSSSEED)
                        //  BYTE[lenP]      P
                        //  BYTE[lenQ]      Q
                        //  BYTE[lenP]      G
                        //  BYTE[lenJ]      J (optional)
                        //  BYTE[lenP]      Y
                        //  BYTE[lenX]      X (if private)
 
                        br.ReadInt32(); // Expected to be DSS_PUB_MAGIC_VER3 or DSS_PRIV_MAGIC_VER3
                        int lenP = (br.ReadInt32() + 7) / 8;
                        int lenQ = (br.ReadInt32() + 7) / 8;
                        int lenJ = (br.ReadInt32() + 7) / 8;
 
                        int lenX = 0;
                        if (includePrivateParameters)
                        {
                            lenX = (br.ReadInt32() + 7) / 8;
                        }
 
                        ReadDSSSeed(dsaParameters, br, true);
 
                        dsaParameters.P = br.ReadReversed(lenP);
                        dsaParameters.Q = br.ReadReversed(lenQ);
                        dsaParameters.G = br.ReadReversed(lenP);
 
                        if (lenJ > 0)
                        {
                            dsaParameters.J = br.ReadReversed(lenJ);
                        }
 
                        dsaParameters.Y = br.ReadReversed(lenP);
 
                        if (includePrivateParameters)
                        {
                            dsaParameters.X = br.ReadReversed(lenX);
                        }
                    }
                    else
                    {
                        // We need to read a key blob as follows:
                        //  DWORD           magic (DSSPUBKEY)
                        //  DWORD           bitlen (DSSPUBKEY)
                        //  BYTE[len]       P
                        //  BYTE[DSS_Q_LEN] Q
                        //  BYTE[len]       G
                        //  BYTE[20]        X (if private)
                        //  BYTE[len]       Y (if public)
                        //  DWORD           counter (DSSSEED)
                        //  BYTE[20]        seed (DSSSEED)
 
                        br.ReadInt32();    // Expected to be DSS_MAGIC or DSS_PRIVATE_MAGIC
                        int len = (br.ReadInt32() + 7) / 8;
                        dsaParameters.P = br.ReadReversed(len);
                        dsaParameters.Q = br.ReadReversed(DSS_Q_LEN);
                        dsaParameters.G = br.ReadReversed(len);
 
                        long keyLocation = 0;
                        if (includePrivateParameters)
                        {
                            // Save the position of the stream for later access to Y.
                            keyLocation = br.BaseStream.Position;
                            dsaParameters.X = br.ReadReversed(20);
                        }
                        else
                            dsaParameters.Y = br.ReadReversed(len);
 
                        ReadDSSSeed(dsaParameters, br, false);
 
                        if (includePrivateParameters)
                        {
                            // If a previous call to CAPI returned a v2 private blob, which was then passed
                            // to ImportCspBlob(byte[] keyBlob) under Unix then that is not supported.
                            // Only Unix calls ToDSAParameters from ImportCspBlob; Windows imports directly via CAPI.
                            // This can only happen if a v2 private blob was obtained directly through
                            // CAPI and saved away for later use, because exporting a private blob with ExportCspBlob
                            // will always export a v3 blob which contains both public and private keys.
                            if (cspPublicBlob == null)
                                throw new CryptographicUnexpectedOperationException();
 
                            // Since DSSPUBKEY is used for either public or private key, we got X
                            // but not Y. To get Y, use the public key blob.
                            using (var msPublicBlob = new MemoryStream(cspPublicBlob))
                            using (var brPublicBlob = new BinaryReader(msPublicBlob))
                            {
                                brPublicBlob.BaseStream.Position = keyLocation;
                                dsaParameters.Y = brPublicBlob.ReadReversed(len);
                            }
                        }
                    }
 
                    return dsaParameters;
                }
            }
            catch (EndOfStreamException)
            {
                // For compat reasons, we throw an E_FAIL CrytoException if CAPI returns a smaller blob than expected.
                // For compat reasons, we ignore the extra bits if the CAPI returns a larger blob than expected.
                throw GetEFailException();
            }
        }
 
        private static void ReadKeyBlobHeader(BinaryReader br, out byte bVersion)
        {
            // The format of BLOBHEADER (or PUBLICKEYSTRUC):
            //  BYTE   bType
            //  BYTE   bVersion
            //  WORD   reserved
            //  ALG_ID aiKeyAlg
 
            br.ReadByte();    // BLOBHEADER.bType: Expected to be 0x6 (PUBLICKEYBLOB) or 0x7 (PRIVATEKEYBLOB), though there's no check for backward compat reasons.
            bVersion = br.ReadByte();      // BLOBHEADER.bVersion: Expected to be 0x2 or 0x3, though there's no check for backward compat reasons.
            br.BaseStream.Position += sizeof(ushort); // BLOBHEADER.wReserved
            int algId = br.ReadInt32();    // BLOBHEADER.aiKeyAlg
            if (algId != CALG_DSS_SIGN)
                throw new PlatformNotSupportedException();
        }
 
        private static void WriteKeyBlobHeader(DSAParameters dsaParameters, BinaryWriter bw, bool isPrivate, out bool isV3)
        {
            // Write out the BLOBHEADER (or PUBLICKEYSTRUC).
            isV3 = false;
 
            // If Y is present and this is a private key,
            // or Y and J are present and this is a public key, this should be a v3 blob.
            byte version = BLOBHEADER_CURRENT_BVERSION;
            if (((dsaParameters.Y != null) && isPrivate) ||
                ((dsaParameters.Y != null) && (dsaParameters.J != null)))
            {
                Debug.Assert(dsaParameters.Y.Length > 0);
                isV3 = true;
                version = 0x3;
            }
            bw.Write((byte)(isPrivate ? PRIVATEKEYBLOB : PUBLICKEYBLOB));  // BLOBHEADER.bType
            bw.Write((byte)version);                                       // BLOBHEADER.bVersion
            bw.Write((ushort)0);                                           // BLOBHEADER.wReserved
            bw.Write((int)CapiHelper.CALG_DSS_SIGN);                       // BLOBHEADER.aiKeyAlg
        }
 
        private static void ReadDSSSeed(DSAParameters dsaParameters, BinaryReader br, bool isV3)
        {
            bool hasSeed;
            int counter = br.ReadInt32();
 
            if (isV3)
            {
                hasSeed = (unchecked((uint)counter != 0xFFFFFFFF));
            }
            else
            {
                // This compares > 0 for backwards compatibility, and not just 0xFFFFFFFF
                hasSeed = (counter > 0); // Does not include 0xFFFFFFFF which is signed value of -1
            }
 
            if (hasSeed)
            {
                dsaParameters.Counter = counter;
                dsaParameters.Seed = br.ReadReversed(20);
            }
            else
            {
                dsaParameters.Counter = 0;
                dsaParameters.Seed = null;
                br.BaseStream.Position += 20; // Advance past seed[20]
            }
        }
 
        private static void WriteDSSSeed(DSAParameters dsaParameters, BinaryWriter bw)
        {
            if (dsaParameters.Seed == null || dsaParameters.Seed.Length == 0)
            {
                bw.Write(0xFFFFFFFF); // counter
 
                // seed[20] needs to be all 0xFF
                for (int i = 0; i < 20; i += sizeof(uint))
                {
                    bw.Write(0xFFFFFFFF);
                }
            }
            else
            {
                bw.Write((int)dsaParameters.Counter);
                bw.WriteReversed(dsaParameters.Seed);
            }
        }
    }
}