Securing APIs with Transport Layer Security (TLS)
The Transport Layer Security protocol (TLS) ensures the confidentiality and integrity of data in transit, and by enforcing client authentication, mutual TLS (mTLS) protects your APIs from intruders.
In any API deployment, Transport Layer Security (TLS) is the most common form of protection. We secure APIs by using TLS to encrypt the communication or protect the data in transit, and also by using TLS mutual authentication to ensure only legitimate clients can access them.
Security over communication links is greatly enhanced by Transport Layer Security (TLS). One of the first tech giants to realize the value of TLS was Google. Google made TLS the default setting in Gmail in Jan 2010 to secure all Gmail communications. As of Oct 2011, Google made google.com available on HTTPS, as well as all Google search queries and results. The HTTP over TLS protocol is HTTPS.
In addition to establishing a secure communication channel, TLS also allows both parties to identify one another. We use one-way TLS on the Internet where only the server authenticates to the client. As a result, the client can determine exactly which server it is communicating with. By observing and matching the server’s certificate with the URL the user hits on the browser, the server’s certificate is verified. Unlike one-way TLS, mutual authentication identifies both the client and the server. Both the client and the server know exactly which server they are communicating with.
Understanding Transmission Control Protocol (TCP)
The Transmission Control Protocol (TCP) provides a good background for understanding how Transport Layer Security (TLS) works. TCP is a layer of abstraction of a reliable network running over an unreliable channel. Host-to-host routing and addressing are provided by IP (Internet Protocol).
For network communication, the TCP/IP protocol suite presents a four-layered model. Using well-defined interfaces, each layer communicates with the others and has its own responsibilities.
A three-way handshake bootstraps any TCP connection. Clients initiate the TCP three-way handshake by sending a TCP packet to the server. In this case, the packet is called the SYN packet. The SYN flag is set in the TCP packet. As part of the SYN packet, the client selects a random sequence number, the source port number, the destination port number, the TCP Segment Len field, and other fields. The TCP Segment Len field indicates how much application data this packet carries. The TCP Segment Len field will be zero for all messages sent during the TCP three-way handshake since no exchange has yet begun.
When the server receives the initiate message from the client, it too picks a random sequence number and passes it back to the client. The packet is known as the SYN-ACK packet. In order to achieve error control and ordered delivery, TCP packets must be uniquely identified. Clients and servers exchange sequence numbers to keep that promise. By numbering the packets, both sides of the communication channel will know which packets get lost during transmission and which packets are duplicates, and how to order a random set of packets.
Upon receiving the SYN-ACK packet from the server, the client sends a TCP packet to acknowledge the handshake. A packet such as this is called the ACK packet. This includes the source port, destination port, the initial client sequence number + 1 as the new sequence number, and the acknowledgment number. The acknowledgment number is calculated by adding 1 to the sequence number in the SYN-ACK packet. The TCP Segment Len field is zero because we are still in the three-way handshake.
After the handshake has been completed, the client and server can begin transmitting application data.
How does TLS Works?
Handshake and Data transfer are two phases of the Transport Layer Security (TLS) protocol.
The handshake phase involves both the client and server learning about each other’s cryptographic capabilities and establishing cryptographic keys to secure the data transfer. After the handshake, the data is transferred. A set of records is created, protected with cryptographic keys established in the first phase, and transferred between the client and the server.
TLS Handshake
TLS introduces its own handshake, similar to the three-way TCP handshake. There are three subprotocols in the TLS handshake.
- Handshake protocol —To protect application data, it is responsible for building an agreement between the client and server on cryptographic keys.
- Change Cipher Spec protocol — To indicate to each other that it’s switching to a cryptographically secured channel, both clients and servers use the Change Cipher Spec protocol.
- Alert protocol — During a TLS connection, it generates alerts and communicates them to all parties involved.
TLS handshakes follow TCP handshakes. Everything in the TLS handshake is just application data for the TCP (transport layer). Upon completion of the TCP handshake, the TLS layer will initiate the TLS handshake.
- In the TLS handshake, the Client Hello is the first message from the client to the server. A Client Hello message includes the highest version of TLS the client supports, a random number generated by the client, cipher suites and compression algorithms supported by the client, and an optional session identifier. By using the session identifier, a session can be resumed rather than starting over.
- The server responds to the Client Hello message from the client with a Server Hello message. At the TLS layer, the Server Hello is the first message from the server to the client. Server Hello messages contain the strongest cipher suite, the strongest TLS protocol version, and the compression algorithm that both the client and server can support. Both parties use the random numbers generated by each other independently to generate the master secret. Using this master secret, encryption keys can be derived later.
- When the Server Hello message is sent to the client, the server sends its public certificate as well as other certificates up to the root CA. To accept the identity of the server, the client must validate these certificates. The public key from the server certificate is used to encrypt the premaster secret key. The premaster key is a shared secret between the client and the server to generate the master secret. A Server Key Exchange is necessary if the public key in the server certificate cannot encrypt the premaster secret key. This step requires the server to create a new key and send it to the client. It will later be used by the client to encrypt its premaster secret key.
- Next, the server must request the client certificate if it requires TLS mutual authentication. The client certificate request message from the server includes a list of certificate authorities trusted by the server and the type of the certificate.
- A Server Hello Done message is then sent to the client by the server. This is an empty message that only indicates to the client that the server has completed its initial phase in the handshake.
- Clients are now required to send their public certificates along with all the certificates in their chain up to the root certificate authority (CA) required to validate their certificates if the server demands them.
- Next, we have the Client Key Exchange message, which includes the TLS protocol version and the premaster secret key. If the premaster secret key is included in the message, it should be encrypted with the public key of the server obtained from the server certificate or with the key sent in the Server Key Exchange message.
- Next is the Certificate Verify message. Client authentication is optional and is only required if the server requires it. The client must sign all TLS handshake messages so far with its private key and send the signature to the server. The server validates the signature using the public key of the client, which was shared previously.
- The client and server have now exchanged all the materials necessary to generate the master secret. A master secret is generated using the client random number, the server random number, and the premaster secret. Using the Change Cipher Spec message, the client now informs the server that all messages generated from this point forward will be protected by the keys already established.
- A Finished message is the last one sent from a client to a server. It’s the hash of the complete message flow in the TLS handshake encrypted by the already established keys. As soon as the server receives the Finished message from the client, it responds with the Change Cipher Spec message. This indicates to the client that the server is ready to communicate with the secret keys that have already been established. The server will then send the client the Finished message.
The TLS handshake is now complete, and now both the client and the server can send data over an encrypted channel.
Securing API with Transport Layer Security (TLS)
Using Spring Boot, a popular microservices development framework for Java, we will develop an API. To execute the code, clone the repository and run it with maven.
$ git clone https://github.com/learncsdesign/secure-API-demo.git
$ mvn spring-boot:run
Open the URL in your browser or use the following command to test the API with a cURL client.
$ curl http://localhost:8080/hello
Greetings from Spring Boot!
To enable TLS, we must first create a public/private key pair for the API server. Using the keytool that comes with the default Java distribution, the following command generates a key pair and stores it in keystore.p12. The file is also referred to as a keystore, and it can be in different formats. Java KeyStore (JKS) and PKCS12 are the two most popular formats. PKCS12 is a standard that belongs to the Public Key Cryptography Standards (PKCS) family, whereas JKS is specific to Java.
keytool -genkeypair -alias tlsdemo -keyalg RSA -keysize 4096 -validity 365 -dname "CN=localhost,OU=self,O=learncsdesign,L=delhi,S=dl,C=in" -keypass changeit -keystore keystore.p12 -storeType PKCS12 -storepass changeit
- -genkeypair — Generates a key pair
- -alias <alias> — Alias name of the entry to process
- -keyalg <alg> — Key algorithm name
- -keysize <size> — Key bit size
- -validity <days> — Validity number of days
- -dname <dname> — Distinguished name,
CN is CommonName of a person
OU is OrganizationUnit
O is OrganizationName
L is LocalityName
S is StateName
C is CountryName - -keypass <arg> — Key Password
- -keystore <keystore> Keystore name
- -storeType <type> — Keystore type
- -storepass <arg> — Keystore password
The keystore file will be called keystore.p12 and protected with the password “changeit”. A self-signed certificate is the one created in this example.
Copy the keystore file (keystore.p12) to the home directory of the project and add an entry to src/main/resources/application.properties to enable TLS for the Spring Boot API as shown below:
# enable/disable https
server.ssl.enabled=true
# keystore format
server.ssl.key-store-type=PKCS12
# keystore location
server.ssl.key-store=keystore.p12
# keystore password
server.ssl.key-store-password=changeit
Make sure everything is working properly by running it again.
$ mvn spring-boot:run ............... Tomcat started on port(s): 8080 (https) with context path ''
Use the following command from the command line to test the API with the cURL client.
$ curl -k https://localhost:8080/hello
Greetings from Spring Boot!
Since we have a self-signed certificate to secure our HTTPS endpoint, we told cURL to ignore the trust validation by using the -k option. We can use the corresponding public certificate instead of -k if we have a self-signed certificate.
By using the following keytool command, we can export the public certificate as a PEM file, tlsdemo.crt.
$ keytool -export -file tlsdemo.crt -alias tlsdemo -rfc -keystore keystore.p12 -storepass changeit
The public certificate has now been pointed to by cURL.
$ curl --cacert tlsdemo.cert https://localhost:8080/hello
Greetings from Spring Boot!
Protecting API with mutual TLS
Now let’s look at how to enable TLS mutual authentication between the API server and the cURL client. System-to-system authentication is usually enabled by TLS mutual authentication.
The first step is to ensure that TLS is enabled and settings are present in application.properties, as shown in the previous section. Let’s enable TLS mutual authentication by adding the following line to src/main/resources/application.properties:
# mutual TLS
server.ssl.client-auth:need
#trust store location
server.ssl.trust-store=truststore
#trust store password
server.ssl.trust-store-password=changeit
Using cURL, we can invoke the hello API to test the flow. To start up the hello API, run the following command and notice the line that prints the HTTPS port.
$ mvn spring-boot:run ............... Tomcat started on port(s): 8080 (https) with context path ''
From another command console, use the following command to test the API with a cURL client.
$ curl -k https://localhost:8080/hello
curl: (56) OpenSSL SSL_read: error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate, errno 0
Since the API is protected with TLS mutual authentication, so we got the error.
In order to fix this, we need to create a key pair for the cURL client and configure the API server so that it trusts the public key.
With the cURL command, we can access the API that is protected by mutual TLS using the key pair we created.
We use the following OpenSSL command to generate a private key and a public key for the cURL client.
$ openssl.exe genrsa -out privkey.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
........++++
....................................++++
e is 65537 (0x010001)
Use the following command to generate a self-signed certificate corresponding to privkey.pem:
$ openssl req -key privkey.pem -new -x509 -sha256 -nodes -out client.crt -subj "/CN=client"
Let’s take down the API server if it is still running, and import the public certificate (client.crt) we created in the preceding step to create the truststore, using the following command.
$ keytool -import -file client.crt -alias client -keystore truststore -storepass changeit
By invoking the hello API using cURL, we can now test the flow. Start by running the hello API with the following command.
$ mvn spring-boot:run ............... Tomcat started on port(s): 8080 (https) with context path ''
Using a different command console, you can test the API with a cURL client.
$ curl -k --key privkey.pem --cert client.crt https://localhost:8080/hello
Greetings from Spring Boot!
If you execute the preceding cURL command with a key pair that is not imported into the truststore, you will see the following error.
curl: (56) OpenSSL SSL_read: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown, errno 0
Summary
- In any API deployment, TLS is the most common form of protection.
- Using TLS, data is protected in transit for confidentiality and integrity, and by using mutual TLS (mTLS), data is protected from intruders by enforcing client authentication.