Configuring SSL/TLS for mobile app backend using Nginx

As you might already know, in iOS 9, for all devices, including iPad and iPhone, Apple requires that all http communication goes through SSL/TLS, and will no longer allow apps bypass that. This is their way to acknowledge that security is important. So you definitely will need a SSL security certificate for mobile app.

Nowadays application security is pretty important, because failing to implement it properly can lead to financial losses, personal data leakage, and all kinds of malicious stuff happening.

Imagine you run a banking app, and an attacker gains access to private user information, or gains privileges to transfer money from their accounts. This will be the end of your banking app and company, probably!

This article will be about configuring SSL on the backend running Nginx, properly. But before we get started, there is one important thing to be said. Protecting app with SSL certificate is only a part of security, albeit important! You should also ensure your mobile backend is secured from other, non-SSL related attacks.

So, let’s get going!

What is SSL/TLS and why Nginx?

SSL (Secure Sockets Layer) and its successor, TLS (Transport Layer Security), are cryptographic protocols designed to provide communications security over a computer network. Thus, if you use SSL/TLS, all traffic that goes from the user to the server is encrypted. It’s the most secure way to encrypt mobile traffic.

What’s better for security than encrypted access to mobile application?

Nginx (pronounced as “Engine X”) is a web server that focuses on high concurrency, performance and low memory usage. One important feature is that it can be used as a reverse proxy for other servers (This means that mobile app’s users communicate with Nginx server, and Nginx server communicates with the backend server).

This allows to do all kind of useful stuff, like caching responses from backend, compressing them before they get sent to the user, encrypting them (this is where TLS comes in), load balancing, etc. And all this comes ready-made, you only need to configure it properly! It saves your time and money on development. Want to use mobile traffic encryption for your project?

Let’s start actually setting up SSL on NGINX for performance and security!

ssl

Do not use default Nginx configuration that comes bundled

The defaults are often pretty much insecure. They are likely to have old and broken protocols enabled, for compatibility purposes, which you will not require for modern iOS/Android apps, and not even for the website (if you run one).

Always use a new version of Nginx and openssl

There are vulnerabilities found in software each day, and you never know when it will be the software you use. Staying up to date will save you from getting bitten by them!

Example of such vulnerability is Heartbleed. It allows an attacker to grab the last 64 kb encrypted by the server. This seems like not much, but if the attacker is clever – he will repeat that continuously, and will grab all the traffic the server received, including passwords, tokens and other private information. This is patched in modern versions of OpenSSL.

Use SHA256-signed SSL certificate, with RSA 2048-bit key (or equivalent)

SHA-1 is a so called cryptographic hash function. Its value is used to verify that the certificate is really coming from you. It has weaknesses found already, and might be broken soon.

Because of this, SHA1-signed certificates are dimmed obsolete by Google, and, moreover, Apple will reject them. Same goes for keys worse than RSA 2048-bit – with the growth of PC’s computational power, a well-funded attacker might be able to break them soon.ssl

After this is all complete, let’s start Nginx. We will assume that all the latter nginx configuration is happening inside a server block. You can take it from example configuration files.:

START NGINX

Point Nginx to your certificate and private key you got earlier, tell it to listen on port 443 (default SSL’s port), and set the server name. We also have ipv6 (second listen block) and spdy enabled. “deffered” also says that this is the default server. If you are doing multisite configuration – it is important that you have only one “deffered” for each port (ipv4 and ipv6 are accounted separately).

server_name example.com;

listen 443 ssl spdy deferred;
listen [::]:443 ssl spdy deferred;

ssl_certificate /path/to/cert.crt;
ssl_certificate_key /path/to/private.key;

We will not cover how to get the certificate and the private key. Some companies selling certificates even provide a web interface to generate them for you, so ask them for help in getting these, when you have decided which company you are going to use.

Use only TLSv1.0 or newer

SSLv2 is susceptible to downgrade attacks (when the attacker forces the client and the server to decide to use weak encryption, and can then break it in less than a day on a modern PC).

SSLv3 is broken by POODLE attack (attacker can get 1 byte of data in 256 requests max. This sounds like not much, but in the real world it is enough to get, for example, authentication data), or, if used with RC4 – it is broken by RC4 weaknesses. There is also POODLE for TLSv1.0, but if you use modern software – it is fixed there.

You can force TLSv1 or higher with the following configuration line:

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;

Review the ciphers used

There are lots of weak ciphers enabled by default. The most notable are export-grade ciphers introduced by US government for export to other countries, and that are easily broken. You can start here .

We use a somewhat modified intermediate compatibility cipher suite, to disable some weak TLSv1.0 ciphers, but to still support TLSv1.0 devices (like Android < 4.4). (Note: If you need support for DSS keys, remove !DSS from the end. We put it there to make openssl’s output somewhat shorter).

This cipher suite gives Forward Secrecy (previously captured traffic cannot be decrypted even if the server key is compromised), best possible encryption (it prefers AES128 over AES256, because AES128 is as strong as AES256 as a recent attack has shown, but it works 4 to 5 times faster), and it does not offer RC4:

ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!MD5:!DES:!eNULL:!EXPORT:!3DES:!PSK:!LOW:!EXP:!SRP:!RC4:!DSS;

Also, do not forget this line:

ssl_prefer_server_ciphers on;

It will force server-preferred cipher suite order.ssl

HTTP Public Key Pinning

What it does is forcing the browser to only use certificates with a certain public key (which is specific to your server). This saves you from attacks with forged certificates, if an attacker can compromise a CA (the one who issues SSL security certificates for mobile app). You can enable it with the following directive:

add_header Public-Key-Pins 'pin-sha256="base64=="; max-age=expireTime;';

or

add_header Public-Key-Pins 'pin-sha256="base64=="; max-age=expireTime; includeSubdomains; report-uri="reportURI";';

Where

  • base64 is key information (command to extract that is shown later),
  • expireTime is the pin validity time.
  • includeSubdomains will also include all the subdomains (e.g. not only example.com, but www.example.com, mail.example.com, api.example.com, etc). This is optional.
  • reportURI is the URI of the address to where to send incidents report (if there is a key different from the pin found). This is also optional.

You can include multiple pins. It is recommended that you include one with a backup key in case the first key is compromised. Just add more pin-sha256 to the header.

To extract information you can use these commands (from certificate, certificate signing request, or key file, or even the site):

openssl rsa -in my-key-file.key -outform der -pubout | openssl dgst -sha256 -binary | openssl enc -base64
openssl req -in my-signing-request.csr -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
openssl x509 -in my-certificate.crt -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
openssl s_client -connect www.example.com:443 | openssl x509 -pubkey -noout | openssl rsa -pubin -outform der | openssl dgst -sha256

HTTP Strict Transport Security

This means that the site forces the usage of SSL/TLS, even before the first request, which can save the user from man-in-the-middle attacks, when an attacker forces the browser to redirect to non-ssl address, and then acts as a medium between the client and the server.ssl

It will save every user who visited your site at least once. Enabling is as simple as adding this line to the configuration:

add_header Strict-Transport-Security 'max-age=31536000;';

or

add_header Strict-Transport-Security 'max-age=31536000; includeSubdomains; preload;';
  • max-age is in seconds. Here it is set to be 1 year.
  • includeSubdomains works pretty much like the one from HTTP Public Key Pinning. It is optional.
  • preload is for those who own the second-level domain, and want the header to be bundled with the browser, to save the users who have not visited the site, ever, from the described attack. This is optional. Details about the data for submission can be found here

GENERATE custom Diffie-Hellman key exchange parameters

The defaults used by modern Nginx are relying on openssl input. By default it generates 1024 bit parameters. Let’s create 2048 parameters:

openssl dhparam -out /path/to/dhparam.pem 2048

… and then use them in Nginx:

ssl_dhparam /path/to/dhparam.pem;

This is it for TLS security, but what about TLS performance? Nginx is well-performing for SSL, and it will not hit the server that much, considering that most of the modern apps are pretty function-heavy. But let’s also optimize performance anyway.

Read also:

Enable session cache

The most time-consuming operations in SSL are key-generation related operations. Let’s save some time on old users visiting again – we will simply reuse their old session. This is called session caching.

ssl_session_cache shared:SSL:50m;
ssl_session_timeout 5m;

Here, shared:SSL:50m says that the session cache is shared between all nginx processes (by default nginx starts a few), SSL is the cache name (it is useful for multisite setups), and 50m is cache size in megabytes (1 megabyte holds about 4000 sessions). ssl_session_timeout 5m; means that the session is stored for 5 minutes.

If you have multiple reverse proxy servers, you should also disable tickets, as they are not shared by them, and might break stuff. Do this by adding this line:

ssl_session_tickets off;

OSCP Stapling

This option shaves off one request from the client handshake initiation, by including certificate revocation information in the communication with the server:

ssl_trusted_certificate /path/to/cert.crt;
resolver 8.8.8.8;
ssl_stapling on;
ssl_stapling_verify on;

WWW subdomain handling and redirecting from http to HTTPS

The last, but not the least – add this server block (for example, above the one you were just using), to redirect all your newcoming users from http to https:

server {
    server_name example.com;

    listen 80 deferred;
    listen [::]:80 deferred;

    return 301 https://example.com$request_uri;
}

If you move out all your ssl configuration to a separate file (apart from listen directives and server name), you can also do a redirect from www subdomain to root domain. Say, ssl.inc contains all your ssl configuration, and is stored in one folder with your configuration file. Then you need to add this server block:

server {
    server_name www.example.com;

    listen 80;
    listen 443 ssl spdy;
    listen [::]:80;
    listen [::]:443 ssl spdy;

    include ssl.inc;

    return 301 https://example.com$request_uri;
}

Conclusionssl

This concludes the configuration of TLS in Nginx for performance and security. HSTS and HPKP should be optional, because they somewhat limit your actions. All the other points said on top are important and we definitely recommend enabling them.

As was said in the beginning – TLS is a part of security for your project, not the whole security. The server also has to be protected from common problems (OWASP Top 10 for example), SELinux should be enabled, and all the other best practices.

If you have any questions about setting up SSL on nginx for performance and security or just need a tip about mobile data encryption techniques write us in comments bellow or contact us here

Comments

1 comment

Comments are closed.