gubus 2.0

2010. 12. 25.

WCF használata vastag kliens alkalmazásoknál II.

Kategória: .NET — gubus @ 11:03

A vastag kliens alkalmazásunk Entity Framework entitásokat kér el a szervertől WCF-en keresztül. A kliens és a szerver oldal közös entitás típusokat használ úgy, hogy a kliens oldali WCF proxy generálásakor a közös entitás assembly-ben található típusokat használja fel a generátor. (“Resuse types from referenced assemblies” beállítás az “Add Service Reference” funkciónál a Visual Studio-ban)

A proxy generálás viszont nem működik, ha az entitások egy része tartalmaz körkörös referenciákat, mert akkor a generálás kivételre fut:

Error: Cannot import wsdl:portType
Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter
Error: Referenced type … with data contract name ‘…’ in namespace ‘…’ cannot be used since it does not match imported DataContract. Need to exclude this type from referenced types.

A hibáról itt van egy összefoglaló:

WCF Client Code Generation – Issue with “Reuse types from referenced assemblies” option in Add Service Reference

Workaround gyanánt az entitásoknál ki kell kapcsolni a körkörös hivatkozást (“IsReference=false” ), és így kell generálni a proxy-t, majd utána újra vissza lehet kapcsolni a körkörös hivatkozást.

És ennél a pontnál lett világosság: hogyha körkörös referenciát használunk, akkor az már rég nem interop webszolgáltatás. Sőt, mivel kliens oldalon és szerver oldalon is .NET van, ez nem is igény, és sokkal egyszerűbb, ha a contract is közös, és egyáltalán nem lenne kliens proxy generálás. Szóval a WSDL-en keresztüli interfészt csak akkor érdemes használni, ha igény is van rá…

When should I use a channel factory?

Use a ChannelFactory when you control both ends of the wire and would rather code directly against the same CLR interface instead of manually keeping the WSDL interface in sync. Instead of using WSDL as the shared contract, you use a shared “interface assembly”.

WCF használata vastag kliens alkalmazásoknál I.

Kategória: .NET — gubus @ 10:51

Néha kliens alkalmazásokban látni a következő WCF hívási mintát:

using (var client = new FooServiceReference.FooServiceClient("myBinding"))
{
	var f = client.GetFoo();
}

A probléma a fenti kóddal az, hogy a kliens minden alkalommal újraépíti a WCF kommunikációt a szerver felé, ami több erőforrásba kerül, mintha egy már megnyitott proxy-n hívná meg a szervert.

Fontos az is, hogy a proxy objektum Fault állapotba kerülhet akkor, ha olyan katasztrofális hiba keletkezett a kliens-szerver kommunikáció közben, melynél nem folytatható tovább a kommunikáció.
Példa a Fault állapot bekövetkeztére:
- reliable session használatakor a hívás timeout-ra fut,
- a szerver SOAP Fault üzenetet küld vissza (kivételt dobott a szerver),
- a kommunikáció iniciliazálásakor az ICommunicationObject.Open() hívás nem sikerül,
- session alapú kapcsolatnál olyan protokoll vagy szerver hiba keletkezik, amely miatt a session érvénytelen lesz.

Ha a proxy példány Fault állapotba kerül, akkor nem lehet tovább használni, nem szabad Close()-t hívni rajta (kivételt dob), és az Abort() meghívásával kell felszabadítani az általa lefoglalt erőforrásokat.

A fenti minta így hibásan viselkedik a Fault állapot bekövetkeztekor, mert a proxy-n hívott Dispose() a Close()-t hívja meg, ami pedig Fault állapotban a Dispose() elszállását fogja okozni, tehát egy kommunikációs kivétel feldolgozása közben keletkezni fog egy másik, az eredeti hibát elfedő kivétel. Erre a hibára remek körbebástyázós megoldást találtak ki Redmondban:

Avoiding Problems with the Using Statement

AnswerJuly CTP – Guidance on factory close and message faults

Csak a try-catch beágyazást ipari méretben alkalmazva spagetti kódot fog eredményezni.

A hatékonyság miatt az is kívánatos lenne, ha az alkalmazás az induláskor hozná létre és inicializálná a proxy objektumokot, ezt a meglévő proxy-t használná az egész élete alatt, és ha az Faulted állapotva kerülne, akkor automatikusa zárná a proxy-t, és nyitná az újat.

A problémát elég jól részletezi a következő két cikk:

Performance Improvement for WCF Client Proxy Creation in .NET 3.5 and Best Practices

WCF Guidance for WPF Developers

Ezek után mindenki szépen neki is foghat a saját barkács proxy kezelőjének megírásához:

1) WCF Client Proxy IDisposable – Generic WCF Service Proxy
A megoldás a Fault állapotba kerülést jól kezeli, de minden hívásnál új Channel-t és ChannelFactory-t hoz létre, ami viszont a sebességnek nem tesz jót.

2. Exception Handling WCF Proxy Generator
Egy Visual Studio extension, melyet Michele Leroux Bustamante fejlesztett ki.
Jellemzők:
- Visual Studio 2008 alá készült az extension
- az extension a ExceptionHandlingProxyBase ősből leszármaztatott proxy osztályokat gyárt. A ExceptionHandlingProxyBase a hölgy saját fejlesztése, egy általános kliens, gyakorlatilag egy alapoktól újraírt ClientBase-nek felel meg.

A megoldás működik, precíz, és látszik a befektetett munka, de mostanában a csapból is a kódgenerátorok folynak, és a plusz bonyolultságtól meg a plusz macerától idegbajt kapok.

3. Going Proxy-less – The WCF Proxy Factory

Nem tűnik elterjedt, vagy kipróbált dolognak.

4. Christian Weyer elődása és példaprogramjai

TechDays Sweden 2009: Presentation slides and demos for ‘WCF Tips & Tricks” session

Az előadás fóliái jók, viszont a példaprogramok a szerző Thinktecture.ServiceModel.dll osztálykönyvtárát használják, melynek nincs forráskódja, és maga az assembly is csak demonstrációs célból van publikálva.

5. WCF Contrib

A WCF Contrib osztálykönyvtárat Amir Zuker készítette, gyakorlatilag ez is egy saját WCF kliens implementáció, de vannak szerver oldali kiegészítései is. Az előző mellett ez tűnik a legátfogóbb megoldásnak, példaprogamok vannak, viszont dokumentáció kevés. Támogatja az aszinkron hívásokat is.

6. Csak szinkron hívásokra egy egyszerűbb megoldás

Először is a ClientBase-t meg kell patkolni egy kicsit:

namespace WcfTools
{
    using System.ServiceModel;

    internal class ClientBaseEx<TInterface> : ClientBase<TInterface>
           where TInterface : class
    {
        public ClientBaseEx(string endpointConfigurationName)
            : base(endpointConfigurationName)
        {

        }

        public ClientBaseEx(string endpointConfigurationName, string remoteAddress)
            : base(endpointConfigurationName, remoteAddress)
        {

        }

        /// <summary>
        /// Protected field published with internal access
        /// </summary>
        internal TInterface Proxy
        {
            get { return Channel; }
        }

        protected override TInterface CreateChannel()
        {
            return base.CreateChannel();
        }
    }
}

Utána készül egy wrapper, mely a ClientBaseEx segítségével hívja meg a szervert:
namespace WcfTools
{
    using System;
    using System.Runtime.InteropServices;
    using System.Security;
    using System.ServiceModel;
    using log4net;

    public class SafeClient<TInterface> where TInterface : class
    {
        protected static readonly ILog log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        #region Static singleton instance
        private static SafeClient<TInterface> instance = new SafeClient<TInterface>();

        public static SafeClient<TInterface> Instance
        {
            get { return SafeClient<TInterface>.instance; }
        }
        #endregion
        #region Private variables
        private ClientBaseEx<TInterface> clientProxy = null;
        private object lockObj = new object();

        /// <summary>
        /// Csak akkor ad a Proxy mező értéket, ha Connect() meghívásra kerül. 
        /// </summary>
        private bool isConnected = false;
        #endregion
        #region Public properties

        public bool BasicAuthenticationEnabled { get; set; }
        public string BasicAuthenticationUserName { get; set; }
        public SecureString BasicAuthenticationPassword { get; set; }
        public string EndpointConfigurationName { get; set; }
        public string RemoteAddressDirectoryUrl { get; set; }
        public string RemoteAddressServiceFile { get; set; }

        /// <summary>
        /// Webszolgáltatás interfésze
        /// </summary>
        public TInterface Proxy
        {
            get
            {
                if (log.IsDebugEnabled) log.Debug("Start");

                TInterface result = null;

                lock (lockObj)
                {
                    if (isConnected)
                    {
                        CheckClientIsOpened();
                        result = clientProxy.Proxy;
                    }
                    else
                    {
                        if (log.IsDebugEnabled) log.Debug("Not connected!");
                    }
                }

                if (log.IsDebugEnabled) log.Debug("End");

                return result;
            }
        }

        #endregion
        #region Public functions
        /// <summary>
        /// Kapcsolódás
        /// </summary>
        public void Connect()
        {
            if (log.IsDebugEnabled) log.Debug("Start");

            lock (lockObj)
            {
                // a proxy ne a régi legyen, mert az autentikációs adatok lehet változtak
                DisconnectInternal();
                CheckClientIsOpened();
                isConnected = true;
            }

            if (log.IsDebugEnabled) log.Debug("End");
        }

        /// <summary>
        /// Kapcsolat bontása
        /// </summary>
        public void Disconnect()
        {
            if (log.IsDebugEnabled) log.Debug("Start");

            lock (lockObj)
            {
                DisconnectInternal();
            }

            if (log.IsDebugEnabled) log.Debug("End");
        }

        #endregion
        #region Private functions

        /// <summary>
        /// Kapcsolat ellenőrzése: szükség esetén bontás, és újranyitás
        /// </summary>
        private void CheckClientIsOpened()
        {
            if (clientProxy != null)
            {
                if (clientProxy.State == CommunicationState.Faulted)
                {
                    if (log.IsDebugEnabled) log.Debug("State == CommunicationState.Faulted, aborting...");

                    try
                    {
                        clientProxy.Abort();
                    }
                    finally
                    {
                        clientProxy = null;
                    }
                }
                else if (clientProxy.State == CommunicationState.Closed)
                {
                    if (log.IsDebugEnabled) log.Debug("State == CommunicationState.Closed.");
                    clientProxy = null;
                }
            }

            if (clientProxy == null)
            {
                if (log.IsDebugEnabled) log.Debug("Creating WCF proxy....");

                //  a könyvtár jelet a végére tesszük, ha nincs ott, mert különben az sikertelen lesz az összefűzés
                this.RemoteAddressDirectoryUrl = CheckTailSlash(this.RemoteAddressDirectoryUrl);

                Uri uriMain = new Uri(this.RemoteAddressDirectoryUrl);
                Uri uriChild = new Uri(uriMain, this.RemoteAddressServiceFile);

                if (log.IsDebugEnabled) log.DebugFormat("Url: '{0}'", uriChild);

                clientProxy = new ClientBaseEx<TInterface>(this.EndpointConfigurationName, uriChild.ToString());

                if (BasicAuthenticationEnabled)
                {
                    clientProxy.ClientCredentials.UserName.UserName = this.BasicAuthenticationUserName;
                    clientProxy.ClientCredentials.UserName.Password = SecureStringToString(this.BasicAuthenticationPassword);
                }

                clientProxy.Open();

                if (log.IsDebugEnabled) log.Debug("Proxy OK.");
            }
        }

        private static string CheckTailSlash(string data)
        {
            string result = data.Trim();

            if (result.Length > 0)
            {
                char last = result[result.Length - 1];
                if (last != '/')
                {
                    result += "/";
                }
            }

            return result;
        }

        private void DisconnectInternal()
        {
            try
            {
                if (clientProxy != null)
                {
                    try
                    {
                        if (clientProxy.State == CommunicationState.Faulted)
                        {
                            clientProxy.Abort();
                        }
                        else
                        {
                            clientProxy.Close();
                        }
                    }
                    finally
                    {
                        clientProxy = null;
                    }
                }
            }
            finally
            {   // mindegy mi történt, a kapcsolat bontva van
                isConnected = false;
            }
        }


        private static string SecureStringToString(SecureString value)
        {
            IntPtr bstr = Marshal.SecureStringToBSTR(value);

            try
            {
                return Marshal.PtrToStringBSTR(bstr);
            }
            finally
            {
                Marshal.FreeBSTR(bstr);
            }
        }

        #endregion
    }
}

Példa a használatra:

// starting fat client 
var client = new SafeClient<FooServiceReference.IFooService>();
client.EndpointConfigurationName = "myBinding";
client.RemoteAddressDirectoryUrl = "http://localhost:52008/";
client.RemoteAddressServiceFile = "FooService.svc";      
client.Connect();

...

// fat client using same SafeClient<T> instance
client.Proxy.GetFoo();

try
{
	client.Proxy.GetFooTimeout();
}
catch (Exception ex)
{
	log.Error(ex);
}

try
{
	client.Proxy.GetFooFaulted();
}
catch (Exception ex)
{
	log.Error(ex);
}

client.Proxy.GetFoo();

...

//  fat client stopping
client.Disconnect();

A vastag kliensnek annyi dolga van, hogy induláskor építse fel a kapcsolatot, és utána a SafeClient példányt őrizgesse, és rajta keresztül hívja a szervert.
A SafeClient rendelkezik egy Instance nevű singleton példánnyal is, tehát akár saját példány nélkül is lehet használni.

Mikor érdemes külön példányokat használni:
- ha ugyanazt az interfészt más-más autentikációs beállításokkal kell hívni, mert ilyenkor nem lenne szerencsés, ha keverednének a szerveren az azonosítási információk.
- többszálú működésnél, ha két szál egyidőben hívja a szervert, és az egyik Fault állapotba billenti a proxy-t, akkor az a másik, még ugyanazzal a proxy példánnyal dolgozó szálra is hatással lesz. Ha ez zavaró, akkor szálanként kell példányokat használni, és valami pool-t kell képezni.

How to sign with a timestamp and OCSP in C# – smart card support

Kategória: .NET — gubus @ 10:11

The samples of itextsharp does not cover the situatuion when the certificate is on the smart card AND the signature need timestamp.

1. First sample: Sign without timestamp and with smart card

How to sign with a smartcard using an external signature dictionary with iTextSharp and .NET 2.0

This sample uses the SignedCms class of the .NET BCL. The SignedCms class creates the complete PKCS#7 message, and it supports when the certificate is on the smart card, but the signature doesn’t have a timestamp. This class is sealed, and no possibility to extend the data with timetamp information.

2. Second sample: Sign with timestamp and without smart card

How to sign with a timestamp and OCSP in C#

The second sample creates the PKCS#7 message with the Bouncy Castle Crypto API, which is packaged with the iTextSharp. The Bouncy Castle API calculates the signature HASH with the private key value of the X509 certificate, and therefore the smart card certificate is useless for this scenario.

The key in this problem is the SetExternalDigest method: if I can calculate the SHA1withRSA HASH with an external component, then the Bouncy Castle API can use the external value.
The RSACryptoServiceProvider class can create SHA1withRSA HASH, but it doesn’t support the smart card too. The reliable solution is the WINAPI, it supports the smart card.

The sample C# code below uses the CryptoAPI PInvoke helper class from Alejandro Campos Magencio.

using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using iTextSharp.text.pdf;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Security;

namespace PdfSignerTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                Console.WriteLine("Start");

                string inputFileName = @"..\..\..\Report.pdf";
                string outputFileName = @"..\..\..\Signed.pdf";
                string timeStampUrl = "http://www.xxxx.com/timestamp.cgi";

                using (FileStream inputPdfStream = new FileStream(inputFileName, FileMode.Open, FileAccess.Read))
                {
                    using (FileStream outputPdfStream = new FileStream(outputFileName, FileMode.Create, FileAccess.Write))
                    {
                        X509Certificate2 card = SelectCertificate();

                        SignPdf(card, inputPdfStream, outputPdfStream, false, "Test Reason", "Test Location", "Test Contact", timeStampUrl, "teszt", "teszt");
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                Console.WriteLine("Done.");
                Console.ReadLine();
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="certificate">smart card certificate</param>
        /// <param name="inputPdfStream">input PDF</param>
        /// <param name="outputPdfStream">output PDF (signed)</param>
        /// <param name="append">append if <CODE>true</CODE> the signature and all the other content will be added as a new revision thus not invalidating existing signature</param>
        /// <param name="reason">Reason field of signature</param>
        /// <param name="location">Location field of signature</param>
        /// <param name="contact">Contact field of signature</param>
        /// <param name="tsaClientUrl">Timestamp server URL</param>
        /// <param name="tsaClientLogin">Login user for timestamp server (BASIC authentication)</param>
        /// <param name="tsaClientPwd">Login password for timestamp server</param>
        public static void SignPdf(X509Certificate2 certificate, Stream inputPdfStream, Stream outputPdfStream, bool append, string reason, string location, string contact, string tsaClientUrl, string tsaClientLogin, string tsaClientPwd)
        {
            // Building certificate chain for the OCSP verification
            X509Chain ch = new X509Chain();
            ch.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
            ch.Build(certificate);

            Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[ch.ChainElements.Count];
            Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
            int i = 0;

            foreach (X509ChainElement element in ch.ChainElements)
            {
                Org.BouncyCastle.X509.X509Certificate chainElement = cp.ReadCertificate(element.Certificate.RawData);
                chain[i] = chainElement;
                i++;
            }

            //Org.BouncyCastle.X509.X509CertificateParser cp = new Org.BouncyCastle.X509.X509CertificateParser();
            //Org.BouncyCastle.X509.X509Certificate[] chain = new Org.BouncyCastle.X509.X509Certificate[] { cp.ReadCertificate(certificate.RawData) };

            PdfReader reader = new PdfReader(inputPdfStream);
            PdfStamper st = PdfStamper.CreateSignature(reader, outputPdfStream, '\0', null, append);
            PdfSignatureAppearance sap = st.SignatureAppearance;

            sap.SetCrypto(null, chain, null, PdfSignatureAppearance.SELF_SIGNED);

            sap.Reason = reason;
            sap.Contact = contact;
            sap.Location = location;

            sap.SetVisibleSignature(new iTextSharp.text.Rectangle(50, 50, 200, 100), 1, null);

            PdfSignature dic = new PdfSignature(PdfName.ADOBE_PPKLITE, new PdfName("adbe.pkcs7.detached"));
            dic.Reason = sap.Reason;
            dic.Location = sap.Location;
            dic.Contact = sap.Contact;
            dic.Date = new PdfDate(sap.SignDate);
            sap.CryptoDictionary = dic;

            int contentEstimated = 15000;

            Dictionary<PdfName, int> exc = new Dictionary<PdfName, int>();
            exc[PdfName.CONTENTS] = contentEstimated * 2 + 2;
            sap.PreClose(exc);

            PdfPKCS7 sgn = new PdfPKCS7(null, chain, null, "SHA1", false);
            IDigest messageDigest = DigestUtilities.GetDigest("SHA1");
            Stream data = sap.RangeStream;
            byte[] buf = new byte[8192];
            int n;
            while ((n = data.Read(buf, 0, buf.Length)) > 0)
            {
                messageDigest.BlockUpdate(buf, 0, n);
            }
            byte[] hash = new byte[messageDigest.GetDigestSize()];
            messageDigest.DoFinal(hash, 0);
            DateTime cal = DateTime.Now;
            byte[] ocsp = null;
            if (chain.Length >= 2)
            {
                String url = PdfPKCS7.GetOCSPURL(chain[0]);
                if (url != null && url.Length > 0)
                    ocsp = new OcspClientBouncyCastle(chain[0], chain[1], url).GetEncoded();

                //File.WriteAllBytes(@"..\..\..\ocsp.pdf", ocsp);
            }
            byte[] sh = sgn.GetAuthenticatedAttributeBytes(hash, cal, ocsp);

            // SHA1withRSA calculated by CAPI
            byte[] signedHashValue = SignSHA1withRSA(certificate, sh);
            sgn.SetExternalDigest(signedHashValue, hash, "RSA");

            byte[] paddedSig = new byte[contentEstimated];

            if (!string.IsNullOrEmpty(tsaClientUrl))
            {
                TSAClientBouncyCastle tsc = new TSAClientBouncyCastle(tsaClientUrl, tsaClientLogin, tsaClientPwd);
                byte[] encodedSigTsa = sgn.GetEncodedPKCS7(hash, cal, tsc, ocsp);
                System.Array.Copy(encodedSigTsa, 0, paddedSig, 0, encodedSigTsa.Length);
                if (contentEstimated + 2 < encodedSigTsa.Length)
                    throw new ApplicationException("Not enough space for signature");
            }
            else
            {
                byte[] encodedSig = sgn.GetEncodedPKCS7(hash, cal, null, ocsp);
                System.Array.Copy(encodedSig, 0, paddedSig, 0, encodedSig.Length);
                if (contentEstimated + 2 < encodedSig.Length)
                    throw new ApplicationException("Not enough space for signature");
            }

            PdfDictionary dic2 = new PdfDictionary();
            dic2.Put(PdfName.CONTENTS, new PdfString(paddedSig).SetHexWriting(true));
            sap.Close(dic2);
        }

        public static byte[] SignSHA1withRSA(X509Certificate2 certificate, byte[] input)
        {
            const Int32 CRYPT_ACQUIRE_USE_PROV_INFO_FLAG = 0x00000002;
            const Int32 CRYPT_ACQUIRE_COMPARE_KEY_FLAG = 0x00000004;

            IntPtr privateKeyHandle = IntPtr.Zero;
            bool isCallerNeedFreeKeyHandle = false;
            IntPtr hashHandle = IntPtr.Zero;
            byte[] result = null;

            try
            {
                IntPtr cardHandle = certificate.Handle;
                Int32 pdwKeySpec = Crypto.AT_SIGNATURE;

                if (!Crypto.CryptAcquireCertificatePrivateKey(cardHandle, CRYPT_ACQUIRE_USE_PROV_INFO_FLAG |
                                                     CRYPT_ACQUIRE_COMPARE_KEY_FLAG, IntPtr.Zero, ref privateKeyHandle, ref pdwKeySpec, ref isCallerNeedFreeKeyHandle))
                {
                    throw new CryptographicException(Marshal.GetLastWin32Error());
                }

                if (!Crypto.CryptCreateHash(privateKeyHandle, Crypto.CALG_SHA1, IntPtr.Zero, 0, ref hashHandle))
                {
                    throw new CryptographicException(Marshal.GetLastWin32Error());
                }

                MemoryStream streamInput = new MemoryStream(input);
                byte[] buffer = new byte[4096];

                while (true)
                {
                    int read = streamInput.Read(buffer, 0, buffer.Length);

                    if (read == 0)
                        break;

                    if (!Crypto.CryptHashData(hashHandle, buffer, read, 0))
                    {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                }

                int pwdSigLen = 0;
                if (!Crypto.CryptSignHash(hashHandle, pdwKeySpec, null, 0, null, ref pwdSigLen))
                {
                    throw new CryptographicException(Marshal.GetLastWin32Error());
                }

                result = new byte[pwdSigLen];

                if (!Crypto.CryptSignHash(hashHandle, pdwKeySpec, null, 0, result, ref pwdSigLen))
                {
                    throw new CryptographicException(Marshal.GetLastWin32Error());
                }

                // CAPI generated hash has a different indian order
                Array.Reverse(result);
            }
            finally
            {

                if (hashHandle != IntPtr.Zero)
                {
                    if (!Crypto.CryptDestroyHash(hashHandle))
                    {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                }

                if (isCallerNeedFreeKeyHandle && privateKeyHandle != IntPtr.Zero)
                {
                    if (!Crypto.CryptReleaseContext(privateKeyHandle, 0))
                    {
                        throw new CryptographicException(Marshal.GetLastWin32Error());
                    }
                }
            }

            return result;

        }

        public static X509Certificate2 SelectCertificate()
        {
            X509Store st = new X509Store(StoreName.My, StoreLocation.CurrentUser);
            st.Open(OpenFlags.ReadOnly);
            X509Certificate2Collection col = st.Certificates.Find(X509FindType.FindByKeyUsage, X509KeyUsageFlags.NonRepudiation, true);

            X509Certificate2 card = null;
            X509Certificate2Collection sel = X509Certificate2UI.SelectFromCollection(col, "Certificates", "Select one to sign", X509SelectionFlag.SingleSelection);
            if (sel.Count > 0)
            {
                X509Certificate2Enumerator en = sel.GetEnumerator();
                en.MoveNext();
                card = en.Current;
            }
            st.Close();
            return card;
        }
    }
}


Sablon: Shocking Blue Green. Köszönjük WordPress.

Follow

Get every new post delivered to your Inbox.