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.
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, thene2ee_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 toFalse
, 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 isTrue
, 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.