The Background

A lot has happened in the world of SSL in the last 12 months and even though our site started with a nice A grade on the wonderful SSL Labs test site, it dipped recently to a paltry C!

Firstly, when Heartbleed happened, us Microsofties breathed a sigh of relief that it didn't affect Windows schannel.dll but two others were of concern, the BEAST attack and the more recent POODLE.

The BEAST attack was a vulnerability in the CBC mode used by a lot of SSL ciphers and even though RC4 didn't have the vulnerability, there were concerns that other weaknesses in RC4 might mean that it is still better to use CBC. Some fixes were then applied to various components by people like MS and Apple and although TLS 1.1 and 1.2 are not prone to BEAST, they are also less widely adopted by browsers!

Anyway, BEAST has kind of died away although POODLE is an attack against SSL v3 and is a reasonably small attack to make (256 requests = 1 byte of information revealed). Although most browsers support TLS 1.0 (the next version above SSLv3), since many/most servers still support SSLv3 from the olden days, not only is the weakness still present for users of older browsers (IE8 on Windows XP for example) but is also allows a downgrade attack, where a man-in-the-middle could negotiate the use of SSLv3 between a newer browser and the server, then allowing it to record and decrypt traffic (potentially). The only way to avoid this is to completely disable SSLv3 and that is what SSL labs is recommending and what Google is planning to ditch in newer versions of Chrome.

The bottom line is that people with old browsers will need to upgrade their browser and possibly their OS to continue using these secured sites but also, if there are old servers that still support SSLv3 and older ciphers, it is about time they updated and upgraded.

The Plan

So the plan is simple. Disable all versions of SSL in my Azure Cloud Service, disable older weaker ciphers such as DES, 3DES, RC2, RC4 and disable the by now soon-to-be-dead MD5 hashing algorithm. This leaves me with AES encryption (128 and 256 bit), CBC and GCM counter modes and 3 sizes of the SHA hashing algorithm. According to https://wiki.mozilla.org/Security/Server_Side_TLS this should be enough to support a medium range of browsers - i.e. not just all the latest and greatest but browsers back to IE8 on Windows 7, for instance. I have since tested the setup on SSL labs and it seems to be happy.

The Solution

How to do it? Well, you can run a startup script on your Azure cloud service but the problem is that if you reboot at the end (which you need to do for the settings to take effect), then when the role restarts, it will run the script again and reboot, which means you need some kind of flag to control how it reboots (only do it once). Fortunately, I found a very helpful script on the MS site that basically does this (although not everything I needed to do). For reference, the original is here.

Their trick is that it checks registry values before setting them and only rebooting if they have changed. This way, when the role reboots and runs the script again, nothing will have changed and the role won't reboot. The problem is that their script only disables SSLv3, it does nothing with removing weak ciphers or hashes but I had the bones of what I needed in their script.

I tried simply adding some more registry entries for ciphers that need to be disabled (like "RC4 40/128"), which is when I found out why this was not very easy to do! The existing script uses the generic new-item function to create the registry keys but Powershell, being very helpful, changes the forward slash in the name to a path character, effectively attempting to create RC4 40 and then a sub key of 128 - it fails!

What I had to do was rework it to use actual RegistryKey objects so I could call things like "CreateSubKey()" which don't change the slash to a path character.

The script is below but some notes are in order!
  1. The script is large based on the MS one but with some additions
  2. I have not used the original suggested cipher suites (although in the original script, this wasn't used anyway) but have used the Mozilla wiki to stick to ECDHE where possible.
  3. I have included some cipher suites which are not enabled by default (the ones with the curve number on the end like P256). However, I don't know how to enable these so they don't appear in the list of supported ciphers on the SSL labs test, if anyone knows, let me know.
  4. By all means modify the script if you want to keep some of the ciphers I have removed
  5. If you save the script as ciphers.ps1 file, make sure its properties say to copy it to the output and you can call it from the startup.cmd file just with PowerShell -ExecutionPolicy Unrestricted .\ciphers.ps1 >> "%TEMP%\StartupLog.txt" 2>&1
# from http://azure.microsoft.com/blog/2014/10/19/how-to-disable-ssl-3-0-in-azure-websites-roles-and-virtual-machines/


$nl = [Environment]::NewLine
$regkeys = @(
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Client",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Client",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Client",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 2.0\Server",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Client",
"HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\SSL 3.0\Server",
"HKLM:\SOFTWARE\Policies\Microsoft\Cryptography\Configuration\SSL\00010002"
)
# Cipher order as per the original script
# $cipherorder = "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256,"
# $cipherorder += "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P384,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA_P256,"
# $cipherorder += "TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA256,"
# $cipherorder += "TLS_RSA_WITH_AES_256_CBC_SHA,TLS_RSA_WITH_AES_128_CBC_SHA,"
# $cipherorder += "TLS_RSA_WITH_3DES_EDE_CBC_SHA,TLS_RSA_WITH_RC4_128_SHA,TLS_RSA_WITH_RC4_128_MD5"

# Cipher order as per Mozilla: https://wiki.mozilla.org/Security/Server_Side_TLS (Intermediate set - as mapped to Windows names)
$cipherorder = "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P256,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256_P521,"
$cipherorder += "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384_P521,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P521,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256_P384,"
$cipherorder += "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256_P521,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA_P521,TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA_P521,"
$cipherorder += "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P256,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P384,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384_P521,"
$cipherorder += "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384_P521,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA_P521,"
$cipherorder += "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA_P521,TLS_RSA_WITH_AES_128_CBC_SHA256,TLS_RSA_WITH_AES_128_CBC_SHA,"
$cipherorder += "TLS_RSA_WITH_AES_256_CBC_SHA256,TLS_RSA_WITH_AES_256_CBC_SHA"


# If any settings are changed, this will change to $True and the server will reboot
$reboot = $False

Function Set-CryptoSetting {
param (
$keyindex,
$value,
$valuedata,
$valuetype,
$restart
)

# Check for existence of registry key, and create if it does not exist
If (!(Test-Path -Path $regkeys[$keyindex])) {
Write-Host "Creating key: " + $regkeys[$keyindex] + "$nl"
New-Item $regkeys[$keyindex] | Out-Null
}

# Get data of registry value, or null if it does not exist
$val = (Get-ItemProperty -Path $regkeys[$keyindex] -Name $value -ErrorAction SilentlyContinue).$value

If ($val -eq $null) {
# Value does not exist - create and set to desired value
Write-Host "Value " + $regkeys[$keyindex] + "\$value does not exist, creating...$nl"
New-ItemProperty -Path $regkeys[$keyindex] -Name $value -Value $valuedata -PropertyType $valuetype | Out-Null
$restart = $True
} Else {
# Value does exist - if not equal to desired value, change it
If ($val -ne $valuedata) {
Write-Host "Value " + $regkeys[$keyindex] + "\$value not correct, setting it$nl"
Set-ItemProperty -Path $regkeys[$keyindex] -Name $value -Value $valuedata
$restart = $True
}
Else
{
Write-Host "Value " + $regkeys[$keyindex] + "\$value already set correctly$nl"
}
}
return $restart
}

# Special function that can handle keys that have a forward slash in them. Powershell changes the forward slash
# to a backslash in any function that takes a path.
Function Set-CryptoKey {
param (
$parent,
$childkey,
$value,
$valuedata,
$valuetype,
$restart
)

$child = $parent.OpenSubKey($childkey, $true);

If ($child -eq $null) {
# Need to create child key
$child = $parent.CreateSubKey($childkey);
}

# Get data of registry value, or null if it does not exist
$val = $child.GetValue($value);

If ($val -eq $null) {
# Value does not exist - create and set to desired value
Write-Host "Value $child\$value does not exist, creating...$nl"
$child.SetValue($value, $valuedata, $valuetype);
$restart = $True
} Else {
# Value does exist - if not equal to desired value, change it
If ($val -ne $valuedata) {
Write-Host "Value $child\$value not correct, setting it$nl"
$child.SetValue($value, $valuedata, $valuetype);
$restart = $True
}
Else
{
Write-Host "Value $child\$value already set correctly$nl"
}
}

return $restart
}

# Check for existence of parent registry keys (SSL 2.0 and SSL 3.0), and create if they do not exist
For ($i = 9; $i -le 12; $i = $i + 3) {
If (!(Test-Path -Path $regkeys[$i])) {
New-Item $regkeys[$i] | Out-Null
}
}

# Ensure SSL 2.0 disabled for client
$reboot = Set-CryptoSetting 10 DisabledByDefault 1 DWord $reboot

# Ensure SSL 2.0 disabled for server
$reboot = Set-CryptoSetting 11 Enabled 0 DWord $reboot

# Ensure SSL 3.0 disabled for client
$reboot = Set-CryptoSetting 13 DisabledByDefault 1 DWord $reboot

# Ensure SSL 3.0 disabled for server
$reboot = Set-CryptoSetting 14 Enabled 0 DWord $reboot

# Set cipher priority
$reboot = Set-CryptoSetting 15 Functions $cipherorder String $reboot

# We have to do something special with these keys if they contain a forward-slash since
# Powershell converts the forward slash to a backslash and it screws up the creation of the key!
#
# Just create these parent level keys first
$cipherskey = (get-item HKLM:\).OpenSubKey("SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers",$true)
If ($cipherskey -eq $null) {
$cipherskey = (get-item HKLM:\).CreateSubKey("SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Ciphers")
}

$hasheskey = (get-item HKLM:\).OpenSubKey("SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes",$true)
If ($hasheskey -eq $null) {
$hasheskey = (get-item HKLM:\).CreateSubKey("SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Hashes")
}

# Then add sub keys using a different function
# Disable RC4, DES, EXPORT, eNULL, aNULL, PSK and aECDH
$reboot = Set-CryptoKey $cipherskey "RC4 128/128" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "Triple DES 168" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "RC2 128/128" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "RC4 64/128" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "RC4 56/128" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "RC2 56/128" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "DES 56" Enabled 0 DWord $reboot # It's not clear whether the key is DES 56 or DES 56/56
$reboot = Set-CryptoKey $cipherskey "DES 56/56" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "RC4 40/128" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $cipherskey "RC2 40/128" Enabled 0 DWord $reboot

# Disable MD5, enable SHA (which should be by default)
$reboot = Set-CryptoKey $hasheskey "MD5" Enabled 0 DWord $reboot
$reboot = Set-CryptoKey $hasheskey "SHA" Enabled 0xFFFFFFFF DWord $reboot

$cipherskey.Close();
$hasheskey.Close();


# If any settings were changed, reboot
If ($reboot) {
Write-Host "Rebooting now..."
shutdown.exe /r /t 5 /c "Crypto settings changed" /f /d p:2:4
}