Sometimes I don't understand Microsoft. They a lot of useful things, but thing implementation is quite poor. For example in Windows Server 2008 R2 we have an option to install certificate enrollment service (hereinafter CES) that will allow to securely enroll certificates outside of domain network perimeter. Also CES allows to enroll certificates from non-domain clients. Here is excellent whitepaper about the subject: Certificate Enrollment Web Services in Windows Server 2008 R2. You can setup only one CES instance via Server Manager snap-in. What if we have multiple CA servers and we need to configure CES to work with them? For example, one CA is configured to issue user certificates only and another CA is configured to issue computer certificates only. Also we need to issue these certificates to external clients. In that case we need to setup at least two Windows Server 2008 R2 servers, assign them public IP address and install required CES instance on each CES server. This is pretty ugly. Hopefully there is a trick to install additional CES instances on the same server via CryptoAPI COM interface: CERTOCM.CertificateEnrollmentServerSetup. Currently this interface is not documented on MSDN, therefore I cannot provide interface explanation links. However I wrote PowerShell script that will add additional CES instance and remove specified or all CES instances from local computer. I have commented some code parts for understanding, but the code generally is self-explanatory.

Let's go:

#####################################################################
# AddRemoveCES.ps1
# Version 1.3
#
# Installs or removes Certificate Enrollment Service (CES) instance
#
# Note: Requires Windows Server 2008 R2 Standard/Enterprise/Datacenter.
#
# Vadims Podans (c) 2010
# http://en-us.sysadmins.lv/
#####################################################################
#requires -Version 2.0

function Add-CES {
<#
.Synopsis
    Installs Certificate Enrollment Service instance to local computer
.Description
    This function installs Certificate Enrollment Service instance and configures it
    to work with specified certification authority.
.Parameter CAConfig
    Specifies certification authority configuration string in:
    CAComputerName\CASamitizedName format. CAComputerName may be either
    DNS or NetBIOS name. If this parameter is omitted, CA selection UI will be
    displayed during instance installation.
.Parameter Authentication
    Specifies authentication type for communication. Possible values are:
    Kerberos, UsrPwd or Certificate. Kerberos is used by default.
.Parameter User
    Sets CES AppPool account name. If this parameter is omitted, ApplicationPoolIdentity
    account will be used.
.Parameter Password
    Sets CES AppPool account password.
.Parameter RenewalOnly
    Sets CES service mode to Renewal Only. In that case CES will process certificate
    renewal requests only. No new certificate requests will be accepted.
.EXAMPLE
    Add-CES
    
    Running command without parameters will cause CA selection UI appearance. You will
    need to select CA server for CES server. In addition, default Kerberos authentication
    will be used.
.EXAMPLE
    Add-CES -CAConfig CA1\Contoso-CA -Authentication Certificate -User CustomUser -Password CustomPassword
    
    In this example CES server will be configured to CA server with Contoso-CA name
    and that is hosted on the computer named CA1. CES server will use client
    certificate for authentication and IIS AppPool will be configured to run
    under CustomUser account that has CustomPassword password.
#>
[CmdletBinding()]
    param(
        [string]$CAConfig,
        [ValidateSet("UsrPwd", "Kerberos", "Certificate")]
        [string]$Authentication = "Kerberos",
        [string]$User,
        [string]$Password,
        [switch]$RenewalOnly
    )

#region Check operating system
    $OS = (Get-WmiObject Win32_OperatingSystem).Caption
    if ($OS -notlike "Microsoft Windows Server 2008 R2*") {
        Write-Warning "Only Windows Server 2008 R2 operating system is supported!"; return
    }
#endregion

#region Check user permissions
# check if user has Enterprise Admins permissions
    $elevated = $false
    foreach ($sid in [Security.Principal.WindowsIdentity]::GetCurrent().Groups) {
        if ($sid.Translate([Security.Principal.SecurityIdentifier]).IsWellKnown([Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid)) {
            $elevated = $true
        }
    }
    if (!$elevated) {Write-Warning "You must be logged on with Enterprise Admins permissions!"; return}
#endregion

#region Obtain SSL certificate from local store or enroll new one
    function Get-Cert {
# retrieve current domain name. this suffix is used to construct current computer FQDN
        try {
            $domain = ([System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()).Name
        # if command above generates error, the computer is not a member of any AD domain
        } catch {Write-Warning "Current computer is not a part of any Active Directory domain!"; return}
        $fqdn = $env:COMPUTERNAME + "." + $domain
        $validCerts = @()
        # retrive all certificates from computer store that have private key and subject equals computer FQDN
        $certs = @(Get-ChildItem cert:\localmachine\my | ?{$_.HasPrivateKey -and $_.subject -eq "CN=$fqdn"})
        # loop extensions for EKU extension and check for Server Authentication OID
        foreach ($cert in $certs) {
            $eku = $cert.extensions | ?{$_.oid.value -eq "2.5.29.37"}
            if ($eku) {
                if ($eku.EnhancedKeyUsages | ?{$_.value -eq "1.3.6.1.5.5.7.3.1"}) {
                    # if certificate meet minimum requirements, write it to valid certs collection
                    $validCerts += $cert
                }
            }
        }
        # sort certificates in the collection by NotAfter and select one with the longest
        # validity
        if ($validCerts.count -gt 0) {
            ($validCerts | sort NotAfter | select -Last 1).Thumbprint
        } else {
            # if no valid certificate exist in the local store, enroll fro new one.
            $enrollment = New-Object -ComObject X509Enrollment.CX509enrollment
            # use ProductType of Win32_OperatingSystem class to determine computer role
            # domain Controller or Member Server.
            $ServerType = Get-WmiObject (Win32_OperatingSystem).ProductType
            if ($ServerType -eq 2) {$enrollment.InitializeFromTemplate(0x3, "DomainController")}
            elseif ($ServerType -eq 3) {$enrollment.InitializeFromTemplate(0x3, "Machine")}
            try {$enrollment.Enroll()}
            catch {
                Write-Warning "Unable to enroll SSL certificate. In order to use CES server"
                Write-Warning "you will have to manually obtain SSL certificate and configure"
                Write-Warning "IIS to use this certificate."
                return
            }
            $base64 = $enrollment.Certificate(1)
            $cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2
            $cert.Import($([Convert]::FromBase64String($base64)))
            $cert.Thumbprint
        }
    }
#endregion

    $auth = @{"Kerberos" = 2; "UsrPwd" = 4; "Certificate" = 8}
    # we can use ServerManager module to install CES binaries
    Import-Module ServerManager
    # at first check if CES is already installed
    $status = (Get-WindowsFeature -Name ADCS-Enroll-Web-Svc).Installed
    # if still no, install binaries, otherwise do nothing
    if (!$status) {$retn = Add-WindowsFeature -Name ADCS-Enroll-Web-Svc
        if (!$retn.Success) {
            Write-Warning "Unable to install CES service installation packages due of the following error:"
            Write-Warning $retn.ExitCode
            return
        }
    }
    # instantiate CES COM object
    $CES = New-Object -ComObject CERTOCM.CertificateEnrollmentServerSetup
    $CES.InitializeInstallDefaults()
    # use ICertConfig.GetConfig() to display CA selection UI
    if ($CAConfig -eq "") {
        $config = New-Object -ComObject CertificateAuthority.Config
        try {
            $bstr = $config.GetConfig(1)
        } catch {Write-Warning "There is no available Enterprise Certification Authorities or user canceled operation."; return}
    } else {$bstr = $CAConfig}
    $Thumbprint = $(Get-Cert)
    if ($User) {
        $CES.SetApplicationPoolCredentials($User, $Password)
    }
    $CES.SetProperty(0x1, $bstr)
    $CES.SetProperty(0x2, $auth.$Authentication)
    $CES.SetProperty(0x3, $Thumbprint)
    if ($RenewalOnly) {$CES.SetProperty(0x5, $true)}
    Write-Host "Performing Certificate Enrollment Service installation with the following settings:" `

    `nCA configuration string: $bstr
 `

    `nAuthentication type: $Authentication
 -ForegroundColor Cyan
    if ($RenewalOnly) {Write-Host "Renewal mode: Yes" -ForegroundColor Cyan}
    else {Write-Host "Renewal mode: No" -ForegroundColor Cyan}
    Write-Host "CES server URL: $($CES.GetProperty(0x4))" -ForegroundColor Cyan
    if ($Thumbprint) {Write-Host "SSL certificate thumbprint: $Thumbprint" -ForegroundColor Cyan}
    Write-Host ("-" * 50) `

    `nInstallation
 results `

    `n
("-" * 50) -ForegroundColor Green
    $CES.Install()
    if ($?) {Write-Host "CEP service was successfully installed!" -ForegroundColor Green}
}

function Remove-CES {
<#
.Synopsis
    Removes Certificate Enrollment Service instance from local computer.
.Description
    This function removes Certificate Enrollment Service instance or instances
    if you wish to remove all CES instances from local computer.
.Parameter CAConfig
    Specifies certification authority configuration string in:
    CAComputerName\CASamitizedName format. CAComputerName may be either
    DNS or NetBIOS name. If this parameter is omitted, CA selection UI will be
    displayed during instance removal. If -Force switch is asserted, this paramter
    will be ignored and all CES instances will be removed from local computer.
.Parameter Authentication
    Specifies authentication type to remove for specified instance. Possible values are:
    Kerberos, UsrPwd or Certificate. Kerberos is used by default. This parameter
    may be used if multiple instances are installed to work with the same CA server
    but they uses different authentication types.
.Parameter Force
    Instructs to ignore CAConfig and Authentication parameters and remove all CES
    instances from local computer.
.EXAMPLE
    Remove-CES -CAConfig CA1\Contoso-CA
    
    Will remove all CES instances that was configured for CA server named Contoso-CA
    and that is hosted on CA1 computer.
.EXAMPLE
    Remove-CES -Force
    
    Will remove all CES instances from local computer.
#>
[CmdletBinding()]
    param (
        [string]$CAConfig,
        [ValidateSet("UsrPwd", "Kerberos", "Certificate")]
        [string]$Authentication = "Kerberos",
        [switch]$Force
    )

#region Check operating system
    $OS = (Get-WmiObject Win32_OperatingSystem).Caption
    if ($OS -notlike "Microsoft Windows Server 2008 R2*") {
        Write-Warning "Only Windows Server 2008 R2 operating system is supported!"; return
    }
#endregion

#region User permissions
# check if user has Enterprise Admins permissions
    $elevated = $false
    foreach ($sid in [Security.Principal.WindowsIdentity]::GetCurrent().Groups) {
        if ($sid.Translate([Security.Principal.SecurityIdentifier]).IsWellKnown([Security.Principal.WellKnownSidType]::AccountEnterpriseAdminsSid)) {
            $elevated = $true
        }
    }
    if (!$elevated) {Write-Warning "You must be logged on with Enterprise Admins permissions!"; return}
#endregion

    $auth = @{"Kerberos" = 2; "UsrPwd" = 4; "Certificate" = 8}
    if ($CAConfig -eq "" -and !$Force) {
        $config = New-Object -ComObject CertificateAuthority.Config
        try {
            $bstr = $config.GetConfig(1)
        } catch {Write-Warning "There is no available Enterprise Certification Authorities or user canceled operation."; return}
    }
    elseif ($CAConfig -ne "" -and !$Force){$bstr = $CAConfig}
    else {$bstr = $null; $auth.$Authentication = $null}
    $CES = New-Object -ComObject CERTOCM.CertificateEnrollmentServerSetup
    Write-Host "Performing Certificate Enrollment Service removal with the fillowing settings:" -ForegroundColor Cyan
    if ($bstr -eq $null) {Write-Host "CA configuration string: Any" -ForegroundColor Cyan}
    else {Write-Host "CA configuration string: $bstr" -ForegroundColor Cyan}
    if ($auth.$Authentication -eq $null) {Write-Host "Authentication type: Any" -ForegroundColor Cyan}
    else {Write-Host "Authentication type: $Authentication" -ForegroundColor Cyan}
    if ($Force) {Write-Host "Remove installation packages: Yes" -ForegroundColor Cyan}
    else {write-Host "Remove installation packages: No" -ForegroundColor Cyan}
    Write-Host ("-" * 50) `

    `nRemoval
 results `

    `n
("-" * 50) -ForegroundColor Green
    $CES.Uninstall($bstr, $auth.$Authentication)
    if ($?) {Write-Host "CES service successfully removed!" -ForegroundColor Green}
    if ($Force) {
        Import-Module ServerManager
        $retn = Remove-WindowsFeature -Name ADCS-Enroll-Web-Svc
        if (!$retn.Success) {
            Write-Warning "CES installation package removal failed due of the following error:"
            Write-Warning $retn.ExitCode
        }
        else {Write-Host "CES installation packages are successfully removed!" -ForegroundColor Green}
    }
}

copy and paste the code to PowerShell console and run the following commands:

  • to install first or additional CES instance:
Add-CES

Running command without parameters will cause CA selection UI appearance. You will need to select CA server for CES server. In addition, default Kerberos authentication will be used.

Add-CES CA1\Contoso-CA certificate CustomUser CustomPassword

In this example CES server will be configured to CA server with Contoso-CA name and that is hosted on the computer named CA1. CES server will use client certificate for authentication and IIS AppPool will be configured to run under CustomUser account that has CustomPassword password.

  • to remove CES instance (or all instances):
Remove-CES CA1\Contoso-CA

Will remove all CES instances that was configured for CA server named Contoso-CA and that is hosted on CA1 computer.

Remove-CES –Force

Will remove all CES instances from local computer. If you specify –Force switch, this will ignore CAConfig and Authentication parameters and will remove all CES instances from local computer.

Note: by default script configure IIS AppPool to ApplicationPoolIdentity. Currently I don't know how to configure AppPool to NetworkService identity, so you may have to manually switch application pool account settings after role installation. This will not required if there is at least one existing CES instance. In that case new CES instance will inherit application pool settings from existing CES instance.

If you have questions or comments — ask me :)

For script download click here:

Have a nice day! :)


Share this article:

Comments:

Ryan

Thank you for writing this script, I just got off with Microsoft support who told me this is not possible. I am having issues with the script it is complaining about the win32_operatingsystem call. I am running windows Server 2008 R2. Any ideas? Thank you, Ryan

Ryan

Thank you for writing this script, I just got off with Microsoft support who told me this is not possible. I am having issues with the script it is complaining about the win32_operatingsystem call. I am running windows Server 2008 R2. Any ideas? Thank you, Ryan

Ryan

up and running, please disregard my prior comment/question

Vadims Podans

Microsoft engineer was incorrect. Windows Server 2008 R2 supports multiple instances of Certificate Enrollment Service. Do I understand that you have resolved issue?

Ryan

Yes, I have resolved the issue. Thank you again for creating this script it made things much easier.

Matthias

Hi Vadims, is it generally possible also to have multiple CEP instances on 1 server? To have one that works for Username/Password for non domain joined and one for Kerberos Authenticating Clients?

Or do you just have to change authentication settings in IIS for the CEP?

 

Thanks a lot

Vadims Podāns

> is it generally possible also to have multiple CEP instances on 1 server?

No, unlike CES, only single instance of CEP service can be installed on single server. This limitation came from SPN management. Each CEP instance requires separate SPN, but you can't have multiple SPNs on the same machine.

Victor

Hello Vadims - We configured our CES/CEP server to use username/password authentication and it works great.  Is it possible to change the authentication type after the fact to use certificate authentication rather than username/password w/out removing and reinstalling the role?

Thanks,

Víctor

Vadims Podāns

> Is it possible to change the authentication type after the fact to use certificate authentication rather than username/password w/out removing and reinstalling the role?

no, there is no other way. You have to uninstall existing service and install a new one.

François

Hello Vadims, here is a doc from Microsoft where it says that "Two CEP/CES instances that are configured on one server" : https://docs.microsoft.com/en-us/windows-server/identity/solution-guides/certificate-enrollment-certificate-key-based-renewal

As far as I tested it, these powershell commands used to configure the second instances of CEP/CES don't work for me (for now) : https://docs.microsoft.com/en-us/windows-server/identity/solution-guides/certificate-enrollment-certificate-key-based-renewal#step-1-install-the-cep-and-ces-for-key-based-renewal-on-the-same-server

Here the error I get :

PS ...> Install-AdcsEnrollmentPolicyWebService -AuthenticationType Certificate -SSLCertThumbprint "xxxxxxxxxxxxxxxxxxxxxxxxx"

...
Performing the operation "Install-AdcsEnrollmentPolicyWebService" on target "XXXXXXXX".
[Y] Yes  [A] Yes to All  [N] No  [L] No to All  [S] Suspend  [?] Help (default is "Y"): Y
Install-AdcsEnrollmentPolicyWebService : Setup could not add this role service because it already exists in the default Web site. Please remove the existing role
service or select a different certification authority (CA) or authentication type. Cannot create a file when that file already exists. 0x800700b7 (WIN32/HTTP: 183
ERROR_ALREADY_EXISTS)

At line:1 char:1
+ Install-AdcsEnrollmentPolicyWebService -AuthenticationType Certificat ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Install-AdcsEnrollmentPolicyWebService], EnrollmentPolicyServiceSetupException
    + FullyQualifiedErrorId : Install,Microsoft.CertificateServices.Deployment.Commands.CEP.InstallAdcsEnrollmentPolicyWebService

 

Could you please tell me what is your opinion about this doc ? 

Thank you in advance !

 

For your information :

PS ...> certutil -config "<myCA>" -enrollmentserverurl
Enrollment Server Url[0]:
  Priority 1
  Authentication 4
    UserName -- 4
  AllowRenewalsOnly 0
  https://myServer/...-CA_CES_UsernamePassword/service.svc/CES
  AllowKeyBasedRenewal 0
CertUtil: -enrollmentServerURL command completed successfully.

 

Vadims Podāns

It is correct: you cannot have multiple CEP (policy servers) instances on same server. Only multiple enrollment services (CES) are supported. And this blog post talks about CES, not CEP.

François

Thank Vadims for your quick answer!

Perhaps I didn't fully understand this article but there are screenshots about IIS console with two CEP Applications ("ADPolicyProvider_CEP_UsernamePassword" and "ADPolicyProvider_CEP_Certificate") for example this one.

 

François

Hello,

Finally I managed to do it after reading this Ms doc : https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-R2-and-2012/jj590165(v=ws.11)?redirectedfrom=MSDN#to-install-a-certificate-enrollment-policy-web-service-that-uses-certificate-authentication

Before using this command : 

    >    Install-AdcsEnrollmentPolicyWebService -AuthenticationType Certificate -SSLCertThumbprint "xxxxxxxxxxxxxxxxxxxxxxxxx"

Execute this command: 

   >    cd cert:\LocalMachine\My

 

Wei

Hi Vadims,

I believe you can install multiple CEP instances on the same server. As Fran&#231;ois mentioned above, he seems to find the command to run before adding the 2nd CEP auth.

My situation is that I am trying to use  a MSA instead of regular domain user account. It worked with Kerberos and Username/Password options, but not with Certificate /KeyBasedRenewal. And I am trying to figure out why.

Vadims Podāns

You don't need to run CEP service under MSA or domain user account. You can use built-in app pool account for that. Where you really want to change domain account with MSA is CES service. Though, I never was able to do it. Impersonation step always fail to me even if SPNs are set correctly.

The trick above is very interesting, though, I never tried to install more than one CEP role on single server. I did it for CES, bet never for CEP. 

Felix Wagner

Hello Vadims,

First of all, I like to say thank you very much for all your wonderful posts! They helped me a lot in the past three months on setting up our new PKI.

But now I struggle with this CES and CEP deployment with Kerberos Authentication. So it is very interesting to read this.

I have found out that I did need to enable Kernel-Mode Authentication in IIS on both CES and CEP to make it work. To make it work on a server where the CA is placed. But I am unsure if this is considered secure. Do you have any thoughts on this?

What I have done is:
* Set SPNs for each CEP and CES instance on their own via a CNAME (found this hint after Server Manager got broken)
* Created a service user -> Like to test it later with this MSU
* Added user to IIS_USR Group on both machines
* Set delegation from the service user to the CA server
* Enabled this IIS Kernel-mode authentication
* Setup GPO to enable the service user to "Allow log on locally", "Impersonate a client after authentication", "Log on as batch job", "Log on as service" and to make everything sure "Obtain an impersonation token for another user in the same session"

But I face an issue if I place CES on another server. I get always thrown Reposnse Codes 500. Not sure how to track it down, and find better logs than those IIS "Failed Request Tracing Rules". Any hint you can give me to look further where this Authentication Error comes from?
 

Thank you very much!

Felix Wagner

Hello Vadims,

I finally solved all of my issues! Your website still helped a lot. So Thank you very much again for all this excellent content.

I can tell you that I have now running CEP and CES. Both with Kerberos Constrained Delegation on the same server, including a gMSA.

Some things I figured out over the time:

  • GPO to allow the gMSA "Allow log on locally", "Impersonate a client after authentication", Log on as a batch job" and "Log on as a service". (Be careful to include default Users, one of them needs test editor)
  • Add the gMSA to IIS_IUSR group
  • Create the gMSA with a DNSHostName directly in your domain
  • Create the SPNs with HTTP to this DNSHostName and the NetBIOS name of the gMSA ( the cool thing about this is, it don't break PWSH or remote administration and don't intercept with CEP on the same server -> The SPN is different, so the constrained delegation is different)
  • Add to the gMSA a PrincipalsAllowedToRetrieveManagedPassword a group in which those CES servers are
  • Edit via ADSI (...) the gMSA and add the "msDS-AllowedToDelegateTo" Attribute to HOST/ and rpcss/ for DNSHostName and NetBios of the CA. So in total, 4 entries.
  • Check that the UserControl attribute of the gMSA in ADSI is minimum a value of 16781312 ( WORKSTATION_TRUST_ACCOUNT | TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION )
  • A certificate which includes the DNSHostName and the NetBIOS name of the gMSA DomainName
  • Give rights on the CA to the gMSA (here I am not sure if the gMSA needs to be assigned to the CA computer or not - will verify this)

Then all this is working out of the box. No further configuration for CEP is needed. It can run under the default Application Pool. Even more without additional rights, it will even fail.

I have a link in which some PowerShell commands describe how this works and which did give me personally the final understanding. Not sure if I am allowed to post it here.

If you like to get to know about my configuration feel free to contact me.

I hope this post might help others too.

Kind regards

Felix Wagner

Rajkiran

After installing CEPCES roles for 1st-time enrollment only, it shows the CES URI but not if this has impacted my Manually SSL cert enrollment.

Does it impact any already existing ADCS enrollment policy or it can add one new Enrollment policy for CES and would work without interrrupting existing Enrollment policy

Vadims Podāns

Once installed, CES endpoint will be automatically added to Active Directory and discoverable by CEP and clients. It doesn't overwrite existing CES endpoints

adrian alan sabas

Can i use this script on windows server 2019 datacenter?

Vadims Podāns

On Windows Server 2019 you can use built-in Install-AdcsEnrollmentWebService command from AdcsDeployment module.

adrian sabas

hi got an issue using this it says

PS C:\Windows\system32> Install-AdcsEnrollmentWebService -applicationpoolidentity -CAConfig "<removed ca name>" -AuthenticationType username
Install-AdcsEnrollmentWebService : You cannot set this property because the application pool "WSEnrollmentServer"
already exists. The group or resource is not in the correct state to perform the requested operation. 0x8007139f
(WIN32: 5023 ERROR_INVALID_STATE)
At line:1 char:1
+ Install-AdcsEnrollmentWebService -applicationpoolidentity -CAConfig " ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Install-AdcsEnrollmentWebService], EnrollmentServiceSetupException
    + FullyQualifiedErrorId : SetCESProperties,Microsoft.CertificateServices.Deployment.Commands.CES.InstallAdcsEnroll
   mentWebService


Post your comment:

Please, solve this little equation and enter result below. Captcha