Table of contents |
Quick index |
|
---|---|---|
|
|
Overall we want to instrument Tomcat to operate over TLS (HTTPS) and create a certificate that we can pass out to clients of Tomcat's services. This will enable clients to recognize that the target of their requests is in fact the service they want (and not another). It does nothing to authenicate clients as worthy of interacting with services running in Tomcat. (That is possible to do, but does not interest us here.) In this exposé, we...
https://tirion:8443/mdht-restlet
I have chosen to work out of two non-canonical subdirectories.
I do all the work under Tomcat, /opt/tomcat/certificates, except at the very end when I copy client-truststore.jks to NiFi.
This is arbitrary and there is no reason you are obliged to follow suit. You could instead make use of those frameworks' conf subdirectories.
Everywhere in here we use Java JKS' default password, "changeit". Anything else is left as an exercise for the student. (Always wanted to say that.)
Service mdht-restlet is an application based on the Model-driven Health Tools which parse or produce HL7v3 Clinical Document Architecture files. Just think of it as any Tomcat-deployed microservice. Some microservice targetable from Apache NiFi's InvokeHTTP is useful, but really only ultimately to verify the TLS functionality in this exposé.
I have created an entry in /etc/hosts (127.0.0.1 tirion) for use in simulating DNS. Using localhost might work, but it's considered bad practice to create a SAN in a certificate for localhost for the obvious security danger it presents.
Now, let's get started...
Not only will we generate a self-signed certificate to be used in a private arrangement between Tomcat and HTTPS clients, we're going to add "tirion" as if a DNS entry to a short list of subject-alternative names (a list of exactly one SAN).
$ keytool -genkeypair -keyalg RSA -keysize 2048 -validity 365 -dname "CN=tomcat" -ext san=dns:tirion \ -alias tomcat -keystore tomcat.jks -storepass changeit -keypass changeit $ ll total 12 drwxr-xr-x 2 russ tomcat 4096 Aug 11 18:20 . drwxr-xr-x 11 root tomcat 4096 Aug 2 15:44 .. -rw-rw-r-- 1 russ russ 2553 Aug 11 18:46 tomcat.jks
This is important to prove that our SAN was injected. Inded it was, as highlighted.
$ keytool -list -v -keystore tomcat.jks -storepass changeit
Keystore type: PKCS12
Keystore provider: SUN
Your keystore contains 1 entry
Alias name: tomcat
Creation date: Aug 11, 2022
Entry type: PrivateKeyEntry
Certificate chain length: 1
Certificate[1]:
Owner: CN=tomcat
Issuer: CN=tomcat
Serial number: 3d907564
Valid from: Thu Aug 11 18:46:24 MDT 2022 until: Fri Aug 11 18:46:24 MDT 2023
Certificate fingerprints:
SHA1: 9D:84:10:5A:CF:3D:A9:9D:62:33:92:D1:46:AD:8C:5E:96:CE:F6:28
SHA256: 57:2D:8C:AA:1D:3C:8D:19:C3:93:6B:7E:D9:DE:75:63:CA:F3:33:2B:8A:12:8E:D4:52:CE:A5:25:23:32:3E:8A
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3
Extensions:
#1: ObjectId: 2.5.29.17 Criticality=false
SubjectAlternativeName [
DNSName: tirion
]
#2: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 19 7B 9E 64 67 C2 E9 33 C4 8B 44 71 7A 1D B2 8E ...dg..3..Dqz...
0010: 22 A7 6C E4 ".l.
]
]
*******************************************
*******************************************
This configuration is performed in ${CATALINA_BASE}/conf/server.xml. Look for "Connector" to find the default connection for port 8080 and add this secure definition after it.
What's very different from the <Connector definition for port 8080, Tomcat's default, is highlighted. "certificate/tomcat.jks" is a path relative to ${CATALINA_BASE}.
<Connector port="8443" protocol="HTTP/1.1" connectionTimeout="20000" scheme="https" secure="true" SSLEnabled="true"> <SSLHostConfig> <Certificate certificateKeyAlias="tomcat" certificateKeystoreFile="certificates/tomcat.jks" certificateKeystorePassword="changeit" /> </SSLHostConfig> </Connector>
Because our certificate is in place and a reference to it is configured in server.xml, we can bounce Tomcat:
# systemctl restart tomcat
...and then, in a browser, hit Tomcat using https://localhost:8443/. We'll see the browser complain about an unsecure certificate—that's because ours is self-signed and therefore unknown—and we'll be able to dig through that telling the browser it doesn't matter.
At that point, if we have the Tomcat manager activated, that's what will show up.
Another way to test this is to use curl. We have to use the --insecure option to reassure curl that we know about our unsecure certificate just as we told the browser to ignore it.
$ curl --insecure --request GET https://localhost:8443/
This command connects to the URL (Tomcat in this case) and requests the server's certificates. We put a certificate there; that's what we'll get back.
$ openssl s_client -connect tirion:8443 -showcerts > client.crt Can't use SSL_get_servername depth=0 CN = tomcat verify error:num=18:self signed certificate verify return:1 depth=0 CN = tomcat verify return:1 $ ll client.crt -rw-rw-r-- 1 russ russ 2501 Aug 11 18:49 client.crt $ cat client.crt ┌───────────────────────────────────────────────────────┐ CONNECTED(00000003) │ (To be honest: if there's anything that perplexes me │ --- │ at all, it's that I don't see the SAN in here while │ Certificate chain │ it shows up as if by magic in the next step.) │ 0 s:CN = tomcat └───────────────────────────────────────────────────────┘ i:CN = tomcat -----BEGIN CERTIFICATE----- MIIC1DCCAbygAwIBAgIEPZB1ZDANBgkqhkiG9w0BAQsFADARMQ8wDQYDVQQDEwZ0 b21jYXQwHhcNMjIwODEyMDA0NjI0WhcNMjMwODEyMDA0NjI0WjARMQ8wDQYDVQQD EwZ0b21jYXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCHvoADJD2Y oQYqCVa7CIVmFFjCbJd/U1s65QSEoTbQ7zc0VaIJ1PrjzTMywNWRh2wHenC1PYV4 JLjXeidOlXH7+Ibpko4KU9TA7RhsjAJpOlZZIXzao5jzyzOPfMnlBlsiM7o0jjB9 d8Lj2KEAfRzgzX+6P7jE9fbw62A3Z2mLQ8NDixv1SoumGySQwJLWJxBoYnRMfLdj sFtuXJVXHiSoslbt6vN3KVYgYLAKX/viDdYItypXGJObONsd+xiajQEMhT1U9qC3 A0Cx9VUlstANn0Iemr7VdgLYegnqpgDENDvHZUqtUiQdItTWCgKFRYGya/+ci9XB 4UqmtkltFEjBAgMBAAGjNDAyMB0GA1UdDgQWBBQZe55kZ8LpM8SLRHF6HbKOIqds 5DARBgNVHREECjAIggZ0aXJpb24wDQYJKoZIhvcNAQELBQADggEBAHmGYGOK7Elx UADCVWpjAOC1U07VCicAUqa4HOyfSp2ZIm3uYtsskXTfv0nRsjuYYDGngGi9lZYn dBk10WaQCuPNBklc4+GKlL087Cg9rPmu+4Vyr4F17SbbZwv3/EeOqShsrhQSQ/38 OrUG5cuDrzOYhoS7HYALDqAkuCiiF2/aa63vGsu9VDCzRi0SpHfmD//ynXSM2bM/ q8YOyxf6R1UYT/5x0lxEGuaiRPXR6iLBqNKDfJKzl38qLNs9/BoN4jnySKgTJB9I bww9Pc5dnXOecsm6r192Y3VBKHSn7dF+5/Hqf9kPYOj6g10/F/wur8AvVah62X5o /ZQSgi2Z9HI= -----END CERTIFICATE----- --- Server certificate subject=CN = tomcat issuer=CN = tomcat --- No client certificate CA names sent Peer signing digest: SHA256 Peer signature type: RSA-PSS Server Temp Key: X25519, 253 bits --- SSL handshake has read 1260 bytes and written 363 bytes Verification error: self signed certificate --- New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Server public key is 2048 bit Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 18 (self signed certificate) --- --- Post-Handshake New Session Ticket arrived: SSL-Session: Protocol : TLSv1.3 Cipher : TLS_AES_256_GCM_SHA384 Session-ID: 343BBD170C7EE800DC57EE54995C1CB49C1359CF450EA723C9E0FA2B46683BB0 Session-ID-ctx: Resumption PSK: 31C6D718594FE331BEB8951DB45A6BC310A24C4B7229066216BF84E5261F12C8D30156DD1D4259D9357D8FA6B4A1EF94 PSK identity: None PSK identity hint: None SRP username: None TLS session ticket lifetime hint: 86400 (seconds) TLS session ticket: 0000 - 3f 4b d5 d8 99 4f 16 21-96 6a 7a 07 08 28 74 96 ?K...O.!.jz..(t. 0010 - 3e 34 2a 16 ad c6 6a bd-eb dc 50 3c 27 18 e8 84 >4*...j...P>'... Start Time: 1660265338 Timeout : 7200 (sec) Verify return code: 18 (self signed certificate) Extended master secret: no Max Early Data: 0 --- read R BLOCK closed
...for use by the client. This keystore will be used to configure clients that exchange requests with our Tomcat:
$ keytool -importcert -file client.crt -alias tomcat -keystore client-truststore.jks -keypass changeit -storepass changeit -noprompt Certificate was added to keystore $ ll total 20 drwxr-xr-x 2 russ tomcat 4096 Aug 11 19:31 . drwxr-xr-x 11 root tomcat 4096 Aug 2 15:44 .. -rw-rw-r-- 1 russ russ 2501 Aug 11 18:49 client.crt -rw-rw-r-- 1 russ russ 1095 Aug 11 19:31 client-truststore.jks -rw-rw-r-- 1 russ russ 2553 Aug 11 18:46 tomcat.jks
There are a number of reasons to do this. Mostly, we want to observe that there is the subject-alternative name, a DNS name, that we added. If not, client use will error out. What else is interesting might be the SHA256: it must match what we see in Tomcat when we get the certificate and look at it. Again, if it doesn't match, the client will error out.
$ keytool -list -v -keystore client-truststore.jks -storepass changeit Keystore type: PKCS12 Keystore provider: SUN Your keystore contains 1 entry Alias name: tomcat Creation date: Aug 11, 2022 Entry type: trustedCertEntry Owner: CN=tomcat Issuer: CN=tomcat Serial number: 3d907564 Valid from: Thu Aug 11 18:46:24 MDT 2022 until: Fri Aug 11 18:46:24 MDT 2023 Certificate fingerprints: SHA1: 9D:84:10:5A:CF:3D:A9:9D:62:33:92:D1:46:AD:8C:5E:96:CE:F6:28 SHA256: 57:2D:8C:AA:1D:3C:8D:19:C3:93:6B:7E:D9:DE:75:63:CA:F3:33:2B:8A:12:8E:D4:52:CE:A5:25:23:32:3E:8A Signature algorithm name: SHA256withRSA Subject Public Key Algorithm: 2048-bit RSA key Version: 3 Extensions: #1: ObjectId: 2.5.29.17 Criticality=false SubjectAlternativeName [ DNSName: tirion ] #2: ObjectId: 2.5.29.14 Criticality=false SubjectKeyIdentifier [ KeyIdentifier [ 0000: 19 7B 9E 64 67 C2 E9 33 C4 8B 44 71 7A 1D B2 8E ...dg..3..Dqz... 0010: 22 A7 6C E4 ".l. ] ] ******************************************* *******************************************
At this point, the file, client-truststore.jks is ready to be deployed to a client. We have already discussed two; here are the details.
$ cp client-truststore /opt/nifi-1.17.0/certificates
-Djavax.net.trustStore=path to client-truststore.jks
-Djavax.net.trustStorePassword=changeit
-Djavax.net.trustStoreType=JKS (from Java 9 onwards, default is PKCS12, so you must do this)
System.setProperty( "javax.net.ssl.trustStore", path to client-truststore.jks ); System.setProperty( "javax.net.ssl.trustStorePassword", "changeit" ); System.setProperty( "javax.net.ssl.trustStoreType", "JKS" );
$ cp client-truststore.jks ${JAVA_HOME}/lib/security/cacerts
—for quick copying and pasting.
keytool -genkeypair -keyalg RSA -keysize 2048 -validity 365 -dname "CN=tomcat" -ext san=dns:tirion -alias tomcat -keystore tomcat.jks -storepass changeit -keypass changeit keytool -list -v -keystore tomcat.jks -storepass changeit vim /opt/tomcat/conf/server.xml openssl s_client -connect tirion:8443 -showcerts > client.crt keytool -importcert -file client.crt -alias tomcat -keystore client-truststore.jks -keypass changeit -storepass changeit -noprompt keytool -list -v -keystore client-truststore.jks -storepass changeit |
Generate Tomcat a new keystore with certificate and key inside. Verify Tomcat's new keystore. Add Connector for 8443. Get Tomcat's certificate "live." Import that certificate into a keystore (client's trust store). Verify our client's trust store. |