私のアプリケーションは、その場で仮想ディレクトリを作成するだけでなく、それらの仮想ディレクトリで実行される STS 対応の Web アプリケーション用のアプリケーション プールも作成します。アプリケーション プールは、ApplicationPoolIdentity アカウント (IIS APPPOOL\MyAppPool) で実行されます。そして、インストールされた証明書へのアクセスをプログラムで許可する方法を見つけようとしています。
私の最初のアプローチは、WinHttpCertCfg を実行するバッチ ファイルを使用することでした。ただし、このアプローチは、「アクティブ化」されたアプリ プール アカウントでのみ機能します。「アクティブ化」とは、新しいアプリケーションを少なくとも 1 回参照したことを意味します。これが発生するまで、WinHttpCertCfg は常に「ハンドルが無効です」というメッセージを返します。
私が試みた次のアプローチは、ここから得られた解決策に基づいていました。このソリューションは、MMC で証明書を参照して [証明書キーの管理] を選択すると、アプリ プール アカウントが一覧表示されるという意味で機能します。WinHttpCertCfg を実行してアクセス権のあるアカウントを一覧表示しても、新しいアプリ プールが一覧表示されます。
しかし、結局のところ... Webアプリケーションを参照すると、「キーセットが存在しません」というメッセージが表示されます。
私の焦点は、2番目のアプローチを修正することです。これが元のコードへの私の変更です
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;
}
}