GetRSAPrivateKey UniqueName returns empty string - powershell

I am generating self signed certifcate using powershell script and importing it to the certificate store. Now I need to assign a new user in manage private keys section for the certificate.
I was trying the following code sample to do so.
$certificate = (import the certificate)
## Identify the user you'll be granting permission to
$grantee_name = 'dev\Batman'
$grantee = New-Object System.Security.Principal.NTAccount($grantee_name)
## Get the location and permission-of the cert's private key
$privatekey_rsa = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($certificate)
$privatekey_file_name = $privatekey_rsa.key.UniqueName
$privatekey_path = "${env:ALLUSERSPROFILE}\Microsoft\Crypto\Keys\${privatekey_file_name}"
$privatekey_file_permissions = Get-Acl -Path $privatekey_path
## Grant the user 'read' access to the key
$access_rule = New-Object System.Security.AccessControl.FileSystemAccessRule($grantee, 'Read', 'None', 'None', 'Allow')
$privatekey_file_permissions.AddAccessRule($access_rule)
Set-Acl -Path $privatekey_path -AclObject $privatekey_file_permissions
In my case
$privatekey_file_name = $privatekey_rsa.key.UniqueName
returns empty string.
Attaching screenshot as well.

When you specify -path and -password parameters, private key is not persisted and the certificate is not installed. In order to install the certificate, you need to remove mentioned parameters and use -StoreLocation parameter to indicate in which store the certificate must be installed.

Related

How do I extract child registry keys from a dynamic HKEY_USERS\SID into a reg file, and replace the SID in the regfile using Powershell

I am writing a backup/restore script for powershell that allows users backup and restore various settings before and after windows installations. One example includes HKEY_USERS\S-1-5-21-3096151585-4241824952-1105149943-1008\SOFTWARE\7 Taskbar Tweaker\Options
How do I save this key recursively as a reg file, just for example with reg export <keyname> <filename> [/y] or another export mechanism, without manually inputting the sid? The SID is dynamic on fresh installations, so I must also replace the SID in the reg file to ensure proper importation.
$User = New-Object System.Security.Principal.NTAccount($env:UserName)
$sid = $User.Translate([System.Security.Principal.SecurityIdentifier]).value
The above snippet gives you the SID of the logged-in user. This when appended to the HKEY_USERS givs you the right path for that username.
New-PSDrive HKU Registry HKEY_USERS
Get-Item "HKU:\${sid}"
This pulls the sid of the current user;
$content = [System.IO.File]::ReadAllText("c:\temp\test.reg").Replace("anoose","$sid"); [System.IO.File]::WriteAllText("c:\temp\test.reg", $content)
This replaces anoose with the current SID, but how do I use a regex pattern to replace an old SID with the current user sid?
I tried the following two regex strings which should locate SID /^S-1-[0-59]-\d{2}-\d{10}-\d{10}-\d{8}-[1-9]\d{3}/ values, /^S-1-[0-59]-\d{2}-\d{10}-\d{10}-\d{8}-[1-9]\d{3}/, or ^S-\d-\d+-(\d+-){1,14}\d+$, or ^S-\d-(\d+-){1,14}\d+$ or S-\d-\d-\d+-\d+-\d+-\d+-\w+, none of these appear to work in my script; ex, $r = [regex] "/^S-1-[0-59]-\d{2}-\d{10}-\d{10}-\d{8}-[1-9]\d{3}/"...
"Replace" tries to replace the regex pattern itself.
Is this what you want?
#get sid from username
$User = New-Object System.Security.Principal.NTAccount($env:UserName)
$sid = $User.Translate([System.Security.Principal.SecurityIdentifier]).value
#set export filename
$filename = "$env:SystemDrive\Export\$($env:UserName).reg"
#key to export, without HIVE or SID e.g. SOFTWARE\Microsoft\Windows
$key = "SOFTWARE\7 Taskbar Tweaker\Options"
#check whether the user hive is loaded
if (Test-Path "Registry::HKEY_USERS\$sid" -PathType Container) {
#it is loaded, check the key
if (Test-Path "Registry::HKEY_USERS\$sid\$key") {
Write-Output "The specified key was found under: $env:UserName. Exporting..."
& "$env:windir\system32\reg.exe" "EXPORT `"HKEY_USERS\$sid\$key`" `"$filename`" /y"
}
else {
Write-Output "The specified key does not exist under: $env:UserName"
}
}
Else {
#it isnt loaded, load it
& "$env:windir\system32\reg.exe" "LOAD HKU\TEMP `"$env:SystemDrive\Users\$env:UserName\NTUSER.DAT`""
#export the key if the key exists
if (Test-Path "Registry::HKU\TEMP\$key") {
Write-Output "The specified key was found under: $env:UserName. Exporting..."
& "$env:windir\system32\reg.exe" "EXPORT `"HKU\TEMP\$key`" `"$filename`" /y"
}
else {
Write-Output "The specified key does not exist under: $env:UserName"
}
#unload it
& "$env:windir\system32\reg.exe" "LOAD HKU\TEMP"
}

Import-PfxCertificate no-ops, but no error or anything

I'm trying to import a PFX file into the local certificate store. However, Import-PfxCertificate just does nothing at all. No return value, no error, nothing:
I can double click on the PFX file in Explorer and import it with the same password, which works. Something about the PowerShell CmdLet isn't working. I've also tried other stores, such as Cert:\LocalMachine\My and TrustedPeople. Running it with -Verbose -Debug doesn't show anything extra. Nothing in the Application or Security event logs either. I'm also running as an admin. Ideas?
The Pfx file might have a cert chain. Treating it as a collection would be a better way of handling the certificate store. See installing cert chain for the C# this was based off;
[string] $certPath = '.\test.pfx';
[string] $certPass = 'MyPassword';
# Create a collection object and populate it using the PFX file
$collection = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new();
$collection.Import($certPath, $certPass, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet);
try {
# Open the Store My/Personal
$store = [System.Security.Cryptography.X509Certificates.X509Store]::new('My');
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite);
foreach ($cert in $collection) {
Write-Host ("Subject is: '{0}'" -f $cert.Subject )
Write-Host ("Issuer is: '{0}'" -f $cert.Issuer )
# Import the certificate into an X509Store object
# source https://support.microsoft.com/en-au/help/950090/installing-a-pfx-file-using-x509certificate-from-a-standard-net-applic
if ($cert.Thumbprint -in #($store.Certificates | % { $_.Thumbprint } )) {
Write-Warning "Certificate is already in the store"
# Force the removal of the certificate so we have no conflicts, not required if this is the first install
$store.Remove($cert)
}
# Add in the certificate
$store.Add($cert);
}
} finally {
if($store) {
# Dispose of the store once we are done
$store.Dispose()
}
}

Export certificate from object with private key Export-Clixml

I'm trying to store a few objects as Clixml from PowerShell.
I can successfully store my array and certificate, but the private key is not exported with it.
Example:
PS > $cert.HasPrivateKey
true
PS > $outObject = $array
PS > $outObject += $cert
PS > $outObject | Export-Clixml -Path .\file.xml
PS > $inObject = Import-Clixml -Path .\file.xml
PS > $newcert = $inObject | Where-Object { $_.GetType().Name -like "*X509Certificate2" }
PS > $newcert.HasPrivateKey
false
I noted that there is a method for $cert.PrivateKey:
ExportParameters Method System.Security.Cryptography.RSAParameters ExportParameters(bool includePrivateParameters)
This script is not specifically running in Windows and the certificate isn't installed in the CABI store, only the variable imported from Get-PfxCertificate.
Long story short, I'm building a module that connects to an API with client authentication. I'm trying to pull client authentication from the Clixml file.
The private key is not a part of X509Certificate2 object, thus it is not exported along with the public certificate. The private key is linked to the public certificate.
In order to export a certificate with a private key, you have to serialize the certificate and private key object before passing it to Export-CliXml.
Use the X509Certificate2.Export(X509Content​Type, Secure​String) method to export the certificate with the associated private key to PFX (PKCS#12 container). The private key material is password-protected.
Use the X509Certificate2.Import(Byte[], Secure​String, X509Key​Storage​Flags) method to import the certificate and associated private key after calling the Import-CliXml cmdlet.
This is the only option you have. Also, be aware that this approach works only when the private key is exportable. If the private key is non-exportable, the Export method will fail.
By converting the certificate object to PFX format (as suggested by Crypt32) and saving my objects in a hash table I was able to successfully export and import the array and certificate with private key.
PS > $cert.HasPrivateKey
true
PS > $pfx = $cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx,'Pa$$w0rd')
PS > $outObject = #{
>> myArray = $array
>> myCert = $pfx
>> }
PS > Export-Clixml -InputObject $outObject -Path .\file.xml
PS > $inObject = Import-Clixml -Path .\file.xml
PS > $newCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::New($inObject.myCert,'Pa$$w0rd')
PS > $newCert.HasPrivateKey
true

Grant IIS 7.5 Application Pool Read Permission to Certificate Private Key by Using PowerShell

I searched all around and could not find a lot of information, basically I have Windows 2008 R2, I created PowerShell script to load a PFX file to certificate store of Local Machine already.
Now I need to grant permission of my app pool to read the private key of the certificate by using PowerShell.
In the old way Windows 2003, I just need to get the actual file sitting in C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\ folder, but it looks like Win 2008 uses a different folder.
Anybody has some solution?
-- Update my version of code --
function Grant-CertificatePermissions([string]$certSubject,[string]$user,[string]$permissionType,[string]$permission = $args[3])
{
$getCert = Get-LocalMachineCertificate $certSubject
$keypath = Get-CertificateStorePath
$certHash = $getCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
$certFullPath = $keypath+$certHash
$certAcl = Get-Acl -Path $certFullPath
try
{
$accessRule=new-object System.Security.AccessControl.FileSystemAccessRule $user, $permissionType, $permission
$certAcl.AddAccessRule($accessRule)
}
catch [System.Exception]
{
throw "Invalid User Id Or Permission"
}
Set-Acl $certFullPath $certAcl
}
function Get-LocalMachineCertificate([string]$subject, [string]$certificateStoreLocation, [string]$certificateStoreName)
{
$getCert = Get-ChildItem -Recurse Cert:\$certificateStoreLocation\$certificateStoreName | Where-Object {$_.Subject -eq $subject}
if(!$getCert)
{
throw "Certificate Not Found"
}
return $getCert
}
function Get-CertificateStorePath
{
$commonCertPathStub = "\Microsoft\Crypto\RSA\MachineKeys\"
$programData = $Env:ProgramData
if(!$programData)
{
$programData = $Env:ALLUSERSPROFILE + "\Application Data"
}
$keypath = $programData + $commonCertPathStub
return $keypath
}
In my Get-CertificateStorePath function I get value as C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\, after I get certificate hash, the complete file looks like C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d82829f7770ea5d85ef978dea67f302d_4cca7190-7e9f-46d7-b180-6656fec432e2, when I execute Get-Acl line I have exception Cannot find path 'C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys\d82829f7770ea5d85ef978dea67f302d_4cca7190-7e9f-46d7-b180-6656fec432e2' because it does not exist..
I browsed that folder, I indeed could not find such a file.
-- Update --
function Import-PfxCertificate ([String]$certPath,[String]$certificateStoreLocation ,[String]$certificateStoreName, $pfxPassword)
{
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
$pfx.Import($certPath, $pfxPassword, "Exportable,PersistKeySet")
$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certificateStoreName,$certificateStoreLocation)
$store.open("MaxAllowed")
$store.add($pfx)
$store.close()
return $pfx
}
2008 R2 uses C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys
Via PowerShell you can see the certs available to IIS here:
cert:\LocalMachine\My
You can cd into that location and look for your cert. Once you find it you can view its private key ID using:
$cert = get-item 2779B37AE3625FD8D2F9596E285C7CDC15049D87
$cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName
This will contain the long hexadecimal filename from the MachineKeys folder.
You can then change the file permissions using the Set-Acl cmdlet.
You can also view the permissions via the Certificates MMC mmc/add snapin/certificates/computer account/local computer and then certificates/personal/certificates/[your cert]/all tasks/manage private keys
If you drag and drop a certificate from User Store\Personal to Computer Store\Personal, the $getCert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName value is wrong. There is no file with that value. Import the certificate again in the right store and it will work.
here is a complete powershell script to give permission to any user on certificate's private key.
How to Grant permission to user on Certificate private key using powershell?
Above link has script and example how to run it on powershell console window.
If you are using ApplicationPoolIdentity then you username will be 'IIS AppPool\AppPoolNameHere'
Note: You will need to use ' ' as there is a space between IIS and AppPool.

Powershell: How do you set the Read/Write Service Principal Name AD Permissions?

In Powershell, how do you set the Read/Write Service Principal Name AD user permissions?
Normally during my build process, I use ADSIedit to navigate to that object, and then go through all the security tabs to get down to put a checkmark next to:
Read Service Principal Name
Write Service Principal Name
But navigating through ADSIedit can take a long time so I'm trying to script the process. If I have a PowerShell LDAP bind with a new user created, how can I use PowerShell to set both of these properties for this user account?
The following is a hacked out code-snippet of the possible pertinent portions of my install script:
$strDomain = "dc=my,dc=com"
$objDomain = [ADSI] "LDAP://" + strDomain
$strSCCMSQLPW = Read-Host -assecurestring "Please enter a password for the " + $strSCCMSQL + " account: "
New-ADUser -SamAccountName $strSCCMSQL + -Name $strSCCMSQL -AccountPassword $strSCCMSQLPW -Enabled $true -Path $strUsersOU + "," + $strDomain -PasswordNeverExpires $true
You need to add an ActiveDirectoryAccessRule object to the ACL of the target object. For setting property specific rigths the trick is to pass in the schemaIDGUID to the attribute. So first we need to find the schemaIDGUID from the Service-Principal-Name schema entry. In the sample code i statically refer to the Service-Principal-Name, better yet would have been to search for the ldapDisplayname to find the entry but I'm sure you can sort that out. In any case this code should do the job:
Function Set-SpnPermission {
param(
[adsi]$TargetObject,
[Security.Principal.IdentityReference]$Identity,
[switch]$Write,
[switch]$Read
)
if(!$write -and !$read){
throw "Missing either -read or -write"
}
$rootDSE = [adsi]"LDAP://RootDSE"
$schemaDN = $rootDSE.psbase.properties["schemaNamingContext"][0]
$spnDN = "LDAP://CN=Service-Principal-Name,$schemaDN"
$spnEntry = [adsi]$spnDN
$guidArg=#("")
$guidArg[0]=$spnEntry.psbase.Properties["schemaIDGUID"][0]
$spnSecGuid = new-object GUID $guidArg
if($read ){$adRight=[DirectoryServices.ActiveDirectoryRights]"ReadProperty" }
if($write){$adRight=[DirectoryServices.ActiveDirectoryRights]"WriteProperty"}
if($write -and $read){$adRight=[DirectoryServices.ActiveDirectoryRights]"readproperty,writeproperty"}
$accessRuleArgs = $identity,$adRight,"Allow",$spnSecGuid,"None"
$spnAce = new-object DirectoryServices.ActiveDirectoryAccessRule $accessRuleArgs
$TargetObject.psbase.ObjectSecurity.AddAccessRule($spnAce)
$TargetObject.psbase.CommitChanges()
return $spnAce
}
Sample lines for calling the function...
$TargetObject = "LDAP://CN=User,OU=My User Org,DC=domain,DC=net"
$Identity = [security.principal.ntaccount]"domain\user"
Set-SpnPermission -TargetObject $TargetObject -Identity $Identity -write -read
Here is an example using Quest to set the permissions on the service principal name attributes.
First, add Quest:
Add-PSSnapin Quest.ActiveRoles.ADManagement;
Set the permission (using Add-QADPermission):
Get-QADUser UserName | Add-QADPermission -Account 'SELF' -Rights 'ReadProperty,WriteProperty' -Property 'servicePrincipalName' -ApplyTo 'ThisObjectOnly';
You can use Quest AD cmdlets. It makes AD permission stuff very easy in PowerShell.
Read this blog for some examples on how to add AD permissions or even copy the AD permissions.
Just lookup Add-QADPermission and it should do your job.