In mutual Transport Layer Security (TLS) (mTLS) authentication, the server and the client must authenticate each other's identity. This adds a few steps to the handshake process of an HTTPS connection. During one-way authentication, the client downloads the server public key certificate from the server for authentication. After the authentication is passed, a secure communication channel is established. During mutual authentication, the client downloads the server public key certificate from the server and uploads its public key certificate to the server for authentication. After the authentication on both sides is passed, a secure communication channel is established.
1. Principle
1.1 Process of one-way authentication
In one-way authentication, the public key certificate and private key files are stored in the server. The following items describe the handshake process.
The client initiates a request to establish an HTTPS connection and sends the information of the SSL protocol version to the server.
The server sends its public key certificate (server.crt) to the client.
The client obtains the public key from the server.crt certificate.
The client generates a random number (key R), uses the obtained public key to encrypt this number, and sends the encrypted number to the server.
The server uses its own private key (server.key) to decrypt the ciphertext to obtain key R.
The server and client use key R for subsequent communication.
1.2 Process of mutual authentication
The client creates a request to establish an HTTPS connection and sends the information of the SSL protocol version to the server.
The server sends its public key certificate (server.crt) to the client.
The client obtains the public key from the server.crt certificate.
The client sends its public key certificate (client.crt) to the server.
The server uses the root certificate (root.crt) to decrypt the client public key certificate to obtain the client public key.
The client sends the supported cipher suites to the server.
The server selects a cipher suite that both sides can accept, uses the client public key to encrypt the cipher suite, and then sends the encrypted cipher suite to the client.
The client uses its own private key to decrypt the cipher suite to obtain a random number (key R), uses the server public key to encrypt the random number, and sends the encrypted number to the server.
The server uses its own private key to decrypt the ciphertext to obtain key R.
The server and client use key R for subsequent communication.
2. Certificate preparation
Based on the description in the previous section, the following certificate files are required to complete mutual authentication:
Server public key certificate: server.crt
Server private key file: server.key
Root certificate: root.crt
Client public key certificate: client.crt
Client private key file: client.key
Client integrated certificate that consists of the public and private keys and is used for browser access: client.p12
You can apply for these certificate files from a certificate authority (CA). You are charged for certificate issuance. We recommend that you choose a trusted certificate authority. If the certificate is used within your enterprise, you can also issue your self-signed certificate. For specific issuance methods, see the Self-signed certificates section.
3. Configure mTLS authentication in API Gateway
After you prepare the required certificate files mentioned in Certificate preparation, you can configure mTLS authentication in API Gateway. This section describes how to bind the server certificate and root certificate of your domain name to API Gateway for mTLS authentication.
Log on to the API Gateway console. In the left-side navigation pane, choose Manage APIs > API Groups. On the API Groups page, click Create Group in the upper-right corner.
On the Create Group page, specify the Instances, Group Name, and BasePath parameters and then click Confirm.
On the API Groups page, click the created group to go to the Group Details page.
In the Independent Domains section, click Bind Domain Name.
In the Bind Domain Name dialog box, specify the Domain Name parameter and other parameters based on your business requirements. Then, click Confirm to bind the domain name to the API group.
In the Independent Domains section of the Group Details page, click Select Certificate for the bound domain name.
In the Select Certificate dialog box, click Add Certificate.
In the Create Certificate dialog box, add the following three certificates described in Certificate preparation:
Enter the content of the server public key certificate (server.crt) in the Certificate Content field.
Enter the content of the server private key file (server.key) in the Private Key field.
Enter the content of the root certificate (root.crt) in the Root Certificate field.
MTLS authentication is configured in API Gateway after you complete the preceding steps.
4. Self-signed certificates
Before you generate a self-signed certificate, generate a CA root certificate to issue the server public key certificate and the client public key certificate. In this section, the root certificate is used to generate two different client certificates and then the two client certificates are used to send requests simultaneously to check whether the server can authenticate them.
4.1 Generate a self-signed root certificate
1. Create a private key for the root certificate.
openssl genrsa -out root.key 2048
2. Create a certificate signing request (CSR) file for the root certificate.
openssl req -new -out root.csr -key root.key
You must specify the following parameters as required. The following configurations provide an example:
Country Name (2 letter code) [XX]:cn
State or Province Name (full name) []:bj
Locality Name (eg, city) [Default City]:bj
Organization Name (eg, company) [Default Company Ltd]:alibaba
Organizational Unit Name (eg, section) []:test
Common Name (eg, your name or your servers hostname) []:root
Email Address []:a.alibaba.com
A challenge password []:
An optional company name []:
3. Create a root certificate.
openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650
When you create a CSR file for a root certificate, server certificate, or client certificate, consider the following points: Set Common Name to root for the root certificate and to a domain name when you create a client or server certificate. Make sure that this parameter is unique for the root, client, and server certificates. Use the same settings for other parameters. Press Enter to skip the password configuration. This also applies when you create a client or server certificate.
After the preceding configurations, you can obtain the root certificate (root.crt) that has a validity period of 10 years. Then, you can use this root certificate to issue server and client certificates.
4.2 Generate a self-signed server certificate
1. Generate a server certificate private key.
openssl genrsa -out server.key 2048
2. Generate a CSR file for the server certificate. For more information about the procedure and precautions, see the "Generate a self-signed root certificate" section.
openssl req -new -out server.csr -key server.key
3. Generate a server public key certificate.
openssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
After the configurations, you can obtain the following files:
server.key (the private key file of the server) and server.crt (the server public key certificate that has a validity period of 10 years, which is generated by using the root certificate and the server private key file).
4.3 Generate a self-signed client certificate
1. Generate a client certificate key.
openssl genrsa -out client.key 2048
openssl genrsa -out client2.key 2048
2. Generate a CSR file for the client certificate. For more information about the procedure and precautions, see the "Generate a self-signed root certificate" section.
openssl req -new -out client.csr -key client.key
openssl req -new -out client2.csr -key client2.key
3. Generate a client certificate.
openssl x509 -req -in client.csr -out client.crt -signkey client.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
openssl x509 -req -in client2.csr -out client2.crt -signkey client2.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
4. To generate a client certificate in P12 format, enter a password and remember the password.
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
openssl pkcs12 -export -clcerts -in client2.crt -inkey client2.key -out client2.p12
You can repeat the preceding three steps to obtain two sets of client certificate files:
client.key and client2.key: private key files.
client.crt and client2.key: client certificates that have a validity period of 10 years.
You can use the root certificate and the client private key to generate client.p12 and client2.p12. The certificates contain the client public and private keys and are mainly used for browser access.
5. Verification
You can specify the paths of certificates in curl commands to verify whether mTLS authentication of an NGINX server is configured. The following test cases are used:
Use client.crt and client.key to call the server.
Use client2.crt and client2.key to call the server.
Call the server without using certificates.
The results of the test cases are demonstrated as follows:
5.1 Successful calls with certificates
Use client.crt and client.key to call the server.
# -- cert specifies the path of the client public key certificate.
# -- key specifies the path of the client private key file.
#-k Do not verify the validity of the certificate. A self-signed certificate is used.
#-v checks the specific SSL handshake process.
curl --cert ./client.crt --key ./client.key https://integration-fred2.fredhuang.com -k -v
* Rebuilt URL to: https://47.93.XX.XX/
* Trying 47.93.XX.XX...
* TCP_NODELAY set
* Connected to 47.93.XX.XX (47.93.XX.XX) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
* CAfile: /etc/ssl/cert.pem
CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: C=CN; ST=BJ; L=BJ; O=Alibaba; OU=Test; CN=integration-fred2.fredhuang.com; emailAddress=a@alibaba.com
* start date: Nov 2 01:01:34 2019 GMT
* expire date: Oct 30 01:01:34 2029 GMT
* issuer: C=CN; ST=BJ; L=BJ; O=Alibaba; OU=Test; CN=root; emailAddress=a@alibaba.com
* SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
> GET / HTTP/1.1
> host:integration-fred2.fredhuang.com
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Server: nginx/1.17.5
< Date: Sat, 02 Nov 2019 02:39:43 GMT
< Content-Type: text/html
< Content-Length: 612
< Last-Modified: Wed, 30 Oct 2019 11:29:45 GMT
< Connection: keep-alive
< ETag: "5db97429-264"
< Accept-Ranges: bytes
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host 47.93.XX.XX left intact
Use client2.crt and client2.key to call the server.
curl --cert ./client2.crt --key ./client2.key https://integration-fred2.fredhuang.com -k
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
5.2 Calls without certificates
curl https://integration-fred2.fredhuang.com -k
<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/1.17.5</center>
</body>
</html>
The test cases are accurate and meet expectations. The first test log shows that a server call without certificates requires a longer time to process. The client verifies the server certificate and uploads its own certificate to the server for verification. Two client certificates issued by using the root certificate can all trigger the mTLS authentication process. Calls without client certificates are rejected by the server.
6. Use Java to call
Because a self-signed certificate is used, if you use Apache HttpClient
to call the server, you must add the server certificate as a trusted certificate. You can also ignore the certificate in the code.
cd $JAVA_HOME
sudo ./bin/keytool -import -alias ttt -keystore cacerts -file /Users/fred/temp/cert5/server.crt
After you set the public key certificate of the server as the trusted certificate, you can initiate HTTPS requests with the client certificate by using the following code.
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import javax.net.ssl.SSLContext;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
public class HttpClientWithClientCert {
private final static String PFX_PATH = "/Users/fred/temp/cert5/client.p12"; //Path of the client certificate.
private final static String PFX_PWD = "123456"; //Password of the client certificate.
public static String sslRequestGet(String url) throws Exception {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
InputStream instream = new FileInputStream(new File(PFX_PATH));
try {
keyStore.load(instream, PFX_PWD.toCharArray());
} finally {
instream.close();
}
SSLContext sslcontext = SSLContexts.custom().loadKeyMaterial(keyStore, PFX_PWD.toCharArray()).build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext
, new String[] { "TLSv1" } // Supported protocols. You can set the value as required.
, null // supportedCipherSuites
, SSLConnectionSocketFactory.getDefaultHostnameVerifier());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslsf).build();
try {
HttpGet httpget = new HttpGet(url);
// httpget.addHeader("host", "integration-fred2.fredhuang.com");// Set headers.
CloseableHttpResponse response = httpclient.execute(httpget);
try {
HttpEntity entity = response.getEntity();
String jsonStr = EntityUtils.toString(response.getEntity(), "UTF-8");// Return a response.
EntityUtils.consume(entity);
return jsonStr;
} finally {
response.close();
}
} finally {
httpclient.close();
}
}
public static void main(String[] args) throws Exception {
System.out.println(System.getProperty("java.home"));
System.out.println(sslRequestGet("https://integration-fred2.fredhuang.com"));
}
}