문제

My application creates virtual directories on the fly as well as application pool for the STS-enabled web applications that run in those virtual directories. The application pools run under the ApplicationPoolIdentity account (IIS APPPOOL\MyAppPool). And I've been trying to figure out ways of programmatically grant access to the installed certificate.

My first approach was to use a batch file executing WinHttpCertCfg. However this approach will only work on app pool accounts that have been "activated". By "activated", I mean I have browsed to the new application at least once. Until this happens - WinHttpCertCfg always returns the message "The handle is invalid".

The next approach I tried was based on a solution obtained from here. This solution works in the sense that when I browse the certificate in MMC and select "Manage Certificate Keys", the app pool accounts are listed. Even when I run WinHttpCertCfg to list the accounts with access - the new app pools are listed.

But after all that...I still get "keyset does not exist" when I browse the web application.

My focus now is on fixing the second approach. Here is my modofication to the original code

public class CertificateHandler
{
    private const string CommonApplicationKeys = @"Microsoft\Crypto\RSA\MachineKeys";
    private const string PersonalKeys = @"Microsoft\Crypto\RSA\";
    private static X509Certificate2 _personalCertificate = null;
    private static X509Certificate2 _trustedCertificate = null;

    public CertificateHandler(string thumbPrint)
    {
        X509Store personalStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
        X509Store trustedStore = new X509Store(StoreName.TrustedPeople, StoreLocation.LocalMachine);

        //open the stores to locate the certificates and cache for future use
        if (_personalCertificate == null)
        {
            _personalCertificate = LoadCertificateFromStore(thumbPrint, personalStore);
        }

        if (_trustedCertificate == null)
        {
            _trustedCertificate = LoadCertificateFromStore(thumbPrint, trustedStore);
        }
    }

    /// <summary>
    /// Grants access to the specified certificate.
    /// </summary>
    /// <param name="thumbPrint">The thumb print of the certificate.</param>
    /// <param name="user">The domain qualified user.</param>
    public void GrantAccessToCertificate(string user)
    {
        //open the store to locate the certificate
        GrantAccessToCertificate(user, _personalCertificate);
        GrantAccessToCertificate(user, _trustedCertificate);
    }

    /// <summary>
    /// Grants access to the specified certificate.
    /// </summary>
    /// <param name="user">The domain qualified user.</param>
    /// <param name="certificate">The certificate to which access is granted</param>
    private void GrantAccessToCertificate(string user, X509Certificate2 certificate)
    {
        RSACryptoServiceProvider crypto = certificate.PrivateKey as RSACryptoServiceProvider;

        if (crypto != null)
        {
            //determine the location of the key
            string keyfilepath = FindKeyLocation(crypto.CspKeyContainerInfo.UniqueKeyContainerName);

            //obtain a file handle on the certificate
            FileInfo file = new FileInfo(Path.Combine(keyfilepath, crypto.CspKeyContainerInfo.UniqueKeyContainerName));

            //Add the user to the access control list for the certificate
            FileSecurity fileControl = file.GetAccessControl();
            NTAccount account = new NTAccount(user);
            fileControl.AddAccessRule(new FileSystemAccessRule(account, FileSystemRights.FullControl, AccessControlType.Allow));
            file.SetAccessControl(fileControl);
        }
    }

    /// <summary>
    /// Loads the certificate mathing the thumbprint from the specified store.
    /// </summary>
    /// <param name="thumbPrint">The thumb print of the certificate.</param>
    /// <param name="store">The store.</param>
    private X509Certificate2 LoadCertificateFromStore(string thumbPrint, X509Store store)
    {
        X509Certificate2 cert = null;

        try
        {
            //fetch the certificates in the store
            store.Open(OpenFlags.MaxAllowed);

            //locate by the specified thumbprint
            var results = store.Certificates.Find(X509FindType.FindByThumbprint, thumbPrint, true);

            if (results.Count > 0)
            {
                cert = results[0];
            }
            else
            {
                throw new FileNotFoundException("No certificate was found matching the specified thumbprint");
            }
        }
        finally
        {
            store.Close();
        }

        return cert;
    }

    /// <summary>
    /// Finds the key location.
    /// </summary>
    /// <param name="keyFileName">Name of the key file.</param>
    /// <returns></returns>
    private string FindKeyLocation(string keyFileName)
    {
        string location = string.Empty;

        //start the search from the common application folder
        string root = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
        string commonLocation = Path.Combine(root, CommonApplicationKeys);

        //filter for the key name
        var keys = Directory.GetFiles(commonLocation, keyFileName);

        if (keys.Length > 0)
        {
            location = commonLocation;
        }
        else
        {
            //now try the personal application folder
            root = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
            string privateLocation = Path.Combine(root, PersonalKeys);

            var subFolders = Directory.GetDirectories(privateLocation);

            if (subFolders.Length > 0)
            {
                foreach (string folder in subFolders)
                {
                    //filter for the key
                    keys = Directory.GetFiles(folder, keyFileName);

                    if (keys.Length != 0)
                    {
                        location = folder;
                    }
                }
            }
            else
            {
                throw new InvalidOperationException("Private key exists but is not accessible");
            }
        }

        return location;
    }
}
도움이 되었습니까?

해결책

I can now confirm that the code above actually works. The reason it appeared it wasn't working was that there was another app pool account to which I hadn't granted access to the certificate. Once that was done, it was all rosy from there on.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top