web-dev-qa-db-fra.com

trouver le certificat sur la carte à puce actuellement sur le lecteur

J'utilise Visual Studio 2013 (C #) pour signer numériquement un document à l'aide d'un certificat de carte à puce. Je ne parviens pas à identifier le certificat actuellement inséré dans le lecteur de carte :(

Windows copie les certificats de toutes les cartes insérées dans le lecteur et les conserve dans le magasin. Je veux utiliser simplement la carte dans le lecteur.

le code que j'utilise est

public static byte[] Sign(Stream inData, string certSubject)
{

    // Access Personal (MY) certificate store of current user
    X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    my.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    // Find the certificate we'll use to sign            
    RSACryptoServiceProvider csp = null;
    foreach (X509Certificate2 cert in my.Certificates)
    {
        if (cert.Subject.Contains(certSubject))
        {
            // We found it. 
            // Get its associated CSP and private key
            if (cert.HasPrivateKey) {
                csp = (RSACryptoServiceProvider)cert.PrivateKey;
                if (csp.CspKeyContainerInfo.HardwareDevice)
                    Console.WriteLine("hardware");                              
                    Console.WriteLine(cert.ToString());
            }
        }
    }
    if (csp == null)
    {
        throw new Exception("No valid cert was found");
    }

    // Hash the data
    SHA1Managed sha1 = new SHA1Managed();
    byte[] hash = sha1.ComputeHash(inData);

    // Sign the hash
    return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}

Mais lorsque l'accès à cert.PrivateKey est demandé à l'utilisateur d'insérer la carte dans le lecteur. Comment détecter et ignorer cette invite de carte ou détecter que le cert a la carte correspondante actuellement dans le lecteur?

Je veux juste utiliser un certificat de carte à puce actuellement dans le lecteur. 

13
Nebojsa Susic

Je crains qu'il ne soit pas possible de détecter si la carte contenant un objet X509Certificate2 spécifique est présente dans le lecteur à l'aide des API .NET standard. La meilleure chose que je pourrais trouver (très rigolote) est la suivante:

public static X509Certificate2 GetDefaultCertificateStoredOnTheCard() 
{ 
    // Acquire public key stored in the default container of the currently inserted card
    CspParameters cspParameters = new CspParameters(1, "Microsoft Base Smart Card Crypto Provider"); 
    RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters); 
    string pubKeyXml = rsaProvider.ToXmlString(false); 

    // Find the certficate in the CurrentUser\My store that matches the public key
    X509Store x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser); 
    x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); 
    foreach (X509Certificate2 cert in x509Store.Certificates) 
    { 
        if ((cert.PublicKey.Key.ToXmlString(false) == pubKeyXml) && cert.HasPrivateKey)
            return cert; 
    } 

    return null; 
}

Toutefois, cette méthode n’est fiable que lorsque les conditions suivantes sont remplies:

  1. Votre carte est accessible via minidriver et le fournisseur cryptographique Microsoft Base Smart Card.
  2. Un seul lecteur connecté à votre ordinateur contient la carte à puce.
  3. Un seul certificat est présent sur la carte actuellement insérée dans le lecteur.

Lorsque la carte contient plusieurs lecteurs avec des cartes à puce connectées ou que plusieurs certificats sont présents, vous ne pouvez pas savoir avec certitude lequel sera renvoyé par cette méthode.

Veuillez noter qu'il existe également d'autres API disponibles pouvant accéder à la carte à puce. PKCS # 11 est un exemple de cette API. Cela peut paraître excessif pour des opérations simples, mais cela peut vous donner un contrôle total sur votre carte et les objets qui y sont stockés. Si vous êtes intéressé et que votre carte à puce est livrée avec la bibliothèque PKCS # 11, vous pouvez consulter mon projet Pkcs11Interop qui donne toute la puissance de l’API PKCS # 11 à l’environnement .NET.

J'espère que cela t'aides :)

Edité pour supprimer la restriction "single certificate":

J'ai légèrement modifié le code. Il utilise désormais une API cryptographique non gérée pour énumérer les noms de tous les conteneurs gérés par le fournisseur cryptographique Microsoft Base Smart Card, puis recherche les objets X509Certificate2 correspondants dans le magasin CurrentUser\My. Veuillez noter que cette approche est également très bidon et que le code fourni peut ne pas fonctionner de manière fiable avec toutes les cartes/minidrivers disponibles sur le marché. Il est généralement préférable et plus facile de laisser l’utilisateur choisir le bon certificat dans la boîte de dialogue de sélection de certificat intégrée.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace CSP
{
    public static class BaseSmartCardCryptoProvider
    {
        private const string _providerName = "Microsoft Base Smart Card Crypto Provider";

        private static class NativeMethods
        {
            public const uint PROV_RSA_FULL = 0x00000001;
            public const uint CRYPT_VERIFYCONTEXT = 0xF0000000;
            public const uint CRYPT_FIRST = 0x00000001;
            public const uint CRYPT_NEXT = 0x00000002;
            public const uint ERROR_NO_MORE_ITEMS = 0x00000103;
            public const uint PP_ENUMCONTAINERS = 0x00000002;

            [DllImport("advapi32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
            public static extern bool CryptAcquireContext(
            ref IntPtr phProv,
            [MarshalAs(UnmanagedType.LPStr)] string pszContainer,
            [MarshalAs(UnmanagedType.LPStr)] string pszProvider,
            uint dwProvType,
            uint dwFlags);

            [DllImport("advapi32.dll", BestFitMapping = false, ThrowOnUnmappableChar = true, SetLastError = true)]
            public static extern bool CryptGetProvParam(
            IntPtr hProv,
            uint dwParam,
            [MarshalAs(UnmanagedType.LPStr)] StringBuilder pbData,
            ref uint pdwDataLen,
            uint dwFlags);

            [DllImport("advapi32.dll", SetLastError = true)]
            public static extern bool CryptReleaseContext(
            IntPtr hProv,
            uint dwFlags);
        }

        public static List<X509Certificate2> GetCertificates()
        {
            List<X509Certificate2> certs = new List<X509Certificate2>();

            X509Store x509Store = null;

            try
            {
                x509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
                x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

                List<string> containers = GetKeyContainers();

                foreach (string container in containers)
                {
                    CspParameters cspParameters = new CspParameters((int)NativeMethods.PROV_RSA_FULL, _providerName, container);
                    cspParameters.Flags = CspProviderFlags.UseExistingKey;
                    string pubKeyXml = null;
                    using (RSACryptoServiceProvider rsaProvider = new RSACryptoServiceProvider(cspParameters))
                        pubKeyXml = rsaProvider.ToXmlString(false);

                    foreach (X509Certificate2 cert in x509Store.Certificates)
                    {
                        if ((cert.PublicKey.Key.ToXmlString(false) == pubKeyXml) && cert.HasPrivateKey)
                            certs.Add(cert);
                    }
                }
            }
            finally
            {
                if (x509Store != null)
                {
                    x509Store.Close();
                    x509Store = null;
                }
            }

            return certs;
        }

        private static List<string> GetKeyContainers()
        {
            List<string> containers = new List<string>();

            IntPtr hProv = IntPtr.Zero;

            try
            {
                if (!NativeMethods.CryptAcquireContext(ref hProv, null, _providerName, NativeMethods.PROV_RSA_FULL, NativeMethods.CRYPT_VERIFYCONTEXT))
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                uint pcbData = 0;
                uint dwFlags = NativeMethods.CRYPT_FIRST;
                if (!NativeMethods.CryptGetProvParam(hProv, NativeMethods.PP_ENUMCONTAINERS, null, ref pcbData, dwFlags))
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                StringBuilder sb = new StringBuilder((int)pcbData + 1);
                while (NativeMethods.CryptGetProvParam(hProv, NativeMethods.PP_ENUMCONTAINERS, sb, ref pcbData, dwFlags))
                {
                    containers.Add(sb.ToString());
                    dwFlags = NativeMethods.CRYPT_NEXT;
                }

                int err = Marshal.GetLastWin32Error();
                if (err != NativeMethods.ERROR_NO_MORE_ITEMS)
                    throw new Win32Exception(err);

                if (hProv != IntPtr.Zero)
                {
                    if (!NativeMethods.CryptReleaseContext(hProv, 0))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    hProv = IntPtr.Zero;
                }
            }
            catch
            {
                if (hProv != IntPtr.Zero)
                {
                    if (!NativeMethods.CryptReleaseContext(hProv, 0))
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    hProv = IntPtr.Zero;
                }

                throw;
            }

            return containers;
        }
    }
}

Appelez simplement la méthode GetCertificates () de la classe fournie pour vérifier si ce code fonctionne avec votre carte:

List<X509Certificate2> certs = CSP.BaseSmartCardCryptoProvider.GetCertificates();
7
jariq

Je me demandais pourquoi vous faites pour chaque certificat dans un magasin lorsque vous connaissez le sujet. Ma suggestion serait:

public static byte[] Sign(Stream inData, string certSubject)
{

    // Access Personal (MY) certificate store of current user
    X509Store my = new X509Store(StoreName.My, StoreLocation.CurrentUser);
    my.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);

    var foundCerts = my.Certificates.Find(X509FindType.FindBySubjectName, certSubject, true);
    if (foundCerts.Count == 0)
        throw new Exception("No valid cert was found");

    var cert = foundCerts[0];
    RSACryptoServiceProvider csp = null;
    // let us assume that certSubject is unique
    if (cert.HasPrivateKey)
    {
        csp = (RSACryptoServiceProvider)cert.PrivateKey;
        if (csp.CspKeyContainerInfo.HardwareDevice)
            Console.WriteLine("hardware");
        Console.WriteLine(cert.ToString());
    }
    else
    {
        throw new Exception("No private key assigned to this certificate");
    }

    // Hash the data
    SHA1Managed sha1 = new SHA1Managed();
    byte[] hash = sha1.ComputeHash(inData);

    // Sign the hash
    return csp.SignHash(hash, CryptoConfig.MapNameToOID("SHA1"));
}

Si vous ne connaissez pas le sujet exact ou si vous espérez trouver un autre certificat portant sur ce sujet, cela ne fonctionnera probablement pas pour vous.

0
pepo