Remediate & Preventing VPN & 802.1X Rejection Due to Strong Certificate Mapping Enforcement



Starting February 2025, and with the best intetions, Microsoft will enforce strong certificate mapping by default on all supported Domain Controllers. This change impacts certificate-based authentication methods, especially:

  • VPN (e.g., Always On VPN)
  • Wired/Wireless 802.1X network access
  • Any system using device-based certificates for authentication

If devices present certificates that do not contain the required security identifier (SID), authentication will fail, potentially resulting in widespread connectivity issues, which today did cause a bit of a scene with this error on the NPS servers:

What was the detailed error

When you open that event Towards the bottom, you will see a reason code and a Description, which in this example informed me of this:

Reason Code: 16
"Authentication failed due to a user credentials mismatch. Either the user name provided does not map to an existing user account or the password was incorrect."

At first glance, this message suggests a typical issue with username or password credentials. However, in environments leveraging certificate-based authentication (such as EAP-TLS), this error can be misleading.

In reality, this issue often has nothing to do with incorrect usernames or passwords. Instead, it typically stems from a failure to meet the authentication requirements enforced by the domain controller. For instance, if the domain controller is enforcing a strong authentication requirement (such as requiring smart card logon or validated certificate mapping), and the presented certificate fails to meet those criteria, the authentication will be blocked—regardless of whether the user or computer object is valid.

This distinction is important for troubleshooting, as administrators might otherwise waste time investigating credential mismatches.

Issue Summary

This issue will alert you to the fact that you’re not compliant when people are unable to connect to services that are using these certificates without strong certificate mapping.

Previously, Microsoft allowed non-compliant certificates via a compatibility mode, logging mapping failures without breaking authentication. Starting February 2025, enforcement becomes the default, meaning:

  • Only certificates with strong mappings (i.e., the SID embedded in the SAN) will be accepted.
  • This poses a risk to environments using device-based certificates that lack the SID.
What Certificates are affected?

Affected Scenario

  • When a user or computer attempts to authenticate using a certificate (e.g., via smart card logon, PKINIT with Kerberos, or certificate-based VPN).
  • The certificate lacks the proper Enhanced Key Usage (EKU) values (like Client Authentication), or has a weak/missing mapping.
  • The domain controller is updated with the KB5014754 hardening (or later), and strict validation is enforced (EKUBehavior=2), or audit is being monitored (EKUBehavior=1).

Not Affected

  1. Certificates used for:
    Server authentication (e.g., HTTPS, LDAPS).
    Code signing, email encryption, or any non-authentication use cases.
  2. Users or systems that do not use certificate-based logon (e.g., passwords, NTLM/Kerberos without PKINIT).

Short-Term Mitigation: Registry Override

You need to apply a registry key can be applied to all Domain Controllers to revert the behavior to compatibility mode:

# Registry Path:
HKLM\SYSTEM\CurrentControlSet\Services\Kdc

# Key:
Name: StrongCertificateBindingEnforcement
Type: REG_DWORD
Value: 1

  • 1 : Compatibility mode – authentication allowed, failures logged
  • 2: Enforcement mode (default from February 2025)
Note: This key will no longer have any effect after September 2025, when enforcement becomes permanent.

Automated Deployment Script for All Domain Controllers

This script identifies all DCs in the domain and remotely applies the registry override:

# Registry settings
$regPath = 'HKLM:\SYSTEM\CurrentControlSet\Services\Kdc'
$regName = 'StrongCertificateBindingEnforcement'
$regValue = 1

# Get all Domain Controllers
$domainControllers = Get-ADDomainController -Filter *

foreach ($dc in $domainControllers) {
    try {
        Invoke-Command -ComputerName $dc.HostName -ScriptBlock {
            param($path, $name, $value)
            New-Item -Path $path -Force | Out-Null
            Set-ItemProperty -Path $path -Name $name -Value $value -Type DWord
            Write-Output "Successfully updated registry on $env:COMPUTERNAME"
        } -ArgumentList $regPath, $regName, $regValue
    }
    catch {
        Write-Warning "Failed to update registry on $($dc.HostName): $_"
    }
}

Enable Domain Control Monitor Mode (to see what is not valid)

You need need to to set the following keys on all your Domain Controllers to get the Event ID 39 which can be analysed to see the failing certificates, these keys need to be set in this location, and they keys need to be a D_WORD value:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Kdc

EKUBehavior = 1

This enables audit mode for Enhanced Key Usage validation. It logs issues when a certificate doesn't have the correct EKU (like Client Authentication) but still allows authentication.

DisableCertificatePurposeCheck = 1

This disables the enforcement of certificate purpose checking, again logging the problem but not blocking the certificate.

Permanent Fix: Updating Device Certificate Templates

To be compliant long-term, device certificates must contain the device's SID in the Subject Alternative Name (SAN)using a special OtherName object:

Required Certificate Extension

  • OID: 1.3.6.1.4.1.311.25.2
  • Value: Binary SID of the device account in AD

Steps to Update the Certificate Template (On-Premises CA)

Open Certificate Templates Management the follow the instructions below:

Important: Ensure your CA supports inclusion of the SID by r

unning on Windows Server 2022 or higher

  1. Duplicate your current device certificate template
  2. On the General tab, name the template something like : Computer Strong Mapping
  3. On the Subject Name tab: 

    Select: "Build from this Active Directory information"
  4. Choose: "Fully distinguished name" or leave as default
  5. On the Extensions tab:

    Confirm Client Authentication is listed in Application Policies
  6. Enable the Subject Alternative Name extension (ensure DNS or Directory Name is included)
  7. Configuring the template for Auto-enrollment (machine-based)
  8. Publish the updated template to the CA
  9. Deploy via GPO-based auto-enrollment to all domain-joined systems

What if the Certificate Authorities are not Server 2022?

If your Certificate Authorities are not running Windows Server 2022, they cannot automatically embed the SID into issued certificates - this means this step will need to be done manually for every single one of your device certificates.

  1. Export the device SID manually using PowerShell:

    (Get-ADComputer $env:COMPUTERNAME).SID.Value
  2.  Request a certificate manually using a .INF file, and include the SID as an SAN entry with the correct OID:

    OID: 1.3.6.1.4.1.311.25.2
    Format: binary encoding of the SID

Then finally use certreq.exe to submit the request to the CA.

This is obviously a very manual process, depending on the amount of devices you have, if you are not looking to upgrade your Certificate Authorities to Server 2022 then you may wish to read the next section.

Could this be a missing Hotfix?

SID extension (1.3.6.1.4.1.311.25.2) is supposed to be added automatically by the Certificate Authority server that received the KB5014754 update, not by your certificate request itself.

If your CA has KB5014754 installed and configured correctly, it should automatically add the SID extension to the certificate even though you're not explicitly requesting it.

If the certificate doesn't have the SID extension after running this script below, it suggests your CA server may not have KB5014754 installed or properly configured 

I do not want to upgrade my servers to server 2022!

I do understand that upgrading certificate authorities can sometimes be a challenging process depending on your skills with certificate authorities, but there is another option you can schedule a script to run locally on the device that will take care of this new certificate for you.

Script: DeployCert.ps1

# Check CA Update Status and Create Certificate Script
# NOTE: This script MUST be run as Administrator

# Check if running as Administrator
$isAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if (-not $isAdmin) {
    Write-Error "This script must be run as Administrator. Please restart PowerShell as Administrator and try again."
    exit 1
}

# Define variables
$caName = "certsrv1\Bear-Cert-Server"

# First, check if the CA has the update installed
Write-Host "Checking if the CA has KB5014754 installed..." -ForegroundColor Cyan
try {
    $caInfo = certutil -config $caName -ping
    Write-Host "CA connection test results:"
    Write-Host $caInfo    

# You would need to check the CA server for KB5014754 directly
    Write-Host "`nNOTE: To confirm KB5014754 is installed on the CA server, log into $caName and run:" -ForegroundColor Yellow
    Write-Host "Get-HotFix -Id KB5014754" -ForegroundColor Yellow
} catch {
    Write-Host "Failed to connect to CA: $_" -ForegroundColor Red
}

# Get FQDN of the computer
$fqdn = "$env:COMPUTERNAME.$((Get-WmiObject Win32_ComputerSystem).Domain)"

# Define file paths
$certReqInf = "$env:TEMP\certreq.inf"
$certReqFile = "$env:TEMP\certreq.req"
$certRespFile = "$env:TEMP\certreq.resp"

# Remove existing files if they exist to prevent prompts
if (Test-Path $certReqFile) {
    Remove-Item $certReqFile -Force
}
if (Test-Path $certRespFile) {
    Remove-Item $certRespFile -Force
}

# Also check for the .rsp file
$certRspFile = "$env:TEMP\certreq.rsp"
if (Test-Path $certRspFile) {
    Remove-Item $certRspFile -Force
    Write-Host "Removed existing response file: $certRspFile"
}

# Generate INF contents - kept simple to let the CA handle the SID extension
$infContent = @"
[Version]
Signature=`$Windows NT`$

[NewRequest]
Subject = "CN=$fqdn"
KeySpec = 1
KeyLength = 2048
Exportable = FALSE
MachineKeySet = TRUE
SMIME = FALSE
PrivateKeyArchive = FALSE
UserProtected = FALSE
UseExistingKeySet = FALSE
ProviderName = "Microsoft RSA SChannel Cryptographic Provider"
ProviderType = 12
RequestType = PKCS10
KeyUsage = 0xa0
FriendlyName = "$fqdn"
[RequestAttributes]
CertificateTemplate=ComputerAuthentication-StrongAuth
[Extensions]
; Add Subject Alternative Name extension
2.5.29.17 = "{text}"
_continue_ = "dns=$fqdn"
"@
Write-Host "Writing INF file to: $certReqInf"
Write-Host "FQDN: $fqdn"

# Write the INF file with ASCII encoding
$infContent | Out-File -FilePath $certReqInf -Encoding ascii

# Show the exact content of the INF file
Write-Host "`nINF File Content:"
Get-Content $certReqInf | ForEach-Object { Write-Host $_ }

# Generate the certificate request
Write-Host "`nGenerating certificate request..."
$certreqOutput = cmd /c "certreq -q -new `"$certReqInf`" `"$certReqFile`"" 2>&1
if ($LASTEXITCODE -ne 0) {
    Write-Error "Failed to create certificate request. Error: $certreqOutput"
    exit 1
}

# Continue if request was successful
if (Test-Path $certReqFile) {
    Write-Host "Certificate request created successfully."   
    # Create a temporary response folder to avoid conflicts
    $tempRespFolder = "$env:TEMP\certresp_$(Get-Random)"
    New-Item -ItemType Directory -Force -Path $tempRespFolder | Out-Null
    $tempRespFile = "$tempRespFolder\certreq.rsp"   

    # Submit the certificate request
    Write-Host "`nSubmitting request to CA $caName..."
    $submitOutput = cmd /c "certreq -q -submit -config `"$caName`" `"$certReqFile`" `"$tempRespFile`"" 2>&1
    if ($LASTEXITCODE -ne 0) {
        # If it's a file exists error, try using a direct yes answer through echo
        if ($submitOutput -match "ERROR_FILE_EXISTS") {
            Write-Host "Trying alternate method to force overwrite..."
            $submitOutput = cmd /c "echo Y | certreq -submit -config `"$caName`" `"$certReqFile`" `"$certRespFile`"" 2>&1
            if ($LASTEXITCODE -ne 0) {
                Write-Error "Failed to submit certificate request. Error: $submitOutput"
                exit 1
            }
        } else {
            Write-Error "Failed to submit certificate request. Error: $submitOutput"
            exit 1

        }
    } else {

# Copy the response file to the expected location
        if (Test-Path $tempRespFile) {
            Copy-Item -Path $tempRespFile -Destination $certRespFile -Force
            Remove-Item -Path $tempRespFolder -Recurse -Force
        }
    }

# Accept and install the certificate
    if (Test-Path $certRespFile) {
        Write-Host "Certificate response received successfully."       
        Write-Host "`nInstalling certificate..."
        $acceptOutput = cmd /c "certreq -q -accept `"$certRespFile`"" 2>&1
        if ($LASTEXITCODE -ne 0) {
            Write-Error "Failed to install certificate. Error: $acceptOutput"
            exit 1
        }       
        Write-Host "Certificate successfully installed for $fqdn."        

# Now check if the certificate has the SID extension
        Write-Host "`nChecking if the certificate has the SID extension (1.3.6.1.4.1.311.25.2)..."
        $certs = Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.Subject -like "*$env:COMPUTERNAME*" }       
        if ($certs.Count -gt 0) {
            $foundSidExt = $false           
            foreach ($cert in $certs) {
                Write-Host "Found certificate: $($cert.Subject) with thumbprint $($cert.Thumbprint)"
                foreach ($ext in $cert.Extensions) {
                    if ($ext.Oid.Value -eq "1.3.6.1.4.1.311.25.2") {
                        $foundSidExt = $true
                        Write-Host "Certificate has the SID extension!" -ForegroundColor Green
                        break
                    }
                }               
                if (-not $foundSidExt) {
                    Write-Host "Certificate does NOT have the SID extension." -ForegroundColor Yellow
                    Write-Host "This might indicate that your CA server does not have KB5014754 installed or properly configured." -ForegroundColor Yellow
                    Write-Host "Please contact your CA administrator to ensure the update is installed and configured correctly." -ForegroundColor Yellow
                }
            }
        } else {
            Write-Host "No certificates found for $env:COMPUTERNAME." -ForegroundColor Red
        }
    } else {
        Write-Error "Certificate response file not found after submission."
    }

} else {
    Write-Error "Certificate request file not found after generation."
}

Validation: Checking if the SID is Present

After issuing a certificate, use PowerShell to inspect the SAN:

Get-ChildItem -Path Cert:\LocalMachine\My | ForEach-Object {
    $hasSID = $false
    foreach ($ext in $_.Extensions) {
        if ($ext.Oid.Value -eq "1.3.6.1.4.1.311.25.2") {
            $hasSID = $true
            break
        }
    }
    if ($hasSID) {
        [PSCustomObject]@{
            Subject = $_.Subject
            Thumbprint = $_.Thumbprint
            Issuer = $_.Issuer
            NotAfter = $_.NotAfter
            HasSIDExtension = $true
        }
    }
} | Format-Table Subject, Thumbprint, NotAfter -AutoSize

You should see a SAN with OtherName and the OID 1.3.6.1.4.1.311.25.2 with the command ot you can confirm with with the mmc as well:


Cleanup the old certificates without this OID

This script will cleanup the old certificates without this OID from the computer:

Script : Remove-CertificatesWithoutSID.ps1

# Run with default settings (local computer name, automatic deletion)
#Remove-CertificatesWithoutSID
#
# To specify a different computer name
#Remove-CertificatesWithoutSID -ComputerName "YourComputerName"
#
# To enable confirmation prompts
#Remove-CertificatesWithoutSID -ConfirmDelete

function Remove-CertificatesWithoutSID {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [switch]$ConfirmDelete = $false,
        
        [Parameter(Mandatory = $false)]
        [ValidateSet('CurrentUser', 'LocalMachine')]
        [string]$CertificateStore = 'LocalMachine',
        
        [Parameter(Mandatory = $false)]
        [string]$StoreName = 'My',
        
        [Parameter(Mandatory = $false)]
        [string]$ComputerName = $env:COMPUTERNAME
    )
    
    # Define the SID Extension OID
    $sidExtensionOID = "1.3.6.1.4.1.311.25.2"
    
    Write-Host "Checking certificates in $CertificateStore\$StoreName for missing SID extensions..."

    # Open the certificate store in ReadWrite mode
    $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, $CertificateStore)
    $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
    
    # Track certificates without SID extension
    $certsToDelete = @()
    
    # Loop through all certificates in the store
    foreach ($cert in $store.Certificates) {
        $hasSIDExtension = $false
        
        # Check each extension for the SID OID
        foreach ($extension in $cert.Extensions) {
            if ($extension.Oid.Value -eq $sidExtensionOID) {
                $hasSIDExtension = $true
                break
            }
        }
        
        # If no SID extension was found, add to the list of certs to delete
        if (-not $hasSIDExtension) {
            $certsToDelete += $cert
        }
    }
    
    # Display certificates to be deleted
    Write-Host "`nFound $($certsToDelete.Count) certificates without SID extension that will be deleted:"
    
    if ($certsToDelete.Count -gt 0) {
        $certsToDelete | ForEach-Object {
            Write-Host "Subject: $($_.Subject)"
            Write-Host "Thumbprint: $($_.Thumbprint)"
            Write-Host "Issuer: $($_.Issuer)"
            Write-Host "NotAfter: $($_.NotAfter)"
            Write-Host "--------------------------"
        }
        
        # Delete certificates
        $deletedCount = 0
        
        foreach ($cert in $certsToDelete) {
            $proceed = $true
            
            # If confirm is enabled, ask for confirmation
            if ($ConfirmDelete) {
                $response = Read-Host "Delete certificate with thumbprint $($cert.Thumbprint)? (Y/N)"
                if ($response -ne "Y" -and $response -ne "y") {
                    $proceed = $false
                    Write-Host "Skipping certificate with thumbprint $($cert.Thumbprint)"
                }
            }
            
            if ($proceed) {
                try {
                    $store.Remove($cert)
                    $deletedCount++
                    Write-Host "Deleted certificate with thumbprint $($cert.Thumbprint)" -ForegroundColor Green
                }
                catch {
                    Write-Host "Failed to delete certificate with thumbprint $($cert.Thumbprint): $_" -ForegroundColor Red
                }
            }
        }
        
        Write-Host "`nDeleted $deletedCount of $($certsToDelete.Count) certificates without SID extension" -ForegroundColor Cyan
    }
    else {
        Write-Host "No certificates without SID extension found. Nothing to delete."
    }
    
    # Close the store
    $store.Close()
}

# Run the command without confirmation (automatic deletion)
Remove-CertificatesWithoutSID

# Alternatively, to run with confirmation:
# Remove-CertificatesWithoutSID -ConfirmDelete

Previous Post Next Post

نموذج الاتصال