I'm trying to install a certificate in the Local Machine Store in a custom action.
The certificate is installed, but when I use it to query AWS, I get this error:
Object contains only the public half
of a key pair. A private key must also
be provided.
The installer is running elevated, the target is Windows Vista.
If I use a separate .exe to install the exact same certificate, using the exact same code, it works.
So what is it that differs when installing a certificate using the Windows Installer?
The code:
private void InstallCertificate(string certificatePath, string certificatePassword)
{
if (IsAdmin())
{
try
{
X509Certificate2 cert = new X509Certificate2(certificatePath, certificatePassword,
X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet);
X509Store store = new X509Store(StoreName.My, StoreLocation.LocalMachine);
store.Open(OpenFlags.ReadWrite);
store.Add(cert);
store.Close();
}
catch (Exception ex)
{
throw new DataException("Certificate appeared to load successfully but also seems to be null.", ex);
}
}
else
{
throw new Exception("Not enough priviliges to install certificate");
}
}
Well, at least this question earned me a tumble weed badge...
It turned out to be the permissions on the installed key file. I had to grant all users read permissions.
And here is the code I used to grant all (local) users read permissions:
private static void AddAccessToCertificate(X509Certificate2 cert)
{
RSACryptoServiceProvider rsa = cert.PrivateKey as RSACryptoServiceProvider;
if (rsa == null) return;
string keyfilepath = FindKeyLocation(rsa.CspKeyContainerInfo.UniqueKeyContainerName);
FileInfo file = new FileInfo(System.IO.Path.Combine(keyfilepath, rsa.CspKeyContainerInfo.UniqueKeyContainerName));
FileSecurity fs = file.GetAccessControl();
SecurityIdentifier sid = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
fs.AddAccessRule(new FileSystemAccessRule(sid, FileSystemRights.Read, AccessControlType.Allow));
file.SetAccessControl(fs);
}
private static string FindKeyLocation(string keyFileName)
{
string pathCommAppData = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), @"Microsoft\Crypto\RSA\MachineKeys");
string[] textArray = Directory.GetFiles(pathCommAppData, keyFileName);
if (textArray.Length > 0) return pathCommAppData;
string pathAppData = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Microsoft\Crypto\RSA\");
textArray = Directory.GetDirectories(pathAppData);
if (textArray.Length > 0)
{
foreach (string str in textArray)
{
textArray = Directory.GetFiles(str, keyFileName);
if (textArray.Length != 0) return str;
}
}
return "Private key exists but is not accessible";
}