Private keys get deleted unexpectedly in Windows Server 2008 R2 - powershell

I am facing a strange problem in developing an installation that should in one of the steps install a certificate.
The problem has to do with granting Certificate’s private key access for an account (e.g. IIS_IUSRS) on Windows Server 2008 R2. The private keys are stored in the location C:\Users\All Users\Microsoft\Crypto\RSA\MachineKeys.
A custom C# Setup Project imports a Certificate and gives access for an account on Certificate’s private key during the installation process. After some time (2-3 sec) the private key file is automatically deleted from the MachineKeys folder. Thus the installed Web Application cannot access the specific certificate and displays the following error message:
“System.Security.Cryptography.CryptographicException: Keyset does not exist”. This error occurs only on Windows Server 2008 R2, while for Windows Server 2003 everything is working correctly.
My question is, why the private key gets deleted and which process does this?
Thx
UPDATE 17/05/2012
I have not yet found a solution to the described problem, and no response has been posted on the other forums where I asked (forums.asp.net, social.msdn.microsoft.com). So, can anyone suggest any other resources or advice for further troubleshooting this issue?
Thanks again

This was happening to me too - my setup script would add the cert and grant access to the PK file fine, and the app would work. Then later, after I had closed the PowerShell editor I re-launched the app and it failed with a keyset not found.
Adding the PersistKeySet flag when importing the cert fixed the problem. Here's the PowerShell code for adding the cert and private key with persistence:
param(
[string]$certStore = "LocalMachine\TrustedPeople",
[string]$filename = "sp.pfx",
[string]$password = "password",
[string]$username = "$Env:COMPUTERNAME\WebSiteUser"
)
function getKeyUniqueName($cert) {
return $cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
}
function getKeyFilePath($cert) {
return "$ENV:ProgramData\Microsoft\Crypto\RSA\MachineKeys\$(getKeyUniqueName($cert))"
}
$certFromFile = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($filename, $password)
$certFromStore = Get-ChildItem "Cert:\$certStore" | Where-Object {$_.Thumbprint -eq $certFromFile.Thumbprint}
$certExistsInStore = $certFromStore.Count -gt 0
$keyExists = $certExistsInStore -and ($certFromStore.PrivateKey -ne $null) -and (getKeyUniqueName($cert) -ne $null) -and (Test-Path(getKeyFilePath($certFromStore)))
if ((!$certExistsInStore) -or (!$keyExists)) {
$keyFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet
$keyFlags = $keyFlags -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$certFromFile.Import($filename, $password, $keyFlags)
$store = Get-Item "Cert:\$certStore"
$store.Open("ReadWrite")
if ($certExistsInStore) {
#Cert is in the store, but we have no persisted private key
#Remove it so we can add the one we just imported with the key file
$store.Remove($certFromStore)
}
$store.Add($certFromFile)
$store.Close()
$certFromStore = $certFromFile
"Installed x509 certificate"
}
$pkFile = Get-Item(getKeyFilePath($certFromStore))
$pkAcl = $pkFile.GetAccessControl("Access")
$readPermission = $username,"Read","Allow"
$readAccessRule = new-object System.Security.AccessControl.FileSystemAccessRule $readPermission
$pkAcl.AddAccessRule($readAccessRule)
Set-Acl $pkFile.FullName $pkAcl
"Granted read permission on private key to web user"

Is very clear that is a security issue “System.Security.”. and you do not have permissions to do the installation., you need to set the permissions on the private key to allow that service account access it.
Edit later: Go to Start->Run->cmd->type mmc->Select File->Add/Remove->Select Certificates->Add->Computer Account->Local., i attach a screenshot is in spanish but I indicated the fields:
Open->Certificates->Personal->Certificates->Right click Certificate-> All Tasks->Manage Private Keys->Add Network Service.
Also check this entry to see how works this feature in Windows Server 2008., then please after you try it, come back and say if you could solve the issue with what I have told you.

http://referencesource.microsoft.com/#System/security/system/security/cryptography/x509/x509certificate2collection.cs,256 shows where the PersistKeySet flag is tested. The PersistKeySet flag is documented at https://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.x509keystorageflags%28v=vs.110%29.aspx with the phrase "The key associated with a PFX file is persisted when importing a certificate." My techno-babble to English translator tells me this means "You must include the PersistKeySet flag if you call the X509Certificate2 constructor and the certificate might already be installed on the machine." This probably applies to the .Import calls too. It's likely the powershell Import-PfxCertificate cmdlet already does this. But if you are doing what the accepted answer shows or what the OP asked, you need to include the special key. We used a variation of ejegg's script in our solution. We have a process that runs every 3 minutes to check that all configured certs are installed and this seems to work fine now.
The symptom we saw in powershell is the HasPrivateKey property is true but the PrivateKey value itself is null. And the key file for the cert in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys was deleted. The FindPrivateKey utility at https://msdn.microsoft.com/en-us/library/aa717039(v=vs.110).aspx helped us watch file get deleted.
So happy 4th birthday to the question with this very late response.

Related

IIS 10 - Import SSL certificate using Powershell - "A specified logon session does not exist"

Importing a .pfx-file to IIS using Powershell is pretty straight forward thanks to guidelines such as this one Use PowerShell to install SSL certificate on IIS. But I do run into an issue when trying to bind port 443 using the imported certificate:
Error: "A specified logon session does not exist. It may already have been terminated. (Exception from HRESULT: 0x80070520)".
This due to "...If you don't already have a cer version, or you do but it includes the private key, enable Allow this certificate to be exported..." (ref. Setup of SharePoint 2013 High-Trust On-premise Add-In Developer / Production environment)
This is how it is set in the GUI
But, looking at the following line in the code which I got from dejanstojanovic.net.
pfx.Import($certPath,$certPass,"Exportable,PersistKeySet")
it is set to Exportable. Removing PersistKeyset does not make a difference. So what could causing this?
The script is not able to set it to Exportable as in the GUI "Allow this certificate to be exported"
...I'm all out of options...
Update
I did tweak the code a bit, using constants and such, but still same issue
$certPath = "D:\ssl\cert-export-to-iis-10.pfx"
$certPass = "password"
$pfx = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
$KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable -bxor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet
$pfx.Import($certPath,$certPass,$KeyStorageFlags)
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store("WebHosting","LocalMachine")
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($pfx)
$store.Close()
$store.Dispose()

DSC problems with Credentials and build 10586

The latest windows 10 build pushed out an updated version of powershell (10586).
In addition to the change required for the certificate documented at https://dscottraynsford.wordpress.com/2015/11/15/windows-10-build-10586-powershell-problems/ i seem to have an additional problem, while trying to apply the configuration:
WarningMessage An error occured while applying the partial configuration [PartialConfiguration]ExternalIntegrationConfiguration. The error message is :
Decryption failed..
Using the same certificate I can successfully create a MOF with build 10.0.10240.16384 , and successfully apply it. So looking at the difference between the two MOFs I see that the MOF built by build 10586 looks like:
instance of MSFT_Credential as $MSFT_Credential6ref
{
Password = "-----BEGIN CMS-----
\nBase64 encrypted
\n-----END CMS-----";
UserName = "SomeUser";
};
instead of what it used to be like in build (10.0.10240.16384):
instance of MSFT_Credential as $MSFT_Credential6ref
{
Password = "Base64 encrypted";
UserName = "SomeUser";
};
So the content is different. I did check to see whether I could decrypt the credential using Get-CmsMessage and unprotect-CmsMessage, and I could. So the public/private key stuff appears to be good.
Should there be an update to the machine that the configuration is being applied to? I don't see any new powershell build.
Any ideas would be appreciated.
Update 2015-12-18: Installing Windows Management Framework (WMF) 5.0 RTM edition that was released 2015-12-17 on the nodes being configured will resolve this error. WMF 5.0 can be downloaded here.
MS has changed the Get-EncryptedPassword function in the PSDesiredStateConfiguration to generate the new format for Password field in a MOF. This prevents DSC nodes from decrypting the password if WMF has not been upgraded to support it. But as MS has not released an update to allow WMF to read this new format then this should be considered a completely broken release.
I have managed to find a work around:
Copy the PSDesiredStateConfiguration module from a pre 10586 machine (e.g. Windows Server 2012 R2 with latest WMF 5.0) to the PowerShell modules folder on the Built 10586 machine.
E.g.
Replace the C:\Windows\System32\WindowsPowerShell\v1.0\Modules\PSDesiredStateConfiguration folder with an older version
Note: You'll need to take ownership of this folder and give yourself permission to write into it because by default only TrustedInstaller can write to this folder.
As far as I'm concerned this version of PSDesiredStateConfiguration is completely broken and you're better off rolling it back until MS can fix it. This will also fix some other reported problems (module versions, new certificate Policy requirements).
FYI, here is the changed code that changes the credential encryption:
Old code in PSDesiredStateConfiguration.psm1:
# Cast the public key correctly
$rsaProvider = [System.Security.Cryptography.RSACryptoServiceProvider]$cert.PublicKey.Key
# Convert to a byte array
$keybytes = [System.Text.Encoding]::UNICODE.GetBytes($Value)
# Add a null terminator to the byte array
$keybytes += 0
$keybytes += 0
try
{
# Encrypt using the public key
$encbytes = $rsaProvider.Encrypt($keybytes, $false)
# Reverse bytes for unmanaged decryption
[Array]::Reverse($encbytes)
# Return a string
[Convert]::ToBase64String($encbytes)
}
catch
{
if($node)
{
$errorMessage = $LocalizedData.PasswordTooLong -f $node
}
else
{
$errorMessage = $LocalizedData.PasswordTooLong -f 'localhost'
}
$exception = New-Object -TypeName System.InvalidOperationException -ArgumentList $errorMessage
Write-Error -Exception $exception -Message $errorMessage -Category InvalidOperation -ErrorId PasswordTooLong
Update-ConfigurationErrorCount
}
New code in PSDesiredStateConfiguration.psm1:
# Encrypt using the public key
$encMsg =Protect-CmsMessage -To $CmsMessageRecipient -Content $Value
# Reverse bytes for unmanaged decryption
#[Array]::Reverse($encbytes)
#$encMsg = $encMsg -replace '-----BEGIN CMS-----',''
#$encMsg = $encMsg -replace "`n",''
#$encMsg = $encMsg -replace '-----END CMS-----',''
return $encMsg

Desired State Configuration credential private key not acquired

I'm trying to use powershell DSC for a few things. I wanted to have the passed credentials encrypted per the instructions at http://technet.microsoft.com/en-us/library/dn781430.aspx it all seems to work fine until I run start-DscConfiguration on the target node and i get the error:
The private key could not be acquired.
+ CategoryInfo : NotSpecified: (root/Microsoft/...gurationManager:String) [], CimException
+ FullyQualifiedErrorId : MI RESULT 1
+ PSComputerName : DmitriyDev
Going back I checked to see that the mof contains the credentials encrypted and the meta.mof contains the matching thumbprint, etc.
going back to the original article i see the example code:
# Get the certificate that works for encryption
function Get-LocalEncryptionCertificateThumbprint
{
(dir Cert:\LocalMachine\my) | %{
# Verify the certificate is for Encryption and valid
if ($_.PrivateKey.KeyExchangeAlgorithm -and $_.Verify())
{
return $_.Thumbprint
}
}
}
When I test my certificate using this code (on the target node) I see that the PrivateKey of the certificate is null. I'm not sure how the certificate is null. Trying a few things with certutil and the technique mentioned http://blogs.technet.com/b/vishalagarwal/archive/2010/03/30/verifying-the-private-key-property-for-a-certificate-in-the-store.aspx it seems that I do indeed have a private key, however Powershell see it only as null.
On the target node, I even exported the public private key manually and reimported them, with no luck as outlined in another dsc tutorial.
I also tried using procmon to see what the problem was on the target node. I see the wmiprvse process and see that it runs as System (as expected), and I checked to make sure that the permissions on the private key allowed for system (all on the target node)
So my question is how do I get my private key to be used by DSC specifically the LCM on the target node? Or how do I diagnose the problem more?
I had a similar error when using New-SelfSignedCertificate to create my certificates. For anyone with similar issues, I suspect the problem is related to the storage provider used by New-SelfSignedCertificate (see http://blogs.technet.com/b/vishalagarwal/archive/2010/03/30/verifying-the-private-key-property-for-a-certificate-in-the-store.aspx, which talks about a problem with the Microsoft Software Key Storage Provider and .NET classes). There's a powershell script available on technet that creates self-signed certificates, and defaults to using a different storage provider, which solved the problem for me.
Okay, i'm not sure exactly why this works, but it does. Using the Computer template seems to work. In terms of work, powershell on the target node can see it's private key from
dir cert:\LocalMachine\My | ? PrivateKey -ne $null
Once that happens it all works as expected. So long story short is don't use the workstation Auth template but the Computer template.

Powershell Script to Install Certificate Into Active Directory Store

I'm trying to write a powershell script to install a certificate into the active directory certificate store,
Here are the steps to do this manually, any help would be greatly appreciated.
On a Windows 2008R2 domain controller,
Click Start -> Run
type MMC
click ok
Click File -> Add/Remove Snap-In
Select "Certificates" -> Add
Select "Service Account"
Click Next
Select "Local Computer"
Click Next
Select "Active Directory Domain Services"
Click Finish
Click Ok
I want the script to install the certificate into :
NTDS\Personal
I would post an image but I don't have enough "reputation" apparently, so I can only provide text instructions.
So basically what I've tried is, I've used this powershell function below to import a certificate into the Local Machine -> Personal Store, which is where most certificates go, and the code works.
But I need to install the certificate into the "NTDS\Personal" store on a domain controller, but the $certRootStore only accepts localmachine or CurrentUser, so I'm stuck : /
function Import-PfxCertificate
{
param
(
[String]$certPath,
[String]$certRootStore = "localmachine",
[String]$certStore = "My",
$pfxPass = $null
)
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
if ($pfxPass -eq $null)
{
$pfxPass = read-host "Password" -assecurestring
}
$pfx.import($certPath,$pfxPass,"Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()
}
Import-PfxCertificate -certPath "d:\Certificate.pfx"
Regards Alex
Using a combination of what you already had above and the registry keys for the two certificate stores this works.
The only other thing is that I don't know how NTDS determines which certificate to use when there are multiple in the certificate store.
function Import-NTDSCertificate {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$PFXFile,
[Parameter(Mandatory)]
[string]$PFXPassword,
#Remove certificate from LocalMachine\Personal certificate store
[switch]$Cleanup
)
begin{
Write-Verbose -Message "Importing PFX file."
$PFXObject = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$PFXObject.Import($PFXFile,$PFXPassword,[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
$thumbprint = $PFXObject.Thumbprint
}
process{
Write-Verbose -Message "Importing certificate into LocalMachine\Personal"
$certificateStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store('My','LocalMachine')
$certificateStore.Open('MaxAllowed')
$certificateStore.Add($PFXObject)
$certificateStore.Close()
Write-Verbose -Message "Copying certificate from LocalMachine\Personal to NTDS\Personal"
$copyParameters = #{
'Path' = "HKLM:\Software\Microsoft\SystemCertificates\MY\Certificates\$thumbprint"
'Destination' = "HKLM:\SOFTWARE\Microsoft\Cryptography\Services\NTDS\SystemCertificates\My\Certificates\$thumbprint"
'Recurse' = $true
}
Copy-Item #copyParameters
}
end{
if ($Cleanup){
Write-Verbose -Message "Removing certificate from LocalMachine\Personal"
$removalParameters = #{
'Path' = "HKLM:\SOFTWARE\Microsoft\SystemCertificates\MY\Certificates\$thumbprint"
'Recurse' = $true
}
Remove-Item #removalParameters
}
}
}
Alright, first the bad news. The only managed certificate stores are LocalMachine and CurrentUser, as we have all seen in powershell.
Now, the not so bad news. We know that the 'physical' location store (physical is MS' word, not mine) exists in the registry on the ADDS server, HKLM\Software\Microsoft\Cryptography\Services\NTDS\SystemCertificates. This was dually verified by both
Using procmon while importing a certificate into the store using the mmc snap-in
Scavenging msdn for this nugget
The link in #2 shows that all physical stores for services are stored in the path mentioned above, substituting NTDS for . The real service name, not the display name.
However,
Because of the bad news. Trying to map it in powershell with that reg key as the root and -PSProvider Certificate will prove disappointing, it was the first thing I tried.
What one can try, is using the X509Store constructor that takes an IntPtr to a SystemStore, as described here. Yes, that invovles some unmanaged code, and mixing the two is something I do rarely, but this and googling for HCERTSTORE C# should get you there.
Even though this post is years old, it is still helpful and turns up in searches, so to address the question of "I don't know how NTDS determines which certificate to use when there are multiple in the certificate store", the answer is that you will get unreliable results when there are two or more valid certificates installed that meet the requested criteria so it is recommended to remove the old/unneeded certificate(s) and just leave the newest/best one for the server auth.

Strange outcome from running Importpfx.exe to import certificates

I am writing a Powershell script to automate the setting up of a Windows 2008 R2 server and one thing that is required is the importing of several certificates into different stores. After doing some research on how best to achieve this, I found that Importpfx.exe was the best choice for what I am aiming to do, which is import one .pfx file into the Trusted People store and another .pfx file into the Personal store, both for the Computer account. I then also need to Manage Private keys on the certificate imported into the Personal store once it has been imported.
At first, I thought that Importpfx.exe was doing this correctly, but after researching on how to manage the private keys via Powershell, I learned that this can be done my editing the acl for the file that corresponds to the imported certificate which should be found here "C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys". This is where I started to notice that something wasn't quite right with the imported certificate. After searching this folder for a new file after importing the certificates, I noticed that no new files had been added to this folder.
I searched the entire C drive for all files sorted by date modified and found that new files had been added to this folder "C:\Users\'user'\AppData\Roaming\Microsoft\Crypto\RSA\S-1-5-21-2545654756-3424728124-1046164030-4917" instead of the expected folder. Whilst I was able to manually manage private keys for the certificate via the certificate store (as I was user who imported it), no other users were able to log onto the machine and manage the private keys, getting the error message "Cannot find the certificate and private key for decryption" (which would make sense given the folder that the corresponding file exists in).
I use a function to get the thumbprint of the certificates before trying to import the .pfx file. The code I have used to run is:
function GetCertificateThumbprint ( [string]$certPreFix, [string]$certPassword, [string]$certFolder, [string]$domain, [bool]$addIfNotFound, [hashtable]$return)
$storePath = "cert:\LocalMachine"
$storeDir = "My"
$storeName = [System.Security.Cryptography.X509Certificates.StoreName]::My
if($certPreFix -eq "XXX")
{
$storeDir = "TrustedPeople"
$storeName = [System.Security.Cryptography.X509Certificates.StoreName]::TrustedPeople
}
$storePath = [System.IO.Path]::Combine($storePath, $storeDir)
#Build the certificate file name and get the file
$certFileName = $certPreFix + "." + $domainName + ".*"
$certFile = Get-ChildItem -Path $certFolder -Include $certFileName -Recurse
if ($certFile)
{
# The certificate file exists so get the thumbprint
$Certificate = New-Object system.Security.Cryptography.X509Certificates.X509Certificate2($certFile, $certPassword)
$certThumbprint = $Certificate.Thumbprint
if($addIfNotFound)
{
# Check for the certificate's thumbprint in store and add if it does not exist already
if(-not(Get-ChildItem $storePath | Where-Object {$_.Thumbprint -eq $certThumbprint}))
{
Set-Location "$Env:windir\Tools"
.\importpfx.exe -f $certFile -p $certPassword -t MACHINE -s $storeDir
}
}
}
Can anyone see if I have done anything wrong? Has anyone come across this issue and got around it somehow? This is causing me issues as I cannot automate the Manage Private keys task properly!
I just ran in to the same problem. You must specify the MachineKeySet X509KeyStorageFlag when creating the certificate object:
New-Object system.Security.Cryptography.X509Certificates.X509Certificate2($certFile, $certPassword, "PersistKeySet,MachineKeySet")
Hopefully that helps someone.