4

Im getting an error as my xConnect certificate appears to have expired today! Im a little suspicious by those dates as it would appear to be valid from Feb 2018 to today. Does this sound possible and would I use SIF to create a new one?

enter image description here

0

3 Answers 3

5

Assuming it's expired you can easily create a new certificate using Windows Powershell by running the following command(so no need of SIF just for that part):

New-SelfSignedCertificate -CertStoreLocation Cert:\LocalMachine\My -DnsName "ClientStaging" -FriendlyName "Client Staging Self Signed" -NotAfter $([datetime]::now.AddYears(10))

Don’t worry about the DnsName — you can assign the cert to any host name.

This is for self-signed certificates. After you run that you will get the thumbprint of it, so just make sure you update the connection string and the AppSettings.config file, under the xconnect folder which is normally something like myxconnectroot\App_Config\AppSettings.config

1
2

You also need to add the xConnect App Pool user to the new certificate via Manage Computer Certificates MMC. Right-click on the new cert then > All Tasks > Manage Private Keys. Add the xConnect App Pool user there.

1

Ran into the same this week on Sitecore 10.1.2. I had difficulty getting indexing operations in CM to accept the new self-signed cert from the accepted answer to take even though https requests to xConnect showed secure; I kept getting different flavors of Invalid Certificate error through CM, including the dreaded "The request was aborted: Could not create SSL/TLS secure channel" error.

It also happened that my local identityserver and site certificates expired same day, so we needed to do this three times, and as you know it involved several configs to update.

Ran across this forum post:

We have a LOT of test environments. As the certs have been expiring, i was complaining about the manual process I was using to change/update these. My clever co-worker Karl bowled up a powershell script that has been such a help! This will generate a new CA cert for you to base all your self-signed certs on, or it will use one you already have. Put the pfx file in the same directory as the script. It displays all the sites in IIS and you pick the one to update - say a Sitecore 9 xconnect site, changes out the cert in the bindings, updates the thumbnails where needed in config. So easy once you've used it a couple of times. Enjoy. NB uploading as a txt which will need to be renamed to a ps1

This was a super helpful script that took care of xConnect, identityserver, and local Sitecore site in just a few moments.

NB: If you're going to go this route, make sure you do not delete your expired certs or unbind them in IIS. The script will use those certs and bindings to reverse-engineer CN, O, OU, etc., for the new ones.


# 2022-03-31: Script to issue/renew local SSL certificates and update configuration for Sitecore sites.
# Note: requires administrative permissions to run successfully. USE AT OWN RISK.
# Author: FuseIT

[CmdletBinding(PositionalBinding=$false)]
param (
    # Root CA password.
    [string] $p,
    # Number of years to issue for.
    [int] $y,
    # Optional settings for new CA certificate.
    [string] $cn,
    [string] $o,
    [string] $ou,
    [string] $fn,
    # Root CA file, or "new" to generate a self-signed CA certificate.
    [Parameter(ValueFromRemainingArguments)]
    [string] $rootfile
)

Import-Module -Name WebAdministration

function GetInputWithDefault ([string]$prompt, [string]$default) {
    $input = ""
    if ($default -eq "") {
        # Input is required.
        while ($input -eq "") {
            $input = Read-Host -Prompt "$($prompt)"
        }
    } else {
        # Allow default value.
        $input = Read-Host -Prompt "$($prompt) [$($default)]"
    }
    if ($default -ne "" -And $input -eq "") {
        $input = $default
    }
    return $input
}

function CreateCACert ([string]$rootfile, [string]$p, [string]$cn, [string]$o, [string]$ou, [string]$fn, [DateTime]$notafter) {
    
    # Get values for new self-signed CA cert.
    $cn = GetInputWithDefault "Common Name (CN)"  "LocalDevCA"
    $o = GetInputWithDefault "Organization (O)" "Local"
    $ou = GetInputWithDefault "Organizational Unit (OU)" "localdev.ca"
    $fn = GetInputWithDefault "Friendly name" "Local dev root CA"
    
    # Generate a new self-signed root CA certificate, in the local Personal store.
    $cacert = New-SelfSignedCertificate `
     -Subject "CN=$($cn),O=$($o),OU=$($ou)" `
     -CertStoreLocation cert:\LocalMachine\My `
     -KeyExportPolicy Exportable `
     -KeyUsage CertSign,CRLSign,DigitalSignature `
     -KeyLength 2048 `
     -KeyUsageProperty All `
     -KeyAlgorithm 'RSA' `
     -HashAlgorithm 'SHA256' `
     -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
     -NotAfter $notafter `
     -FriendlyName "$($fn)"
    
    Write-Host ""
    
    # Verify.
    if ($cacert -eq $null) {
        Write-Host "Failed to create self-signed CA certificate."
        return $null
    }
    Write-Host "Created self-signed CA certificate:"
    Write-Host $cacert
    Write-Host "----------------------------------------"
    Write-Host ""
    
    # Perform the export and verify.
    $certfile = Export-PfxCertificate -Cert $cacert -FilePath $rootfile -Password (ConvertTo-SecureString -String $p -AsPlainText -Force)
    if ($certfile -eq $null) {
        Write-Host "Failed to export self-signed CA cert to PFX file."
        return $null
    }
    Write-Host "Self-signed CA certificate exported to $($rootfile)."
    Write-Host ""
    
    return $cacert
}

function GetCACertFromFile ([string]$rootfile, [string]$p) {
    # Read the existing CA cert file to get the thumbprint (and verify that it is a valid cert).
    $cacert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    $cacert.Import((Resolve-Path $rootfile), (ConvertTo-SecureString -String $p -AsPlainText -Force), [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
    
    # Get-PfxCertificate did not have the -Password parameter prior to Powershell version 6.
    # $cacert = Get-PfxCertificate -FilePath $rootfile -Password (ConvertTo-SecureString -String $p -AsPlainText -Force)
    
    if ($cacert -eq $null -Or $cacert.Thumbprint -eq "") {
        Write-Host "Failed to load CA certificate from file $($rootfile)."
        return $null
    }
    
    return $cacert
}

function FindOrImportRootCACert ([string]$thumbprint, [string]$rootfile, [string]$p) {
    # Attempt to locate the CA certificate in the root store.
    $rootcert = Get-ChildItem Cert:\LocalMachine\Root | Where-Object {$_.Thumbprint -eq $thumbprint}
    
    if ($rootcert -eq $null) {
        # Offer to install the certificate from file.
        if ((Read-Host -Prompt "CA certificate not in Root store. Install? [y/N]") -ne "Y") {
            return $null
        }
        $rootcert = Import-PfxCertificate -FilePath $rootfile -Password (ConvertTo-SecureString -String $p -AsPlainText -Force) -CertStoreLocation Cert:\LocalMachine\Root
        
        if ($rootcert -eq $null) {
            Write-Host "Certificate import to Root store failed."
        }
    }
    
    return $rootcert
}

function FindOrImportPersonalCACert ($rootcert) {
    # Attempt to locate existing.
    $signingcert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $rootcert.Thumbprint}
    
    if ($signingcert -eq $null) {
        # Add to Personal store.
        $mystore = Get-Item cert:\LocalMachine\My
        $mystore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
        $mystore.Add($rootcert)
        $mystore.Close()
        $signingcert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $rootcert.Thumbprint}
        
        if ($signingcert -eq $null) {
            Write-Host "Certificate import to Personal store failed."
        }
    }
    
    return $signingcert
}

function GetIISBindingCertificates ([int]$idx) {
    $bindinglist =@()

    # Iterate IIS SSL bindings.
    Get-ChildItem -Path IIS:SSLBindings | ForEach-Object -Process `
    {
        if ($_.Sites) {
            # Locate certificate matching thumbprint in Personal store.
            $certificate = Get-ChildItem -Path CERT:LocalMachine/My |
                Where-Object -Property Thumbprint -EQ -Value $_.Thumbprint

            $bindinglist += [PsCustomObject]@{
                Select = $idx
                Site = $_.Sites.Value
                DnsName = $certificate.DnsNameList
                Expiry = $certificate.NotAfter
                Thumbprint = $certificate.Thumbprint
                Subject = $certificate.Subject
                FriendlyName = $certificate.FriendlyName
                Port = $_.Port
                HostName = $_.Host
                IPAddress = $_.IPAddress
            }
            
            $idx++
        }
    }
    
    return $bindinglist
}

function CreateSelfSignedCertificate ([String[]]$dnsnamearr, [DateTime]$notafter, $signingcert, [string]$subject, [string]$fname) {
    # Generate new self-signed certificate, valid for configured period, into the Personal store.
    $newcert = New-SelfSignedCertificate `
        -CertStoreLocation cert:\LocalMachine\My `
        -DnsName $dnsnamearr `
        -NotAfter $notafter `
        -Signer $signingcert `
        -KeyLength 2048 `
        -KeyUsageProperty All `
        -KeyAlgorithm 'RSA' `
        -HashAlgorithm 'SHA256' `
        -Subject $subject `
        -FriendlyName $fname
        
    return $newcert
}

function GetCertificateAcl ($cert) {
    # Get permissions from old certificate.
    $certkeys = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
    # The existing cert keys may be in one of a couple of subdirectories, just find match by name.
    $aclpath = Get-ChildItem -Path "$env:ALLUSERSPROFILE\Microsoft\Crypto\" -Name "$($certkeys.key.UniqueName)" -Recurse
    $aclpath = "$env:ALLUSERSPROFILE\Microsoft\Crypto\$aclpath"
    if ($aclpath -eq $null) {
        return $null
    } else {
        return Get-Acl -Path $aclpath
    }
}

function SetCertificateAcl ($cert, $acl) {
    # Apply ACL to certificate.
    $certkeys = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
    $aclpath = Get-ChildItem -Path "$env:ALLUSERSPROFILE\Microsoft\Crypto\" -Name "$($certkeys.key.UniqueName)" -Recurse
    $aclpath = "$env:ALLUSERSPROFILE\Microsoft\Crypto\$aclpath"
    if ($aclpath -ne $null) {
        Set-Acl -Path $aclpath -AclObject $acl
    }
    
    return $aclpath
}

function AddAclRule ($acl, $identity) {
    $arglist = $identity, "FullControl", "Allow"
    $newrule = New-Object System.Security.AccessControl.FileSystemAccessRule -ArgumentList $arglist
    $acl.SetAccessRule($newrule)
}

function CopyCertificateAcl ($oldcert, $newcert) {
    # Get permissions from old certificate.
    $acl = GetCertificateAcl $oldcert
    if ($acl -ne $null) {
        # Apply to new.
        $aclpath = SetCertificateAcl $newcert $acl
    } else {
        $aclpath = $null
        Write-Host "Unable to locate private keys to match permissions from old certificate, please set permissions on the new certificate manually."
    }
    
    return $aclpath
}

function UpdateIISBinding ($site, $selectedbinding, $newcert) {
    # Update the IIS binding (make sure to get the matching one where multiple exist for the same site).
    # Note strange things happen if we don't explicitly remove the old one and add a new one. Also assume SNI.
    if ($selectedbinding.IPAddress) {
        Remove-WebBinding -Name $site.Name -Protocol "https" -Port $selectedbinding.Port -IPAddress $selectedbinding.IPAddress
        New-WebBinding -Name $site.Name -Protocol "https" -Port $selectedbinding.Port -IPAddress $selectedbinding.IPAddress -SslFlags 1
        $binding = Get-WebBinding -Name $site.Name -Protocol "https" -Port $selectedbinding.Port -IPAddress $selectedbinding.IPAddress
    } else {
        Remove-WebBinding -Name $site.Name -Protocol "https" -Port $selectedbinding.Port -HostHeader $selectedbinding.HostName
        New-WebBinding -Name $site.Name -Protocol "https" -Port $selectedbinding.Port -HostHeader $selectedbinding.HostName -SslFlags 1
        $binding = Get-WebBinding -Name $site.Name -Protocol "https" -Port $selectedbinding.Port -HostHeader $selectedbinding.HostName
    }
    $binding.AddSslCertificate($newcert.GetCertHashString(), "my")
    Write-Host ""
    Write-Host "Updated IIS Binding to use new certificate"
}

# ==============================
# Main script start.
# ==============================

if ($rootfile -eq "" -Or $p -eq "") {
    # Root CA file and password parameters are required.
    echo "Usage: RenewSSLCert.ps1 |new -p ROOT_CA_PWD [-y YEARS_VALIDITY=10]"
    echo ""
    exit
}
if ($rootfile.ToLower() -ne "new" -And (Test-Path($rootfile)) -ne $true) {
    # Root CA must be either "new" or a valid filename.
    echo "Root CA file '$($rootfile)' not found."
    echo ""
    exit
}

if ($y -eq 0) {
    # Default to 10 years validity period.
    $y = 10
}
# Validity period for new certificates.
$notafter = (Get-Date).AddYears($y)

echo ""

if ($rootfile.ToLower() -eq "new") {
    # Export the certificate to a file in the current directory.
    $dirpath = Split-Path $MyInvocation.MyCommand.Path
    $rootfile = "$dirpath\RootCA.pfx"
    
    # Confirm creating the self-signed CA cert.
    if ((Read-Host -Prompt "A self-signed CA certificate will be generated and installed with password '$($p)'. OK? [y/N]") -ne "Y") {
        exit
    }
    
    # Create.
    $cacert = CreateCACert $rootfile $p $cn $o $ou $fn $notafter
}
else {
    $cacert = GetCACertFromFile $rootfile $p
}
if ($cacert -eq $null) {
    exit
}

# Ensure the CA certificate is installed in the Root store.
$rootcert = FindOrImportRootCACert $cacert.Thumbprint $rootfile $p
if ($rootcert -eq $null) {
    exit
}

# The CA certificate must be present in the Personal store to be used for signing.
$signingcert = FindOrImportPersonalCACert $rootcert
if ($signingcert -eq $null) {
    exit
}
echo "CA signing certificate ready for use."
echo ""

# ==============================
# CA certificate setup complete.
# ==============================

echo "Certificate renewal options:"
$bindinglist = GetIISBindingCertificates 2

if ($bindinglist.count -eq 0) {
    echo "No existing IIS SSL bindings found."
}

# A couple of special options.
echo "[0] Cancel"
echo "[1] Generate a new certificate for a binding not shown"

# Display the certificate list and ask the user for a selection.
foreach ($b in $bindinglist) {
    echo "[$($b.Select)] $($b.Site) - $($b.DnsName)"
}
echo ""
[uint16]$bindingidx = Read-Host -Prompt "Please select a certificate to renew or 0 to exit"
echo ""

# Make sure we got a sensible response.
if ($bindingidx -eq 0) {
    exit
}
if ($bindingidx -lt 1 -Or $bindingidx -gt ($bindinglist.count + 1)) {
    echo "Invalid selection"
    exit
}

if ($bindingidx -eq 1) {
    $site = $null
    $sitepath = ""
    $selectedbinding = $null
    $oldcert = $null
    $aclpath = $null
    
    # Issue a new certificate for a binding that doesn't appear.
    $dnsnames = GetInputWithDefault "Domain Name(s), comma-separated" ""
    $fname = GetInputWithDefault "Friendly name" ""
    $dnsnamearr = $dnsnames.Split(",")
    
    $newcert = CreateSelfSignedCertificate $dnsnamearr $notafter $signingcert $dnsnames $fname
}
else {

    $bindingidx = $bindingidx - 2
    $selectedbinding = $bindinglist[$bindingidx]
    $oldcert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {$_.Thumbprint -eq $selectedbinding.Thumbprint}

    # Make sure we can load the full IIS site info.
    $site = Get-IISSite $selectedbinding.Site
    if ($site -eq $null) {
        echo "Failed to load IIS site information."
        exit
    }
    $sitepath = $site.Applications["/"].VirtualDirectories["/"].PhysicalPath

    # Display the full certificate details and warning.
    echo ""
    echo "================================"
    echo $oldcert
    echo ""
    echo "This will generate a new certificate using the CA certificate '$($signingcert.FriendlyName)', which is locally trusted but NOT FOR PRODUCTION USE!"

    # Big warning because this is a self-signed certificate.
    if ((Read-Host -Prompt "ARE YOU SURE? [y/N]") -ne "Y") {
        exit
    }

    # Need to convert to a string array for DnsName property.
    [String[]]$dnsnamearr = @()
    foreach ($d in $selectedbinding.DnsName) {
        $dnsnamearr += "$d"
    }
    # Check Subject and FriendlyName values.
    $subject = "$($selectedbinding.Subject)"
    if ($subject.Length -eq 0) {
        $subject = $dnsnamearr[0]
    }
    $fname = "$($selectedbinding.FriendlyName)"
    if ($fname.Length -eq 0) {
        $fname = $dnsnamearr[0]
    }

    # Create a new certificate using details from the old certificate.
    $newcert = CreateSelfSignedCertificate $dnsnamearr $notafter $signingcert $subject $fname
}

# Display to user.
echo ""
echo "New certificate created:"
$newcert
echo ""

if ($oldcert -ne $null) {
    # Copy the permissions from the old to the new.
    $aclpath = CopyCertificateAcl $oldcert $newcert
}

if ($aclpath -eq $null) {
    # Either a new certificate or failed to copy ACL for replacement. Attempt to set manually.
    $acl = GetCertificateAcl $newcert
    if ($acl -eq $null) {
        echo "Unable to load ACL for certificate, permissions will need to be set manually."
    } else {
        $identities = GetInputWithDefault "Assign certificate key permissions to (comma-separated)" "IIS_IUSRS,LOCAL SERVICE"
        
        # Iterate the provided identities and attempt to add one by one.
        foreach ($identity in $identities.Split(",")) {
            AddAclRule $acl $identity
        }
        
        # Apply the ACL.
        SetCertificateAcl $newcert $acl
    }
    echo "Further permission changes can be made via the Certificates Manager if required."
}
echo ""

# ==============================
# New certificate setup complete.
# ==============================

if ($selectedbinding -eq $null) {
    # This is as far as we can go for new certificates.
    echo "The IIS binding will need to be configured to use the new certificate manually."
    echo ""
    if ((Read-Host -Prompt "If this is for a Sitecore site further manual configuration is required. Show details? [y/N]") -eq "Y") {
        echo ""
        echo "Your new certificate thumbprint is: $($newcert.Thumbprint)"
        echo ""
        echo "For an XConnect site, set this thumbprint in:"
        echo "  * \App_Config\AppSettings.config"
        echo "  * \App_Data\jobs\continuous\AutomationEngine\App_Config\ConnectionStrings.config"
        echo "  * \App_Data\jobs\continuous\ProcessingEngine\App_Config\ConnectionStrings.config"
        echo "  * Primary Sitecore site \App_Config\ConnectionStrings.config for the XConnect connection."
        echo ""
        echo "For an Identity server site, set this thumbprint in:"
        echo "  * \Config\production\Sitecore.Identity.Server.Host.xml"
    }
    exit
}

# Update the IIS binding to use the new certificate.
UpdateIISBinding $site $selectedbinding $newcert

# ==============================
# IIS binding setup complete.
# ==============================

# Sitecore configuration update support (optional).

# We need to distinguish between three different types: main sitecore, xconnect, identityserver.
# Each has different configuration requirements.
echo ""
echo "Additional automatic changes can be made to update the certificate thumbprint for Sitecore."
echo "Please select the Sitecore site type:"
do {
    write-host "Sitecore [X]Connect"
    write-host "Sitecore [I]dentity server"
    write-host ""
    write-host "[0] Do not attempt changes"
    write-host ""
    write-host -nonewline "Enter type: "
    $sitetype = read-host
    write-host ""
    $ok = @("X","I","0") -contains $sitetype
    if ( -not $ok) { write-host "Invalid selection" }
}
until ( $ok )

# Locate the sitecore web root and appropriate config files, and validate whether everything looks good.
switch ($sitetype) {
    "X" {
        # We need the following config files:
        $files = $("\App_Config\AppSettings.config",
            "\App_Data\jobs\continuous\AutomationEngine\App_Config\ConnectionStrings.config",
            "\App_Data\jobs\continuous\ProcessingEngine\App_Config\ConnectionStrings.config")
        foreach ($f in $files) {
            $fp = "$sitepath$f"
            if ((Test-Path -Path $fp) -ne $true) {
                echo "Expecting file at $fp but none found - you will need to manually change the thumbprint to the new one."
            } else {
                # Replace all instances of the old thumbprint with the new one.
                (Get-Content $fp) -replace $oldcert.Thumbprint, $newcert.Thumbprint | Set-Content $fp
                echo "Replaced $($oldcert.Thumbprint) with $($newcert.Thumbprint) in $fp"
            }
        }
        
        # The primary site also needs updating with the new XConnect cert thumbprint.
        $idx = 1
        $sitelist =@()
        echo ""
        echo "Select primary Sitecore site for XConnect thumbprint update:"
        # Iterate IIS sites.
        Get-IISSite | ForEach-Object -Process {
            if ($_.Name -ne $selectedbinding.Site) {
                $sitelist += [PsCustomObject]@{
                    Select = $idx
                    Site = $_.Name
                }
                
                $idx++
            }
        }

        if ($sitelist.count -gt 0) {            
            # Display the certificate list and ask the user for a selection.
            foreach ($b in $sitelist) {
                echo "[$($b.Select)] $($b.Site)"
            }
            [uint16]$scidx = Read-Host -Prompt "Please select a site or 0 to skip"
            echo ""

            # Make sure we got a sensible response.
            if ($scidx -gt $sitelist.count) {
                echo "Invalid selection, skipping. The primary Sitecore site will need its \App_Config\ConnectionStrings.config file updated with the new XConnect certificate thumbprint manually."
            }
            elseif ($scidx -gt 0) {
                $scsite = Get-IISSite $sitelist[$scidx - 1].Site
                if ($scsite -eq $null) {
                    echo "Failed to load IIS site information."
                } else {
                    $scsitepath = $scsite.Applications["/"].VirtualDirectories["/"].PhysicalPath
                    $fp = "$scsitepath\App_Config\ConnectionStrings.config"
                    if ((Test-Path -Path $fp) -ne $true) {
                        echo "Expecting file at $fp but none found - you will need to manually change the thumbprint to the new one."
                    } else {
                        # Replace all instances of the old thumbprint with the new one.
                        (Get-Content $fp) -replace $oldcert.Thumbprint, $newcert.Thumbprint | Set-Content $fp
                        echo "Replaced $($oldcert.Thumbprint) with $($newcert.Thumbprint) in $fp"
                    }
                }
            }
        } else {
            echo "No other Sites found, the primary Sitecore site will need its \App_Config\ConnectionStrings.config file updated with the new XConnect certificate thumbprint manually."
        }
    }
    
    "I" {
        $fp = "$sitepath\Config\production\Sitecore.IdentityServer.Host.xml"
        if ((Test-Path -Path $fp) -ne $true) {
            echo "Expecting file at $fp but none found - you will need to manually change the thumbprint to the new one."
        } else {
            # Replace all instances of the old thumbprint with the new one.
            (Get-Content $fp) -replace $oldcert.Thumbprint, $newcert.Thumbprint | Set-Content $fp
            echo "Replaced $($oldcert.Thumbprint) with $($newcert.Thumbprint) in $fp"
        }
    }
}

echo ""
# Offer to delete the old certificate.
if ((Read-Host -Prompt "Delete the old certificate? [y/N]") -eq "Y") {
    $oldcert | Remove-Item
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.