Self-Signed Certificates for Win10

on Friday, November 24, 2017

Browsers have implemented all sorts of great new security measures to ensure that certificates are pretty valid. So, using a self-signed certificate today is more difficult than it used to be. Also, IIS for Win8/10 gained access for using a Central Certificate Store. So, here’s some scripts that:

  • Create a Self-Signed Cert
    • Creates a self-signed cert with a DNS Name (browsers don’t like it when the Subject Alternative Name doesn’t list the DNS Name).
    • Creates a Shared SSL folder on disk and adds permissions for IIS’s Central Certificate Store account will read the certs with.
    • Exports the cert to the Shared SSL folder as a .pfx.
    • Reimports the certs to the machines Trusted Root Authority (needed for browsers to verify the cert is trusted)
    • Adds the 443/SSL binding to the site (if it exists) in IIS
  • Re-Add Cert to Trusted Root Authority
    • Before Win10, Microsoft implemented a background task which will periodically check the certs installed in your Machine Trusted Root Authority which are self-signed and removes them. So, this script re-installs them.
    • It will look through the shared SSL folder created in the previous script and add any certs back to the local Machine Trusted Root Authority that are missing.
  • Re-Add Cert to Trusted Root Authority Scheduled Task
    • Schedules the script to run hourly
### Create-SelfSignedCert.ps1

$name = "site.name.com" # only need to edit this


# get the shared ssl password for dev - this will be applied to the cert
$pfxPassword = "your pfx password"

# you can only create a self-signed cert in the \My store
$certLoc = "Cert:\LocalMachine\My"
$cert = New-SelfSignedCertificate `
            -FriendlyName $name `
            -KeyAlgorithm RSA `
            -KeyLength 4096 `
            -CertStoreLocation $certLoc `
            -DnsName $name

# ensure the path the directory for the central certificate store is setup with permissions
# NOTE: This assumes that IIS is already setup with Central Cert Store, where
#       1) The user account is "Domain\AccountName"
#       2) The $pfxPassword Certificate Private Key Password
$sharedPath = "D:\AllContent\SharedSSL\Local"
if((Test-Path $sharedPath) -eq $false) {
    mkdir $sharedPath

    $acl = Get-Acl $sharedPath
    $objUser = New-Object System.Security.Principal.NTAccount("Domain\AccountName") 
	$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($objUser, "ReadAndExecute,ListDirectory", "ContainerInherit, ObjectInherit", "None", "Allow")
	$acl.AddAccessRule($rule)
	Set-Acl $sharedPath $acl
}


# export from the \My store to the Central Cert Store on disk
$thumbprint = $cert.Thumbprint
$certPath = "$certLoc\$thumbprint"
$pfxPath = "$sharedPath\$name.pfx"
if(Test-Path $pfxPath) { del $pfxPath }
Export-PfxCertificate `
    -Cert $certPath `
    -FilePath $pfxPath `
    -Password $pfxPassword


# reimport the cert into the Trusted Root Authorities
$authRootLoc = "Cert:\LocalMachine\AuthRoot"
Import-PfxCertificate `
    -FilePath $pfxPath `
    -CertStoreLocation $authRootLoc `
    -Password $pfxPassword `
    -Exportable


# delete it from the \My store
del $certPath # removes from cert:\localmachine\my


# if the website doesn't have the https binding, add it
Import-Module WebAdministration

if(Test-Path "IIS:\Sites\$name") {
    $httpsBindings = Get-WebBinding -Name $name -Protocol "https"
    $found = $httpsBindings |? { $_.bindingInformation -eq "*:443:$name" -and $_.sslFlags -eq 3 }
    if($found -eq $null) {
        New-WebBinding -Name $name -Protocol "https" -Port 443 -IPAddress "*" -HostHeader $name -SslFlags 3
    }
}
### Add-SslCertsToAuthRoot.ps1

$Error.Clear()

Import-Module PowerShellLogging
$name = "Add-SslCertsToAuthRoot"
$start = [DateTime]::Now
$startFormatted = $start.ToString("yyyyMMddHHmmss")
$logdir = "E:\Logs\Scripts\IIS\$name"
$logpath = "$logdir\$name-log-$startFormatted.txt"
$log = Enable-LogFile $logpath

try {

    #### FUNCTIONS - START ####
    Function Get-X509Certificate {
	Param (
        [Parameter(Mandatory=$True)]
		[ValidateScript({Test-Path $_})]
		[String]$PfxFile,
		[Parameter(Mandatory=$True)]
		[string]$PfxPassword=$null
	)

	    # Create new, empty X509 Certificate (v2) object
	    $X509Certificate = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2

	    # Call class import method using password
        try {
			$X509Certificate.Import($PfxFile,$PfxPassword,"PersistKeySet")
			Write-Verbose "Successfully accessed Pfx certificate $PfxFile."
		} catch {
			Write-Warning "Error processing $PfxFile. Please check the Pfx certificate password."
			Return $false
		}
	
        Return $X509Certificate
    }

    # http://www.orcsweb.com/blog/james/powershell-ing-on-windows-server-how-to-import-certificates-using-powershell/
    Function Import-PfxCertificate {
    Param(
	    [Parameter(Mandatory = $true)]
	    [String]$CertPath,
	    [ValidateSet("CurrentUser","LocalMachine")]
	    [String]$CertRootStore = "LocalMachine",
	    [String]$CertStore = "My",
	    $PfxPass = $null
    )
        Process {
	        $pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
	        if ($pfxPass -eq $null) {$pfxPass = read-host "Enter the pfx password" -assecurestring}
	        $pfx.import($certPath,$pfxPass,"Exportable,PersistKeySet")
 
	        $store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)

	        $serverName = [System.Net.Dns]::GetHostName();
	        Write-Warning ("Adding certificate " + $pfx.FriendlyName + " to $CertRootStore/$CertStore on $serverName. Thumbprint = " + $pfx.Thumbprint)
	        $store.open("MaxAllowed")
	        $store.add($pfx)
	        $store.close()
	        Write-Host ("Added certificate " + $pfx.FriendlyName + " to $CertRootStore/$CertStore on $serverName. Thumbprint = " + $pfx.Thumbprint)
        }
    }
    #### FUNCTIONS - END ####


    #### SCRIPT - START ####
    $sharedPath = "D:\AllContent\SharedSSL\Local"
    $authRootLoc = "Cert:\LocalMachine\AuthRoot"
    
    $pfxPassword = "your password" # need to set this

    $pfxs = dir $sharedPath -file -Filter *.pfx
    foreach($pfx in $pfxs) {    
        $cert = Get-X509Certificate -PfxFile $pfx.FullName -PfxPassword $pfxSecret.Password
        $certPath = "$authRootLoc\$($cert.Thumbprint)"
        if((Test-Path $certPath) -eq $false) {
            $null = Import-PfxCertificate -FilePath $pfx.FullName -CertStoreLocation $authRootLoc -Password $pfxPassword -Exportable
            Write-Host "$($cert.Subject) ($($cert.Thumbprint)) Added"
        } else {
            Write-Host "$($cert.Subject) ($($cert.Thumbprint)) Already Exists"
        }
    }
    #### SCRIPT - END ####

} finally {
    foreach($er in $Error) { $er }

    Disable-LogFile $log
}
### Install-Add-SslCertsToAuthRoot.ps1

$yourUsername = "your username" # needs local admin rights on your machine (you probably have it)
$yourPassword = "your password"

$name = "Add-SslCertsToAuthRoot"
$filename = "$name.ps1"
$fp = "D:\AllContent\Scripts\IIS\$filename"
$taskName = $name
$fp = "powershell $fp"

$found = . schtasks.exe /query /tn "$taskName" 2>null
if($found -ne $null) {
    . schtasks.exe /delete /tn "$taskName" /f
    $found = $null
}
if($found -eq $null) {
    . schtasks.exe /create /ru $yourUsername /rp $yourPassword /tn "$taskName" /sc daily /st "01:00" /tr "$fp"
    . schtasks.exe /run /tn "$taskName"
}

Wnf Kernel Memory Leak

on Friday, November 17, 2017

Back in 2015, we started using Win2012 R2 servers and within a day of Production usage we started seeing Out of Memory errors on the servers. Looking at the Task Manager, we could easily see that a massive amount of Kernel Memory was being used. But why?

Using some forums posts, SysInternals, and I think a Scott Hanselman blog entry we were able to use PoolMon.exe to see that the system using all the Kernel Memory was Wnf. We had no idea what it was and went down some rabbit holes before finding this forum post.

Microsoft Support would later tell us the problem had something to with a design change to Remote Registry and how it deals with going idle, and another design change in Windows Server 2012 R2 about how it choose which services to make idle. Anyways, the fix was easy to implement (just a real pain to find):

If you want the service to not stop when Idle, you can set this registry key:
key : HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\RemoteRegistry
name : DisableIdleStop
REG_DWORD, data : 1

Here’s what it looks like when the leak is happening:

image


Creative Commons License
This site uses Alex Gorbatchev's SyntaxHighlighter, and hosted by herdingcode.com's Jon Galloway.