Skip Ribbon Commands
Skip to main content

Ondrej Sevecek's English Pages

:

Engineering and troubleshooting by Directory Master!
MCM: Directory
April 27
How to create self-signed certificate from PowerShell without having PowerShell 5 cmdlets

Yes, it is possible to create self-signed certificates from command line on nearly any computer today even without having the PowerShell 5 installed. The PowerShell 5 comes with the all new New-SelfSignedCertificate cmdlet but apparently requires this newer version of the PowerShell which I often cannot rely on. While it is possible, since ever, to do the same with CERTREQ command line utility which is available built-in on all currently supported corporate editions of Windows.

I just wrapped the CERTREQ into the following powershell script. Yes, it does need PowerShell 2 only. The script simply compiles INF file for the CERTREQ. You can get documentation on the INF contents here.

function global:Create-SelfSignedCert (
  [Parameter(Mandatory = $true)]                                                                                                                                 [string] $subject,
                                                                                                                                                                 [string] $friendlyName,
                                 [ValidateSet('md5', 'md4', 'md2', 'sha1', 'sha256', 'sha384', 'sha512')]                                                        [string] $hash = 'sha256',
                                 [ValidateScript({ $_ -gt 0 })]                                                                                                  [int] $keyLength = 2048,
                                 [ValidateSet('seconds', 'minutes', 'hours', 'days', 'weeks', 'months', 'years')]                                                [string] $validityUnit = 'Months',
                                 [ValidateScript({ $_ -gt 0 })]                                                                                                  [int] $validityLen = 3,
                                 [ValidateNotNullOrEmpty()]                                                                                                      [string] $provider = "Microsoft Software Key Storage Provider",
                                                                                                                                                                 [switch] $machineKey,
                                 [ValidateSet('ClientAuthentication', 'ServerAuthentication')]                                                                   [string[]] $eku = @('ClientAuthentication'),
                                 [ValidateSet('DigitalSignature', 'KeyEncipherment', 'NonRepudiation', 'CertificateSigning', 'CrlSigning', 'OfflineCrlSigning')] [string[]] $keyUsage = @('DigitalSignature')
  )
{
  # Note: the file really must contain the last empty line
  [string] $muster = @'

; Built with SAPHA toolkit - (C) Ondrej Sevecek, 2012-2020 - www.sevecek.com, ondrej@sevecek.com

[Version]
Signature = "$Windows NT$"

[NewRequest]
RequestType = Cert
Subject = "$subject$"
HashAlgorithm = $hash$
KeyLength = $keyLength$
KeySpec = $keySpec$
KeyUsage = $keyUsage$ ; CERT_DIGITAL_SIGNATURE_KEY_USAGE
ValidityPeriod = $validityUnit$
ValidityPeriodUnits = $validityLen$
ProviderName = "$provider$"
Silent = true
MachineKeySet = $machineKey$
Exportable = false
FriendlyName = "$friendlyName$"

[Extensions]
"2.5.29.37" = "{text}$eku$"

'@

  [hashtable] $ekuOIDs = @{
    ServerAuthentication = '1.3.6.1.5.5.7.3.1'
    ClientAuthentication = '1.3.6.1.5.5.7.3.2'
    }

  [hashtable] $keyUsages = @{
    DigitalSignature = 0x80
    KeyEncipherment = 0x20
    NonRepudiation = 0x40
    CertificateSigning = 0x04
    CrlSigning = 0x02
    OfflineCrlSigning = 0x02
    }

  [string] $thumbprint = $null

  [System.Management.Automation.ActionPreference] $local:errorActionInternalBackup = $global:errorActionPreference
  try { $global:errorActionPreference = [System.Management.Automation.ActionPreference]::Stop

    $muster = $muster.Replace('$subject$', $subject)
    $muster = $muster.Replace('$friendlyName$', $friendlyName)
    $muster = $muster.Replace('$hash$', $hash)
    $muster = $muster.Replace('$keyLength$', $keyLength)
    $muster = $muster.Replace('$validityUnit$', $validityUnit)
    $muster = $muster.Replace('$validityLen$', $validityLen)
    $muster = $muster.Replace('$provider$', $provider)
    $muster = $muster.Replace('$machineKey$', $machineKey)
    $muster = $muster.Replace('$eku$', (($eku | % { $ekuOIDs[$_] }) -join ','))

    [int] $keyUsageInt = 0
    foreach ($oneKeyUsage in $keyUsage) {

      $keyUsageInt = $keyUsageInt -bor $keyUsages[$oneKeyUsage]
    }

    $muster = $muster.Replace('$keyUsage$', ('0x{0:X2}' -f $keyUsageInt))

    if ($keyUsages -contains 'KeyEncipherment') {

      $muster = $muster.Replace('$keySpec$', 'AT_SIGNATURE')

    } else {

      $muster = $muster.Replace('$keySpec$', 'AT_KEYEXCHANGE')
    }
    
    [string] $infFile = Join-Path $env:TEMP ('sapha-self-signed-certificate-{0}.inf' -f ([DateTime]::Now.ToString('yyyyMMddHHmmss')))
    [string] $cerFile = [System.IO.Path]::ChangeExtension($infFile, '.cer')

    Write-Host ('Creat a self-signed certificate: {0} | {1} | {2}' -f $subject, $friendlyName, $provider)
    
    Write-Host ('Save certificate request file: {0}' -f $infFile)
    $muster.Split("`n") | % { $_.Trim() } | Out-File -FilePath $infFile -Encoding Unicode -Force

    Write-Host ('Create the certificate with CERTREQ: {0}' -f $cerFile)
    [Collections.ArrayList] $cmdOut = @()
    certreq -f -q -new $infFile $cerFile | % { [void] $cmdOut.Add($_) }
    [int] $cmdRes = $LASTEXITCODE
    if ($cmdRes -ne 0) { throw  ('CERTREQ error: 0x{0:X8} | {1}' -f $cmdRes, ($cmdOut -join "`r`n")) }
    if (-not (Test-Path $cerFile)) { throw  ('No certificate file created: {0}' -f $cerFile) }

    Write-Host ('Loading the created certificate from file: {0}' -f $cerFile)
    [Security.Cryptography.X509Certificates.X509Certificate2] $certificateLoaded = New-Object Security.Cryptography.X509Certificates.X509Certificate2
    $certificateLoaded.Import($cerFile)

    if ($certificateLoaded.Thumbprint -notmatch '\A[a-fA-F0-9]{40}\Z') { throw  ('Invalid certificate loaded: {0} | {1}' -f $cerFile, $certificateLoaded.Thumbprint) }

    if ($machineKey) {

      $certCreatePath = ('Cert:\LocalMachine\My\{0}' -f $certificateLoaded.Thumbprint)

    } else {

      $certCreatePath = ('Cert:\CurrentUser\My\{0}' -f $certificateLoaded.Thumbprint)
    }

    [Security.Cryptography.X509Certificates.X509Certificate2] $certificateCreated = Get-Item $certCreatePath
    if ([object]::Equals($certificateCreated, $null)) { throw  ('Cannot open the local certificate store: {0}' -f $certificateLoaded.Thumbprint) }

    Write-Host ('Certificate created: {0} | {1} | exp = {2} | {3} | provider = {4}' -f $certificateLoaded.SubjectName.Name, $certificateLoaded.Thumbprint, $certificateLoaded.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'), $certCreatePath, $provider)

    if ($certificateLoaded.NotBefore -gt ([DateTime]::Now.AddMinutes(-10))) { throw  ('Weird certificate expiration date: {0}' -f $certificateLoaded.NotBefore.ToString('yyyy-MM-dd HH:mm:ss')) }

    $thumbprint = $certificateCreated.Thumbprint

  } finally { $global:errorActionPreference = $local:errorActionInternalBackup
  }

  return $thumbprint
}
April 01
LG smart TV not playing some DLNA movies correctly

Our new LG smart TV (actually the 43UM7100PLB) connected over WiFi network to NAS device (network attached storage) of Western Digital (WD) MyCloud Ex2 which supports DLNA (digital living network alliance). Some movies were not playing on the TV correctly. The stated error message was Unable to play. This file can't be recognized. Which was weird because they actually were playing for several seconds and their library thumbnails were displaying ok. If I put the movie file on a USB they were playing well too.

It turned out that encoding of the video or its format was not a problem. I tried to connect the LG smart TV to the WiFi router and thus to the NAS over network cable instead of WiFi which solved it. I suppose the problem was slow or failing and unstable WiFi connection, because the problem arrised mainly with larger video files.

February 28
Microsoft NLB is incompatible with SR-IOV hardware acceleration on Hyper-V

If you plan to use Microsoft NLB (Network Load Balancing) in unicast mode in Hyper-V virtual machines, these VMs must not use any Hyper-V virtual switch configured for SR-IOV (single root IO virtualization). Otherwise the virtual machine does not receive any traffic on the virtual NLB MAC address.

The unicast mode of NLB uses a virtual unicast MAC address. The problem with SR-IOV though is that it needs the virtual machines to register all their unicast MAC addresses with the physical network adapter in order to have the hardware acceleration.

The SR-IOV is a technology supported by server hardware NIC (network interface cards) which can distribute network traffic directly to the virtual machines (VMs) according to either their destination MAC address or their assigned VLAN ID. In order to tell the SR-IOV NIC the actual MAC address of a VM the hosting Hyper-V must know the MAC address of the virtual machine. In case of NLB the virtual MAC address is managed dynamically by the NLB driver inside the VM and the hypervizor does not know anything about it.

This function depends on the virtual switch being configured as non-SR-IOV. It does not matter if you enable the SR-IOV in the properties of the VM's network port. You must not have the SR-IOV enabled on the external virtual switch.

January 31
Assign RDP server certificate by using PowerShell

The following script finds the best certificate for RDP in the local machine certificate Personal (MY) store and assigns it for the use by the RDP server. Note that it prefers the Remote Desktop Authentication EKU (Enhanced Key Usage, 1.3.6.1.4.1.311.54.1.2). The certificate must be valid and have private key available, the script selects the certificate which is valid for the longest time. The script also makes sure that the Network Service account is granted read permission to the certificate private key.

[object[]] $validCerts = dir cert:\LocalMachine\My | ? { $_.HasPrivateKey -and $_.NotBefore -le ([DateTime]::Now) -and $_.NotAfter -gt ([DateTime]::Now)} | sort -Descending NotAfter
$certRDP = $null
$certTLS = $null

if ($validCerts.Length -gt 0) { foreach ($oneValidCert in $validCerts) {

  [string[]] $ekus = $oneValidCert.Extensions['2.5.29.37'].EnhancedKeyUsages | select -Expand Value
  if (($ekus -contains '1.3.6.1.4.1.311.54.1.2') -and ([object]::Equals($certRDP, $null))) {

    $certRDP = $oneValidCert
    Write-Host ('Found best RDP certificate: {0} | {1} | {2}' -f $certRDP.Subject, $certRDP.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'), $certRDP.Thumbprint)

  } elseif (($ekus -contains '1.3.6.1.5.5.7.3.1') -and ([object]::Equals($certTLS, $nullo))) {

    $certTLS = $oneValidCert
    Write-Host ('Found best TLS certificate: {0} | {1} | {2}' -f $certTLS.Subject, $certTLS.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'), $certTLS.Thumbprint)
  }
}}

$certBest = $null

if (-not ([object]::Equals($certRDP, $null))) {

  $certBest = $certRDP

} elseif (-not ([object]::Equals($certTLS, $null))) {

  $certBest = $certTLS
}

if ([object]::Equals($certBest, $null)) {

  throw ('Cannot find any suitable RDP certificate')
}

$thumbBytes = New-Object byte[] 20
for ($i = 0; $i -lt 20; $i ++) {

  $oneByte = $certBest.Thumbprint.SubString(($i * 2), 2)
  $thumbBytes[$i] = [Convert]::ToByte($oneByte, 16)
}

Write-Host ('Selected: thumbprint = {0} | {1}' -f $certBest.Thumbprint, ([BitConverter]::ToString($thumbBytes)))
Write-Host ('Selected: subject = {0}' -f $certBest.Subject)
Write-Host ('Selected: SAN = {0}' -f ($certBest.DnsNameList -join ','))
Write-Host ('Selected: expires = {0}' -f $certBest.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'))
Write-Host ('Selected: issuer = {0}' -f $certBest.Issuer)

Remove-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations' SelfSignedCertifi -Force -EA SilentlyContinue
Set-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' SSLCertificateSHA1Hash $thumbBytes -Type Binary

# Note: RDP requires the private key to be accessible by Network Service
certutil -repairstore my $certBest.Thumbprint 'D:P(A;;0x80120089;;;NS)(A;;0xd01f01ff;;;SY)(A;;0xd01f01ff;;;BA)'

Restart-Service SessionEnv -Force
December 04
How to remove expired user certificates from Active Directory user accounts using PowerShell

The certification authority software of Active Directory Certificate Services (ADCS) running in the enterprise installation mode (AD integrated CA) can publish user certificates which it generates into the respective AD user account so that other users can find the certificates for their colleagues and use them for encryption. This function is usually completely unnecessary because only few environments use user certificates for any data encryption at all.

But yes, the default User certificate template has the setting called Publish certificate in Active Directory enabled by default which is then also the case for all duplicates created from this default User template.

The issued certificates get published in their DER binary form into the userCertificate multivalued attribute of their respective AD user object. Expired certificates are not removed automatically. If you want to find all user accounts in the local AD domain and remove any expired certificates from the accounts, you can use the following PowerShell script. The script not only deletes the expired certificate from the user account, it also saves the certificate into TEMP if that was for anything.

Note that the script handles only the published certificates stored in the userCertificate attribute (public certificates without private keys). It does not clean the certificates nor their private keys from the private credentials roaming msPKI attributes.

$ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop

[object[]] $usersWithCerts = Get-ADUser -LDAPFilter '(userCertificate=*)' -Properties userCertificate

Write-Host ('Found user accounts with any certificate: #{0}' -f $usersWithCerts.Length)
foreach ($oneUserWithCert in $usersWithCerts) {

  Write-Host ('One user: {0} | certs = #{1} | {2}' -f $oneUserWithCert.sAMAccountName, $oneUserWithCert.userCertificate.Count, $oneUserWithCert.distinguishedName)

  foreach ($oneCertBin in $oneUserWithCert.userCertificate) {

    $oneCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    $oneCert.Import($oneCertBin)

    $isExpired = $oneCert.NotAfter -lt ([DateTime]::Now)
    Write-Host ('  certificate: {0} | expires = {1} (valid = {2}) | usage = {3}' -f $oneCert.Subject, $oneCert.NotAfter.ToString('yyyy-MM-dd HH:mm:ss'), (-not $isExpired), ($oneCert.EnhancedKeyUsageList -join ', '))

    if ($isExpired) {

      $saveExpired = Join-Path $env:TEMP ('expired-cert-{0}-exp{1}-{2}.cer' -f $oneUserWithCert.sAMAccountName, $oneCert.NotAfter.ToString('yyyy-MM-dd-HH-mm-ss'), $oneCert.Thumbprint)
      [void] ([System.IO.File]::WriteAllBytes($saveExpired, $oneCert.Export('Cert')))
      Write-Host ('  removing: saved = {0}' -f $saveExpired)

      Set-ADUser -Identity $oneUserWithCert -Certificates @{ Remove = $oneCert }
    }
  }
}

 

July 24
How to disable Windows Admin Center pop-up in Server Manager in registry

The Server Manager console on Windows Server 2019 now pop-ups by default with an annoying dialog box offering the option ty Try managing servers with Windows Admin Center (WindowsAdminCenter). If you want to disable this message by using registry key somewhat centrally, you can set the following registry value DoNotPopWACConsoleAtSMLaunch to 1:

HKLM\SOFTWARE\Microsoft\ServerManager
DoNotPopWACConsoleAtSMLaunch = DWORD = 1
May 27
Users get error message when connecting to RDP host using RemoteGuard

RemoteGuard is a fine new technology for RDP running to and from Windows 2016 and Window 10.1607 which allows for some basic credential protection of users' NTLM password hashes and TGT tickets. In order to use the remote guard feature you must either start mstsc client with /remoteGuard command line switch or have that feature enforced by a client machine group policy setting.

It is a documented fact that the sole use of the /remoteGuard switch requires the connecting user to be member of local Administrators group on the remote RDP host. In case the user is not member of the local Administrators group on the remote RDP host machine, the user receives the following error message displayed on the remote desktop screen after connecting:

The requested session access is denied

If you want your users to connect while not being members of local Administrators group on the remote RDP server then you have to enforce the RemoteGuard use on the client side by using group policy (local or GPO) setting:

Computer Configuration
  Policies
    Administrative Templates
      System
        Credentials Delegation

Restrict delegation of credentials to remote servers
    Enabled + Require Remote Credential Guard

Yes, weirdly enough, but really the /remoteGuard command line switch is apparently different from the GPO setting. And yes, both are client side matters. The only thing that you need to enable on the RDP server host is the DisableRestrictedAdmin registry value which is the same for both remote guard and restricted admin features.

HKLM\System\CurrentControlSet\Control\LSA
DisableRestrictedAdmin = DWORD = 0
February 06
Rolling upgrade of Windows 2012 R2 failover cluster right up to Windows 2019

No. It is not supported​ to join Windows 2019 nodes into the 2012 R2 failover cluster. You can join there Windows 2016 but not the newer 2019.

Windows 2019 being added to an existing cluster expects the cluster to be running at the Windows 2016 functional level at least.

The newer 2019 version expects the cluster nodes to have self-signed certificates (ClusInfraCert) generated for their intra-cluster SChannel (TLS) communication on port TCP 3343. Which is not the case with the older 2012 R2 version nodes that only use Kerberos for node authentication and communication protection.

If you want to join the Windows 2019 into the 2012 R2 cluster you will get an error (after some timeout) stating simply that the 2019 cannot communicate with any node of the existing cluster.

August 22
How to disable all existing Windows Firewall rules with a single NETSH command

This simple it is in fact:

NETSH ADVFIREWALL FIREWALL SET RULE all NEW enable=no

Cheers!

December 13
RD Gateway error event 210 caused by NLB configured with no affinity

You may get the error 210 in the TerminalServices-Gateway Admin log on a Remote Desktop Gateway server (RD Gateway) saying that Http transport: IN channel could not find a corresponding OUT channel. This error may happen if you operate a load balanced RD Gateway farm and the load balancing mechanism does not use any affinity. RD Gateway requires at least the single affinity to be used.

The no affinity setting means that any TCP connection being established from a client may end up at any load balanced farm member. The RD Gateway on the other hand must establish two TCP connections, one for inbound and the other for outbound transport, while both connections must hit the same RD GW farm member. Thus we must configure the load balancing technology (usually the NLB) to connect all TCP connections from a single client to the same NLB farm member.

1 - 10Next
 

 About this blog

 
Ondrej Sevecek 

Ondrej Sevecek is technical consultant, writer and speaker specialising in network security, PKI, identity management and Active Directory on Microsoft Windows platform. Ondrej is Microsoft Certified Master (MCM:Directory and MCSM:Directory) and the  Most Valuable Professional (MVP: Enterprise Security). He also maintains his CISA and CHFI:Computer Hacking Forensic Investigator and CEH:Certified Ethical Hacker certifications.

Ondrej is also MCT and gives lectures in the greatest of European training centers GOPAS.