SSL in Azure App Service
Azure app service is a multitenant infrastructure. When web apps are created on azure app service, we provide a default hostname which is sitename.azurewebsites.net. Clients can make requests over HTTP or HTTPs to these hostnames. When using HTTPs against the default hostname for the site, a certificate with subject name *.azurewebsites.net is returned to the client for SSL.
When customers configure an app service with a custom domain name, by default the system allows you to pick between an
SNI-SSL binding type or
IP-SSL binding type.
SNI-SSL bindings are free of cost. For an SNI binding to be in use, clients making requests to this host will need to include an SNI header in the TLS initial handshake message. From a specification standpoint, TLS 1.0 supports SNI header extension, and all modern browsers includes the SNI header by default.
In contrast, there can be older versions of browser clients or legacy library clients. For custom domains to work correctly for these clients, the site must be configured to have IPSSL bindings.
What is SNI and IP-SSL really and why does it matter?
When the initial SSL handshake request arrives at a web server, the mandatory parameters like the version of SSL being negotiated, the cipher suites the client supports are included in the clear text
CLIENT HELLO message. From the server standpoint, with this information, the server needs to select the appropriate certificate to use for this request. For a web server with a single site/hostname, the matter is easy, you can configure the server with just one binding/certificate on a single port and all is fine.
However, in a webserver which hosts multiple sites or hostnames, based on the version + cipher suites alone, the server cannot pick the correct certificate. Hence, this requires the bindings on the server to be configured based on a different IP for each of the hostname the server wishes to support.
How does SNI help?
To solve the problem of not requiring unique IP address on the server side to support multiple hostnames with SSL, the concept of an SNI extension was introduced in RFC 3546 [
https://tools.ietf.org/html/rfc3546], back in 2003.
Per this spec a client can include the destination host name in the clear text
CLIENT HELLO message as an additional server name extension. Now, at the server, based on this information, the server can select the appropriate certificate to use for the handshake, thus solving the problem of not requiring unique IP addresses.
Why does Azure App Service support both SNI-SSL and IP-SSL bindings?
While the RFC for SNI was introduced in 2003, and all major browsers added support shortly after that, there are still legacy HTTPs clients which doesn’t include the SNI header extension in the client hello. If an azure app service customer anticipates such a client to use their site hosted in azure app service, it is recommended to add an IP-SSL binding, so that this site now has a dedicated IP Address. This allows us to pick the correct certificate for that hostname.
What are the breaking changes with TLS 1.2 compliance initiative?
We now enforce TLS 1.2 for clients which arrive without SNI header. Note that these are typically old browser clients which do not support SNI or programmatic clients which were written long ago and not updated to include the SNI header name.
- For sites which did not have a custom domain name configured:
These hostnames are always
SNI-SSL (or more like SNI-Required).
For these sites, the hostnames used is the default sitename.azurewebsites.net. Customers can configure the minimum required TLS version on this site to be 1.0 and above, and we will honor it.
However, if a request arrives without the SNI extension in the
CLIENT HELLO message, we are unable to identify the exact site the request is eventually destined to, and hence we require this request to be at least of TLS 1.2 for compliance reasons.
- Sites with custom domain configured:
For these sites just like default host name, the configuration for the minimum required TLS version can be set and will be honored by the system. However, if a request arrives without the SNI extension in the CLIENT HELLO, we require this client to at least negotiate TLS 1.2, just like the case of default hostname.
- IP-SSL: No breaking change, everything should continue to work as is.
For IP-SSL bindings, since there is a dedicated IP for the hostname, our system can correctly determine the destination site based on the incoming IP address, and hence there is no requirement of SNI header or minimum TLS version requirement on requests without the SNI header. The minimum configured TLS version for the web app will take effect.
I'm currently using SNI-SSL bindings: What do I do if I'm impacted?
For browser clients, upgrade browser clients to modern browsers. For security best practices, it is highly recommended to use modern up to date browsers to protect the site and the user.
For programmatic scenarios, update application code to either use TLS 1.2, or explicitly include the hostname in the client hello handshake if using TLS 1.0
To update the TLS version in a .NET app using HttpWebRequest or HttpClient, update the SecurityProtocol in the ServicePointManager as shown below.
// To update the TLS version in a .NET C# app using HttpClient/HttpWebRequest,
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
- Note that when using HttpWebRequest/HttpClient class to make SSL requests, SNI header is automatically added.
If both a) and b) are not an option, the last resort and the least preferred option, would be to use IP-SSL bindings.
- If you are using the default hostname, then you will need to add a custom domain name with IP-SSL.
- If you are already using a custom domain name, you'll need to change the binding to IP-SSL.
/* Sample code */
using System;
using System.Net;
namespace SNIClient
{
class Program
{
static void Main(string[] args)
{
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create("https://mysite.azurewebsites.net/");
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
response.Close();
}
catch (WebException ex)
{
Console.WriteLine(ex.Message);
}
}
}
}
What if I have questions?