Last updated: December 05, 2025
Author: Paul Namalomba
- SESKA Computational Engineer
- Software Developer
- PhD Candidate (Civil Engineering Spec. Computational and Applied Mechanics)
Contact: kabwenzenamalomba@gmail.com
Website: paulnamalomba.github.io
SSL/TLS provides encrypted communication channels for secure data transmission. This guide covers certificate generation, management, and verification using PowerShell and OpenSSL on Windows. Power users need to understand certificate lifecycle management, cipher suite configuration, and security best practices for production deployments.
choco install opensslopenssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodesopenssl x509 -in cert.pem -text -nooutopenssl s_client -connect example.com:443 -showcertsopenssl pkcs12 -export -out certificate.pfx -inkey key.pem -in cert.pemOpenSSL Configuration File (openssl.cnf):
[req]
default_bits = 4096
default_md = sha256
distinguished_name = req_distinguished_name
x509_extensions = v3_ca
[req_distinguished_name]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
organizationName = Organization Name
commonName = Common Name
[v3_ca]
subjectAltName = @alt_names
basicConstraints = critical,CA:FALSE
keyUsage = digitalSignature, keyEncipherment
[alt_names]
DNS.1 = example.com
DNS.2 = *.example.com
IP.1 = 192.168.1.100
Best Practices: - Use 4096-bit RSA keys or 256-bit ECDSA keys minimum - Set certificate validity to 397 days maximum (browser requirement) - Always include Subject Alternative Names (SANs) for hostnames - Store private keys with restrictive permissions (ACLs) - Use Hardware Security Modules (HSMs) for production CA keys - Implement certificate rotation 30 days before expiration
Windows Registry Hardening:
# Disable TLS 1.0 and 1.1
New-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server' -Force
New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server' -Name 'Enabled' -Value 0 -PropertyType 'DWord'
New-Item 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server' -Force
New-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server' -Name 'Enabled' -Value 0 -PropertyType 'DWord'
Generate a self-signed certificate with Subject Alternative Names for local development, valid for one year with 4096-bit RSA key.
# Create configuration file
$config = @"
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
x509_extensions = v3_req
[dn]
CN = localhost
C = US
ST = State
L = City
O = Organization
OU = Development
[v3_req]
subjectAltName = @alt_names
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[alt_names]
DNS.1 = localhost
DNS.2 = *.localhost
IP.1 = 127.0.0.1
IP.2 = ::1
"@
Set-Content -Path "openssl.cnf" -Value $config
# Generate certificate
openssl req -x509 -newkey rsa:4096 -keyout private.key -out certificate.crt -days 365 -nodes -config openssl.cnf
# Verify certificate
openssl x509 -in certificate.crt -text -noout | Select-String "Subject Alternative Name" -Context 0,3
Create a CSR for submission to a Certificate Authority with proper organizational details and multiple SANs for production domain.
# Generate private key
openssl genrsa -out production.key 4096
# Create CSR config
$csrConfig = @"
[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[dn]
CN = example.com
C = US
ST = California
L = San Francisco
O = Example Inc
OU = Engineering
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
DNS.4 = *.example.com
"@
Set-Content -Path "csr.cnf" -Value $csrConfig
# Generate CSR
openssl req -new -key production.key -out production.csr -config csr.cnf
# Verify CSR content
openssl req -in production.csr -noout -text | Select-String "Subject:|DNS:"
Convert between PEM and PFX formats, create certificate bundles with intermediate CAs, and set password protection for distribution.
# Convert PEM to PFX with password
$password = ConvertTo-SecureString -String "StrongPassword123!" -Force -AsPlainText
openssl pkcs12 -export -out certificate.pfx -inkey private.key -in certificate.crt -certfile ca-bundle.crt -password pass:StrongPassword123!
# Convert PFX to PEM
openssl pkcs12 -in certificate.pfx -out certificate-full.pem -nodes -password pass:StrongPassword123!
# Extract only certificate from PFX
openssl pkcs12 -in certificate.pfx -clcerts -nokeys -out certificate-only.crt -password pass:StrongPassword123!
# Extract only private key from PFX
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out private-only.key -password pass:StrongPassword123!
# Create certificate chain bundle
Get-Content certificate.crt, intermediate.crt, root.crt | Set-Content -Path full-chain.pem
Test TLS connectivity, enumerate supported cipher suites, and verify certificate chain for a remote server.
# Test TLS connection with full handshake details
openssl s_client -connect example.com:443 -showcerts -servername example.com
# Test specific TLS version
openssl s_client -connect example.com:443 -tls1_2 -servername example.com
# Extract server certificate
$cert = openssl s_client -connect example.com:443 -servername example.com 2>$null | openssl x509
$cert | Set-Content -Path "server-cert.pem"
# Check certificate expiration
$expiryDate = openssl x509 -in server-cert.pem -noout -enddate
Write-Host "Certificate expires: $expiryDate"
# List supported cipher suites
openssl ciphers -v 'HIGH:!aNULL:!MD5:!3DES' | ForEach-Object { $_ }
# Test specific cipher suite
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384 -servername example.com
Verify certificate chain against CA bundle, check OCSP revocation status, and validate certificate properties.
# Verify certificate against CA bundle
openssl verify -CAfile ca-bundle.pem certificate.crt
# Extract OCSP responder URL
$ocspUrl = openssl x509 -in certificate.crt -noout -ocsp_uri
Write-Host "OCSP Responder: $ocspUrl"
# Check OCSP status
openssl ocsp -issuer ca-bundle.pem -cert certificate.crt -url $ocspUrl -CAfile ca-bundle.pem
# Verify certificate dates
$notBefore = openssl x509 -in certificate.crt -noout -startdate
$notAfter = openssl x509 -in certificate.crt -noout -enddate
Write-Host "Valid from: $notBefore"
Write-Host "Valid until: $notAfter"
# Check certificate fingerprint (SHA256)
$fingerprint = openssl x509 -in certificate.crt -noout -fingerprint -sha256
Write-Host "SHA256 Fingerprint: $fingerprint"
Import certificates into Windows Certificate Store programmatically with proper store locations and access permissions.
# Import PFX to Personal store for Current User
$pfxPassword = ConvertTo-SecureString -String "StrongPassword123!" -Force -AsPlainText
Import-PfxCertificate -FilePath "certificate.pfx" -CertStoreLocation Cert:\CurrentUser\My -Password $pfxPassword
# Import PFX to Local Machine store (requires admin)
Import-PfxCertificate -FilePath "certificate.pfx" -CertStoreLocation Cert:\LocalMachine\My -Password $pfxPassword -Exportable
# Import CA certificate to Trusted Root
Import-Certificate -FilePath "ca-root.crt" -CertStoreLocation Cert:\LocalMachine\Root
# List certificates in Personal store
Get-ChildItem -Path Cert:\CurrentUser\My | Select-Object Subject, Thumbprint, NotAfter
# Export certificate from store
$cert = Get-ChildItem -Path Cert:\CurrentUser\My | Where-Object { $_.Subject -like "*example.com*" } | Select-Object -First 1
Export-Certificate -Cert $cert -FilePath "exported-cert.cer"
Certificate Verification Failures:
- Error: "unable to get local issuer certificate"
- Cause: Missing intermediate CA certificates in chain
- Fix: Download intermediate certificates from CA and include in bundle: openssl verify -CAfile full-chain.pem cert.crt
Private Key Mismatch:
- Error: "key values mismatch"
- Cause: Private key doesn't match certificate public key
- Fix: Compare modulus: openssl x509 -noout -modulus -in cert.crt | openssl md5 vs openssl rsa -noout -modulus -in key.pem | openssl md5
Certificate Expired:
- Error: "certificate has expired"
- Check: openssl x509 -in cert.crt -noout -dates
- Fix: Renew certificate before expiration; automate with Let's Encrypt or ACME protocol
Common Logs to Check:
- Windows Event Viewer: Security logs (Event ID 36888 for certificate errors)
- Application logs for TLS handshake failures
- IIS logs: %SystemDrive%\inetpub\logs\LogFiles
- OpenSSL debug output: Add -debug or -msg flags to s_client
Cipher Suite Negotiation Failures:
- Check supported ciphers: openssl ciphers -v | Select-String "TLSv1.2"
- Server preference: Use -cipher flag to test specific suites
- Client/server mismatch: Ensure overlap in supported cipher suites
Key Size vs Performance: - RSA 2048-bit: Baseline performance, adequate for most use cases - RSA 4096-bit: 7x slower key generation, 2x slower handshakes, recommended for CA and long-lived certificates - ECDSA P-256: Faster handshakes than RSA, smaller key sizes, use for high-throughput servers
Session Resumption:
# Enable TLS session tickets in IIS (reduces handshake overhead)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL" -Name "EnableSessionTicket" -Value 1
# Configure session cache timeout (seconds)
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL" -Name "ServerCacheTime" -Value 3600
OCSP Stapling:
- Reduces client-side latency by 200-300ms per handshake
- Enable in IIS 8.0+: Enabled by default
- Verify: openssl s_client -connect example.com:443 -status -servername example.com | Select-String "OCSP"
Hardware Acceleration:
- Use AES-NI CPU instructions for AES-GCM cipher suites (10x faster)
- Verify support: openssl speed -evp aes-256-gcm
- Offload to dedicated crypto hardware for >10Gbps workloads
Monitoring Commands:
# Measure TLS handshake time
Measure-Command { openssl s_client -connect example.com:443 -servername example.com < $null }
# Profile cipher suite performance
openssl speed rsa2048 rsa4096 ecdsap256
# Monitor certificate expiration
Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.NotAfter -lt (Get-Date).AddDays(30) }