End-to-end encryption and session verification

RADKit offers support for TLS-based end-to-end encryption (E2EE) between the client and the service. When E2EE is enabled, no data flowing between the client and the service can be inspected by the cloud infrastructure, ensuring privacy and security of the data. On top of this, we support session verification as well, in order to establish mutual trust between a RADKit client and service.

How it works

E2EE works by routing all requests from the client to the service through a TLS connection. This TLS connection is layered on top of our “multiplexing web socket protocol” [1], but stretches all the way from the client to the service. Multiple RPC requests can be multiplexed within one E2EE connection.

The service operates as a TLS server and the client functions as a TLS client, even though on the underlying protocol, they are both a web socket client. This means that the service must present a valid TLS certificate for the client to validate. By default, the service uses a self-signed certificate, but this can be replaced with any other certificate.

../_images/e2ee.svg

Note that the client and service use web socket connections that are encrypted with HTTPS, regardless of whether an E2EE TLS connection is tunneled through the web sockets. This means that when E2EE is enabled, there will be two layers of TLS encryption within each other.

Enabling E2EE on the service

Currently, if an E2EE certificate is not present, the service will automatically generate one during startup and store it in ~/.radkit/service/e2ee.{key,pem}. The key is encrypted using a password stored in ~/.radkit/service/secrets.json, which is itself encrypted with the superadmin password. As a result, E2EE is enabled by default on the service.

A new E2EE certificate can be generated by executing:

radkit-service renew-e2ee-certificate

To store the encryption key in secrets.json, the superadmin password will be asked for.

When the service is started, the SHA-256 fingerprint of this certificate is displayed in the terminal panel and on the connectivity page in the web UI of the service.

To enforce end-to-end encryption, the service.e2ee.require_e2ee setting should be set to true. This is already the default, which means that only the capabilities RPC call can be called without E2EE, but every other RPC call requires end-to-end encryption. Note that on the service-side, we can’t enforce that a radkit-client will verify the fingerprint, however radkit-client is designed so that a fingerprint verification will always be performed when an access token for session verification (see next paragraph) is being used.

Enabling E2EE session verification on the service

To enforce E2EE session verification, the service.e2ee.require_e2ee_session_verification setting should be set to true. Given that radkit-clients will only use E2EE session verification through an end-to-end encrypted connection, this implies that E2EE also becomes required. This setting can be modified through the web interface of the service.

Enabling end-to-end encryption and verification on the client

In the radkit_client, with the default settings, end-to-end encryption (E2EE) will automatically be used when it is available. However, in order for E2EE to be effective, at least, the fingerprint of the certificate presented by the service should be verified.

First, any of the login functions can be used to construct a Client object. At this point, no E2EE-related information should be specified. After this, we can call the service method and pass the necessary information for end-to-end encryption and E2EE session verification.

# Do client login first:
sso_login(...)

# Address the service:
service = service_cloud(
     "<service-id>",
     e2ee_fingerprint="...",
     access_token="...",
)

Both e2ee_fingerprint and access_token are optional parameters, however:

  • In order for E2EE to be effective, we have to verify the fingerprint. So, e2ee_fingerprint needs to be passed. Without this, E2EE will still be used when it is available, but there is no guarantee that that the connection hasn’t been tampered with.

  • The access_token needs to be passed if and only if the service requires end-to-end encryption and session verification. In this case, not passing this token will cause the service to reject the incoming connection.

  • If an access_token is given, then e2ee_fingerprint should also be given. RADKit will refuse to send the access token if the connection is not trusted.

A RADKit client user should obtain the correct values for both the fingerprint and access token from a RADKit service administrator, and they should be communicated securely. The “e2ee_fingerprint” value that is passed should never be obtained by inspecting information within a radkit-client.

When a service rejects a connection, because of an incorrect fingerprint or access token, then an exception will be raised. For retrying, we can call client.service_cloud() again or alternatively, we can call service.reload() with the same parameters on the Service object itself.

service.reload(
     e2ee_fingerprint="...",
     access_token="...",
)

Note

There is a client.use_e2ee_default setting as well. This will be ignored if an E2EE fingerprint has been passed, in that case we always use E2EE. This setting only controls whether or not E2EE should be used in case that no fingerprint was passed for verification.

Inspecting the requests in radkit-client

For every request that the client performs, the E2EE information can be queried. The client.requests property shows the list off all requests for a particular client, and the service.requests property shows all the request for a specific service of that client. If we take any request from that list and access its e2ee attribute, we can view the E2EE information for that individual request.

For instance:

>>> client.requests[5].e2ee
<radkit_client.sync.request.E2EEInformation object at 0xffff82cbbfd0>
---------------------  --------------------------------------------------------------------------
request                AsyncSimpleRequest(status='SUCCESS', rpc_name='ping')
client_id              jslender@cisco.com
service_id             nz0o-urm0-14xc
domain_name            PROD
e2ee_used              True
sha256_hash_hexdigest  7f780...............................
x509_certificate       <Certificate(subject=<Name(CN=end-to-end-encrypted-radkit-service)>, ...)>
---------------------  --------------------------------------------------------------------------

In the above output, we can see the SHA-256 fingerprint of the certificate that was used for the TLS connection. It is important that this fingerprint matches the one displayed on the service side. If if does not, it indicates that there is a third party intercepting the TLS connection and terminating it with their own certificate. This is known as a “man-in-the-middle” attack.

Extra notes

  • When the use_h2_when_available setting in radkit_client is set to False, then a new E2EE session will be started for every RPC call, and a separate E2EE session verification flow will be performed. By default, this setting is True, which means that only one E2EE session needs to be created and verified for a connection to a single service, and all RPC requests will be multiplexed through this secure connection.