File: src\libraries\Common\src\System\Security\Cryptography\DSAKeyFormatHelper.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.Formats.Asn1;
using System.Numerics;
using System.Security.Cryptography.Asn1;
 
namespace System.Security.Cryptography
{
    internal static class DSAKeyFormatHelper
    {
        private static readonly string[] s_validOids =
        {
            Oids.Dsa,
        };
 
        internal static void ReadDsaPrivateKey(
            ReadOnlyMemory<byte> xBytes,
            in AlgorithmIdentifierAsn algId,
            out DSAParameters ret)
        {
            if (!algId.Parameters.HasValue)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            BigInteger x;
 
            try
            {
                ReadOnlySpan<byte> xSpan = AsnDecoder.ReadIntegerBytes(
                    xBytes.Span,
                    AsnEncodingRules.DER,
                    out int consumed);
 
                if (consumed != xBytes.Length)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
 
                // Force a positive interpretation because Windows sometimes writes negative numbers.
                x = new BigInteger(xSpan, isUnsigned: true, isBigEndian: true);
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }
 
            DssParms parms = DssParms.Decode(algId.Parameters.Value, AsnEncodingRules.BER);
 
            // Sanity checks from FIPS 186-4 4.1/4.2.  Since FIPS 186-5 withdrew DSA/DSS
            // these will never change again.
            //
            // This technically allows a non-standard combination of 1024-bit P and 256-bit Q,
            // but that will get filtered out by the underlying provider.
            // These checks just prevent obviously bad data from wasting work on reinterpretation.
            if (parms.P.Sign < 0 ||
                parms.Q.Sign < 0 ||
                !IsValidPLength(parms.P.GetBitLength()) ||
                !IsValidQLength(parms.Q.GetBitLength()) ||
                parms.G <= 1 ||
                parms.G >= parms.P ||
                x <= 1 ||
                x >= parms.Q)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            ret = new DSAParameters
            {
                P = parms.P.ToByteArray(isUnsigned: true, isBigEndian: true),
                Q = parms.Q.ToByteArray(isUnsigned: true, isBigEndian: true),
            };
 
            ret.G = parms.G.ExportKeyParameter(ret.P.Length);
            ret.X = x.ExportKeyParameter(ret.Q.Length);
 
            // The public key is not contained within the format, calculate it.
            BigInteger y = BigInteger.ModPow(parms.G, x, parms.P);
            ret.Y = y.ExportKeyParameter(ret.P.Length);
        }
 
        internal static void ReadDsaPublicKey(
            ReadOnlyMemory<byte> yBytes,
            in AlgorithmIdentifierAsn algId,
            out DSAParameters ret)
        {
            if (!algId.Parameters.HasValue)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            BigInteger y;
 
            try
            {
                y = AsnDecoder.ReadInteger(
                    yBytes.Span,
                    AsnEncodingRules.DER,
                    out int consumed);
 
                if (consumed != yBytes.Length)
                {
                    throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
                }
            }
            catch (AsnContentException e)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding, e);
            }
 
            DssParms parms = DssParms.Decode(algId.Parameters.Value, AsnEncodingRules.BER);
 
            // Sanity checks from FIPS 186-4 4.1/4.2.  Since FIPS 186-5 withdrew DSA/DSS
            // these will never change again.
            //
            // This technically allows a non-standard combination of 1024-bit P and 256-bit Q,
            // but that will get filtered out by the underlying provider.
            // These checks just prevent obviously bad data from wasting work on reinterpretation.
            if (parms.P.Sign < 0 ||
                parms.Q.Sign < 0 ||
                !IsValidPLength(parms.P.GetBitLength()) ||
                !IsValidQLength(parms.Q.GetBitLength()) ||
                parms.G <= 1 ||
                parms.G >= parms.P ||
                y <= 1 ||
                y >= parms.P)
            {
                throw new CryptographicException(SR.Cryptography_Der_Invalid_Encoding);
            }
 
            ret = new DSAParameters
            {
                P = parms.P.ToByteArray(isUnsigned: true, isBigEndian: true),
                Q = parms.Q.ToByteArray(isUnsigned: true, isBigEndian: true),
            };
 
            ret.G = parms.G.ExportKeyParameter(ret.P.Length);
            ret.Y = y.ExportKeyParameter(ret.P.Length);
        }
 
        private static bool IsValidPLength(long pBitLength)
        {
            return pBitLength switch
            {
                // FIPS 186-3/186-4
                1024 or 2048 or 3072 => true,
                // FIPS 186-1/186-2
                >= 512 and < 1024 => pBitLength % 64 == 0,
                _ => false,
            };
        }
 
        private static bool IsValidQLength(long qBitLength)
        {
            // FIPS 186-1/186-2 only allows 160
            // FIPS 186-3/186-4 allow 160/224/256
            return qBitLength is 160 or 224 or 256;
        }
 
        internal static void ReadSubjectPublicKeyInfo(
            ReadOnlySpan<byte> source,
            out int bytesRead,
            out DSAParameters key)
        {
            KeyFormatHelper.ReadSubjectPublicKeyInfo<DSAParameters>(
                s_validOids,
                source,
                ReadDsaPublicKey,
                out bytesRead,
                out key);
        }
 
        internal static ReadOnlyMemory<byte> ReadSubjectPublicKeyInfo(
             ReadOnlyMemory<byte> source,
             out int bytesRead)
        {
            return KeyFormatHelper.ReadSubjectPublicKeyInfo(
                s_validOids,
                source,
                out bytesRead);
        }
 
        internal static void ReadPkcs8(
            ReadOnlySpan<byte> source,
            out int bytesRead,
            out DSAParameters key)
        {
            KeyFormatHelper.ReadPkcs8<DSAParameters>(
                s_validOids,
                source,
                ReadDsaPrivateKey,
                out bytesRead,
                out key);
        }
 
        internal static void ReadEncryptedPkcs8(
            ReadOnlySpan<byte> source,
            ReadOnlySpan<char> password,
            out int bytesRead,
            out DSAParameters key)
        {
            KeyFormatHelper.ReadEncryptedPkcs8<DSAParameters>(
                s_validOids,
                source,
                password,
                ReadDsaPrivateKey,
                out bytesRead,
                out key);
        }
 
        internal static void ReadEncryptedPkcs8(
            ReadOnlySpan<byte> source,
            ReadOnlySpan<byte> passwordBytes,
            out int bytesRead,
            out DSAParameters key)
        {
            KeyFormatHelper.ReadEncryptedPkcs8<DSAParameters>(
                s_validOids,
                source,
                passwordBytes,
                ReadDsaPrivateKey,
                out bytesRead,
                out key);
        }
 
        internal static AsnWriter WriteSubjectPublicKeyInfo(in DSAParameters dsaParameters)
        {
            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
 
            writer.PushSequence();
            WriteAlgorithmId(writer, dsaParameters);
            WriteKeyComponent(writer, dsaParameters.Y, bitString: true);
            writer.PopSequence();
 
            return writer;
        }
 
        internal static AsnWriter WritePkcs8(in DSAParameters dsaParameters)
        {
            AsnWriter writer = new AsnWriter(AsnEncodingRules.DER);
 
            writer.PushSequence();
            writer.WriteInteger(0);
            WriteAlgorithmId(writer, dsaParameters);
            WriteKeyComponent(writer, dsaParameters.X, bitString: false);
            writer.PopSequence();
 
            return writer;
        }
 
        private static void WriteAlgorithmId(AsnWriter writer, in DSAParameters dsaParameters)
        {
            writer.PushSequence();
            writer.WriteObjectIdentifier(Oids.Dsa);
 
            // Dss-Parms ::= SEQUENCE {
            //   p INTEGER,
            //   q INTEGER,
            //   g INTEGER  }
            writer.PushSequence();
            writer.WriteKeyParameterInteger(dsaParameters.P);
            writer.WriteKeyParameterInteger(dsaParameters.Q);
            writer.WriteKeyParameterInteger(dsaParameters.G);
            writer.PopSequence();
            writer.PopSequence();
        }
 
        private static void WriteKeyComponent(AsnWriter writer, byte[]? component, bool bitString)
        {
            if (bitString)
            {
                AsnWriter inner = new AsnWriter(AsnEncodingRules.DER);
                inner.WriteKeyParameterInteger(component);
                inner.Encode(writer, static (writer, encoded) => writer.WriteBitString(encoded));
            }
            else
            {
                using (writer.PushOctetString())
                {
                    writer.WriteKeyParameterInteger(component);
                }
            }
        }
    }
}