// ==++==
// 
//   Copyright (c) Microsoft Corporation.  All rights reserved.
// 
// ==--==
// <OWNER>[....]</OWNER>
// 

//
// DSACryptoServiceProvider.cs
//
// CSP-based implementation of DSA
//

namespace System.Security.Cryptography {
    using System;
    using System.IO;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Permissions;
    using System.Globalization;
    using System.Runtime.Versioning;
    using System.Diagnostics.Contracts;

    // Object layout of the DSAParameters structure
    internal class DSACspObject {
        internal byte[] P;
        internal byte[] Q;
        internal byte[] G;
        internal byte[] Y;
        internal byte[] J;
        internal byte[] X;
        internal byte[] Seed;
        internal int Counter;
    }

    [System.Runtime.InteropServices.ComVisible(true)]
    public sealed class DSACryptoServiceProvider : DSA, ICspAsymmetricAlgorithm {
        private int _dwKeySize;
        private CspParameters _parameters;
        private bool _randomKeyContainer;
        [System.Security.SecurityCritical] // auto-generated
        private SafeProvHandle _safeProvHandle;
        [System.Security.SecurityCritical] // auto-generated
        private SafeKeyHandle _safeKeyHandle;
        private SHA1CryptoServiceProvider _sha1;

        private static volatile CspProviderFlags s_UseMachineKeyStore = 0;

        //
        // public constructors
        //

        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public DSACryptoServiceProvider() 
            : this(0, new CspParameters(Constants.PROV_DSS_DH, null, null, s_UseMachineKeyStore)) {
        }

        [ResourceExposure(ResourceScope.None)]
        [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
        public DSACryptoServiceProvider(int dwKeySize)
            : this(dwKeySize, new CspParameters(Constants.PROV_DSS_DH, null, null, s_UseMachineKeyStore)) {
        }

        public DSACryptoServiceProvider(CspParameters parameters) 
            : this(0, parameters) {
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        public DSACryptoServiceProvider(int dwKeySize, CspParameters parameters) {
            if (dwKeySize < 0)
                throw new ArgumentOutOfRangeException("dwKeySize", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum"));
            Contract.EndContractBlock();

            _parameters = Utils.SaveCspParameters(CspAlgorithmType.Dss, parameters, s_UseMachineKeyStore, ref _randomKeyContainer);
            LegalKeySizesValue = new KeySizes[] { new KeySizes(512, 1024, 64) }; // per the DSS spec
            _dwKeySize = dwKeySize;
            _sha1 = new SHA1CryptoServiceProvider();

            // If this is not a random container we generate, create it eagerly 
            // in the constructor so we can report any errors now.
            if (!_randomKeyContainer || Environment.GetCompatibilityFlag(CompatibilityFlag.EagerlyGenerateRandomAsymmKeys))
                GetKeyPair();
        }

        //
        // private methods
        //

        [System.Security.SecurityCritical]  // auto-generated
        private void GetKeyPair () {
            if (_safeKeyHandle == null) {
                lock (this) {
                    if (_safeKeyHandle == null)
                        Utils.GetKeyPairHelper(CspAlgorithmType.Dss, _parameters, _randomKeyContainer, _dwKeySize, ref _safeProvHandle, ref _safeKeyHandle);
                }
            }
        }

        [System.Security.SecuritySafeCritical] // overrides public transparent member
        protected override void Dispose(bool disposing) {
            base.Dispose(disposing);

            if (_safeKeyHandle != null && !_safeKeyHandle.IsClosed)
                _safeKeyHandle.Dispose();
            if (_safeProvHandle != null && !_safeProvHandle.IsClosed)
                _safeProvHandle.Dispose();
        }

        //
        // public properties
        //

        [System.Runtime.InteropServices.ComVisible(false)]
        public bool PublicOnly {
            [System.Security.SecuritySafeCritical]  // auto-generated
            get {
                GetKeyPair();
                byte[] publicKey = (byte[]) Utils._GetKeyParameter(_safeKeyHandle, Constants.CLR_PUBLICKEYONLY);
                return (publicKey[0] == 1);
            }
        }

        [System.Runtime.InteropServices.ComVisible(false)]
        public CspKeyContainerInfo CspKeyContainerInfo {
            [System.Security.SecuritySafeCritical]  // auto-generated
            get {
                GetKeyPair();
                return new CspKeyContainerInfo(_parameters, _randomKeyContainer);
            }
        }

        public override int KeySize {
            [System.Security.SecuritySafeCritical]  // auto-generated
            get {
                GetKeyPair();
                byte[] keySize = (byte[]) Utils._GetKeyParameter(_safeKeyHandle, Constants.CLR_KEYLEN);
                _dwKeySize = (keySize[0] | (keySize[1] << 8) | (keySize[2] << 16) | (keySize[3] << 24));
                return _dwKeySize;
            }
        }

        public override string KeyExchangeAlgorithm {
            get { return null; }
        }

        public override string SignatureAlgorithm {
            get { return "http://www.w3.org/2000/09/xmldsig#dsa-sha1"; }
        }

        public static bool UseMachineKeyStore {
            get { return (s_UseMachineKeyStore == CspProviderFlags.UseMachineKeyStore); }
            set { s_UseMachineKeyStore = (value ? CspProviderFlags.UseMachineKeyStore : 0); }
        }

        public bool PersistKeyInCsp {
            [System.Security.SecuritySafeCritical]  // auto-generated
            get {
                if (_safeProvHandle == null) {
                    lock (this) {
                        if (_safeProvHandle == null)
                            _safeProvHandle = Utils.CreateProvHandle(_parameters, _randomKeyContainer);
                    }
                }
                return Utils.GetPersistKeyInCsp(_safeProvHandle);
            }
            [System.Security.SecuritySafeCritical]  // auto-generated
            set {
                bool oldPersistKeyInCsp = this.PersistKeyInCsp;
                if (value == oldPersistKeyInCsp)
                    return;

                KeyContainerPermission kp = new KeyContainerPermission(KeyContainerPermissionFlags.NoFlags);
                if (!value) {
                    KeyContainerPermissionAccessEntry entry = new KeyContainerPermissionAccessEntry(_parameters, KeyContainerPermissionFlags.Delete);
                    kp.AccessEntries.Add(entry);
                } else {
                    KeyContainerPermissionAccessEntry entry = new KeyContainerPermissionAccessEntry(_parameters, KeyContainerPermissionFlags.Create);
                    kp.AccessEntries.Add(entry);
                }
                kp.Demand();

                Utils.SetPersistKeyInCsp(_safeProvHandle, value);
            }
        }

        //
        // public methods
        //

        [System.Security.SecuritySafeCritical]  // auto-generated
        public override DSAParameters ExportParameters (bool includePrivateParameters) {
            GetKeyPair();
            if (includePrivateParameters) {
                KeyContainerPermission kp = new KeyContainerPermission(KeyContainerPermissionFlags.NoFlags);
                KeyContainerPermissionAccessEntry entry = new KeyContainerPermissionAccessEntry(_parameters, KeyContainerPermissionFlags.Export);
                kp.AccessEntries.Add(entry);
                kp.Demand();
            }
            DSACspObject dsaCspObject = new DSACspObject();
            int blobType = includePrivateParameters ? Constants.PRIVATEKEYBLOB : Constants.PUBLICKEYBLOB;
            // _ExportKey will check for failures and throw an exception
            Utils._ExportKey(_safeKeyHandle, blobType, dsaCspObject);
            return DSAObjectToStruct(dsaCspObject);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        [System.Runtime.InteropServices.ComVisible(false)]
        public byte[] ExportCspBlob (bool includePrivateParameters) {
            GetKeyPair();
            return Utils.ExportCspBlobHelper(includePrivateParameters, _parameters, _safeKeyHandle);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        public override void ImportParameters(DSAParameters parameters) {
            DSACspObject dsaCspObject = DSAStructToObject(parameters);
            // Free the current key handle
            if (_safeKeyHandle != null && !_safeKeyHandle.IsClosed)
                _safeKeyHandle.Dispose();
            _safeKeyHandle = SafeKeyHandle.InvalidHandle;

            if (IsPublic(parameters)) {
                // Use our CRYPT_VERIFYCONTEXT handle, CRYPT_EXPORTABLE is not applicable to public only keys, so pass false
                Utils._ImportKey(Utils.StaticDssProvHandle, Constants.CALG_DSS_SIGN, (CspProviderFlags) 0, dsaCspObject, ref _safeKeyHandle);
            } else {
                KeyContainerPermission kp = new KeyContainerPermission(KeyContainerPermissionFlags.NoFlags);
                KeyContainerPermissionAccessEntry entry = new KeyContainerPermissionAccessEntry(_parameters, KeyContainerPermissionFlags.Import);
                kp.AccessEntries.Add(entry);
                kp.Demand();
                if (_safeProvHandle == null)
                    _safeProvHandle = Utils.CreateProvHandle(_parameters, _randomKeyContainer);
                // Now, import the key into the CSP; _ImportKey will check for failures.
                Utils._ImportKey(_safeProvHandle, Constants.CALG_DSS_SIGN, _parameters.Flags, dsaCspObject, ref _safeKeyHandle);
            }
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        [System.Runtime.InteropServices.ComVisible(false)]
        public void ImportCspBlob (byte[] keyBlob) {
            Utils.ImportCspBlobHelper(CspAlgorithmType.Dss, keyBlob, IsPublic(keyBlob), ref _parameters, _randomKeyContainer, ref _safeProvHandle, ref _safeKeyHandle);
        }

        public byte[] SignData(Stream inputStream) {
            byte[] hashVal = _sha1.ComputeHash(inputStream);
            return SignHash(hashVal, null);
        }

        public byte[] SignData(byte[] buffer) {
            byte[] hashVal = _sha1.ComputeHash(buffer);
            return SignHash(hashVal, null);
        }

        public byte[] SignData(byte[] buffer, int offset, int count) {
            byte[] hashVal = _sha1.ComputeHash(buffer, offset, count);
            return SignHash(hashVal, null);
        }

        public bool VerifyData(byte[] rgbData, byte[] rgbSignature) {
            byte[] hashVal = _sha1.ComputeHash(rgbData);
            return VerifyHash(hashVal, null, rgbSignature);
        }

        override public byte[] CreateSignature(byte[] rgbHash) {
            return SignHash(rgbHash, null);
        }

        override public bool VerifySignature(byte[] rgbHash, byte[] rgbSignature) {
            return VerifyHash(rgbHash, null, rgbSignature);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        public byte[] SignHash(byte[] rgbHash, string str) {
            if (rgbHash == null)
                throw new ArgumentNullException("rgbHash");
            Contract.EndContractBlock();
            if (PublicOnly)
                throw new CryptographicException(Environment.GetResourceString("Cryptography_CSP_NoPrivateKey"));

            int calgHash = X509Utils.NameOrOidToAlgId(str, OidGroup.HashAlgorithm);
            if (rgbHash.Length != _sha1.HashSize / 8)
                throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidHashSize", "SHA1", _sha1.HashSize / 8));

            GetKeyPair();
            if (!CspKeyContainerInfo.RandomlyGenerated) {
                KeyContainerPermission kp = new KeyContainerPermission(KeyContainerPermissionFlags.NoFlags);
                KeyContainerPermissionAccessEntry entry = new KeyContainerPermissionAccessEntry(_parameters, KeyContainerPermissionFlags.Sign);
                kp.AccessEntries.Add(entry);
                kp.Demand();
            }
            return Utils.SignValue(_safeKeyHandle, _parameters.KeyNumber, Constants.CALG_DSS_SIGN, calgHash, rgbHash);
        }

        [System.Security.SecuritySafeCritical]  // auto-generated
        public bool VerifyHash(byte[] rgbHash, string str, byte[] rgbSignature) {
            if (rgbHash == null)
                throw new ArgumentNullException("rgbHash");
            if (rgbSignature == null)
                throw new ArgumentNullException("rgbSignature");
            Contract.EndContractBlock();

            int calgHash = X509Utils.NameOrOidToAlgId(str, OidGroup.HashAlgorithm);
            if (rgbHash.Length != _sha1.HashSize / 8) 
                throw new CryptographicException(Environment.GetResourceString("Cryptography_InvalidHashSize", "SHA1", _sha1.HashSize / 8));

            GetKeyPair();
            return Utils.VerifySign(_safeKeyHandle, Constants.CALG_DSS_SIGN, calgHash, rgbHash, rgbSignature);
        }

        //
        // private static methods
        //

        private static DSAParameters DSAObjectToStruct (DSACspObject dsaCspObject) {
            DSAParameters dsaParams = new DSAParameters();
            dsaParams.P = dsaCspObject.P;
            dsaParams.Q = dsaCspObject.Q;
            dsaParams.G = dsaCspObject.G;
            dsaParams.Y = dsaCspObject.Y;
            dsaParams.J = dsaCspObject.J;
            dsaParams.X = dsaCspObject.X;
            dsaParams.Seed = dsaCspObject.Seed;
            dsaParams.Counter = dsaCspObject.Counter;
            return dsaParams;
        }

        private static DSACspObject DSAStructToObject (DSAParameters dsaParams) {
            DSACspObject dsaCspObject = new DSACspObject();
            dsaCspObject.P = dsaParams.P;
            dsaCspObject.Q = dsaParams.Q;
            dsaCspObject.G = dsaParams.G;
            dsaCspObject.Y = dsaParams.Y;
            dsaCspObject.J = dsaParams.J;
            dsaCspObject.X = dsaParams.X;
            dsaCspObject.Seed = dsaParams.Seed;
            dsaCspObject.Counter = dsaParams.Counter;
            return dsaCspObject;
        }

        private static bool IsPublic (DSAParameters dsaParams) {
            return (dsaParams.X == null);
        }

        // find whether a DSS key blob is public.
        private static bool IsPublic (byte[] keyBlob) {
            if (keyBlob == null)
                throw new ArgumentNullException("keyBlob");
            Contract.EndContractBlock();

            // The CAPI DSS public key representation consists of the following sequence:
            //  - BLOBHEADER
            //  - DSSPUBKEY
            //  - rgbP[cbKey]
            //  - rgbQ[20]
            //  - rgbG[cbKey]
            //  - rgbY[cbKey]
            //  - DSSSEED

            // The first should be PUBLICKEYBLOB and magic should be DSS_MAGIC "DSS1" or DSS_PUB_MAGIC_VER3 "DSS3"
            if (keyBlob[0] != Constants.PUBLICKEYBLOB)
                return false;

            if ((keyBlob[11] != 0x31 && keyBlob[11] != 0x33) || keyBlob[10] != 0x53 || keyBlob[9] != 0x53 || keyBlob[8] != 0x44)
                return false;

            return true;
        }
    }
}
