This page describes how to configure Smile CDR to use TLS (aka SSL) on an HTTP server.
If you are already comfortable with how TLS works, please feel free to skip to the next section.
The HTTPS protocol uses an encryption standard called TLS (formerly called SSL). While there are nuances to these terms, you can think of them all as being interchangeable. TLS is based on public/private key cryptography. You can think of this type of cryptography as being a bit like a physical lock and key used to open a door, where the server has a key and the client has a lock that the server needs to open.
This analogy is a bit strange, but it works: In the real world, we use a lock on the door to make sure that only the right person (the one holding the key) is allowed to enter the building. Of course the lock is not private: Anyone can look at the lock. The key however needs to be kept private since anyone who gets a good look at it can make a copy and get access to the building. A web server using HTTPS is of course concerned with encryption (this is the primary purpose of HTTPS) but this protocol also serves a second less obvious purpose: It allows the client to be sure they are talking to the right server. The problem here makes sense: If a user is connecting to their bank server (or their health records!) they are going to enter their credentials and other sensitive information. If they have connected to a server that is impersonating their bank, it doesn't matter that the connection is encrypted since they will still send the same credentials. So TLS needs to allow the client to trust that they have connected to the right server.
This is where the lock and key analogy comes in. The server (the bank) has a key that belongs only to it. The client (the user) has a lock that this key can open. When the client is about to give their credentials to the bank, they will first say "show me that your key works in my lock" and if it works we have trust.
In the virtual world, the key is called a "private key" and the lock is a "public certificate". Your server has a private key that belongs only to it, and your client has a public certificate that can only be validated by someone who holds the corresponding private key.
You may be wondering: how can the server expect a client that it has never met before to magically have a certificate (the lock) that it can pair with its private key (the key).
The answer to this question depends on the fact that both parts are created at the same time in a single operation. This called Generating a Keypair. There are two ways of going about this:
You can use a private key that was issued by a trusted third party company, including LetsEncrypt as well as a number of private/commercial providers.
The advantage to this approach is that clients (such as web browsers, as well as tools like curl/wget and the HAPI FHIR client) come with a set of built-in certificates for these well known providers.
For simplicity here, we are skipping over an important concept called "certificate chaining", but the important part is this: If you use a well-known provider to generate your keypair, you can be assured that your clients probably come with an appropriate certificate pre-installed.
You can generate your own "keypair", which is a private key and a corresponding certificate. This is commonly known as "self-signing".
This approach is completely secure and can be done with freely available tools, but it has one disadvantage: The certificate that the client needs in order to verify the server's key will not be pre-installed on your client.
This means that you will need to explicitly supply the certificate to any clients via configuration.
Private keys and certificates are stored for use by Smile CDR in special files called KeyStore files. There are four formats of KeyStore that are supported by Smile CDR:
Java KeyStore File (JKS): The Java KeyStore file uses the extension .jks
and was the only acceptable way to store keys for Java applications for a long time. These files are generally managed using the Java Keytool command line utility.
PKCS#12 File (P12): The PKCS#12 file uses the extension .p12
. PKCS#12 is a standard for container files that is supported by a wide variety of tools, including Smile CDR.
Note that while JKS files are still supported and are perfectly acceptable to use, it is generally recommended to use PKCS#12 files instead for new applications, since PKCS#12 is a standard
PEM File: The PEM (Privacy Enhanced Mail) file uses the extension .pem
. A single key and/or certificate can also be transported in a PEM file. PEM files do not have an associated password, unlike JKS and P12 files.
JSON Web KeyStore (JWKS) File: The JWKS file uses the extension .jwks
. JWKS files use a text-based JSON encoding to represent keys and certificates. They are used for keys and certificates associated with OpenID Connect clients and servers.
Within a KeyStore file, each key or certificate will be given an ID or Alias, which is just a string used to identify an individual key within a KeyStore. In this context these two words are synonymous: JKS files generally refer to the key alias where other files use the word ID.
TLS Client Authentication, sometimes called Two-Way TLS or TLS Mutual Authentication builds on the approach above by adding an additional Keypair.
With standard TLS, the client is able to trust that the server is who it claims to be. This is good for the client, but does not provide any assurance to the server that the client is who they claim to be.
When using TLS Client Authentication, the server has a private key and the client has a corresponding certificate (as explained above), but the client also has a (different) private key and the server has a certificate corresponding to this key.
In this way, the client can verify the identity of the server (by verifying the server key) and the server can verify the identity of the client (by verifying the client key).
In Smile CDR, several configuration items can be used to provide keys and certificates to various modules:
KeyStore File: The KeyStore File holds private keys. For HTTP Servers, this allows the server to support standard TLS. For HTTP Clients, ths allows the client to connect to a server requiring TLS Client Authentication.
TrustStore File: The TrustStore file holds public certificates. For HTTP Servers, this file contains certificates for any clients that will connect to the server using TLS Client Authentication. For HTTP Clients, this file contains certificates for any self-signed keys used by servers.
Use the following command to create a KeyStore containing a key with the alias server
:
keytool -genkey -keyalg RSA -alias server -keystore keystore.p12 -storepass changeit -storetype pkcs12 -validity 360 -keysize 4096
This will produce the following output. User input is shown in red. Note that the "First and Last Name" question corresponds to the CN (common name) attribute in the key. This should generally be the domain name for the server where this key will be used in the case of HTTPS servers:
To export the public certificate from the newly generated keypair file:
openssl pkcs12 -in keystore.p12 -clcerts -nokeys -out truststore.pem
This will produce the following output. User input is shown in red.
The following steps show how to create a KeyStore using a private key and chain obtained from LetsEncrypt. Note that you will need to replace "[hostname]" with your actual LetsEncrypt hostname in several places.
# Export the key into a PKCS12 file
openssl pkcs12 -export -in /etc/letsencrypt/live/[hostname]/cert.pem -inkey /etc/letsencrypt/live/[hostname]/privkey.pem -out keystore.p12 -name [hostname] -CAfile /etc/letsencrypt/live/[hostname]/chain.pem -caname root -password pass:changeit
TLS-enabled servers may optionally be configured with a whitelist or a blacklist for both ciphers and protocol.
This can be used to restrict specific ciphers, or ban old versions of TLS/SSL.
To see a list of available ciphers, consult your JDK documentation. The following links may be useful:
If possible, consider setting a protocol whitelist of TLSv1.3
, as this ensures that all clients are required to connect using the most modern revision of the TLS specification. Note that this may prevent older clients from being able to connect. See CanIUse TLS 1.3 for a rundown of browser support.
The TLS 1.0 protocol is generally not recommended for use as it has known vulnerabilities that have been addressed in newer versions of the TLS specification. However, the following configuration is known to work in forcing the use of TLS 1.0 (e.g. to interact with a JDK 6 application that can not be upgraded).
TLSv1
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
If the client does not already have a certificate it can use to identify itself, the following steps may be followed by the client.
Create the server key. Note that this file should be generated by the client, and kept secure.
openssl genrsa -out ca.key 4096
This command will produce output similar to the following:
Next, generate a certificate. During this step you will be asked for information about your organization.
openssl req -new -x509 -days 365 -key ca.key -out ca.crt
This command will prompt for some details about the organization making the request.
This step generates a certificate that is specific to the client application making requests. First, we generate a key.
openssl genrsa -out client.key 4096
Next, generate a signing request.
openssl req -new -key client.key -out client.csr
This will produce output similar to the following. Note that you must not use the exact same answers to all questions that you provided when creating the Certificate Authority Root above.
Next, generate a signed certificate.
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
This will produce output similar to the following:
Optionally, you may now verify that the certificate works:
openssl verify -verbose -CAfile ca.crt client.crt
Finally, generate a PKCS#12 KeyStore that can be imported into Smile CDR. Note that the KeyStore password of changeit
is used below. You may choose to use a different password. When this command has completed, you will have a file called truststore.p12
.
keytool -import -alias acme -file client.crt -keystore truststore.p12 -storepass changeit -storetype pkcs12
This will produce output similar to the following.
The following command creates a private PKCS#12 KeyStore containing both the key and certificate. This file will be needed by your client in order to invoke the service.
openssl pkcs12 -export -clcerts -in client.crt -inkey client.key -out client.p12
This command will produce output similar to the following:
In your endpoint module, set the following settings:
Client Authentication | yes |
TLS TrustStore Filename | file:///path/to/cacerts.p12 |
TLS TrustStore Password | Password from truststore above |
The following example shows a cURL command being used to present the generated client key to the server.
curl --cert client/client.crt --key client/client.key https://localhost:8000
TLS-enabled FHIR endpoints allows for supplying a custom Java KeyStore through theServer Interceptor Framework
.
To provide a custom Java KeyStore, follow these steps:
Enable TLS support with endpoint propertytls.enabled
Provide a KeyStore password with the FHIR Endpoint configuration propertytls.keystore.keypass
Provide a business specific interceptor implementation annotated with@CdrHook
on the pointcut CdrPointcut.SERVER_CONFIGURATION_KEYSTORE
Register the interceptor
class with Smile CDR
Example:
@CdrHook(CdrPointcut.SERVER_CONFIGURATION_KEYSTORE)
public KeyStore provideKeystore(String theKeystorePassword) throws KeyStoreException, CertificateException, IOException, NoSuchAlgorithmException {
InputStream keystoreInputStream = getInputStream();
String keyStoreType = KeyStore.getDefaultType();
KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(keystoreInputStream, theKeystorePassword.toCharArray());
return keyStore;
}
See the cdr-interceptor-starterproject
for a working example.
There are some Smile Util commands that make HTTP requests to FHIR REST endpoints.
If the endpoints are secured (i.e. use HTTPS), then a TLS JSON
authentication file must be provided in order authenticate the client and / or server.
The JSON
authentication file should have the following structure:
{
"keyStore": {
"filePath" : "file:///path-to-keystore-file.p12",
"storePass": "somePassword",
"keyPass": "somePassword",
"alias": "someAlias"
},
"trustStore": {
"filePath" : "file:///path-to-truststore-file.p12",
"storePass": "somePassword",
"alias": "someAlias"
}
}
keyStore
– Information about the client's KeyStore that will be used by the server to authenticate the HTTPS request.
keyStore.filePath
– File path to the client's KeyStore. This can be in the format classpath:path/to/file.p12
or file:///path/to/file.p12
. Valid file extensions are .jks
(Java Keystore) or .p12
(PKCS#12 store).
keyStore.storePass
– Password for the KeyStore. If the value supplied is "PROMPT", Smile Util will prompt the user to enter a password interactively.
keyStore.keyPass
– Password for the key inside the KeyStore. If the value supplied is "PROMPT", Smile Util will prompt the user to enter a password interactively.
keyStore.alias
– Alias of the key inside the KeyStore.
trustStore
– Information about the client's TrustStore that will be used by the client to authenticate the HTTPS response.
trustStore.filePath
– File path to the client's TrustStore. This can be in the format classpath:path/to/file.p12
or file:///path/to/file.p12
. Valid file extensions are .jks
(Java Keystore) or .p12
(PKCS#12 store).
trustStore.storePass
– Password for the TrustStore. If the value supplied is "PROMPT", Smile Util will prompt the user to enter a password interactively.
trustStore.alias
– Alias of the certificate inside the TrustStore.