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)

openssl s_client -showcerts -connect 192.168.3.10:8181 -servername connect.carequality.com
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
…..


openssl s_client -showcerts -connect 192.168.3.10:8181 -servername connect.ehealth.com
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.  

openssl s_client -connect localhost:8181 -cert gateway-client-cq.crt -key gateway.key -showcerts -servername carequality -CAfile carequality-chain.crt
---
Server certificate
subject=/C=US/O=nhin/OU=nhin-carequality/CN=192.168.3.10
issuer=/C=US/ST=Virginia/O=CareQuality LLC/OU=CareQuality LLC Certificate Authority/CN=CareQuality LLC Intermediate CA
---
Acceptable client certificate CA names
/C=US/ST=Virginia/O=CONNECT LLC/OU=CONNECT LLC Certificate Authority/CN=CONNECT LLC Intermediate CA
/C=US/ST=Virginia/O=CareQuality LLC/OU=CareQuality LLC Certificate Authority/CN=CareQuality LLC Intermediate CA
/C=US/ST=Virginia/O=CONNECT LLC/OU=CONNECT LLC Certificate Authority/CN=CONNECT LLC Root CA
/C=US/ST=Virginia/O=CareQuality LLC/OU=CareQuality LLC Certificate Authority/CN=CareQuality LLC Root CA
Client Certificate Types: RSA sign, DSA sign, ECDSA sign
Requested Signature Algorithms: ECDSA+SHA512:RSA+SHA512:ECDSA+SHA384:RSA+SHA384:ECDSA+SHA256:RSA+SHA256:DSA+SHA256:ECDSA+SHA224:RSA+SHA224:DSA+SHA224:ECDSA+SHA1:RSA+SHA1:DSA+SHA1
Shared Requested Signature Algorithms: ECDSA+SHA512:RSA+SHA512:ECDSA+SHA384:RSA+SHA384:ECDSA+SHA256:RSA+SHA256:DSA+SHA256:ECDSA+SHA224:RSA+SHA224:DSA+SHA224:ECDSA+SHA1:RSA+SHA1:DSA+SHA1
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 5719 bytes and written 5283 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES256-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : ECDHE-RSA-AES256-SHA384
    Session-ID: 5C2F872E2601C782CAEE1084E652933A65A3E8188FF71ED61B59CF61951FB1E1
    Session-ID-ctx:
    Master-Key: 5FBF99BDDF58954A6A031B6A805445837698BB01A16A6E3C9F292B9D11CC51154ABC427BC5695ED02C57BD99D9E32C4C
    Key-Arg   : None
    Krb5 Principal: None
    PSK identity: None
    PSK identity hint: None
    Start Time: 1546618670
    Timeout   : 300 (sec)
    Verify return code: 0 (ok)
---

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