Multiple certificate support
Overview
One CONNECT instance can facilitate secure message exchange for multiple exchanges with CONNECT multiple certificate support.The document will layout three different approaches: Server Name Indication (SNI), Multiple Ports and Hybrid (combine SNI and multiple port). By utilizing SNI, CONNECT can be configured to support multiple certificate with the same IP address and same port number. Alternatively, CONNECT can be configured to use multiple ports for each exchange where each port presents different certificate. Or we can combine both approaches (SNI and multiple ports) to allow legacy system to participate in multiple exchanges.
This wiki page includes details on how multi-exchange messaging can be implemented on one instance of CONNECT with examples of how to set up a multiple certificate support test scenario. These examples can be customized for live production implementations.
Proof of concept
Multi-exchange messaging and multiple certificate support have been tested on WildFly 8.2.1 and Wildfly 15.0.0 (SNI approach) only. Future testing will include additional application servers and CA-issued SSL certificates.
SNI Approach
What is SNI and how does CONNECT use SNI?
SNI is an extension added to Transport Layer Security (TLS), to allow a server to present multiple certificates on the same IP address and port number. SNI implementation requires the client to server_name as part of TLS negotiation. When the server receives the server_name extension during TLS/SSL handshake, it will select the look up its own configuration and present the correct certification. However, if server doesn’t receive the server_name extension, the server can be configure to select default certification or reject connection. With this approach, CONNECT can deploy with one instance and having multiple certification for each exchange
For open-source application servers, Wildfly 15.0.0 is the only server that support server side SNI on its HTTPS listeners. Below is how CONNECT configures SNI on wildfly 15.0.0
<subsystem xmlns="urn:wildfly:elytron:5.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto"> ... <tls> <key-stores> <key-store name="ehealth"> <credential-reference clear-text="changeit"/> <implementation type="JKS"/> <file path="/modules/system/layers/base/org/connectopensource/configuration/main/gateway.jks" relative-to="jboss.home.dir"/> </key-store> <key-store name="carequality"> <credential-reference clear-text="changeit"/> <implementation type="JKS"/> <file path="/modules/system/layers/base/org/connectopensource/configuration/main/gateway.jks" relative-to="jboss.home.dir"/> </key-store> <key-store name="carequalityTS"> <credential-reference clear-text="changeit"/> <implementation type="JKS"/> <file path="/modules/system/layers/base/org/connectopensource/configuration/main/cacerts.jks" relative-to="jboss.home.dir"/> </key-store> <key-store name="ehealthTS"> <credential-reference clear-text="changeit"/> <implementation type="JKS"/> <file path="/modules/system/layers/base/org/connectopensource/configuration/main/cacerts.jks" relative-to="jboss.home.dir"/> </key-store> </key-stores> <key-managers> <key-manager name="ehealthKM" key-store="ehealth" alias-filter="gateway"> <credential-reference clear-text="changeit"/> </key-manager> <key-manager name="carequalityKM" key-store="carequality" alias-filter="carequality"> <credential-reference clear-text="changeit"/> </key-manager> </key-managers> <trust-managers> <trust-manager name="ehealthTM" key-store="ehealthTS"/> <trust-manager name="carequalityTM" key-store="carequalityTS"/> </trust-managers> <server-ssl-contexts> <server-ssl-context name="ehealthSSC" need-client-auth="true" key-manager="ehealthKM" trust-manager="ehealthTM"/> <server-ssl-context name="carequalitySSC" need-client-auth="true" key-manager="carequalityKM" trust-manager="carequalityTM"/> </server-ssl-contexts> <server-ssl-sni-contexts> <server-ssl-sni-context name="connectSNI" default-ssl-context="ehealthSSC"> <sni-mapping host="connect.carequality.com" ssl-context="carequalitySSC"/> <sni-mapping host="connect.ehealth.com" ssl-context="ehealthSSC"/> </server-ssl-sni-context> </server-ssl-sni-contexts> </tls> </subsystem > .... <subsystem xmlns="urn:jboss:domain:undertow:8.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other"> .... <https-listener name="https" socket-binding="connect" ssl-context="connectSNI" enable-http2="true"/> </subsystem>
Assumption: gateway.jks is our keystore where it has two private key which define under alias "gateway" and "carequality". We import all CAs (root/intermediate) certs into cacerts.jks.
Explanation: In this configuration, we define ssl context (connectSNI) in https-listener. This connectSNI SSL Context will determine which SSL context will use based on the server_name during the TLS negotiation. In this example, if the server_name is connect.carequality.com(<sni-mapping host="connect.carequality.com" ssl-context="carequalitySSC"/>), the application server will select carequalitySSC which then uses key-manager="carequalityKM" trust-manager="carequalityTM" to present the carequality cert to the requestor. If the server_name is connect.ehealth.com(<sni-mapping host="connect.ehealth.com" ssl-context="ehealthSSC"/>), the server will select ehealthSSC or use ehealth cert to present. However, if nothing match, it will default to ehealthSSC (<server-ssl-sni-context name="connectSNI" default-ssl-context="ehealthSSC">)
We will use openssl tool as a client to demonstrate how SNI work behind the scene. Below is server log with SSL debug enabled when clients send server_name during TLS negotiation(openssl s_client -showcerts -connect 192.168.3.10:8181 -servername ehealth)
2018-12-31 18:10:18,802 INFO [stdout] (default task-1) *** ClientHello, TLSv1.2 2018-12-31 18:10:18,802 INFO [stdout] (default task-1) RandomCookie: GMT: -843595063 bytes = { 193, 82, 52, 92, 135, 7, 153, 132, 72, 10, 42, 116, 149, 45, 194, 110, 95, 101, 133, 231, 70, 213, 27, 225, 193, 209, 184, 129 } 2018-12-31 18:10:18,802 INFO [stdout] (default task-1) Session ID: {} 2018-12-31 18:10:18,802 INFO [stdout] (default task-1) Cipher Suites: [TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, TLS_DH_DSS_WITH_AES_256_GCM_SHA384, TLS_DHE_DSS_WITH_AES_256_GCM_SHA384, TLS_DH_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_GCM_SHA384, TLS_DHE_RSA_WITH_AES_256_CBC_SHA256, TLS_DHE_DSS_WITH_AES_256_CBC_SHA256, TLS_DH_RSA_WITH_AES_256_CBC_SHA256, TLS_DH_DSS_WITH_AES_256_CBC_SHA256, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, TLS_DH_RSA_WITH_AES_256_CBC_SHA, TLS_DH_DSS_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA, TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA, TLS_DH_RSA_WITH_CAMELLIA_256_CBC_SHA, TLS_DH_DSS_WITH_CAMELLIA_256_CBC_SHA, TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384, TLS_ECDH_RSA_WITH_AES_256_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_256_GCM_SHA384, TLS_RSA_WITH_AES_256_CBC_SHA256, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_CAMELLIA_256_CBC_SHA, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, TLS_DH_DSS_WITH_AES_128_GCM_SHA256, TLS_DHE_DSS_WITH_AES_128_GCM_SHA256, TLS_DH_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_GCM_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA256, TLS_DHE_DSS_WITH_AES_128_CBC_SHA256, TLS_DH_RSA_WITH_AES_128_CBC_SHA256, TLS_DH_DSS_WITH_AES_128_CBC_SHA256, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DH_RSA_WITH_AES_128_CBC_SHA, TLS_DH_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_SEED_CBC_SHA, TLS_DHE_DSS_WITH_SEED_CBC_SHA, TLS_DH_RSA_WITH_SEED_CBC_SHA, TLS_DH_DSS_WITH_SEED_CBC_SHA, TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA, TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA, TLS_DH_RSA_WITH_CAMELLIA_128_CBC_SHA, TLS_DH_DSS_WITH_CAMELLIA_128_CBC_SHA, TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256, TLS_ECDH_RSA_WITH_AES_128_CBC_SHA, TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_SEED_CBC_SHA, TLS_RSA_WITH_CAMELLIA_128_CBC_SHA, SSL_RSA_WITH_IDEA_CBC_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, TLS_ECDH_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_MD5, TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA, TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_DH_RSA_WITH_DES_CBC_SHA, SSL_DH_DSS_WITH_DES_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV] 2018-12-31 18:10:18,802 INFO [stdout] (default task-1) Compression Methods: { 1, 0 } 2018-12-31 18:10:18,802 INFO [stdout] (default task-1) Extension server_name, server_name: [type=host_name (0), value=connect.carequality.com] 2018-12-31 18:10:18,815 INFO [stdout] (default task-1) *** Certificate chain 2018-12-31 18:10:18,816 INFO [stdout] (default task-1) chain [0] = [ 2018-12-31 18:10:18,816 INFO [stdout] (default task-1) [ 2018-12-31 18:10:18,816 INFO [stdout] (default task-1) Version: V3 2018-12-31 18:10:18,816 INFO [stdout] (default task-1) Subject: CN=192.168.3.10, OU=nhin-carequality, O=nhin, C=US …..
2018-12-31 18:44:36,444 INFO [stdout] (default task-1) *** ClientHello, TLSv1.2 2018-12-31 18:44:36,445 INFO [stdout] (default task-1) RandomCookie: GMT: 1702591203 bytes = { 37, 214, 124, 138, 45, 58, 135, 149, 109, 172, 102, 228, 36, 223, 22, 218, 146, 196, 165, 107, 132, 171, 117, 27, 84, 129, 84, 83 } 2018-12-31 18:44:36,445 INFO [stdout] (default task-1) Extension server_name, server_name: [type=host_name (0), value=connect.ehealth.com] 2018-12-31 18:44:36,456 INFO [stdout] (default task-1) *** Certificate chain 2018-12-31 18:44:36,456 INFO [stdout] (default task-1) chain [0] = [ 2018-12-31 18:44:36,456 INFO [stdout] (default task-1) [ 2018-12-31 18:44:36,456 INFO [stdout] (default task-1) Version: V3 2018-12-31 18:44:36,456 INFO [stdout] (default task-1) Subject: CN=192.168.3.10, OU=nhin-ehealth, O=nhin, C=US
On the CXF client side, we may only need to add the following line:Â
serviceEndpoint.getHTTPClientPolicy().setHost(servername);
http://cxf.apache.org/docs/client-http-transport-including-ssl-support.html
How does the gateway choose which certificate to send in the multiple exchange scenarios?
When a request is initiated, CONNECT will use the certificate alias identified in the Exchange Manager to determine which certificate from the keystore to sign the assertion and present at the TLS layer. In additional to that, CONNECT will send server_name during TLS protocol (based on SNI implementation). If the responding gateway doesn’t support SNI or not recognized, the responding gateway should either continue the handshake without an error or send the fatal one.Â
Below is how CONNECT implement:
In exchangeInfo.xml, set the certificate alias for each exchange:
<exchange type="uddi"> <name>Exchange 1</name> <certificateAlias>gateway</certificateAlias>
This allows CONNECT know which certificate to present during TLS client handshakes and to use for signing secure messages, based on the targeted exchange. The value must exactly match the alias of the designated certificate.
In the entity request, make sure the targeted exchange (exchangeName) is specified:
<urn:NhinTargetCommunities> <urn1:nhinTargetCommunity> <urn1:homeCommunity> <urn1:description>${#Project#RemoteHCDescription}</urn1:description> <urn1:homeCommunityId>${#Project#RemoteHCID}</urn1:homeCommunityId> <urn1:name>${#Project#RemoteHCDescription}</urn1:name> </urn1:homeCommunity> </urn1:nhinTargetCommunity> <urn:exchangeName>exchange1</urn:exchangeName> </urn:NhinTargetCommunities>
Parsing SNI value for certificate presentation and signature validation
CONNECT As Responding Gateway
If the client is able to send host name and correct certificates, no additional configurations are needed apart from what is listed above. This is tested using openssl and Wildfly 15 server.
We might encounter some message level processing issues when the Nationwide Health Information Network (NwHIN) message is send out however this is unknown at this time. Â
Wildfly15 SSL Handshake log
Multiple Ports Approach
In this implementation, CONNECT will use multiple ports for multiple exchanges where each port presents different certificate. Each exchange is bound to a specific port that is set up via application server configurations. Each port configuration will identify a certificate alias and each exchange in the CONNECT Exchange Manager also identifies a certificate alias that ultimately binds the exchange to a specific port based on the matching certificate aliases. When a request is initiated, CONNECT will use the certificate alias identified in the Exchange Manager to determine which certificate from the trust store to present at the TLS layer and to use to sign outgoing messages. As a responding gateway, the server must receive requests on the same Secure Sockets Layer (SSL) ports configured for each specific exchange. For example, if port 8181 has been configured for Exchange A, all incoming requests directed to Exchange A must also be addressed to port 8181
Server configurations
CONNECT requires the use of multiple SSL ports to support secure messaging with multiple exchanges (certificates).
The following is an example for setting up a server with two SSL ports (8181 and 9191):
- Port 8181 for inbound/outbound SSL requests using certificate 1
- Port 9191 for inbound/outbound SSL requests using certificate 2
Add the following to standalone.xml for WildFly (or the appropriate server configuration file for other app servers):
<security-realm name="ApplicationRealm2"> <server-identities> <ssl> <keystore path="modules/system/layers/base/org/connectopensource/configuration/main/gateway.jks" relative-to="jboss.home.dir" keystore-password="changeit" alias="gateway_b"/> </ssl> </server-identities> <authentication> <truststore path="modules/system/layers/base/org/connectopensource/configuration/main/cacerts.jks" relative-to="jboss.home.dir" keystore-password="changeit"/> <local default-user="$local" allowed-users="*" skip-group-loading="true"/> <properties path="application-users.properties" relative-to="jboss.server.config.dir"/> </authentication> <authorization> <properties path="application-roles.properties" relative-to="jboss.server.config.dir"/> </authorization> </security-realm> --- <https-listener name="https2" socket-binding="connect2" security-realm="ApplicationRealm2" verify-client="REQUIRED"/> --- <socket-binding name="connect2" port="9191"/> ---
Execute the following to test the new port set up:Â Â $ openssl s_client -showcerts -connect localhost:<portnumber>
CONNECT configurations
In exchangeInfo.xml, set the certificate alias for each exchange:
<exchange type="uddi"> <name>Exchange 1</name> <certificateAlias>gateway</certificateAlias>
This lets CONNECT know which certificate to present during TLS client handshakes and to use for signing secure messages, based on the targeted exchange. The value must exactly match the alias of the designated certificate.
In the entity request, make sure the targeted exchange (exchangeName) is specified:
<urn:NhinTargetCommunities> <urn1:nhinTargetCommunity> <urn1:homeCommunity> <urn1:description>${#Project#RemoteHCDescription}</urn1:description> <urn1:homeCommunityId>${#Project#RemoteHCID}</urn1:homeCommunityId> <urn1:name>${#Project#RemoteHCDescription}</urn1:name> </urn1:homeCommunity> </urn1:nhinTargetCommunity> <urn:exchangeName>exchange1</urn:exchangeName> </urn:NhinTargetCommunities>
Hybrid Approach (SNI + Multiple port)Â
Since some application servers haven't support SNI yet and exchange platform may requires participants only expose port 443. As a result, this approach will add apache server in front of the legacy system. The apache server will support SNI and expose to the public while the legacy system will place behind the firewall. However, the legacy system still need to configure to have multiple ports for each exchange. When the apache server detects hostname during TLS negotiation, it forward the request to whatever port the legacy system supports. Below is what the apache configuration looks like
<VirtualHost 192.168.1.1:443> ServerName connect.carequality.com SSLEngine on SSLCertificateFile /path/to/your_domain_name.crt SSLCertificateKeyFile /path/to/your_private.key SSLCertificateChainFile /path/to/CA.crt ProxyPreserveHost on ProxyPass / https://<legacy_IP>:8181 </VirtualHost> <VirtualHost 192.168.1.1:443> ServerName connect.ehealth.com SSLEngine on SSLCertificateFile /path/to/your_domain_name.crt SSLCertificateKeyFile /path/to/your_private.key SSLCertificateChainFile /path/to/CA.crt ProxyPass / https://<legacy_IP>:9191 </VirtualHost>
Explanation: When client passes server_name (connect.ehealth.com) to 192:168.1.1:443, the apache server will route to legacy system on port 9191 while the server_name (connect.carequality.com) will route to the port 8181. In this approach, it will allow CONNECT to use multiple certs on multiple exchange with the same IP address and same port number (443)
List of servers support SNI
Server | SNI | Link |
---|---|---|
Wildfly 15 | Yes | https://wildfly.org/news/2018/12/13/WildFly15-Final-Released/ |
Weblogic (12.2.1.3) | No | https://docs.oracle.com/middleware/12213/wls/SECMG/standards.htm#SECMG743 |
Websphere 8.5 with IBM Java 7.0 | Yes | https://www.ibm.com/support/knowledgecenter/SSYKE2_7.0.0/com.ibm.java.security.component.70.doc/security-component/whats_new/security_changes_70/security_whatsnew.html |